Compare commits

..

2 Commits

Author SHA1 Message Date
Trinity
2d9185a5ec fix: send tx with 2 notes 2025-10-22 08:44:24 +07:00
2816e5333b feat: new keygen and importkeys for protocol upgrade 2025-10-21 13:09:07 +07:00
5 changed files with 342 additions and 333 deletions

View File

@ -13,11 +13,12 @@ import (
var (
DomainSeparator = []byte("Nockchain seed")
PrivateKeyStart = []byte{4, 178, 67, 11}
PublicKeyStart = []byte{234, 230, 92}
PrivateKeyStart = []byte{1, 16, 99, 49}
PublicKeyStart = []byte{12, 14, 187, 9}
MagicDyckForPoint = []uint64{0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1}
MagicDyckForT8 = []uint64{0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}
MagicDyckForSeed = []uint64{0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}
MagicDyckForName = []uint64{0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1}
Tip5Zero = [5]uint64{1730770831742798981, 2676322185709933211, 8329210750824781744, 16756092452590401876, 3547445316740171466}
Tip5One = [5]uint64{6727110957294540849, 15606243244732609007, 11887284596344881785, 10646863421881571398, 8146872807338919620}
Tip5ZeroZero = [5]uint64{4372149332062030091, 17876920912185183887, 13348798570422431948, 8872865212694716527, 3385176510443841516}
@ -29,6 +30,13 @@ type MasterKey struct {
ChainCode []byte
}
type KeyType int32
const (
KeyType_PRIVATE KeyType = 0
KeyType_PUBLIC KeyType = 1
)
func (m *MasterKey) DeriveChild(index uint64) (MasterKey, error) {
idxBytes := make([]byte, 4)
binary.BigEndian.PutUint32(idxBytes, uint32(index))
@ -184,18 +192,29 @@ func BigIntToT8(data big.Int) [8]uint64 {
res := rip(data)
return [8]uint64(res)
}
func SerializeExtend(chainCode []byte, key []byte, version []byte) []byte {
data := version
func SerializeExtend(chainCode []byte, key []byte, keyType KeyType) []byte {
data := []byte{}
// dep: depth in chain
// idx: index at depth
// pf: parent fingerprint
// ver: version
ver := 0
depth := 0
idx := []byte{0, 0, 0, 0}
pf := []byte{0, 0, 0, 0}
data = append(data, byte(depth%256))
data = append(data, pf...)
data = append(data, idx...)
data = append(data, chainCode...)
var typ []byte
switch keyType {
case KeyType_PRIVATE:
typ = PrivateKeyStart
case KeyType_PUBLIC:
typ = PublicKeyStart
}
data = append(data, typ...) // 4 bytes
data = append(data, byte(ver)) // 1 byte
data = append(data, byte(depth%256)) // 1 byte
data = append(data, pf...) // 4 bytes
data = append(data, idx...) // 4 bytes
data = append(data, chainCode...) // 32 bytes
data = append(data, key...)
return AddChecksum(data)
}

View File

@ -61,6 +61,24 @@ func HashName(name *nockchain.NockchainName) [5]uint64 {
return crypto.Tip5RehashTenCell(firstNameHash, crypto.Tip5RehashTenCell(lastNameHash, crypto.Tip5Zero))
}
func HashNameVarLen(name *nockchain.NockchainName) [5]uint64 {
belts := []crypto.Belt{}
for _, i := range crypto.Base58ToTip5Hash(name.First) {
belts = append(belts, crypto.Belt{Value: i})
}
for _, i := range crypto.Base58ToTip5Hash(name.Last) {
belts = append(belts, crypto.Belt{Value: i})
}
belts = append(belts, crypto.BELT_ZERO)
size := len(belts)
belts = append([]crypto.Belt{{Value: uint64(size)}}, belts...)
for _, i := range crypto.MagicDyckForName {
belts = append(belts, crypto.Belt{Value: i})
}
res := crypto.Tip5HashBelts(belts)
return res
}
func HashNote(note *nockchain.NockchainNote) ([5]uint64, error) {
versionHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: uint64(note.Version)}})
blockHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: note.BlockHeight}})
@ -225,86 +243,36 @@ func HashSpend(spend *nockchain.NockchainSpend) ([5]uint64, error) {
if err != nil {
return [5]uint64{}, err
}
seedsCount := len(spend.Seeds)
var finalSeedHash [5]uint64
if seedsCount == 1 {
seedHash, err := HashSeedWithoutSource(spend.Seeds[0])
if err != nil {
return [5]uint64{}, err
}
finalSeedHash = crypto.Tip5RehashTenCell(seedHash, crypto.Tip5ZeroZero)
seedsTree := NewZTree(
func(i interface{}) [5]uint64 {
if seed, ok := i.(*nockchain.NockchainSeed); ok {
seedHash, err := HashSeedVarLen(seed)
if err != nil {
return [5]uint64{}
}
return seedHash
} else {
seed1Hash, err := HashSeedWithoutSource(spend.Seeds[0])
if err != nil {
return [5]uint64{}, err
return [5]uint64{}
}
seed1HashVarLen, err := HashSeedVarLen(spend.Seeds[0])
if err != nil {
return [5]uint64{}, err
}
seed1DoubleHash := crypto.Tip5RehashTenCell(seed1HashVarLen, seed1HashVarLen)
seed2Hash, err := HashSeedWithoutSource(spend.Seeds[1])
if err != nil {
return [5]uint64{}, err
}
seed2HashVarLen, err := HashSeedVarLen(spend.Seeds[1])
if err != nil {
return [5]uint64{}, err
}
seed2DoubleHash := crypto.Tip5RehashTenCell(seed2HashVarLen, seed2HashVarLen)
seedHash1BigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(seed1HashVarLen)))
seed1DoubleHashBigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(seed1DoubleHash)))
seedHash2BigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(seed2HashVarLen)))
seed2DoubleHashBigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(seed2DoubleHash)))
if seedHash1BigInt.Cmp(seedHash2BigInt) == -1 {
// seed 1 < seed 2
if seed1DoubleHashBigInt.Cmp(seed2DoubleHashBigInt) == -1 {
// seed1
// / \
// ~ seed2
// / \
// ~ ~
finalSeedHash = crypto.Tip5RehashTenCell(seed2Hash, crypto.Tip5ZeroZero)
finalSeedHash = crypto.Tip5RehashTenCell(crypto.Tip5Zero, finalSeedHash)
finalSeedHash = crypto.Tip5RehashTenCell(seed1Hash, finalSeedHash)
},
func(i interface{}) ([5]uint64, error) {
if seed, ok := i.(*nockchain.NockchainSeed); ok {
hash, err := HashSeedWithoutSource(seed)
return hash, err
} else {
// seed2
// / \
// seed1 ~
// / \
// ~ ~
finalSeedHash = crypto.Tip5RehashTenCell(seed1Hash, crypto.Tip5ZeroZero)
finalSeedHash = crypto.Tip5RehashTenCell(finalSeedHash, crypto.Tip5Zero)
finalSeedHash = crypto.Tip5RehashTenCell(seed2Hash, finalSeedHash)
return [5]uint64{}, fmt.Errorf("invalid input type")
}
},
)
for _, seed := range spend.Seeds {
seedsTree.Insert(seed, seed)
}
finalSeedHash, err := seedsTree.Hash()
if err != nil {
return [5]uint64{}, err
}
} else {
// seed 1 > seed 2
if seed1DoubleHashBigInt.Cmp(seed2DoubleHashBigInt) == -1 {
// seed1
// / \
// seed2 ~
// / \
// ~ ~
finalSeedHash = crypto.Tip5RehashTenCell(seed2Hash, crypto.Tip5ZeroZero)
finalSeedHash = crypto.Tip5RehashTenCell(finalSeedHash, crypto.Tip5Zero)
finalSeedHash = crypto.Tip5RehashTenCell(seed1Hash, finalSeedHash)
} else {
// seed2
// / \
// ~ seed1
// / \
// ~ ~
finalSeedHash = crypto.Tip5RehashTenCell(seed1Hash, crypto.Tip5ZeroZero)
finalSeedHash = crypto.Tip5RehashTenCell(crypto.Tip5Zero, finalSeedHash)
finalSeedHash = crypto.Tip5RehashTenCell(seed2Hash, finalSeedHash)
}
}
}
feeHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: spend.Fee}})
seedHashFee := crypto.Tip5RehashTenCell(finalSeedHash, feeHash)
return crypto.Tip5RehashTenCell(sigHash, seedHashFee), nil
@ -312,84 +280,33 @@ func HashSpend(spend *nockchain.NockchainSpend) ([5]uint64, error) {
}
func HashMsg(spend *nockchain.NockchainSpend) ([5]uint64, error) {
seedsCount := len(spend.Seeds)
var finalSeedHash [5]uint64
if seedsCount == 1 {
seedHash, err := HashSeed(spend.Seeds[0])
seedsTree := NewZTree(
func(i interface{}) [5]uint64 {
if seed, ok := i.(*nockchain.NockchainSeed); ok {
seedHash, err := HashSeedVarLen(seed)
if err != nil {
return [5]uint64{}, err
return [5]uint64{}
}
finalSeedHash = crypto.Tip5RehashTenCell(seedHash, crypto.Tip5ZeroZero)
return seedHash
} else {
seed1HashVarLen, err := HashSeedVarLen(spend.Seeds[0])
if err != nil {
return [5]uint64{}, err
return [5]uint64{}
}
seed1Hash, err := HashSeed(spend.Seeds[0])
if err != nil {
return [5]uint64{}, err
}
seed1DoubleHash := crypto.Tip5RehashTenCell(seed1HashVarLen, seed1HashVarLen)
seed2HashVarLen, err := HashSeedVarLen(spend.Seeds[1])
if err != nil {
return [5]uint64{}, err
}
seed2Hash, err := HashSeed(spend.Seeds[1])
if err != nil {
return [5]uint64{}, err
}
seed2DoubleHash := crypto.Tip5RehashTenCell(seed2HashVarLen, seed2HashVarLen)
seedHash1BigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(seed1HashVarLen)))
seed1DoubleHashBigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(seed1DoubleHash)))
seedHash2BigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(seed2HashVarLen)))
seed2DoubleHashBigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(seed2DoubleHash)))
if seedHash1BigInt.Cmp(seedHash2BigInt) == -1 {
// seed 1 < seed 2
if seed1DoubleHashBigInt.Cmp(seed2DoubleHashBigInt) == -1 {
// seed1
// / \
// ~ seed2
// / \
// ~ ~
finalSeedHash = crypto.Tip5RehashTenCell(seed2Hash, crypto.Tip5ZeroZero)
finalSeedHash = crypto.Tip5RehashTenCell(crypto.Tip5Zero, finalSeedHash)
finalSeedHash = crypto.Tip5RehashTenCell(seed1Hash, finalSeedHash)
},
func(i interface{}) ([5]uint64, error) {
if seed, ok := i.(*nockchain.NockchainSeed); ok {
hash, err := HashSeed(seed)
return hash, err
} else {
// seed2
// / \
// seed1 ~
// / \
// ~ ~
finalSeedHash = crypto.Tip5RehashTenCell(seed1Hash, crypto.Tip5ZeroZero)
finalSeedHash = crypto.Tip5RehashTenCell(finalSeedHash, crypto.Tip5Zero)
finalSeedHash = crypto.Tip5RehashTenCell(seed2Hash, finalSeedHash)
}
} else {
// seed 1 > seed 2
if seed1DoubleHashBigInt.Cmp(seed2DoubleHashBigInt) == -1 {
// seed1
// / \
// seed2 ~
// / \
// ~ ~
finalSeedHash = crypto.Tip5RehashTenCell(seed2Hash, crypto.Tip5ZeroZero)
finalSeedHash = crypto.Tip5RehashTenCell(finalSeedHash, crypto.Tip5Zero)
finalSeedHash = crypto.Tip5RehashTenCell(seed1Hash, finalSeedHash)
} else {
// seed2
// / \
// ~ seed1
// / \
// ~ ~
finalSeedHash = crypto.Tip5RehashTenCell(seed1Hash, crypto.Tip5ZeroZero)
finalSeedHash = crypto.Tip5RehashTenCell(crypto.Tip5Zero, finalSeedHash)
finalSeedHash = crypto.Tip5RehashTenCell(seed2Hash, finalSeedHash)
return [5]uint64{}, fmt.Errorf("invalid input type")
}
},
)
for _, seed := range spend.Seeds {
seedsTree.Insert(seed, seed)
}
finalSeedHash, err := seedsTree.Hash()
if err != nil {
return [5]uint64{}, err
}
feeHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: spend.Fee}})
return crypto.Tip5RehashTenCell(finalSeedHash, feeHash), nil
@ -417,7 +334,7 @@ func ComputeTxId(inputs []*nockchain.NockchainInput, timelockRange *nockchain.Ti
inputTree := NewZTree(
func(i interface{}) [5]uint64 {
if name, ok := i.(*nockchain.NockchainName); ok {
return HashName(name)
return HashNameVarLen(name)
} else {
return [5]uint64{}
}

View File

@ -54,8 +54,8 @@ func (h *GprcHandler) Keygen(ctx context.Context, req *nockchain.KeygenRequest)
PrivateKey: base58.Encode(masterKey.PrivateKey),
PublicKey: base58.Encode(masterKey.PublicKey),
ChainCode: base58.Encode(masterKey.ChainCode),
ImportPrivateKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, privBytes, crypto.PrivateKeyStart)),
ImportPublicKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, masterKey.PublicKey, crypto.PublicKeyStart)),
ImportPrivateKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, privBytes, crypto.KeyType_PRIVATE)),
ImportPublicKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, masterKey.PublicKey, crypto.KeyType_PUBLIC)),
}, nil
}
@ -68,21 +68,21 @@ func (h *GprcHandler) ImportKeys(ctx context.Context, req *nockchain.ImportKeysR
data := base58.Decode(req.Key)
switch {
case strings.HasPrefix(req.Key, "zprv"):
if len(data) != 82 {
return nil, fmt.Errorf("invalid extended private key length: %d (expected 82)", len(data))
if len(data) != 83 {
return nil, fmt.Errorf("invalid extended private key length: %d (expected 83)", len(data))
}
if data[45] != 0x00 {
return nil, fmt.Errorf("invalid private key prefix at byte 45: 0x%02x (expected 0x00)", data[45])
if data[46] != 0x00 {
return nil, fmt.Errorf("invalid private key prefix at byte 46: 0x%02x (expected 0x00)", data[46])
}
hash := sha256.Sum256(data[:78])
hash := sha256.Sum256(data[:79])
hash = sha256.Sum256(hash[:])
if !slices.Equal(hash[:4], data[78:]) {
if !slices.Equal(hash[:4], data[79:]) {
return nil, fmt.Errorf("invalid checksum")
}
chainCode := make([]byte, 32)
copy(chainCode, data[13:45])
copy(chainCode, data[14:46])
privateKey := make([]byte, 32)
copy(privateKey, data[46:78])
copy(privateKey, data[47:79])
masterKey, err := crypto.MasterKeyFromPrivKey(chainCode, privateKey)
if err != nil {
return nil, err
@ -93,31 +93,31 @@ func (h *GprcHandler) ImportKeys(ctx context.Context, req *nockchain.ImportKeysR
PrivateKey: base58.Encode(masterKey.PrivateKey),
PublicKey: base58.Encode(masterKey.PublicKey),
ChainCode: base58.Encode(masterKey.ChainCode),
ImportPrivateKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, privBytes, crypto.PrivateKeyStart)),
ImportPublicKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, masterKey.PublicKey, crypto.PublicKeyStart)),
ImportPrivateKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, privBytes, crypto.KeyType_PRIVATE)),
ImportPublicKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, masterKey.PublicKey, crypto.KeyType_PUBLIC)),
}, nil
case strings.HasPrefix(req.Key, "zpub"):
if len(data) != 145 {
return nil, fmt.Errorf("invalid extended public key length: %d (expected 145)", len(data))
if len(data) != 147 {
return nil, fmt.Errorf("invalid extended public key length: %d (expected 147)", len(data))
}
hash := sha256.Sum256(data[:141])
hash := sha256.Sum256(data[:143])
hash = sha256.Sum256(hash[:])
if !slices.Equal(hash[:4], data[141:]) {
if !slices.Equal(hash[:4], data[143:]) {
return nil, fmt.Errorf("invalid checksum")
}
chainCode := make([]byte, 32)
copy(chainCode, data[12:44])
copy(chainCode, data[14:46])
publicKey := make([]byte, 97)
copy(publicKey, data[44:141])
copy(publicKey, data[46:143])
return &nockchain.ImportKeysResponse{
Seed: "",
PrivateKey: "",
PublicKey: base58.Encode(publicKey),
ChainCode: base58.Encode(chainCode),
ImportPrivateKey: "",
ImportPublicKey: base58.Encode(crypto.SerializeExtend(chainCode, publicKey, crypto.PublicKeyStart)),
ImportPublicKey: base58.Encode(crypto.SerializeExtend(chainCode, publicKey, crypto.KeyType_PUBLIC)),
}, nil
default:
return nil, fmt.Errorf("invalid extended key")
@ -145,8 +145,8 @@ func (h *GprcHandler) ImportKeys(ctx context.Context, req *nockchain.ImportKeysR
PrivateKey: base58.Encode(masterKey.PrivateKey),
PublicKey: base58.Encode(masterKey.PublicKey),
ChainCode: base58.Encode(masterKey.ChainCode),
ImportPrivateKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, privBytes, crypto.PrivateKeyStart)),
ImportPublicKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, masterKey.PublicKey, crypto.PublicKeyStart)),
ImportPrivateKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, privBytes, crypto.KeyType_PRIVATE)),
ImportPublicKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, masterKey.PublicKey, crypto.KeyType_PUBLIC)),
}, nil
case nockchain.ImportType_SEEDPHRASE:
masterKey, err := crypto.MasterKeyFromSeed(req.Key)
@ -159,8 +159,8 @@ func (h *GprcHandler) ImportKeys(ctx context.Context, req *nockchain.ImportKeysR
PrivateKey: base58.Encode(masterKey.PrivateKey),
PublicKey: base58.Encode(masterKey.PublicKey),
ChainCode: base58.Encode(masterKey.ChainCode),
ImportPrivateKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, privBytes, crypto.PrivateKeyStart)),
ImportPublicKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, masterKey.PublicKey, crypto.PublicKeyStart)),
ImportPrivateKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, privBytes, crypto.KeyType_PRIVATE)),
ImportPublicKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, masterKey.PublicKey, crypto.KeyType_PUBLIC)),
}, nil
case nockchain.ImportType_WATCH_ONLY:
pubKey := base58.Decode(req.Key)
@ -188,21 +188,21 @@ func (h *GprcHandler) DeriveChild(ctx context.Context, req *nockchain.DeriveChil
}
switch {
case strings.HasPrefix(req.ImportedKey, "zprv"):
if len(data) != 82 {
return nil, fmt.Errorf("invalid extended private key length: %d (expected 82)", len(data))
if len(data) != 83 {
return nil, fmt.Errorf("invalid extended private key length: %d (expected 83)", len(data))
}
if data[45] != 0x00 {
return nil, fmt.Errorf("invalid private key prefix at byte 45: 0x%02x (expected 0x00)", data[45])
if data[46] != 0x00 {
return nil, fmt.Errorf("invalid private key prefix at byte 46: 0x%02x (expected 0x00)", data[46])
}
hash := sha256.Sum256(data[:78])
hash := sha256.Sum256(data[:79])
hash = sha256.Sum256(hash[:])
if !slices.Equal(hash[:4], data[78:]) {
if !slices.Equal(hash[:4], data[79:]) {
return nil, fmt.Errorf("invalid checksum")
}
chainCode := make([]byte, 32)
copy(chainCode, data[13:45])
copy(chainCode, data[14:46])
privateKey := make([]byte, 32)
copy(privateKey, data[46:78])
copy(privateKey, data[47:79])
masterKey, err := crypto.MasterKeyFromPrivKey(chainCode, privateKey)
if err != nil {
return nil, err
@ -219,20 +219,20 @@ func (h *GprcHandler) DeriveChild(ctx context.Context, req *nockchain.DeriveChil
}, nil
case strings.HasPrefix(req.ImportedKey, "zpub"):
if len(data) != 145 {
if len(data) != 147 {
return nil, fmt.Errorf("invalid extended public key length: %d (expected 145)", len(data))
}
hash := sha256.Sum256(data[:141])
hash := sha256.Sum256(data[:143])
hash = sha256.Sum256(hash[:])
if !slices.Equal(hash[:4], data[141:]) {
if !slices.Equal(hash[:4], data[143:]) {
return nil, fmt.Errorf("invalid checksum")
}
chainCode := make([]byte, 32)
copy(chainCode, data[13:45])
copy(chainCode, data[14:46])
publicKey := make([]byte, 97)
copy(publicKey, data[45:141])
copy(publicKey, data[46:143])
masterKey := crypto.MasterKey{
PublicKey: publicKey,
@ -287,7 +287,7 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque
nameTree := NewZTree(
func(i interface{}) [5]uint64 {
if name, ok := i.(*nockchain.NockchainName); ok {
return HashName(name)
return HashNameVarLen(name)
} else {
return [5]uint64{}
}
@ -297,16 +297,18 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque
for _, name := range nnames {
nameTree.Insert(name, nil)
}
nameLeftMost := nameTree.KeyLeftMost().(*nockchain.NockchainName)
idxLeftMost := -1
nameList := nameTree.Tap()
nameKeys := []string{}
for _, nameKey := range nameList {
name := nameKey.Key.(*nockchain.NockchainName)
nameKeys = append(nameKeys, name.First+" "+name.Last)
}
indices := make([]int, len(nnames))
for i, name := range nnames {
if name.First == nameLeftMost.First && name.Last == nameLeftMost.Last {
idxLeftMost = i
if idx := slices.Index(nameKeys, name.First+" "+name.Last); idx != -1 {
indices[idx] = i
}
}
if idxLeftMost == -1 {
return nil, fmt.Errorf("unable to find left most node")
}
recipents := []*nockchain.NockchainLock{}
if strings.Contains(req.Recipients, "[") {
@ -411,40 +413,40 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque
}
}
inputs := []*nockchain.NockchainInput{}
for i := 0; i < len(names); i++ {
if notes[i] == nil {
return nil, fmt.Errorf("notes scanned is missing at name: %s", names[i])
inputs := make([]*nockchain.NockchainInput, len(names))
isSpent := false
for i := 0; i < len(nameKeys); i++ {
idx := indices[i]
if notes[idx] == nil {
return nil, fmt.Errorf("notes scanned is missing at name: %s", names[idx])
}
if notes[i].Asset < gifts[i]+req.Fee {
return nil, fmt.Errorf("note not enough balance")
if notes[idx].Asset < gifts[idx] {
return nil, fmt.Errorf("note %s not enough balance", names[idx])
}
parentHash, err := HashNote(notes[i])
parentHash, err := HashNote(notes[idx])
if err != nil {
return nil, err
}
seeds := []*nockchain.NockchainSeed{
{
OutputSource: nil,
Recipient: recipents[i],
Recipient: recipents[idx],
TimelockIntent: req.TimelockIntent,
Gift: gifts[i],
Gift: gifts[idx],
ParentHash: crypto.Tip5HashToBase58(parentHash),
},
}
if notes[i].Asset < gifts[i] {
return nil, fmt.Errorf("insufficient funds for notes %s", names[i])
}
assetLeft := notes[i].Asset - gifts[i]
if i == idxLeftMost {
if assetLeft > req.Fee {
assetLeft := notes[idx].Asset - gifts[idx]
fee := uint64(0)
if !isSpent && assetLeft >= req.Fee {
isSpent = true
fee = req.Fee
assetLeft -= req.Fee
} else {
return nil, fmt.Errorf("insufficient funds for notes %s", names[i])
}
}
if assetLeft != 0 {
seeds = append(seeds, &nockchain.NockchainSeed{
OutputSource: nil,
@ -457,19 +459,10 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque
ParentHash: crypto.Tip5HashToBase58(parentHash),
})
}
var spend nockchain.NockchainSpend
if i == idxLeftMost {
spend = nockchain.NockchainSpend{
spend := nockchain.NockchainSpend{
Signatures: nil,
Seeds: seeds,
Fee: req.Fee,
}
} else {
spend = nockchain.NockchainSpend{
Signatures: nil,
Seeds: seeds,
Fee: 0,
}
Fee: fee,
}
msg, err := HashMsg(&spend)
@ -492,13 +485,16 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque
}
input := nockchain.NockchainInput{
Name: nnames[i],
Note: notes[i],
Name: nnames[idx],
Note: notes[idx],
Spend: &spend,
}
inputs = append(inputs, &input)
inputs[idx] = &input
}
if !isSpent {
return nil, fmt.Errorf("insufficient funds")
}
var timelockRange *nockchain.TimelockRange
if req.TimelockIntent == nil {
timelockRange = &nockchain.TimelockRange{

View File

@ -17,13 +17,13 @@ import (
// The entropy, salt and result is taken from "nockchain-wallet keygen" command
func TestKeyGen(t *testing.T) {
entropyBigInt, isOk := new(big.Int).SetString("29615235796517918707367078072007441124337225858809749976291970867443501879006", 10)
entropyBigInt, isOk := new(big.Int).SetString("37133536588676344913489312523941366110857274548479981512263368615793750653450", 10)
assert.True(t, isOk)
entropy := entropyBigInt.Bytes()
assert.Len(t, entropy, 32)
saltBigInt, isOk := new(big.Int).SetString("212311808188922973323281316240858086116", 10)
saltBigInt, isOk := new(big.Int).SetString("251632902249061493058993135304695174381", 10)
assert.True(t, isOk)
salt := saltBigInt.Bytes()
@ -33,38 +33,38 @@ func TestKeyGen(t *testing.T) {
slices.Reverse(argonBytes)
mnemonic, err := bip39.NewMnemonic(argonBytes)
assert.NoError(t, err)
assert.Equal(t, mnemonic, "brass vacuum stairs hurt brisk govern describe enforce fly exact rescue capable belt flavor lottery sauce easy frame orange legal injury border obey novel")
assert.Equal(t, mnemonic, "include lounge salad chicken trumpet embrace grace mercy pulp submit alter weapon plastic welcome funny female palm satoshi open file knock sun fade match")
masterKey, err := crypto.MasterKeyFromSeed(mnemonic)
assert.NoError(t, err)
assert.Equal(t,
base58.Encode(masterKey.PublicKey),
"39DL6YA1kSRCKMjzpFEtC8rmnxVuseUrP2LnViwY7YEhZYZkX2HmnAZ63Uwy1DwuXstmF1VeJDucg719xw49j9CKL3bsKq3A6SZN918CowcgQroHsgohj7dYgpGRWk41s42F",
"34VqjU7ojQXWiFZz7kvXe1xfxhbdimmqqUAgp21XGESLqJSXxHkqrcquWirFcCPPj1pmSL4pRc8GndZoiiK8ijeYjgcJ3QR7fb2s4b2WdJhDao4Dx7gw3NRSt4RjXawqUQw6",
)
assert.Equal(t,
base58.Encode(masterKey.PrivateKey),
"4SyUrsbGKPRknzvGakWmFbYefzHzb1r4LUmJpQD8WPcR",
"3B8Q5ZTHH63h9DT6WSwNZhea5zvtueuKpxk3qwZJEjsg",
)
assert.Equal(t,
base58.Encode(masterKey.ChainCode),
"58SARPmADHvUcpq7XfBoCgwzy5QC8Kb3JrezpHqA85x2",
"2ztGPxS8xYzMXoAHf3HMbMuyh4siew8X4Kz4KuTXvqX8",
)
// assert import priv/pubkey
privBytes := append([]byte{0x00}, masterKey.PrivateKey...)
importPrivKey := crypto.SerializeExtend(masterKey.ChainCode, privBytes, crypto.PrivateKeyStart)
assert.Len(t, importPrivKey, 82)
importPubKey := crypto.SerializeExtend(masterKey.ChainCode, masterKey.PublicKey, crypto.PublicKeyStart)
assert.Len(t, importPubKey, 145)
importPrivKey := crypto.SerializeExtend(masterKey.ChainCode, privBytes, crypto.KeyType_PRIVATE)
assert.Len(t, importPrivKey, 83)
importPubKey := crypto.SerializeExtend(masterKey.ChainCode, masterKey.PublicKey, crypto.KeyType_PUBLIC)
assert.Len(t, importPubKey, 147)
assert.Equal(t,
base58.Encode(importPrivKey),
"zprv2CyrSHEkzQzu4HCtJRFiP4t2rVMauZwLfJDFrNbqS8Pz3nsmXy5bAUx2HYUykaMuU4MiQTHsDcKYjLCjrPfpceNT9XBHgx1pUjKzBrF6Wdo",
"zprvLpf3WSvYWmHRd3jj5oR8UXr7bi88pGnfJXj1dM9RnwJwu1MLo6fZzcKauqpxL2W7dk2fmjYKAUzavbyaGpnvqY5QndcrUzsBrPQAHXNnhiXx",
)
assert.Equal(t,
base58.Encode(importPubKey),
"zpubUQwNTNE3hsCkMpBnD37W5QirkyVryokAVPLnPin1c6M13RRsq3yEJbwp5ies6qXF6DvJq5Woxw6ygT53PSVrmrsQgtHhbfMEixKNFm7qb4mELhpyoovpFEV1YPHFZx4xQGYBNF6qvXU6AHNh4TLrUdkYAdXKS2J5rPiSVPrXKGo8fLG6ZBCGBjJfPcwDb2VEJC",
"zpub2jgndknkQprVYB4X4mqREyn7ZTUE5zp9qkSugdpiqhC5NSeNBceafoz6jGSLEpzJhaLryvY8MF6TokwZN627UXhsg5zd2U12woEL82UtZaLHRL8PZi8YiQnE41BiNJwkfpWjzAbq8mwbAHV3nXUEFgJW2BKCz4GmfoMCkCUDhJppYh5KFsMUkN41DzsfFZHuFrzo",
)
}
@ -76,32 +76,32 @@ func TestImportKey(t *testing.T) {
errStr string
}
correctImportPrivKey := base58.Decode("zprv2CyrSHEkzQzu4HCtJRFiP4t2rVMauZwLfJDFrNbqS8Pz3nsmXy5bAUx2HYUykaMuU4MiQTHsDcKYjLCjrPfpceNT9XBHgx1pUjKzBrF6Wdo")
invalidImportPrivKeyPrefix := make([]byte, 82)
correctImportPrivKey := base58.Decode("zprvLpf3WSvYWmHRd3jj5oR8UXr7bi88pGnfJXj1dM9RnwJwu1MLo6fZzcKauqpxL2W7dk2fmjYKAUzavbyaGpnvqY5QndcrUzsBrPQAHXNnhiXx")
invalidImportPrivKeyPrefix := make([]byte, 83)
copy(invalidImportPrivKeyPrefix[:], correctImportPrivKey)
invalidImportPrivKeyPrefix[45] = 0x01
invalidImportPrivKeyPrefix[46] = 0x01
invalidImportPrivKeyChecksum := make([]byte, 82)
invalidImportPrivKeyChecksum := make([]byte, 83)
copy(invalidImportPrivKeyChecksum[:], correctImportPrivKey)
copy(invalidImportPrivKeyChecksum[78:], []byte{1, 2, 3, 4})
copy(invalidImportPrivKeyChecksum[79:], []byte{1, 2, 3, 4})
correctImportPubkey := base58.Decode("zpubUQwNTNE3hsCkMpBnD37W5QirkyVryokAVPLnPin1c6M13RRsq3yEJbwp5ies6qXF6DvJq5Woxw6ygT53PSVrmrsQgtHhbfMEixKNFm7qb4mELhpyoovpFEV1YPHFZx4xQGYBNF6qvXU6AHNh4TLrUdkYAdXKS2J5rPiSVPrXKGo8fLG6ZBCGBjJfPcwDb2VEJC")
invalidImportPubkeyChecksum := make([]byte, 145)
correctImportPubkey := base58.Decode("zpub2jgndknkQprVYB4X4mqREyn7ZTUE5zp9qkSugdpiqhC5NSeNBceafoz6jGSLEpzJhaLryvY8MF6TokwZN627UXhsg5zd2U12woEL82UtZaLHRL8PZi8YiQnE41BiNJwkfpWjzAbq8mwbAHV3nXUEFgJW2BKCz4GmfoMCkCUDhJppYh5KFsMUkN41DzsfFZHuFrzo")
invalidImportPubkeyChecksum := make([]byte, 147)
copy(invalidImportPubkeyChecksum[:], correctImportPubkey)
copy(invalidImportPubkeyChecksum[141:], []byte{1, 2, 3, 4})
copy(invalidImportPubkeyChecksum[143:], []byte{1, 2, 3, 4})
response := &nockchain.ImportKeysResponse{
PublicKey: "39DL6YA1kSRCKMjzpFEtC8rmnxVuseUrP2LnViwY7YEhZYZkX2HmnAZ63Uwy1DwuXstmF1VeJDucg719xw49j9CKL3bsKq3A6SZN918CowcgQroHsgohj7dYgpGRWk41s42F",
PrivateKey: "4SyUrsbGKPRknzvGakWmFbYefzHzb1r4LUmJpQD8WPcR",
ChainCode: "58SARPmADHvUcpq7XfBoCgwzy5QC8Kb3JrezpHqA85x2",
PublicKey: "34VqjU7ojQXWiFZz7kvXe1xfxhbdimmqqUAgp21XGESLqJSXxHkqrcquWirFcCPPj1pmSL4pRc8GndZoiiK8ijeYjgcJ3QR7fb2s4b2WdJhDao4Dx7gw3NRSt4RjXawqUQw6",
PrivateKey: "3B8Q5ZTHH63h9DT6WSwNZhea5zvtueuKpxk3qwZJEjsg",
ChainCode: "2ztGPxS8xYzMXoAHf3HMbMuyh4siew8X4Kz4KuTXvqX8",
ImportPrivateKey: base58.Encode(correctImportPrivKey),
ImportPublicKey: base58.Encode(correctImportPubkey),
}
responseReadOnly := &nockchain.ImportKeysResponse{
PublicKey: "39DL6YA1kSRCKMjzpFEtC8rmnxVuseUrP2LnViwY7YEhZYZkX2HmnAZ63Uwy1DwuXstmF1VeJDucg719xw49j9CKL3bsKq3A6SZN918CowcgQroHsgohj7dYgpGRWk41s42F",
PublicKey: "34VqjU7ojQXWiFZz7kvXe1xfxhbdimmqqUAgp21XGESLqJSXxHkqrcquWirFcCPPj1pmSL4pRc8GndZoiiK8ijeYjgcJ3QR7fb2s4b2WdJhDao4Dx7gw3NRSt4RjXawqUQw6",
PrivateKey: "",
ChainCode: "58SARPmADHvUcpq7XfBoCgwzy5QC8Kb3JrezpHqA85x2",
ChainCode: "2ztGPxS8xYzMXoAHf3HMbMuyh4siew8X4Kz4KuTXvqX8",
ImportPrivateKey: "",
ImportPublicKey: base58.Encode(correctImportPubkey),
}
@ -142,7 +142,7 @@ func TestImportKey(t *testing.T) {
},
expectResp: nil,
isErr: true,
errStr: "invalid private key prefix at byte 45",
errStr: "invalid private key prefix at byte 46",
},
{
req: &nockchain.ImportKeysRequest{
@ -195,7 +195,7 @@ func TestImportKey(t *testing.T) {
// case missing chaincode when import master privkey
{
req: &nockchain.ImportKeysRequest{
Key: "4SyUrsbGKPRknzvGakWmFbYefzHzb1r4LUmJpQD8WPcR",
Key: "3B8Q5ZTHH63h9DT6WSwNZhea5zvtueuKpxk3qwZJEjsg",
ImportType: nockchain.ImportType_MASTER_PRIVKEY,
},
expectResp: nil,
@ -205,7 +205,7 @@ func TestImportKey(t *testing.T) {
// case invalid length
{
req: &nockchain.ImportKeysRequest{
Key: "abcdxyz,4SyUrsbGKPRknzvGakWmFbYefzHzb1r4LUmJpQD8WPcR",
Key: "abcdxyz,3B8Q5ZTHH63h9DT6WSwNZhea5zvtueuKpxk3qwZJEjsg",
ImportType: nockchain.ImportType_MASTER_PRIVKEY,
},
expectResp: nil,
@ -214,7 +214,7 @@ func TestImportKey(t *testing.T) {
},
{
req: &nockchain.ImportKeysRequest{
Key: "58SARPmADHvUcpq7XfBoCgwzy5QC8Kb3JrezpHqA85x2,abcdxyz",
Key: "2ztGPxS8xYzMXoAHf3HMbMuyh4siew8X4Kz4KuTXvqX8,abcdxyz",
ImportType: nockchain.ImportType_MASTER_PRIVKEY,
},
expectResp: nil,
@ -224,7 +224,7 @@ func TestImportKey(t *testing.T) {
// case success import master privkey
{
req: &nockchain.ImportKeysRequest{
Key: "58SARPmADHvUcpq7XfBoCgwzy5QC8Kb3JrezpHqA85x2,4SyUrsbGKPRknzvGakWmFbYefzHzb1r4LUmJpQD8WPcR",
Key: "2ztGPxS8xYzMXoAHf3HMbMuyh4siew8X4Kz4KuTXvqX8,3B8Q5ZTHH63h9DT6WSwNZhea5zvtueuKpxk3qwZJEjsg",
ImportType: nockchain.ImportType_MASTER_PRIVKEY,
},
expectResp: response,
@ -234,7 +234,7 @@ func TestImportKey(t *testing.T) {
// case success import seed
{
req: &nockchain.ImportKeysRequest{
Key: "brass vacuum stairs hurt brisk govern describe enforce fly exact rescue capable belt flavor lottery sauce easy frame orange legal injury border obey novel",
Key: "include lounge salad chicken trumpet embrace grace mercy pulp submit alter weapon plastic welcome funny female palm satoshi open file knock sun fade match",
ImportType: nockchain.ImportType_SEEDPHRASE,
},
expectResp: response,
@ -244,11 +244,11 @@ func TestImportKey(t *testing.T) {
// case sucess import pubkey
{
req: &nockchain.ImportKeysRequest{
Key: "39DL6YA1kSRCKMjzpFEtC8rmnxVuseUrP2LnViwY7YEhZYZkX2HmnAZ63Uwy1DwuXstmF1VeJDucg719xw49j9CKL3bsKq3A6SZN918CowcgQroHsgohj7dYgpGRWk41s42F",
Key: "34VqjU7ojQXWiFZz7kvXe1xfxhbdimmqqUAgp21XGESLqJSXxHkqrcquWirFcCPPj1pmSL4pRc8GndZoiiK8ijeYjgcJ3QR7fb2s4b2WdJhDao4Dx7gw3NRSt4RjXawqUQw6",
ImportType: nockchain.ImportType_WATCH_ONLY,
},
expectResp: &nockchain.ImportKeysResponse{
PublicKey: "39DL6YA1kSRCKMjzpFEtC8rmnxVuseUrP2LnViwY7YEhZYZkX2HmnAZ63Uwy1DwuXstmF1VeJDucg719xw49j9CKL3bsKq3A6SZN918CowcgQroHsgohj7dYgpGRWk41s42F",
PublicKey: "34VqjU7ojQXWiFZz7kvXe1xfxhbdimmqqUAgp21XGESLqJSXxHkqrcquWirFcCPPj1pmSL4pRc8GndZoiiK8ijeYjgcJ3QR7fb2s4b2WdJhDao4Dx7gw3NRSt4RjXawqUQw6",
},
isErr: false,
errStr: "",
@ -472,21 +472,5 @@ func TestCreateTx(t *testing.T) {
assert.NoError(t, err)
// the result is taken from create-tx scripts
//assert.Equal(t, res.RawTx.TxId, "8gjTa6H1MrKXPWgNJCF9fsYE7PUfqhYzxetUoTftz7zyHCv24ZYHDM3")
_, err = nc.WalletSendTransaction(res.RawTx)
if err != nil {
panic(err)
}
txAccepted, err := nc.TxAccepted(res.RawTx.TxId)
if err != nil {
panic(err)
}
fmt.Println("tx pass: ", res.RawTx.TxId)
if !txAccepted.GetAccepted() {
panic("tx not accepted")
}
// expected: A8vSeRde61B4sZccSnNPEnkQgTe15EssoFwyhQXbkhtk4UNm5hyGSid
assert.Equal(t, res.RawTx.TxId, "A8vSeRde61B4sZccSnNPEnkQgTe15EssoFwyhQXbkhtk4UNm5hyGSid")
}

View File

@ -1,8 +1,10 @@
package wallet
import (
"math/big"
"slices"
"github.com/btcsuite/btcd/btcutil/base58"
"github.com/phamminh0811/private-grpc/crypto"
)
@ -20,6 +22,11 @@ type ZTree struct {
HashValueFunc func(interface{}) ([5]uint64, error)
}
type ZPair struct {
Key interface{}
Value interface{}
}
func NewZTree(hashKeyFunc func(interface{}) [5]uint64, hashValueFunc func(interface{}) ([5]uint64, error)) *ZTree {
return &ZTree{
Root: nil,
@ -36,13 +43,13 @@ func (z *ZTree) Hash() ([5]uint64, error) {
return z.Root.HashNode(z.HashValueFunc)
}
func (z *ZTree) KeyLeftMost() interface{} {
node := z.Root
for node.Left != nil {
node = node.Left
}
return node.Key
func (t *ZTree) Tap() []ZPair {
tap := []ZPair{}
TapNode(t.Root, &tap)
slices.Reverse(tap)
return tap
}
func (node *ZNode) InsertNode(hashFunc func(interface{}) [5]uint64, key, value interface{}) *ZNode {
if node == nil {
node = &ZNode{
@ -53,42 +60,18 @@ func (node *ZNode) InsertNode(hashFunc func(interface{}) [5]uint64, key, value i
}
return node
}
keyHash := hashFunc(key)
keyDoubleHash := crypto.Tip5RehashTenCell(keyHash, keyHash)
nodeKeyHash := hashFunc(node.Key)
nodeKeyDoubleHash := crypto.Tip5RehashTenCell(nodeKeyHash, nodeKeyHash)
if slices.Compare(keyHash[:], nodeKeyHash[:]) == -1 {
keyHashBigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(keyHash)))
nodeKeyHashBigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(nodeKeyHash)))
keyDoubleHashBigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(keyDoubleHash)))
nodeKeyDoubleHashBigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(nodeKeyDoubleHash)))
if keyHashBigInt.Cmp(nodeKeyHashBigInt) == 1 {
// key < node key
if slices.Compare(keyDoubleHash[:], nodeKeyDoubleHash[:]) == -1 {
// reinsert in left
node.Left = node.Left.InsertNode(hashFunc, key, value)
} else {
// new key
// / \
// ~ old key
// / \
// ... ...
nodeKey := node.Key
nodeValue := node.Value
leftNode := node.Left
rightNode := node.Right
node = &ZNode{
Key: key,
Value: value,
Right: nil,
Left: &ZNode{
Key: nodeKey,
Value: nodeValue,
Left: leftNode,
Right: rightNode,
},
}
}
} else {
// key > node key
if slices.Compare(keyDoubleHash[:], nodeKeyDoubleHash[:]) == -1 {
if keyDoubleHashBigInt.Cmp(nodeKeyDoubleHashBigInt) == 1 {
// reinsert in right
node.Right = node.Right.InsertNode(hashFunc, key, value)
} else {
@ -101,6 +84,92 @@ func (node *ZNode) InsertNode(hashFunc func(interface{}) [5]uint64, key, value i
nodeValue := node.Value
leftNode := node.Left
rightNode := node.Right
if rightNode == nil {
node = &ZNode{
Key: key,
Value: value,
Right: nil,
Left: &ZNode{
Key: nodeKey,
Value: nodeValue,
Left: leftNode,
Right: nil,
},
}
} else {
rightNodeKeyHash := hashFunc(rightNode.Key)
rightNodeKeyHashBigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(rightNodeKeyHash)))
if rightNodeKeyHashBigInt.Cmp(keyHashBigInt) == -1 {
node = &ZNode{
Key: key,
Value: value,
Right: nil,
Left: &ZNode{
Key: nodeKey,
Value: nodeValue,
Left: leftNode,
Right: rightNode,
},
}
} else {
node = &ZNode{
Key: key,
Value: value,
Right: rightNode,
Left: &ZNode{
Key: nodeKey,
Value: nodeValue,
Left: leftNode,
Right: nil,
},
}
}
}
}
} else {
// key > node key
if keyDoubleHashBigInt.Cmp(nodeKeyDoubleHashBigInt) == 1 {
// reinsert in left
node.Left = node.Left.InsertNode(hashFunc, key, value)
} else {
// new key
// / \
// ~ old key
// / \
// ... ...
nodeKey := node.Key
nodeValue := node.Value
leftNode := node.Left
rightNode := node.Right
if leftNode == nil {
node = &ZNode{
Key: key,
Value: value,
Right: &ZNode{
Key: nodeKey,
Value: nodeValue,
Left: nil,
Right: rightNode,
},
Left: nil,
}
} else {
leftNodeKeyHash := hashFunc(leftNode.Key)
leftNodeKeyHashBigInt := new(big.Int).SetBytes(base58.Decode(crypto.Tip5HashToBase58(leftNodeKeyHash)))
if leftNodeKeyHashBigInt.Cmp(keyHashBigInt) == -1 {
node = &ZNode{
Key: key,
Value: value,
Right: &ZNode{
Key: nodeKey,
Value: nodeValue,
Left: nil,
Right: rightNode,
},
Left: leftNode,
}
} else {
node = &ZNode{
Key: key,
Value: value,
@ -114,6 +183,8 @@ func (node *ZNode) InsertNode(hashFunc func(interface{}) [5]uint64, key, value i
}
}
}
}
}
return node
}
@ -138,3 +209,25 @@ func (node *ZNode) HashNode(hashFunc func(interface{}) ([5]uint64, error)) ([5]u
}
return crypto.Tip5RehashTenCell(valHash, hashLeftRight), nil
}
func TapNode(node *ZNode, acc *[]ZPair) {
if node == nil {
return
}
stored := false
if node.Right != nil {
TapNode(node.Right, acc)
} else {
*acc = append(*acc, ZPair{Key: node.Key, Value: node.Value})
stored = true
}
if node.Left != nil {
TapNode(node.Left, acc)
}
if !stored {
*acc = append(*acc, ZPair{Key: node.Key, Value: node.Value})
}
}