bitcoinrb 1.4.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +2 -2
- data/.ruby-version +1 -1
- data/README.md +16 -5
- data/bitcoinrb.gemspec +2 -2
- data/lib/bitcoin/bip324/cipher.rb +113 -0
- data/lib/bitcoin/bip324/ell_swift_pubkey.rb +42 -0
- data/lib/bitcoin/bip324/fs_chacha20.rb +132 -0
- data/lib/bitcoin/bip324/fs_chacha_poly1305.rb +129 -0
- data/lib/bitcoin/bip324.rb +144 -0
- data/lib/bitcoin/descriptor/addr.rb +31 -0
- data/lib/bitcoin/descriptor/checksum.rb +74 -0
- data/lib/bitcoin/descriptor/combo.rb +30 -0
- data/lib/bitcoin/descriptor/expression.rb +122 -0
- data/lib/bitcoin/descriptor/key_expression.rb +23 -0
- data/lib/bitcoin/descriptor/multi.rb +49 -0
- data/lib/bitcoin/descriptor/multi_a.rb +43 -0
- data/lib/bitcoin/descriptor/pk.rb +27 -0
- data/lib/bitcoin/descriptor/pkh.rb +15 -0
- data/lib/bitcoin/descriptor/raw.rb +32 -0
- data/lib/bitcoin/descriptor/script_expression.rb +24 -0
- data/lib/bitcoin/descriptor/sh.rb +31 -0
- data/lib/bitcoin/descriptor/sorted_multi.rb +15 -0
- data/lib/bitcoin/descriptor/sorted_multi_a.rb +15 -0
- data/lib/bitcoin/descriptor/tr.rb +91 -0
- data/lib/bitcoin/descriptor/wpkh.rb +19 -0
- data/lib/bitcoin/descriptor/wsh.rb +30 -0
- data/lib/bitcoin/descriptor.rb +176 -100
- data/lib/bitcoin/ext/ecdsa.rb +0 -6
- data/lib/bitcoin/key.rb +16 -4
- data/lib/bitcoin/message_sign.rb +13 -8
- data/lib/bitcoin/script/script.rb +8 -3
- data/lib/bitcoin/secp256k1/native.rb +62 -6
- data/lib/bitcoin/secp256k1/ruby.rb +21 -4
- data/lib/bitcoin/taproot/custom_depth_builder.rb +64 -0
- data/lib/bitcoin/taproot/simple_builder.rb +1 -6
- data/lib/bitcoin/taproot.rb +1 -0
- data/lib/bitcoin/tx.rb +1 -1
- data/lib/bitcoin/util.rb +11 -3
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin.rb +1 -0
- 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,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
|