Compare commits

..

No commits in common. "2d9185a5ec2b039d6365b6489fefcaba64da1999" and "262f7d3bc06403d1f8ac4a8583ee04cf40495fa4" have entirely different histories.

5 changed files with 336 additions and 345 deletions

View File

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

View File

@ -61,24 +61,6 @@ func HashName(name *nockchain.NockchainName) [5]uint64 {
return crypto.Tip5RehashTenCell(firstNameHash, crypto.Tip5RehashTenCell(lastNameHash, crypto.Tip5Zero)) 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) { func HashNote(note *nockchain.NockchainNote) ([5]uint64, error) {
versionHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: uint64(note.Version)}}) versionHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: uint64(note.Version)}})
blockHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: note.BlockHeight}}) blockHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: note.BlockHeight}})
@ -243,36 +225,86 @@ func HashSpend(spend *nockchain.NockchainSpend) ([5]uint64, error) {
if err != nil { if err != nil {
return [5]uint64{}, err return [5]uint64{}, err
} }
seedsCount := len(spend.Seeds)
seedsTree := NewZTree( var finalSeedHash [5]uint64
func(i interface{}) [5]uint64 { if seedsCount == 1 {
if seed, ok := i.(*nockchain.NockchainSeed); ok { seedHash, err := HashSeedWithoutSource(spend.Seeds[0])
seedHash, err := HashSeedVarLen(seed)
if err != nil {
return [5]uint64{}
}
return seedHash
} else {
return [5]uint64{}
}
},
func(i interface{}) ([5]uint64, error) {
if seed, ok := i.(*nockchain.NockchainSeed); ok {
hash, err := HashSeedWithoutSource(seed)
return hash, err
} else {
return [5]uint64{}, fmt.Errorf("invalid input type")
}
},
)
for _, seed := range spend.Seeds {
seedsTree.Insert(seed, seed)
}
finalSeedHash, err := seedsTree.Hash()
if err != nil { if err != nil {
return [5]uint64{}, err return [5]uint64{}, err
} }
finalSeedHash = crypto.Tip5RehashTenCell(seedHash, crypto.Tip5ZeroZero)
} else {
seed1Hash, err := HashSeedWithoutSource(spend.Seeds[0])
if err != nil {
return [5]uint64{}, err
}
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)
} 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)
}
}
}
feeHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: spend.Fee}}) feeHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: spend.Fee}})
seedHashFee := crypto.Tip5RehashTenCell(finalSeedHash, feeHash) seedHashFee := crypto.Tip5RehashTenCell(finalSeedHash, feeHash)
return crypto.Tip5RehashTenCell(sigHash, seedHashFee), nil return crypto.Tip5RehashTenCell(sigHash, seedHashFee), nil
@ -280,34 +312,85 @@ func HashSpend(spend *nockchain.NockchainSpend) ([5]uint64, error) {
} }
func HashMsg(spend *nockchain.NockchainSpend) ([5]uint64, error) { func HashMsg(spend *nockchain.NockchainSpend) ([5]uint64, error) {
seedsTree := NewZTree( seedsCount := len(spend.Seeds)
func(i interface{}) [5]uint64 { var finalSeedHash [5]uint64
if seed, ok := i.(*nockchain.NockchainSeed); ok { if seedsCount == 1 {
seedHash, err := HashSeedVarLen(seed) seedHash, err := HashSeed(spend.Seeds[0])
if err != nil {
return [5]uint64{}
}
return seedHash
} else {
return [5]uint64{}
}
},
func(i interface{}) ([5]uint64, error) {
if seed, ok := i.(*nockchain.NockchainSeed); ok {
hash, err := HashSeed(seed)
return hash, err
} else {
return [5]uint64{}, fmt.Errorf("invalid input type")
}
},
)
for _, seed := range spend.Seeds {
seedsTree.Insert(seed, seed)
}
finalSeedHash, err := seedsTree.Hash()
if err != nil { if err != nil {
return [5]uint64{}, err return [5]uint64{}, err
} }
finalSeedHash = crypto.Tip5RehashTenCell(seedHash, crypto.Tip5ZeroZero)
} else {
seed1HashVarLen, err := HashSeedVarLen(spend.Seeds[0])
if err != nil {
return [5]uint64{}, err
}
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)
} 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)
}
}
}
feeHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: spend.Fee}}) feeHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: spend.Fee}})
return crypto.Tip5RehashTenCell(finalSeedHash, feeHash), nil return crypto.Tip5RehashTenCell(finalSeedHash, feeHash), nil
} }
@ -334,7 +417,7 @@ func ComputeTxId(inputs []*nockchain.NockchainInput, timelockRange *nockchain.Ti
inputTree := NewZTree( inputTree := NewZTree(
func(i interface{}) [5]uint64 { func(i interface{}) [5]uint64 {
if name, ok := i.(*nockchain.NockchainName); ok { if name, ok := i.(*nockchain.NockchainName); ok {
return HashNameVarLen(name) return HashName(name)
} else { } else {
return [5]uint64{} return [5]uint64{}
} }

View File

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

View File

@ -17,13 +17,13 @@ import (
// The entropy, salt and result is taken from "nockchain-wallet keygen" command // The entropy, salt and result is taken from "nockchain-wallet keygen" command
func TestKeyGen(t *testing.T) { func TestKeyGen(t *testing.T) {
entropyBigInt, isOk := new(big.Int).SetString("37133536588676344913489312523941366110857274548479981512263368615793750653450", 10) entropyBigInt, isOk := new(big.Int).SetString("29615235796517918707367078072007441124337225858809749976291970867443501879006", 10)
assert.True(t, isOk) assert.True(t, isOk)
entropy := entropyBigInt.Bytes() entropy := entropyBigInt.Bytes()
assert.Len(t, entropy, 32) assert.Len(t, entropy, 32)
saltBigInt, isOk := new(big.Int).SetString("251632902249061493058993135304695174381", 10) saltBigInt, isOk := new(big.Int).SetString("212311808188922973323281316240858086116", 10)
assert.True(t, isOk) assert.True(t, isOk)
salt := saltBigInt.Bytes() salt := saltBigInt.Bytes()
@ -33,38 +33,38 @@ func TestKeyGen(t *testing.T) {
slices.Reverse(argonBytes) slices.Reverse(argonBytes)
mnemonic, err := bip39.NewMnemonic(argonBytes) mnemonic, err := bip39.NewMnemonic(argonBytes)
assert.NoError(t, err) assert.NoError(t, err)
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") 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")
masterKey, err := crypto.MasterKeyFromSeed(mnemonic) masterKey, err := crypto.MasterKeyFromSeed(mnemonic)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, assert.Equal(t,
base58.Encode(masterKey.PublicKey), base58.Encode(masterKey.PublicKey),
"34VqjU7ojQXWiFZz7kvXe1xfxhbdimmqqUAgp21XGESLqJSXxHkqrcquWirFcCPPj1pmSL4pRc8GndZoiiK8ijeYjgcJ3QR7fb2s4b2WdJhDao4Dx7gw3NRSt4RjXawqUQw6", "39DL6YA1kSRCKMjzpFEtC8rmnxVuseUrP2LnViwY7YEhZYZkX2HmnAZ63Uwy1DwuXstmF1VeJDucg719xw49j9CKL3bsKq3A6SZN918CowcgQroHsgohj7dYgpGRWk41s42F",
) )
assert.Equal(t, assert.Equal(t,
base58.Encode(masterKey.PrivateKey), base58.Encode(masterKey.PrivateKey),
"3B8Q5ZTHH63h9DT6WSwNZhea5zvtueuKpxk3qwZJEjsg", "4SyUrsbGKPRknzvGakWmFbYefzHzb1r4LUmJpQD8WPcR",
) )
assert.Equal(t, assert.Equal(t,
base58.Encode(masterKey.ChainCode), base58.Encode(masterKey.ChainCode),
"2ztGPxS8xYzMXoAHf3HMbMuyh4siew8X4Kz4KuTXvqX8", "58SARPmADHvUcpq7XfBoCgwzy5QC8Kb3JrezpHqA85x2",
) )
// assert import priv/pubkey // assert import priv/pubkey
privBytes := append([]byte{0x00}, masterKey.PrivateKey...) privBytes := append([]byte{0x00}, masterKey.PrivateKey...)
importPrivKey := crypto.SerializeExtend(masterKey.ChainCode, privBytes, crypto.KeyType_PRIVATE) importPrivKey := crypto.SerializeExtend(masterKey.ChainCode, privBytes, crypto.PrivateKeyStart)
assert.Len(t, importPrivKey, 83) assert.Len(t, importPrivKey, 82)
importPubKey := crypto.SerializeExtend(masterKey.ChainCode, masterKey.PublicKey, crypto.KeyType_PUBLIC) importPubKey := crypto.SerializeExtend(masterKey.ChainCode, masterKey.PublicKey, crypto.PublicKeyStart)
assert.Len(t, importPubKey, 147) assert.Len(t, importPubKey, 145)
assert.Equal(t, assert.Equal(t,
base58.Encode(importPrivKey), base58.Encode(importPrivKey),
"zprvLpf3WSvYWmHRd3jj5oR8UXr7bi88pGnfJXj1dM9RnwJwu1MLo6fZzcKauqpxL2W7dk2fmjYKAUzavbyaGpnvqY5QndcrUzsBrPQAHXNnhiXx", "zprv2CyrSHEkzQzu4HCtJRFiP4t2rVMauZwLfJDFrNbqS8Pz3nsmXy5bAUx2HYUykaMuU4MiQTHsDcKYjLCjrPfpceNT9XBHgx1pUjKzBrF6Wdo",
) )
assert.Equal(t, assert.Equal(t,
base58.Encode(importPubKey), base58.Encode(importPubKey),
"zpub2jgndknkQprVYB4X4mqREyn7ZTUE5zp9qkSugdpiqhC5NSeNBceafoz6jGSLEpzJhaLryvY8MF6TokwZN627UXhsg5zd2U12woEL82UtZaLHRL8PZi8YiQnE41BiNJwkfpWjzAbq8mwbAHV3nXUEFgJW2BKCz4GmfoMCkCUDhJppYh5KFsMUkN41DzsfFZHuFrzo", "zpubUQwNTNE3hsCkMpBnD37W5QirkyVryokAVPLnPin1c6M13RRsq3yEJbwp5ies6qXF6DvJq5Woxw6ygT53PSVrmrsQgtHhbfMEixKNFm7qb4mELhpyoovpFEV1YPHFZx4xQGYBNF6qvXU6AHNh4TLrUdkYAdXKS2J5rPiSVPrXKGo8fLG6ZBCGBjJfPcwDb2VEJC",
) )
} }
@ -76,32 +76,32 @@ func TestImportKey(t *testing.T) {
errStr string errStr string
} }
correctImportPrivKey := base58.Decode("zprvLpf3WSvYWmHRd3jj5oR8UXr7bi88pGnfJXj1dM9RnwJwu1MLo6fZzcKauqpxL2W7dk2fmjYKAUzavbyaGpnvqY5QndcrUzsBrPQAHXNnhiXx") correctImportPrivKey := base58.Decode("zprv2CyrSHEkzQzu4HCtJRFiP4t2rVMauZwLfJDFrNbqS8Pz3nsmXy5bAUx2HYUykaMuU4MiQTHsDcKYjLCjrPfpceNT9XBHgx1pUjKzBrF6Wdo")
invalidImportPrivKeyPrefix := make([]byte, 83) invalidImportPrivKeyPrefix := make([]byte, 82)
copy(invalidImportPrivKeyPrefix[:], correctImportPrivKey) copy(invalidImportPrivKeyPrefix[:], correctImportPrivKey)
invalidImportPrivKeyPrefix[46] = 0x01 invalidImportPrivKeyPrefix[45] = 0x01
invalidImportPrivKeyChecksum := make([]byte, 83) invalidImportPrivKeyChecksum := make([]byte, 82)
copy(invalidImportPrivKeyChecksum[:], correctImportPrivKey) copy(invalidImportPrivKeyChecksum[:], correctImportPrivKey)
copy(invalidImportPrivKeyChecksum[79:], []byte{1, 2, 3, 4}) copy(invalidImportPrivKeyChecksum[78:], []byte{1, 2, 3, 4})
correctImportPubkey := base58.Decode("zpub2jgndknkQprVYB4X4mqREyn7ZTUE5zp9qkSugdpiqhC5NSeNBceafoz6jGSLEpzJhaLryvY8MF6TokwZN627UXhsg5zd2U12woEL82UtZaLHRL8PZi8YiQnE41BiNJwkfpWjzAbq8mwbAHV3nXUEFgJW2BKCz4GmfoMCkCUDhJppYh5KFsMUkN41DzsfFZHuFrzo") correctImportPubkey := base58.Decode("zpubUQwNTNE3hsCkMpBnD37W5QirkyVryokAVPLnPin1c6M13RRsq3yEJbwp5ies6qXF6DvJq5Woxw6ygT53PSVrmrsQgtHhbfMEixKNFm7qb4mELhpyoovpFEV1YPHFZx4xQGYBNF6qvXU6AHNh4TLrUdkYAdXKS2J5rPiSVPrXKGo8fLG6ZBCGBjJfPcwDb2VEJC")
invalidImportPubkeyChecksum := make([]byte, 147) invalidImportPubkeyChecksum := make([]byte, 145)
copy(invalidImportPubkeyChecksum[:], correctImportPubkey) copy(invalidImportPubkeyChecksum[:], correctImportPubkey)
copy(invalidImportPubkeyChecksum[143:], []byte{1, 2, 3, 4}) copy(invalidImportPubkeyChecksum[141:], []byte{1, 2, 3, 4})
response := &nockchain.ImportKeysResponse{ response := &nockchain.ImportKeysResponse{
PublicKey: "34VqjU7ojQXWiFZz7kvXe1xfxhbdimmqqUAgp21XGESLqJSXxHkqrcquWirFcCPPj1pmSL4pRc8GndZoiiK8ijeYjgcJ3QR7fb2s4b2WdJhDao4Dx7gw3NRSt4RjXawqUQw6", PublicKey: "39DL6YA1kSRCKMjzpFEtC8rmnxVuseUrP2LnViwY7YEhZYZkX2HmnAZ63Uwy1DwuXstmF1VeJDucg719xw49j9CKL3bsKq3A6SZN918CowcgQroHsgohj7dYgpGRWk41s42F",
PrivateKey: "3B8Q5ZTHH63h9DT6WSwNZhea5zvtueuKpxk3qwZJEjsg", PrivateKey: "4SyUrsbGKPRknzvGakWmFbYefzHzb1r4LUmJpQD8WPcR",
ChainCode: "2ztGPxS8xYzMXoAHf3HMbMuyh4siew8X4Kz4KuTXvqX8", ChainCode: "58SARPmADHvUcpq7XfBoCgwzy5QC8Kb3JrezpHqA85x2",
ImportPrivateKey: base58.Encode(correctImportPrivKey), ImportPrivateKey: base58.Encode(correctImportPrivKey),
ImportPublicKey: base58.Encode(correctImportPubkey), ImportPublicKey: base58.Encode(correctImportPubkey),
} }
responseReadOnly := &nockchain.ImportKeysResponse{ responseReadOnly := &nockchain.ImportKeysResponse{
PublicKey: "34VqjU7ojQXWiFZz7kvXe1xfxhbdimmqqUAgp21XGESLqJSXxHkqrcquWirFcCPPj1pmSL4pRc8GndZoiiK8ijeYjgcJ3QR7fb2s4b2WdJhDao4Dx7gw3NRSt4RjXawqUQw6", PublicKey: "39DL6YA1kSRCKMjzpFEtC8rmnxVuseUrP2LnViwY7YEhZYZkX2HmnAZ63Uwy1DwuXstmF1VeJDucg719xw49j9CKL3bsKq3A6SZN918CowcgQroHsgohj7dYgpGRWk41s42F",
PrivateKey: "", PrivateKey: "",
ChainCode: "2ztGPxS8xYzMXoAHf3HMbMuyh4siew8X4Kz4KuTXvqX8", ChainCode: "58SARPmADHvUcpq7XfBoCgwzy5QC8Kb3JrezpHqA85x2",
ImportPrivateKey: "", ImportPrivateKey: "",
ImportPublicKey: base58.Encode(correctImportPubkey), ImportPublicKey: base58.Encode(correctImportPubkey),
} }
@ -142,7 +142,7 @@ func TestImportKey(t *testing.T) {
}, },
expectResp: nil, expectResp: nil,
isErr: true, isErr: true,
errStr: "invalid private key prefix at byte 46", errStr: "invalid private key prefix at byte 45",
}, },
{ {
req: &nockchain.ImportKeysRequest{ req: &nockchain.ImportKeysRequest{
@ -195,7 +195,7 @@ func TestImportKey(t *testing.T) {
// case missing chaincode when import master privkey // case missing chaincode when import master privkey
{ {
req: &nockchain.ImportKeysRequest{ req: &nockchain.ImportKeysRequest{
Key: "3B8Q5ZTHH63h9DT6WSwNZhea5zvtueuKpxk3qwZJEjsg", Key: "4SyUrsbGKPRknzvGakWmFbYefzHzb1r4LUmJpQD8WPcR",
ImportType: nockchain.ImportType_MASTER_PRIVKEY, ImportType: nockchain.ImportType_MASTER_PRIVKEY,
}, },
expectResp: nil, expectResp: nil,
@ -205,7 +205,7 @@ func TestImportKey(t *testing.T) {
// case invalid length // case invalid length
{ {
req: &nockchain.ImportKeysRequest{ req: &nockchain.ImportKeysRequest{
Key: "abcdxyz,3B8Q5ZTHH63h9DT6WSwNZhea5zvtueuKpxk3qwZJEjsg", Key: "abcdxyz,4SyUrsbGKPRknzvGakWmFbYefzHzb1r4LUmJpQD8WPcR",
ImportType: nockchain.ImportType_MASTER_PRIVKEY, ImportType: nockchain.ImportType_MASTER_PRIVKEY,
}, },
expectResp: nil, expectResp: nil,
@ -214,7 +214,7 @@ func TestImportKey(t *testing.T) {
}, },
{ {
req: &nockchain.ImportKeysRequest{ req: &nockchain.ImportKeysRequest{
Key: "2ztGPxS8xYzMXoAHf3HMbMuyh4siew8X4Kz4KuTXvqX8,abcdxyz", Key: "58SARPmADHvUcpq7XfBoCgwzy5QC8Kb3JrezpHqA85x2,abcdxyz",
ImportType: nockchain.ImportType_MASTER_PRIVKEY, ImportType: nockchain.ImportType_MASTER_PRIVKEY,
}, },
expectResp: nil, expectResp: nil,
@ -224,7 +224,7 @@ func TestImportKey(t *testing.T) {
// case success import master privkey // case success import master privkey
{ {
req: &nockchain.ImportKeysRequest{ req: &nockchain.ImportKeysRequest{
Key: "2ztGPxS8xYzMXoAHf3HMbMuyh4siew8X4Kz4KuTXvqX8,3B8Q5ZTHH63h9DT6WSwNZhea5zvtueuKpxk3qwZJEjsg", Key: "58SARPmADHvUcpq7XfBoCgwzy5QC8Kb3JrezpHqA85x2,4SyUrsbGKPRknzvGakWmFbYefzHzb1r4LUmJpQD8WPcR",
ImportType: nockchain.ImportType_MASTER_PRIVKEY, ImportType: nockchain.ImportType_MASTER_PRIVKEY,
}, },
expectResp: response, expectResp: response,
@ -234,7 +234,7 @@ func TestImportKey(t *testing.T) {
// case success import seed // case success import seed
{ {
req: &nockchain.ImportKeysRequest{ req: &nockchain.ImportKeysRequest{
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", 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",
ImportType: nockchain.ImportType_SEEDPHRASE, ImportType: nockchain.ImportType_SEEDPHRASE,
}, },
expectResp: response, expectResp: response,
@ -244,11 +244,11 @@ func TestImportKey(t *testing.T) {
// case sucess import pubkey // case sucess import pubkey
{ {
req: &nockchain.ImportKeysRequest{ req: &nockchain.ImportKeysRequest{
Key: "34VqjU7ojQXWiFZz7kvXe1xfxhbdimmqqUAgp21XGESLqJSXxHkqrcquWirFcCPPj1pmSL4pRc8GndZoiiK8ijeYjgcJ3QR7fb2s4b2WdJhDao4Dx7gw3NRSt4RjXawqUQw6", Key: "39DL6YA1kSRCKMjzpFEtC8rmnxVuseUrP2LnViwY7YEhZYZkX2HmnAZ63Uwy1DwuXstmF1VeJDucg719xw49j9CKL3bsKq3A6SZN918CowcgQroHsgohj7dYgpGRWk41s42F",
ImportType: nockchain.ImportType_WATCH_ONLY, ImportType: nockchain.ImportType_WATCH_ONLY,
}, },
expectResp: &nockchain.ImportKeysResponse{ expectResp: &nockchain.ImportKeysResponse{
PublicKey: "34VqjU7ojQXWiFZz7kvXe1xfxhbdimmqqUAgp21XGESLqJSXxHkqrcquWirFcCPPj1pmSL4pRc8GndZoiiK8ijeYjgcJ3QR7fb2s4b2WdJhDao4Dx7gw3NRSt4RjXawqUQw6", PublicKey: "39DL6YA1kSRCKMjzpFEtC8rmnxVuseUrP2LnViwY7YEhZYZkX2HmnAZ63Uwy1DwuXstmF1VeJDucg719xw49j9CKL3bsKq3A6SZN918CowcgQroHsgohj7dYgpGRWk41s42F",
}, },
isErr: false, isErr: false,
errStr: "", errStr: "",
@ -472,5 +472,21 @@ func TestCreateTx(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
// the result is taken from create-tx scripts // the result is taken from create-tx scripts
assert.Equal(t, res.RawTx.TxId, "A8vSeRde61B4sZccSnNPEnkQgTe15EssoFwyhQXbkhtk4UNm5hyGSid") //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
} }

View File

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