Skip to content

Commit

Permalink
chore(BUX-164): move BEEF tx SPV to separate file
Browse files Browse the repository at this point in the history
  • Loading branch information
pawellewandowski98 committed Oct 6, 2023
1 parent 5c38327 commit 8ab64b5
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 143 deletions.
137 changes: 0 additions & 137 deletions p2p_beef_tx.go
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions p2p_beef_tx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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)))
})
}
128 changes: 128 additions & 0 deletions p2p_spv.go
Original file line number Diff line number Diff line change
@@ -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
}
4 changes: 2 additions & 2 deletions server/mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion server/p2p_receive_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 8ab64b5

Please sign in to comment.