bitcoinrb 0.8.0 → 1.1.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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/README.md +1 -1
- data/lib/bitcoin/constants.rb +31 -16
- data/lib/bitcoin/ext/object_ext.rb +29 -0
- data/lib/bitcoin/ext.rb +1 -0
- data/lib/bitcoin/key.rb +0 -2
- data/lib/bitcoin/network/message_handler.rb +2 -0
- data/lib/bitcoin/psbt/input.rb +35 -1
- data/lib/bitcoin/psbt/output.rb +6 -0
- data/lib/bitcoin/psbt/proprietary.rb +44 -0
- data/lib/bitcoin/psbt/tx.rb +16 -1
- data/lib/bitcoin/psbt.rb +37 -5
- data/lib/bitcoin/script/script.rb +2 -2
- data/lib/bitcoin/script/script_interpreter.rb +2 -2
- data/lib/bitcoin/sighash_generator.rb +4 -1
- data/lib/bitcoin/taproot/leaf_node.rb +1 -1
- data/lib/bitcoin/taproot/simple_builder.rb +59 -43
- data/lib/bitcoin/taproot.rb +36 -0
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin.rb +0 -26
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6b5d0d0d17295086ec1cf989eb1df1be2d776bff6c5a38e85c34e21e258f56b
|
4
|
+
data.tar.gz: c93206160d6709f0050cbe8a001df40f712a6e8a779a61117c8c0cc911d4fd2f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fa5759ab05a69bfdaadda07d5cbf74c700bba2586f2544eaf823199c357b88405814dcaed7c6466a1a94e50027d5289a51f057d86815699e92862cb447b2478f
|
7
|
+
data.tar.gz: 8629b46d88f6674482bfaff89dffcf28e90edc857649aeaff56d579c12d0e4df99f9cc7cad63a31ecc1742afc64cfa089a7f86754be5614e27c145e0e693e1cb
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby-3.0.
|
1
|
+
ruby-3.0.2
|
data/README.md
CHANGED
@@ -15,7 +15,7 @@ Bitcoinrb supports following feature:
|
|
15
15
|
* Key generation and verification for ECDSA, including [BIP-32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) and [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) supports.
|
16
16
|
* ECDSA signature(RFC6979 -Deterministic ECDSA, LOW-S, LOW-R support)
|
17
17
|
* Segwit support (parsing segwit payload, Bech32 address, sign for segwit tx, [BIP-141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki), [BIP-143](https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki), [BIP-144](https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki))
|
18
|
-
* [BIP-173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki)
|
18
|
+
* bech32([BIP-173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki)) and bech32m([BIP-350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki)) address support
|
19
19
|
* [BIP-174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki) PSBT(Partially Signed Bitcoin Transaction) support
|
20
20
|
* [BIP-85](https://github.com/bitcoin/bips/blob/master/bip-0085.mediawiki) Deterministic Entropy From BIP32 Keychains support by `Bitcoin::BIP85Entropy` class.
|
21
21
|
* Schnorr signature([BIP-340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki))
|
data/lib/bitcoin/constants.rb
CHANGED
@@ -3,6 +3,15 @@ module Bitcoin
|
|
3
3
|
COIN = 100_000_000
|
4
4
|
MAX_MONEY = 21_000_000 * COIN
|
5
5
|
|
6
|
+
# Byte size of the ripemd160 hash
|
7
|
+
RIPEMD160_SIZE = 20
|
8
|
+
# Byte size of the SHA256 hash
|
9
|
+
SHA256_SIZE = 32
|
10
|
+
# Byte size of the HASH160 hash
|
11
|
+
HASH160_SIZE = 20
|
12
|
+
# Byte size of the HASH256 hash
|
13
|
+
HASH256_SIZE = 32
|
14
|
+
|
6
15
|
# The maximum allowed size for a serialized block, in bytes (only for buffer size limits)
|
7
16
|
MAX_BLOCK_SERIALIZED_SIZE = 4_000_000
|
8
17
|
# The maximum allowed weight for a block, see BIP 141 (network rule)
|
@@ -53,22 +62,28 @@ module Bitcoin
|
|
53
62
|
MANDATORY_SCRIPT_VERIFY_FLAGS = SCRIPT_VERIFY_P2SH
|
54
63
|
|
55
64
|
# Standard script verification flags that standard transactions will comply with.
|
56
|
-
STANDARD_SCRIPT_VERIFY_FLAGS = [
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
65
|
+
STANDARD_SCRIPT_VERIFY_FLAGS = [
|
66
|
+
MANDATORY_SCRIPT_VERIFY_FLAGS,
|
67
|
+
SCRIPT_VERIFY_DERSIG,
|
68
|
+
SCRIPT_VERIFY_STRICTENC,
|
69
|
+
SCRIPT_VERIFY_MINIMALDATA,
|
70
|
+
SCRIPT_VERIFY_NULLDUMMY,
|
71
|
+
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS,
|
72
|
+
SCRIPT_VERIFY_CLEANSTACK,
|
73
|
+
SCRIPT_VERIFY_MINIMALIF,
|
74
|
+
SCRIPT_VERIFY_NULLFAIL,
|
75
|
+
SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY,
|
76
|
+
SCRIPT_VERIFY_CHECKSEQUENCEVERIFY,
|
77
|
+
SCRIPT_VERIFY_LOW_S,
|
78
|
+
SCRIPT_VERIFY_WITNESS,
|
79
|
+
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM,
|
80
|
+
SCRIPT_VERIFY_WITNESS_PUBKEYTYPE,
|
81
|
+
SCRIPT_VERIFY_CONST_SCRIPTCODE,
|
82
|
+
SCRIPT_VERIFY_TAPROOT,
|
83
|
+
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION,
|
84
|
+
SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS,
|
85
|
+
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE
|
86
|
+
].inject(SCRIPT_VERIFY_NONE){|flags, f| flags |= f}
|
72
87
|
|
73
88
|
# for script
|
74
89
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Ext
|
3
|
+
module ObjectExt
|
4
|
+
refine Object do
|
5
|
+
def build_json
|
6
|
+
if self.is_a?(Array)
|
7
|
+
"[#{self.map{|o|o.to_h.to_json}.join(',')}]"
|
8
|
+
else
|
9
|
+
to_h.to_json
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_h
|
14
|
+
return self if self.is_a?(String)
|
15
|
+
instance_variables.inject({}) do |result, var|
|
16
|
+
key = var.to_s
|
17
|
+
key.slice!(0) if key.start_with?('@')
|
18
|
+
value = instance_variable_get(var)
|
19
|
+
if value.is_a?(Array)
|
20
|
+
result.update(key => value.map{|v|v.to_h})
|
21
|
+
else
|
22
|
+
result.update(key => value.class.to_s.start_with?("Bitcoin::") ? value.to_h : value)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/bitcoin/ext.rb
CHANGED
data/lib/bitcoin/key.rb
CHANGED
@@ -30,7 +30,6 @@ module Bitcoin
|
|
30
30
|
# @param [Boolean] compressed [Deprecated] whether public key is compressed.
|
31
31
|
# @return [Bitcoin::Key] a key object.
|
32
32
|
def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true, allow_hybrid: false)
|
33
|
-
puts "[Warning] Use key_type parameter instead of compressed. compressed parameter removed in the future." if key_type.nil? && !compressed.nil? && pubkey.nil?
|
34
33
|
if key_type
|
35
34
|
@key_type = key_type
|
36
35
|
compressed = @key_type != TYPES[:uncompressed]
|
@@ -213,7 +212,6 @@ module Bitcoin
|
|
213
212
|
# get xonly public key (32 bytes).
|
214
213
|
# @return [String] xonly public key with hex format
|
215
214
|
def xonly_pubkey
|
216
|
-
puts "Derive a public key whose y-coordinate is different from this public key." if compressed? && pubkey[0...2] != '02'
|
217
215
|
pubkey[2..65]
|
218
216
|
end
|
219
217
|
|
data/lib/bitcoin/psbt/input.rb
CHANGED
@@ -14,6 +14,11 @@ module Bitcoin
|
|
14
14
|
attr_accessor :hd_key_paths
|
15
15
|
attr_accessor :partial_sigs
|
16
16
|
attr_accessor :sighash_type
|
17
|
+
attr_accessor :ripemd160_preimages
|
18
|
+
attr_accessor :sha256_preimages
|
19
|
+
attr_accessor :hash160_preimages
|
20
|
+
attr_accessor :hash256_preimages
|
21
|
+
attr_accessor :proprietaries
|
17
22
|
attr_accessor :unknowns
|
18
23
|
|
19
24
|
def initialize(non_witness_utxo: nil, witness_utxo: nil)
|
@@ -21,6 +26,11 @@ module Bitcoin
|
|
21
26
|
@witness_utxo = witness_utxo
|
22
27
|
@partial_sigs = {}
|
23
28
|
@hd_key_paths = {}
|
29
|
+
@ripemd160_preimages = {}
|
30
|
+
@sha256_preimages = {}
|
31
|
+
@hash160_preimages = {}
|
32
|
+
@hash256_preimages = {}
|
33
|
+
@proprietaries = []
|
24
34
|
@unknowns = {}
|
25
35
|
end
|
26
36
|
|
@@ -59,7 +69,7 @@ module Bitcoin
|
|
59
69
|
input.partial_sigs[pubkey.pubkey] = value
|
60
70
|
when PSBT_IN_TYPES[:sighash]
|
61
71
|
raise ArgumentError, 'Invalid input sighash type typed key.' unless key_len == 1
|
62
|
-
raise ArgumentError 'Duplicate Key, input sighash type already provided.' if input.sighash_type
|
72
|
+
raise ArgumentError, 'Duplicate Key, input sighash type already provided.' if input.sighash_type
|
63
73
|
input.sighash_type = value.unpack1('I')
|
64
74
|
when PSBT_IN_TYPES[:redeem_script]
|
65
75
|
raise ArgumentError, 'Invalid redeemscript typed key.' unless key_len == 1
|
@@ -81,6 +91,25 @@ module Bitcoin
|
|
81
91
|
raise ArgumentError, 'Invalid final script witness typed key.' unless key_len == 1
|
82
92
|
raise ArgumentError, 'Duplicate Key, input final scriptWitness already provided.' if input.final_script_witness
|
83
93
|
input.final_script_witness = Bitcoin::ScriptWitness.parse_from_payload(value)
|
94
|
+
when PSBT_IN_TYPES[:ripemd160]
|
95
|
+
raise ArgumentError, 'Size of key was not the expected size for the type ripemd160 preimage' unless key.bytesize == RIPEMD160_SIZE
|
96
|
+
raise ArgumentError, 'Duplicate Key, input ripemd160 preimage already provided' if input.ripemd160_preimages[key.bth]
|
97
|
+
input.ripemd160_preimages[key.bth] = value.bth
|
98
|
+
when PSBT_IN_TYPES[:sha256]
|
99
|
+
raise ArgumentError, 'Size of key was not the expected size for the type sha256 preimage' unless key.bytesize == SHA256_SIZE
|
100
|
+
raise ArgumentError, 'Duplicate Key, input sha256 preimage already provided' if input.sha256_preimages[key.bth]
|
101
|
+
input.sha256_preimages[key.bth] = value.bth
|
102
|
+
when PSBT_IN_TYPES[:hash160]
|
103
|
+
raise ArgumentError, 'Size of key was not the expected size for the type hash160 preimage' unless key.bytesize == HASH160_SIZE
|
104
|
+
raise ArgumentError, 'Duplicate Key, input hash160 preimage already provided' if input.hash160_preimages[key.bth]
|
105
|
+
input.hash160_preimages[key.bth] = value.bth
|
106
|
+
when PSBT_IN_TYPES[:hash256]
|
107
|
+
raise ArgumentError, 'Size of key was not the expected size for the type hash256 preimage' unless key.bytesize == HASH256_SIZE
|
108
|
+
raise ArgumentError, 'Duplicate Key, input hash256 preimage already provided' if input.hash256_preimages[key.bth]
|
109
|
+
input.hash256_preimages[key.bth] = value.bth
|
110
|
+
when PSBT_IN_TYPES[:proprietary]
|
111
|
+
raise ArgumentError, 'Duplicate Key, key for proprietary value already provided.' if input.proprietaries.any?{|p| p.key == key}
|
112
|
+
input.proprietaries << Proprietary.new(key, value)
|
84
113
|
else
|
85
114
|
unknown_key = ([key_type].pack('C') + key).bth
|
86
115
|
raise ArgumentError, 'Duplicate Key, key for unknown value already provided.' if input.unknowns[unknown_key]
|
@@ -102,9 +131,14 @@ module Bitcoin
|
|
102
131
|
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:redeem_script], value: redeem_script.to_payload) if redeem_script
|
103
132
|
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:witness_script], value: witness_script.to_payload) if witness_script
|
104
133
|
payload << hd_key_paths.values.map(&:to_payload).join
|
134
|
+
payload << ripemd160_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:ripemd160], key: k.htb, value: v.htb)}.join
|
135
|
+
payload << sha256_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:sha256], key: k.htb, value: v.htb)}.join
|
136
|
+
payload << hash160_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:hash160], key: k.htb, value: v.htb)}.join
|
137
|
+
payload << hash256_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:hash256], key: k.htb, value: v.htb)}.join
|
105
138
|
end
|
106
139
|
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:script_sig], value: final_script_sig.to_payload) if final_script_sig
|
107
140
|
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:script_witness], value: final_script_witness.to_payload) if final_script_witness
|
141
|
+
payload << proprietaries.map(&:to_payload).join
|
108
142
|
payload << unknowns.map {|k,v|Bitcoin.pack_var_int(k.htb.bytesize) << k.htb << Bitcoin.pack_var_int(v.bytesize) << v}.join
|
109
143
|
payload << PSBT_SEPARATOR.itb
|
110
144
|
payload
|
data/lib/bitcoin/psbt/output.rb
CHANGED
@@ -7,10 +7,12 @@ module Bitcoin
|
|
7
7
|
attr_accessor :redeem_script
|
8
8
|
attr_accessor :witness_script
|
9
9
|
attr_accessor :hd_key_paths
|
10
|
+
attr_accessor :proprietaries
|
10
11
|
attr_accessor :unknowns
|
11
12
|
|
12
13
|
def initialize
|
13
14
|
@hd_key_paths = {}
|
15
|
+
@proprietaries = []
|
14
16
|
@unknowns = {}
|
15
17
|
end
|
16
18
|
|
@@ -41,6 +43,9 @@ module Bitcoin
|
|
41
43
|
when PSBT_OUT_TYPES[:bip32_derivation]
|
42
44
|
raise ArgumentError, 'Duplicate Key, pubkey derivation path already provided' if output.hd_key_paths[key.bth]
|
43
45
|
output.hd_key_paths[key.bth] = Bitcoin::PSBT::HDKeyPath.new(key, Bitcoin::PSBT::KeyOriginInfo.parse_from_payload(value))
|
46
|
+
when PSBT_OUT_TYPES[:proprietary]
|
47
|
+
raise ArgumentError, 'Duplicate Key, key for proprietary value already provided.' if output.proprietaries.any?{|p| p.key == key}
|
48
|
+
output.proprietaries << Proprietary.new(key, value)
|
44
49
|
else
|
45
50
|
unknown_key = ([key_type].pack('C') + key).bth
|
46
51
|
raise ArgumentError, 'Duplicate Key, key for unknown value already provided' if output.unknowns[unknown_key]
|
@@ -56,6 +61,7 @@ module Bitcoin
|
|
56
61
|
payload << PSBT.serialize_to_vector(PSBT_OUT_TYPES[:redeem_script], value: redeem_script) if redeem_script
|
57
62
|
payload << PSBT.serialize_to_vector(PSBT_OUT_TYPES[:witness_script], value: witness_script) if witness_script
|
58
63
|
payload << hd_key_paths.values.map{|v|v.to_payload(PSBT_OUT_TYPES[:bip32_derivation])}.join
|
64
|
+
payload << proprietaries.map(&:to_payload).join
|
59
65
|
payload << unknowns.map {|k,v|Bitcoin.pack_var_int(k.htb.bytesize) << k.htb << Bitcoin.pack_var_int(v.bytesize) << v}.join
|
60
66
|
payload << PSBT_SEPARATOR.itb
|
61
67
|
payload
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module PSBT
|
3
|
+
# Proprietary element of PSBT
|
4
|
+
# https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#Proprietary_Use_Type
|
5
|
+
class Proprietary
|
6
|
+
attr_accessor :identifier # binary format
|
7
|
+
attr_accessor :sub_type # integer
|
8
|
+
attr_accessor :value # binary format
|
9
|
+
|
10
|
+
# @param [String] key key with binary format without key type(0xfc).
|
11
|
+
# @param [String] value value with binary format.
|
12
|
+
def initialize(key, value)
|
13
|
+
buf = StringIO.new(key)
|
14
|
+
id_len = Bitcoin.unpack_var_int_from_io(buf)
|
15
|
+
@identifier = buf.read(id_len)
|
16
|
+
@sub_type = Bitcoin.unpack_var_int_from_io(buf)
|
17
|
+
@value = value
|
18
|
+
end
|
19
|
+
|
20
|
+
# Show contents
|
21
|
+
# @return [String]
|
22
|
+
def to_s
|
23
|
+
"identifier: #{identifier&.bth}, sub type: #{sub_type}, value: #{value&.bth}"
|
24
|
+
end
|
25
|
+
|
26
|
+
# Get key data with key type(0xfc).
|
27
|
+
# @return [String] key data with binary format.
|
28
|
+
def key
|
29
|
+
k = [PSBT_GLOBAL_TYPES[:proprietary]].pack('C')
|
30
|
+
k << Bitcoin.pack_var_int(identifier ? identifier.bytesize : 0)
|
31
|
+
k << identifier if identifier
|
32
|
+
k << Bitcoin.pack_var_int(sub_type)
|
33
|
+
k
|
34
|
+
end
|
35
|
+
|
36
|
+
# Convert to payload
|
37
|
+
# @return [String] payload with binary format.
|
38
|
+
def to_payload
|
39
|
+
k = key
|
40
|
+
Bitcoin.pack_var_int(k.bytesize) + k + Bitcoin.pack_var_int(value.bytesize) + value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/bitcoin/psbt/tx.rb
CHANGED
@@ -32,6 +32,7 @@ module Bitcoin
|
|
32
32
|
attr_accessor :xpubs
|
33
33
|
attr_reader :inputs
|
34
34
|
attr_reader :outputs
|
35
|
+
attr_accessor :proprietaries
|
35
36
|
attr_accessor :unknowns
|
36
37
|
attr_accessor :version_number
|
37
38
|
|
@@ -40,6 +41,7 @@ module Bitcoin
|
|
40
41
|
@xpubs = []
|
41
42
|
@inputs = tx ? tx.in.map{Input.new}: []
|
42
43
|
@outputs = tx ? tx.out.map{Output.new}: []
|
44
|
+
@proprietaries = []
|
43
45
|
@unknowns = {}
|
44
46
|
end
|
45
47
|
|
@@ -66,7 +68,7 @@ module Bitcoin
|
|
66
68
|
found_sep = true
|
67
69
|
break
|
68
70
|
end
|
69
|
-
key_type =
|
71
|
+
key_type = Bitcoin.unpack_var_int_from_io(buf)
|
70
72
|
key = buf.read(key_len - 1)
|
71
73
|
value = buf.read(Bitcoin.unpack_var_int_from_io(buf))
|
72
74
|
|
@@ -89,6 +91,9 @@ module Bitcoin
|
|
89
91
|
when PSBT_GLOBAL_TYPES[:ver]
|
90
92
|
partial_tx.version_number = value.unpack1('V')
|
91
93
|
raise ArgumentError, "An unsupported version was detected." if SUPPORT_VERSION < partial_tx.version_number
|
94
|
+
when PSBT_GLOBAL_TYPES[:proprietary]
|
95
|
+
raise ArgumentError, 'Duplicate Key, key for proprietary value already provided.' if partial_tx.proprietaries.any?{|p| p.key == key}
|
96
|
+
partial_tx.proprietaries << Proprietary.new(key, value)
|
92
97
|
else
|
93
98
|
raise ArgumentError, 'Duplicate Key, key for unknown value already provided.' if partial_tx.unknowns[key]
|
94
99
|
partial_tx.unknowns[([key_type].pack('C') + key).bth] = value
|
@@ -148,6 +153,7 @@ module Bitcoin
|
|
148
153
|
payload << PSBT.serialize_to_vector(PSBT_GLOBAL_TYPES[:unsigned_tx], value: tx.to_payload)
|
149
154
|
payload << xpubs.map(&:to_payload).join
|
150
155
|
payload << PSBT.serialize_to_vector(PSBT_GLOBAL_TYPES[:ver], value: [version_number].pack('V')) if version_number
|
156
|
+
payload << proprietaries.map(&:to_payload).join
|
151
157
|
payload << unknowns.map {|k,v|Bitcoin.pack_var_int(k.htb.bytesize) << k.htb << Bitcoin.pack_var_int(v.bytesize) << v}.join
|
152
158
|
|
153
159
|
payload << PSBT_SEPARATOR.itb
|
@@ -162,6 +168,15 @@ module Bitcoin
|
|
162
168
|
Base64.strict_encode64(to_payload)
|
163
169
|
end
|
164
170
|
|
171
|
+
# Store the PSBT to a file.
|
172
|
+
# @param [String] path File path to store.
|
173
|
+
def to_file(path)
|
174
|
+
raise ArgumentError, 'The file already exists' if File.exist?(path)
|
175
|
+
File.open(path, 'w') do |f|
|
176
|
+
f.write(to_payload)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
165
180
|
# update input key-value maps.
|
166
181
|
# @param [Bitcoin::Tx] prev_tx previous tx reference by input.
|
167
182
|
# @param [Bitcoin::Script] redeem_script redeem script to set input.
|
data/lib/bitcoin/psbt.rb
CHANGED
@@ -9,14 +9,38 @@ module Bitcoin
|
|
9
9
|
autoload :Output, 'bitcoin/psbt/output'
|
10
10
|
autoload :KeyOriginInfo, 'bitcoin/psbt/key_origin_info'
|
11
11
|
autoload :HDKeyPath, 'bitcoin/psbt/hd_key_path'
|
12
|
+
autoload :Proprietary, 'bitcoin/psbt/proprietary'
|
12
13
|
|
13
14
|
# constants for PSBT
|
14
15
|
PSBT_MAGIC_BYTES = 0x70736274
|
15
|
-
PSBT_GLOBAL_TYPES = {
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
PSBT_GLOBAL_TYPES = {
|
17
|
+
unsigned_tx: 0x00,
|
18
|
+
xpub: 0x01,
|
19
|
+
ver: 0xfb,
|
20
|
+
proprietary: 0xfc
|
21
|
+
}
|
22
|
+
PSBT_IN_TYPES = {
|
23
|
+
non_witness_utxo: 0x00,
|
24
|
+
witness_utxo: 0x01,
|
25
|
+
partial_sig: 0x02,
|
26
|
+
sighash: 0x03,
|
27
|
+
redeem_script: 0x04,
|
28
|
+
witness_script: 0x05,
|
29
|
+
bip32_derivation: 0x06,
|
30
|
+
script_sig: 0x07,
|
31
|
+
script_witness: 0x08,
|
32
|
+
ripemd160: 0x0a,
|
33
|
+
sha256: 0x0b,
|
34
|
+
hash160: 0x0c,
|
35
|
+
hash256: 0x0d,
|
36
|
+
proprietary: 0xfc
|
37
|
+
}
|
38
|
+
PSBT_OUT_TYPES = {
|
39
|
+
redeem_script: 0x00,
|
40
|
+
witness_script: 0x01,
|
41
|
+
bip32_derivation: 0x02,
|
42
|
+
proprietary: 0xfc
|
43
|
+
}
|
20
44
|
PSBT_SEPARATOR = 0x00
|
21
45
|
|
22
46
|
SUPPORT_VERSION = 0
|
@@ -31,6 +55,14 @@ module Bitcoin
|
|
31
55
|
s << Bitcoin.pack_var_int(value.bytesize) << value
|
32
56
|
s
|
33
57
|
end
|
58
|
+
|
59
|
+
# Load PSBT from file.
|
60
|
+
# @param [String] path File path of PSBT.
|
61
|
+
# @return [Bitcoin::PSBT::Tx] PSBT object.
|
62
|
+
def load_from_file(path)
|
63
|
+
raise ArgumentError, 'File not found' unless File.exist?(path)
|
64
|
+
Bitcoin::PSBT::Tx.parse_from_payload(File.read(path))
|
65
|
+
end
|
34
66
|
end
|
35
67
|
|
36
68
|
end
|
@@ -551,7 +551,7 @@ module Bitcoin
|
|
551
551
|
def p2pkh_addr
|
552
552
|
return nil unless p2pkh?
|
553
553
|
hash160 = chunks[2].pushed_data.bth
|
554
|
-
return nil unless hash160.htb.bytesize ==
|
554
|
+
return nil unless hash160.htb.bytesize == RIPEMD160_SIZE
|
555
555
|
Bitcoin.encode_base58_address(hash160, Bitcoin.chain_params.address_version)
|
556
556
|
end
|
557
557
|
|
@@ -564,7 +564,7 @@ module Bitcoin
|
|
564
564
|
def p2sh_addr
|
565
565
|
return nil unless p2sh?
|
566
566
|
hash160 = chunks[1].pushed_data.bth
|
567
|
-
return nil unless hash160.htb.bytesize ==
|
567
|
+
return nil unless hash160.htb.bytesize == RIPEMD160_SIZE
|
568
568
|
Bitcoin.encode_base58_address(hash160, Bitcoin.chain_params.p2sh_version)
|
569
569
|
end
|
570
570
|
|
@@ -163,9 +163,9 @@ module Bitcoin
|
|
163
163
|
end
|
164
164
|
return set_error(SCRIPT_ERR_STACK_SIZE) if stack.size > MAX_STACK_SIZE
|
165
165
|
need_evaluate = true
|
166
|
+
elsif flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION)
|
167
|
+
return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION)
|
166
168
|
end
|
167
|
-
|
168
|
-
return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION) if flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION)
|
169
169
|
return true unless need_evaluate
|
170
170
|
end
|
171
171
|
elsif flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
|
@@ -62,6 +62,7 @@ module Bitcoin
|
|
62
62
|
|
63
63
|
def generate(tx, input_index, hash_type, opts)
|
64
64
|
amount = opts[:amount]
|
65
|
+
raise ArgumentError, 'segwit sighash requires amount.' unless amount
|
65
66
|
output_script = opts[:script_code]
|
66
67
|
skip_separator_index = opts[:skip_separator_index]
|
67
68
|
hash_prevouts = Bitcoin.double_sha256(tx.inputs.map{|i|i.out_point.to_payload}.join)
|
@@ -70,7 +71,9 @@ module Bitcoin
|
|
70
71
|
amount = [amount].pack('Q')
|
71
72
|
nsequence = [tx.inputs[input_index].sequence].pack('V')
|
72
73
|
hash_outputs = Bitcoin.double_sha256(tx.outputs.map{|o|o.to_payload}.join)
|
73
|
-
|
74
|
+
if output_script.p2wsh?
|
75
|
+
warn('The output_script must be a witness script, not the P2WSH itself.')
|
76
|
+
end
|
74
77
|
script_code = output_script.to_script_code(skip_separator_index)
|
75
78
|
|
76
79
|
case (hash_type & 0x1f)
|
@@ -7,7 +7,7 @@ module Bitcoin
|
|
7
7
|
# Initialize
|
8
8
|
# @param [Bitcoin::Script] script Locking script
|
9
9
|
# @param [Integer] leaf_ver The leaf version of this script.
|
10
|
-
def initialize(script, leaf_ver)
|
10
|
+
def initialize(script, leaf_ver = Bitcoin::TAPROOT_LEAF_TAPSCRIPT)
|
11
11
|
raise Taproot::Error, 'script must be Bitcoin::Script object' unless script.is_a?(Bitcoin::Script)
|
12
12
|
@script = script
|
13
13
|
@leaf_ver = leaf_ver
|
@@ -1,34 +1,50 @@
|
|
1
1
|
module Bitcoin
|
2
2
|
module Taproot
|
3
3
|
|
4
|
-
# Utility class to construct Taproot outputs from internal key and script tree.
|
4
|
+
# Utility class to construct Taproot outputs from internal key and script tree.keyPathSpending
|
5
5
|
# SimpleBuilder builds a script tree that places all lock scripts, in the order they are added, as leaf nodes.
|
6
6
|
# It is not possible to specify the depth of the locking script or to insert any intermediate nodes.
|
7
7
|
class SimpleBuilder
|
8
8
|
include Bitcoin::Opcodes
|
9
9
|
|
10
10
|
attr_reader :internal_key # String with hex format
|
11
|
-
attr_reader :
|
11
|
+
attr_reader :branches # List of branch that has two child leaves
|
12
12
|
|
13
13
|
# Initialize builder.
|
14
14
|
# @param [String] internal_key Internal public key with hex format.
|
15
|
-
# @param [Array[Bitcoin::
|
16
|
-
# @
|
17
|
-
# @raise [Bitcoin::Taproot::Builder] +internal_pubkey+ dose not xonly public key or script in +scripts+ does not instance of Bitcoin::Script.
|
15
|
+
# @param [Array[Bitcoin::Taproot::LeafNode]] leaves (Optional) Array of leaf nodes for each lock condition.
|
16
|
+
# @raise [Bitcoin::Taproot::Builder] +internal_pubkey+ dose not xonly public key or leaf in +leaves+ does not instance of Bitcoin::Taproot::LeafNode.
|
18
17
|
# @return [Bitcoin::Taproot::SimpleBuilder]
|
19
|
-
def initialize(internal_key,
|
18
|
+
def initialize(internal_key, leaves = [])
|
20
19
|
raise Error, 'Internal public key must be 32 bytes' unless internal_key.htb.bytesize == 32
|
21
|
-
|
20
|
+
raise Error, 'leaf must be Bitcoin::Taproot::LeafNode object' if leaves.find{ |leaf| !leaf.is_a?(Bitcoin::Taproot::LeafNode)}
|
21
|
+
|
22
|
+
@leaves = leaves
|
23
|
+
@branches = leaves.each_slice(2).map.to_a
|
22
24
|
@internal_key = internal_key
|
23
25
|
end
|
24
26
|
|
25
|
-
# Add
|
26
|
-
# @param [Bitcoin::
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
# Add a leaf node to the end of the current branch.
|
28
|
+
# @param [Bitcoin::Taproot::LeafNode] leaf Leaf node to be added.
|
29
|
+
def add_leaf(leaf)
|
30
|
+
raise Error, 'leaf must be Bitcoin::Taproot::LeafNode object' unless leaf.is_a?(Bitcoin::Taproot::LeafNode)
|
31
|
+
|
32
|
+
if branches.last&.size == 1
|
33
|
+
branches.last << leaf
|
34
|
+
else
|
35
|
+
branches << [leaf]
|
36
|
+
end
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
# Add a pair of leaf nodes as a branch. If there is only one, add a branch with only one child.
|
41
|
+
# @param [Bitcoin::Taproot::LeafNode] leaf1 Leaf node to be added.
|
42
|
+
# @param [Bitcoin::Taproot::LeafNode] leaf2 Leaf node to be added.
|
43
|
+
def add_branch(leaf1, leaf2 = nil)
|
44
|
+
raise Error, 'leaf1 must be Bitcoin::Taproot::LeafNode object' unless leaf1.is_a?(Bitcoin::Taproot::LeafNode)
|
45
|
+
raise Error, 'leaf2 must be Bitcoin::Taproot::LeafNode object' if leaf2 && !leaf2.is_a?(Bitcoin::Taproot::LeafNode)
|
46
|
+
|
47
|
+
branches << (leaf2.nil? ? [leaf1] : [leaf1, leaf2])
|
32
48
|
self
|
33
49
|
end
|
34
50
|
|
@@ -42,8 +58,7 @@ module Bitcoin
|
|
42
58
|
# Compute the tweaked public key.
|
43
59
|
# @return [Bitcoin::Key] the tweaked public key
|
44
60
|
def tweak_public_key
|
45
|
-
|
46
|
-
Bitcoin::Key.from_point(key.to_point + Bitcoin::Key.from_xonly_pubkey(internal_key).to_point)
|
61
|
+
Taproot.tweak_public_key(Bitcoin::Key.from_xonly_pubkey(internal_key), merkle_root)
|
47
62
|
end
|
48
63
|
|
49
64
|
# Compute the secret key for a tweaked public key.
|
@@ -51,29 +66,33 @@ module Bitcoin
|
|
51
66
|
# @return [Bitcoin::Key] secret key for a tweaked public key
|
52
67
|
def tweak_private_key(key)
|
53
68
|
raise Error, 'Requires private key' unless key.priv_key
|
54
|
-
|
55
|
-
|
56
|
-
Bitcoin::Key.new(priv_key: ((tweak.bti + private_key) % ECDSA::Group::Secp256k1.order).to_even_length_hex)
|
69
|
+
|
70
|
+
Taproot.tweak_private_key(key, merkle_root)
|
57
71
|
end
|
58
72
|
|
59
73
|
# Generate control block needed to unlock with script-path.
|
60
|
-
# @param [Bitcoin::
|
61
|
-
# @param [Integer] leaf_ver leaf version of script.
|
74
|
+
# @param [Bitcoin::Taproot::LeafNode] leaf Leaf to use for unlocking.
|
62
75
|
# @return [String] control block with binary format.
|
63
|
-
def control_block(
|
64
|
-
path = inclusion_proof(
|
76
|
+
def control_block(leaf)
|
77
|
+
path = inclusion_proof(leaf)
|
65
78
|
parity = tweak_public_key.to_point.has_even_y? ? 0 : 1
|
66
|
-
[parity + leaf_ver].pack("C") + internal_key.htb + path.join
|
79
|
+
[parity + leaf.leaf_ver].pack("C") + internal_key.htb + path.join
|
67
80
|
end
|
68
81
|
|
69
|
-
# Generate inclusion proof for +
|
70
|
-
# @param [Bitcoin::
|
71
|
-
# @param [Integer] leaf_ver (Optional) The leaf version of tapscript.
|
82
|
+
# Generate inclusion proof for +leaf+.
|
83
|
+
# @param [Bitcoin::Taproot::LeafNode] leaf The leaf node in script tree.
|
72
84
|
# @return [Array[String]] Inclusion proof.
|
73
|
-
|
74
|
-
|
75
|
-
parent_hash = leaf_hash(script, leaf_ver: leaf_ver)
|
85
|
+
# @raise [Bitcoin::Taproot::Error] If the specified +leaf+ does not exist
|
86
|
+
def inclusion_proof(leaf)
|
76
87
|
proofs = []
|
88
|
+
target_branch = branches.find{|b| b.include?(leaf)}
|
89
|
+
raise Error 'Specified leaf does not exist' unless target_branch
|
90
|
+
|
91
|
+
# flatten each branch
|
92
|
+
proofs << hash_value(target_branch.find{|b| b != leaf}) if target_branch.size == 2
|
93
|
+
parent_hash = combine_hash(target_branch)
|
94
|
+
parents = branches.map {|pair| combine_hash(pair)}
|
95
|
+
|
77
96
|
until parents.size == 1
|
78
97
|
parents = parents.each_slice(2).map do |pair|
|
79
98
|
combined = combine_hash(pair)
|
@@ -92,29 +111,26 @@ module Bitcoin
|
|
92
111
|
proofs
|
93
112
|
end
|
94
113
|
|
95
|
-
# Computes leaf hash
|
96
|
-
# @param [Bitcoin::Script] script
|
97
|
-
# @param [Integer] leaf_ver leaf version
|
98
|
-
# @@return [String] leaf hash with binary format.
|
99
|
-
def leaf_hash(script, leaf_ver: Bitcoin::TAPROOT_LEAF_TAPSCRIPT)
|
100
|
-
raise Error, 'script does not exist' unless leaves.find{ |leaf| leaf.script == script}
|
101
|
-
LeafNode.new(script, leaf_ver).leaf_hash
|
102
|
-
end
|
103
|
-
|
104
114
|
private
|
105
115
|
|
106
116
|
# Compute tweak from script tree.
|
107
117
|
# @return [String] tweak with binary format.
|
108
118
|
def tweak
|
109
|
-
|
119
|
+
Taproot.tweak(Bitcoin::Key.from_xonly_pubkey(internal_key), merkle_root)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Calculate merkle root from branches.
|
123
|
+
# @return [String] merkle root with hex format.
|
124
|
+
def merkle_root
|
125
|
+
parents = branches.map {|pair| combine_hash(pair)}
|
110
126
|
if parents.empty?
|
111
127
|
parents = ['']
|
128
|
+
elsif parents.size == 1
|
129
|
+
parents = [combine_hash(parents)]
|
112
130
|
else
|
113
131
|
parents = parents.each_slice(2).map { |pair| combine_hash(pair) } until parents.size == 1
|
114
132
|
end
|
115
|
-
|
116
|
-
raise Error, 'tweak value exceeds the curve order' if t.bti >= ECDSA::Group::Secp256k1.order
|
117
|
-
t
|
133
|
+
parents.first.bth
|
118
134
|
end
|
119
135
|
|
120
136
|
def combine_hash(pair)
|
data/lib/bitcoin/taproot.rb
CHANGED
@@ -5,5 +5,41 @@ module Bitcoin
|
|
5
5
|
|
6
6
|
autoload :LeafNode, 'bitcoin/taproot/leaf_node'
|
7
7
|
autoload :SimpleBuilder, 'bitcoin/taproot/simple_builder'
|
8
|
+
|
9
|
+
module_function
|
10
|
+
|
11
|
+
# Calculate tweak value from +internal_pubkey+ and +merkle_root+.
|
12
|
+
# @param [Bitcoin::Key] internal_key Internal key with hex format(x-only public key).
|
13
|
+
# @param [String] merkle_root Merkle root value of script tree with hex format.
|
14
|
+
# @return [String] teak value with binary format.
|
15
|
+
def tweak(internal_key, merkle_root)
|
16
|
+
raise Error, 'internal_key must be Bitcoin::Key object.' unless internal_key.is_a?(Bitcoin::Key)
|
17
|
+
|
18
|
+
merkle_root ||= ''
|
19
|
+
t = Bitcoin.tagged_hash('TapTweak', internal_key.xonly_pubkey.htb + merkle_root.htb)
|
20
|
+
raise Error, 'tweak value exceeds the curve order' if t.bti >= ECDSA::Group::Secp256k1.order
|
21
|
+
|
22
|
+
t
|
23
|
+
end
|
24
|
+
|
25
|
+
# Generate tweak public key form +internal_pubkey+ and +merkle_root+.
|
26
|
+
# @param [Bitcoin::Key] internal_key Internal key with hex format(x-only public key).
|
27
|
+
# @param [String] merkle_root Merkle root value of script tree with hex format.
|
28
|
+
# @return [Bitcoin::Key] Tweaked public key.
|
29
|
+
def tweak_public_key(internal_key, merkle_root)
|
30
|
+
t = tweak(internal_key, merkle_root)
|
31
|
+
key = Bitcoin::Key.new(priv_key: t.bth, key_type: Key::TYPES[:compressed])
|
32
|
+
Bitcoin::Key.from_point(key.to_point + internal_key.to_point)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Generate tweak private key
|
36
|
+
#
|
37
|
+
def tweak_private_key(internal_private_key, merkle_root)
|
38
|
+
p = internal_private_key.to_point
|
39
|
+
private_key = p.has_even_y? ? internal_private_key.priv_key.to_i(16) :
|
40
|
+
ECDSA::Group::Secp256k1.order - internal_private_key.priv_key.to_i(16)
|
41
|
+
t = tweak(internal_private_key, merkle_root)
|
42
|
+
Bitcoin::Key.new(priv_key: ((t.bti + private_key) % ECDSA::Group::Secp256k1.order).to_even_length_hex)
|
43
|
+
end
|
8
44
|
end
|
9
45
|
end
|
data/lib/bitcoin/version.rb
CHANGED
data/lib/bitcoin.rb
CHANGED
@@ -194,32 +194,6 @@ module Bitcoin
|
|
194
194
|
|
195
195
|
end
|
196
196
|
|
197
|
-
class ::Object
|
198
|
-
|
199
|
-
def build_json
|
200
|
-
if self.is_a?(Array)
|
201
|
-
"[#{self.map{|o|o.to_h.to_json}.join(',')}]"
|
202
|
-
else
|
203
|
-
to_h.to_json
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
def to_h
|
208
|
-
return self if self.is_a?(String)
|
209
|
-
instance_variables.inject({}) do |result, var|
|
210
|
-
key = var.to_s
|
211
|
-
key.slice!(0) if key.start_with?('@')
|
212
|
-
value = instance_variable_get(var)
|
213
|
-
if value.is_a?(Array)
|
214
|
-
result.update(key => value.map{|v|v.to_h})
|
215
|
-
else
|
216
|
-
result.update(key => value.class.to_s.start_with?("Bitcoin::") ? value.to_h : value)
|
217
|
-
end
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
end
|
222
|
-
|
223
197
|
class ::Integer
|
224
198
|
def to_even_length_hex
|
225
199
|
hex = to_s(16)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bitcoinrb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- azuchi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-05-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ecdsa
|
@@ -364,6 +364,7 @@ files:
|
|
364
364
|
- lib/bitcoin/ext/array_ext.rb
|
365
365
|
- lib/bitcoin/ext/ecdsa.rb
|
366
366
|
- lib/bitcoin/ext/json_parser.rb
|
367
|
+
- lib/bitcoin/ext/object_ext.rb
|
367
368
|
- lib/bitcoin/ext_key.rb
|
368
369
|
- lib/bitcoin/gcs_filter.rb
|
369
370
|
- lib/bitcoin/key.rb
|
@@ -450,6 +451,7 @@ files:
|
|
450
451
|
- lib/bitcoin/psbt/input.rb
|
451
452
|
- lib/bitcoin/psbt/key_origin_info.rb
|
452
453
|
- lib/bitcoin/psbt/output.rb
|
454
|
+
- lib/bitcoin/psbt/proprietary.rb
|
453
455
|
- lib/bitcoin/psbt/tx.rb
|
454
456
|
- lib/bitcoin/rpc.rb
|
455
457
|
- lib/bitcoin/rpc/bitcoin_core_client.rb
|
@@ -514,7 +516,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
514
516
|
- !ruby/object:Gem::Version
|
515
517
|
version: '0'
|
516
518
|
requirements: []
|
517
|
-
rubygems_version: 3.
|
519
|
+
rubygems_version: 3.1.4
|
518
520
|
signing_key:
|
519
521
|
specification_version: 4
|
520
522
|
summary: The implementation of Bitcoin Protocol for Ruby.
|