bitcoinrb 1.12.0 → 1.13.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/.github/workflows/ruby.yml +1 -1
- data/bitcoinrb.gemspec +1 -6
- data/lib/bitcoin/bip321_uri.rb +3 -6
- data/lib/bitcoin/bip324/fs_chacha20.rb +4 -6
- data/lib/bitcoin/bip324/fs_chacha_poly1305.rb +20 -17
- data/lib/bitcoin/bip324.rb +3 -1
- data/lib/bitcoin/descriptor/checksum.rb +6 -2
- data/lib/bitcoin/descriptor/expression.rb +2 -2
- data/lib/bitcoin/descriptor/sp.rb +219 -0
- data/lib/bitcoin/descriptor.rb +20 -0
- data/lib/bitcoin/ext_key.rb +11 -7
- data/lib/bitcoin/key.rb +1 -1
- data/lib/bitcoin/message/cf_parser.rb +2 -2
- data/lib/bitcoin/message/fee_filter.rb +2 -2
- data/lib/bitcoin/message/header_and_short_ids.rb +4 -4
- data/lib/bitcoin/message/inventory.rb +1 -1
- data/lib/bitcoin/message/network_addr.rb +7 -3
- data/lib/bitcoin/message/ping.rb +5 -3
- data/lib/bitcoin/message/pong.rb +2 -2
- data/lib/bitcoin/message/send_cmpct.rb +2 -2
- data/lib/bitcoin/message/version.rb +4 -4
- data/lib/bitcoin/message.rb +12 -2
- data/lib/bitcoin/message_sign.rb +98 -38
- data/lib/bitcoin/mnemonic.rb +9 -4
- data/lib/bitcoin/psbt/input.rb +51 -33
- data/lib/bitcoin/psbt/key_origin_info.rb +2 -2
- data/lib/bitcoin/psbt/output.rb +9 -5
- data/lib/bitcoin/psbt/tx.rb +16 -10
- data/lib/bitcoin/psbt.rb +1 -1
- data/lib/bitcoin/rpc.rb +0 -2
- data/lib/bitcoin/script/script.rb +14 -4
- data/lib/bitcoin/secp256k1/rfc6979.rb +6 -3
- data/lib/bitcoin/sighash_generator.rb +2 -2
- data/lib/bitcoin/silent_payment/output.rb +46 -0
- data/lib/bitcoin/silent_payment.rb +113 -12
- data/lib/bitcoin/taproot/control_block.rb +1 -0
- data/lib/bitcoin/tx.rb +8 -6
- data/lib/bitcoin/tx_out.rb +2 -2
- data/lib/bitcoin/util.rb +5 -5
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet/account.rb +2 -2
- data/lib/bitcoin/wallet/db.rb +3 -3
- data/lib/bitcoin/wallet/master_key.rb +2 -2
- data/lib/bitcoin/wallet/utxo.rb +2 -2
- data/lib/bitcoin.rb +1 -6
- metadata +18 -108
- data/exe/bitcoinrb-cli +0 -5
- data/exe/bitcoinrbd +0 -46
- data/lib/bitcoin/network/connection.rb +0 -73
- data/lib/bitcoin/network/message_handler.rb +0 -243
- data/lib/bitcoin/network/peer.rb +0 -228
- data/lib/bitcoin/network/peer_discovery.rb +0 -42
- data/lib/bitcoin/network/pool.rb +0 -135
- data/lib/bitcoin/network.rb +0 -13
- data/lib/bitcoin/node/cli.rb +0 -122
- data/lib/bitcoin/node/configuration.rb +0 -40
- data/lib/bitcoin/node/spv.rb +0 -87
- data/lib/bitcoin/node.rb +0 -7
- data/lib/bitcoin/rpc/http_server.rb +0 -84
- data/lib/bitcoin/rpc/request_handler.rb +0 -150
- data/lib/bitcoin/store/chain_entry.rb +0 -68
- data/lib/bitcoin/store/db/level_db.rb +0 -98
- data/lib/bitcoin/store/db.rb +0 -9
- data/lib/bitcoin/store/spv_chain.rb +0 -101
- data/lib/bitcoin/store/utxo_db.rb +0 -226
- data/lib/bitcoin/store.rb +0 -12
data/lib/bitcoin/message/ping.rb
CHANGED
|
@@ -14,15 +14,17 @@ module Bitcoin
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def self.parse_from_payload(payload)
|
|
17
|
-
new(payload.unpack1('Q'))
|
|
17
|
+
new(payload.unpack1('Q<'))
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def to_payload
|
|
21
|
-
[nonce].pack('Q')
|
|
21
|
+
nonce ? [nonce].pack('Q<') : ''
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
# Generate pong message as a response. Return nil if the ping has no nonce
|
|
25
|
+
# (sent by a node before BIP-31), since such a ping does not expect a pong.
|
|
24
26
|
def to_response
|
|
25
|
-
Pong.new(nonce)
|
|
27
|
+
Pong.new(nonce) if nonce
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
end
|
data/lib/bitcoin/message/pong.rb
CHANGED
|
@@ -22,12 +22,12 @@ module Bitcoin
|
|
|
22
22
|
def self.parse_from_payload(payload)
|
|
23
23
|
buf = StringIO.new(payload)
|
|
24
24
|
mode = buf.read(1).unpack1('c')
|
|
25
|
-
version = buf.read(8).unpack1('Q')
|
|
25
|
+
version = buf.read(8).unpack1('Q<')
|
|
26
26
|
new(mode, version)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def to_payload
|
|
30
|
-
[mode, version].pack('cQ')
|
|
30
|
+
[mode, version].pack('cQ<')
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def high?
|
|
@@ -32,7 +32,7 @@ module Bitcoin
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def self.parse_from_payload(payload)
|
|
35
|
-
version, services, timestamp, local_addr, remote_addr, nonce, rest = payload.unpack('
|
|
35
|
+
version, services, timestamp, local_addr, remote_addr, nonce, rest = payload.unpack('VQ<Q<a26a26Q<a*')
|
|
36
36
|
v = new
|
|
37
37
|
v.version = version
|
|
38
38
|
v.services = services
|
|
@@ -50,10 +50,10 @@ module Bitcoin
|
|
|
50
50
|
|
|
51
51
|
def to_payload
|
|
52
52
|
[
|
|
53
|
-
[version, services, timestamp].pack('
|
|
53
|
+
[version, services, timestamp].pack('VQ<Q<'),
|
|
54
54
|
local_addr.to_payload(true),
|
|
55
55
|
remote_addr.to_payload(true),
|
|
56
|
-
[nonce].pack('Q'),
|
|
56
|
+
[nonce].pack('Q<'),
|
|
57
57
|
pack_var_string(user_agent),
|
|
58
58
|
[start_height].pack('V'),
|
|
59
59
|
pack_boolean(relay)
|
|
@@ -61,7 +61,7 @@ module Bitcoin
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
def unpack_relay_field(payload)
|
|
64
|
-
( version >= 70001 && payload ) ? unpack_boolean(payload) : [ true, nil ]
|
|
64
|
+
( version >= 70001 && payload && !payload.empty? ) ? unpack_boolean(payload) : [ true, nil ]
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
# Check whether +service_flag+ support this version.
|
data/lib/bitcoin/message.rb
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
module Bitcoin
|
|
2
2
|
module Message
|
|
3
3
|
|
|
4
|
-
class Error < StandardError; end
|
|
5
|
-
|
|
6
4
|
autoload :Base, 'bitcoin/message/base'
|
|
7
5
|
autoload :Inventory, 'bitcoin/message/inventory'
|
|
8
6
|
autoload :InventoriesParser, 'bitcoin/message/inventories_parser'
|
|
@@ -107,6 +105,8 @@ module Bitcoin
|
|
|
107
105
|
Pong.parse_from_payload(payload)
|
|
108
106
|
when GetHeaders::COMMAND
|
|
109
107
|
GetHeaders.parse_from_payload(payload)
|
|
108
|
+
when GetBlocks::COMMAND
|
|
109
|
+
GetBlocks.parse_from_payload(payload)
|
|
110
110
|
when Headers::COMMAND
|
|
111
111
|
Headers.parse_from_payload(payload)
|
|
112
112
|
when Block::COMMAND
|
|
@@ -125,8 +125,18 @@ module Bitcoin
|
|
|
125
125
|
Inv.parse_from_payload(payload)
|
|
126
126
|
when MerkleBlock::COMMAND
|
|
127
127
|
MerkleBlock.parse_from_payload(payload)
|
|
128
|
+
when FilterLoad::COMMAND
|
|
129
|
+
FilterLoad.parse_from_payload(payload)
|
|
130
|
+
when FilterAdd::COMMAND
|
|
131
|
+
FilterAdd.parse_from_payload(payload)
|
|
132
|
+
when FilterClear::COMMAND
|
|
133
|
+
FilterClear.new
|
|
128
134
|
when CmpctBlock::COMMAND
|
|
129
135
|
CmpctBlock.parse_from_payload(payload)
|
|
136
|
+
when GetBlockTxn::COMMAND
|
|
137
|
+
GetBlockTxn.parse_from_payload(payload)
|
|
138
|
+
when BlockTxn::COMMAND
|
|
139
|
+
BlockTxn.parse_from_payload(payload)
|
|
130
140
|
when GetData::COMMAND
|
|
131
141
|
GetData.parse_from_payload(payload)
|
|
132
142
|
when GetCFHeaders::COMMAND
|
data/lib/bitcoin/message_sign.rb
CHANGED
|
@@ -10,6 +10,22 @@ module Bitcoin
|
|
|
10
10
|
FORMAT_SIMPLE = :simple
|
|
11
11
|
FORMAT_FULL = :full
|
|
12
12
|
|
|
13
|
+
# Prefix
|
|
14
|
+
SIGNATURE_PREFIX_SIMPLE = 'smp'
|
|
15
|
+
SIGNATURE_PREFIX_FULL = 'ful'
|
|
16
|
+
SIGNATURE_PREFIX_POF = 'pof'
|
|
17
|
+
|
|
18
|
+
# BIP-322 Required rules
|
|
19
|
+
BIP322_VERIFY_FLAGS = [
|
|
20
|
+
SCRIPT_VERIFY_P2SH, SCRIPT_VERIFY_DERSIG, SCRIPT_VERIFY_STRICTENC,
|
|
21
|
+
SCRIPT_VERIFY_LOW_S, SCRIPT_VERIFY_NULLFAIL, SCRIPT_VERIFY_MINIMALDATA,
|
|
22
|
+
SCRIPT_VERIFY_CLEANSTACK, SCRIPT_VERIFY_MINIMALIF,
|
|
23
|
+
SCRIPT_VERIFY_CONST_SCRIPTCODE,
|
|
24
|
+
SCRIPT_VERIFY_WITNESS, SCRIPT_VERIFY_TAPROOT,
|
|
25
|
+
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS,
|
|
26
|
+
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM
|
|
27
|
+
].inject(:|)
|
|
28
|
+
|
|
13
29
|
# Sign a message.
|
|
14
30
|
# @param [Bitcoin::Key] key Private key to sign.
|
|
15
31
|
# @param [String] message The message to be signed.
|
|
@@ -20,28 +36,38 @@ module Bitcoin
|
|
|
20
36
|
def sign_message(key, message, prefix: Bitcoin.chain_params.message_magic, format: FORMAT_LEGACY, address: nil)
|
|
21
37
|
validate_format!(format)
|
|
22
38
|
digest = message_hash(message, prefix: prefix, legacy: format == FORMAT_LEGACY)
|
|
39
|
+
prefix_marker = ''
|
|
23
40
|
sig = case format
|
|
24
41
|
when FORMAT_LEGACY
|
|
25
42
|
key.sign_compact(digest)
|
|
26
43
|
else
|
|
27
44
|
validate_address!(address)
|
|
28
45
|
addr = Bitcoin::Script.parse_from_addr(address)
|
|
29
|
-
sig_ver, algo = if addr.p2wpkh?
|
|
46
|
+
sig_ver, algo = if addr.p2wpkh? || addr.p2wsh?
|
|
30
47
|
[:witness_v0, :ecdsa]
|
|
31
48
|
elsif addr.p2tr?
|
|
32
49
|
[:taproot, :schnorr]
|
|
33
50
|
else
|
|
34
|
-
|
|
51
|
+
|
|
35
52
|
end
|
|
36
53
|
tx = to_sign_tx(digest, address)
|
|
37
|
-
prev_out = Bitcoin::TxOut.new(script_pubkey: addr)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
54
|
+
prev_out = Bitcoin::TxOut.new(script_pubkey: addr, value: 0)
|
|
55
|
+
if addr.p2tr?
|
|
56
|
+
sighash = tx.sighash_for_input(0, addr, sig_version: :taproot, prevouts: [prev_out], hash_type: Bitcoin::SIGHASH_TYPE[:default])
|
|
57
|
+
tweaked = Bitcoin::Taproot.tweak_private_key(key, '')
|
|
58
|
+
tx.in[0].script_witness.stack << tweaked.sign(sighash, algo: :schnorr)
|
|
59
|
+
elsif addr.p2wpkh? || addr.p2wsh?
|
|
60
|
+
sighash = tx.sighash_for_input(0, addr, sig_version: :witness_v0, amount: 0, prevouts: [prev_out])
|
|
61
|
+
ecdsa = key.sign(sighash, algo: :ecdsa) + [Bitcoin::SIGHASH_TYPE[:all]].pack('C')
|
|
62
|
+
tx.in[0].script_witness.stack << ecdsa
|
|
63
|
+
tx.in[0].script_witness.stack << key.pubkey.htb
|
|
64
|
+
else
|
|
65
|
+
raise ArgumentError, "#{address} dose not supported."
|
|
66
|
+
end
|
|
67
|
+
prefix_marker = format == FORMAT_SIMPLE ? SIGNATURE_PREFIX_SIMPLE : SIGNATURE_PREFIX_FULL
|
|
42
68
|
format == FORMAT_SIMPLE ? tx.in[0].script_witness.to_payload : tx.to_payload
|
|
43
69
|
end
|
|
44
|
-
Base64.strict_encode64(sig)
|
|
70
|
+
prefix_marker + Base64.strict_encode64(sig)
|
|
45
71
|
end
|
|
46
72
|
|
|
47
73
|
# Verify a signed message.
|
|
@@ -51,41 +77,41 @@ module Bitcoin
|
|
|
51
77
|
# @return [Boolean] Verification result.
|
|
52
78
|
def verify_message(address, signature, message, prefix: Bitcoin.chain_params.message_magic)
|
|
53
79
|
addr_script = Bitcoin::Script.parse_from_addr(address)
|
|
80
|
+
variant, body = case signature
|
|
81
|
+
when ''
|
|
82
|
+
raise ArgumentError, 'signature too short'
|
|
83
|
+
when /\A#{SIGNATURE_PREFIX_SIMPLE}/
|
|
84
|
+
[:simple, signature[3..]]
|
|
85
|
+
when /\A#{SIGNATURE_PREFIX_FULL}/
|
|
86
|
+
[:full, signature[3..]]
|
|
87
|
+
when /\A#{SIGNATURE_PREFIX_POF}/
|
|
88
|
+
[:pof, signature[3..]]
|
|
89
|
+
else
|
|
90
|
+
[:fallback, signature]
|
|
91
|
+
end
|
|
54
92
|
begin
|
|
55
|
-
|
|
93
|
+
payload = Base64.strict_decode64(body)
|
|
56
94
|
rescue ArgumentError
|
|
57
95
|
raise ArgumentError, 'Invalid signature'
|
|
58
96
|
end
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
end
|
|
68
|
-
elsif addr_script.witness_program?
|
|
69
|
-
# BIP322 verification
|
|
70
|
-
digest = message_hash(message, prefix: prefix, legacy: false)
|
|
97
|
+
|
|
98
|
+
case variant
|
|
99
|
+
when :simple, :fallback
|
|
100
|
+
return verify_legacy(address, payload, message, prefix: prefix) if addr_script.p2pkh?
|
|
101
|
+
raise ArgumentError, 'simple format not supported for this address' unless
|
|
102
|
+
addr_script.p2wpkh? || addr_script.p2wsh? || addr_script.p2tr?
|
|
103
|
+
verify_simple(addr_script, payload, message, prefix: prefix)
|
|
104
|
+
when :full
|
|
71
105
|
begin
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
to_spend = to_spend_tx(digest, address)
|
|
76
|
-
return false unless tx.in[0].out_point.tx_hash == to_spend.tx_hash
|
|
77
|
-
rescue Exception
|
|
78
|
-
# Simple
|
|
79
|
-
tx = to_sign_tx(digest, address)
|
|
80
|
-
tx.in[0].script_witness = Bitcoin::ScriptWitness.parse_from_payload(sig)
|
|
106
|
+
tx = Bitcoin::Tx.parse_from_payload(payload)
|
|
107
|
+
rescue StandardError
|
|
108
|
+
raise ArgumentError, 'error parsing signature as full variant'
|
|
81
109
|
end
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
interpreter = Bitcoin::ScriptInterpreter.new(flags: flags, checker: Bitcoin::TxChecker.new(tx: tx, input_index: 0, prevouts: [tx_out]))
|
|
86
|
-
interpreter.verify_script(Bitcoin::Script.new, script_pubkey, tx.in[0].script_witness)
|
|
110
|
+
verify_full(addr_script, tx, message, prefix: prefix)
|
|
111
|
+
when :pof
|
|
112
|
+
raise NotImplementedError, 'proof of funds variant is not supported'
|
|
87
113
|
else
|
|
88
|
-
raise ArgumentError, "
|
|
114
|
+
raise ArgumentError, "unknown signature variant: #{variant.inspect}"
|
|
89
115
|
end
|
|
90
116
|
end
|
|
91
117
|
|
|
@@ -99,8 +125,7 @@ module Bitcoin
|
|
|
99
125
|
end
|
|
100
126
|
|
|
101
127
|
def validate_address!(address)
|
|
102
|
-
|
|
103
|
-
raise ArgumentError, 'This address unsupported' if script.p2sh? || script.p2wsh?
|
|
128
|
+
Bitcoin::Script.parse_from_addr(address)
|
|
104
129
|
end
|
|
105
130
|
|
|
106
131
|
def validate_format!(format)
|
|
@@ -110,6 +135,7 @@ module Bitcoin
|
|
|
110
135
|
end
|
|
111
136
|
|
|
112
137
|
def validate_to_sign_tx!(tx)
|
|
138
|
+
raise ArgumentError, "Invalid version." unless [0, 2].include?(tx.version)
|
|
113
139
|
raise ArgumentError, "Multiple inputs (proof of funds) are not supported." unless tx.in.length == 1
|
|
114
140
|
raise ArgumentError, "vin[0].prevout.n must be 0." unless tx.in[0].out_point.index == 0
|
|
115
141
|
raise ArgumentError, "Multiple outputs are not supported." unless tx.out.length == 1
|
|
@@ -140,8 +166,42 @@ module Bitcoin
|
|
|
140
166
|
tx
|
|
141
167
|
end
|
|
142
168
|
|
|
169
|
+
def verify_simple(addr_script, witness_payload, message, prefix:)
|
|
170
|
+
digest = message_hash(message, prefix: prefix, legacy: false)
|
|
171
|
+
tx = to_sign_tx(digest, addr_script.to_addr)
|
|
172
|
+
tx.in[0].script_witness = Bitcoin::ScriptWitness.parse_from_payload(witness_payload)
|
|
173
|
+
run_interpreter(tx, addr_script)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def verify_full(addr_script, tx, message, prefix:)
|
|
177
|
+
digest = message_hash(message, prefix: prefix, legacy: false)
|
|
178
|
+
to_spend = to_spend_tx(digest, addr_script.to_addr)
|
|
179
|
+
validate_to_sign_tx!(tx)
|
|
180
|
+
return false unless tx.in[0].out_point.tx_hash == to_spend.tx_hash
|
|
181
|
+
run_interpreter(tx, addr_script)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def run_interpreter(tx, script_pubkey)
|
|
185
|
+
tx_out = Bitcoin::TxOut.new(script_pubkey: script_pubkey)
|
|
186
|
+
checker = Bitcoin::TxChecker.new(tx: tx, input_index: 0, prevouts: [tx_out])
|
|
187
|
+
interpreter = Bitcoin::ScriptInterpreter.new(flags: BIP322_VERIFY_FLAGS, checker: checker)
|
|
188
|
+
interpreter.verify_script(tx.in[0].script_sig, script_pubkey, tx.in[0].script_witness)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def verify_legacy(address, sig_bytes, message, prefix:)
|
|
192
|
+
pubkey = Bitcoin::Key.recover_compact(message_hash(message, prefix: prefix, legacy: true), sig_bytes)
|
|
193
|
+
return false unless pubkey
|
|
194
|
+
pubkey.to_p2pkh == address
|
|
195
|
+
rescue StandardError
|
|
196
|
+
false
|
|
197
|
+
end
|
|
198
|
+
|
|
143
199
|
private_class_method :validate_address!
|
|
144
200
|
private_class_method :validate_format!
|
|
145
201
|
private_class_method :validate_to_sign_tx!
|
|
202
|
+
private_class_method :run_interpreter
|
|
203
|
+
private_class_method :verify_simple
|
|
204
|
+
private_class_method :verify_full
|
|
205
|
+
private_class_method :verify_legacy
|
|
146
206
|
end
|
|
147
207
|
end
|
data/lib/bitcoin/mnemonic.rb
CHANGED
|
@@ -24,7 +24,7 @@ module Bitcoin
|
|
|
24
24
|
def to_entropy(words)
|
|
25
25
|
word_master = load_words
|
|
26
26
|
mnemonic = words.map do |w|
|
|
27
|
-
index = word_master.index(w.downcase)
|
|
27
|
+
index = word_master.index(normalize(w).downcase)
|
|
28
28
|
raise IndexError, 'word not found in words list.' unless index
|
|
29
29
|
index.to_s(2).rjust(11, '0')
|
|
30
30
|
end.join
|
|
@@ -53,8 +53,8 @@ module Bitcoin
|
|
|
53
53
|
# @return [String] seed
|
|
54
54
|
def to_seed(mnemonic, passphrase: '')
|
|
55
55
|
to_entropy(mnemonic)
|
|
56
|
-
OpenSSL::PKCS5.pbkdf2_hmac(mnemonic.join(' ').downcase,
|
|
57
|
-
'mnemonic' + passphrase, 2048, 64, OpenSSL::Digest::SHA512.new).bth
|
|
56
|
+
OpenSSL::PKCS5.pbkdf2_hmac(normalize(mnemonic.join(' ')).downcase,
|
|
57
|
+
'mnemonic' + normalize(passphrase), 2048, 64, OpenSSL::Digest::SHA512.new).bth
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
# calculate entropy checksum
|
|
@@ -69,7 +69,12 @@ module Bitcoin
|
|
|
69
69
|
|
|
70
70
|
# load word list contents
|
|
71
71
|
def load_words
|
|
72
|
-
File.readlines("#{WORD_DIR}/#{language}.txt").map(
|
|
72
|
+
File.readlines("#{WORD_DIR}/#{language}.txt", encoding: Encoding::UTF_8).map{|w| normalize(w.strip)}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# BIP-39 requires that mnemonic sentences and passphrases are normalized using NFKD.
|
|
76
|
+
def normalize(str)
|
|
77
|
+
str.unicode_normalize(:nfkd)
|
|
73
78
|
end
|
|
74
79
|
|
|
75
80
|
end
|
data/lib/bitcoin/psbt/input.rb
CHANGED
|
@@ -55,8 +55,9 @@ module Bitcoin
|
|
|
55
55
|
found_sep = true
|
|
56
56
|
break
|
|
57
57
|
end
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
key_start = buf.pos
|
|
59
|
+
key_type = Bitcoin.unpack_var_int_from_io(buf)
|
|
60
|
+
key = buf.read(key_len - (buf.pos - key_start))
|
|
60
61
|
value = buf.read(Bitcoin.unpack_var_int_from_io(buf))
|
|
61
62
|
|
|
62
63
|
case key_type
|
|
@@ -79,7 +80,8 @@ module Bitcoin
|
|
|
79
80
|
when PSBT_IN_TYPES[:sighash]
|
|
80
81
|
raise ArgumentError, 'Invalid input sighash type typed key.' unless key_len == 1
|
|
81
82
|
raise ArgumentError, 'Duplicate Key, input sighash type already provided.' if input.sighash_type
|
|
82
|
-
input.
|
|
83
|
+
raise ArgumentError, 'Invalid input sighash type value.' unless value.bytesize == 4
|
|
84
|
+
input.sighash_type = value.unpack1('V')
|
|
83
85
|
when PSBT_IN_TYPES[:redeem_script]
|
|
84
86
|
raise ArgumentError, 'Invalid redeemscript typed key.' unless key_len == 1
|
|
85
87
|
raise ArgumentError, 'Duplicate Key, input redeemScript already provided.' if input.redeem_script
|
|
@@ -117,10 +119,12 @@ module Bitcoin
|
|
|
117
119
|
raise ArgumentError, 'Duplicate Key, input hash256 preimage already provided' if input.hash256_preimages[key.bth]
|
|
118
120
|
input.hash256_preimages[key.bth] = value.bth
|
|
119
121
|
when PSBT_IN_TYPES[:proprietary]
|
|
120
|
-
|
|
121
|
-
input.proprietaries
|
|
122
|
+
proprietary = Proprietary.new(key, value)
|
|
123
|
+
raise ArgumentError, 'Duplicate Key, key for proprietary value already provided.' if input.proprietaries.any?{|p| p.key == proprietary.key}
|
|
124
|
+
input.proprietaries << proprietary
|
|
122
125
|
when PSBT_IN_TYPES[:tap_key_sig]
|
|
123
126
|
raise ArgumentError, 'Size of key was not the expected size for the type tap key sig' unless key_len == 1
|
|
127
|
+
raise ArgumentError, 'Duplicate Key, input tap key sig already provided.' if input.tap_key_sig
|
|
124
128
|
raise ArgumentError, 'Invalid schnorr signature size for the type tap key sig' unless [64, 65].include?(value.bytesize)
|
|
125
129
|
input.tap_key_sig = value.bth
|
|
126
130
|
when PSBT_IN_TYPES[:tap_script_sig]
|
|
@@ -142,14 +146,16 @@ module Bitcoin
|
|
|
142
146
|
input.tap_bip32_derivations[key.bth] = value.bth
|
|
143
147
|
when PSBT_IN_TYPES[:tap_internal_key]
|
|
144
148
|
raise ArgumentError, 'Size of key was not the expected size for the type tap internal key' unless key_len == 1
|
|
149
|
+
raise ArgumentError, 'Duplicate Key, input tap internal key already provided.' if input.tap_internal_key
|
|
145
150
|
raise ArgumentError, 'Invalid x-only public key size for the type tap internal key' unless value.bytesize == X_ONLY_PUBKEY_SIZE
|
|
146
151
|
input.tap_internal_key = value.bth
|
|
147
152
|
when PSBT_IN_TYPES[:tap_merkle_root]
|
|
148
153
|
raise ArgumentError, 'Size of key was not the expected size for the type tap merkle root' unless key_len == 1
|
|
154
|
+
raise ArgumentError, 'Duplicate Key, input tap merkle root already provided.' if input.tap_merkle_root
|
|
149
155
|
raise ArgumentError, 'Invalid merkle root hash size for the type tap merkle root' unless value.bytesize == 32
|
|
150
156
|
input.tap_merkle_root = value.bth
|
|
151
157
|
else
|
|
152
|
-
unknown_key = (
|
|
158
|
+
unknown_key = (Bitcoin.pack_var_int(key_type) + key).bth
|
|
153
159
|
raise ArgumentError, 'Duplicate Key, key for unknown value already provided.' if input.unknowns[unknown_key]
|
|
154
160
|
input.unknowns[unknown_key] = value
|
|
155
161
|
end
|
|
@@ -163,23 +169,21 @@ module Bitcoin
|
|
|
163
169
|
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:non_witness_utxo], value:
|
|
164
170
|
(witness_utxo && valid_witness_input?) ? non_witness_utxo.serialize_old_format : non_witness_utxo.to_payload) if non_witness_utxo
|
|
165
171
|
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:witness_utxo], value: witness_utxo.to_payload) if witness_utxo
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_merkle_root], value: tap_merkle_root.htb) if tap_merkle_root
|
|
182
|
-
end
|
|
172
|
+
payload << partial_sigs.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:partial_sig], key: k.htb, value: v)}.join
|
|
173
|
+
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:sighash], value: [sighash_type].pack('V')) if sighash_type
|
|
174
|
+
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:redeem_script], value: redeem_script.to_payload) if redeem_script
|
|
175
|
+
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:witness_script], value: witness_script.to_payload) if witness_script
|
|
176
|
+
payload << hd_key_paths.values.map(&:to_payload).join
|
|
177
|
+
payload << ripemd160_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:ripemd160], key: k.htb, value: v.htb)}.join
|
|
178
|
+
payload << sha256_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:sha256], key: k.htb, value: v.htb)}.join
|
|
179
|
+
payload << hash160_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:hash160], key: k.htb, value: v.htb)}.join
|
|
180
|
+
payload << hash256_preimages.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:hash256], key: k.htb, value: v.htb)}.join
|
|
181
|
+
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_key_sig], value: tap_key_sig.htb) if tap_key_sig
|
|
182
|
+
payload << tap_script_sigs.map{|k, v| PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_script_sig], key: k.htb, value: v.htb)}.join
|
|
183
|
+
payload << tap_leaf_scripts.map{|k, v| PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_leaf_script], key: k.to_payload, value: v.htb)}.join
|
|
184
|
+
payload << tap_bip32_derivations.map{|k, v| PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_bip32_derivation], key: k.htb, value: v.htb)}.join
|
|
185
|
+
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_internal_key], value: tap_internal_key.htb) if tap_internal_key
|
|
186
|
+
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:tap_merkle_root], value: tap_merkle_root.htb) if tap_merkle_root
|
|
183
187
|
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:script_sig], value: final_script_sig.to_payload) if final_script_sig
|
|
184
188
|
payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:script_witness], value: final_script_witness.to_payload) if final_script_witness
|
|
185
189
|
payload << proprietaries.map(&:to_payload).join
|
|
@@ -191,10 +195,13 @@ module Bitcoin
|
|
|
191
195
|
# Check whether input's scriptPubkey is correct witness.
|
|
192
196
|
# @return [Boolean]
|
|
193
197
|
def valid_witness_input?
|
|
194
|
-
return
|
|
195
|
-
return true if witness_utxo
|
|
198
|
+
return false unless witness_utxo
|
|
199
|
+
return true if witness_utxo.script_pubkey.p2wpkh? # P2WPKH
|
|
200
|
+
# P2WSH. The scriptPubkey commits to the witness script directly, so a redeem script is not used.
|
|
201
|
+
return true if witness_utxo.script_pubkey.p2wsh? && witness_script &&
|
|
202
|
+
Bitcoin::Script.to_p2wsh(witness_script) == witness_utxo.script_pubkey
|
|
196
203
|
# segwit nested in P2SH
|
|
197
|
-
if witness_utxo
|
|
204
|
+
if witness_utxo.script_pubkey.p2sh? && redeem_script&.witness_program? && redeem_script.to_p2sh == witness_utxo.script_pubkey
|
|
198
205
|
return true if redeem_script.p2wpkh?# nested p2wpkh
|
|
199
206
|
return true if witness_script&.to_sha256 == redeem_script.witness_data[1].bth # nested p2wsh
|
|
200
207
|
end
|
|
@@ -235,19 +242,19 @@ module Bitcoin
|
|
|
235
242
|
# @return [Bitcoin::PSBT::Input] combined object.
|
|
236
243
|
def merge(psbi)
|
|
237
244
|
raise ArgumentError, 'The argument psbt must be an instance of Bitcoin::PSBT::Input.' unless psbi.is_a?(Bitcoin::PSBT::Input)
|
|
238
|
-
raise ArgumentError, 'The Partially Signed Input\'s non_witness_utxo are different.' unless non_witness_utxo
|
|
239
|
-
raise ArgumentError, 'The Partially Signed Input\'s witness_utxo are different.' unless witness_utxo
|
|
245
|
+
raise ArgumentError, 'The Partially Signed Input\'s non_witness_utxo are different.' unless same_field?(non_witness_utxo, psbi.non_witness_utxo)
|
|
246
|
+
raise ArgumentError, 'The Partially Signed Input\'s witness_utxo are different.' unless same_field?(witness_utxo, psbi.witness_utxo)
|
|
240
247
|
raise ArgumentError, 'The Partially Signed Input\'s sighash_type are different.' if sighash_type && psbi.sighash_type && sighash_type != psbi.sighash_type
|
|
241
|
-
raise ArgumentError, 'The Partially Signed Input\'s redeem_script are different.' unless redeem_script
|
|
242
|
-
raise ArgumentError, 'The Partially Signed Input\'s witness_script are different.' unless witness_script
|
|
248
|
+
raise ArgumentError, 'The Partially Signed Input\'s redeem_script are different.' unless same_field?(redeem_script, psbi.redeem_script)
|
|
249
|
+
raise ArgumentError, 'The Partially Signed Input\'s witness_script are different.' unless same_field?(witness_script, psbi.witness_script)
|
|
243
250
|
combined = Bitcoin::PSBT::Input.new(non_witness_utxo: non_witness_utxo, witness_utxo: witness_utxo)
|
|
244
251
|
combined.unknowns = Hash[unknowns.merge(psbi.unknowns).sort]
|
|
245
252
|
combined.redeem_script = redeem_script
|
|
246
253
|
combined.witness_script = witness_script
|
|
247
254
|
combined.sighash_type = sighash_type
|
|
248
255
|
sigs = Hash[partial_sigs.merge(psbi.partial_sigs)]
|
|
249
|
-
redeem_script.get_multisig_pubkeys.each{|pubkey|combined.partial_sigs[pubkey.bth] = sigs[pubkey.bth]} if redeem_script&.multisig?
|
|
250
|
-
witness_script.get_multisig_pubkeys.each{|pubkey|combined.partial_sigs[pubkey.bth] = sigs[pubkey.bth]} if witness_script&.multisig?
|
|
256
|
+
redeem_script.get_multisig_pubkeys.each{|pubkey|combined.partial_sigs[pubkey.bth] = sigs[pubkey.bth] if sigs[pubkey.bth]} if redeem_script&.multisig?
|
|
257
|
+
witness_script.get_multisig_pubkeys.each{|pubkey|combined.partial_sigs[pubkey.bth] = sigs[pubkey.bth] if sigs[pubkey.bth]} if witness_script&.multisig?
|
|
251
258
|
combined.hd_key_paths = hd_key_paths.merge(psbi.hd_key_paths)
|
|
252
259
|
combined
|
|
253
260
|
end
|
|
@@ -288,7 +295,7 @@ module Bitcoin
|
|
|
288
295
|
h[:non_witness_utxo] = non_witness_utxo.to_h if non_witness_utxo
|
|
289
296
|
h[:witness_utxo] = witness_utxo.to_h if witness_utxo
|
|
290
297
|
h[:redeem_script] = redeem_script.to_h if redeem_script
|
|
291
|
-
h[:witness_script] = witness_script.to_h if
|
|
298
|
+
h[:witness_script] = witness_script.to_h if witness_script
|
|
292
299
|
h[:final_script_sig] = final_script_sig.to_h if final_script_sig
|
|
293
300
|
h[:final_script_witness] = final_script_witness.to_h if final_script_witness
|
|
294
301
|
h[:bip32_derivs] = hd_key_paths.values.map(&:to_h) unless hd_key_paths.empty?
|
|
@@ -309,6 +316,17 @@ module Bitcoin
|
|
|
309
316
|
h
|
|
310
317
|
end
|
|
311
318
|
|
|
319
|
+
private
|
|
320
|
+
|
|
321
|
+
# Whether +a+ and +b+ are the same field value. Unlike ==, this does not raise
|
|
322
|
+
# NoMethodError when only one of them is nil.
|
|
323
|
+
# @return [Boolean]
|
|
324
|
+
def same_field?(a, b)
|
|
325
|
+
return true if a.nil? && b.nil?
|
|
326
|
+
return false if a.nil? || b.nil?
|
|
327
|
+
a == b
|
|
328
|
+
end
|
|
329
|
+
|
|
312
330
|
end
|
|
313
331
|
|
|
314
332
|
end
|
|
@@ -15,11 +15,11 @@ module Bitcoin
|
|
|
15
15
|
|
|
16
16
|
def self.parse_from_payload(payload)
|
|
17
17
|
buf = StringIO.new(payload)
|
|
18
|
-
self.new(fingerprint: buf.read(4).bth, key_paths: buf.read.unpack('
|
|
18
|
+
self.new(fingerprint: buf.read(4).bth, key_paths: buf.read.unpack('V*'))
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def to_payload
|
|
22
|
-
fingerprint.htb + key_paths.pack('
|
|
22
|
+
fingerprint.htb + key_paths.pack('V*')
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def to_h
|
data/lib/bitcoin/psbt/output.rb
CHANGED
|
@@ -32,8 +32,9 @@ module Bitcoin
|
|
|
32
32
|
found_sep = true
|
|
33
33
|
break
|
|
34
34
|
end
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
key_start = buf.pos
|
|
36
|
+
key_type = Bitcoin.unpack_var_int_from_io(buf)
|
|
37
|
+
key = buf.read(key_len - (buf.pos - key_start))
|
|
37
38
|
value = buf.read(Bitcoin.unpack_var_int_from_io(buf))
|
|
38
39
|
case key_type
|
|
39
40
|
when PSBT_OUT_TYPES[:redeem_script]
|
|
@@ -48,21 +49,24 @@ module Bitcoin
|
|
|
48
49
|
raise ArgumentError, 'Duplicate Key, pubkey derivation path already provided' if output.hd_key_paths[key.bth]
|
|
49
50
|
output.hd_key_paths[key.bth] = Bitcoin::PSBT::HDKeyPath.new(key, Bitcoin::PSBT::KeyOriginInfo.parse_from_payload(value))
|
|
50
51
|
when PSBT_OUT_TYPES[:proprietary]
|
|
51
|
-
|
|
52
|
-
output.proprietaries
|
|
52
|
+
proprietary = Proprietary.new(key, value)
|
|
53
|
+
raise ArgumentError, 'Duplicate Key, key for proprietary value already provided.' if output.proprietaries.any?{|p| p.key == proprietary.key}
|
|
54
|
+
output.proprietaries << proprietary
|
|
53
55
|
when PSBT_OUT_TYPES[:tap_internal_key]
|
|
54
56
|
raise ArgumentError, 'Invalid output tap internal key typed key.' unless key_len == 1
|
|
57
|
+
raise ArgumentError, 'Duplicate Key, output tap internal key already provided.' if output.tap_internal_key
|
|
55
58
|
raise ArgumentError, 'Invalid x-only public key size for the type tap internal key' unless value.bytesize == X_ONLY_PUBKEY_SIZE
|
|
56
59
|
output.tap_internal_key = value.bth
|
|
57
60
|
when PSBT_OUT_TYPES[:tap_tree]
|
|
58
61
|
raise ArgumentError, 'Invalid output tap tree typed key.' unless key_len == 1
|
|
62
|
+
raise ArgumentError, 'Duplicate Key, output tap tree already provided.' if output.tap_tree
|
|
59
63
|
output.tap_tree = value.bth # TODO implement builder.
|
|
60
64
|
when PSBT_OUT_TYPES[:tap_bip32_derivation]
|
|
61
65
|
raise ArgumentError, 'Duplicate Key, key for tap bip32 derivation value already provided.' if output.tap_bip32_derivations[key.bth]
|
|
62
66
|
raise ArgumentError, 'Size of key was not the expected size for the type tap bip32 derivation' unless key.bytesize == X_ONLY_PUBKEY_SIZE
|
|
63
67
|
output.tap_bip32_derivations[key.bth] = value.bth
|
|
64
68
|
else
|
|
65
|
-
unknown_key = (
|
|
69
|
+
unknown_key = (Bitcoin.pack_var_int(key_type) + key).bth
|
|
66
70
|
raise ArgumentError, 'Duplicate Key, key for unknown value already provided' if output.unknowns[unknown_key]
|
|
67
71
|
output.unknowns[unknown_key] = value
|
|
68
72
|
end
|
data/lib/bitcoin/psbt/tx.rb
CHANGED
|
@@ -68,8 +68,9 @@ module Bitcoin
|
|
|
68
68
|
found_sep = true
|
|
69
69
|
break
|
|
70
70
|
end
|
|
71
|
+
key_start = buf.pos
|
|
71
72
|
key_type = Bitcoin.unpack_var_int_from_io(buf)
|
|
72
|
-
key = buf.read(key_len -
|
|
73
|
+
key = buf.read(key_len - (buf.pos - key_start))
|
|
73
74
|
value = buf.read(Bitcoin.unpack_var_int_from_io(buf))
|
|
74
75
|
|
|
75
76
|
case key_type
|
|
@@ -89,14 +90,19 @@ module Bitcoin
|
|
|
89
90
|
raise ArgumentError, "global xpub's depth and the number of indexes not matched." unless xpub.depth == info.key_paths.size
|
|
90
91
|
partial_tx.xpubs << Bitcoin::PSBT::GlobalXpub.new(xpub, info)
|
|
91
92
|
when PSBT_GLOBAL_TYPES[:ver]
|
|
93
|
+
raise ArgumentError, 'Invalid global version typed key.' unless key_len == 1
|
|
94
|
+
raise ArgumentError, 'Duplicate Key, global version already provided.' if partial_tx.version_number
|
|
95
|
+
raise ArgumentError, 'Invalid global version value.' unless value.bytesize == 4
|
|
92
96
|
partial_tx.version_number = value.unpack1('V')
|
|
93
97
|
raise ArgumentError, "An unsupported version was detected." if SUPPORT_VERSION < partial_tx.version_number
|
|
94
98
|
when PSBT_GLOBAL_TYPES[:proprietary]
|
|
95
|
-
|
|
96
|
-
partial_tx.proprietaries
|
|
99
|
+
proprietary = Proprietary.new(key, value)
|
|
100
|
+
raise ArgumentError, 'Duplicate Key, key for proprietary value already provided.' if partial_tx.proprietaries.any?{|p| p.key == proprietary.key}
|
|
101
|
+
partial_tx.proprietaries << proprietary
|
|
97
102
|
else
|
|
98
|
-
|
|
99
|
-
|
|
103
|
+
unknown_key = (Bitcoin.pack_var_int(key_type) + key).bth
|
|
104
|
+
raise ArgumentError, 'Duplicate Key, key for unknown value already provided.' if partial_tx.unknowns[unknown_key]
|
|
105
|
+
partial_tx.unknowns[unknown_key] = value
|
|
100
106
|
end
|
|
101
107
|
end
|
|
102
108
|
|
|
@@ -206,7 +212,6 @@ module Bitcoin
|
|
|
206
212
|
inputs[i].redeem_script = redeem_script if redeem_script
|
|
207
213
|
inputs[i].witness_script = witness_script if witness_script
|
|
208
214
|
inputs[i].hd_key_paths = hd_key_paths.map(&:pubkey).zip(hd_key_paths).to_h
|
|
209
|
-
break
|
|
210
215
|
end
|
|
211
216
|
end
|
|
212
217
|
end
|
|
@@ -267,20 +272,21 @@ module Bitcoin
|
|
|
267
272
|
# extract final tx.
|
|
268
273
|
# @return [Bitcoin::Tx] final tx.
|
|
269
274
|
def extract_tx
|
|
270
|
-
extract_tx = tx.
|
|
275
|
+
extract_tx = Bitcoin::Tx.parse_from_payload(tx.to_payload)
|
|
271
276
|
inputs.each_with_index do |input, index|
|
|
272
277
|
extract_tx.in[index].script_sig = input.final_script_sig if input.final_script_sig
|
|
273
278
|
extract_tx.in[index].script_witness = input.final_script_witness if input.final_script_witness
|
|
274
279
|
end
|
|
275
280
|
# validate signature
|
|
276
|
-
|
|
281
|
+
extract_tx.in.each_with_index do |tx_in, index|
|
|
277
282
|
input = inputs[index]
|
|
278
283
|
if input.non_witness_utxo
|
|
279
284
|
utxo = input.non_witness_utxo.out[tx_in.out_point.index]
|
|
280
|
-
raise "input[#{index}]'s signature is invalid.'" unless
|
|
285
|
+
raise "input[#{index}]'s signature is invalid.'" unless extract_tx.verify_input_sig(index, utxo.script_pubkey)
|
|
281
286
|
else
|
|
282
287
|
utxo = input.witness_utxo
|
|
283
|
-
raise "input[#{index}]
|
|
288
|
+
raise ArgumentError, "input[#{index}] does not have utxo." unless utxo
|
|
289
|
+
raise "input[#{index}]'s signature is invalid.'" unless extract_tx.verify_input_sig(index, utxo.script_pubkey, amount: utxo.value)
|
|
284
290
|
end
|
|
285
291
|
end
|
|
286
292
|
extract_tx
|