bitcoinrb 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d105b6b0882459caee5c30db5e4ca8700a274c8fac2cd27f863e61719a1b484
4
- data.tar.gz: fb458ded6c3c0a892bcb546f6e753b160aeff8afaa917a5510eefd090d048dbe
3
+ metadata.gz: cbc1459b87de8addec350661eb84c3a8bd87503eff03d6bad50c329660fc2749
4
+ data.tar.gz: ccb70c6d406a1b4f7134c7c8b8f13f536ba8197d3479b4fca635c073cccd6f11
5
5
  SHA512:
6
- metadata.gz: b6062ddface7588de792e84b2c6c6394ffb145dd27aaf5fe22aae0ba191491478b958bf481a55c3f8547aa0a6066e8c7439c7ef4b625b26446dd5583f54cb61c
7
- data.tar.gz: 6768c846fb94280e5ef4f776f15ac532f1868142998d37bf551070be925a4ad7b19243322f287d1f607f1abf24816cad337735ff2eb6be3caa5afe4c187171fa
6
+ metadata.gz: 5ed8400e197cb53d7232265b87f201174e60dae37952e075dae4e4f961b41e9967a24fa2998845100212747493a0dc2bd0b03a79ca208a2beb824cfd48f4a9bf
7
+ data.tar.gz: 7585bf0a8179d1144a6f6310a63817538075a111ca92a248c2469df962c2fb913f091abffacdf57096cfb183ebe40406080ce807deca3c052c1a63b1934d895c
@@ -21,8 +21,12 @@ module Bitcoin
21
21
  @nonce = nonce
22
22
  end
23
23
 
24
+ # Parse block header from payload.
25
+ # @param [String|StringIO] payload block header paylaod
26
+ # @return [Bitcoin::BlockHeader]
24
27
  def self.parse_from_payload(payload)
25
- version, prev_hash, merkle_root, time, bits, nonce = payload.unpack('Va32a32VVV')
28
+ buf = payload.is_a?(String) ? StringIO.new(payload) : payload
29
+ version, prev_hash, merkle_root, time, bits, nonce = buf.read(80).unpack('Va32a32VVV')
26
30
  new(version, prev_hash.bth, merkle_root.bth, time, bits, nonce)
27
31
  end
28
32
 
@@ -11,6 +11,8 @@ module Bitcoin
11
11
  HASH160_SIZE = 20
12
12
  # Byte size of the HASH256 hash
13
13
  HASH256_SIZE = 32
14
+ # Byte size of x-only public key
15
+ X_ONLY_PUBKEY_SIZE = 32
14
16
 
15
17
  # The maximum allowed size for a serialized block, in bytes (only for buffer size limits)
16
18
  MAX_BLOCK_SERIALIZED_SIZE = 4_000_000
data/lib/bitcoin/key.rb CHANGED
@@ -17,7 +17,7 @@ module Bitcoin
17
17
  attr_accessor :key_type
18
18
  attr_reader :secp256k1_module
19
19
 
20
- TYPES = {uncompressed: 0x00, compressed: 0x01, p2pkh: 0x10, p2wpkh: 0x11, p2wpkh_p2sh: 0x12}
20
+ TYPES = {uncompressed: 0x00, compressed: 0x01, p2pkh: 0x10, p2wpkh: 0x11, p2wpkh_p2sh: 0x12, p2tr: 0x13}
21
21
 
22
22
  MIN_PRIV_KEY_MOD_ORDER = 0x01
23
23
  # Order of secp256k1's generator minus 1.
@@ -81,7 +81,7 @@ module Bitcoin
81
81
  # @param [String] xonly_pubkey xonly public key with hex format.
82
82
  # @return [Bitcoin::Key] key object has public key.
83
83
  def self.from_xonly_pubkey(xonly_pubkey)
84
- raise ArgumentError, 'xonly_pubkey must be 32 bytes' unless xonly_pubkey.htb.bytesize == 32
84
+ raise ArgumentError, "xonly_pubkey must be #{X_ONLY_PUBKEY_SIZE} bytes" unless xonly_pubkey.htb.bytesize == X_ONLY_PUBKEY_SIZE
85
85
  Bitcoin::Key.new(pubkey: "02#{xonly_pubkey}", key_type: TYPES[:compressed])
86
86
  end
87
87
 
@@ -180,23 +180,68 @@ module Bitcoin
180
180
  end
