bitcoinrb 1.4.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +2 -2
  3. data/.ruby-version +1 -1
  4. data/README.md +16 -5
  5. data/bitcoinrb.gemspec +2 -2
  6. data/lib/bitcoin/bip324/cipher.rb +113 -0
  7. data/lib/bitcoin/bip324/ell_swift_pubkey.rb +42 -0
  8. data/lib/bitcoin/bip324/fs_chacha20.rb +132 -0
  9. data/lib/bitcoin/bip324/fs_chacha_poly1305.rb +129 -0
  10. data/lib/bitcoin/bip324.rb +144 -0
  11. data/lib/bitcoin/descriptor/addr.rb +31 -0
  12. data/lib/bitcoin/descriptor/checksum.rb +74 -0
  13. data/lib/bitcoin/descriptor/combo.rb +30 -0
  14. data/lib/bitcoin/descriptor/expression.rb +122 -0
  15. data/lib/bitcoin/descriptor/key_expression.rb +23 -0
  16. data/lib/bitcoin/descriptor/multi.rb +49 -0
  17. data/lib/bitcoin/descriptor/multi_a.rb +43 -0
  18. data/lib/bitcoin/descriptor/pk.rb +27 -0
  19. data/lib/bitcoin/descriptor/pkh.rb +15 -0
  20. data/lib/bitcoin/descriptor/raw.rb +32 -0
  21. data/lib/bitcoin/descriptor/script_expression.rb +24 -0
  22. data/lib/bitcoin/descriptor/sh.rb +31 -0
  23. data/lib/bitcoin/descriptor/sorted_multi.rb +15 -0
  24. data/lib/bitcoin/descriptor/sorted_multi_a.rb +15 -0
  25. data/lib/bitcoin/descriptor/tr.rb +91 -0
  26. data/lib/bitcoin/descriptor/wpkh.rb +19 -0
  27. data/lib/bitcoin/descriptor/wsh.rb +30 -0
  28. data/lib/bitcoin/descriptor.rb +176 -100
  29. data/lib/bitcoin/ext/ecdsa.rb +0 -6
  30. data/lib/bitcoin/key.rb +16 -4
  31. data/lib/bitcoin/message_sign.rb +13 -8
  32. data/lib/bitcoin/script/script.rb +8 -3
  33. data/lib/bitcoin/secp256k1/native.rb +62 -6
  34. data/lib/bitcoin/secp256k1/ruby.rb +21 -4
  35. data/lib/bitcoin/taproot/custom_depth_builder.rb +64 -0
  36. data/lib/bitcoin/taproot/simple_builder.rb +1 -6
  37. data/lib/bitcoin/taproot.rb +1 -0
  38. data/lib/bitcoin/tx.rb +1 -1
  39. data/lib/bitcoin/util.rb +11 -3
  40. data/lib/bitcoin/version.rb +1 -1
  41. data/lib/bitcoin.rb +1 -0
  42. metadata +30 -7
@@ -3,145 +3,221 @@ module Bitcoin
3
3
  module Descriptor
4
4
 
5
5
  include Bitcoin::Opcodes
6
-
7
- # generate P2PK output for the given public key.
6
+ autoload :Expression, 'bitcoin/descriptor/expression'
7
+ autoload :KeyExpression, 'bitcoin/descriptor/key_expression'
8
+ autoload :ScriptExpression, 'bitcoin/descriptor/script_expression'
9
+ autoload :Pk, 'bitcoin/descriptor/pk'
10
+ autoload :Pkh, 'bitcoin/descriptor/pkh'
11
+ autoload :Wpkh, 'bitcoin/descriptor/wpkh'
12
+ autoload :Sh, 'bitcoin/descriptor/sh'
13
+ autoload :Wsh, 'bitcoin/descriptor/wsh'
14
+ autoload :Combo, 'bitcoin/descriptor/combo'
15
+ autoload :Multi, 'bitcoin/descriptor/multi'
16
+ autoload :SortedMulti, 'bitcoin/descriptor/sorted_multi'
17
+ autoload :Raw, 'bitcoin/descriptor/raw'
18
+ autoload :Addr, 'bitcoin/descriptor/addr'
19
+ autoload :Tr, 'bitcoin/descriptor/tr'
20
+ autoload :MultiA, 'bitcoin/descriptor/multi_a'
21
+ autoload :SortedMultiA, 'bitcoin/descriptor/sorted_multi_a'
22
+ autoload :Checksum, 'bitcoin/descriptor/checksum'
23
+
24
+ module_function
25
+
26
+ # Generate P2PK output for the given public key.
8
27
  # @param [String] key private key or public key with hex format
