fix: send tx with 2 notes
This commit is contained in:
parent
552a9e9d9b
commit
e34c2738d0
@ -388,7 +388,6 @@ func HashMsg(spend *nockchain.NockchainSpend) ([5]uint64, error) {
|
||||
finalSeedHash = crypto.Tip5RehashTenCell(seed1Hash, crypto.Tip5ZeroZero)
|
||||
finalSeedHash = crypto.Tip5RehashTenCell(crypto.Tip5Zero, finalSeedHash)
|
||||
finalSeedHash = crypto.Tip5RehashTenCell(seed2Hash, finalSeedHash)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -410,16 +409,34 @@ func HashInput(input *nockchain.NockchainInput) ([5]uint64, error) {
|
||||
|
||||
}
|
||||
hashNoteSpend := crypto.Tip5RehashTenCell(noteHash, spendHash)
|
||||
inputHash := crypto.Tip5RehashTenCell(nameHash, hashNoteSpend)
|
||||
return crypto.Tip5RehashTenCell(inputHash, crypto.Tip5ZeroZero), nil
|
||||
return crypto.Tip5RehashTenCell(nameHash, hashNoteSpend), nil
|
||||
}
|
||||
|
||||
func ComputeTxId(inputs []*nockchain.NockchainInput, timelockRange *nockchain.TimelockRange, totalFees uint64) ([5]uint64, error) {
|
||||
// TODO: do it with multiple intputs
|
||||
input := inputs[0]
|
||||
inputHash, err := HashInput(input)
|
||||
|
||||
inputTree := NewZTree(
|
||||
func(i interface{}) [5]uint64 {
|
||||
if name, ok := i.(*nockchain.NockchainName); ok {
|
||||
return HashName(name)
|
||||
} else {
|
||||
return [5]uint64{}
|
||||
}
|
||||
},
|
||||
func(i interface{}) ([5]uint64, error) {
|
||||
if input, ok := i.(*nockchain.NockchainInput); ok {
|
||||
hash, err := HashInput(input)
|
||||
return hash, err
|
||||
} else {
|
||||
return [5]uint64{}, fmt.Errorf("invalid input type")
|
||||
}
|
||||
},
|
||||
)
|
||||
for _, input := range inputs {
|
||||
inputTree.Insert(input.Name, input)
|
||||
}
|
||||
inputHash, err := inputTree.Hash()
|
||||
if err != nil {
|
||||
return [5]uint64{}, err
|
||||
return [5]uint64{}, fmt.Errorf("error hashing inputs: %v", err)
|
||||
}
|
||||
|
||||
timelockHash := HashTimelockRange(timelockRange)
|
||||
|
@ -267,8 +267,7 @@ func (h *GprcHandler) DeriveChild(ctx context.Context, req *nockchain.DeriveChil
|
||||
//
|
||||
// - `fee` - Transaction fee to be subtracted from one of the input notes
|
||||
func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxRequest) (*nockchain.CreateTxResponse, error) {
|
||||
firstNames := [][5]uint64{}
|
||||
lastNames := [][5]uint64{}
|
||||
nnames := []*nockchain.NockchainName{}
|
||||
names := strings.Split(req.Names, ",")
|
||||
notes := make([]*nockchain.NockchainNote, len(names))
|
||||
for _, name := range names {
|
||||
@ -277,12 +276,38 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque
|
||||
inner := name[1 : len(name)-1]
|
||||
part := strings.Split(inner, " ")
|
||||
if len(part) == 2 {
|
||||
firstNames = append(firstNames, crypto.Base58ToTip5Hash(part[0]))
|
||||
lastNames = append(lastNames, crypto.Base58ToTip5Hash(part[1]))
|
||||
nnames = append(nnames, &nockchain.NockchainName{
|
||||
First: part[0],
|
||||
Last: part[1],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nameTree := NewZTree(
|
||||
func(i interface{}) [5]uint64 {
|
||||
if name, ok := i.(*nockchain.NockchainName); ok {
|
||||
return HashName(name)
|
||||
} else {
|
||||
return [5]uint64{}
|
||||
}
|
||||
},
|
||||
nil,
|
||||
)
|
||||
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
|
||||
}
|
||||
}
|
||||
if idxLeftMost == -1 {
|
||||
return nil, fmt.Errorf("unable to find left most node")
|
||||
}
|
||||
|
||||
recipents := []*nockchain.NockchainLock{}
|
||||
if strings.Contains(req.Recipients, "[") {
|
||||
pairs := strings.Split(req.Recipients, ",")
|
||||
@ -329,7 +354,7 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque
|
||||
// No additional validation needed - any number of names is allowed
|
||||
} else {
|
||||
// Multiple mode: all lengths must match
|
||||
if len(firstNames) != len(recipents) || len(firstNames) != len(gifts) {
|
||||
if len(nnames) != len(recipents) || len(nnames) != len(gifts) {
|
||||
return nil, fmt.Errorf("multiple recipient mode requires names, recipients, and gifts to have the same length")
|
||||
}
|
||||
}
|
||||
@ -409,8 +434,18 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque
|
||||
ParentHash: crypto.Tip5HashToBase58(parentHash),
|
||||
},
|
||||
}
|
||||
|
||||
if notes[i].Asset < gifts[i]+req.Fee {
|
||||
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])
|
||||
}
|
||||
}
|
||||
if assetLeft != 0 {
|
||||
seeds = append(seeds, &nockchain.NockchainSeed{
|
||||
OutputSource: nil,
|
||||
Recipient: &nockchain.NockchainLock{
|
||||
@ -418,15 +453,23 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque
|
||||
Pubkeys: []string{base58.Encode(masterKey.PublicKey)},
|
||||
},
|
||||
TimelockIntent: req.TimelockIntent,
|
||||
Gift: notes[i].Asset - gifts[i] - req.Fee,
|
||||
Gift: assetLeft,
|
||||
ParentHash: crypto.Tip5HashToBase58(parentHash),
|
||||
})
|
||||
}
|
||||
|
||||
spend := nockchain.NockchainSpend{
|
||||
Signatures: nil,
|
||||
Seeds: seeds,
|
||||
Fee: req.Fee,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
msg, err := HashMsg(&spend)
|
||||
@ -449,10 +492,7 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque
|
||||
}
|
||||
|
||||
input := nockchain.NockchainInput{
|
||||
Name: &nockchain.NockchainName{
|
||||
First: crypto.Tip5HashToBase58(firstNames[i]),
|
||||
Last: crypto.Tip5HashToBase58(lastNames[i]),
|
||||
},
|
||||
Name: nnames[i],
|
||||
Note: notes[i],
|
||||
Spend: &spend,
|
||||
}
|
||||
@ -478,7 +518,7 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque
|
||||
TxId: "",
|
||||
Inputs: inputs,
|
||||
TimelockRange: timelockRange,
|
||||
TotalFees: req.Fee * uint64(len(inputs)),
|
||||
TotalFees: req.Fee,
|
||||
}
|
||||
txId, err := ComputeTxId(inputs, timelockRange, req.Fee)
|
||||
if err != nil {
|
||||
|
@ -2,6 +2,7 @@ package wallet_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"slices"
|
||||
"testing"
|
||||
@ -424,3 +425,42 @@ func TestFullFlow(t *testing.T) {
|
||||
Accepted: true,
|
||||
})
|
||||
}
|
||||
|
||||
// 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"
|
||||
masterKey1, err := crypto.MasterKeyFromSeed(seed1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
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"
|
||||
masterKey2, err := crypto.MasterKeyFromSeed(seed2)
|
||||
assert.NoError(t, err)
|
||||
|
||||
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"
|
||||
fee := 100
|
||||
|
||||
req := &nockchain.CreateTxRequest{
|
||||
Names: inputs,
|
||||
Recipients: recipients,
|
||||
Gifts: gifts,
|
||||
Fee: uint64(fee),
|
||||
IsMasterKey: true,
|
||||
Key: base58.Encode(masterKey1.PrivateKey),
|
||||
ChainCode: base58.Encode(masterKey1.ChainCode),
|
||||
Index: 0,
|
||||
Hardened: false,
|
||||
TimelockIntent: nil,
|
||||
}
|
||||
|
||||
res, err := handler.CreateTx(context.Background(), req)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// the result is taken from create-tx scripts
|
||||
assert.Equal(t, res.RawTx.TxId, "8gjTa6H1MrKXPWgNJCF9fsYE7PUfqhYzxetUoTftz7zyHCv24ZYHDM3")
|
||||
}
|
||||
|
140
wallet/ztree.go
Normal file
140
wallet/ztree.go
Normal file
@ -0,0 +1,140 @@
|
||||
package wallet
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/phamminh0811/private-grpc/crypto"
|
||||
)
|
||||
|
||||
type ZNode struct {
|
||||
Key interface{}
|
||||
Value interface{}
|
||||
|
||||
Left *ZNode
|
||||
Right *ZNode
|
||||
}
|
||||
|
||||
type ZTree struct {
|
||||
Root *ZNode
|
||||
HashKeyFunc func(interface{}) [5]uint64
|
||||
HashValueFunc func(interface{}) ([5]uint64, error)
|
||||
}
|
||||
|
||||
func NewZTree(hashKeyFunc func(interface{}) [5]uint64, hashValueFunc func(interface{}) ([5]uint64, error)) *ZTree {
|
||||
return &ZTree{
|
||||
Root: nil,
|
||||
HashKeyFunc: hashKeyFunc,
|
||||
HashValueFunc: hashValueFunc,
|
||||
}
|
||||
}
|
||||
|
||||
func (z *ZTree) Insert(key, value interface{}) {
|
||||
z.Root = z.Root.InsertNode(z.HashKeyFunc, key, value)
|
||||
}
|
||||
|
||||
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 (node *ZNode) InsertNode(hashFunc func(interface{}) [5]uint64, key, value interface{}) *ZNode {
|
||||
if node == nil {
|
||||
node = &ZNode{
|
||||
Key: key,
|
||||
Value: value,
|
||||
Left: nil,
|
||||
Right: nil,
|
||||
}
|
||||
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 {
|
||||
// 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 {
|
||||
// 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{
|
||||
Key: key,
|
||||
Value: value,
|
||||
Right: &ZNode{
|
||||
Key: nodeKey,
|
||||
Value: nodeValue,
|
||||
Left: leftNode,
|
||||
Right: rightNode,
|
||||
},
|
||||
Left: nil,
|
||||
}
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func (node *ZNode) HashNode(hashFunc func(interface{}) ([5]uint64, error)) ([5]uint64, error) {
|
||||
if node == nil {
|
||||
return crypto.Tip5Zero, nil
|
||||
}
|
||||
|
||||
leftHash, err := node.Left.HashNode(hashFunc)
|
||||
if err != nil {
|
||||
return [5]uint64{}, err
|
||||
}
|
||||
rightHash, err := node.Right.HashNode(hashFunc)
|
||||
if err != nil {
|
||||
return [5]uint64{}, err
|
||||
}
|
||||
|
||||
hashLeftRight := crypto.Tip5RehashTenCell(leftHash, rightHash)
|
||||
valHash, err := hashFunc(node.Value)
|
||||
if err != nil {
|
||||
return [5]uint64{}, err
|
||||
}
|
||||
return crypto.Tip5RehashTenCell(valHash, hashLeftRight), nil
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user