181
181
 
182
182
  # get pay to pubkey hash address
183
- # @deprecated
183
+ # @return [String] address
184
184
  def to_p2pkh
185
185
  Bitcoin::Script.to_p2pkh(hash160).to_addr
186
186
  end
187
187
 
188
188
  # get pay to witness pubkey hash address
189
- # @deprecated
189
+ # @return [String] address
190
190
  def to_p2wpkh
191
191
  Bitcoin::Script.to_p2wpkh(hash160).to_addr
192
192
  end
193
193
 
194
+ # Get pay to taproot address
195
+ # @return [String] address
196
+ def to_p2tr
197
+ Bitcoin::Script.to_p2tr(self).to_addr
198
+ end
199
+
194
200
  # get p2wpkh address nested in p2sh.
195
201
  # @deprecated
196
202
  def to_nested_p2wpkh
197
203
  Bitcoin::Script.to_p2wpkh(hash160).to_p2sh.to_addr
198
204
  end
199
205
 
206
+ # Determine if it is a P2PKH from key_type.
207
+ # @return [Boolean]
208
+ def p2pkh?
209
+ [TYPES[:uncompressed], TYPES[:compressed], TYPES[:p2pkh]].include?(key_type)
210
+ end
211
+
212
+ # Determine if it is a P2WPKH from key_type.
213
+ # @return [Boolean]
214
+ def p2wpkh?
215
+ key_type == TYPES[:p2wpkh]
216
+ end
217
+
218
+ # Determine if it is a nested P2WPKH from key_type.
219
+ # @return [Boolean]
220
+ def nested_p2wpkh?
221
+ key_type == TYPES[:p2wpkh_p2sh]
222
+ end
223
+
224
+ # Determine if it is a nested P2TR from key_type.
225
+ # @return [Boolean]
226
+ def p2tr?
227
+ key_type == TYPES[:p2tr]
228
+ end
229
+
230
+ # Returns the address corresponding to key_type.
231
+ # @return [String] address
232
+ def to_addr
233
+ case key_type
234
+ when TYPES[:uncompressed], TYPES[:compressed], TYPES[:p2pkh]
235
+ to_p2pkh
236
+ when TYPES[:p2wpkh]
237
+ to_p2wpkh
238
+ when TYPES[:p2wpkh_p2sh]
239
+ to_nested_p2wpkh
240
+ when TYPES[:p2tr]
241
+ to_p2tr
242
+ end
243
+ end
244
+
200
245
  def compressed?
201
246
  key_type != TYPES[:uncompressed]
202
247
  end
@@ -20,6 +20,15 @@ module Bitcoin
20
20
  self.new(txid.rhex, index)
21
21
  end
22
22
 
23
+ # Parse from payload
24
+ # @param [String|StringIO] payload prvout payload with binary format
25
+ # @return [Bitcoin::OutPoint]
26
+ def self.parse_from_payload(payload)
27
+ buf = payload.is_a?(String) ? StringIO.new(payload) : payload
28
+ hash, index = buf.read(36).unpack('a32V')
29
+ OutPoint.new(hash.bth, index)
30
+ end
31
+
23
32
  def coinbase?
24
33
  tx_hash == COINBASE_HASH && index == COINBASE_INDEX
25
34
  end
@@ -19,6 +19,12 @@ module Bitcoin
19
19
  attr_accessor :hash160_preimages
20
20
  attr_accessor :hash256_preimages
21
21
  attr_accessor :proprietaries
22
+ attr_accessor :tap_key_sig
23
+ attr_accessor :tap_script_sigs
24
+ attr_accessor :tap_leaf_scripts
25
+ attr_accessor :tap_bip32_derivations
26
+ attr_accessor :tap_internal_key
27
+ attr_accessor :tap_merkle_root
22
28
  attr_accessor :unknowns
23
29
 
24
30
  def initialize(non_witness_utxo: nil, witness_utxo: nil)
@@ -31,6 +37,9 @@ module Bitcoin
31
37
  @hash160_preimages = {}
32
38
  @hash256_preimages = {}
33
39
  @proprietaries = []
40
+ @tap_script_sigs = {}
41
+ @tap_leaf_scripts = {}
42
+ @tap_bip32_derivations = {}
34
43
  @unknowns = {}
35
44
  end
36
45
 
@@ -110,6 +119,35 @@ module Bitcoin
110
119
  when PSBT_IN_TYPES[:proprietary]