9
- # @return [Bitcoin::Script] P2PK script.
28
+ # @return [Bitcoin::Descriptor::Pk]
10
29
  def pk(key)
11
- Bitcoin::Script.new << extract_pubkey(key) << OP_CHECKSIG
30
+ Pk.new(key)
12
31
  end
13
32
 
14
- # generate P2PKH output for the given public key.
33
+ # Generate P2PKH output for the given public key.
15
34
  # @param [String] key private key or public key with hex format.
16
- # @return [Bitcoin::Script] P2PKH script.
35
+ # @return [Bitcoin::Descriptor::Pkh]
17
36
  def pkh(key)
18
- Bitcoin::Script.to_p2pkh(Bitcoin.hash160(extract_pubkey(key)))
37
+ Pkh.new(key)
19
38
  end
20
39
 
21
- # generate P2PKH output for the given public key.
40
+ # Generate P2PKH output for the given public key.
22
41
  # @param [String] key private key or public key with hex format.
23
- # @return [Bitcoin::Script] P2WPKH script.
42
+ # @return [Bitcoin::Descriptor::Wpkh]
24
43
  def wpkh(key)
25
- pubkey = extract_pubkey(key)
26
- raise ArgumentError, "Uncompressed key are not allowed." unless compressed_key?(pubkey)
27
- Bitcoin::Script.to_p2wpkh(Bitcoin.hash160(pubkey))
44
+ Wpkh.new(key)
28
45
  end
29
46
 
30
- # generate P2SH embed the argument.
31
- # @param [String or Script] script script to be embed.
32
- # @return [Bitcoin::Script] P2SH script.
33
- def sh(script)
34
- script = script.to_hex if script.is_a?(Bitcoin::Script)
35
- raise ArgumentError, "P2SH script is too large, 547 bytes is larger than #{Bitcoin::MAX_SCRIPT_ELEMENT_SIZE} bytes." if script.htb.bytesize > Bitcoin::MAX_SCRIPT_ELEMENT_SIZE
36
- Bitcoin::Script.to_p2sh(Bitcoin.hash160(script))
47
+ # Generate P2SH embed the argument.
48
+ # @param [Bitcoin::Descriptor::Base] exp script expression to be embed.
49
+ # @return [Bitcoin::Descriptor::Sh]
50
+ def sh(exp)
51
+ Sh.new(exp)
37
52
  end
38
53
 
39
- # generate P2WSH embed the argument.
40
- # @param [String or Script] script script to be embed.
41
- # @return [Bitcoin::Script] P2WSH script.
42
- def wsh(script)
43
- script = Bitcoin::Script(script.htb) if script.is_a?(String)
44
- raise ArgumentError, "P2SH script is too large, 547 bytes is larger than #{Bitcoin::MAX_SCRIPT_ELEMENT_SIZE} bytes." if script.to_payload.bytesize > Bitcoin::MAX_SCRIPT_ELEMENT_SIZE
45
- raise ArgumentError, "Uncompressed key are not allowed." if script.get_pubkeys.any?{|p|!compressed_key?(p)}
46
- Bitcoin::Script.to_p2wsh(script)
54
+ # Generate P2WSH embed the argument.
55
+ # @param [Bitcoin::Descriptor::Expression] exp script expression to be embed.
56
+ # @return [Bitcoin::Descriptor::Wsh]
57
+ def wsh(exp)
58
+ Wsh.new(exp)
47
59
  end
48
60
 
49
- # an alias for the collection of `pk(KEY)` and `pkh(KEY)`.
61
+ # An alias for the collection of `pk(KEY)` and `pkh(KEY)`.
50
62
  # If the key is compressed, it also includes `wpkh(KEY)` and `sh(wpkh(KEY))`.
51
63
  # @param [String] key private key or public key with hex format.
52
- # @return [Array[Bitcoin::Script]]
64
+ # @return [Bitcoin::Descriptor::Combo]
53
65
  def combo(key)
54
- result = [pk(key), pkh(key)]
55
- pubkey = extract_pubkey(key)
56
- if compressed_key?(pubkey)
57
- result << wpkh(key)
58
- result << sh(result.last)
59
- end
60
- result
66
+ Combo.new(key)
61
67
  end
62
68
 
63
- # generate multisig output for given keys.
69
+ # Generate multisig output for given keys.
64
70
  # @param [Integer] threshold the threshold of multisig.
