diff --git a/wallet/nockchain_service.go b/wallet/nockchain_service.go index 5ad5aa3..7faf21f 100644 --- a/wallet/nockchain_service.go +++ b/wallet/nockchain_service.go @@ -113,3 +113,39 @@ func (nc *NockchainClient) TxAccepted(txId string) (*nockchain.TransactionAccept return nil, fmt.Errorf("invalid result type") } } + +func (nc *NockchainClient) WalletSendTransaction(tx *nockchain.RawTx) (*nockchain.WalletSendTransactionResponse, error) { + inputs := []*nockchain.NamedInput{} + for _, i := range tx.Inputs { + input, err := ConvertInput(i) + if err != nil { + return nil, fmt.Errorf("error converting input: %v", err) + } + inputs = append(inputs, input) + } + req := nockchain.WalletSendTransactionRequest{ + TxId: ParseHash(tx.TxId), + RawTx: &nockchain.RawTransaction{ + NamedInputs: inputs, + TotalFees: &nockchain.Nicks{Value: tx.TotalFees}, + TimelockRange: &nockchain.TimeLockRangeAbsolute{ + Min: (*nockchain.BlockHeight)(tx.TimelockRange.Min), + Max: (*nockchain.BlockHeight)(tx.TimelockRange.Max), + }, + }, + } + + resp, err := nc.client.WalletSendTransaction(context.Background(), &req) + if err != nil { + return nil, err + } + + switch resp.Result.(type) { + case *nockchain.WalletSendTransactionResponse_Ack: + return resp, nil + case *nockchain.WalletSendTransactionResponse_Error: + return nil, fmt.Errorf("error: %s", resp.GetError().Message) + default: + return nil, fmt.Errorf("invalid result type") + } +} diff --git a/wallet/nockhash.go b/wallet/nockhash.go index 70023f1..1d45183 100644 --- a/wallet/nockhash.go +++ b/wallet/nockhash.go @@ -313,109 +313,6 @@ 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: true, - }, - Asset: entry.Note.Assets.Value, - } -} - -func ParseTimelockIntent(timelock *nockchain.TimeLockIntent) *nockchain.TimelockIntent { - if timelock == nil { - return nil - } - 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/types.go b/wallet/types.go new file mode 100644 index 0000000..38d12de --- /dev/null +++ b/wallet/types.go @@ -0,0 +1,323 @@ +package wallet + +import ( + "github.com/btcsuite/btcd/btcutil/base58" + "github.com/phamminh0811/private-grpc/crypto" + "github.com/phamminh0811/private-grpc/nockchain" +) + +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: true, + }, + Asset: entry.Note.Assets.Value, + } +} + +func ConvertRawTx(rawTx *nockchain.RawTx) nockchain.RawTransaction { + id := crypto.Base58ToTip5Hash(rawTx.TxId) + + var timelockRange *nockchain.TimeLockRangeAbsolute + if rawTx.TimelockRange != nil { + timelockRange = &nockchain.TimeLockRangeAbsolute{ + Min: (*nockchain.BlockHeight)(rawTx.TimelockRange.Min), + Max: (*nockchain.BlockHeight)(rawTx.TimelockRange.Max), + } + } + + return nockchain.RawTransaction{ + Id: &nockchain.Hash{ + Belt_1: &nockchain.Belt{Value: id[0]}, + Belt_2: &nockchain.Belt{Value: id[1]}, + Belt_3: &nockchain.Belt{Value: id[2]}, + Belt_4: &nockchain.Belt{Value: id[3]}, + Belt_5: &nockchain.Belt{Value: id[4]}, + }, + TimelockRange: timelockRange, + TotalFees: &nockchain.Nicks{ + Value: rawTx.TotalFees, + }, + } +} + +func ConvertInput(input *nockchain.NockchainInput) (*nockchain.NamedInput, error) { + pks := []*nockchain.SchnorrPubkey{} + + for _, i := range input.Note.Lock.Pubkeys { + pk, err := ParseSchnorrPubkey(i) + if err != nil { + return nil, err + } + pks = append(pks, pk) + } + seeds := []*nockchain.Seed{} + for _, seed := range input.Spend.Seeds { + seedPks := []*nockchain.SchnorrPubkey{} + + for _, i := range input.Note.Lock.Pubkeys { + pk, err := ParseSchnorrPubkey(i) + if err != nil { + return nil, err + } + pks = append(seedPks, pk) + } + seeds = append(seeds, &nockchain.Seed{ + OutputSource: &nockchain.OutputSource{ + Source: &nockchain.Source{ + Hash: ParseHash(seed.OutputSource.Source), + Coinbase: input.Note.Source.IsCoinbase, + }, + }, + Recipient: &nockchain.Lock{ + KeysRequired: uint32(seed.Recipient.KeysRequired), + SchnorrPubkeys: seedPks, + }, + TimelockIntent: ConvertTimelockIntent(seed.TimelockIntent), + Gift: &nockchain.Nicks{Value: seed.Gift}, + ParentHash: ParseHash(seed.ParentHash), + }) + } + + sigEntries := []*nockchain.SignatureEntry{} + for _, sig := range input.Spend.Signatures { + pk, err := ParseSchnorrPubkey(sig.Pubkey) + if err != nil { + return nil, err + } + sigEntries = append(sigEntries, &nockchain.SignatureEntry{ + SchnorrPubkey: pk, + Signature: &nockchain.SchnorrSignature{ + Chal: ParseEightBelt(sig.Chal), + Sig: ParseEightBelt(sig.Sig), + }, + }) + } + return &nockchain.NamedInput{ + Name: ConvertName(input.Name), + Input: &nockchain.Input{ + Note: &nockchain.Note{ + OriginPage: &nockchain.BlockHeight{ + Value: input.Note.BlockHeight, + }, + Timelock: ConvertTimelockIntent(input.Note.Timelock), + Name: ConvertName(input.Name), + Lock: &nockchain.Lock{ + KeysRequired: uint32(input.Note.Lock.KeysRequired), + SchnorrPubkeys: pks, + }, + Source: &nockchain.Source{ + Hash: ParseHash(input.Note.Source.Source), + Coinbase: input.Note.Source.IsCoinbase, + }, + Assets: &nockchain.Nicks{Value: input.Note.Asset}, + Version: &nockchain.NoteVersion{Value: uint32(input.Note.Version)}, + }, + Spend: &nockchain.Spend{ + Signature: &nockchain.Signature{ + Entries: sigEntries, + }, + Seeds: seeds, + MinerFeeNicks: &nockchain.Nicks{Value: input.Spend.Fee}, + }, + }, + }, nil +} + +func ConvertName(name *nockchain.NockchainName) *nockchain.Name { + return &nockchain.Name{ + First: ParseHash(name.First), + Last: ParseHash(name.Last), + } +} +func ConvertTimelockIntent(timelock *nockchain.TimelockIntent) *nockchain.TimeLockIntent { + if timelock == nil { + return nil + } + + if timelock.Absolute != nil && timelock.Relative != nil { + return &nockchain.TimeLockIntent{ + Value: &nockchain.TimeLockIntent_AbsoluteAndRelative{ + AbsoluteAndRelative: &nockchain.TimeLockRangeAbsoluteAndRelative{ + Absolute: &nockchain.TimeLockRangeAbsolute{ + Min: (*nockchain.BlockHeight)(timelock.Absolute.Min), + Max: (*nockchain.BlockHeight)(timelock.Absolute.Max), + }, + Relative: &nockchain.TimeLockRangeRelative{ + Min: (*nockchain.BlockHeightDelta)(timelock.Relative.Min), + Max: (*nockchain.BlockHeightDelta)(timelock.Relative.Max), + }, + }, + }, + } + } + + if timelock.Absolute != nil { + return &nockchain.TimeLockIntent{ + Value: &nockchain.TimeLockIntent_Absolute{ + Absolute: &nockchain.TimeLockRangeAbsolute{ + Min: (*nockchain.BlockHeight)(timelock.Absolute.Min), + Max: (*nockchain.BlockHeight)(timelock.Absolute.Max), + }, + }, + } + } + + if timelock.Relative != nil { + return &nockchain.TimeLockIntent{ + Value: &nockchain.TimeLockIntent_Relative{ + Relative: &nockchain.TimeLockRangeRelative{ + Min: (*nockchain.BlockHeightDelta)(timelock.Relative.Min), + Max: (*nockchain.BlockHeightDelta)(timelock.Relative.Max), + }, + }, + } + } + + return nil +} + +func ParseHash(hash string) *nockchain.Hash { + hashTip5 := crypto.Base58ToTip5Hash(hash) + return &nockchain.Hash{ + Belt_1: &nockchain.Belt{Value: hashTip5[0]}, + Belt_2: &nockchain.Belt{Value: hashTip5[1]}, + Belt_3: &nockchain.Belt{Value: hashTip5[2]}, + Belt_4: &nockchain.Belt{Value: hashTip5[3]}, + Belt_5: &nockchain.Belt{Value: hashTip5[4]}, + } +} + +func ParseEightBelt(data []uint64) *nockchain.EightBelt { + return &nockchain.EightBelt{ + Belt_1: &nockchain.Belt{Value: data[0]}, + Belt_2: &nockchain.Belt{Value: data[1]}, + Belt_3: &nockchain.Belt{Value: data[2]}, + Belt_4: &nockchain.Belt{Value: data[3]}, + Belt_5: &nockchain.Belt{Value: data[4]}, + Belt_6: &nockchain.Belt{Value: data[5]}, + Belt_7: &nockchain.Belt{Value: data[6]}, + Belt_8: &nockchain.Belt{Value: data[7]}, + } +} +func ParseSchnorrPubkey(key string) (*nockchain.SchnorrPubkey, error) { + pk, err := crypto.CheetaPointFromBytes(base58.Decode(key)) + if err != nil { + return nil, err + } + return &nockchain.SchnorrPubkey{ + Value: &nockchain.CheetahPoint{ + X: &nockchain.SixBelt{ + Belt_1: &nockchain.Belt{Value: pk.X[0].Value}, + Belt_2: &nockchain.Belt{Value: pk.X[1].Value}, + Belt_3: &nockchain.Belt{Value: pk.X[2].Value}, + Belt_4: &nockchain.Belt{Value: pk.X[3].Value}, + Belt_5: &nockchain.Belt{Value: pk.X[4].Value}, + Belt_6: &nockchain.Belt{Value: pk.X[5].Value}, + }, + Y: &nockchain.SixBelt{ + Belt_1: &nockchain.Belt{Value: pk.Y[0].Value}, + Belt_2: &nockchain.Belt{Value: pk.Y[1].Value}, + Belt_3: &nockchain.Belt{Value: pk.Y[2].Value}, + Belt_4: &nockchain.Belt{Value: pk.Y[3].Value}, + Belt_5: &nockchain.Belt{Value: pk.Y[4].Value}, + Belt_6: &nockchain.Belt{Value: pk.Y[5].Value}, + }, + Inf: pk.Inf, + }, + }, nil +} +func ParseTimelockIntent(timelock *nockchain.TimeLockIntent) *nockchain.TimelockIntent { + if timelock == nil { + return nil + } + 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 + } +}