bitcoinrb 1.8.2 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bitcoinrb.gemspec +1 -0
- data/lib/bitcoin/block.rb +5 -3
- data/lib/bitcoin/descriptor/combo.rb +4 -0
- data/lib/bitcoin/descriptor/expression.rb +2 -0
- data/lib/bitcoin/descriptor/key_expression.rb +8 -3
- data/lib/bitcoin/descriptor/musig.rb +75 -0
- data/lib/bitcoin/descriptor/pk.rb +5 -0
- data/lib/bitcoin/descriptor/pkh.rb +5 -0
- data/lib/bitcoin/descriptor/raw_tr.rb +9 -1
- data/lib/bitcoin/descriptor/script_expression.rb +1 -0
- data/lib/bitcoin/descriptor/tr.rb +1 -0
- data/lib/bitcoin/descriptor/wpkh.rb +1 -0
- data/lib/bitcoin/descriptor.rb +63 -22
- data/lib/bitcoin/message/merkle_block.rb +7 -1
- data/lib/bitcoin/{merkle_tree.rb → partial_tree.rb} +4 -16
- data/lib/bitcoin/taproot/custom_depth_builder.rb +11 -22
- data/lib/bitcoin/taproot/simple_builder.rb +24 -53
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin.rb +2 -1
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e9112a5bf75bc7840748693cf849b887cfafd82bbb40dafb8d6eb47e4e0c1e2
|
4
|
+
data.tar.gz: b712757ad33c4283ac0b048d527ae0d0d4c597b2b089a94baf4655b70f3441a3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0adb44f570489b3e577fe5f7e42c922f315976ed90e0ba286a727e941b70f6ca5d16fb090f6f014bb4b0f36907ba87c65e3881f31b40c706bf8d79958890536d
|
7
|
+
data.tar.gz: 90d803c95d478211ec5054012f96eac13d5ce44872df43e2ce483ed3460343e6051c33d8a7a4e6679d824f922cc99a547855aacd8a857cd3d3ece7c496107171
|
data/bitcoinrb.gemspec
CHANGED
data/lib/bitcoin/block.rb
CHANGED
@@ -28,7 +28,7 @@ module Bitcoin
|
|
28
28
|
header = BlockHeader.new(
|
29
29
|
version,
|
30
30
|
'00' * 32,
|
31
|
-
|
31
|
+
Merkle::BinaryTree.new(config: Merkle::Config.bitcoin, leaves: [coinbase.txid]).compute_root.rhex,
|
32
32
|
time,
|
33
33
|
bits,
|
34
34
|
nonce
|
@@ -72,7 +72,8 @@ module Bitcoin
|
|
72
72
|
|
73
73
|
# calculate merkle root from tx list.
|
74
74
|
def calculate_merkle_root
|
75
|
-
|
75
|
+
tree = Merkle::BinaryTree.new(config: Merkle::Config.bitcoin, leaves: transactions.map(&:tx_hash))
|
76
|
+
tree.compute_root
|
76
77
|
end
|
77
78
|
|
78
79
|
# check the witness commitment in coinbase tx matches witness commitment calculated from tx list.
|
@@ -85,7 +86,8 @@ module Bitcoin
|
|
85
86
|
witness_hashes = [COINBASE_WTXID]
|
86
87
|
witness_hashes += (transactions[1..-1].map(&:witness_hash))
|
87
88
|
reserved_value = transactions[0].inputs[0].script_witness.stack.map(&:bth).join
|
88
|
-
|
89
|
+
tree = Merkle::BinaryTree.new(config: Merkle::Config.bitcoin, leaves: witness_hashes)
|
90
|
+
root_hash = tree.compute_root
|
89
91
|
Bitcoin.double_sha256([root_hash + reserved_value].pack('H*')).bth
|
90
92
|
end
|
91
93
|
|
@@ -98,6 +98,8 @@ module Bitcoin
|
|
98
98
|
is_private = key.is_a?(Bitcoin::ExtKey)
|
99
99
|
paths.each do |path|
|
100
100
|
raise ArgumentError, 'xpub can not derive hardened key.' if !is_private && path.end_with?("'")
|
101
|
+
raise ArgumentError, 'Key ranges are not supported.' if path.include?("*")
|
102
|
+
raise ArgumentError, 'Key multipath are not supported.' if path.include?("<")
|
101
103
|
if is_private
|
102
104
|
hardened = path.end_with?("'")
|
103
105
|
path = hardened ? path[0..-2] : path
|
@@ -6,9 +6,9 @@ module Bitcoin
|
|
6
6
|
# Constructor
|
7
7
|
# @raise [ArgumentError] If +key+ is invalid.
|
8
8
|
def initialize(key)
|
9
|
-
raise ArgumentError, "Key must be string." unless key.is_a?
|
10
|
-
extract_pubkey(key)
|
9
|
+
raise ArgumentError, "Key must be string or MuSig." unless key.is_a?(String) || key.is_a?(MuSig)
|
11
10
|
@key = key
|
11
|
+
extracted_key
|
12
12
|
end
|
13
13
|
|
14
14
|
def args
|
@@ -22,9 +22,14 @@ module Bitcoin
|
|
22
22
|
# Get extracted key.
|
23
23
|
# @return [Bitcoin::Key] Extracted key.
|
24
24
|
def extracted_key
|
25
|
-
extract_pubkey(key)
|
25
|
+
extract_pubkey(musig? ? key.to_hex : key)
|
26
26
|
end
|
27
27
|
|
28
|
+
# Key is musig or not?
|
29
|
+
# @return [Boolean]
|
30
|
+
def musig?
|
31
|
+
key.is_a?(MuSig)
|
32
|
+
end
|
28
33
|
end
|
29
34
|
end
|
30
35
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Descriptor
|
3
|
+
|
4
|
+
# musig() descriptor class.
|
5
|
+
# @see https://github.com/bitcoin/bips/blob/master/bip-0390.mediawiki
|
6
|
+
class MuSig < Expression
|
7
|
+
|
8
|
+
# SHA256 of `MuSig2MuSig2MuSig2`
|
9
|
+
# https://github.com/bitcoin/bips/blob/master/bip-0328.mediawiki
|
10
|
+
CHAINCODE = '868087ca02a6f974c4598924c36b57762d32cb45717167e300622c7167e38965'.htb
|
11
|
+
|
12
|
+
attr_reader :keys
|
13
|
+
attr_reader :path
|
14
|
+
|
15
|
+
# Constructor.
|
16
|
+
# @param [Array] keys An array of key strings.
|
17
|
+
# @param [String] path (Optional) derivation path.
|
18
|
+
# @return [Bitcoin::Descriptor::MuSig]
|
19
|
+
def initialize(keys, path = nil)
|
20
|
+
raise ArgumentError, "keys must be an array." unless keys.is_a?(Array)
|
21
|
+
unless path.nil?
|
22
|
+
raise ArgumentError, "path must be String." unless path.is_a?(String)
|
23
|
+
raise ArgumentError, "path must be start with /." unless path.start_with?("/")
|
24
|
+
end
|
25
|
+
validate_keys!(keys, path)
|
26
|
+
@keys = keys
|
27
|
+
@path = path
|
28
|
+
end
|
29
|
+
|
30
|
+
def type
|
31
|
+
:musig
|
32
|
+
end
|
33
|
+
|
34
|
+
def top_level?
|
35
|
+
false
|
36
|
+
end
|
37
|
+
|
38
|
+
# Convert to single key with hex format.
|
39
|
+
# @return [String]
|
40
|
+
def to_hex
|
41
|
+
sorted_key = Schnorr::MuSig2.sort_pubkeys(keys.map{|k| extract_pubkey(k).pubkey})
|
42
|
+
agg_key = Schnorr::MuSig2.aggregate(sorted_key)
|
43
|
+
if path.nil?
|
44
|
+
agg_key.x_only_pubkey
|
45
|
+
else
|
46
|
+
ext_key = Bitcoin::ExtPubkey.new
|
47
|
+
ext_key.pubkey = agg_key.q.to_hex
|
48
|
+
ext_key.depth = 0
|
49
|
+
ext_key.chain_code = CHAINCODE
|
50
|
+
_, *paths = path.split('/')
|
51
|
+
derived_key = derive_path(ext_key, paths)
|
52
|
+
derived_key.key.xonly_pubkey
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_s(checksum: nil)
|
57
|
+
desc = "#{type.to_s}(#{keys.join(',')})"
|
58
|
+
desc << path if path
|
59
|
+
checksum ? Checksum.descsum_create(desc) : desc
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def validate_keys!(keys, path)
|
65
|
+
raise ArgumentError, 'musig() cannot have hardened child derivation.' if path && path.include?('h')
|
66
|
+
keys.each do |k|
|
67
|
+
if path
|
68
|
+
raise ArgumentError, 'Ranged musig() requires all participants to be xpubs.' unless k.start_with?('xpub')
|
69
|
+
end
|
70
|
+
extract_pubkey(k)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -1,9 +1,17 @@
|
|
1
1
|
module Bitcoin
|
2
2
|
module Descriptor
|
3
|
-
# rawtr()
|
3
|
+
# rawtr() descriptor
|
4
|
+
# @see
|
4
5
|
class RawTr < KeyExpression
|
5
6
|
include Bitcoin::Opcodes
|
6
7
|
|
8
|
+
# Constructor
|
9
|
+
# @raise [ArgumentError] If +key+ is invalid.
|
10
|
+
def initialize(key)
|
11
|
+
key = key.to_hex if key.is_a?(MuSig)
|
12
|
+
super(key)
|
13
|
+
end
|
14
|
+
|
7
15
|
def type
|
8
16
|
:rawtr
|
9
17
|
end
|
@@ -16,6 +16,7 @@ module Bitcoin
|
|
16
16
|
private
|
17
17
|
|
18
18
|
def validate!(script)
|
19
|
+
raise ArgumentError, "musig() is not allowed in #{type.to_s}()." if script.is_a?(MuSig) || (script.is_a?(KeyExpression) && script.musig?)
|
19
20
|
raise ArgumentError, "Can only have #{script.type.to_s}() at top level." if script.is_a?(Expression) && script.top_level?
|
20
21
|
raise ArgumentError, 'Can only have multi_a/sortedmulti_a inside tr().' if script.is_a?(MultiA) || script.is_a?(SortedMultiA)
|
21
22
|
end
|
@@ -9,6 +9,7 @@ module Bitcoin
|
|
9
9
|
|
10
10
|
# Constructor.
|
11
11
|
def initialize(key, tree = nil)
|
12
|
+
key = key.to_hex if key.is_a?(MuSig)
|
12
13
|
raise ArgumentError, "Key must be string." unless key.is_a?(String)
|
13
14
|
k = extract_pubkey(key)
|
14
15
|
raise ArgumentError, "Uncompressed key are not allowed." unless k.compressed?
|
@@ -3,6 +3,7 @@ module Bitcoin
|
|
3
3
|
# wpkh() expression
|
4
4
|
class Wpkh < KeyExpression
|
5
5
|
def initialize(key)
|
6
|
+
raise ArgumentError, 'musig() is not allowed in wpkh().' if key.is_a?(MuSig)
|
6
7
|
super(key)
|
7
8
|
raise ArgumentError, "Uncompressed key are not allowed." unless extract_pubkey(key).compressed?
|
8
9
|
end
|
data/lib/bitcoin/descriptor.rb
CHANGED
@@ -21,45 +21,46 @@ module Bitcoin
|
|
21
21
|
autoload :SortedMultiA, 'bitcoin/descriptor/sorted_multi_a'
|
22
22
|
autoload :RawTr, 'bitcoin/descriptor/raw_tr'
|
23
23
|
autoload :Checksum, 'bitcoin/descriptor/checksum'
|
24
|
+
autoload :MuSig, 'bitcoin/descriptor/musig'
|
24
25
|
|
25
26
|
module_function
|
26
27
|
|
27
|
-
# Generate
|
28
|
+
# Generate pk() descriptor.
|
28
29
|
# @param [String] key private key or public key with hex format
|
29
30
|
# @return [Bitcoin::Descriptor::Pk]
|
30
31
|
def pk(key)
|
31
32
|
Pk.new(key)
|
32
33
|
end
|
33
34
|
|
34
|
-
# Generate
|
35
|
+
# Generate pkh() descriptor.
|
35
36
|
# @param [String] key private key or public key with hex format.
|
36
37
|
# @return [Bitcoin::Descriptor::Pkh]
|
37
38
|
def pkh(key)
|
38
39
|
Pkh.new(key)
|
39
40
|
end
|
40
41
|
|
41
|
-
# Generate
|
42
|
+
# Generate wpkh() descriptor.
|
42
43
|
# @param [String] key private key or public key with hex format.
|
43
44
|
# @return [Bitcoin::Descriptor::Wpkh]
|
44
45
|
def wpkh(key)
|
45
46
|
Wpkh.new(key)
|
46
47
|
end
|
47
48
|
|
48
|
-
# Generate
|
49
|
+
# Generate sh() descriptor.
|
49
50
|
# @param [Bitcoin::Descriptor::Base] exp script expression to be embed.
|
50
51
|
# @return [Bitcoin::Descriptor::Sh]
|
51
52
|
def sh(exp)
|
52
53
|
Sh.new(exp)
|
53
54
|
end
|
54
55
|
|
55
|
-
# Generate
|
56
|
+
# Generate wsh() descriptor.
|
56
57
|
# @param [Bitcoin::Descriptor::Expression] exp script expression to be embed.
|
57
58
|
# @return [Bitcoin::Descriptor::Wsh]
|
58
59
|
def wsh(exp)
|
59
60
|
Wsh.new(exp)
|
60
61
|
end
|
61
62
|
|
62
|
-
#
|
63
|
+
# Generate combo() descriptor.
|
63
64
|
# If the key is compressed, it also includes `wpkh(KEY)` and `sh(wpkh(KEY))`.
|
64
65
|
# @param [String] key private key or public key with hex format.
|
65
66
|
# @return [Bitcoin::Descriptor::Combo]
|
@@ -67,7 +68,7 @@ module Bitcoin
|
|
67
68
|
Combo.new(key)
|
68
69
|
end
|
69
70
|
|
70
|
-
# Generate
|
71
|
+
# Generate multi() descriptor.
|
71
72
|
# @param [Integer] threshold the threshold of multisig.
|
72
73
|
# @param [Array[String]] keys an array of keys.
|
73
74
|
# @return [Bitcoin::Descriptor::Multi] multisig script.
|
@@ -75,7 +76,7 @@ module Bitcoin
|
|
75
76
|
Multi.new(threshold, keys)
|
76
77
|
end
|
77
78
|
|
78
|
-
# Generate
|
79
|
+
# Generate sortedmulti() descriptor.
|
79
80
|
# @param [Integer] threshold the threshold of multisig.
|
80
81
|
# @param [Array[String]] keys an array of keys.
|
81
82
|
# @return [Bitcoin::Descriptor::SortedMulti]
|
@@ -83,21 +84,21 @@ module Bitcoin
|
|
83
84
|
SortedMulti.new(threshold, keys)
|
84
85
|
end
|
85
86
|
|
86
|
-
# Generate raw
|
87
|
+
# Generate raw() descriptor.
|
87
88
|
# @param [String] hex Hex string of bitcoin script.
|
88
89
|
# @return [Bitcoin::Descriptor::Raw]
|
89
90
|
def raw(hex)
|
90
91
|
Raw.new(hex)
|
91
92
|
end
|
92
93
|
|
93
|
-
# Generate
|
94
|
+
# Generate addr() descriptor.
|
94
95
|
# @param [String] addr Bitcoin address.
|
95
96
|
# @return [Bitcoin::Descriptor::Addr]
|
96
97
|
def addr(addr)
|
97
98
|
Addr.new(addr)
|
98
99
|
end
|
99
100
|
|
100
|
-
# Generate
|
101
|
+
# Generate tr() descriptor.
|
101
102
|
# @param [String] key
|
102
103
|
# @param [String] tree
|
103
104
|
# @return [Bitcoin::Descriptor::Tr]
|
@@ -105,14 +106,14 @@ module Bitcoin
|
|
105
106
|
Tr.new(key, tree)
|
106
107
|
end
|
107
108
|
|
108
|
-
# Generate
|
109
|
+
# Generate rawtr() descriptor.
|
109
110
|
# @param [String] key
|
110
111
|
# @return [Bitcoin::Descriptor::RawTr]
|
111
112
|
def rawtr(key)
|
112
113
|
RawTr.new(key)
|
113
114
|
end
|
114
115
|
|
115
|
-
# Generate
|
116
|
+
# Generate multi_a() descriptor.
|
116
117
|
# @param [Integer] threshold the threshold of multisig.
|
117
118
|
# @param [Array[String]] keys an array of keys.
|
118
119
|
# @return [Bitcoin::Descriptor::MultiA] multisig script.
|
@@ -120,7 +121,7 @@ module Bitcoin
|
|
120
121
|
MultiA.new(threshold, keys)
|
121
122
|
end
|
122
123
|
|
123
|
-
# Generate
|
124
|
+
# Generate sortedmulti_a() descriptor.
|
124
125
|
# @param [Integer] threshold the threshold of multisig.
|
125
126
|
# @param [Array[String]] keys an array of keys.
|
126
127
|
# @return [Bitcoin::Descriptor::SortedMulti]
|
@@ -128,16 +129,30 @@ module Bitcoin
|
|
128
129
|
SortedMultiA.new(threshold, keys)
|
129
130
|
end
|
130
131
|
|
132
|
+
# Generate musig() descriptor.
|
133
|
+
# @param [Array] keys An array fo keys.
|
134
|
+
# @param [String] path
|
135
|
+
# @return [Bitcoin::Descriptor::MuSig]
|
136
|
+
def musig(*keys, path: nil)
|
137
|
+
MuSig.new(keys, path)
|
138
|
+
end
|
139
|
+
|
131
140
|
# Parse descriptor string.
|
132
141
|
# @param [String] string Descriptor string.
|
133
142
|
# @return [Bitcoin::Descriptor::Expression]
|
134
143
|
def parse(string, top_level = true)
|
135
144
|
validate_checksum!(string)
|
136
145
|
content, _ = string.split('#')
|
137
|
-
exp, args_str = content.match(/(\w+)\((.+)\)/).captures
|
146
|
+
exp, args_str, path = content.match(/(\w+)\((.+)\)(.*)/).captures # split "tag(10, 20)/0/1"
|
147
|
+
raise ArgumentError, 'Invalid format.' if exp != 'musig' && !path.empty?
|
148
|
+
|
138
149
|
case exp
|
139
150
|
when 'pk'
|
140
|
-
|
151
|
+
if args_str.include?('(')
|
152
|
+
pk(parse(args_str))
|
153
|
+
else
|
154
|
+
pk(args_str)
|
155
|
+
end
|
141
156
|
when 'pkh'
|
142
157
|
pkh(args_str)
|
143
158
|
when 'wpkh'
|
@@ -169,16 +184,24 @@ module Bitcoin
|
|
169
184
|
when 'addr'
|
170
185
|
addr(args_str)
|
171
186
|
when 'tr'
|
172
|
-
key,
|
173
|
-
if
|
187
|
+
key, tree = split_two(args_str)
|
188
|
+
key = parse(key) if key.include?('(')
|
189
|
+
if tree.nil?
|
174
190
|
tr(key)
|
175
|
-
elsif
|
176
|
-
tr(key, parse_nested_string(
|
191
|
+
elsif tree.start_with?('{')
|
192
|
+
tr(key, parse_nested_string(tree))
|
177
193
|
else
|
178
|
-
tr(key, parse(
|
194
|
+
tr(key, parse(tree, false))
|
179
195
|
end
|
180
196
|
when 'rawtr'
|
181
|
-
|
197
|
+
if args_str.include?('(')
|
198
|
+
rawtr(parse(args_str, false))
|
199
|
+
else
|
200
|
+
rawtr(args_str)
|
201
|
+
end
|
202
|
+
when 'musig'
|
203
|
+
keys = args_str.split(',')
|
204
|
+
path.empty? ? musig(*keys) : musig(*keys, path: path)
|
182
205
|
else
|
183
206
|
raise ArgumentError, "Parse failed: #{string}"
|
184
207
|
end
|
@@ -229,5 +252,23 @@ module Bitcoin
|
|
229
252
|
current << parse(buffer, false) unless buffer.empty?
|
230
253
|
current.first
|
231
254
|
end
|
255
|
+
|
256
|
+
def split_two(content)
|
257
|
+
paren_depth = 0
|
258
|
+
split_pos = content.chars.find_index do |char|
|
259
|
+
case char
|
260
|
+
when '(' then paren_depth += 1; false
|
261
|
+
when ')' then paren_depth -= 1; false
|
262
|
+
when ',' then paren_depth == 0
|
263
|
+
else false
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
if split_pos
|
268
|
+
[content[0...split_pos], content[split_pos+1..-1]]
|
269
|
+
else
|
270
|
+
[content, nil]
|
271
|
+
end
|
272
|
+
end
|
232
273
|
end
|
233
274
|
end
|
@@ -2,7 +2,7 @@ module Bitcoin
|
|
2
2
|
module Message
|
3
3
|
|
4
4
|
# merckleblock message
|
5
|
-
# https://bitcoin.org/
|
5
|
+
# https://developer.bitcoin.org/reference/p2p_networking.html#merkleblock
|
6
6
|
class MerkleBlock < Base
|
7
7
|
|
8
8
|
COMMAND = 'merkleblock'
|
@@ -36,6 +36,12 @@ module Bitcoin
|
|
36
36
|
hashes.map(&:htb).join << Bitcoin.pack_var_int(flags.htb.bytesize) << flags.htb
|
37
37
|
end
|
38
38
|
|
39
|
+
# Generate partial tree.
|
40
|
+
# @return [Bitcoin::PartialTree]
|
41
|
+
def partial_tree
|
42
|
+
Bitcoin::PartialTree.build(tx_count, hashes, Bitcoin.byte_to_bit(flags.htb))
|
43
|
+
end
|
44
|
+
|
39
45
|
end
|
40
46
|
|
41
47
|
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
module Bitcoin
|
2
2
|
|
3
|
-
#
|
4
|
-
|
3
|
+
# A class for recovering partial from merkleblock message.
|
4
|
+
# For a complete Merkle tree implementation, migrate to the merkle gem.
|
5
|
+
class PartialTree
|
5
6
|
|
6
7
|
attr_accessor :root
|
7
8
|
|
@@ -13,21 +14,8 @@ module Bitcoin
|
|
13
14
|
root.value
|
14
15
|
end
|
15
16
|
|
16
|
-
def self.build_from_leaf(txids)
|
17
|
-
if txids.size == 1
|
18
|
-
nodes = [Node.new(txids.first)]
|
19
|
-
else
|
20
|
-
nodes = txids.each_slice(2).map{ |m|
|
21
|
-
left = Node.new(m[0])
|
22
|
-
right = Node.new(m[1] ? m[1] : m[0])
|
23
|
-
[left, right]
|
24
|
-
}.flatten
|
25
|
-
end
|
26
|
-
new(build_initial_tree(nodes))
|
27
|
-
end
|
28
|
-
|
29
17
|
# https://bitcoin.org/en/developer-reference#creating-a-merkleblock-message
|
30
|
-
def self.
|
18
|
+
def self.build(tx_count, hashes, flags)
|
31
19
|
flags = flags.each_char.map(&:to_i)
|
32
20
|
root = build_initial_tree( Array.new(tx_count) { Node.new })
|
33
21
|
current_node = root
|
@@ -31,33 +31,22 @@ module Bitcoin
|
|
31
31
|
raise NotImplementedError
|
32
32
|
end
|
33
33
|
|
34
|
-
def control_block(leaf)
|
35
|
-
raise NotImplementedError # TODO
|
36
|
-
end
|
37
|
-
|
38
|
-
def inclusion_proof(leaf)
|
39
|
-
raise NotImplementedError # TODO
|
40
|
-
end
|
41
|
-
|
42
34
|
private
|
43
35
|
|
44
36
|
def merkle_root
|
45
|
-
|
37
|
+
return '' if tree.empty?
|
38
|
+
script_tree = Merkle::CustomTree.new(config: Merkle::Config.taptree, leaves: extract_leaves(tree))
|
39
|
+
script_tree.compute_root
|
46
40
|
end
|
47
41
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
build_tree(right)
|
57
|
-
else
|
58
|
-
right
|
59
|
-
end
|
60
|
-
combine_hash([left_hash, right_hash])
|
42
|
+
def extract_leaves(leaves)
|
43
|
+
leaves.map do |leaf|
|
44
|
+
if leaf.is_a?(Bitcoin::Taproot::LeafNode)
|
45
|
+
leaf.leaf_hash
|
46
|
+
elsif leaf.is_a?(Array)
|
47
|
+
extract_leaves(leaf)
|
48
|
+
end
|
49
|
+
end
|
61
50
|
end
|
62
51
|
end
|
63
52
|
end
|
@@ -20,7 +20,6 @@ module Bitcoin
|
|
20
20
|
raise Error, "Internal public key must be #{X_ONLY_PUBKEY_SIZE} bytes" unless internal_key.htb.bytesize == X_ONLY_PUBKEY_SIZE
|
21
21
|
raise Error, 'leaf must be Bitcoin::Taproot::LeafNode object' if leaves.find{ |leaf| !leaf.is_a?(Bitcoin::Taproot::LeafNode)}
|
22
22
|
|
23
|
-
@leaves = leaves
|
24
23
|
@branches = leaves.each_slice(2).map.to_a
|
25
24
|
@internal_key = internal_key
|
26
25
|
end
|
@@ -75,74 +74,46 @@ module Bitcoin
|
|
75
74
|
# @param [Bitcoin::Taproot::LeafNode] leaf Leaf to use for unlocking.
|
76
75
|
# @return [Bitcoin::Taproot::ControlBlock] control block.
|
77
76
|
def control_block(leaf)
|
78
|
-
path = inclusion_proof(leaf)
|
77
|
+
path = inclusion_proof(leaf).siblings
|
79
78
|
parity = tweak_public_key.to_point.has_even_y? ? 0 : 1
|
80
|
-
ControlBlock.new(parity, leaf.leaf_ver, internal_key, path
|
79
|
+
ControlBlock.new(parity, leaf.leaf_ver, internal_key, path)
|
81
80
|
end
|
82
81
|
|
83
82
|
# Generate inclusion proof for +leaf+.
|
84
83
|
# @param [Bitcoin::Taproot::LeafNode] leaf The leaf node in script tree.
|
85
|
-
# @return [
|
84
|
+
# @return [Merkle::Proof] Inclusion proof.
|
86
85
|
# @raise [Bitcoin::Taproot::Error] If the specified +leaf+ does not exist
|
87
86
|
def inclusion_proof(leaf)
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
until parents.size == 1
|
98
|
-
parents = parents.each_slice(2).map do |pair|
|
99
|
-
combined = combine_hash(pair)
|
100
|
-
unless pair.size == 1
|
101
|
-
if hash_value(pair[0]) == parent_hash
|
102
|
-
proofs << hash_value(pair[1])
|
103
|
-
parent_hash = combined
|
104
|
-
elsif hash_value(pair[1]) == parent_hash
|
105
|
-
proofs << hash_value(pair[0])
|
106
|
-
parent_hash = combined
|
107
|
-
end
|
108
|
-
end
|
109
|
-
combined
|
87
|
+
tree = script_tree
|
88
|
+
leaf_index = 0
|
89
|
+
branches.each.with_index do |branch, i|
|
90
|
+
if branch.include?(leaf)
|
91
|
+
leaf_index += (branch[0] == leaf ? 0 : 1)
|
92
|
+
break
|
93
|
+
else
|
94
|
+
leaf_index += branch.length
|
110
95
|
end
|
111
96
|
end
|
112
|
-
|
97
|
+
tree.generate_proof(leaf_index)
|
113
98
|
end
|
114
99
|
|
115
100
|
private
|
116
101
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
else
|
126
|
-
parents = parents.each_slice(2).map { |pair| combine_hash(pair) } until parents.size == 1
|
127
|
-
end
|
128
|
-
parents.first.bth
|
129
|
-
end
|
130
|
-
|
131
|
-
def combine_hash(pair)
|
132
|
-
if pair.size == 1
|
133
|
-
hash_value(pair[0])
|
134
|
-
else
|
135
|
-
hash1 = hash_value(pair[0])
|
136
|
-
hash2 = hash_value(pair[1])
|
137
|
-
|
138
|
-
# Lexicographically sort a and b's hash, and compute parent hash.
|
139
|
-
payload = hash1.bth < hash2.bth ? hash1 + hash2 : hash2 + hash1
|
140
|
-
Bitcoin.tagged_hash('TapBranch', payload)
|
102
|
+
def script_tree
|
103
|
+
leaves = []
|
104
|
+
branches.each do |pair|
|
105
|
+
if leaves.empty? || leaves.length == 1
|
106
|
+
leaves << pair.map(&:leaf_hash)
|
107
|
+
elsif leaves.length == 2
|
108
|
+
leaves = [leaves, pair.map(&:leaf_hash)]
|
109
|
+
end
|
141
110
|
end
|
111
|
+
Merkle::CustomTree.new(config: Merkle::Config.taptree, leaves: leaves)
|
142
112
|
end
|
143
113
|
|
144
|
-
def
|
145
|
-
|
114
|
+
def merkle_root
|
115
|
+
return '' if branches.empty?
|
116
|
+
script_tree.compute_root
|
146
117
|
end
|
147
118
|
end
|
148
119
|
end
|
data/lib/bitcoin/version.rb
CHANGED
data/lib/bitcoin.rb
CHANGED
@@ -10,6 +10,7 @@ require 'bech32'
|
|
10
10
|
require 'base64'
|
11
11
|
require 'observer'
|
12
12
|
require 'tmpdir'
|
13
|
+
require 'merkle'
|
13
14
|
require_relative 'openassets'
|
14
15
|
|
15
16
|
module Bitcoin
|
@@ -31,7 +32,7 @@ module Bitcoin
|
|
31
32
|
autoload :TxOut, 'bitcoin/tx_out'
|
32
33
|
autoload :OutPoint, 'bitcoin/out_point'
|
33
34
|
autoload :ScriptWitness, 'bitcoin/script_witness'
|
34
|
-
autoload :
|
35
|
+
autoload :PartialTree, 'bitcoin/partial_tree'
|
35
36
|
autoload :Key, 'bitcoin/key'
|
36
37
|
autoload :ExtKey, 'bitcoin/ext_key'
|
37
38
|
autoload :ExtPubkey, 'bitcoin/ext_key'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bitcoinrb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- azuchi
|
@@ -233,6 +233,20 @@ dependencies:
|
|
233
233
|
- - ">="
|
234
234
|
- !ruby/object:Gem::Version
|
235
235
|
version: '0'
|
236
|
+
- !ruby/object:Gem::Dependency
|
237
|
+
name: merkle
|
238
|
+
requirement: !ruby/object:Gem::Requirement
|
239
|
+
requirements:
|
240
|
+
- - '='
|
241
|
+
- !ruby/object:Gem::Version
|
242
|
+
version: 0.3.0
|
243
|
+
type: :runtime
|
244
|
+
prerelease: false
|
245
|
+
version_requirements: !ruby/object:Gem::Requirement
|
246
|
+
requirements:
|
247
|
+
- - '='
|
248
|
+
- !ruby/object:Gem::Version
|
249
|
+
version: 0.3.0
|
236
250
|
description: The implementation of Bitcoin Protocol for Ruby.
|
237
251
|
email:
|
238
252
|
- azuchi@chaintope.com
|
@@ -287,6 +301,7 @@ files:
|
|
287
301
|
- lib/bitcoin/descriptor/key_expression.rb
|
288
302
|
- lib/bitcoin/descriptor/multi.rb
|
289
303
|
- lib/bitcoin/descriptor/multi_a.rb
|
304
|
+
- lib/bitcoin/descriptor/musig.rb
|
290
305
|
- lib/bitcoin/descriptor/pk.rb
|
291
306
|
- lib/bitcoin/descriptor/pkh.rb
|
292
307
|
- lib/bitcoin/descriptor/raw.rb
|
@@ -308,7 +323,6 @@ files:
|
|
308
323
|
- lib/bitcoin/key.rb
|
309
324
|
- lib/bitcoin/key_path.rb
|
310
325
|
- lib/bitcoin/logger.rb
|
311
|
-
- lib/bitcoin/merkle_tree.rb
|
312
326
|
- lib/bitcoin/message.rb
|
313
327
|
- lib/bitcoin/message/addr.rb
|
314
328
|
- lib/bitcoin/message/addr_v2.rb
|
@@ -376,6 +390,7 @@ files:
|
|
376
390
|
- lib/bitcoin/node/spv.rb
|
377
391
|
- lib/bitcoin/opcodes.rb
|
378
392
|
- lib/bitcoin/out_point.rb
|
393
|
+
- lib/bitcoin/partial_tree.rb
|
379
394
|
- lib/bitcoin/payment_code.rb
|
380
395
|
- lib/bitcoin/psbt.rb
|
381
396
|
- lib/bitcoin/psbt/hd_key_path.rb
|