111
120
  raise ArgumentError, 'Duplicate Key, key for proprietary value already provided.' if input.proprietaries.any?{|p| p.key == key}
112
121
  input.proprietaries << Proprietary.new(key, value)
122
+ when PSBT_IN_TYPES[:tap_key_sig]
123
+ raise ArgumentError, 'Size of key was not the expected size for the type tap key sig' unless key_len == 1
124
+ raise ArgumentError, 'Invalid schnorr signature size for the type tap key sig' unless [64, 65].include?(value.bytesize)
125
+ input.tap_key_sig = value.bth
126
+ when PSBT_IN_TYPES[:tap_script_sig]
127
+ raise ArgumentError, 'Duplicate Key, key for tap script sig value already provided.' if input.tap_script_sigs[key.bth]
128
+ raise ArgumentError, 'Size of key was not the expected size for the type tap script sig' unless key.bytesize == (X_ONLY_PUBKEY_SIZE + 32)
129
+ raise ArgumentError, 'Invalid schnorr signature size for the type tap script sig' unless [64, 65].include?(value.bytesize)
130
+ input.tap_script_sigs[key.bth] = value.bth
131
+ when PSBT_IN_TYPES[:tap_leaf_script]
132
+ begin
133
+ cb = Bitcoin::Taproot::ControlBlock.parse_from_payload(key)
134
+ raise ArgumentError, 'Duplicate Key, key for tap leaf script value already provided.' if input.tap_leaf_scripts[cb]
135
+ input.tap_leaf_scripts[cb] = value.bth
136
+ rescue Bitcoin::Taproot::Error => e
137
+ raise ArgumentError, e.message
138
+ end
139
+ when PSBT_IN_TYPES[:tap_bip32_derivation]
140
+ raise ArgumentError, 'Duplicate Key, key for tap bip32 derivation value already provided.' if input.tap_bip32_derivations[key.bth]
141
+ raise ArgumentError, 'Size of key was not the expected size for the type tap bip32 derivation' unless key.bytesize == X_ONLY_PUBKEY_SIZE
142
+ input.tap_bip32_derivations[key.bth] = value.bth
143
+ when PSBT_IN_TYPES[:tap_internal_key]
144
+ raise ArgumentError, 'Size of key was not the expected size for the type tap internal key' unless key_len == 1
145
+ raise ArgumentError, 'Invalid x-only public key size for the type tap internal key' unless value.bytesize == X_ONLY_PUBKEY_SIZE
146
+ input.tap_internal_key = value.bth
147
+ when PSBT_IN_TYPES[:tap_merkle_root]
148
+ raise ArgumentError, 'Size of key was not the expected size for the type tap merkle root' unless key_len == 1
149
+ raise ArgumentError, 'Invalid merkle root hash size for the type tap merkle root' unless value.bytesize == 32
150
+ input.tap_merkle_root = value.bth
113
151
  else
114
152
  unknown_key = ([key_type].pack('C') + key).bth
115
153
  raise ArgumentError, 'Duplicate Key, key for unknown value already provided.' if input.unknowns[unknown_key]
@@ -135,6 +173,12 @@ module Bitcoin
135
173
  payload << sha256_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:sha256], key: k.htb, value: v.htb)}.join
136
174
  payload << hash160_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:hash160], key: k.htb, value: v.htb)}.join
137
175
  payload << hash256_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:hash256], key: k.htb, value: v.htb)}.join
176
+ payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_key_sig], value: tap_key_sig.htb) if tap_key_sig
177
+ payload << tap_script_sigs.map{|k, v| PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_script_sig], key: k.htb, value: v.htb)}.join
178
+ payload << tap_leaf_scripts.map{|k, v| PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_leaf_script], key: k.to_payload, value: v.htb)}.join
179
+ payload << tap_bip32_derivations.map{|k, v| PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_bip32_derivation], key: k.htb, value: v.htb)}.join
180
+ payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_internal_key], value: tap_internal_key.htb) if tap_internal_key
181
+ payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_merkle_root], value: tap_merkle_root.htb) if tap_merkle_root
138
182
  end
139
183
  payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:script_sig], value: final_script_sig.to_payload) if final_script_sig
140
184
  payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:script_witness], value: final_script_witness.to_payload) if final_script_witness