65
71
  # @param [Array[String]] keys an array of keys.
66
- # @return [Bitcoin::Script] multisig script.
67
- def multi(threshold, *keys, sort: false)
68
- raise ArgumentError, 'Multisig threshold is not valid.' unless threshold.is_a?(Integer)
69
- raise ArgumentError, 'Multisig threshold cannot be 0, must be at least 1.' unless threshold > 0
70
- raise ArgumentError, 'Multisig threshold cannot be larger than the number of keys.' if threshold > keys.size
71
- raise ArgumentError, 'Multisig must have between 1 and 16 keys, inclusive.' if keys.size > 16
72
- pubkeys = keys.map{|key| extract_pubkey(key) }
73
- Bitcoin::Script.to_multisig_script(threshold, pubkeys, sort: sort)
72
+ # @return [Bitcoin::Descriptor::Multi] multisig script.
73
+ def multi(threshold, *keys)
74
+ Multi.new(threshold, keys)
74
75
  end
75
76
 
76
- # generate sorted multisig output for given keys.
77
+ # Generate sorted multisig output for given keys.
77
78
  # @param [Integer] threshold the threshold of multisig.
78
79
  # @param [Array[String]] keys an array of keys.
79
- # @return [Bitcoin::Script] multisig script.
80
+ # @return [Bitcoin::Descriptor::SortedMulti]
80
81
  def sortedmulti(threshold, *keys)
81
- multi(threshold, *keys, sort: true)
82
- end
83
-
84
- private
85
-
86
- # extract public key from KEY format.
87
- # @param [String] key KEY string.
88
- # @return [String] public key.
89
- def extract_pubkey(key)
90
- if key.start_with?('[') # BIP32 fingerprint
91
- raise ArgumentError, 'Invalid key origin.' if key.count('[') > 1 || key.count(']') > 1
92
- info = key[1...key.index(']')] # TODO
93
- fingerprint, *paths = info.split('/')
94
- raise ArgumentError, 'Fingerprint is not hex.' unless fingerprint.valid_hex?
95
- raise ArgumentError, 'Fingerprint is not 4 bytes.' unless fingerprint.size == 8
96
- key = key[(key.index(']') + 1)..-1]
97
- else
98
- raise ArgumentError, 'Invalid key origin.' if key.include?(']')
99
- end
82
+ SortedMulti.new(threshold, keys)
83
+ end
100
84
 
101
- # check BIP32 derivation path
102
- key, *paths = key.split('/')
85
+ # Generate raw output script about +hex+.
86
+ # @param [String] hex Hex string of bitcoin script.
87
+ # @return [Bitcoin::Descriptor::Raw]
88
+ def raw(hex)
89
+ Raw.new(hex)
90
+ end
103
91
 
