diff --git a/wallet/nockhash.go b/wallet/nockhash.go index 5153097..d6b79da 100644 --- a/wallet/nockhash.go +++ b/wallet/nockhash.go @@ -1,7 +1,6 @@ package wallet import ( - "fmt" "math/big" "github.com/btcsuite/btcd/btcutil/base58" @@ -153,6 +152,23 @@ func HashSource(source *nockchain.NockchainSource) [5]uint64 { } } +func HashParent(note *nockchain.NockchainNote) ([5]uint64, error) { + nameHash := HashName(note.Name) + lockHash, err := HashLock(note.Lock) + if err != nil { + return [5]uint64{}, err + } + + sourceHash := HashSource(note.Source) + assetHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: uint64(note.Asset)}}) + + hashSourceAsset := crypto.Tip5RehashTenCell(sourceHash, assetHash) + + q := crypto.Tip5RehashTenCell(nameHash, crypto.Tip5RehashTenCell(lockHash, hashSourceAsset)) + + return q, nil +} + func HashSeedWithoutSource(seed *nockchain.NockchainSeed) ([5]uint64, error) { lockHash, err := HashLock(seed.Recipient) if err != nil { @@ -217,7 +233,6 @@ func HashSpend(spend *nockchain.NockchainSpend) ([5]uint64, error) { if err != nil { return [5]uint64{}, err } - fmt.Println("") finalSeedHash = crypto.Tip5RehashTenCell(seedHash, finalSeedHash) } } @@ -227,6 +242,29 @@ func HashSpend(spend *nockchain.NockchainSpend) ([5]uint64, error) { } +func HashMsg(spend *nockchain.NockchainSpend) ([5]uint64, error) { + seedsCount := len(spend.Seeds) + finalSeedHash, err := HashSeed(spend.Seeds[seedsCount-1]) + + if err != nil { + return [5]uint64{}, err + } + finalSeedHash = crypto.Tip5RehashTenCell(finalSeedHash, crypto.Tip5ZeroZero) + finalSeedHash = crypto.Tip5RehashTenCell(finalSeedHash, crypto.Tip5Zero) + + if seedsCount != 1 { + for i := 1; i < seedsCount; i++ { + seedHash, err := HashSeed(spend.Seeds[seedsCount-1-i]) + if err != nil { + return [5]uint64{}, err + } + finalSeedHash = crypto.Tip5RehashTenCell(seedHash, finalSeedHash) + } + } + feeHash := crypto.Tip5HashBelts([]crypto.Belt{{Value: 1}, {Value: spend.Fee}}) + return crypto.Tip5RehashTenCell(finalSeedHash, feeHash), nil +} + func HashInput(input *nockchain.NockchainInput) ([5]uint64, error) { nameHash := HashName(input.Name) @@ -287,6 +325,106 @@ func ComputeSig(m crypto.MasterKey, msg [5]uint64) ([8]uint64, [8]uint64, error) return chalT8, sigT8, nil } +func ParseBalanceEntry(entry *nockchain.BalanceEntry) nockchain.NockchainNote { + version := nockchain.Version(entry.Note.Version.Value) + pubkeys := []string{} + for _, pk := range entry.Note.Lock.SchnorrPubkeys { + pkPoint := crypto.CheetahPoint{ + X: [6]crypto.Belt{ + {Value: pk.Value.X.Belt_1.Value}, + {Value: pk.Value.X.Belt_2.Value}, + {Value: pk.Value.X.Belt_3.Value}, + {Value: pk.Value.X.Belt_4.Value}, + {Value: pk.Value.X.Belt_5.Value}, + {Value: pk.Value.X.Belt_6.Value}, + }, + Y: [6]crypto.Belt{ + {Value: pk.Value.Y.Belt_1.Value}, + {Value: pk.Value.Y.Belt_2.Value}, + {Value: pk.Value.Y.Belt_3.Value}, + {Value: pk.Value.Y.Belt_4.Value}, + {Value: pk.Value.Y.Belt_5.Value}, + {Value: pk.Value.Y.Belt_6.Value}, + }, + Inf: pk.Value.Inf, + } + + pkStr := base58.Encode(pkPoint.Bytes()) + pubkeys = append(pubkeys, pkStr) + } + + sourceHash := [5]uint64{ + entry.Note.Source.Hash.Belt_1.Value, + entry.Note.Source.Hash.Belt_2.Value, + entry.Note.Source.Hash.Belt_3.Value, + entry.Note.Source.Hash.Belt_4.Value, + entry.Note.Source.Hash.Belt_5.Value, + } + return nockchain.NockchainNote{ + Version: version, + BlockHeight: entry.Note.OriginPage.Value, + Timelock: ParseTimelockIntent(entry.Note.Timelock), + Name: &nockchain.NockchainName{ + First: crypto.Tip5HashToBase58([5]uint64{ + entry.Name.First.Belt_1.Value, + entry.Name.First.Belt_2.Value, + entry.Name.First.Belt_3.Value, + entry.Name.First.Belt_4.Value, + entry.Name.First.Belt_5.Value, + }), + Last: crypto.Tip5HashToBase58([5]uint64{ + entry.Name.Last.Belt_1.Value, + entry.Name.Last.Belt_2.Value, + entry.Name.Last.Belt_3.Value, + entry.Name.Last.Belt_4.Value, + entry.Name.Last.Belt_5.Value, + }), + }, + Lock: &nockchain.NockchainLock{ + KeysRequired: uint64(entry.Note.Lock.KeysRequired), + Pubkeys: pubkeys, + }, + Source: &nockchain.NockchainSource{ + Source: crypto.Tip5HashToBase58(sourceHash), + IsCoinbase: entry.Note.Source.Coinbase, + }, + Asset: entry.Note.Assets.Value, + } +} + +func ParseTimelockIntent(timelock *nockchain.TimeLockIntent) *nockchain.TimelockIntent { + switch timelock.Value.(type) { + case *nockchain.TimeLockIntent_Absolute: + return &nockchain.TimelockIntent{ + Absolute: &nockchain.TimelockRange{ + Min: (*nockchain.Timelock)(timelock.GetAbsolute().Min), + Max: (*nockchain.Timelock)(timelock.GetAbsolute().Max), + }, + Relative: nil, + } + case *nockchain.TimeLockIntent_Relative: + return &nockchain.TimelockIntent{ + Absolute: nil, + Relative: &nockchain.TimelockRange{ + Min: (*nockchain.Timelock)(timelock.GetRelative().Min), + Max: (*nockchain.Timelock)(timelock.GetRelative().Max), + }, + } + case *nockchain.TimeLockIntent_AbsoluteAndRelative: + return &nockchain.TimelockIntent{ + Absolute: &nockchain.TimelockRange{ + Min: (*nockchain.Timelock)(timelock.GetAbsoluteAndRelative().Absolute.Min), + Max: (*nockchain.Timelock)(timelock.GetAbsoluteAndRelative().Absolute.Max), + }, + Relative: &nockchain.TimelockRange{ + Min: (*nockchain.Timelock)(timelock.GetAbsoluteAndRelative().Relative.Min), + Max: (*nockchain.Timelock)(timelock.GetAbsoluteAndRelative().Relative.Max), + }, + } + default: + return nil + } +} func first(ownerHash [5]uint64) [5]uint64 { ownerHashZero := crypto.Tip5RehashTenCell(ownerHash, crypto.Tip5Zero) ownerHashZeroOne := crypto.Tip5RehashTenCell(crypto.Tip5One, ownerHashZero) diff --git a/wallet/service.go b/wallet/service.go index 23c4ae3..417b0f8 100644 --- a/wallet/service.go +++ b/wallet/service.go @@ -263,7 +263,9 @@ func (h *GprcHandler) DeriveChild(ctx context.Context, req *nockchain.DeriveChil func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxRequest) (*nockchain.CreateTxResponse, error) { firstNames := [][5]uint64{} lastNames := [][5]uint64{} - for _, name := range strings.Split(req.Names, ",") { + names := strings.Split(req.Names, ",") + notes := make([]*nockchain.NockchainNote, len(names)) + for _, name := range names { name = strings.TrimSpace(name) if strings.HasPrefix(name, "[") && strings.HasSuffix(name, "]") { inner := name[1 : len(name)-1] @@ -275,11 +277,7 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque } } - type Recipent struct { - index uint64 - pubkeys [][]byte - } - recipents := []Recipent{} + recipents := []*nockchain.NockchainLock{} if strings.Contains(req.Recipients, "[") { pairs := strings.Split(req.Recipients, ",") for _, pair := range pairs { @@ -295,13 +293,9 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque continue } - pubkeyStrs := strings.Split(parts[1], ",") - var pubkeys [][]byte - for _, s := range pubkeyStrs { - pubkeys = append(pubkeys, base58.Decode(strings.TrimSpace(s))) - } + pubkeysStr := strings.Split(parts[1], ",") - recipents = append(recipents, Recipent{index: number, pubkeys: pubkeys}) + recipents = append(recipents, &nockchain.NockchainLock{KeysRequired: number, Pubkeys: pubkeysStr}) } } } @@ -310,7 +304,7 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque addrs := strings.Split(req.Recipients, ",") for _, addr := range addrs { - recipents = append(recipents, Recipent{index: uint64(1), pubkeys: [][]byte{base58.Decode(strings.TrimSpace(addr))}}) + recipents = append(recipents, &nockchain.NockchainLock{KeysRequired: uint64(1), Pubkeys: []string{strings.TrimSpace(addr)}}) } } gifts := []uint64{} @@ -372,9 +366,130 @@ func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxReque return nil, err } if len(masterKeyScan.Notes) != 0 { - // TODO: check notes by first and last name + for _, note := range masterKeyScan.Notes { + firstName := crypto.Tip5HashToBase58([5]uint64{ + note.Name.First.Belt_1.Value, + note.Name.First.Belt_2.Value, + note.Name.First.Belt_3.Value, + note.Name.First.Belt_4.Value, + note.Name.First.Belt_5.Value, + }) + lastName := crypto.Tip5HashToBase58([5]uint64{ + note.Name.Last.Belt_1.Value, + note.Name.Last.Belt_2.Value, + note.Name.Last.Belt_3.Value, + note.Name.Last.Belt_4.Value, + note.Name.Last.Belt_5.Value, + }) + name := "[" + firstName + " " + lastName + "]" + if i := slices.Index(names, name); i != -1 { + balanceEntry := ParseBalanceEntry(note) + notes[i] = &balanceEntry + } + } } - return nil, nil + + 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]) + } + + if notes[i].Asset < gifts[i]+req.Fee { + return nil, fmt.Errorf("note not enough balance") + } + + parentHash, err := HashParent(notes[i]) + if err != nil { + return nil, err + } + seeds := []*nockchain.NockchainSeed{ + { + OutputSource: nil, + Recipient: recipents[i], + TimelockIntent: req.TimelockIntent, + Gift: gifts[i], + ParentHash: crypto.Tip5HashToBase58(parentHash), + }, + } + + if notes[i].Asset < gifts[i]+req.Fee { + seeds = append(seeds, &nockchain.NockchainSeed{ + OutputSource: nil, + Recipient: &nockchain.NockchainLock{ + KeysRequired: 1, + Pubkeys: []string{base58.Encode(masterKey.PublicKey)}, + }, + TimelockIntent: req.TimelockIntent, + Gift: notes[i].Asset - gifts[i] - req.Fee, + ParentHash: crypto.Tip5HashToBase58(parentHash), + }) + } + + spend := nockchain.NockchainSpend{ + Signatures: nil, + Seeds: seeds, + Fee: req.Fee, + } + + msg, err := HashMsg(&spend) + if err != nil { + return nil, err + } + + if len(masterKey.PrivateKey) != 0 { + // sign + chalT8, sigT8, err := ComputeSig(*masterKey, msg) + if err != nil { + return nil, err + } + + spend.Signatures = []*nockchain.NockchainSignature{ + { + Pubkey: base58.Encode(masterKey.PublicKey), + Chal: chalT8[:], + Sig: sigT8[:], + }, + } + } + + input := nockchain.NockchainInput{ + Name: &nockchain.NockchainName{ + First: crypto.Tip5HashToBase58(firstNames[i]), + Last: crypto.Tip5HashToBase58(lastNames[i]), + }, + Note: notes[i], + Spend: &spend, + } + inputs = append(inputs, &input) + } + + var timelockRange *nockchain.TimelockRange + if req.TimelockIntent == nil { + timelockRange = nil + } else { + if req.TimelockIntent.Absolute != nil { + timelockRange = req.TimelockIntent.Absolute + } + if req.TimelockIntent.Relative != nil { + timelockRange = req.TimelockIntent.Relative + + } + } + rawTx := nockchain.RawTx{ + TxId: "", + Inputs: inputs, + TimelockRange: timelockRange, + TotalFees: req.Fee, + } + txId, err := ComputeTxId(inputs, timelockRange, req.Fee) + if err != nil { + return nil, err + } + rawTx.TxId = crypto.Tip5HashToBase58(txId) + return &nockchain.CreateTxResponse{ + RawTx: &rawTx, + }, nil } func (h *GprcHandler) Scan(ctx context.Context, req *nockchain.ScanRequest) (*nockchain.ScanResponse, error) {