bitcoinrb 1.5.0 → 1.7.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +1 -1
  3. data/README.md +7 -5
  4. data/lib/bitcoin/block.rb +27 -0
  5. data/lib/bitcoin/descriptor/addr.rb +31 -0
  6. data/lib/bitcoin/descriptor/checksum.rb +74 -0
  7. data/lib/bitcoin/descriptor/combo.rb +30 -0
  8. data/lib/bitcoin/descriptor/expression.rb +122 -0
  9. data/lib/bitcoin/descriptor/key_expression.rb +30 -0
  10. data/lib/bitcoin/descriptor/multi.rb +49 -0
  11. data/lib/bitcoin/descriptor/multi_a.rb +43 -0
  12. data/lib/bitcoin/descriptor/pk.rb +27 -0
  13. data/lib/bitcoin/descriptor/pkh.rb +15 -0
  14. data/lib/bitcoin/descriptor/raw.rb +32 -0
  15. data/lib/bitcoin/descriptor/raw_tr.rb +20 -0
  16. data/lib/bitcoin/descriptor/script_expression.rb +24 -0
  17. data/lib/bitcoin/descriptor/sh.rb +31 -0
  18. data/lib/bitcoin/descriptor/sorted_multi.rb +15 -0
  19. data/lib/bitcoin/descriptor/sorted_multi_a.rb +15 -0
  20. data/lib/bitcoin/descriptor/tr.rb +91 -0
  21. data/lib/bitcoin/descriptor/wpkh.rb +19 -0
  22. data/lib/bitcoin/descriptor/wsh.rb +30 -0
  23. data/lib/bitcoin/descriptor.rb +186 -100
  24. data/lib/bitcoin/key.rb +1 -1
  25. data/lib/bitcoin/message_sign.rb +2 -1
  26. data/lib/bitcoin/script/script.rb +8 -3
  27. data/lib/bitcoin/script_witness.rb +1 -0
  28. data/lib/bitcoin/secp256k1/native.rb +1 -1
  29. data/lib/bitcoin/silent_payment.rb +5 -0
  30. data/lib/bitcoin/sp/addr.rb +55 -0
  31. data/lib/bitcoin/taproot/custom_depth_builder.rb +64 -0
  32. data/lib/bitcoin/taproot/simple_builder.rb +1 -6
  33. data/lib/bitcoin/taproot.rb +1 -0
  34. data/lib/bitcoin/tx.rb +14 -1
  35. data/lib/bitcoin/version.rb +1 -1
  36. data/lib/bitcoin.rb +1 -0
  37. metadata +23 -2
