bitcoinrb 1.4.0 → 1.6.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 (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
@@ -0,0 +1,31 @@
1
+ module Bitcoin
2
+ module Descriptor
3
+ class Addr < Expression
4
+ include Bitcoin::Util
5
+
6
+ attr_reader :addr
7
+
8
+ def initialize(addr)
9
+ raise ArgumentError, "Address must be string." unless addr.is_a?(String)
10
+ raise ArgumentError, "Address is not valid." unless valid_address?(addr)
11
+ @addr = addr
12
+ end
13
+
14
+ def type
15
+ :addr
16
+ end
17
+
18
+ def to_script
19
+ Bitcoin::Script.parse_from_addr(addr)
20
+ end
21
+
22
+ def top_level?
23
+ true
24
+ end
25
+
26
+ def args
27
+ addr
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,74 @@
1
+ module Bitcoin
2
+ module Descriptor
3
+ # Descriptor checksum.
4
+ # https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki#checksum
5
+ module Checksum
6
+
7
+ INPUT_CHARSET = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ "
8
+ CHECKSUM_CHARSET = Bech32::CHARSET
9
+ GENERATOR = [0xF5DEE51989, 0xA9FDCA3312, 0x1BAB10E32D, 0x3706B1677A, 0x644D626FFD]
10
+
11
+ module_function
12
+
13
+ # Verify that the checksum is correct in a descriptor
14
+ # @param [String] s Descriptor string.
15
+ # @return [Boolean]
16
+ def descsum_check(s)
17
+ return false unless s[-9] == '#'
18
+ s[-8..-1].each_char do |c|
19
+ return false unless CHECKSUM_CHARSET.include?(c)
20
+ end
21
+ symbols = descsum_expand(s[0...-9]) + s[-8..-1].each_char.map{|c|CHECKSUM_CHARSET.index(c)}
22
+ descsum_polymod(symbols) == 1
23
+ end
24
+
25
+ # Add a checksum to a descriptor without
26
+ # @param [String] s Descriptor string without checksum.
27
+ # @return [String] Descriptor string with checksum.
28
+ def descsum_create(s)
29
+ symbols = descsum_expand(s) + [0, 0, 0, 0, 0, 0, 0, 0]
30
+ checksum = descsum_polymod(symbols) ^ 1
31
+ result = 8.times.map do |i|
32
+ CHECKSUM_CHARSET[(checksum >> (5 * (7 - i))) & 31]
33
+ end.join
34
+ "#{s}##{result}"
35
+ end
36
+
37
+ # Internal function that does the character to symbol expansion.
38
+ # @param [String] s Descriptor string without checksum.
39
+ # @return [Array] symbols. An array of integer.
40
+ def descsum_expand(s)
41
+ groups = []
42
+ symbols = []
43
+ s.each_char do |c|
44
+ return nil unless INPUT_CHARSET.include?(c)
45
+ v = INPUT_CHARSET.index(c)
46
+ symbols << (v & 31)
47
+ groups << (v >> 5)
48
+ if groups.length == 3
49
+ symbols << (groups[0] * 9 + groups[1] * 3 + groups[2])
50
+ groups = []
51
+ end
52
+ end
53
+ symbols << groups[0] if groups.length == 1
54
+ symbols << (groups[0] * 3 + groups[1]) if groups.length == 2
55
+ symbols
56
+ end
57
+
58
+ # Internal function that computes the descriptor checksum.
59
+ # @param [Array] symbols
60
+ # @return [Integer]
61
+ def descsum_polymod(symbols)
62
+ chk = 1
63
+ symbols.each do |value|
64
+ top = chk >> 35
65
+ chk = (chk & 0x7FFFFFFFF) << 5 ^ value
66
+ 5.times do |i|
67
+ chk ^= GENERATOR[i] if ((top >> i) & 1) == 1
68
+ end
69
+ end
70
+ chk
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,30 @@
1
+ module Bitcoin
2
+ module Descriptor
3
+ # combo() expression
4
+ class Combo < KeyExpression
5
+
6
+ def type
7
+ :combo
8
+ end
9
+
10
+ def to_scripts
11
+ candidates = [Pk.new(key), Pkh.new(key)]
12
+ pubkey = extract_pubkey(key)
13
+ if pubkey.compressed?
14
+ candidates << Wpkh.new(pubkey.pubkey)
15
+ candidates << Sh.new(candidates.last)
16
+ end
17
+ candidates.map(&:to_script)
18
+ end
19
+
20
+ def ==(other)
21
+ return false unless other.is_a?(Combo)
22
+ type == other.type && to_scripts == other.to_scripts
23
+ end
24
+
25
+ def top_level?
26
+ true
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,122 @@
1
+ module Bitcoin
2
+ module Descriptor
3
+ # Expression for descriptor.
4
+ class Expression
5
+
6
+ # Get expression type.
7
+ # @return [Symbol]
8
+ def type
9
+ raise NotImplementedError
10
+ end
11
+
12
+ # Convert to bitcoin script
13
+ # @return [Bitcoin::Script]
14
+ def to_script
15
+ raise NotImplementedError
16
+ end
17
+
18
+ # Whether this is top level or not.
19
+ # @return [Boolean]
20
+ def top_level?
21
+ raise NotImplementedError
22
+ end
23
+
24
+ # Get args for this expression.
25
+ # @return [String] args
26
+ def args
27
+ raise NotImplementedError
28
+ end
29
+
30
+ # Get descriptor string.
31
+ # @param [Boolean] checksum If true, append checksum.
32
+ # @return [String] Descriptor string.
33
+ def to_s(checksum: false)
34
+ desc = "#{type.to_s}(#{args})"
35
+ checksum ? Checksum.descsum_create(desc) : desc
36
+ end
37
+
38
+ # Convert to bitcoin script as hex string.
39
+ # @return [String]
40
+ def to_hex
41
+ to_script.to_hex
42
+ end
43
+
44
+ # Check whether +key+ is compressed public key or not.
45
+ # @return [Boolean]
46
+ def compressed_key?(key)
47
+ %w(02 03).include?(key[0..1]) && [key].pack("H*").bytesize == 33
48
+ end
49
+
50
+ # Extract public key from KEY format.
51
+ # @param [String] key KEY string.
52
+ # @return [Bitcoin::Key] public key.
53
+ def extract_pubkey(key)
54
+ if key.start_with?('[') # BIP32 fingerprint
55
+ raise ArgumentError, "Multiple ']' characters found for a single pubkey." if key.count('[') > 1 || key.count(']') > 1
56
+ info = key[1...key.index(']')]
57
+ fingerprint, *paths = info.split('/')
58
+ raise ArgumentError, "Fingerprint '#{fingerprint}' is not hex." unless fingerprint.valid_hex?
59
+ raise ArgumentError, "Fingerprint '#{fingerprint}' is not 4 bytes." unless fingerprint.size == 8
60
+ key = key[(key.index(']') + 1)..-1]
61
+ else
62
+ raise ArgumentError, 'Invalid key origin.' if key.include?(']')
63
+ end
64
+
65
+ # check BIP32 derivation path
66
+ key, *paths = key.split('/')
67
+
68
+ raise ArgumentError, "No key provided." unless key
69
+
70
+ if key.start_with?('xprv')
71
+ key = Bitcoin::ExtKey.from_base58(key)
72
+ key = derive_path(key, paths) if paths
73
+ elsif key.start_with?('xpub')
74
+ key = Bitcoin::ExtPubkey.from_base58(key)
75
+ key = derive_path(key, paths) if paths
76
+ else
77
+ begin
78
+ key = Bitcoin::Key.from_wif(key)
79
+ rescue ArgumentError
80
+ key = if key.length == 64
81
+ Bitcoin::Key.from_xonly_pubkey(key)
82
+ else
83
+ key_type = compressed_key?(key) ? Bitcoin::Key::TYPES[:compressed] : Bitcoin::Key::TYPES[:uncompressed]
84
+ Bitcoin::Key.new(pubkey: key, key_type: key_type)
85
+ end
86
+ end
87
+ end
88
+ key = key.is_a?(Bitcoin::Key) ? key : key.key
89
+ raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless key.fully_valid_pubkey?
90
+ key
91
+ end
92
+
93
+ # Derive key using +paths+.
94
+ # @param [Bitcoin::ExtKey or Bitcoin::ExtPubkey] key
95
+ # @param [String] paths derivation path.
96
+ # @return [Bitcoin::Key]
97
+ def derive_path(key, paths)
98
+ is_private = key.is_a?(Bitcoin::ExtKey)
99
+ paths.each do |path|
100
+ raise ArgumentError, 'xpub can not derive hardened key.' if !is_private && path.end_with?("'")
101
+ if is_private
102
+ hardened = path.end_with?("'")
103
+ path = hardened ? path[0..-2] : path
104
+ raise ArgumentError, 'Key path value is not a valid value.' unless path =~ /^[0-9]+$/
105
+ raise ArgumentError, 'Key path value is out of range.' if !hardened && path.to_i >= Bitcoin::HARDENED_THRESHOLD
106
+ key = key.derive(path.to_i, hardened)
107
+ else
108
+ raise ArgumentError, 'Key path value is not a valid value.' unless path =~ /^[0-9]+$/
109
+ raise ArgumentError, 'Key path value is out of range.' if path.to_i >= Bitcoin::HARDENED_THRESHOLD
110
+ key = key.derive(path.to_i)
111
+ end
112
+ end
113
+ key
114
+ end
115
+
116
+ def ==(other)
117
+ return false unless other.is_a?(Expression)
118
+ type == other.type && to_script == other.to_script
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,23 @@
1
+ module Bitcoin
2
+ module Descriptor
3
+ class KeyExpression < Expression
4
+ attr_reader :key
5
+
6
+ # Constructor
7
+ # @raise [ArgumentError] If +key+ is invalid.
8
+ def initialize(key)
9
+ raise ArgumentError, "Key must be string." unless key.is_a? String
10
+ extract_pubkey(key)
11
+ @key = key
12
+ end
13
+
14
+ def args
15
+ key
16
+ end
17
+
18
+ def top_level?
19
+ false
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,49 @@
1
+ module Bitcoin
2
+ module Descriptor
3
+ # multi() expression
4
+ class Multi < Expression
5
+
6
+ attr_reader :threshold
7
+ attr_reader :keys
8
+
9
+ def initialize(threshold, keys)
10
+ validate!(threshold, keys)
11
+ @threshold = threshold
12
+ @keys = keys
13
+ end
14
+
15
+ def type
16
+ :multi
17
+ end
18
+
19
+ def to_script
20
+ Script.to_multisig_script(threshold, keys.map{|key| extract_pubkey(key).pubkey }, sort: false)
21
+ end
22
+
23
+ def to_hex
24
+ result = to_script
25
+ pubkey_count = result.get_pubkeys.length
26
+ raise RuntimeError, "Cannot have #{pubkey_count} pubkeys in bare multisig; only at most 3 pubkeys." if pubkey_count > 3
27
+ result.to_hex
28
+ end
29
+
30
+ def args
31
+ "#{threshold},#{keys.join(',')}"
32
+ end
33
+
34
+ def top_level?
35
+ false
36
+ end
37
+
38
+ private
39
+
40
+ def validate!(threshold, keys)
41
+ raise ArgumentError, "Multisig threshold '#{threshold}' is not valid." unless threshold.is_a?(Integer)
42
+ raise ArgumentError, 'Multisig threshold cannot be 0, must be at least 1.' unless threshold > 0
43
+ raise ArgumentError, 'Multisig threshold cannot be larger than the number of keys.' if threshold > keys.size
44
+ raise ArgumentError, "Multisig must have between 1 and #{Bitcoin::MAX_PUBKEYS_PER_MULTISIG} keys, inclusive." if keys.size > Bitcoin::MAX_PUBKEYS_PER_MULTISIG
45
+ keys.each{|key| extract_pubkey(key) }
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,43 @@
1
+ module Bitcoin
2
+ module Descriptor
3
+ # multi_a() expression
4
+ # @see https://github.com/bitcoin/bips/blob/master/bip-0387.mediawiki
5
+ class MultiA < Multi
6
+ include Bitcoin::Opcodes
7
+
8
+ def type
9
+ :multi_a
10
+ end
11
+
12
+ def to_hex
13
+ raise RuntimeError, "Can only have multi_a/sortedmulti_a inside tr()."
14
+ end
15
+
16
+ def to_script
17
+ multisig_script(keys.map{|k| extract_pubkey(k).xonly_pubkey})
18
+ end
19
+
20
+ private
21
+
22
+ def multisig_script(keys)
23
+ script = Bitcoin::Script.new
24
+ keys.each.with_index do |k, i|
25
+ script << k
26
+ script << (i == 0 ? OP_CHECKSIG : OP_CHECKSIGADD)
27
+ end
28
+ script << threshold << OP_NUMEQUAL
29
+ end
30
+
31
+ def validate!(threshold, keys)
32
+ raise ArgumentError, "Multisig threshold '#{threshold}' is not valid." unless threshold.is_a?(Integer)
33
+ raise ArgumentError, 'Multisig threshold cannot be 0, must be at least 1.' unless threshold > 0
34
+ raise ArgumentError, 'Multisig threshold cannot be larger than the number of keys.' if threshold > keys.size
35
+ raise ArgumentError, "Multisig must have between 1 and 999 keys, inclusive." if keys.size > 999
36
+ keys.each do |key|
37
+ k = extract_pubkey(key)
38
+ raise ArgumentError, "Uncompressed key are not allowed." unless k.compressed?
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,27 @@
1
+ module Bitcoin
2
+ module Descriptor
3
+ # pk() expression
4
+ class Pk < KeyExpression
5
+ include Bitcoin::Opcodes
6
+
7
+ attr_accessor :xonly
8
+
9
+ def initialize(key)
10
+ super(key)
11
+ @xonly = false
12
+ end
13
+
14
+ def type
15
+ :pk
16
+ end
17
+
18
+ # Convert to bitcoin script.
19
+ # @return [Bitcoin::Script]
20
+ def to_script
21
+ k = extract_pubkey(key)
22
+ target_key = xonly ? k.xonly_pubkey : k.pubkey
23
+ Bitcoin::Script.new << target_key << OP_CHECKSIG
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,15 @@
1
+ module Bitcoin
2
+ module Descriptor
3
+ # pkh() expression
4
+ class Pkh < KeyExpression
5
+
6
+ def type
7
+ :pkh
8
+ end
9
+
10
+ def to_script
11
+ Script.to_p2pkh(extract_pubkey(key).hash160)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ module Bitcoin
2
+ module Descriptor
3
+ class Raw < Expression
4
+
5
+ attr_reader :hex
6
+
7
+ # Constructor
8
+ # @param [String] hex
9
+ def initialize(hex)
10
+ raise ArgumentError, "Raw script must be string." unless hex.is_a?(String)
11
+ raise ArgumentError, "Raw script is not hex." unless hex.valid_hex?
12
+ @hex = hex
13
+ end
14
+
15
+ def type
16
+ :raw
17
+ end
18
+
19
+ def to_script
20
+ Bitcoin::Script.parse_from_payload(hex.htb)
21
+ end
22
+
23
+ def args
24
+ hex
25
+ end
26
+
27
+ def top_level?
28
+ true
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,24 @@
1
+ module Bitcoin
2
+ module Descriptor
3
+ class ScriptExpression < Expression
4
+
5
+ attr_reader :script
6
+
7
+ def initialize(script)
8
+ validate!(script)
9
+ @script = script
10
+ end
11
+
12
+ def args
13
+ script.to_s
14
+ end
15
+
16
+ private
17
+
18
+ def validate!(script)
19
+ raise ArgumentError, "Can only have #{script.type.to_s}() at top level." if script.is_a?(Expression) && script.top_level?
20
+ raise ArgumentError, 'Can only have multi_a/sortedmulti_a inside tr().' if script.is_a?(MultiA) || script.is_a?(SortedMultiA)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,31 @@
1
+ module Bitcoin
2
+ module Descriptor
3
+ # sh expression
4
+ class Sh < ScriptExpression
5
+
6
+ def type
7
+ :sh
8
+ end
9
+
10
+ def to_script
11
+ script.to_script.to_p2sh
12
+ end
13
+
14
+ def top_level?
15
+ true
16
+ end
17
+
18
+ private
19
+
20
+ def validate!(script)
21
+ super(script)
22
+ raise ArgumentError, 'A function is needed within P2SH.' unless script.is_a?(Expression)
23
+ script_size = script.to_script.size
24
+ if script_size > Bitcoin::MAX_SCRIPT_ELEMENT_SIZE
25
+ raise ArgumentError,
26
+ "P2SH script is too large, #{script_size} bytes is larger than #{Bitcoin::MAX_SCRIPT_ELEMENT_SIZE} bytes."
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ module Bitcoin
2
+ module Descriptor
3
+ # sortedmulti() expression
4
+ class SortedMulti < Multi
5
+
6
+ def type
7
+ :sortedmulti
8
+ end
9
+
10
+ def to_script
11
+ Script.to_multisig_script(threshold, keys.map{|key| extract_pubkey(key).pubkey }, sort: true)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Bitcoin
2
+ module Descriptor
3
+ # sortedmulti_a expression
4
+ # @see https://github.com/bitcoin/bips/blob/master/bip-0387.mediawiki
5
+ class SortedMultiA < MultiA
6
+ def type
7
+ :sortedmulti_a
8
+ end
9
+
10
+ def to_script
11
+ multisig_script( keys.map{|k| extract_pubkey(k).xonly_pubkey}.sort)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -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(extract_pubkey(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