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
@@ -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