Skip to content

Commit

Permalink
Add more bitcoin proof tests
Browse files Browse the repository at this point in the history
  • Loading branch information
skosito committed Mar 18, 2024
1 parent cce926d commit cff5322
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 34 deletions.
50 changes: 50 additions & 0 deletions common/bitcoin/bitcoin_spv_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package bitcoin

import (
"testing"

"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/stretchr/testify/require"
)

func TestProve(t *testing.T) {
t.Run("returns true", func(t *testing.T) {
result := Prove(chainhash.Hash{}, chainhash.Hash{}, []byte{}, 0)
require.True(t, result)
})
}

func TestVerifyHash256Merkle(t *testing.T) {
tests := []struct {
name string
proof []byte
index uint
want bool
}{
{
name: "valid length but invalid index and content",
proof: make([]byte, 32),
index: 0,
want: true,
},
{
name: "invalid length not multiple of 32",
proof: make([]byte, 34),
index: 0,
want: false,
},
{
name: "invalid length equal to 64",
proof: make([]byte, 64),
index: 0,
want: false,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result := VerifyHash256Merkle(tc.proof, tc.index)
require.Equal(t, tc.want, result)
})
}
}
97 changes: 63 additions & 34 deletions common/bitcoin/proof_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package bitcoin_test
package bitcoin

import (
"bytes"
Expand All @@ -9,10 +9,10 @@ import (
"testing"

"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/stretchr/testify/require"
"github.com/zeta-chain/zetacore/common/bitcoin"
)

type Block struct {
Expand All @@ -31,23 +31,68 @@ type Blocks struct {
func TestBitcoinMerkleProof(t *testing.T) {
blocks := LoadTestBlocks(t)

for _, b := range blocks.Blocks {
// Deserialize the header bytes from base64
headerBytes, err := base64.StdEncoding.DecodeString(b.HeaderBase64)
require.NoError(t, err)
header := unmarshalHeader(t, headerBytes)
t.Run("it should verify merkle proof", func(t *testing.T) {
for _, b := range blocks.Blocks {
// Deserialize the header bytes from base64
headerBytes, err := base64.StdEncoding.DecodeString(b.HeaderBase64)
require.NoError(t, err)
header := unmarshalHeader(t, headerBytes)

// Deserialize the block bytes from base64
blockBytes, err := base64.StdEncoding.DecodeString(b.BlockBase64)
require.NoError(t, err)
blockVerbose := &btcjson.GetBlockVerboseTxResult{}
err = json.Unmarshal(blockBytes, blockVerbose)
require.NoError(t, err)
// Deserialize the block bytes from base64
blockBytes, err := base64.StdEncoding.DecodeString(b.BlockBase64)
require.NoError(t, err)
blockVerbose := &btcjson.GetBlockVerboseTxResult{}
err = json.Unmarshal(blockBytes, blockVerbose)
require.NoError(t, err)

// Validate block
validateBitcoinBlock(t, header, headerBytes, blockVerbose, b.OutTxid, b.TssAddress, b.Nonce)
}
txns := getBlockTxs(t, blockVerbose)

// Build a Merkle tree from the transaction hashes and verify each transaction
mk := NewMerkle(txns)
for i, tx := range txns {
path, index, err := mk.BuildMerkleProof(i)
require.NoError(t, err)

// True proof should verify
pass := Prove(*tx.Hash(), header.MerkleRoot, path, index)
require.True(t, pass)

// Fake proof should not verify
fakeIndex := index ^ 0xffffffff // flip all bits
pass = Prove(*tx.Hash(), header.MerkleRoot, path, fakeIndex)
require.False(t, pass)
}
}
})

t.Run("it should fail if tree is empty", func(t *testing.T) {
mt := Merkle{
tree: []*chainhash.Hash{},
}

_, _, err := mt.BuildMerkleProof(0)
require.Error(t, err)
})

t.Run("it should fail if tree len + 1 is not power of 2", func(t *testing.T) {
mt := Merkle{
tree: []*chainhash.Hash{{}, {}},
}

_, _, err := mt.BuildMerkleProof(0)
require.Error(t, err)
})

t.Run("it should fail if txIndex out of range", func(t *testing.T) {
mt := Merkle{
tree: []*chainhash.Hash{{}},
}

_, _, err := mt.BuildMerkleProof(2)
require.Error(t, err)
})
}

func LoadTestBlocks(t *testing.T) Blocks {
file, err := os.Open("../testdata/test_blocks.json")
require.NoError(t, err)
Expand All @@ -68,8 +113,7 @@ func unmarshalHeader(t *testing.T, headerBytes []byte) *wire.BlockHeader {
return &header
}

func validateBitcoinBlock(t *testing.T, header *wire.BlockHeader, headerBytes []byte, blockVerbose *btcjson.GetBlockVerboseTxResult, outTxid string, tssAddress string, nonce uint64) {
// Deserialization should work for each transaction in the block
func getBlockTxs(t *testing.T, blockVerbose *btcjson.GetBlockVerboseTxResult) []*btcutil.Tx {
txns := []*btcutil.Tx{}
for _, res := range blockVerbose.Tx {
txBytes, err := hex.DecodeString(res.Hex)
Expand All @@ -78,20 +122,5 @@ func validateBitcoinBlock(t *testing.T, header *wire.BlockHeader, headerBytes []
require.NoError(t, err)
txns = append(txns, tx)
}

// Build a Merkle tree from the transaction hashes and verify each transaction
mk := bitcoin.NewMerkle(txns)
for i, tx := range txns {
path, index, err := mk.BuildMerkleProof(i)
require.NoError(t, err)

// True proof should verify
pass := bitcoin.Prove(*tx.Hash(), header.MerkleRoot, path, index)
require.True(t, pass)

// Fake proof should not verify
fakeIndex := index ^ 0xffffffff // flip all bits
pass = bitcoin.Prove(*tx.Hash(), header.MerkleRoot, path, fakeIndex)
require.False(t, pass)
}
return txns
}

0 comments on commit cff5322

Please sign in to comment.