From 8ab64b594322c758759d0db3c4a7b521e6334e6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Lewandowski?= Date: Fri, 6 Oct 2023 10:20:40 +0200 Subject: [PATCH] chore(BUX-164): move BEEF tx SPV to separate file --- p2p_beef_tx.go | 137 ------------------------------ p2p_beef_tx_test.go | 6 +- p2p_spv.go | 128 ++++++++++++++++++++++++++++ server/mock_test.go | 4 +- server/p2p_receive_transaction.go | 2 +- 5 files changed, 134 insertions(+), 143 deletions(-) create mode 100644 p2p_spv.go diff --git a/p2p_beef_tx.go b/p2p_beef_tx.go index 67e1428..a71c4df 100644 --- a/p2p_beef_tx.go +++ b/p2p_beef_tx.go @@ -1,23 +1,13 @@ package paymail import ( - "context" "encoding/hex" "errors" "fmt" - "github.com/libsv/bitcoin-hc/transports/http/endpoints/api/merkleroots" "github.com/libsv/go-bc" "github.com/libsv/go-bt/v2" - "github.com/libsv/go-bt/v2/bscript/interpreter" ) -type MerkleRootVerifier interface { - VerifyMerkleRoots( - ctx context.Context, - merkleRoots []string, - ) (*merkleroots.MerkleRootsConfirmationsResponse, error) -} - const ( BEEFMarkerPart1 = 0xBE BEEFMarkerPart2 = 0xEF @@ -57,133 +47,6 @@ func (dBeef *DecodedBEEF) GetMerkleRoots() ([]string, error) { return merkleRoots, nil } -// ExecuteSimplifiedPaymentVerification executes the SPV for decoded BEEF tx -func (dBeef *DecodedBEEF) ExecuteSimplifiedPaymentVerification(provider MerkleRootVerifier) error { - err := dBeef.satoshisInInputsGreaterThanZero() - if err != nil { - return err - } - - err = dBeef.satoshisInOutputsGreaterThanZero() - if err != nil { - return err - } - - err = dBeef.validateSatoshisSum() - if err != nil { - return err - } - - err = dBeef.validateLockTime() - if err != nil { - return err - } - - err = dBeef.validateScripts() - if err != nil { - return err - } - - err = dBeef.verifyMerkleRoots(provider) - if err != nil { - return err - } - - return nil -} - -func (dBeef *DecodedBEEF) satoshisInOutputsGreaterThanZero() error { - if len(dBeef.ProcessedTxData.Transaction.Outputs) == 0 { - return errors.New("invalid output, no outputs") - } - return nil -} - -func (dBeef *DecodedBEEF) satoshisInInputsGreaterThanZero() error { - if len(dBeef.ProcessedTxData.Transaction.Inputs) == 0 { - return errors.New("invalid input, no inputs") - } - return nil -} - -func (dBeef *DecodedBEEF) verifyMerkleRoots(provider MerkleRootVerifier) error { - merkleRoots, err := dBeef.GetMerkleRoots() - if err != nil { - return err - } - - res, err := provider.VerifyMerkleRoots(context.Background(), merkleRoots) - if err != nil { - return err - } - - if !res.AllConfirmed { - return errors.New("not all merkle roots were confirmed") - } - return nil -} - -func (dBeef *DecodedBEEF) validateScripts() error { - for _, input := range dBeef.ProcessedTxData.Transaction.Inputs { - txId := input.PreviousTxID() - for j, input2 := range dBeef.InputsTxData { - if input2.Transaction.TxID() == string(txId) { - result := verifyScripts(dBeef.ProcessedTxData.Transaction, input2.Transaction, j) - if !result { - return errors.New("invalid script") - } - break - } - } - } - return nil -} - -func (dBeef *DecodedBEEF) validateSatoshisSum() error { - inputSum, outputSum := uint64(0), uint64(0) - for i, input := range dBeef.ProcessedTxData.Transaction.Inputs { - input2 := dBeef.InputsTxData[i] - inputSum += input2.Transaction.Outputs[input.PreviousTxOutIndex].Satoshis - } - for _, output := range dBeef.ProcessedTxData.Transaction.Outputs { - outputSum += output.Satoshis - } - - if inputSum <= outputSum { - return errors.New("invalid input and output sum, outputs can not be larger than inputs") - } - return nil -} - -func (dBeef *DecodedBEEF) validateLockTime() error { - if dBeef.ProcessedTxData.Transaction.LockTime == 0 { - for _, input := range dBeef.ProcessedTxData.Transaction.Inputs { - if input.SequenceNumber != 0xffffffff { - return errors.New("invalid sequence") - } - } - } else { - return errors.New("invalid locktime") - } - return nil -} - -// Verify locking and unlocking scripts pair -func verifyScripts(tx, prevTx *bt.Tx, inputIdx int) bool { - input := tx.InputIdx(inputIdx) - prevOutput := prevTx.OutputIdx(int(input.PreviousTxOutIndex)) - - if err := interpreter.NewEngine().Execute( - interpreter.WithTx(tx, inputIdx, prevOutput), - interpreter.WithForkID(), - interpreter.WithAfterGenesis(), - ); err != nil { - fmt.Println(err) - return false - } - return true -} - func calculateMerkleRoot(baseTx string, offset uint64, cmp []map[string]uint64) (string, error) { for i := len(cmp) - 1; i >= 0; i-- { var leftNode, rightNode string diff --git a/p2p_beef_tx_test.go b/p2p_beef_tx_test.go index ff7d213..67e0576 100644 --- a/p2p_beef_tx_test.go +++ b/p2p_beef_tx_test.go @@ -14,8 +14,8 @@ import ( // Mock implementation of a service provider type mockServiceProvider struct{} -// ExecuteSimplifiedPaymentVerification is a mock implementation of this interface -func (m *mockServiceProvider) VerifyMerkleRoots(ctx context.Context, merkleProofs []string) (*merkleroots.MerkleRootsConfirmationsResponse, error) { +// VerifyMerkleRoots is a mock implementation of this interface +func (m *mockServiceProvider) VerifyMerkleRoots(_ context.Context, _ []string) (*merkleroots.MerkleRootsConfirmationsResponse, error) { // Verify the merkle roots return &merkleroots.MerkleRootsConfirmationsResponse{AllConfirmed: true}, nil } @@ -271,6 +271,6 @@ func TestDecodedBeef(t *testing.T) { require.Nil(t, err) t.Run("SPV on valid beef", func(t *testing.T) { - require.Nil(t, validDecodedBeef.ExecuteSimplifiedPaymentVerification(new(mockServiceProvider))) + require.Nil(t, ExecuteSimplifiedPaymentVerification(validDecodedBeef, new(mockServiceProvider))) }) } diff --git a/p2p_spv.go b/p2p_spv.go new file mode 100644 index 0000000..800aa61 --- /dev/null +++ b/p2p_spv.go @@ -0,0 +1,128 @@ +package paymail + +import ( + "context" + "errors" + "fmt" + "github.com/libsv/bitcoin-hc/transports/http/endpoints/api/merkleroots" + "github.com/libsv/go-bt/v2" + "github.com/libsv/go-bt/v2/bscript/interpreter" +) + +type MerkleRootVerifier interface { + VerifyMerkleRoots( + ctx context.Context, + merkleRoots []string, + ) (*merkleroots.MerkleRootsConfirmationsResponse, error) +} + +// ExecuteSimplifiedPaymentVerification executes the SPV for decoded BEEF tx +func ExecuteSimplifiedPaymentVerification(dBeef *DecodedBEEF, provider MerkleRootVerifier) error { + err := validateSatoshisSum(dBeef) + if err != nil { + return err + } + + err = validateLockTime(dBeef) + if err != nil { + return err + } + + err = validateScripts(dBeef) + if err != nil { + return err + } + + err = verifyMerkleRoots(dBeef, provider) + if err != nil { + return err + } + + return nil +} + +func verifyMerkleRoots(dBeef *DecodedBEEF, provider MerkleRootVerifier) error { + merkleRoots, err := dBeef.GetMerkleRoots() + if err != nil { + return err + } + + res, err := provider.VerifyMerkleRoots(context.Background(), merkleRoots) + if err != nil { + return err + } + + if !res.AllConfirmed { + return errors.New("not all merkle roots were confirmed") + } + return nil +} + +func validateScripts(dBeef *DecodedBEEF) error { + for _, input := range dBeef.ProcessedTxData.Transaction.Inputs { + txId := input.PreviousTxID() + for j, input2 := range dBeef.InputsTxData { + if input2.Transaction.TxID() == string(txId) { + result := verifyScripts(dBeef.ProcessedTxData.Transaction, input2.Transaction, j) + if !result { + return errors.New("invalid script") + } + break + } + } + } + return nil +} + +func validateSatoshisSum(dBeef *DecodedBEEF) error { + if len(dBeef.ProcessedTxData.Transaction.Outputs) == 0 { + return errors.New("invalid output, no outputs") + } + + if len(dBeef.ProcessedTxData.Transaction.Inputs) == 0 { + return errors.New("invalid input, no inputs") + } + + inputSum, outputSum := uint64(0), uint64(0) + for i, input := range dBeef.ProcessedTxData.Transaction.Inputs { + inputParentTx := dBeef.InputsTxData[i] + inputSum += inputParentTx.Transaction.Outputs[input.PreviousTxOutIndex].Satoshis + } + for _, output := range dBeef.ProcessedTxData.Transaction.Outputs { + outputSum += output.Satoshis + } + + if inputSum <= outputSum { + return errors.New("invalid input and output sum, outputs can not be larger than inputs") + } + return nil +} + +func validateLockTime(dBeef *DecodedBEEF) error { + if dBeef.ProcessedTxData.Transaction.LockTime == 0 { + for _, input := range dBeef.ProcessedTxData.Transaction.Inputs { + if input.SequenceNumber != 0xffffffff { + return errors.New("invalid sequence") + } + } + } else { + return errors.New("invalid locktime") + } + return nil +} + +// Verify locking and unlocking scripts pair +func verifyScripts(tx, prevTx *bt.Tx, inputIdx int) bool { + input := tx.InputIdx(inputIdx) + prevOutput := prevTx.OutputIdx(int(input.PreviousTxOutIndex)) + + if err := interpreter.NewEngine().Execute( + interpreter.WithTx(tx, inputIdx, prevOutput), + interpreter.WithForkID(), + interpreter.WithAfterGenesis(), + ); err != nil { + fmt.Println(err) + return false + } + return true +} diff --git a/server/mock_test.go b/server/mock_test.go index e7fda3d..c75919c 100644 --- a/server/mock_test.go +++ b/server/mock_test.go @@ -42,8 +42,8 @@ func (m *mockServiceProvider) RecordTransaction(_ context.Context, return nil, nil } -// ExecuteSimplifiedPaymentVerification is a mock implementation of this interface -func (m *mockServiceProvider) VerifyMerkleRoots(ctx context.Context, merkleProofs []string) (*merkleroots.MerkleRootsConfirmationsResponse, error) { +// VerifyMerkleRoots is a mock implementation of this interface +func (m *mockServiceProvider) VerifyMerkleRoots(_ context.Context, _ []string) (*merkleroots.MerkleRootsConfirmationsResponse, error) { // Verify the merkle roots return &merkleroots.MerkleRootsConfirmationsResponse{AllConfirmed: true}, nil diff --git a/server/p2p_receive_transaction.go b/server/p2p_receive_transaction.go index 5b2ed0a..2e02ef9 100644 --- a/server/p2p_receive_transaction.go +++ b/server/p2p_receive_transaction.go @@ -87,7 +87,7 @@ func (c *Configuration) p2pReceiveBeefTx(w http.ResponseWriter, req *http.Reques panic("empty beef after parsing!") } - err := dBeef.ExecuteSimplifiedPaymentVerification(c.actions) + err := paymail.ExecuteSimplifiedPaymentVerification(dBeef, c.actions) if err != nil { ErrorResponse(w, ErrorSimplifiedPaymentVerification, err.Error(), http.StatusExpectationFailed) return