@@ -0,0 +1,91 @@
1
+ module Bitcoin
2
+ module Descriptor
3
+ # tr() expression.
4
+ # @see https://github.com/bitcoin/bips/blob/master/bip-0386.mediawiki
5
+ class Tr < Expression
6
+
7
+ attr_reader :key
8
+ attr_reader :tree
9
+
10
+ # Constructor.
11
+ def initialize(key, tree = nil)
12
+ raise ArgumentError, "Key must be string." unless key.is_a?(String)
13
+ k = extract_pubkey(key)
14
+ raise ArgumentError, "Uncompressed key are not allowed." unless k.compressed?
15
+ validate_tree!(tree)
16
+ @key = key
17
+ @tree = tree
18
+ end
19
+
20
+ def type
21
+ :tr
22
+ end
23
+
24
+ def top_level?
25
+ true
26
+ end
27
+
28
+ def args
29
+ if tree.nil?
30
+ key
31
+ else
32
+ tree.is_a?(Array) ? "#{key},#{tree_string(tree)}" : "#{key},#{tree}"
33
+ end
34
+ end
35
+
36
+ def to_script
37
+ builder = build_tree_scripts
38
+ builder.build
39
+ end
40
+
41
+ private
42
+
43
+ def build_tree_scripts
44
+ internal_key = extract_pubkey(key)
45
+ return Bitcoin::Taproot::SimpleBuilder.new(internal_key.xonly_pubkey) if tree.nil?
46
+ if tree.is_a?(Expression)
47
+ tree.xonly = true if tree.respond_to?(:xonly)
48
+ Bitcoin::Taproot::SimpleBuilder.new(internal_key.xonly_pubkey, [Bitcoin::Taproot::LeafNode.new(tree.to_script)])
49
+ elsif tree.is_a?(Array)
50
+ Bitcoin::Taproot::CustomDepthBuilder.new(internal_key.xonly_pubkey, parse_tree_items(tree))
51
+ end
52
+ end
53
+
54
+ def parse_tree_items(arry)
55
+ items = []
56
+ arry.each do |item|
57
+ if item.is_a?(Array)
58
+ items << parse_tree_items(item)
59
+ elsif item.is_a?(Expression)
60
+ item.xonly = true
61
+ items << Bitcoin::Taproot::LeafNode.new(item.to_script)
62
+ else
63
+ raise RuntimeError, "Unsupported item #{item}"
64
+ end
65
+ end
66
+ items
67
+ end
68
+
69
+ def validate_tree!(tree)
70
+ return if tree.nil? || tree.is_a?(Expression)
71
+ if tree.is_a?(Array)
72
+ tree.each do |item|
73
+ validate_tree!(item)
74
+ end
75
+ else
76
+ raise ArgumentError, "tree must be a expression or array of expression."
77
+ end
78
+ end
79
+
80
+ def tree_string(tree)
81
+ buffer = '{'
82
+ left, right = tree
83
+ buffer << (left.is_a?(Array) ? tree_string(left) : left.to_s)
84
+ buffer << ","
85
+ buffer << (right.is_a?(Array) ? tree_string(right) : right.to_s)
86
+ buffer << '}'
87
+ buffer
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,19 @@
1
+ module Bitcoin
2
+ module Descriptor
3
+ # wpkh() expression
4
+ class Wpkh < KeyExpression
5
+ def initialize(key)
6
+ super(key)
7
+ raise ArgumentError, "Uncompressed key are not allowed." unless extract_pubkey(key).compressed?
8
+ end
9
+
10
+ def type
11
+ :wpkh
12
+ end
13
+
14
+ def to_script
15
+ Script.to_p2wpkh(extracted_key.hash160)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ module Bitcoin
2
+ module Descriptor
3
+ # wsh() expression
4
+ class Wsh < ScriptExpression
5
+
6
+ def type
7
+ :wsh
8
+ end
9
+
10
+ def to_script
11
+ Script.to_p2wsh(script.to_script)
12
+ end
13
+
14
+ def top_level?
15
+ false
16
+ end
17
+
18
+ def validate!(script)
19
+ super(script)
20
+ raise ArgumentError, 'A function is needed within P2WSH.' unless script.is_a?(Expression)
21
+ if script.is_a?(Wpkh) || script.is_a?(Wsh)
22
+ raise ArgumentError, "Can only have #{script.type}() at top level or inside sh()."
23
+ end
24
+ if script.to_script.get_pubkeys.any?{|p|!compressed_key?(p)}
25
+ raise ArgumentError, "Uncompressed key are not allowed."
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -3,145 +3,231 @@ 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 :RawTr, 'bitcoin/descriptor/raw_tr'
23
+ autoload :Checksum, 'bitcoin/descriptor/checksum'
24
+
25
+ module_function
26
+
27
+ # Generate P2PK output for the given public key.
8
28
  # @param [String] key private key or public key with hex format
9
- # @return [Bitcoin::Script] P2PK script.
29
+ # @return [Bitcoin::Descriptor::Pk]
10
30
  def pk(key)
11
- Bitcoin::Script.new << extract_pubkey(key) << OP_CHECKSIG
31
+ Pk.new(key)
12
32
  end
13
33
 
14
- # generate P2PKH output for the given public key.
34
+ # Generate P2PKH output for the given public key.
15
35
  # @param [String] key private key or public key with hex format.
16
- # @return [Bitcoin::Script] P2PKH script.
36
+ # @return [Bitcoin::Descriptor::Pkh]
17
37
  def pkh(key)
18
- Bitcoin::Script.to_p2pkh(Bitcoin.hash160(extract_pubkey(key)))
38
+ Pkh.new(key)
19
39
  end
20
40
 
21
- # generate P2PKH output for the given public key.
41
+ # Generate P2PKH output for the given public key.
22
42
  # @param [String] key private key or public key with hex format.