@@ -8,11 +8,15 @@ module Bitcoin
8
8
  attr_accessor :witness_script
9
9
  attr_accessor :hd_key_paths
10
10
  attr_accessor :proprietaries
11
+ attr_accessor :tap_internal_key
12
+ attr_accessor :tap_tree
13
+ attr_accessor :tap_bip32_derivations
11
14
  attr_accessor :unknowns
12
15
 
13
16
  def initialize
14
17
  @hd_key_paths = {}
15
18
  @proprietaries = []
19
+ @tap_bip32_derivations = {}
16
20
  @unknowns = {}
17
21
  end
18
22
 
@@ -46,6 +50,17 @@ module Bitcoin
46
50
  when PSBT_OUT_TYPES[:proprietary]
47
51
  raise ArgumentError, 'Duplicate Key, key for proprietary value already provided.' if output.proprietaries.any?{|p| p.key == key}
48
52
  output.proprietaries << Proprietary.new(key, value)
53
+ when PSBT_OUT_TYPES[:tap_internal_key]
54
+ raise ArgumentError, 'Invalid output tap internal key typed key.' unless key_len == 1
55
+ raise ArgumentError, 'Invalid x-only public key size for the type tap internal key' unless value.bytesize == X_ONLY_PUBKEY_SIZE
56
+ output.tap_internal_key = value.bth
57
+ when PSBT_OUT_TYPES[:tap_tree]
58
+ raise ArgumentError, 'Invalid output tap tree typed key.' unless key_len == 1
59
+ output.tap_tree = value.bth # TODO implement builder.
60
+ when PSBT_OUT_TYPES[:tap_bip32_derivation]
61
+ raise ArgumentError, 'Duplicate Key, key for tap bip32 derivation value already provided.' if output.tap_bip32_derivations[key.bth]
62
+ raise ArgumentError, 'Size of key was not the expected size for the type tap bip32 derivation' unless key.bytesize == X_ONLY_PUBKEY_SIZE
63
+ output.tap_bip32_derivations[key.bth] = value.bth
49
64
  else
50
65
  unknown_key = ([key_type].pack('C') + key).bth
51
66
  raise ArgumentError, 'Duplicate Key, key for unknown value already provided' if output.unknowns[unknown_key]
@@ -62,6 +77,9 @@ module Bitcoin
62
77
  payload << PSBT.serialize_to_vector(PSBT_OUT_TYPES[:witness_script], value: witness_script) if witness_script
63
78
  payload << hd_key_paths.values.map{|v|v.to_payload(PSBT_OUT_TYPES[:bip32_derivation])}.join
64
79
  payload << proprietaries.map(&:to_payload).join
80
+ payload << PSBT.serialize_to_vector(PSBT_OUT_TYPES[:tap_internal_key], value: tap_internal_key.htb) if tap_internal_key
81
+ payload << PSBT.serialize_to_vector(PSBT_OUT_TYPES[:tap_tree], value: tap_tree.htb) if tap_tree
82
+ payload << tap_bip32_derivations.map{|k, v|PSBT.serialize_to_vector(PSBT_OUT_TYPES[:tap_bip32_derivation], key: k.htb, value: v.htb)}.join
65
83
  payload << unknowns.map {|k,v|Bitcoin.pack_var_int(k.htb.bytesize) << k.htb << Bitcoin.pack_var_int(v.bytesize) << v}.join
66
84
  payload << PSBT_SEPARATOR.itb
67
85
  payload
data/lib/bitcoin/psbt.rb CHANGED
@@ -33,12 +33,21 @@ module Bitcoin
33
33
  sha256: 0x0b,
34
34
  hash160: 0x0c,
35
35
  hash256: 0x0d,
36
+ tap_key_sig: 0x13,
37
+ tap_script_sig: 0x14,
38
+ tap_leaf_script: 0x15,
39
+ tap_bip32_derivation: 0x16,
40
+ tap_internal_key: 0x17,
41
+ tap_merkle_root: 0x18,
36
42
  proprietary: 0xfc
37
43
  }
38
44
  PSBT_OUT_TYPES = {
39
45
  redeem_script: 0x00,
40
46
  witness_script: 0x01,
41
47
  bip32_derivation: 0x02,
48
+ tap_internal_key: 0x05,
49
+ tap_tree: 0x06,
50
+ tap_bip32_derivation: 0x07,
42
51
  proprietary: 0xfc
43
52
  }
