Skip to content

Commit 69bc2b8

Browse files
authored
Claim pegin (#165)
* added factory method FromPublicKeys for creating multiscript payment * merkle tree v1-unstable * invalid peg-in tx, wip * refactor after review * first working claim-pegin version * skipping test since it requires nigiri * comments, test fix * removing todos
1 parent fc6154a commit 69bc2b8

9 files changed

Lines changed: 959 additions & 32 deletions

File tree

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ help:
55

66
test:
77
export API_URL=http://localhost:3001; \
8+
export API_BTC_URL=http://localhost:3000; \
89
go test -count=1 -v ./...

block/merkle_block.go

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
package block
2+
3+
import (
4+
"bytes"
5+
"encoding/hex"
6+
"errors"
7+
8+
"github.com/btcsuite/btcd/wire"
9+
10+
"github.com/btcsuite/btcd/blockchain"
11+
12+
"github.com/btcsuite/btcd/chaincfg/chainhash"
13+
)
14+
15+
const (
16+
// The maximum allowed weight for a block, see BIP 141 (network rule)
17+
maxBlockWeight = 4000000
18+
witnessScaleFactor = 4
19+
minTransactionWeight = witnessScaleFactor * 60 // 60 is the lower bound for the size of a valid serialized tx
20+
21+
)
22+
23+
type MerkleBlock struct {
24+
BlockHeader *wire.BlockHeader
25+
PartialMerkleTree *PartialMerkleTree
26+
}
27+
28+
type PartialMerkleTree struct {
29+
TxTotalCount uint32
30+
TxHashes [][]byte
31+
FBad bool
32+
VBits []bool
33+
}
34+
35+
func NewMerkleBlockFromBuffer(buf *bytes.Buffer) (*MerkleBlock, error) {
36+
return deserializeMerkleBlock(buf)
37+
}
38+
39+
func NewMerkleBlockFromHex(h string) (*MerkleBlock, error) {
40+
hexBytes, err := hex.DecodeString(h)
41+
if err != nil {
42+
return nil, err
43+
}
44+
buf := bytes.NewBuffer(hexBytes)
45+
return NewMerkleBlockFromBuffer(buf)
46+
}
47+
48+
func (m *MerkleBlock) ExtractMatches() (*chainhash.Hash, []chainhash.Hash, error) {
49+
vMatch := make([]chainhash.Hash, 0)
50+
51+
if m.PartialMerkleTree.TxTotalCount == 0 {
52+
return nil, nil, errors.New("tx count equal 0")
53+
}
54+
55+
if m.PartialMerkleTree.TxTotalCount > maxBlockWeight/minTransactionWeight {
56+
return nil, nil, errors.New("invalid tx count")
57+
}
58+
if len(m.PartialMerkleTree.TxHashes) > int(m.PartialMerkleTree.TxTotalCount) {
59+
return nil, nil, errors.New(
60+
"there can never be more hashes provided than one for every txid",
61+
)
62+
}
63+
64+
if len(m.PartialMerkleTree.VBits) < len(m.PartialMerkleTree.TxHashes) {
65+
return nil, nil, errors.New(
66+
"there must be at least one bit per node in the partial tree, " +
67+
"and at least one node per hash",
68+
)
69+
}
70+
71+
// Calculate the number of merkle branches (height) in the tree.
72+
height := uint32(0)
73+
for m.calcTreeWidth(height) > 1 {
74+
height++
75+
}
76+
77+
var bitsUsed, hashUsed, position = 0, 0, 0
78+
hashMerkleRoot, err := m.traverseAndExtract(
79+
height,
80+
position,
81+
&bitsUsed,
82+
&hashUsed,
83+
&vMatch,
84+
)
85+
if err != nil {
86+
return nil, nil, err
87+
}
88+
89+
if m.PartialMerkleTree.FBad {
90+
return nil, nil, errors.New(
91+
"there must be at least one bit per node in the partial tree, " +
92+
"and at least one node per hash",
93+
)
94+
}
95+
// verify that all bits were consumed (except for the padding caused by serializing it as a byte sequence)
96+
if (bitsUsed+7)/8 != (len(m.PartialMerkleTree.VBits)+7)/8 {
97+
return nil, nil, errors.New(
98+
"except for the padding caused by serializing it as a byte" +
99+
" sequence, not all bits were consumed",
100+
)
101+
}
102+
// verify that all hashes were consumed
103+
if hashUsed != len(m.PartialMerkleTree.TxHashes) {
104+
return nil, nil, errors.New("not all hashes were consumed")
105+
}
106+
107+
return hashMerkleRoot, vMatch, nil
108+
}
109+
110+
func (m *MerkleBlock) traverseAndExtract(
111+
height uint32,
112+
position int,
113+
bitsUsed *int,
114+
hashUsed *int,
115+
vMatch *[]chainhash.Hash,
116+
) (*chainhash.Hash, error) {
117+
if *bitsUsed >= len(m.PartialMerkleTree.VBits) {
118+
m.PartialMerkleTree.FBad = true
119+
return nil, errors.New("overflowed the bits array")
120+
}
121+
122+
fParentOfMatch := m.PartialMerkleTree.VBits[*bitsUsed]
123+
*bitsUsed++
124+
if height == 0 || !fParentOfMatch {
125+
// if at height 0, or nothing interesting below, use stored hash and do not descend
126+
if *hashUsed >= len(m.PartialMerkleTree.TxHashes) {
127+
m.PartialMerkleTree.FBad = true
128+
return nil, errors.New("overflowed the hash array")
129+
}
130+
hash, err := chainhash.NewHash(m.PartialMerkleTree.TxHashes[*hashUsed])
131+
if err != nil {
132+
return nil, err
133+
}
134+
135+
*hashUsed++
136+
if height == 0 && fParentOfMatch { // in case of height 0, we have a matched txid
137+
*vMatch = append(*vMatch, *hash)
138+
}
139+
140+
return hash, nil
141+
} else {
142+
//otherwise, descend into the subtrees to extract matched txids and hashes
143+
left, err := m.traverseAndExtract(
144+
height-1,
145+
position*2,
146+
bitsUsed,
147+
hashUsed,
148+
vMatch,
149+
)
150+
if err != nil {
151+
return nil, err
152+
}
153+
var right *chainhash.Hash
154+
if position*2+1 < int(m.calcTreeWidth(height-1)) {
155+
right, err = m.traverseAndExtract(
156+
height-1,
157+
position*2+1,
158+
bitsUsed,
159+
hashUsed,
160+
vMatch,
161+
)
162+
if err != nil {
163+
return nil, err
164+
}
165+
if left.IsEqual(right) {
166+
// The left and right branches should never be identical, as the transaction
167+
// hashes covered by them must each be unique.
168+
m.PartialMerkleTree.FBad = true
169+
}
170+
} else {
171+
right = left
172+
}
173+
174+
return blockchain.HashMerkleBranches(left, right), nil
175+
}
176+
}
177+
178+
// calcTreeWidth calculates and returns the the number of nodes (width) or a
179+
// merkle tree at the given depth-first height.
180+
func (m *MerkleBlock) calcTreeWidth(height uint32) uint32 {
181+
return (m.PartialMerkleTree.TxTotalCount + (1 << height) - 1) >> height
182+
}
183+
184+
func deserializePartialMerkleTree(
185+
mb wire.MsgMerkleBlock,
186+
) (*PartialMerkleTree, error) {
187+
txHashes := make([][]byte, 0, len(mb.Hashes))
188+
for _, v := range mb.Hashes {
189+
190+
txHashes = append(txHashes, v.CloneBytes())
191+
}
192+
193+
return &PartialMerkleTree{
194+
TxTotalCount: mb.Transactions,
195+
TxHashes: txHashes,
196+
FBad: false,
197+
VBits: serializeVBits(mb.Flags),
198+
}, nil
199+
}
200+
201+
func deserializeMerkleBlock(buf *bytes.Buffer) (*MerkleBlock, error) {
202+
mb := wire.MsgMerkleBlock{}
203+
err := mb.BtcDecode(buf, wire.ProtocolVersion, wire.LatestEncoding)
204+
if err != nil {
205+
return nil, err
206+
}
207+
208+
partialMerkleTree, err := deserializePartialMerkleTree(mb)
209+
if err != nil {
210+
return nil, err
211+
}
212+
213+
return &MerkleBlock{
214+
BlockHeader: &mb.Header,
215+
PartialMerkleTree: partialMerkleTree,
216+
}, nil
217+
}
218+
219+
func serializeVBits(b []byte) []bool {
220+
bits := make([]bool, 0)
221+
222+
for _, v := range b {
223+
l := byteToBits(v)
224+
for _, v := range l {
225+
if v == 1 {
226+
bits = append(bits, true)
227+
} else {
228+
bits = append(bits, false)
229+
}
230+
}
231+
}
232+
233+
return bits
234+
}
235+
236+
func byteToBits(b byte) []byte {
237+
return []byte{
238+
(b >> 0) & 0x1,
239+
(b >> 1) & 0x1,
240+
(b >> 2) & 0x1,
241+
(b >> 3) & 0x1,
242+
(b >> 4) & 0x1,
243+
(b >> 5) & 0x1,
244+
(b >> 6) & 0x1,
245+
(b >> 7) & 0x1,
246+
}
247+
}

block/merkle_block_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package block
2+
3+
import (
4+
"encoding/hex"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
9+
"github.com/vulpemventures/go-elements/elementsutil"
10+
)
11+
12+
func TestDeserializeMerkleBlock(t *testing.T) {
13+
txOutProof := "0000003095a03ddcb18a359a1d41072ed373d45e57200f57d6f318238a7a6eb18df4c02dd4187f08f314f8436ac76f38cbe03a791a0893f26444ea5d5ec8f5b9b95a93015280ab60ffff7f20010000000200000002bdde18f707d02aa18ba82926965dc8bb8991d9510cd98e2812cc44b7aae8d3959745d887c8459cd4441f1dfe1a2d7ecbe225db87eeaa68bb22df6fdbf7017c480105"
14+
15+
merkleBlock, err := NewMerkleBlockFromHex(txOutProof)
16+
if err != nil {
17+
t.Fatal(err)
18+
}
19+
20+
for _, v := range merkleBlock.PartialMerkleTree.TxHashes {
21+
t.Log(hex.EncodeToString(elementsutil.ReverseBytes(v)))
22+
}
23+
}
24+
25+
func TestExtractMatches(t *testing.T) {
26+
txOutProof := "0000003095a03ddcb18a359a1d41072ed373d45e57200f57d6f318238a7a6eb18df4c02dd4187f08f314f8436ac76f38cbe03a791a0893f26444ea5d5ec8f5b9b95a93015280ab60ffff7f20010000000200000002bdde18f707d02aa18ba82926965dc8bb8991d9510cd98e2812cc44b7aae8d3959745d887c8459cd4441f1dfe1a2d7ecbe225db87eeaa68bb22df6fdbf7017c480105"
27+
28+
merkleBlock, err := NewMerkleBlockFromHex(txOutProof)
29+
if err != nil {
30+
t.Fatal(err)
31+
}
32+
33+
hashMerkleRoot, matchedHashes, err := merkleBlock.ExtractMatches()
34+
if err != nil {
35+
t.Fatal(err)
36+
}
37+
38+
assert.Equal(
39+
t,
40+
true,
41+
merkleBlock.BlockHeader.MerkleRoot.IsEqual(hashMerkleRoot),
42+
)
43+
assert.Equal(t, 1, len(matchedHashes))
44+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ require (
88
github.com/btcsuite/btcutil/psbt v1.0.2
99
github.com/stretchr/testify v1.7.0
1010
github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941
11-
github.com/vulpemventures/go-secp256k1-zkp v1.1.1-0.20210422114014-d9d676d706c1
11+
github.com/vulpemventures/go-secp256k1-zkp v1.1.2
1212
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
1313
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,8 @@ github.com/vulpemventures/go-secp256k1-zkp v1.1.0 h1:Z3qKc/lYxEQ1cukwwjD4n7EBCrg
302302
github.com/vulpemventures/go-secp256k1-zkp v1.1.0/go.mod h1:zo7CpgkuPgoe7fAV+inyxsI9IhGmcoFgyD8nqZaPSOM=
303303
github.com/vulpemventures/go-secp256k1-zkp v1.1.1-0.20210422114014-d9d676d706c1 h1:9rPKzVfNwHve4TkCtcECzT1/zTCuN90K08VXjaI0bb0=
304304
github.com/vulpemventures/go-secp256k1-zkp v1.1.1-0.20210422114014-d9d676d706c1/go.mod h1:zo7CpgkuPgoe7fAV+inyxsI9IhGmcoFgyD8nqZaPSOM=
305+
github.com/vulpemventures/go-secp256k1-zkp v1.1.2 h1:ZfM0r4QtkpQbNywlT9LFXXZBuMQ1Q8QiHGUTz4QV88M=
306+
github.com/vulpemventures/go-secp256k1-zkp v1.1.2/go.mod h1:zo7CpgkuPgoe7fAV+inyxsI9IhGmcoFgyD8nqZaPSOM=
305307
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
306308
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
307309
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=

0 commit comments

Comments
 (0)