23
- # @return [Bitcoin::Script] P2WPKH script.
43
+ # @return [Bitcoin::Descriptor::Wpkh]
24
44
  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))
45
+ Wpkh.new(key)
28
46
  end
29
47
 
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))
48
+ # Generate P2SH embed the argument.
49
+ # @param [Bitcoin::Descriptor::Base] exp script expression to be embed.
50
+ # @return [Bitcoin::Descriptor::Sh]
51
+ def sh(exp)
52
+ Sh.new(exp)
37
53
  end
38
54
 
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)
55
+ # Generate P2WSH embed the argument.
56
+ # @param [Bitcoin::Descriptor::Expression] exp script expression to be embed.
57
+ # @return [Bitcoin::Descriptor::Wsh]
58
+ def wsh(exp)
59
+ Wsh.new(exp)
47
60
  end
48
61
 
49
- # an alias for the collection of `pk(KEY)` and `pkh(KEY)`.
62
+ # An alias for the collection of `pk(KEY)` and `pkh(KEY)`.
50
63
  # If the key is compressed, it also includes `wpkh(KEY)` and `sh(wpkh(KEY))`.
51
64
  # @param [String] key private key or public key with hex format.
52
- # @return [Array[Bitcoin::Script]]
65
+ # @return [Bitcoin::Descriptor::Combo]
53
66
  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
67
+ Combo.new(key)
61
68
  end
62
69
 
63
- # generate multisig output for given keys.
70
+ # Generate multisig output for given keys.
64
71
  # @param [Integer] threshold the threshold of multisig.
65
72
  # @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)
73
+ # @return [Bitcoin::Descriptor::Multi] multisig script.
74
+ def multi(threshold, *keys)
75
+ Multi.new(threshold, keys)
74
76
  end
75
77
 
76
- # generate sorted multisig output for given keys.
78
+ # Generate sorted multisig output for given keys.
77
79
  # @param [Integer] threshold the threshold of multisig.
78
80
  # @param [Array[String]] keys an array of keys.
79
- # @return [Bitcoin::Script] multisig script.
81
+ # @return [Bitcoin::Descriptor::SortedMulti]
80
82
  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
83
+ SortedMulti.new(threshold, keys)
84
+ end
100
85
 
101
- # check BIP32 derivation path
102
- key, *paths = key.split('/')
86
+ # Generate raw output script about +hex+.
87
+ # @param [String] hex Hex string of bitcoin script.
88
+ # @return [Bitcoin::Descriptor::Raw]
89
+ def raw(hex)
90
+ Raw.new(hex)
91
+ end
103
92
 
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)
93
+ # Generate raw output script about +hex+.
94
+ # @param [String] addr Bitcoin address.
95
+ # @return [Bitcoin::Descriptor::Addr]
96
+ def addr(addr)
97
+ Addr.new(addr)
98
+ end
99
+
100
+ # Generate taproot output script descriptor.
101
+ # @param [String] key
102
+ # @param [String] tree
103
+ # @return [Bitcoin::Descriptor::Tr]
104
+ def tr(key, tree = nil)
105
+ Tr.new(key, tree)
106
+ end
107
+
108
+ # Generate taproot output script descriptor.
109
+ # @param [String] key
110
+ # @return [Bitcoin::Descriptor::RawTr]
111
+ def rawtr(key)
112
+ RawTr.new(key)
113
+ end
114
+
115
+ # Generate tapscript 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::MultiA] multisig script.
119
+ def multi_a(threshold, *keys)
120
+ MultiA.new(threshold, keys)
121
+ end
122
+
123
+ # Generate tapscript sorted multisig output for given keys.
124
+ # @param [Integer] threshold the threshold of multisig.
125
+ # @param [Array[String]] keys an array of keys.
126
+ # @return [Bitcoin::Descriptor::SortedMulti]
127
+ def sortedmulti_a(threshold, *keys)
128
+ SortedMultiA.new(threshold, keys)
129
+ end
130
+
131
+ # Parse descriptor string.
132
+ # @param [String] string Descriptor string.
133
+ # @return [Bitcoin::Descriptor::Expression]
134
+ def parse(string, top_level = true)
135
+ validate_checksum!(string)
136
+ content, _ = string.split('#')
137
+ exp, args_str = content.match(/(\w+)\((.+)\)/).captures
138
+ case exp
139
+ when 'pk'
140
+ pk(args_str)
141
+ when 'pkh'
142
+ pkh(args_str)
143
+ when 'wpkh'
144
+ wpkh(args_str)
145
+ when 'sh'
146
+ sh(parse(args_str, false))
147
+ when 'wsh'
148
+ wsh(parse(args_str, false))
149
+ when 'combo'
150
+ combo(args_str)
151
+ when 'multi', 'sortedmulti', 'multi_a', 'sortedmulti_a'
152
+ args = args_str.split(',')
153
+ threshold = args[0].to_i
154
+ keys = args[1..-1]
155
+ case exp
156
+ when 'multi'
157
+ multi(threshold, *keys)
158
+ when 'sortedmulti'
159
+ sortedmulti(threshold, *keys)
160
+ when 'multi_a'
161
+ raise ArgumentError, "Can only have multi_a/sortedmulti_a inside tr()." if top_level
162
+ multi_a(threshold, *keys)
163
+ when 'sortedmulti_a'
164
+ raise ArgumentError, "Can only have multi_a/sortedmulti_a inside tr()." if top_level
165
+ sortedmulti_a(threshold, *keys)
116
166
  end