44
53
  PSBT_SEPARATOR = 0x00
@@ -15,15 +15,28 @@ module Bitcoin
15
15
  end
16
16
 
17
17
  # generate P2PKH script
18
+ # @param [String] pubkey_hash public key hash with hex format
18
19
  def self.to_p2pkh(pubkey_hash)
19
20
  new << OP_DUP << OP_HASH160 << pubkey_hash << OP_EQUALVERIFY << OP_CHECKSIG
20
21
  end
21
22
 
22
23
  # generate P2WPKH script
24
+ # @param [String] pubkey_hash public key hash with hex format
25
+ # @return [Bitcoin::Script]
23
26
  def self.to_p2wpkh(pubkey_hash)
24
27
  new << WITNESS_VERSION_V0 << pubkey_hash
25
28
  end
26
29
 
30
+ # Generate P2TR script
31
+ # @param [String or Bitcoin::Key] xonly_pubkey public key with hex format or Bitcoin::Key
32
+ # @return [Bitcoin::Script]
33
+ # @raise ArgumentError
34
+ def self.to_p2tr(xonly_pubkey)
35
+ xonly_pubkey = xonly_pubkey.xonly_pubkey if xonly_pubkey.is_a?(Bitcoin::Key)
36
+ raise ArgumentError, 'Invalid public key size' unless xonly_pubkey.htb.bytesize == Bitcoin::WITNESS_V1_TAPROOT_SIZE
37
+ new << OP_1 << xonly_pubkey
38
+ end
39
+
27
40
  # generate m of n multisig p2sh script
28
41
  # @param [String] m the number of signatures required for multisig
29
42
  # @param [Array] pubkeys array of public keys that compose multisig
@@ -136,16 +136,15 @@ module Bitcoin
136
136
  else
137
137
  sig_version = :tapscript
138
138
  # Script path spending (stack size is >1 after removing optional annex)