104
- if key.start_with?('xprv')
105
- key = Bitcoin::ExtKey.from_base58(key)
106
- key = derive_path(key, paths, true) if paths
107
- elsif key.start_with?('xpub')
108
- key = Bitcoin::ExtPubkey.from_base58(key)
109
- key = derive_path(key, paths, false) if paths
110
- else
111
- begin
112
- key = Bitcoin::Key.from_wif(key)
113
- rescue ArgumentError
114
- key_type = compressed_key?(key) ? Bitcoin::Key::TYPES[:compressed] : Bitcoin::Key::TYPES[:uncompressed]
115
- key = Bitcoin::Key.new(pubkey: key, key_type: key_type)
92
+ # Generate raw output script about +hex+.
93
+ # @param [String] addr Bitcoin address.
94
+ # @return [Bitcoin::Descriptor::Addr]
95
+ def addr(addr)
96
+ Addr.new(addr)
97
+ end
98
+
99
+ # Generate taproot output script descriptor.
100
+ # @param [String] key
101
+ # @param [String] tree
102
+ # @return [Bitcoin::Descriptor::Tr]
103
+ def tr(key, tree = nil)
104
+ Tr.new(key, tree)
105
+ end
106
+
107
+ # Generate tapscript multisig output for given keys.
108
+ # @param [Integer] threshold the threshold of multisig.
109
+ # @param [Array[String]] keys an array of keys.
110
+ # @return [Bitcoin::Descriptor::MultiA] multisig script.
111
+ def multi_a(threshold, *keys)
112
+ MultiA.new(threshold, keys)
113
+ end
114
+
115
+ # Generate tapscript sorted multisig output for given keys.
116
+ # @param [Integer] threshold the threshold of multisig.
117
+ # @param [Array[String]] keys an array of keys.
118
+ # @return [Bitcoin::Descriptor::SortedMulti]
119
+ def sortedmulti_a(threshold, *keys)
120
+ SortedMultiA.new(threshold, keys)
121
+ end
122
+
123
+ # Parse descriptor string.
124
+ # @param [String] string Descriptor string.
125
+ # @return [Bitcoin::Descriptor::Expression]
126
+ def parse(string, top_level = true)
127
+ validate_checksum!(string)
128
+ content, _ = string.split('#')
129
+ exp, args_str = content.match(/(\w+)\((.+)\)/).captures
130
+ case exp
131
+ when 'pk'
132
+ pk(args_str)
133
+ when 'pkh'
134
+ pkh(args_str)
135
+ when 'wpkh'
136
+ wpkh(args_str)
137
+ when 'sh'
138
+ sh(parse(args_str, false))
139
+ when 'wsh'
140
+ wsh(parse(args_str, false))
141
+ when 'combo'
142
+ combo(args_str)
143
+ when 'multi', 'sortedmulti', 'multi_a', 'sortedmulti_a'
144
+ args = args_str.split(',')
145
+ threshold = args[0].to_i
146
+ keys = args[1..-1]
147
+ case exp
148
+ when 'multi'
149
+ multi(threshold, *keys)
150
+ when 'sortedmulti'
151
+ sortedmulti(threshold, *keys)
152
+ when 'multi_a'
153
+ raise ArgumentError, "Can only have multi_a/sortedmulti_a inside tr()." if top_level
154
+ multi_a(threshold, *keys)
155
+ when 'sortedmulti_a'
156
+ raise ArgumentError, "Can only have multi_a/sortedmulti_a inside tr()." if top_level
157
+ sortedmulti_a(threshold, *keys)
116
158
  end
159
+ when 'raw'
160
+ raw(args_str)
161
+ when 'addr'
162
+ addr(args_str)
163
+ when 'tr'
164
+ key, rest = args_str.split(',', 2)
165
+ if rest.nil?
166
+ tr(key)
167
+ elsif rest.start_with?('{')
168
+ tr(key, parse_nested_string(rest))
169
+ else
170
+ tr(key, parse(rest, false))
171
+ end
172
+ else
173
+ raise ArgumentError, "Parse failed: #{string}"
117
174
  end
118
- key = key.is_a?(Bitcoin::Key) ? key : key.key
119
- raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless key.fully_valid_pubkey?
120
- key.pubkey
121
175
  end
122
176
 
123
- def compressed_key?(key)
124
- %w(02 03).include?(key[0..1]) && [key].pack("H*").bytesize == 33
177
+ # Validate descriptor checksum.
178
+ # @raise [ArgumentError] If +descriptor+ has invalid checksum.
179
+ def validate_checksum!(descriptor)
180
+ return unless descriptor.include?("#")
181
+ content, *checksums = descriptor.split("#")
182
+ raise ArgumentError, "Multiple '#' symbols." if checksums.length > 1
183
+ checksum = checksums.first
184
+ len = checksum.nil? ? 0 : checksum.length
185
+ raise ArgumentError, "Expected 8 character checksum, not #{len} characters." unless len == 8
186
+ _, calc_checksum = Checksum.descsum_create(content).split('#')
187
+ unless calc_checksum == checksum
188
+ raise ArgumentError, "Provided checksum '#{checksum}' does not match computed checksum '#{calc_checksum}'."
189
+ end
125
190
  end
126
191
 
127
- def derive_path(key, paths, is_private)
128
- paths.each do |path|
129
- raise ArgumentError, 'xpub can not derive hardened key.' if !is_private && path.end_with?("'")
130
- if is_private
131
- hardened = path.end_with?("'")
132
- path = hardened ? path[0..-2] : path
133
- raise ArgumentError, 'Key path value is not a valid value.' unless path =~ /^[0-9]+$/
134
- raise ArgumentError, 'Key path value is out of range.' if !hardened && path.to_i >= Bitcoin::HARDENED_THRESHOLD
135
- key = key.derive(path.to_i, hardened)
192
+ def parse_nested_string(string)
193
+ return nil if string.nil?
194
+ stack = []
195
+ current = []
196
+ buffer = ""
197
+ string.each_char do |c|
198
+ case c
199
+ when '{'
200
+ stack << current
201
+ current = []
202
+ when '}'
203
+ unless buffer.empty?
204
+ current << parse(buffer, false)
205
+ buffer = ""
206
+ end
207
+ nested = current
208
+ current = stack.pop
209
+ current << nested
210
+ when ','
211
+ unless buffer.empty?
212
+ current << parse(buffer, false)
213
+ buffer = ""
214
+ end
136
215
  else
