bitcoinrb 0.8.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|