139
- control = stack.pop.htb
140
- script_payload = stack.pop.htb
141
- if control.bytesize < TAPROOT_CONTROL_BASE_SIZE || control.bytesize > TAPROOT_CONTROL_MAX_SIZE ||
142
- (control.bytesize - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE != 0
139
+ begin
140
+ control = Bitcoin::Taproot::ControlBlock.parse_from_payload(stack.pop.htb)
141
+ rescue Bitcoin::Taproot::Error
143
142
  return set_error(SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE)
144
143
  end
145
- leaf_ver = control[0].bti & TAPROOT_LEAF_MASK
146
- opts[:leaf_hash] = Bitcoin.tagged_hash('TapLeaf', [leaf_ver].pack('C') + Bitcoin.pack_var_string(script_payload))
144
+ script_payload = stack.pop.htb
145
+ opts[:leaf_hash] = Bitcoin.tagged_hash('TapLeaf', [control.leaf_ver].pack('C') + Bitcoin.pack_var_string(script_payload))
147
146
  return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless valid_taproot_commitment?(control, program, opts[:leaf_hash])
148
- if (control[0].bti & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT
147
+ if control.leaf_ver == TAPROOT_LEAF_TAPSCRIPT
149
148
  opts[:weight_left] = witness.to_payload.bytesize + VALIDATION_WEIGHT_OFFSET
150
149
  script_pubkey = Bitcoin::Script.parse_from_payload(script_payload)
151
150
  script_pubkey.chunks.each do |c|
@@ -698,19 +697,11 @@ module Bitcoin
698
697
  # check whether valid taproot commitment.
699
698
  def valid_taproot_commitment?(control, program, leaf_hash)
700
699
  begin
701
- path_len = (control.bytesize - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE
702
- xonly_pubkey = control[1...TAPROOT_CONTROL_BASE_SIZE]
703
- p = Bitcoin::Key.from_xonly_pubkey(xonly_pubkey.bth)
700
+ p = Bitcoin::Key.from_xonly_pubkey(control.internal_key)
704
701
  k = leaf_hash
705
- path_len.times do |i|
706
- pos = (TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * i)
707
- e = control[pos...(pos + TAPROOT_CONTROL_NODE_SIZE)]
708
- k = Bitcoin.tagged_hash('TapBranch', k.bth < e.bth ? k + e : e + k)
709
- end
710
- t = Bitcoin.tagged_hash('TapTweak', xonly_pubkey + k)
711
- key = Bitcoin::Key.new(priv_key: t.bth, key_type: Key::TYPES[:compressed])
712
- q = key.to_point + p.to_point
713
- return q.x == program.bti && (control[0].bti & 1) == (q.y % 2)
702
+ control.paths.each { |e| k = Bitcoin.tagged_hash('TapBranch', k.bth < e ? k + e.htb : e.htb + k) }
703
+ q = Bitcoin::Taproot.tweak_public_key(p, k.bth).to_point
704
+ return q.x == program.bti && control.parity == (q.y % 2)
714
705
  rescue ArgumentError
715
706
  return false
716
707
  end
@@ -751,7 +742,7 @@ module Bitcoin
751
742
  pubkey_size = pubkey.htb.bytesize
752
743
  if pubkey_size == 0
753
744
  return set_error(SCRIPT_ERR_PUBKEYTYPE)
754
- elsif pubkey_size == 32
745
+ elsif pubkey_size == X_ONLY_PUBKEY_SIZE
755
746
  if success
756
747
  result = checker.check_schnorr_sig(sig, pubkey, :tapscript, opts)
757
748
  return checker.has_error? ? set_error(checker.error_code) : set_error(SCRIPT_ERR_SCHNORR_SIG) unless result
@@ -760,7 +751,6 @@ module Bitcoin
760
751
  return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE) if flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE)
761
752
  end
762
753
  success
763
- # return true
764
754
  end
765
755
 
766
756
  end
@@ -232,7 +232,7 @@ module Bitcoin
232
232
  def full_pubkey_from_xonly_pubkey(pub_key)
233
233
  with_context do |context|
234
234
  pubkey = pub_key.htb
235
- raise ArgumentError, 'Pubkey size must be 32 bytes.' unless pubkey.bytesize == 32
235
+ raise ArgumentError, "Pubkey size must be #{X_ONLY_PUBKEY_SIZE} bytes." unless pubkey.bytesize == X_ONLY_PUBKEY_SIZE
236
236
  xonly_pubkey = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
237
237
  full_pubkey = FFI::MemoryPointer.new(:uchar, 64)
238
238
  raise ArgumentError, 'An invalid public key was specified.' unless secp256k1_xonly_pubkey_parse(context, full_pubkey, xonly_pubkey) == 1
@@ -32,7 +32,7 @@ module Bitcoin
32
32
  # @return [Boolean] result.
33
33
  def valid_xonly_pubkey?(pub_key)
34
34
  pubkey = pub_key.htb
35
- return false unless pubkey.bytesize == 32
35
+ return false unless pubkey.bytesize == X_ONLY_PUBKEY_SIZE
36
36
  begin
37
37
  ECDSA::Format::PointOctetString.decode(pubkey, ECDSA::Group::Secp256k1)
38
38
  rescue Exception
@@ -0,0 +1,47 @@
1
+ module Bitcoin
2
+ module Taproot
3
+ class ControlBlock
4
+ include Bitcoin::HexConverter
5
+
6
+ attr_accessor :parity
7
+ attr_accessor :leaf_ver
8
+ attr_accessor :internal_key
9
+ attr_accessor :paths
10
+
11
+ # @param [Integer] parity
12
+ # @param [Integer] leaf_ver
13
+ # @param [String] internal_key public key with hex format.
14
+ # @param [Array[String]] paths array of hash values of sibling nodes in the tree that serve as proof of inclusion
15
+ def initialize(parity, leaf_ver, internal_key, paths = [])
16
+ @parity = parity
17
+ @leaf_ver = leaf_ver
18
+ @internal_key = internal_key
19
+ @paths = paths
20
+ end
21
+
22
+ # parse control block from payload.
23
+ # @raise [Bitcoin::Taproot::Error]
24
+ # @return [Bitcoin::Taproot::ControlBlock]
25
+ def self.parse_from_payload(payload)
26
+ raise Bitcoin::Taproot::Error, 'Invalid data length for Control Block' if payload.bytesize > TAPROOT_CONTROL_MAX_SIZE
27
+ raise Bitcoin::Taproot::Error, 'Invalid data length for path in Control Block' unless (payload.bytesize - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE == 0
28
+ control, internal_key, paths = payload.unpack('Ca32a*')
29
+ parity = control & 1
30
+ leaf_ver = control - parity
31
+ raw_paths = StringIO.new(paths)
32
+ paths = (paths.bytesize / 32).times.map { raw_paths.read(32).bth }
33
+ ControlBlock.new(parity, leaf_ver, internal_key.bth, paths)
34
+ end
35
+
36
+ # Convert to payload.
37
+ # @return [String] payload with binary format.
38
+ def to_payload
39
+ [parity + leaf_ver].pack("C") + internal_key.htb + paths.map(&:htb).join
40
+ end
41
+
42
+ def ==(other)
43
+ to_payload == other.to_payload
44
+ end
45
+ end
46
+ end
47
+ end
@@ -16,7 +16,7 @@ module Bitcoin
16
16
  # @raise [Bitcoin::Taproot::Builder] +internal_pubkey+ dose not xonly public key or leaf in +leaves+ does not instance of Bitcoin::Taproot::LeafNode.
17
17
  # @return [Bitcoin::Taproot::SimpleBuilder]
18
18
  def initialize(internal_key, leaves = [])
19
- raise Error, 'Internal public key must be 32 bytes' unless internal_key.htb.bytesize == 32
19
+ raise Error, "Internal public key must be #{X_ONLY_PUBKEY_SIZE} bytes" unless internal_key.htb.bytesize == X_ONLY_PUBKEY_SIZE
20
20
  raise Error, 'leaf must be Bitcoin::Taproot::LeafNode object' if leaves.find{ |leaf| !leaf.is_a?(Bitcoin::Taproot::LeafNode)}
21
21
 
22
22
  @leaves = leaves
@@ -72,11 +72,11 @@ module Bitcoin
72
72
 
73
73
  # Generate control block needed to unlock with script-path.
74
74
  # @param [Bitcoin::Taproot::LeafNode] leaf Leaf to use for unlocking.
75
- # @return [String] control block with binary format.
75
+ # @return [Bitcoin::Taproot::ControlBlock] control block.
76
76
  def control_block(leaf)
77
77
  path = inclusion_proof(leaf)
78
78
  parity = tweak_public_key.to_point.has_even_y? ? 0 : 1
79
- [parity + leaf.leaf_ver].pack("C") + internal_key.htb + path.join
79
+ ControlBlock.new(parity, leaf.leaf_ver, internal_key, path.map(&:bth))
80
80
  end
81
81
 
82
82
  # Generate inclusion proof for +leaf+.
@@ -4,6 +4,7 @@ module Bitcoin
4
4
  class Error < StandardError; end
5
5
 
6
6
  autoload :LeafNode, 'bitcoin/taproot/leaf_node'
7
+ autoload :ControlBlock, 'bitcoin/taproot/control_block'
7
8
  autoload :SimpleBuilder, 'bitcoin/taproot/simple_builder'
8
9
 
9
10
  module_function
data/lib/bitcoin/tx_in.rb CHANGED
@@ -34,8 +34,7 @@ module Bitcoin
34
34
  def self.parse_from_payload(payload)
35
35
  buf = payload.is_a?(String) ? StringIO.new(payload) : payload
36
36
  i = new
37
- hash, index = buf.read(36).unpack('a32V')
38
- i.out_point = OutPoint.new(hash.bth, index)
37
+ i.out_point = OutPoint.parse_from_payload(buf)
39
38
  sig_length = Bitcoin.unpack_var_int_from_io(buf)
40
39
  if sig_length == 0
41
40
  i.script_sig = Bitcoin::Script.new
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "1.1.1"
2
+ VERSION = "1.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bitcoinrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - azuchi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-05-22 00:00:00.000000000 Z
11
+ date: 2022-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ecdsa
@@ -479,6 +479,7 @@ files:
479
479
  - lib/bitcoin/store/spv_chain.rb
480
480
  - lib/bitcoin/store/utxo_db.rb
481
481
  - lib/bitcoin/taproot.rb
482
+ - lib/bitcoin/taproot/control_block.rb
482
483
  - lib/bitcoin/taproot/leaf_node.rb
483
484
  - lib/bitcoin/taproot/simple_builder.rb
484
485
  - lib/bitcoin/tx.rb
@@ -516,7 +517,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
516
517
  - !ruby/object:Gem::Version
517
518
  version: '0'
518
519
  requirements: []
519
- rubygems_version: 3.3.7
520
+ rubygems_version: 3.3.23
520
521
  signing_key:
521
522
  specification_version: 4
522
523
  summary: The implementation of Bitcoin Protocol for Ruby.