137
- raise ArgumentError, 'Key path value is not a valid value.' unless path =~ /^[0-9]+$/
138
- raise ArgumentError, 'Key path value is out of range.' if path.to_i >= Bitcoin::HARDENED_THRESHOLD
139
- key = key.derive(path.to_i)
216
+ buffer << c
140
217
  end
141
218
  end
142
- key
219
+ current << parse(buffer, false) unless buffer.empty?
220
+ current.first
143
221
  end
144
-
145
222
  end
146
-
147
223
  end
@@ -10,12 +10,6 @@ class ::ECDSA::Signature
10
10
  end
11
11
  end
12
12
 
13
- class ::ECDSA::Point
14
- def to_hex(compression = true)
15
- ECDSA::Format::PointOctetString.encode(self, compression: compression).bth
16
- end
17
- end
18
-
19
13
  module ::ECDSA::Format::PointOctetString
20
14
 
21
15
  class << self
data/lib/bitcoin/key.rb CHANGED
@@ -83,7 +83,7 @@ module Bitcoin
83
83
  # @return [Bitcoin::Key] key object has public key.
84
84
  def self.from_xonly_pubkey(xonly_pubkey)
85
85
  raise ArgumentError, "xonly_pubkey must be #{X_ONLY_PUBKEY_SIZE} bytes" unless xonly_pubkey.htb.bytesize == X_ONLY_PUBKEY_SIZE
86
- Bitcoin::Key.new(pubkey: "02#{xonly_pubkey}", key_type: TYPES[:compressed])
86
+ Bitcoin::Key.new(pubkey: "02#{xonly_pubkey}", key_type: TYPES[:p2tr])
87
87
  end
88
88
 
89
89
  # Generate from public key point.
@@ -146,9 +146,9 @@ module Bitcoin
146
146
  # @return [Bitcoin::Key] Recovered public key.
147
147
  def self.recover_compact(data, signature)
148
148
  rec_id = signature.unpack1('C')
149
- rec = rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE
150
- raise ArgumentError, 'Invalid signature parameter' if rec < 0 || rec > 15
151
- rec = rec & 3
149
+ rec = (rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE) & 3
150
+ raise ArgumentError, 'Invalid signature parameter' if rec < 0 || rec > 3
151
+
152
152
  compressed = (rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE) & 4 != 0
153
153
  Bitcoin.secp_impl.recover_compact(data, signature, rec, compressed)
154
154
  end
@@ -344,6 +344,18 @@ module Bitcoin
344
344
  valid_pubkey? && secp256k1_module.parse_ec_pubkey?(pubkey, allow_hybrid)
345
345
  end
346
346
 
347
+ # Create an ellswift-encoded public key for this key, with specified entropy.
348
+ # @return [Bitcoin::BIP324::EllSwiftPubkey]
349
+ # @raise ArgumentError If ent32 does not 32 bytes.
350
+ def create_ell_pubkey
351
+ raise ArgumentError, "private_key required." unless priv_key
352
+ if secp256k1_module.is_a?(Bitcoin::Secp256k1::Native)
353
+ Bitcoin::BIP324::EllSwiftPubkey.new(secp256k1_module.ellswift_create(priv_key))
354
+ else
355
+ Bitcoin::BIP324::EllSwiftPubkey.new(Bitcoin::BIP324.xelligatorswift(xonly_pubkey))
356
+ end
357
+ end
358
+
347
359
  private
348
360
 
349
361
  def self.compare_big_endian(c1, c2)
@@ -50,18 +50,22 @@ module Bitcoin
50
50
  # @param [String] message The message that was signed.
51
51
  # @return [Boolean] Verification result.
52
52
  def verify_message(address, signature, message, prefix: Bitcoin.chain_params.message_magic)
53
- validate_address!(address)
53
+ addr_script = Bitcoin::Script.parse_from_addr(address)
54
54
  begin
55
55
  sig = Base64.strict_decode64(signature)
56
56
  rescue ArgumentError
57
57
  raise ArgumentError, 'Invalid signature'
58
58
  end
