From 26ec3b763e1268150d8a4fcf553096d7fb553c0d Mon Sep 17 00:00:00 2001 From: Manuel Haug Date: Fri, 12 Apr 2024 23:14:42 +0200 Subject: [PATCH] implement missing wallet methods and add transaction tests --- reducedtransaction.go | 5 + token.go | 8 ++ transaction.go | 5 + transaction_test.go | 309 ++++++++++++++++++++++++++++++++++++++++++ verifier.go | 20 +++ wallet.go | 141 ++++++++++++++++++- 6 files changed, 486 insertions(+), 2 deletions(-) create mode 100644 verifier.go diff --git a/reducedtransaction.go b/reducedtransaction.go index 64b2331..1d3a4a5 100644 --- a/reducedtransaction.go +++ b/reducedtransaction.go @@ -19,6 +19,7 @@ import ( type ReducedTransaction interface { // UnsignedTransaction returns the UnsignedTransaction UnsignedTransaction() UnsignedTransaction + pointer() C.ReducedTransactionPtr } type reducedTransaction struct { @@ -53,6 +54,10 @@ func (r *reducedTransaction) UnsignedTransaction() UnsignedTransaction { return newUnsignedTransaction(ut) } +func (r *reducedTransaction) pointer() C.ReducedTransactionPtr { + return r.p +} + func finalizeReducedTransaction(r *reducedTransaction) { C.ergo_lib_reduced_tx_delete(r.p) } diff --git a/token.go b/token.go index da5ed5d..d7dd200 100644 --- a/token.go +++ b/token.go @@ -44,6 +44,14 @@ func NewTokenId(s string) (TokenId, error) { return newTokenId(t), nil } +// NewTokenIdFromBoxId creates a TokenId from ergo box id (32 byte digest) +func NewTokenIdFromBoxId(boxId BoxId) TokenId { + var p C.TokenIdPtr + C.ergo_lib_token_id_from_box_id(boxId.pointer(), &p) + t := &tokenId{p: p} + return newTokenId(t) +} + func finalizeTokenId(t *tokenId) { C.ergo_lib_token_id_delete(t.p) } diff --git a/transaction.go b/transaction.go index e29b1cb..95345d9 100644 --- a/transaction.go +++ b/transaction.go @@ -156,6 +156,7 @@ type TransactionHintsBag interface { AddHintsForInput(index uint32, hintsBag HintsBag) // AllHintsForInput gets HintsBag corresponding to input index AllHintsForInput(index uint32) HintsBag + pointer() C.TransactionHintsBagPtr } type transactionHintsBag struct { @@ -188,6 +189,10 @@ func (t *transactionHintsBag) AllHintsForInput(index uint32) HintsBag { return newHintsBag(h) } +func (t *transactionHintsBag) pointer() C.TransactionHintsBagPtr { + return t.p +} + func finalizeTransactionHintsBag(t *transactionHintsBag) { C.ergo_lib_transaction_hints_bag_delete(t.p) } diff --git a/transaction_test.go b/transaction_test.go index 0b643e3..c6216a5 100644 --- a/transaction_test.go +++ b/transaction_test.go @@ -1,6 +1,7 @@ package ergo import ( + "encoding/hex" "github.com/stretchr/testify/assert" "testing" ) @@ -13,3 +14,311 @@ func TestTxId(t *testing.T) { assert.Equal(t, txIdStr, resTxIdStr) } + +func TestTxBuilder_Build(t *testing.T) { + recipient, _ := NewAddress("3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN") + boxJson := `{ + "boxId": "e56847ed19b3dc6b72828fcfb992fdf7310828cf291221269b7ffc72fd66706e", + "value": 67500000000, + "ergoTree": "100204a00b08cd021dde34603426402615658f1d970cfa7c7bd92ac81a8b16eeebff264d59ce4604ea02d192a39a8cc7a70173007301", + "assets": [], + "creationHeight": 284761, + "additionalRegisters": {}, + "transactionId": "9148408c04c2e38a6402a7950d6157730fa7d49e9ab3b9cadec481d7769918e9", + "index": 1 + }` + unspentBox, _ := NewBoxFromJson(boxJson) + unspentBoxes := NewBoxes() + unspentBoxes.Add(unspentBox) + + testContract, _ := NewContractPayToAddress(recipient) + + outBoxValue := SafeUserMinBoxValue() + outbox, _ := NewBoxCandidateBuilder(outBoxValue, testContract, 0).Build() + + txOutputs := NewBoxCandidates() + txOutputs.Add(outbox) + + fee := SuggestedTxFee() + changeAddr, _ := NewAddress("3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN") + + testDataInputs := NewDataInputs() + testBoxSelector := NewSimpleBoxSelector() + + targetBalance, _ := SumOfBoxValues(outBoxValue, fee) + + testBoxSelection, _ := testBoxSelector.Select(unspentBoxes, targetBalance, NewTokens()) + + testTxBuilder := NewTxBuilder(testBoxSelection, txOutputs, 0, fee, changeAddr) + testTxBuilder.SetDataInputs(testDataInputs) + + tx, txErr := testTxBuilder.Build() + assert.NoError(t, txErr) + txJson, txJsonErr := tx.JsonEIP12() + assert.NoError(t, txJsonErr) + assert.Equal(t, `{"inputs":[{"boxId":"e56847ed19b3dc6b72828fcfb992fdf7310828cf291221269b7ffc72fd66706e","extension":{}}],"data_inputs":[],"outputs":[{"value":"1000000","ergoTree":"0008cd02229ac0a22560d7bdfa4eb1de64e688390e85339c08aaf018b22d5ce93593192f","assets":[],"additionalRegisters":{},"creationHeight":0},{"value":"67497900000","ergoTree":"0008cd02229ac0a22560d7bdfa4eb1de64e688390e85339c08aaf018b22d5ce93593192f","assets":[],"additionalRegisters":{},"creationHeight":0},{"value":"1100000","ergoTree":"1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304","assets":[],"additionalRegisters":{},"creationHeight":0}]}`, txJson) +} + +func testBlockHeadersFromJson() BlockHeaders { + blockHeaderJsn := ` { + "extensionId": "d16f25b14457186df4c5f6355579cc769261ce1aebc8209949ca6feadbac5a3f", + "difficulty": "626412390187008", + "votes": "040000", + "timestamp": 1618929697400, + "size": 221, + "stateRoot": "8ad868627ea4f7de6e2a2fe3f98fafe57f914e0f2ef3331c006def36c697f92713", + "height": 471746, + "nBits": 117586360, + "version": 2, + "id": "4caa17e62fe66ba7bd69597afdc996ae35b1ff12e0ba90c22ff288a4de10e91b", + "adProofsRoot": "d882aaf42e0a95eb95fcce5c3705adf758e591532f733efe790ac3c404730c39", + "transactionsRoot": "63eaa9aff76a1de3d71c81e4b2d92e8d97ae572a8e9ab9e66599ed0912dd2f8b", + "extensionHash": "3f91f3c680beb26615fdec251aee3f81aaf5a02740806c167c0f3c929471df44", + "powSolutions": { + "pk": "02b3a06d6eaa8671431ba1db4dd427a77f75a5c2acbd71bfb725d38adc2b55f669", + "w": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "n": "5939ecfee6b0d7f4", + "d": 0 + }, + "adProofsId": "86eaa41f328bee598e33e52c9e515952ad3b7874102f762847f17318a776a7ae", + "transactionsId": "ac80245714f25aa2fafe5494ad02a26d46e7955b8f5709f3659f1b9440797b3e", + "parentId": "6481752bace5fa5acba5d5ef7124d48826664742d46c974c98a2d60ace229a34" + }` + testBlockHeaders := NewBlockHeaders() + for i := 0; i < 10; i++ { + testBlockHeader, _ := NewBlockHeader(blockHeaderJsn) + testBlockHeaders.Add(testBlockHeader) + } + return testBlockHeaders +} + +func TestWallet_SignTransaction(t *testing.T) { + sk := NewSecretKey() + inputContract, _ := NewContractPayToAddress(sk.Address()) + testTxIdStr := "93d344aa527e18e5a221db060ea1a868f46b61e4537e6e5f69ecc40334c15e38" + testTxId, _ := NewTxId(testTxIdStr) + inputBoxVal, _ := NewBoxValue(1000000000) + inputBox, _ := NewBox(inputBoxVal, 0, inputContract, testTxId, 0, NewTokens()) + + recipient, _ := NewAddress("3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN") + unspentBoxes := NewBoxes() + unspentBoxes.Add(inputBox) + testContract, _ := NewContractPayToAddress(recipient) + outBoxValue := SafeUserMinBoxValue() + outbox, _ := NewBoxCandidateBuilder(outBoxValue, testContract, 0).Build() + txOutputs := NewBoxCandidates() + txOutputs.Add(outbox) + fee := SuggestedTxFee() + changeAddr, _ := NewAddress("3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN") + testDataInputs := NewDataInputs() + testBoxSelector := NewSimpleBoxSelector() + + targetBalance, _ := SumOfBoxValues(outBoxValue, fee) + testBoxSelection, _ := testBoxSelector.Select(unspentBoxes, targetBalance, NewTokens()) + + testTxBuilder := NewTxBuilder(testBoxSelection, txOutputs, 0, fee, changeAddr) + testTxBuilder.SetDataInputs(testDataInputs) + + tx, txErr := testTxBuilder.Build() + assert.NoError(t, txErr) + _, txJsonErr := tx.JsonEIP12() + assert.NoError(t, txJsonErr) + + txDataInputs := NewBoxes() + + testBlockHeaders := testBlockHeadersFromJson() + testBlockHeader, _ := testBlockHeaders.Get(0) + testPreHeader := NewPreHeader(testBlockHeader) + + ctx, _ := NewStateContext(testPreHeader, testBlockHeaders) + testSecretKeys := NewSecretKeys() + testSecretKeys.Add(sk) + testWallet := NewWalletFromSecretKeys(testSecretKeys) + + signedTx, signingErr := testWallet.SignTransaction(ctx, tx, unspentBoxes, txDataInputs) + assert.NoError(t, signingErr) + _, signedTxJsonErr := signedTx.JsonEIP12() + assert.NoError(t, signedTxJsonErr) +} + +func TestMintToken(t *testing.T) { + recipient, _ := NewAddress("3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN") + boxJson := `{ + "boxId": "e56847ed19b3dc6b72828fcfb992fdf7310828cf291221269b7ffc72fd66706e", + "value": 67500000000, + "ergoTree": "100204a00b08cd021dde34603426402615658f1d970cfa7c7bd92ac81a8b16eeebff264d59ce4604ea02d192a39a8cc7a70173007301", + "assets": [], + "creationHeight": 284761, + "additionalRegisters": {}, + "transactionId": "9148408c04c2e38a6402a7950d6157730fa7d49e9ab3b9cadec481d7769918e9", + "index": 1 + }` + unspentBox, _ := NewBoxFromJson(boxJson) + unspentBoxes := NewBoxes() + unspentBoxes.Add(unspentBox) + + testContract, _ := NewContractPayToAddress(recipient) + outBoxValue := SafeUserMinBoxValue() + fee := SuggestedTxFee() + + testBoxSelector := NewSimpleBoxSelector() + + targetBalance, _ := SumOfBoxValues(outBoxValue, fee) + testBoxSelection, _ := testBoxSelector.Select(unspentBoxes, targetBalance, NewTokens()) + + // Mint token + mintBox, _ := testBoxSelection.Boxes().Get(0) + testTokenId := NewTokenIdFromBoxId(mintBox.BoxId()) + testTokenAmount, _ := NewTokenAmount(1) + testToken := NewToken(testTokenId, testTokenAmount) + testBoxBuilder := NewBoxCandidateBuilder(outBoxValue, testContract, 0) + testBoxBuilder.MintToken(testToken, "TKN", "token desc", 2) + outbox, _ := testBoxBuilder.Build() + + txOutputs := NewBoxCandidates() + txOutputs.Add(outbox) + changeAddr, _ := NewAddress("3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN") + testDataInputs := NewDataInputs() + + testTxBuilder := NewTxBuilder(testBoxSelection, txOutputs, 0, fee, changeAddr) + testTxBuilder.SetDataInputs(testDataInputs) + tx, txErr := testTxBuilder.Build() + assert.NoError(t, txErr) + _, txJsonErr := tx.JsonEIP12() + assert.NoError(t, txJsonErr) +} + +func TestBurnToken(t *testing.T) { + recipient, _ := NewAddress("3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN") + boxJson := `{ + "boxId": "0cf7b9e71961cc473242de389c8e594a4e5d630ddd2e4e590083fb0afb386341", + "value": 11491500000, + "ergoTree": "100f040005c801056404000e2019719268d230fd9093e4db0e2e42a07883ffe976e77c7419efc1bb218a05d4ba04000500043c040204c096b10204020101040205c096b1020400d805d601b2a5730000d602e4c6a70405d6039c9d720273017302d604b5db6501fed9010463ededed93e4c67204050ec5a7938cb2db6308720473030001730492e4c672040605997202720390e4c6720406059a72027203d605b17204ea02d1edededededed93cbc27201e4c6a7060e917205730593db63087201db6308a793e4c6720104059db072047306d9010641639a8c720601e4c68c72060206057e72050593e4c6720105049ae4c6a70504730792c1720199c1a77e9c9a720573087309058cb072048602730a730bd901063c400163d802d6088c720601d6098c72080186029a7209730ceded8c72080293c2b2a5720900d0cde4c68c720602040792c1b2a5720900730d02b2ad7204d9010663cde4c672060407730e00", + "assets": [ + { + "tokenId": "19475d9a78377ff0f36e9826cec439727bea522f6ffa3bda32e20d2f8b3103ac", + "amount": 1 + } + ], + "creationHeight": 348198, + "additionalRegisters": { + "R4": "059acd9109", + "R5": "04f2c02a", + "R6": "0e20277c78751ff6f68d4dcd082eeea9506324911a875b6b9cd4d177d4fcab061327" + }, + "transactionId": "5ed0e572a8c097b053965519a696f413f7be02754345e8ed650377e29a6dedb3", + "index": 0 + }` + unspentBox, _ := NewBoxFromJson(boxJson) + unspentBoxes := NewBoxes() + unspentBoxes.Add(unspentBox) + + testTokenId, _ := NewTokenId("19475d9a78377ff0f36e9826cec439727bea522f6ffa3bda32e20d2f8b3103ac") + testTokenAmount, _ := NewTokenAmount(1) + testToken := NewToken(testTokenId, testTokenAmount) + + testBoxSelector := NewSimpleBoxSelector() + + testTokens := NewTokens() + testTokens.Add(testToken) + + outBoxValue := SafeUserMinBoxValue() + fee := SuggestedTxFee() + + targetBalance, _ := SumOfBoxValues(outBoxValue, fee) + testBoxSelection, _ := testBoxSelector.Select(unspentBoxes, targetBalance, testTokens) + testContract, _ := NewContractPayToAddress(recipient) + testBoxBuilder := NewBoxCandidateBuilder(outBoxValue, testContract, 0) + outbox, _ := testBoxBuilder.Build() + txOutputs := NewBoxCandidates() + txOutputs.Add(outbox) + changeAddr, _ := NewAddress("3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN") + testDataInputs := NewDataInputs() + + testTxBuilder := NewTxBuilder(testBoxSelection, txOutputs, 0, fee, changeAddr) + testTxBuilder.SetDataInputs(testDataInputs) + _, buildErr := testTxBuilder.Build() + assert.Error(t, buildErr) + testTxBuilder.SetTokenBurnPermit(testTokens) + _, txErr := testTxBuilder.Build() + assert.NoError(t, txErr) +} + +func TestMultiSigTx(t *testing.T) { + aliceByteSecret, _ := hex.DecodeString("e726ad60a073a49f7851f4d11a83de6c9c7f99e17314fcce560f00a51a8a3d18") + aliceSecret, _ := NewSecretKeyFromBytes(aliceByteSecret) + bobByteSecret, _ := hex.DecodeString("9e6616b4e44818d21b8dfdd5ea87eb822480e7856ab910d00f5834dc64db79b3") + bobSecret, _ := NewSecretKeyFromBytes(bobByteSecret) + alicePkBytes, _ := hex.DecodeString("cd03c8e1527efae4be9868cea6767157fcccac66489842738efed0a302e4f81710d0") + + // Pay 2 Script address of a multi_sig contract with contract { alicePK && bobPK } + multiSigAddress, _ := NewAddress("JryiCXrc7x5D8AhS9DYX1TDzW5C5mT6QyTMQaptF76EQkM15cetxtYKq3u6LymLZLVCyjtgbTKFcfuuX9LLi49Ec5m2p6cwsg5NyEsCQ7na83yEPN") + inputContract, _ := NewContractPayToAddress(multiSigAddress) + testTxId, _ := NewTxId("0000000000000000000000000000000000000000000000000000000000000000") + testInputBoxValue, _ := NewBoxValue(1000000000) + testInputBox, _ := NewBox(testInputBoxValue, 0, inputContract, testTxId, 0, NewTokens()) + + // create a transaction that spends the "simulated" box + recipient, _ := NewAddress("3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN") + unspentBoxes := NewBoxes() + unspentBoxes.Add(testInputBox) + testContract, _ := NewContractPayToAddress(recipient) + outBoxValue := SafeUserMinBoxValue() + fee := SuggestedTxFee() + outbox, _ := NewBoxCandidateBuilder(outBoxValue, testContract, 0).Build() + txOutputs := NewBoxCandidates() + txOutputs.Add(outbox) + changeAddr, _ := NewAddress("3WvsT2Gm4EpsM9Pg18PdY6XyhNNMqXDsvJTbbf6ihLvAmSb7u5RN") + testDataInputs := NewDataInputs() + testBoxSelector := NewSimpleBoxSelector() + + targetBalance, _ := SumOfBoxValues(outBoxValue, fee) + testBoxSelection, _ := testBoxSelector.Select(unspentBoxes, targetBalance, NewTokens()) + testTxBuilder := NewTxBuilder(testBoxSelection, txOutputs, 0, fee, changeAddr) + testTxBuilder.SetDataInputs(testDataInputs) + + tx, buildErr := testTxBuilder.Build() + assert.NoError(t, buildErr) + + txDataInputs := NewBoxes() + + testBlockHeaders := testBlockHeadersFromJson() + testBlockHeader, _ := testBlockHeaders.Get(0) + testPreHeader := NewPreHeader(testBlockHeader) + ctx, _ := NewStateContext(testPreHeader, testBlockHeaders) + + sksAlice := NewSecretKeys() + sksAlice.Add(aliceSecret) + walletAlice := NewWalletFromSecretKeys(sksAlice) + + sksBob := NewSecretKeys() + sksBob.Add(bobSecret) + walletBob := NewWalletFromSecretKeys(sksBob) + + bobGeneratedCommitments, _ := walletBob.GenerateCommitments(ctx, tx, unspentBoxes, txDataInputs) + bobHints := bobGeneratedCommitments.AllHintsForInput(0) + bobKnown, _ := bobHints.Get(0) + bobOwn, _ := bobHints.Get(1) + + testHintsBag := NewHintsBag() + testHintsBag.Add(bobKnown) + + aliceTxHintsBag := NewTransactionHintsBag() + aliceTxHintsBag.AddHintsForInput(0, testHintsBag) + + partialSigned, _ := walletAlice.SignTransactionMulti(ctx, tx, unspentBoxes, txDataInputs, aliceTxHintsBag) + + realPropositions := NewPropositions() + simulatedPropositions := NewPropositions() + _ = realPropositions.Add(alicePkBytes) + + bobExtractedHints, _ := ExtractHintsFromSignedTransaction(partialSigned, ctx, unspentBoxes, txDataInputs, realPropositions, simulatedPropositions) + bobHintsBag := bobExtractedHints.AllHintsForInput(0) + bobHintsBag.Add(bobOwn) + + bobTxHintsBag := NewTransactionHintsBag() + bobTxHintsBag.AddHintsForInput(0, bobHintsBag) + + _, signErr := walletBob.SignTransactionMulti(ctx, tx, unspentBoxes, txDataInputs, bobTxHintsBag) + assert.NoError(t, signErr) +} diff --git a/verifier.go b/verifier.go new file mode 100644 index 0000000..8afe9c2 --- /dev/null +++ b/verifier.go @@ -0,0 +1,20 @@ +package ergo + +/* +#include "ergo.h" +*/ +import "C" +import "unsafe" + +// VerifySignature verifies that the signature is presented to satisfy SigmaProp conditions +func VerifySignature(address Address, message []byte, signature SignedMessage) (bool, error) { + byteData := C.CBytes(message) + defer C.free(unsafe.Pointer(byteData)) + + res := C.ergo_lib_verify_signature(address.pointer(), (*C.uchar)(byteData), C.ulong(len(message)), signature.pointer()) + err := newError(res.error) + if err.isError() { + return false, err.error() + } + return bool(res.value), nil +} diff --git a/wallet.go b/wallet.go index e9f8332..44bc76e 100644 --- a/wallet.go +++ b/wallet.go @@ -79,12 +79,33 @@ func finalizeMnemonicGenerator(m *mnemonicGenerator) { } type Wallet interface { + // AddSecret adds a secret to the wallets prover + AddSecret(secret SecretKey) error + // SignTransaction signs a transaction + SignTransaction(stateContext StateContext, unsignedTx UnsignedTransaction, boxesToSpend Boxes, dataBoxes Boxes) (Transaction, error) + // SignTransactionMulti signs a multi signature transaction + SignTransactionMulti(stateContext StateContext, unsignedTx UnsignedTransaction, boxesToSpend Boxes, dataBoxes Boxes, txHints TransactionHintsBag) (Transaction, error) + // SignReducedTransaction signs a reduced transaction (generating proofs for inputs) + SignReducedTransaction(reducedTx ReducedTransaction) (Transaction, error) + // SignReducedTransactionMulti signs a multi signature reduced transaction + SignReducedTransactionMulti(reducedTx ReducedTransaction, txHints TransactionHintsBag) (Transaction, error) + // GenerateCommitments generates Commitments for unsigned tx + GenerateCommitments(stateContext StateContext, unsignedTx UnsignedTransaction, boxesToSpend Boxes, dataBoxes Boxes) (TransactionHintsBag, error) + // GenerateCommitmentsForReducedTransaction generates Commitments for reduced transaction + GenerateCommitmentsForReducedTransaction(reducedTx ReducedTransaction) (TransactionHintsBag, error) + // SignMessageUsingP2PK signs an arbitrary message using a P2PK address + SignMessageUsingP2PK(address Address, message []byte) (SignedMessage, error) } type wallet struct { p C.WalletPtr } +func newWallet(w *wallet) Wallet { + runtime.SetFinalizer(w, finalizeWallet) + return w +} + // NewWallet creates a Wallet instance loading secret key from mnemonic or throws error if a DlogSecretKey cannot be parsed from the provided phrase func NewWallet(mnemonicPhrase string, mnemonicPassword string) (Wallet, error) { mnemonic := C.CString(mnemonicPhrase) @@ -103,11 +124,127 @@ func NewWallet(mnemonicPhrase string, mnemonicPassword string) (Wallet, error) { w := &wallet{p: p} - runtime.SetFinalizer(w, finalizeWallet) + return newWallet(w), nil +} + +// NewWalletFromSecretKeys creates a Wallet from secrets +func NewWalletFromSecretKeys(secrets SecretKeys) Wallet { + var p C.WalletPtr + C.ergo_lib_wallet_from_secrets(secrets.pointer(), &p) + w := &wallet{p: p} + return newWallet(w) +} + +func (w *wallet) AddSecret(secret SecretKey) error { + errPtr := C.ergo_lib_wallet_add_secret(w.p, secret.pointer()) + err := newError(errPtr) + if err.isError() { + return err.error() + } + return nil +} + +func (w *wallet) SignTransaction(stateContext StateContext, unsignedTx UnsignedTransaction, boxesToSpend Boxes, dataBoxes Boxes) (Transaction, error) { + var p C.TransactionPtr + errPtr := C.ergo_lib_wallet_sign_transaction(w.p, stateContext.pointer(), unsignedTx.pointer(), boxesToSpend.pointer(), dataBoxes.pointer(), &p) + err := newError(errPtr) + if err.isError() { + return nil, err.error() + } + t := &transaction{p: p} + return newTransaction(t), nil +} + +func (w *wallet) SignTransactionMulti(stateContext StateContext, unsignedTx UnsignedTransaction, boxesToSpend Boxes, dataBoxes Boxes, txHints TransactionHintsBag) (Transaction, error) { + var p C.TransactionPtr + errPtr := C.ergo_lib_wallet_sign_transaction_multi(w.p, stateContext.pointer(), unsignedTx.pointer(), boxesToSpend.pointer(), dataBoxes.pointer(), txHints.pointer(), &p) + err := newError(errPtr) + if err.isError() { + return nil, err.error() + } + t := &transaction{p: p} + return newTransaction(t), nil +} + +func (w *wallet) SignReducedTransaction(reducedTx ReducedTransaction) (Transaction, error) { + var p C.TransactionPtr + errPtr := C.ergo_lib_wallet_sign_reduced_transaction(w.p, reducedTx.pointer(), &p) + err := newError(errPtr) + if err.isError() { + return nil, err.error() + } + t := &transaction{p: p} + return newTransaction(t), nil +} + +func (w *wallet) SignReducedTransactionMulti(reducedTx ReducedTransaction, txHints TransactionHintsBag) (Transaction, error) { + var p C.TransactionPtr + errPtr := C.ergo_lib_wallet_sign_reduced_transaction_multi(w.p, reducedTx.pointer(), txHints.pointer(), &p) + err := newError(errPtr) + if err.isError() { + return nil, err.error() + } + t := &transaction{p: p} + return newTransaction(t), nil +} - return w, nil +func (w *wallet) GenerateCommitments(stateContext StateContext, unsignedTx UnsignedTransaction, boxesToSpend Boxes, dataBoxes Boxes) (TransactionHintsBag, error) { + var p C.TransactionHintsBagPtr + errPtr := C.ergo_lib_wallet_generate_commitments(w.p, stateContext.pointer(), unsignedTx.pointer(), boxesToSpend.pointer(), dataBoxes.pointer(), &p) + err := newError(errPtr) + if err.isError() { + return nil, err.error() + } + th := &transactionHintsBag{p: p} + return newTransactionHintsBag(th), nil +} + +func (w *wallet) GenerateCommitmentsForReducedTransaction(reducedTx ReducedTransaction) (TransactionHintsBag, error) { + var p C.TransactionHintsBagPtr + errPtr := C.ergo_lib_wallet_generate_commitments_for_reduced_transaction(w.p, reducedTx.pointer(), &p) + err := newError(errPtr) + if err.isError() { + return nil, err.error() + } + th := &transactionHintsBag{p: p} + return newTransactionHintsBag(th), nil +} + +func (w *wallet) SignMessageUsingP2PK(address Address, message []byte) (SignedMessage, error) { + byteData := C.CBytes(message) + defer C.free(unsafe.Pointer(byteData)) + + var p C.SignedMessagePtr + errPtr := C.ergo_lib_wallet_sign_message_using_p2pk(w.p, address.pointer(), (*C.uchar)(byteData), C.ulong(len(message)), &p) + err := newError(errPtr) + if err.isError() { + return nil, err.error() + } + sm := &signedMessage{p: p} + return newSignedMessage(sm), nil } func finalizeWallet(w *wallet) { C.ergo_lib_wallet_delete(w.p) } + +type SignedMessage interface { + pointer() C.SignedMessagePtr +} + +type signedMessage struct { + p C.SignedMessagePtr +} + +func newSignedMessage(s *signedMessage) SignedMessage { + runtime.SetFinalizer(s, finalizeSignedMessage) + return s +} + +func (s *signedMessage) pointer() C.SignedMessagePtr { + return s.p +} + +func finalizeSignedMessage(s *signedMessage) { + C.ergo_lib_signed_message_delete(s.p) +}