167
+ when 'raw'
168
+ raw(args_str)
169
+ when 'addr'
170
+ addr(args_str)
171
+ when 'tr'
172
+ key, rest = args_str.split(',', 2)
173
+ if rest.nil?
174
+ tr(key)
175
+ elsif rest.start_with?('{')
176
+ tr(key, parse_nested_string(rest))
177
+ else
178
+ tr(key, parse(rest, false))
179
+ end
180
+ when 'rawtr'
181
+ rawtr(args_str)
182
+ else
183
+ raise ArgumentError, "Parse failed: #{string}"
117
184
  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
185
  end
122
186
 
123
- def compressed_key?(key)
124
- %w(02 03).include?(key[0..1]) && [key].pack("H*").bytesize == 33
187
+ # Validate descriptor checksum.
188
+ # @raise [ArgumentError] If +descriptor+ has invalid checksum.
189
+ def validate_checksum!(descriptor)
190
+ return unless descriptor.include?("#")
191
+ content, *checksums = descriptor.split("#")
192
+ raise ArgumentError, "Multiple '#' symbols." if checksums.length > 1
193
+ checksum = checksums.first
194
+ len = checksum.nil? ? 0 : checksum.length
195
+ raise ArgumentError, "Expected 8 character checksum, not #{len} characters." unless len == 8
196
+ _, calc_checksum = Checksum.descsum_create(content).split('#')
197
+ unless calc_checksum == checksum
198
+ raise ArgumentError, "Provided checksum '#{checksum}' does not match computed checksum '#{calc_checksum}'."
199
+ end
125
200
  end
126
201
 
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)
202
+ def parse_nested_string(string)
203
+ return nil if string.nil?
204
+ stack = []
205
+ current = []
206
+ buffer = ""
207
+ string.each_char do |c|
208
+ case c
209
+ when '{'
210
+ stack << current
211
+ current = []
212
+ when '}'
213
+ unless buffer.empty?
214
+ current << parse(buffer, false)
215
+ buffer = ""
216
+ end
217
+ nested = current
218
+ current = stack.pop
219
+ current << nested
220
+ when ','
221
+ unless buffer.empty?
222
+ current << parse(buffer, false)
223
+ buffer = ""
224
+ end
136
225
  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)
226
+ buffer << c
140
227
  end
141
228
  end
142
- key
229
+ current << parse(buffer, false) unless buffer.empty?
230
+ current.first
143
231
  end
144
-
145
232
  end
146
-
147
233
  end
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.
@@ -71,7 +71,8 @@ module Bitcoin
71
71
  tx.in[0].script_witness = Bitcoin::ScriptWitness.parse_from_payload(sig)
72
72
  script_pubkey = Bitcoin::Script.parse_from_addr(address)
73
73
  tx_out = Bitcoin::TxOut.new(script_pubkey: script_pubkey)