59
- begin
60
- # Legacy verification
61
- pubkey = Bitcoin::Key.recover_compact(message_hash(message, prefix: prefix, legacy: true), sig)
62
- return false unless pubkey
63
- pubkey.to_p2pkh == address
64
- rescue ArgumentError
59
+ if addr_script.p2pkh?
60
+ begin
61
+ # Legacy verification
62
+ pubkey = Bitcoin::Key.recover_compact(message_hash(message, prefix: prefix, legacy: true), sig)
63
+ return false unless pubkey
64
+ pubkey.to_p2pkh == address
65
+ rescue RuntimeError
66
+ return false
67
+ end
68
+ elsif addr_script.witness_program?
65
69
  # BIP322 verification
66
70
  tx = to_sign_tx(message_hash(message, prefix: prefix, legacy: false), address)
67
71
  tx.in[0].script_witness = Bitcoin::ScriptWitness.parse_from_payload(sig)
@@ -69,6 +73,8 @@ module Bitcoin
69
73
  tx_out = Bitcoin::TxOut.new(script_pubkey: script_pubkey)
70
74
  interpreter = Bitcoin::ScriptInterpreter.new(checker: Bitcoin::TxChecker.new(tx: tx, input_index: 0, prevouts: [tx_out]))
71
75
  interpreter.verify_script(Bitcoin::Script.new, script_pubkey, tx.in[0].script_witness)
76
+ else
77
+ raise ArgumentError, "This address unsupported."
72
78
  end
73
79
  end
74
80
 
@@ -82,7 +88,6 @@ module Bitcoin
82
88
  end
83
89
 
84
90
  def validate_address!(address)
85
- raise ArgumentError, 'Invalid address' unless Bitcoin.valid_address?(address)
86
91
  script = Bitcoin::Script.parse_from_addr(address)
87
92
  raise ArgumentError, 'This address unsupported' if script.p2sh? || script.p2wsh?
88
93
  end
@@ -55,7 +55,9 @@ module Bitcoin
55
55
 
56
56
  # generate p2sh script with this as a redeem script
57
57
  # @return [Script] P2SH script
58
+ # @raise [RuntimeError] If the script size exceeds 520 bytes
58
59
  def to_p2sh
60
+ raise RuntimeError, "P2SH redeem script must be 520 bytes or less." if size > Bitcoin::MAX_SCRIPT_ELEMENT_SIZE
59
61
  Script.to_p2sh(to_hash160)
60
62
  end
61
63
 
@@ -74,9 +76,11 @@ module Bitcoin
74
76
  end
75
77
 
76
78
  # generate p2wsh script for +redeem_script+
77
- # @param [Script] redeem_script target redeem script
78
- # @param [Script] p2wsh script
79
+ # @param [Bitcoin::Script] redeem_script target redeem script
80
+ # @return [Bitcoin::Script] p2wsh script
81
+ # @raise [ArgumentError] If the script size exceeds 10,000 bytes
79
82
  def self.to_p2wsh(redeem_script)
83
+ raise ArgumentError, 'P2WSH witness script must be 10,000 bytes or less.' if redeem_script.size > Bitcoin::MAX_SCRIPT_SIZE
80
84
  new << WITNESS_VERSION_V0 << redeem_script.to_sha256
81
85
  end
82
86
 
@@ -146,7 +150,7 @@ module Bitcoin
146
150
  end
147
151
  if buf.eof?
148
152
  s.chunks << [len].pack('C')
149
- else buf.eof?
153
+ else
150
154
  chunk = (packed_size ? (opcode + packed_size) : (opcode)) + buf.read(len)
151
155
  s.chunks << chunk
152
156
  end
@@ -555,6 +559,7 @@ module Bitcoin
555
559
  return 'multisig' if multisig?
556
560
  return 'witness_v0_keyhash' if p2wpkh?
557
561
  return 'witness_v0_scripthash' if p2wsh?
562
+ return 'witness_v1_taproot' if p2tr?
558
563
  'nonstandard'
559
564
  end
560
565
 
@@ -5,9 +5,9 @@ module Bitcoin
5
5
  module Secp256k1
6
6
 
7
7
  # binding for secp256k1 (https://github.com/bitcoin-core/secp256k1/)
8
- # commit: efad3506a8937162e8010f5839fdf3771dfcf516
8
+ # tag: v0.4.0
9
9
  # this is not included by default, to enable set shared object path to ENV['SECP256K1_LIB_PATH']
