Skip to content

Commit

Permalink
feat: Bitcoin block header and merkle proof (#1263)
Browse files Browse the repository at this point in the history
* initiated bitcoin header and proof

* added smoke test for bitcoin merkle proof and RPC query

* make generate

* fix gosec and unit test

* Update common/headers_test.go

Co-authored-by: Lucas Bertrand <[email protected]>

* code adjustment according to feedback of PR review

* corrected a typo and added more comment to function

* fix gosec error

---------

Co-authored-by: charliec <[email protected]>
Co-authored-by: Lucas Bertrand <[email protected]>
Co-authored-by: brewmaster012 <[email protected]>
  • Loading branch information
4 people authored Oct 12, 2023
1 parent 0001806 commit b6bfa57
Show file tree
Hide file tree
Showing 32 changed files with 1,806 additions and 235 deletions.
407 changes: 407 additions & 0 deletions common/bitcoin/bitcoin.pb.go

Large diffs are not rendered by default.

99 changes: 99 additions & 0 deletions common/bitcoin/bitcoin_spv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright 2020 Indefinite Integral Incorporated

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package bitcoin

// This file was adapted from Summa bitcoin-spv. Here are some modifications:
// - define 'Hash256Digest' as alias for 'chainhash.Hash'
// - keep only Prove() and dependent functions

import (
"bytes"
"crypto/sha256"

"github.com/btcsuite/btcd/chaincfg/chainhash"
)

type Hash256Digest = chainhash.Hash

// Prove checks the validity of a merkle proof
func Prove(txid Hash256Digest, merkleRoot Hash256Digest, intermediateNodes []byte, index uint) bool {
// Shortcut the empty-block case
if bytes.Equal(txid[:], merkleRoot[:]) && index == 0 && len(intermediateNodes) == 0 {
return true
}

proof := []byte{}
proof = append(proof, txid[:]...)
proof = append(proof, intermediateNodes...)
proof = append(proof, merkleRoot[:]...)

return VerifyHash256Merkle(proof, index)
}

// Hash256 implements bitcoin's hash256 (double sha2)
func Hash256(in []byte) Hash256Digest {
first := sha256.Sum256(in)
second := sha256.Sum256(first[:])
return Hash256Digest(second)
}

// Hash256MerkleStep concatenates and hashes two inputs for merkle proving
func Hash256MerkleStep(a []byte, b []byte) Hash256Digest {
c := []byte{}
c = append(c, a...)
c = append(c, b...)
return Hash256(c)
}

// VerifyHash256Merkle checks a merkle inclusion proof's validity.
// Note that `index` is not a reliable indicator of location within a block.
func VerifyHash256Merkle(proof []byte, index uint) bool {
var current Hash256Digest
idx := index
proofLength := len(proof)

if proofLength%32 != 0 {
return false
}

if proofLength == 32 {
return true
}

if proofLength == 64 {
return false
}

root := proof[proofLength-32:]

cur := proof[:32:32]
copy(current[:], cur)

numSteps := (proofLength / 32) - 1

for i := 1; i < numSteps; i++ {
start := i * 32
end := i*32 + 32
next := proof[start:end:end]
if idx%2 == 1 {
current = Hash256MerkleStep(next, current[:])
} else {
current = Hash256MerkleStep(current[:], next)
}
idx >>= 1
}

return bytes.Equal(current[:], root)
}
73 changes: 73 additions & 0 deletions common/bitcoin/proof.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package bitcoin

import (
"errors"

"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcutil"
)

const BitcoinBlockHeaderLen = 80

// Merkle is a wrapper around "github.com/btcsuite/btcd/blockchain" merkle tree.
// Additionally, it provides a method to generate a merkle proof for a given transaction.
type Merkle struct {
tree []*chainhash.Hash
}

func NewMerkle(txns []*btcutil.Tx) *Merkle {
return &Merkle{
tree: blockchain.BuildMerkleTreeStore(txns, false),
}
}

// BuildMerkleProof builds merkle proof for a given transaction index in block.
func (m *Merkle) BuildMerkleProof(txIndex int) ([]byte, uint, error) {
if len(m.tree) <= 0 {
return nil, 0, errors.New("merkle tree is empty")
}

// len(m.tree) + 1 must be a power of 2. E.g. 2, 4, 8, 16, 32, 64, 128, 256, ...
N := len(m.tree) + 1
if N&(N-1) != 0 {
return nil, 0, errors.New("merkle tree is not full")
}

// Ensure the provided txIndex points to a valid leaf node.
if txIndex >= N/2 || m.tree[txIndex] == nil {
return nil, 0, errors.New("transaction index is invalid")
}
path := make([]byte, 0)
var siblingIndexes uint

// Find intermediate nodes on the path to the root buttom-up.
nodeIndex := txIndex
nodesOnLevel := N / 2
for nodesOnLevel > 1 {
var flag uint
var sibling *chainhash.Hash

if nodeIndex%2 == 1 {
flag = 1 // left sibling
sibling = m.tree[nodeIndex-1]
} else {
flag = 0 // right sibling
if m.tree[nodeIndex+1] == nil {
sibling = m.tree[nodeIndex] // When there is no right sibling, self hash is used.
} else {
sibling = m.tree[nodeIndex+1]
}
}

// Append the sibling and flag to the proof.
path = append(path, sibling[:]...)
siblingIndexes |= flag << (len(path)/32 - 1)

// Go up one level to the parent node.
nodeIndex = N - nodesOnLevel + (nodeIndex%nodesOnLevel)/2
nodesOnLevel /= 2
}

return path, siblingIndexes, nil
}
Loading

0 comments on commit b6bfa57

Please sign in to comment.