bitcoinrb 1.1.1 → 1.2.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 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.