10
- # for linux, ENV['SECP256K1_LIB_PATH'] = '/usr/local/lib/libsecp256k1.so'
10
+ # for linux, ENV['SECP256K1_LIB_PATH'] = '/usr/local/lib/libsecp256k1.so' or '/usr/lib64/libsecp256k1.so'
11
11
  # for mac,
12
12
  module Native
13
13
  include ::FFI::Library
@@ -50,14 +50,20 @@ module Bitcoin
50
50
  attach_function(:secp256k1_ecdsa_signature_parse_der, [:pointer, :pointer, :pointer, :size_t], :int)
51
51
  attach_function(:secp256k1_ecdsa_signature_normalize, [:pointer, :pointer, :pointer], :int)
52
52
  attach_function(:secp256k1_ecdsa_verify, [:pointer, :pointer, :pointer, :pointer], :int)
53
- attach_function(:secp256k1_schnorrsig_sign, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
54
- attach_function(:secp256k1_schnorrsig_verify, [:pointer, :pointer, :pointer, :pointer], :int)
53
+ attach_function(:secp256k1_schnorrsig_sign32, [:pointer, :pointer, :pointer, :pointer, :pointer], :int)
54
+ attach_function(:secp256k1_schnorrsig_verify, [:pointer, :pointer, :pointer, :size_t, :pointer], :int)
55
55
  attach_function(:secp256k1_keypair_create, [:pointer, :pointer, :pointer], :int)
56
56
  attach_function(:secp256k1_xonly_pubkey_parse, [:pointer, :pointer, :pointer], :int)
57
57
  attach_function(:secp256k1_ecdsa_sign_recoverable, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
58
58
  attach_function(:secp256k1_ecdsa_recoverable_signature_serialize_compact, [:pointer, :pointer, :pointer, :pointer], :int)
59
59
  attach_function(:secp256k1_ecdsa_recover, [:pointer, :pointer, :pointer, :pointer], :int)
60
60
  attach_function(:secp256k1_ecdsa_recoverable_signature_parse_compact, [:pointer, :pointer, :pointer, :int], :int)
61
+ attach_function(:secp256k1_ellswift_decode, [:pointer, :pointer, :pointer], :int)
62
+ attach_function(:secp256k1_ellswift_create, [:pointer, :pointer, :pointer, :pointer], :int)
63
+ # Define function pointer
64
+ callback(:secp256k1_ellswift_xdh_hash_function, [:pointer, :pointer, :pointer, :pointer, :pointer], :int)
65
+ attach_variable(:secp256k1_ellswift_xdh_hash_function_bip324, :secp256k1_ellswift_xdh_hash_function)
66
+ attach_function(:secp256k1_ellswift_xdh, [:pointer, :pointer, :pointer, :pointer, :pointer, :int, :pointer, :pointer], :int)
61
67
  end
62
68
 
63
69
  def with_context(flags: (SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN))
@@ -224,6 +230,56 @@ module Bitcoin
224
230
  true
225
231
  end
226
232
 
233
+ # Decode ellswift public key.
234
+ # @param [String] ell_key ElligatorSwift key with binary format.
235
+ # @return [String] Decoded public key with hex format
236
+ def ellswift_decode(ell_key)
237
+ with_context do |context|
238
+ ell64 = FFI::MemoryPointer.new(:uchar, ell_key.bytesize).put_bytes(0, ell_key)
239
+ internal = FFI::MemoryPointer.new(:uchar, 64)
240
+ result = secp256k1_ellswift_decode(context, internal, ell64)
241
+ raise ArgumentError, 'Decode failed.' unless result == 1
242
+ serialize_pubkey_internal(context, internal, true)
243
+ end
244
+ end
245
+
246
+ # Compute an ElligatorSwift public key for a secret key.
247
+ # @param [String] priv_key private key with hex format
248
+ # @return [String] ElligatorSwift public key with hex format.
249
+ def ellswift_create(priv_key)
250
+ with_context(flags: SECP256K1_CONTEXT_SIGN) do |context|
251
+ ell64 = FFI::MemoryPointer.new(:uchar, 64)
252
+ seckey32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, priv_key.htb)
253
+ result = secp256k1_ellswift_create(context, ell64, seckey32, nil)
254
+ raise ArgumentError, 'Failed to create ElligatorSwift public key.' unless result == 1
255
+ ell64.read_string(64).bth
256
+ end
257
+ end
258
+
259
+ # Compute X coordinate of shared ECDH point between elswift pubkey and privkey.
260
+ # @param [Bitcoin::BIP324::EllSwiftPubkey] their_ell_pubkey Their EllSwift public key.
261
+ # @param [Bitcoin::BIP324::EllSwiftPubkey] our_ell_pubkey Our EllSwift public key.
262
+ # @param [String] priv_key private key with hex format.
263
+ # @param [Boolean] initiating Whether your initiator or not.
264
+ # @return [String] x coordinate with hex format.
265
+ def ellswift_ecdh_xonly(their_ell_pubkey, our_ell_pubkey, priv_key, initiating)
266
+ with_context(flags: SECP256K1_CONTEXT_SIGN) do |context|
267
+ output = FFI::MemoryPointer.new(:uchar, 32)
268
+ our_ell_ptr = FFI::MemoryPointer.new(:uchar, 64).put_bytes(0, our_ell_pubkey.key)
269
+ their_ell_ptr = FFI::MemoryPointer.new(:uchar, 64).put_bytes(0, their_ell_pubkey.key)
270
+ seckey32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, priv_key.htb)
271
+ hashfp = secp256k1_ellswift_xdh_hash_function_bip324
272
+ result = secp256k1_ellswift_xdh(context, output,
273
+ initiating ? our_ell_ptr : their_ell_ptr,
274
+ initiating ? their_ell_ptr : our_ell_ptr,
275
+ seckey32,
276
+ initiating ? 0 : 1,
277
+ hashfp, nil)
278
+ raise ArgumentError, "secret was invalid or hashfp returned 0" unless result == 1
279
+ output.read_string(32).bth
280
+ end
281
+ end
282
+
227
283
  private