74
- interpreter = Bitcoin::ScriptInterpreter.new(checker: Bitcoin::TxChecker.new(tx: tx, input_index: 0, prevouts: [tx_out]))
74
+ flags = Bitcoin::STANDARD_SCRIPT_VERIFY_FLAGS
75
+ interpreter = Bitcoin::ScriptInterpreter.new(flags: flags, checker: Bitcoin::TxChecker.new(tx: tx, input_index: 0, prevouts: [tx_out]))
75
76
  interpreter.verify_script(Bitcoin::Script.new, script_pubkey, tx.in[0].script_witness)
76
77
  else
77
78
  raise ArgumentError, "This address unsupported."
@@ -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
 
@@ -11,6 +11,7 @@ module Bitcoin
11
11
 
12
12
  def self.parse_from_payload(payload)
13
13
  buf = payload.is_a?(StringIO) ? payload : StringIO.new(payload)
14
+ return self.new if buf.eof?
14
15
  size = Bitcoin.unpack_var_int_from_io(buf)
15
16
  stack = size.times.map do
16
17
  buf.read(Bitcoin.unpack_var_int_from_io(buf))
@@ -7,7 +7,7 @@ module Bitcoin
7
7
  # binding for secp256k1 (https://github.com/bitcoin-core/secp256k1/)
8
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
@@ -0,0 +1,5 @@
1
+ module Bitcoin
2
+ module SilentPayment
3
+ autoload :Addr, 'bitcoin/sp/addr'
4
+ end
5
+ end
@@ -0,0 +1,55 @@
1
+ module Bitcoin
2
+ module SilentPayment
3
+ class Addr
4
+
5
+ HRP_MAINNET = 'sp'
6
+ HRP_TESTNET = 'tsp'
7
+ MAX_CHARACTERS = 1023
8
+
9
+ attr_reader :version
10
+ attr_reader :scan_key
11
+ attr_reader :spend_key
12
+
13
+ # Constructor.
14
+ # @param [Bitcoin::Key] scan_key
15
+ # @param [Bitcoin::Key] spend_key
16
+ def initialize(version, scan_key:, spend_key:)
17
+ raise ArgumentError, "version must be integer." unless version.is_a?(Integer)
18
+ raise ArgumentError, "scan_key must be Bitcoin::Key." unless scan_key.is_a?(Bitcoin::Key)
19
+ raise ArgumentError, "spend_key must be Bitcoin::Key." unless spend_key.is_a?(Bitcoin::Key)
20
+ raise ArgumentError, "version '#{version}' is unsupported." unless version.zero?
21
+
22
+ @version = version
23
+ @scan_key = scan_key
24
+ @spend_key = spend_key
25
+ end
26
+
27
+ # Parse silent payment address.
28
+ # @param [String] A silent payment address.
29
+ # @return [Bitcoin::SilentPayment::Addr]
30
+ def self.from_string(addr)
31
+ raise ArgumentError, "addr must be string." unless addr.is_a?(String)
32
+ hrp, data, spec = Bech32.decode(addr, MAX_CHARACTERS)
33
+ unless hrp == Bitcoin.chain_params.mainnet? ? HRP_MAINNET : HRP_TESTNET
34
+ raise ArgumentError, "The specified hrp is different from the current network HRP."
35
+ end
36
+ raise ArgumentError, "spec must be bech32m." unless spec == Bech32::Encoding::BECH32M
37
+
38
+ ver = data[0]
39
+ payload = Bech32.convert_bits(data[1..-1], 5, 8, false).pack("C*")
40
+ scan_key = Bitcoin::Key.new(pubkey: payload[0...33].bth, key_type: Bitcoin::Key::TYPES[:compressed])
41
+ spend_key = Bitcoin::Key.new(pubkey: payload[33..-1].bth, key_type: Bitcoin::Key::TYPES[:compressed])
42
+ Addr.new(ver, scan_key: scan_key, spend_key: spend_key)
43
+ end
44
+
45
+ # Get silent payment address.
46
+ # @return [String]
47
+ def to_s
48
+ hrp = Bitcoin.chain_params.mainnet? ? HRP_MAINNET : HRP_TESTNET
49
+ payload = [scan_key.pubkey + spend_key.pubkey].pack("H*").unpack('C*')
50
+ Bech32.encode(hrp, [version] + Bech32.convert_bits(payload, 8, 5), Bech32::Encoding::BECH32M)
51
+ end
52
+
53
+ end
54
+ end
55
+ end