From f91d39b9435b82d958489b808dac75d39edeb660 Mon Sep 17 00:00:00 2001 From: "Chung Hoang Tuan Kiet (Lucky)" Date: Thu, 16 Oct 2025 16:15:48 +0700 Subject: [PATCH 1/5] feat: test submit tx --- wallet/service_test.go | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/wallet/service_test.go b/wallet/service_test.go index 9abffcf..452f102 100644 --- a/wallet/service_test.go +++ b/wallet/service_test.go @@ -428,21 +428,29 @@ func TestFullFlow(t *testing.T) { // This test should be run with timeout 300s func TestCreateTx(t *testing.T) { - seed1 := "pledge vessel toilet sunny hockey skirt spend wire disorder attitude crumble lecture problem bundle bone rather address over suit ancient primary gospel silent repair" + seed1 := "brass vacuum stairs hurt brisk govern describe enforce fly exact rescue capable belt flavor lottery sauce easy frame orange legal injury border obey novel" + masterKey1, err := crypto.MasterKeyFromSeed(seed1) assert.NoError(t, err) + fmt.Println("masterKey1: ", base58.Encode(masterKey1.PublicKey)) - seed2 := "brass vacuum stairs hurt brisk govern describe enforce fly exact rescue capable belt flavor lottery sauce easy frame orange legal injury border obey novel" + seed2 := "chimney endorse scan ramp cheap harvest mother ball winter way barrel foil tissue pupil answer worth right undo one chimney element grape image unlock" masterKey2, err := crypto.MasterKeyFromSeed(seed2) assert.NoError(t, err) + fmt.Println("masterKey2", base58.Encode(masterKey2.PublicKey)) nc, err := wallet.NewNockchainClient("nockchain-api.zorp.io:443") assert.NoError(t, err) handler := wallet.NewGprcHandler(*nc) - inputs := "[CxiTK4HjqPRebUkoy6rH89ZcNGuH4goHkmKgmgCJxZEFS2C3qrDoh4y 91z5muQKgHZDcChdnCSTEtvuN8dbbXp5wzNs5xrCkce2YvSX1q6fu3d],[CxiTK4HjqPRebUkoy6rH89ZcNGuH4goHkmKgmgCJxZEFS2C3qrDoh4y 3a9VWigUM1TuS79yM4dAqkiUg6WJMUPGwVKJpQUHCLJcFZsCWM2q6pF],[CxiTK4HjqPRebUkoy6rH89ZcNGuH4goHkmKgmgCJxZEFS2C3qrDoh4y 9RkPvHMtuYtjV2qvB86u7zcnr26SdVcKzFnHgfhUSEG1W3FgKgLpbBm]" - recipients := fmt.Sprintf("%s,%s,%s", base58.Encode(masterKey2.PublicKey), base58.Encode(masterKey2.PublicKey), base58.Encode(masterKey2.PublicKey)) - gifts := "100,100,100" + //inputs := "[CxiTK4HjqPRebUkoy6rH89ZcNGuH4goHkmKgmgCJxZEFS2C3qrDoh4y 91z5muQKgHZDcChdnCSTEtvuN8dbbXp5wzNs5xrCkce2YvSX1q6fu3d],[CxiTK4HjqPRebUkoy6rH89ZcNGuH4goHkmKgmgCJxZEFS2C3qrDoh4y 3a9VWigUM1TuS79yM4dAqkiUg6WJMUPGwVKJpQUHCLJcFZsCWM2q6pF],[CxiTK4HjqPRebUkoy6rH89ZcNGuH4goHkmKgmgCJxZEFS2C3qrDoh4y 9RkPvHMtuYtjV2qvB86u7zcnr26SdVcKzFnHgfhUSEG1W3FgKgLpbBm]" + //inputs := "[7PGL7VUMmBBFLxBta9hxJy2yyPNyWww6mXB4B7L78CEM7vu8E1gZY6B 76WR3YEgKaPXN9YshstDhHo82yg7ghJwoYGy9hVoihdSogAMpb9Jbyb],[7PGL7VUMmBBFLxBta9hxJy2yyPNyWww6mXB4B7L78CEM7vu8E1gZY6B 4h3Y3RPn5oAuHEmVtysyxNfJgUiy1DsjfRn4GzgZvM7asPPgJ6Jb7iy]" + //inputs := "[7PGL7VUMmBBFLxBta9hxJy2yyPNyWww6mXB4B7L78CEM7vu8E1gZY6B 76WR3YEgKaPXN9YshstDhHo82yg7ghJwoYGy9hVoihdSogAMpb9Jbyb],[7PGL7VUMmBBFLxBta9hxJy2yyPNyWww6mXB4B7L78CEM7vu8E1gZY6B 4h3Y3RPn5oAuHEmVtysyxNfJgUiy1DsjfRn4GzgZvM7asPPgJ6Jb7iy]" + inputs := "[5bcr83LnCYyHqQh4ExK6metaf1cs7JYDqMYc92Awqhwc6VEpZJL9wj9 AmSjC3SDNtb7ZrUkTXc242BvGeimeL1nAV4CqV63HpLMryhom4L9W59],[5bcr83LnCYyHqQh4ExK6metaf1cs7JYDqMYc92Awqhwc6VEpZJL9wj9 jH4hX8h69qfBKfCnx41da6bk8oU7NtirBuDgnPvtJ6SXoh6t2Fap2S]" + //inputs := "[5bcr83LnCYyHqQh4ExK6metaf1cs7JYDqMYc92Awqhwc6VEpZJL9wj9 4d5zzcHuc9spJ1k3ozKYc4SyGm3APeN8wjsg5RkN27FaFsa7oMoJCDf],[5bcr83LnCYyHqQh4ExK6metaf1cs7JYDqMYc92Awqhwc6VEpZJL9wj9 5ZgFvf8qt8YCa2Nru2VZG2FF7P82Xcj4EsM2EQKiddQ8C43ciG91276]" + recipients := fmt.Sprintf("%s,%s", base58.Encode(masterKey2.PublicKey), base58.Encode(masterKey2.PublicKey)) + //recipients := "37Ttw4d6Fq1WGis5qVz8SbeEtpqsbg2ihArBedi4ZeuhFFo8tbCNvwWNq9D8KFBc2qv7uzvPJmKmJg68aEHEh21FiXk9iJCmzyE3NqdSgpsPMCx7Q39yhUUrKkKvGnHUKzMe,37Ttw4d6Fq1WGis5qVz8SbeEtpqsbg2ihArBedi4ZeuhFFo8tbCNvwWNq9D8KFBc2qv7uzvPJmKmJg68aEHEh21FiXk9iJCmzyE3NqdSgpsPMCx7Q39yhUUrKkKvGnHUKzMe" //fmt.Sprintf("%s", base58.Encode(masterKey2.PublicKey)) + gifts := "100,100" fee := 100 req := &nockchain.CreateTxRequest{ @@ -462,5 +470,20 @@ func TestCreateTx(t *testing.T) { assert.NoError(t, err) // the result is taken from create-tx scripts - assert.Equal(t, res.RawTx.TxId, "8gjTa6H1MrKXPWgNJCF9fsYE7PUfqhYzxetUoTftz7zyHCv24ZYHDM3") + //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") + } } From 262f7d3bc06403d1f8ac4a8583ee04cf40495fa4 Mon Sep 17 00:00:00 2001 From: "Chung Hoang Tuan Kiet (Lucky)" Date: Mon, 20 Oct 2025 09:35:25 +0700 Subject: [PATCH 2/5] feat: test submit tx --- wallet/service_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/wallet/service_test.go b/wallet/service_test.go index 452f102..9d0451e 100644 --- a/wallet/service_test.go +++ b/wallet/service_test.go @@ -434,6 +434,8 @@ func TestCreateTx(t *testing.T) { assert.NoError(t, err) fmt.Println("masterKey1: ", base58.Encode(masterKey1.PublicKey)) + // nockchain-wallet create-tx --names '[5bcr83LnCYyHqQh4ExK6metaf1cs7JYDqMYc92Awqhwc6VEpZJL9wj9 AmSjC3SDNtb7ZrUkTXc242BvGeimeL1nAV4CqV63HpLMryhom4L9W59],[5bcr83LnCYyHqQh4ExK6metaf1cs7JYDqMYc92Awqhwc6VEpZJL9wj9 7oo4x3fuwcJ5DrbFY1yf715ctjtE6CqqPagJWT8d687Q6sgMVWc1SXz],[5bcr83LnCYyHqQh4ExK6metaf1cs7JYDqMYc92Awqhwc6VEpZJL9wj9 8BrF9XAKwzvFdy8p7KAB8VEvcswPADhs2WamhWSLUErYN9z8U8cynaA]' --recipients '37Ttw4d6Fq1WGis5qVz8SbeEtpqsbg2ihArBedi4ZeuhFFo8tbCNvwWNq9D8KFBc2qv7uzvPJmKmJg68aEHEh21FiXk9iJCmzyE3NqdSgpsPMCx7Q39yhUUrKkKvGnHUKzMe,37Ttw4d6Fq1WGis5qVz8SbeEtpqsbg2ihArBedi4ZeuhFFo8tbCNvwWNq9D8KFBc2qv7uzvPJmKmJg68aEHEh21FiXk9iJCmzyE3NqdSgpsPMCx7Q39yhUUrKkKvGnHUKzMe,37Ttw4d6Fq1WGis5qVz8SbeEtpqsbg2ihArBedi4ZeuhFFo8tbCNvwWNq9D8KFBc2qv7uzvPJmKmJg68aEHEh21FiXk9iJCmzyE3NqdSgpsPMCx7Q39yhUUrKkKvGnHUKzMe' --gifts '50,50,50' --fee 100 + seed2 := "chimney endorse scan ramp cheap harvest mother ball winter way barrel foil tissue pupil answer worth right undo one chimney element grape image unlock" masterKey2, err := crypto.MasterKeyFromSeed(seed2) assert.NoError(t, err) @@ -446,11 +448,11 @@ func TestCreateTx(t *testing.T) { //inputs := "[CxiTK4HjqPRebUkoy6rH89ZcNGuH4goHkmKgmgCJxZEFS2C3qrDoh4y 91z5muQKgHZDcChdnCSTEtvuN8dbbXp5wzNs5xrCkce2YvSX1q6fu3d],[CxiTK4HjqPRebUkoy6rH89ZcNGuH4goHkmKgmgCJxZEFS2C3qrDoh4y 3a9VWigUM1TuS79yM4dAqkiUg6WJMUPGwVKJpQUHCLJcFZsCWM2q6pF],[CxiTK4HjqPRebUkoy6rH89ZcNGuH4goHkmKgmgCJxZEFS2C3qrDoh4y 9RkPvHMtuYtjV2qvB86u7zcnr26SdVcKzFnHgfhUSEG1W3FgKgLpbBm]" //inputs := "[7PGL7VUMmBBFLxBta9hxJy2yyPNyWww6mXB4B7L78CEM7vu8E1gZY6B 76WR3YEgKaPXN9YshstDhHo82yg7ghJwoYGy9hVoihdSogAMpb9Jbyb],[7PGL7VUMmBBFLxBta9hxJy2yyPNyWww6mXB4B7L78CEM7vu8E1gZY6B 4h3Y3RPn5oAuHEmVtysyxNfJgUiy1DsjfRn4GzgZvM7asPPgJ6Jb7iy]" //inputs := "[7PGL7VUMmBBFLxBta9hxJy2yyPNyWww6mXB4B7L78CEM7vu8E1gZY6B 76WR3YEgKaPXN9YshstDhHo82yg7ghJwoYGy9hVoihdSogAMpb9Jbyb],[7PGL7VUMmBBFLxBta9hxJy2yyPNyWww6mXB4B7L78CEM7vu8E1gZY6B 4h3Y3RPn5oAuHEmVtysyxNfJgUiy1DsjfRn4GzgZvM7asPPgJ6Jb7iy]" - inputs := "[5bcr83LnCYyHqQh4ExK6metaf1cs7JYDqMYc92Awqhwc6VEpZJL9wj9 AmSjC3SDNtb7ZrUkTXc242BvGeimeL1nAV4CqV63HpLMryhom4L9W59],[5bcr83LnCYyHqQh4ExK6metaf1cs7JYDqMYc92Awqhwc6VEpZJL9wj9 jH4hX8h69qfBKfCnx41da6bk8oU7NtirBuDgnPvtJ6SXoh6t2Fap2S]" + inputs := "[5bcr83LnCYyHqQh4ExK6metaf1cs7JYDqMYc92Awqhwc6VEpZJL9wj9 AmSjC3SDNtb7ZrUkTXc242BvGeimeL1nAV4CqV63HpLMryhom4L9W59],[5bcr83LnCYyHqQh4ExK6metaf1cs7JYDqMYc92Awqhwc6VEpZJL9wj9 7oo4x3fuwcJ5DrbFY1yf715ctjtE6CqqPagJWT8d687Q6sgMVWc1SXz],[5bcr83LnCYyHqQh4ExK6metaf1cs7JYDqMYc92Awqhwc6VEpZJL9wj9 8BrF9XAKwzvFdy8p7KAB8VEvcswPADhs2WamhWSLUErYN9z8U8cynaA]" //inputs := "[5bcr83LnCYyHqQh4ExK6metaf1cs7JYDqMYc92Awqhwc6VEpZJL9wj9 4d5zzcHuc9spJ1k3ozKYc4SyGm3APeN8wjsg5RkN27FaFsa7oMoJCDf],[5bcr83LnCYyHqQh4ExK6metaf1cs7JYDqMYc92Awqhwc6VEpZJL9wj9 5ZgFvf8qt8YCa2Nru2VZG2FF7P82Xcj4EsM2EQKiddQ8C43ciG91276]" - recipients := fmt.Sprintf("%s,%s", base58.Encode(masterKey2.PublicKey), base58.Encode(masterKey2.PublicKey)) + recipients := fmt.Sprintf("%s,%s,%s", base58.Encode(masterKey2.PublicKey), base58.Encode(masterKey2.PublicKey), base58.Encode(masterKey2.PublicKey)) //recipients := "37Ttw4d6Fq1WGis5qVz8SbeEtpqsbg2ihArBedi4ZeuhFFo8tbCNvwWNq9D8KFBc2qv7uzvPJmKmJg68aEHEh21FiXk9iJCmzyE3NqdSgpsPMCx7Q39yhUUrKkKvGnHUKzMe,37Ttw4d6Fq1WGis5qVz8SbeEtpqsbg2ihArBedi4ZeuhFFo8tbCNvwWNq9D8KFBc2qv7uzvPJmKmJg68aEHEh21FiXk9iJCmzyE3NqdSgpsPMCx7Q39yhUUrKkKvGnHUKzMe" //fmt.Sprintf("%s", base58.Encode(masterKey2.PublicKey)) - gifts := "100,100" + gifts := "50,50,50" fee := 100 req := &nockchain.CreateTxRequest{ @@ -486,4 +488,5 @@ func TestCreateTx(t *testing.T) { if !txAccepted.GetAccepted() { panic("tx not accepted") } + // expected: A8vSeRde61B4sZccSnNPEnkQgTe15EssoFwyhQXbkhtk4UNm5hyGSid } From 2816e5333b842f0a2e8fd03c51fe554cb1bf1c44 Mon Sep 17 00:00:00 2001 From: Anh Minh <1phamminh0811@gmail.com> Date: Mon, 20 Oct 2025 10:12:49 +0700 Subject: [PATCH 3/5] feat: new keygen and importkeys for protocol upgrade --- crypto/master_key.go | 34 +++++++++++++++----- wallet/service.go | 72 +++++++++++++++++++++--------------------- wallet/service_test.go | 66 +++++++++++++++++++------------------- 3 files changed, 95 insertions(+), 77 deletions(-) diff --git a/crypto/master_key.go b/crypto/master_key.go index 06fd139..5606061 100644 --- a/crypto/master_key.go +++ b/crypto/master_key.go @@ -13,8 +13,8 @@ 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} @@ -29,6 +29,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 +191,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) } diff --git a/wallet/service.go b/wallet/service.go index 7ec260d..1c0dfc9 100644 --- a/wallet/service.go +++ b/wallet/service.go @@ -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, diff --git a/wallet/service_test.go b/wallet/service_test.go index 9d0451e..5cc6daf 100644 --- a/wallet/service_test.go +++ b/wallet/service_test.go @@ -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: "", From 2d9185a5ec2b039d6365b6489fefcaba64da1999 Mon Sep 17 00:00:00 2001 From: Trinity Date: Wed, 22 Oct 2025 08:44:24 +0700 Subject: [PATCH 4/5] fix: send tx with 2 notes --- crypto/master_key.go | 1 + wallet/nockhash.go | 215 +++++++++++++---------------------------- wallet/service.go | 84 ++++++++-------- wallet/service_test.go | 18 +--- wallet/ztree.go | 185 ++++++++++++++++++++++++++--------- 5 files changed, 247 insertions(+), 256 deletions(-) diff --git a/crypto/master_key.go b/crypto/master_key.go index 5606061..8319f70 100644 --- a/crypto/master_key.go +++ b/crypto/master_key.go @@ -18,6 +18,7 @@ var ( 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} diff --git a/wallet/nockhash.go b/wallet/nockhash.go index ac50671..6afa06a 100644 --- a/wallet/nockhash.go +++ b/wallet/nockhash.go @@ -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) - } 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) + 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 { - // seed2 - // / \ - // seed1 ~ - // / \ - // ~ ~ - finalSeedHash = crypto.Tip5RehashTenCell(seed1Hash, crypto.Tip5ZeroZero) - finalSeedHash = crypto.Tip5RehashTenCell(finalSeedHash, crypto.Tip5Zero) - finalSeedHash = crypto.Tip5RehashTenCell(seed2Hash, finalSeedHash) + return [5]uint64{} } - } 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) + }, + 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(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}}) 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]) - if err != nil { - 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) + 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 { - // seed2 - // / \ - // seed1 ~ - // / \ - // ~ ~ - finalSeedHash = crypto.Tip5RehashTenCell(seed1Hash, crypto.Tip5ZeroZero) - finalSeedHash = crypto.Tip5RehashTenCell(finalSeedHash, crypto.Tip5Zero) - finalSeedHash = crypto.Tip5RehashTenCell(seed2Hash, finalSeedHash) + return [5]uint64{} } - } 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) + }, + 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(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{} } diff --git a/wallet/service.go b/wallet/service.go index 1c0dfc9..6d13916 100644 --- a/wallet/service.go +++ b/wallet/service.go @@ -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,15 +297,17 @@ 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 - for i, name := range nnames { - if name.First == nameLeftMost.First && name.Last == nameLeftMost.Last { - idxLeftMost = i - } + nameList := nameTree.Tap() + nameKeys := []string{} + for _, nameKey := range nameList { + name := nameKey.Key.(*nockchain.NockchainName) + nameKeys = append(nameKeys, name.First+" "+name.Last) } - if idxLeftMost == -1 { - return nil, fmt.Errorf("unable to find left most node") + indices := make([]int, len(nnames)) + for i, name := range nnames { + if idx := slices.Index(nameKeys, name.First+" "+name.Last); idx != -1 { + indices[idx] = i + } } recipents := []*nockchain.NockchainLock{} @@ -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 -= req.Fee - } else { - return nil, fmt.Errorf("insufficient funds for notes %s", names[i]) - } + + assetLeft := notes[idx].Asset - gifts[idx] + fee := uint64(0) + if !isSpent && assetLeft >= req.Fee { + isSpent = true + fee = req.Fee + assetLeft -= req.Fee } + 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{ - Signatures: nil, - Seeds: seeds, - Fee: req.Fee, - } - } else { - spend = nockchain.NockchainSpend{ - Signatures: nil, - Seeds: seeds, - Fee: 0, - } + spend := nockchain.NockchainSpend{ + Signatures: nil, + Seeds: seeds, + 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{ diff --git a/wallet/service_test.go b/wallet/service_test.go index 5cc6daf..40f254e 100644 --- a/wallet/service_test.go +++ b/wallet/service_test.go @@ -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") } diff --git a/wallet/ztree.go b/wallet/ztree.go index ce8d3d6..87f0e4b 100644 --- a/wallet/ztree.go +++ b/wallet/ztree.go @@ -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,16 +84,104 @@ func (node *ZNode) InsertNode(hashFunc func(interface{}) [5]uint64, key, value i nodeValue := node.Value leftNode := node.Left rightNode := node.Right - node = &ZNode{ - Key: key, - Value: value, - Right: &ZNode{ - Key: nodeKey, - Value: nodeValue, - Left: leftNode, - Right: rightNode, - }, - Left: nil, + 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, + Right: &ZNode{ + Key: nodeKey, + Value: nodeValue, + Left: leftNode, + Right: rightNode, + }, + Left: nil, + } + } } } } @@ -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}) + } +} From 828628d82f450101b3cd0ac14eba0f109fdcaed9 Mon Sep 17 00:00:00 2001 From: Trinity Date: Thu, 23 Oct 2025 10:53:25 +0700 Subject: [PATCH 5/5] clean --- wallet/service_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/wallet/service_test.go b/wallet/service_test.go index 40f254e..8889406 100644 --- a/wallet/service_test.go +++ b/wallet/service_test.go @@ -445,13 +445,8 @@ func TestCreateTx(t *testing.T) { assert.NoError(t, err) handler := wallet.NewGprcHandler(*nc) - //inputs := "[CxiTK4HjqPRebUkoy6rH89ZcNGuH4goHkmKgmgCJxZEFS2C3qrDoh4y 91z5muQKgHZDcChdnCSTEtvuN8dbbXp5wzNs5xrCkce2YvSX1q6fu3d],[CxiTK4HjqPRebUkoy6rH89ZcNGuH4goHkmKgmgCJxZEFS2C3qrDoh4y 3a9VWigUM1TuS79yM4dAqkiUg6WJMUPGwVKJpQUHCLJcFZsCWM2q6pF],[CxiTK4HjqPRebUkoy6rH89ZcNGuH4goHkmKgmgCJxZEFS2C3qrDoh4y 9RkPvHMtuYtjV2qvB86u7zcnr26SdVcKzFnHgfhUSEG1W3FgKgLpbBm]" - //inputs := "[7PGL7VUMmBBFLxBta9hxJy2yyPNyWww6mXB4B7L78CEM7vu8E1gZY6B 76WR3YEgKaPXN9YshstDhHo82yg7ghJwoYGy9hVoihdSogAMpb9Jbyb],[7PGL7VUMmBBFLxBta9hxJy2yyPNyWww6mXB4B7L78CEM7vu8E1gZY6B 4h3Y3RPn5oAuHEmVtysyxNfJgUiy1DsjfRn4GzgZvM7asPPgJ6Jb7iy]" - //inputs := "[7PGL7VUMmBBFLxBta9hxJy2yyPNyWww6mXB4B7L78CEM7vu8E1gZY6B 76WR3YEgKaPXN9YshstDhHo82yg7ghJwoYGy9hVoihdSogAMpb9Jbyb],[7PGL7VUMmBBFLxBta9hxJy2yyPNyWww6mXB4B7L78CEM7vu8E1gZY6B 4h3Y3RPn5oAuHEmVtysyxNfJgUiy1DsjfRn4GzgZvM7asPPgJ6Jb7iy]" inputs := "[5bcr83LnCYyHqQh4ExK6metaf1cs7JYDqMYc92Awqhwc6VEpZJL9wj9 AmSjC3SDNtb7ZrUkTXc242BvGeimeL1nAV4CqV63HpLMryhom4L9W59],[5bcr83LnCYyHqQh4ExK6metaf1cs7JYDqMYc92Awqhwc6VEpZJL9wj9 7oo4x3fuwcJ5DrbFY1yf715ctjtE6CqqPagJWT8d687Q6sgMVWc1SXz],[5bcr83LnCYyHqQh4ExK6metaf1cs7JYDqMYc92Awqhwc6VEpZJL9wj9 8BrF9XAKwzvFdy8p7KAB8VEvcswPADhs2WamhWSLUErYN9z8U8cynaA]" - //inputs := "[5bcr83LnCYyHqQh4ExK6metaf1cs7JYDqMYc92Awqhwc6VEpZJL9wj9 4d5zzcHuc9spJ1k3ozKYc4SyGm3APeN8wjsg5RkN27FaFsa7oMoJCDf],[5bcr83LnCYyHqQh4ExK6metaf1cs7JYDqMYc92Awqhwc6VEpZJL9wj9 5ZgFvf8qt8YCa2Nru2VZG2FF7P82Xcj4EsM2EQKiddQ8C43ciG91276]" recipients := fmt.Sprintf("%s,%s,%s", base58.Encode(masterKey2.PublicKey), base58.Encode(masterKey2.PublicKey), base58.Encode(masterKey2.PublicKey)) - //recipients := "37Ttw4d6Fq1WGis5qVz8SbeEtpqsbg2ihArBedi4ZeuhFFo8tbCNvwWNq9D8KFBc2qv7uzvPJmKmJg68aEHEh21FiXk9iJCmzyE3NqdSgpsPMCx7Q39yhUUrKkKvGnHUKzMe,37Ttw4d6Fq1WGis5qVz8SbeEtpqsbg2ihArBedi4ZeuhFFo8tbCNvwWNq9D8KFBc2qv7uzvPJmKmJg68aEHEh21FiXk9iJCmzyE3NqdSgpsPMCx7Q39yhUUrKkKvGnHUKzMe" //fmt.Sprintf("%s", base58.Encode(masterKey2.PublicKey)) gifts := "50,50,50" fee := 100