228
284
 
229
285
  # Calculate full public key(64 bytes) from public key(32 bytes).
@@ -280,7 +336,7 @@ module Bitcoin
280
336
  signature = FFI::MemoryPointer.new(:uchar, 64)
281
337
  msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
282
338
  aux_rand = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, aux_rand) if aux_rand
283
- raise 'Failed to generate schnorr signature.' unless secp256k1_schnorrsig_sign(context, signature, msg32, keypair, nil, aux_rand) == 1
339
+ raise 'Failed to generate schnorr signature.' unless secp256k1_schnorrsig_sign32(context, signature, msg32, keypair, aux_rand) == 1
284
340
  signature.read_string(64)
285
341
  end
286
342
  end
@@ -316,7 +372,7 @@ module Bitcoin
316
372
  xonly_pubkey = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
317
373
  signature = FFI::MemoryPointer.new(:uchar, sig.bytesize).put_bytes(0, sig)
318
374
  msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
319
- result = secp256k1_schnorrsig_verify(context, signature, msg32, xonly_pubkey)
375
+ result = secp256k1_schnorrsig_verify(context, signature, msg32, 32, xonly_pubkey)
320
376
  result == 1
321
377
  end
322
378
  end
@@ -77,13 +77,30 @@ module Bitcoin
77
77
  # @param [Boolean] compressed whether compressed public key or not.
78
78
  # @return [Bitcoin::Key] Recovered public key.
79
79
  def recover_compact(data, signature, rec, compressed)
80
+ group = Bitcoin::Secp256k1::GROUP
80
81
  r = ECDSA::Format::IntegerOctetString.decode(signature[1...33])
81
82
  s = ECDSA::Format::IntegerOctetString.decode(signature[33..-1])
82
- ECDSA.recover_public_key(Bitcoin::Secp256k1::GROUP, data, ECDSA::Signature.new(r, s)).each do |p|
83
- if p.y & 1 == rec
84
- return Bitcoin::Key.from_point(p, compressed: compressed)
85
- end
83
+ return nil if r.zero?
84
+ return nil if s.zero?
85
+
86
+ digest = ECDSA.normalize_digest(data, group.bit_length)
87
+ field = ECDSA::PrimeField.new(group.order)
88
+
89
+ unless rec & 2 == 0
90
+ r = field.mod(r + group.order)
86
91
  end
92
+
93
+ is_odd = (rec & 1 == 1)
94
+ y_coordinate = group.solve_for_y(r).find{|y| is_odd ? y.odd? : y.even?}
95
+
96
+ p = group.new_point([r, y_coordinate])
97
+
98
+ inv_r = field.inverse(r)
99
+ u1 = field.mod(inv_r * digest)
100
+ u2 = field.mod(inv_r * s)
101
+ q = p * u2 + (group.new_point(u1)).negate
102
+ return nil if q.infinity?
103
+ Bitcoin::Key.from_point(q, compressed: compressed)
87
104
  end
88
105
 
89
106
  # verify signature using public key