bitcoinrb 0.5.0 → 0.9.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 +37 -0
- data/.rspec_parallel +2 -0
- data/.ruby-version +1 -1
- data/README.md +11 -1
- data/bitcoinrb.gemspec +7 -6
- data/lib/bitcoin/block_filter.rb +14 -0
- data/lib/bitcoin/chain_params.rb +9 -0
- data/lib/bitcoin/chainparams/signet.yml +39 -0
- data/lib/bitcoin/constants.rb +45 -4
- data/lib/bitcoin/descriptor.rb +1 -1
- data/lib/bitcoin/errors.rb +19 -0
- data/lib/bitcoin/ext/array_ext.rb +22 -0
- data/lib/bitcoin/ext/ecdsa.rb +36 -0
- data/lib/bitcoin/ext.rb +1 -0
- data/lib/bitcoin/ext_key.rb +36 -20
- data/lib/bitcoin/key.rb +85 -28
- data/lib/bitcoin/message/addr_v2.rb +34 -0
- data/lib/bitcoin/message/base.rb +16 -0
- data/lib/bitcoin/message/cfcheckpt.rb +2 -2
- data/lib/bitcoin/message/cfheaders.rb +1 -1
- data/lib/bitcoin/message/cfilter.rb +1 -1
- data/lib/bitcoin/message/fee_filter.rb +1 -1
- data/lib/bitcoin/message/filter_load.rb +3 -3
- data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
- data/lib/bitcoin/message/inventory.rb +1 -1
- data/lib/bitcoin/message/merkle_block.rb +1 -1
- data/lib/bitcoin/message/network_addr.rb +141 -18
- data/lib/bitcoin/message/ping.rb +1 -1
- data/lib/bitcoin/message/pong.rb +1 -1
- data/lib/bitcoin/message/send_addr_v2.rb +13 -0
- data/lib/bitcoin/message/send_cmpct.rb +2 -2
- data/lib/bitcoin/message/tx.rb +1 -1
- data/lib/bitcoin/message.rb +72 -0
- data/lib/bitcoin/message_sign.rb +47 -0
- data/lib/bitcoin/mnemonic.rb +2 -2
- data/lib/bitcoin/network/peer_discovery.rb +1 -3
- data/lib/bitcoin/node/configuration.rb +3 -1
- data/lib/bitcoin/node/spv.rb +8 -0
- data/lib/bitcoin/opcodes.rb +14 -1
- data/lib/bitcoin/payment_code.rb +2 -2
- data/lib/bitcoin/payments/payment.pb.rb +1 -1
- data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
- data/lib/bitcoin/psbt/input.rb +4 -4
- data/lib/bitcoin/psbt/output.rb +1 -1
- data/lib/bitcoin/psbt/tx.rb +14 -5
- data/lib/bitcoin/psbt.rb +8 -0
- data/lib/bitcoin/rpc/bitcoin_core_client.rb +1 -1
- data/lib/bitcoin/rpc/request_handler.rb +3 -3
- data/lib/bitcoin/script/script.rb +80 -30
- data/lib/bitcoin/script/script_error.rb +27 -1
- data/lib/bitcoin/script/script_interpreter.rb +164 -62
- data/lib/bitcoin/script/tx_checker.rb +62 -14
- data/lib/bitcoin/secp256k1/native.rb +184 -17
- data/lib/bitcoin/secp256k1/ruby.rb +108 -21
- data/lib/bitcoin/sighash_generator.rb +157 -0
- data/lib/bitcoin/taproot/leaf_node.rb +23 -0
- data/lib/bitcoin/taproot/simple_builder.rb +155 -0
- data/lib/bitcoin/taproot.rb +45 -0
- data/lib/bitcoin/tx.rb +30 -96
- data/lib/bitcoin/tx_in.rb +1 -1
- data/lib/bitcoin/tx_out.rb +2 -3
- data/lib/bitcoin/util.rb +15 -6
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet/account.rb +1 -1
- data/lib/bitcoin.rb +32 -24
- metadata +58 -18
- data/.travis.yml +0 -12
data/lib/bitcoin/message.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module Bitcoin
|
2
2
|
module Message
|
3
3
|
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
4
6
|
autoload :Base, 'bitcoin/message/base'
|
5
7
|
autoload :Inventory, 'bitcoin/message/inventory'
|
6
8
|
autoload :InventoriesParser, 'bitcoin/message/inventories_parser'
|
@@ -44,6 +46,8 @@ module Bitcoin
|
|
44
46
|
autoload :CFCheckpt, 'bitcoin/message/cfcheckpt'
|
45
47
|
autoload :CFilter, 'bitcoin/message/cfilter'
|
46
48
|
autoload :CFHeaders, 'bitcoin/message/cfheaders'
|
49
|
+
autoload :SendAddrV2, 'bitcoin/message/send_addr_v2'
|
50
|
+
autoload :AddrV2, 'bitcoin/message/addr_v2'
|
47
51
|
|
48
52
|
USER_AGENT = "/bitcoinrb:#{Bitcoin::VERSION}/"
|
49
53
|
|
@@ -73,5 +77,73 @@ module Bitcoin
|
|
73
77
|
compact_witness: 70015
|
74
78
|
}
|
75
79
|
|
80
|
+
module_function
|
81
|
+
|
82
|
+
# Decode P2P message.
|
83
|
+
# @param [String] command P2P message command string.
|
84
|
+
# @param [String] payload P2P message payload with hex format..
|
85
|
+
# @return [Bitcoin::Message::]
|
86
|
+
def decode(command, payload = nil)
|
87
|
+
payload = payload.htb if payload
|
88
|
+
case command
|
89
|
+
when Bitcoin::Message::Version::COMMAND
|
90
|
+
Bitcoin::Message::Version.parse_from_payload(payload)
|
91
|
+
when Bitcoin::Message::VerAck::COMMAND
|
92
|
+
Bitcoin::Message::VerAck.new
|
93
|
+
when Bitcoin::Message::GetAddr::COMMAND
|
94
|
+
Bitcoin::Message::GetAddr.new
|
95
|
+
when Bitcoin::Message::Addr::COMMAND
|
96
|
+
Bitcoin::Message::Addr.parse_from_payload(payload)
|
97
|
+
when Bitcoin::Message::SendHeaders::COMMAND
|
98
|
+
Bitcoin::Message::SendHeaders.new
|
99
|
+
when Bitcoin::Message::FeeFilter::COMMAND
|
100
|
+
Bitcoin::Message::FeeFilter.parse_from_payload(payload)
|
101
|
+
when Bitcoin::Message::Ping::COMMAND
|
102
|
+
Bitcoin::Message::Ping.parse_from_payload(payload)
|
103
|
+
when Bitcoin::Message::Pong::COMMAND
|
104
|
+
Bitcoin::Message::Pong.parse_from_payload(payload)
|
105
|
+
when Bitcoin::Message::GetHeaders::COMMAND
|
106
|
+
Bitcoin::Message::GetHeaders.parse_from_payload(payload)
|
107
|
+
when Bitcoin::Message::Headers::COMMAND
|
108
|
+
Bitcoin::Message::Headers.parse_from_payload(payload)
|
109
|
+
when Bitcoin::Message::Block::COMMAND
|
110
|
+
Bitcoin::Message::Block.parse_from_payload(payload)
|
111
|
+
when Bitcoin::Message::Tx::COMMAND
|
112
|
+
Bitcoin::Message::Tx.parse_from_payload(payload)
|
113
|
+
when Bitcoin::Message::NotFound::COMMAND
|
114
|
+
Bitcoin::Message::NotFound.parse_from_payload(payload)
|
115
|
+
when Bitcoin::Message::MemPool::COMMAND
|
116
|
+
Bitcoin::Message::MemPool.new
|
117
|
+
when Bitcoin::Message::Reject::COMMAND
|
118
|
+
Bitcoin::Message::Reject.parse_from_payload(payload)
|
119
|
+
when Bitcoin::Message::SendCmpct::COMMAND
|
120
|
+
Bitcoin::Message::SendCmpct.parse_from_payload(payload)
|
121
|
+
when Bitcoin::Message::Inv::COMMAND
|
122
|
+
Bitcoin::Message::Inv.parse_from_payload(payload)
|
123
|
+
when Bitcoin::Message::MerkleBlock::COMMAND
|
124
|
+
Bitcoin::Message::MerkleBlock.parse_from_payload(payload)
|
125
|
+
when Bitcoin::Message::CmpctBlock::COMMAND
|
126
|
+
Bitcoin::Message::CmpctBlock.parse_from_payload(payload)
|
127
|
+
when Bitcoin::Message::GetData::COMMAND
|
128
|
+
Bitcoin::Message::GetData.parse_from_payload(payload)
|
129
|
+
when Bitcoin::Message::GetCFHeaders::COMMAND
|
130
|
+
Bitcoin::Message::GetCFHeaders.parse_from_payload(payload)
|
131
|
+
when Bitcoin::Message::GetCFilters::COMMAND
|
132
|
+
Bitcoin::Message::GetCFilters.parse_from_payload(payload)
|
133
|
+
when Bitcoin::Message::GetCFCheckpt::COMMAND
|
134
|
+
Bitcoin::Message::GetCFCheckpt.parse_from_payload(payload)
|
135
|
+
when Bitcoin::Message::CFCheckpt::COMMAND
|
136
|
+
Bitcoin::Message::CFCheckpt.parse_from_payload(payload)
|
137
|
+
when Bitcoin::Message::CFHeaders::COMMAND
|
138
|
+
Bitcoin::Message::CFHeaders.parse_from_payload(payload)
|
139
|
+
when Bitcoin::Message::CFilter::COMMAND
|
140
|
+
Bitcoin::Message::CFilter.parse_from_payload(payload)
|
141
|
+
when Bitcoin::Message::SendAddrV2::COMMAND
|
142
|
+
Bitcoin::Message::SendAddrV2.new
|
143
|
+
when Bitcoin::Message::AddrV2::COMMAND
|
144
|
+
Bitcoin::Message::AddrV2.parse_from_payload(payload)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
76
148
|
end
|
77
149
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
|
3
|
+
module MessageSign
|
4
|
+
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# Sign a message.
|
10
|
+
# @param [Bitcoin::Key] key Private key to sign with.
|
11
|
+
# @param [String] message The message to sign.
|
12
|
+
# @return [String] Signature, base64 encoded.
|
13
|
+
def sign_message(key, message, prefix: Bitcoin.chain_params.message_magic)
|
14
|
+
digest = message_hash(message, prefix: prefix)
|
15
|
+
compact_sig = key.sign_compact(digest)
|
16
|
+
Base64.strict_encode64(compact_sig)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Verify a signed message.
|
20
|
+
# @param [String] address Signer's bitcoin address, it must refer to a public key.
|
21
|
+
# @param [String] signature The signature in base64 format.
|
22
|
+
# @param [String] message The message that was signed.
|
23
|
+
# @return [Boolean] Verification result.
|
24
|
+
def verify_message(address, signature, message, prefix: Bitcoin.chain_params.message_magic)
|
25
|
+
validate_address!(address)
|
26
|
+
sig = Base64.decode64(signature)
|
27
|
+
raise ArgumentError, 'Invalid signature length' unless sig.bytesize == Bitcoin::Key::COMPACT_SIGNATURE_SIZE
|
28
|
+
digest = message_hash(message, prefix: prefix)
|
29
|
+
pubkey = Bitcoin::Key.recover_compact(digest, sig)
|
30
|
+
return false unless pubkey
|
31
|
+
pubkey.to_p2pkh == address
|
32
|
+
end
|
33
|
+
|
34
|
+
# Hashes a message for signing and verification.
|
35
|
+
def message_hash(message, prefix: Bitcoin.chain_params.message_magic)
|
36
|
+
Bitcoin.double_sha256(Bitcoin.pack_var_string(prefix) << Bitcoin.pack_var_string(message))
|
37
|
+
end
|
38
|
+
|
39
|
+
def validate_address!(address)
|
40
|
+
raise ArgumentError, 'Invalid address' unless Bitcoin.valid_address?(address)
|
41
|
+
script = Bitcoin::Script.parse_from_addr(address)
|
42
|
+
raise ArgumentError, 'Address has no key' unless script.p2pkh?
|
43
|
+
end
|
44
|
+
|
45
|
+
private_class_method :validate_address!
|
46
|
+
end
|
47
|
+
end
|
data/lib/bitcoin/mnemonic.rb
CHANGED
@@ -39,7 +39,7 @@ module Bitcoin
|
|
39
39
|
# @return [Array] the array of mnemonic word.
|
40
40
|
def to_mnemonic(entropy)
|
41
41
|
raise ArgumentError, 'entropy is empty.' if entropy.nil? || entropy.empty?
|
42
|
-
e = entropy.htb.
|
42
|
+
e = entropy.htb.unpack1('B*')
|
43
43
|
seed = e + checksum(e)
|
44
44
|
mnemonic_index = seed.chars.each_slice(11).map{|i|i.join.to_i(2)}
|
45
45
|
word_master = load_words
|
@@ -61,7 +61,7 @@ module Bitcoin
|
|
61
61
|
# @param [String] entropy an entropy with bit string format
|
62
62
|
# @return [String] an entropy checksum with bit string format
|
63
63
|
def checksum(entropy)
|
64
|
-
b = Bitcoin.sha256([entropy].pack('B*')).
|
64
|
+
b = Bitcoin.sha256([entropy].pack('B*')).unpack1('B*')
|
65
65
|
b.slice(0, (entropy.length/32))
|
66
66
|
end
|
67
67
|
|
@@ -30,9 +30,7 @@ module Bitcoin
|
|
30
30
|
logger.debug 'discover peer address from DNS seeds.'
|
31
31
|
dns_seeds.map { |seed|
|
32
32
|
begin
|
33
|
-
#
|
34
|
-
# https://github.com/bitcoin/bitcoin/pull/8083#issuecomment-221552835
|
35
|
-
Socket.getaddrinfo("x5.#{seed}", Bitcoin.chain_params.default_port).map{|a|a[2]}.uniq
|
33
|
+
Socket.getaddrinfo("#{seed}", Bitcoin.chain_params.default_port).map{|a|a[2]}.uniq
|
36
34
|
rescue SocketError => e
|
37
35
|
logger.error "SocketError occurred when load DNS seed: #{seed}, error: #{e.message}"
|
38
36
|
nil
|
@@ -4,8 +4,10 @@ module Bitcoin
|
|
4
4
|
module Node
|
5
5
|
class Configuration
|
6
6
|
|
7
|
-
attr_reader :conf
|
7
|
+
attr_reader :conf # Hash
|
8
8
|
|
9
|
+
# initialize configuration
|
10
|
+
# @param [Hash] opts parameter for node.
|
9
11
|
def initialize(opts = {})
|
10
12
|
# TODO apply configuration file.
|
11
13
|
opts[:network] = :mainnet unless opts[:network]
|
data/lib/bitcoin/node/spv.rb
CHANGED
@@ -13,6 +13,14 @@ module Bitcoin
|
|
13
13
|
attr_accessor :wallet
|
14
14
|
attr_accessor :bloom
|
15
15
|
|
16
|
+
# Initialize spv settings
|
17
|
+
# @param [Bitcoin::Node::Configuration] configuration configuration for spv.
|
18
|
+
#
|
19
|
+
# ```ruby
|
20
|
+
# config = Bitcoin::Node::Configuration.new(network: :mainnet)
|
21
|
+
# spv = Bitcoin::Node::SPV.new(config)
|
22
|
+
# spv.run
|
23
|
+
# ````
|
16
24
|
def initialize(configuration)
|
17
25
|
@chain = Bitcoin::Store::SPVChain.new
|
18
26
|
@configuration = configuration
|
data/lib/bitcoin/opcodes.rb
CHANGED
@@ -136,6 +136,8 @@ module Bitcoin
|
|
136
136
|
OP_NOP9 = 0xb8
|
137
137
|
OP_NOP10 = 0xb9
|
138
138
|
|
139
|
+
OP_CHECKSIGADD = 0xba # BIP 342 opcodes (Tapscript)
|
140
|
+
|
139
141
|
# https://en.bitcoin.it/wiki/Script#Pseudo-words
|
140
142
|
OP_PUBKEYHASH = 0xfd
|
141
143
|
OP_PUBKEY = 0xfe
|
@@ -145,6 +147,9 @@ module Bitcoin
|
|
145
147
|
OPCODES_MAP = Hash[*(constants.grep(/^OP_/) - [:OP_NOP2, :OP_NOP3, :OP_CHECKLOCKTIMEVERIFY, :OP_CHECKSEQUENCEVERIFY]).map { |c| [const_get(c), c.to_s] }.flatten]
|
146
148
|
NAME_MAP = Hash[*constants.grep(/^OP_/).map { |c| [c.to_s, const_get(c)] }.flatten]
|
147
149
|
|
150
|
+
OP_SUCCESSES = [0x50, 0x62, 0x89, 0x8a, 0x8d, 0x8e, (0x7e..0x81).to_a,
|
151
|
+
(0x83..0x86).to_a, (0x95..0x99).to_a, (0xbb..0xfe).to_a].flatten
|
152
|
+
|
148
153
|
def opcode_to_name(opcode)
|
149
154
|
return OPCODES_MAP[opcode].delete('OP_') if opcode == OP_0 || (opcode <= OP_16 && opcode >= OP_1)
|
150
155
|
OPCODES_MAP[opcode]
|
@@ -156,7 +161,8 @@ module Bitcoin
|
|
156
161
|
end
|
157
162
|
|
158
163
|
# whether opcode is predefined opcode
|
159
|
-
def defined?(opcode)
|
164
|
+
def defined?(opcode, allow_success = false)
|
165
|
+
return true if allow_success && op_success?(opcode)
|
160
166
|
!opcode_to_name(opcode).nil?
|
161
167
|
end
|
162
168
|
|
@@ -174,5 +180,12 @@ module Bitcoin
|
|
174
180
|
nil
|
175
181
|
end
|
176
182
|
|
183
|
+
# Check whether +opcode+ is OP_SUCCESSx or not?
|
184
|
+
# @param [Integer] opcode an opcode.
|
185
|
+
# @return [Boolean] if +opcode+ is OP_SUCCESSx return true, otherwise false.
|
186
|
+
def op_success?(opcode)
|
187
|
+
OP_SUCCESSES.include?(opcode)
|
188
|
+
end
|
189
|
+
|
177
190
|
end
|
178
191
|
end
|
data/lib/bitcoin/payment_code.rb
CHANGED
@@ -61,8 +61,8 @@ module Bitcoin
|
|
61
61
|
raise ArgumentError, 'invalid version byte' unless hex[0..1] == VERSION_BYTE
|
62
62
|
raise ArgumentError, 'invalid version' unless PaymentCode.support_version?(version)
|
63
63
|
raise ArgumentError, 'invalid sign' unless PaymentCode.support_sign?(sign)
|
64
|
-
raise ArgumentError,
|
65
|
-
raise ArgumentError,
|
64
|
+
raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless Bitcoin::Key.new(priv_key: nil, pubkey: sign + public_key).fully_valid_pubkey?
|
65
|
+
raise ArgumentError, Errors::Messages::INVALID_CHECKSUM unless Bitcoin.calc_checksum(payment_code) == hex[-8..-1]
|
66
66
|
|
67
67
|
x_value = payment_code[8..71]
|
68
68
|
chain_code_hex = payment_code[72..135]
|
@@ -12,7 +12,7 @@ module Bitcoin
|
|
12
12
|
pubkey = pubkey.encoding == Encoding::ASCII_8BIT ? pubkey : pubkey.htb
|
13
13
|
raise ArgumentError, 'Size of key was not the expected size for the type BIP32 keypath.' unless [Bitcoin::Key::PUBLIC_KEY_SIZE, Bitcoin::Key::COMPRESSED_PUBLIC_KEY_SIZE].include?(pubkey.bytesize)
|
14
14
|
pubkey = Bitcoin::Key.new(pubkey: pubkey.bth)
|
15
|
-
raise ArgumentError,
|
15
|
+
raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless pubkey.fully_valid_pubkey?
|
16
16
|
@pubkey = pubkey.pubkey
|
17
17
|
@info = info
|
18
18
|
end
|
data/lib/bitcoin/psbt/input.rb
CHANGED
@@ -36,7 +36,7 @@ module Bitcoin
|
|
36
36
|
found_sep = true
|
37
37
|
break
|
38
38
|
end
|
39
|
-
key_type = buf.read(1).
|
39
|
+
key_type = buf.read(1).unpack1('C')
|
40
40
|
key = buf.read(key_len - 1)
|
41
41
|
value = buf.read(Bitcoin.unpack_var_int_from_io(buf))
|
42
42
|
|
@@ -44,7 +44,7 @@ module Bitcoin
|
|
44
44
|
when PSBT_IN_TYPES[:non_witness_utxo]
|
45
45
|
raise ArgumentError, 'Invalid non-witness utxo typed key.' unless key_len == 1
|
46
46
|
raise ArgumentError, 'Duplicate Key, input non-witness utxo already provided.' if input.non_witness_utxo
|
47
|
-
input.non_witness_utxo = Bitcoin::Tx.parse_from_payload(value)
|
47
|
+
input.non_witness_utxo = Bitcoin::Tx.parse_from_payload(value, strict: true)
|
48
48
|
when PSBT_IN_TYPES[:witness_utxo]
|
49
49
|
raise ArgumentError, 'Invalid input witness utxo typed key.' unless key_len == 1
|
50
50
|
raise ArgumentError, 'Duplicate Key, input witness utxo already provided.' if input.witness_utxo
|
@@ -54,13 +54,13 @@ module Bitcoin
|
|
54
54
|
raise ArgumentError, 'Size of key was not the expected size for the type partial signature pubkey.'
|
55
55
|
end
|
56
56
|
pubkey = Bitcoin::Key.new(pubkey: key.bth)
|
57
|
-
raise ArgumentError,
|
57
|
+
raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless pubkey.fully_valid_pubkey?
|
58
58
|
raise ArgumentError, 'Duplicate Key, input partial signature for pubkey already provided.' if input.partial_sigs[pubkey.pubkey]
|
59
59
|
input.partial_sigs[pubkey.pubkey] = value
|
60
60
|
when PSBT_IN_TYPES[:sighash]
|
61
61
|
raise ArgumentError, 'Invalid input sighash type typed key.' unless key_len == 1
|
62
62
|
raise ArgumentError 'Duplicate Key, input sighash type already provided.' if input.sighash_type
|
63
|
-
input.sighash_type = value.
|
63
|
+
input.sighash_type = value.unpack1('I')
|
64
64
|
when PSBT_IN_TYPES[:redeem_script]
|
65
65
|
raise ArgumentError, 'Invalid redeemscript typed key.' unless key_len == 1
|
66
66
|
raise ArgumentError, 'Duplicate Key, input redeemScript already provided.' if input.redeem_script
|
data/lib/bitcoin/psbt/output.rb
CHANGED
data/lib/bitcoin/psbt/tx.rb
CHANGED
@@ -55,7 +55,7 @@ module Bitcoin
|
|
55
55
|
# @return [Bitcoin::PartiallySignedTx]
|
56
56
|
def self.parse_from_payload(payload)
|
57
57
|
buf = StringIO.new(payload)
|
58
|
-
raise ArgumentError, 'Invalid PSBT magic bytes.' unless buf.read(4).
|
58
|
+
raise ArgumentError, 'Invalid PSBT magic bytes.' unless buf.read(4).unpack1('N') == PSBT_MAGIC_BYTES
|
59
59
|
raise ArgumentError, 'Invalid PSBT separator.' unless buf.read(1).bth.to_i(16) == 0xff
|
60
60
|
partial_tx = self.new
|
61
61
|
found_sep = false
|
@@ -66,7 +66,7 @@ module Bitcoin
|
|
66
66
|
found_sep = true
|
67
67
|
break
|
68
68
|
end
|
69
|
-
key_type = buf.read(1).
|
69
|
+
key_type = buf.read(1).unpack1('C')
|
70
70
|
key = buf.read(key_len - 1)
|
71
71
|
value = buf.read(Bitcoin.unpack_var_int_from_io(buf))
|
72
72
|
|
@@ -74,20 +74,20 @@ module Bitcoin
|
|
74
74
|
when PSBT_GLOBAL_TYPES[:unsigned_tx]
|
75
75
|
raise ArgumentError, 'Invalid global transaction typed key.' unless key_len == 1
|
76
76
|
raise ArgumentError, 'Duplicate Key, unsigned tx already provided.' if partial_tx.tx
|
77
|
-
partial_tx.tx = Bitcoin::Tx.parse_from_payload(value, non_witness: true)
|
77
|
+
partial_tx.tx = Bitcoin::Tx.parse_from_payload(value, non_witness: true, strict: true)
|
78
78
|
partial_tx.tx.in.each do |tx_in|
|
79
79
|
raise ArgumentError, 'Unsigned tx does not have empty scriptSigs and scriptWitnesses.' if !tx_in.script_sig.empty? || !tx_in.script_witness.empty?
|
80
80
|
end
|
81
81
|
when PSBT_GLOBAL_TYPES[:xpub]
|
82
82
|
raise ArgumentError, 'Size of key was not the expected size for the type global xpub.' unless key.size == Bitcoin::BIP32_EXTKEY_WITH_VERSION_SIZE
|
83
83
|
xpub = Bitcoin::ExtPubkey.parse_from_payload(key)
|
84
|
-
raise ArgumentError,
|
84
|
+
raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless xpub.key.fully_valid_pubkey?
|
85
85
|
raise ArgumentError, 'Duplicate key, global xpub already provided' if partial_tx.xpubs.any?{|x|x.xpub == xpub}
|
86
86
|
info = Bitcoin::PSBT::KeyOriginInfo.parse_from_payload(value)
|
87
87
|
raise ArgumentError, "global xpub's depth and the number of indexes not matched." unless xpub.depth == info.key_paths.size
|
88
88
|
partial_tx.xpubs << Bitcoin::PSBT::GlobalXpub.new(xpub, info)
|
89
89
|
when PSBT_GLOBAL_TYPES[:ver]
|
90
|
-
partial_tx.version_number = value.
|
90
|
+
partial_tx.version_number = value.unpack1('V')
|
91
91
|
raise ArgumentError, "An unsupported version was detected." if SUPPORT_VERSION < partial_tx.version_number
|
92
92
|
else
|
93
93
|
raise ArgumentError, 'Duplicate Key, key for unknown value already provided.' if partial_tx.unknowns[key]
|
@@ -162,6 +162,15 @@ module Bitcoin
|
|
162
162
|
Base64.strict_encode64(to_payload)
|
163
163
|
end
|
164
164
|
|
165
|
+
# Store the PSBT to a file.
|
166
|
+
# @param [String] path File path to store.
|
167
|
+
def to_file(path)
|
168
|
+
raise ArgumentError, 'The file already exists' if File.exist?(path)
|
169
|
+
File.open(path, 'w') do |f|
|
170
|
+
f.write(to_payload)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
165
174
|
# update input key-value maps.
|
166
175
|
# @param [Bitcoin::Tx] prev_tx previous tx reference by input.
|
167
176
|
# @param [Bitcoin::Script] redeem_script redeem script to set input.
|
data/lib/bitcoin/psbt.rb
CHANGED
@@ -31,6 +31,14 @@ module Bitcoin
|
|
31
31
|
s << Bitcoin.pack_var_int(value.bytesize) << value
|
32
32
|
s
|
33
33
|
end
|
34
|
+
|
35
|
+
# Load PSBT from file.
|
36
|
+
# @param [String] path File path of PSBT.
|
37
|
+
# @return [Bitcoin::PSBT::Tx] PSBT object.
|
38
|
+
def load_from_file(path)
|
39
|
+
raise ArgumentError, 'File not found' unless File.exist?(path)
|
40
|
+
Bitcoin::PSBT::Tx.parse_from_payload(File.read(path))
|
41
|
+
end
|
34
42
|
end
|
35
43
|
|
36
44
|
end
|
@@ -63,7 +63,7 @@ module Bitcoin
|
|
63
63
|
response = http.request(request)
|
64
64
|
body = response.body
|
65
65
|
response = Bitcoin::Ext::JsonParser.new(body.gsub(/\\u([\da-fA-F]{4})/) { [$1].pack('H*').unpack('n*').pack('U*').encode('ISO-8859-1').force_encoding('UTF-8') }).parse
|
66
|
-
raise response['error'].
|
66
|
+
raise response['error'].to_json if response['error']
|
67
67
|
response['result']
|
68
68
|
end
|
69
69
|
|
@@ -49,7 +49,7 @@ module Bitcoin
|
|
49
49
|
# Returns connected peer information.
|
50
50
|
def getpeerinfo
|
51
51
|
node.pool.peers.map do |peer|
|
52
|
-
local_addr = "#{peer.remote_version.remote_addr.
|
52
|
+
local_addr = "#{peer.remote_version.remote_addr.addr_string}:18333"
|
53
53
|
{
|
54
54
|
id: peer.id,
|
55
55
|
addr: "#{peer.host}:#{peer.port}",
|
@@ -75,7 +75,7 @@ module Bitcoin
|
|
75
75
|
|
76
76
|
# broadcast transaction
|
77
77
|
def sendrawtransaction(hex_tx)
|
78
|
-
tx = Bitcoin::Tx.parse_from_payload(hex_tx.htb)
|
78
|
+
tx = Bitcoin::Tx.parse_from_payload(hex_tx.htb, strict: true)
|
79
79
|
# TODO check wether tx is valid
|
80
80
|
node.broadcast(tx)
|
81
81
|
tx.txid
|
@@ -84,7 +84,7 @@ module Bitcoin
|
|
84
84
|
# decode tx data.
|
85
85
|
def decoderawtransaction(hex_tx)
|
86
86
|
begin
|
87
|
-
Bitcoin::Tx.parse_from_payload(hex_tx.htb).to_h
|
87
|
+
Bitcoin::Tx.parse_from_payload(hex_tx.htb, strict: true ).to_h
|
88
88
|
rescue Exception
|
89
89
|
raise ArgumentError.new('TX decode failed')
|
90
90
|
end
|
@@ -21,7 +21,7 @@ module Bitcoin
|
|
21
21
|
|
22
22
|
# generate P2WPKH script
|
23
23
|
def self.to_p2wpkh(pubkey_hash)
|
24
|
-
new <<
|
24
|
+
new << WITNESS_VERSION_V0 << pubkey_hash
|
25
25
|
end
|
26
26
|
|
27
27
|
# generate m of n multisig p2sh script
|
@@ -52,7 +52,7 @@ module Bitcoin
|
|
52
52
|
end
|
53
53
|
|
54
54
|
# generate m of n multisig script
|
55
|
-
# @param [
|
55
|
+
# @param [Integer] m the number of signatures required for multisig
|
56
56
|
# @param [Array] pubkeys array of public keys that compose multisig
|
57
57
|
# @return [Script] multisig script.
|
58
58
|
def self.to_multisig_script(m, pubkeys, sort: false)
|
@@ -64,7 +64,7 @@ module Bitcoin
|
|
64
64
|
# @param [Script] redeem_script target redeem script
|
65
65
|
# @param [Script] p2wsh script
|
66
66
|
def self.to_p2wsh(redeem_script)
|
67
|
-
new <<
|
67
|
+
new << WITNESS_VERSION_V0 << redeem_script.to_sha256
|
68
68
|
end
|
69
69
|
|
70
70
|
# generate script from string.
|
@@ -87,17 +87,21 @@ module Bitcoin
|
|
87
87
|
def self.parse_from_addr(addr)
|
88
88
|
begin
|
89
89
|
segwit_addr = Bech32::SegwitAddr.new(addr)
|
90
|
-
raise 'Invalid
|
90
|
+
raise ArgumentError, 'Invalid address.' unless Bitcoin.chain_params.bech32_hrp == segwit_addr.hrp
|
91
91
|
Bitcoin::Script.parse_from_payload(segwit_addr.to_script_pubkey.htb)
|
92
92
|
rescue Exception => e
|
93
|
+
begin
|
93
94
|
hex, addr_version = Bitcoin.decode_base58_address(addr)
|
95
|
+
rescue
|
96
|
+
raise ArgumentError, 'Invalid address.'
|
97
|
+
end
|
94
98
|
case addr_version
|
95
99
|
when Bitcoin.chain_params.address_version
|
96
100
|
Bitcoin::Script.to_p2pkh(hex)
|
97
101
|
when Bitcoin.chain_params.p2sh_version
|
98
102
|
Bitcoin::Script.to_p2sh(hex)
|
99
103
|
else
|
100
|
-
|
104
|
+
raise ArgumentError, 'Invalid address.'
|
101
105
|
end
|
102
106
|
end
|
103
107
|
end
|
@@ -110,25 +114,28 @@ module Bitcoin
|
|
110
114
|
if opcode.pushdata?
|
111
115
|
pushcode = opcode.ord
|
112
116
|
packed_size = nil
|
117
|
+
if buf.eof?
|
118
|
+
s.chunks << opcode
|
119
|
+
return s
|
120
|
+
end
|
113
121
|
len = case pushcode
|
114
122
|
when OP_PUSHDATA1
|
115
123
|
packed_size = buf.read(1)
|
116
|
-
packed_size.
|
124
|
+
packed_size.unpack1('C')
|
117
125
|
when OP_PUSHDATA2
|
118
126
|
packed_size = buf.read(2)
|
119
|
-
packed_size.
|
127
|
+
packed_size.unpack1('v')
|
120
128
|
when OP_PUSHDATA4
|
121
129
|
packed_size = buf.read(4)
|
122
|
-
packed_size.
|
130
|
+
packed_size.unpack1('V')
|
123
131
|
else
|
124
|
-
pushcode
|
132
|
+
pushcode < OP_PUSHDATA1 ? pushcode : 0
|
125
133
|
end
|
126
|
-
if
|
127
|
-
s.chunks << [len].pack('C')
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
end
|
134
|
+
if buf.eof?
|
135
|
+
s.chunks << [len].pack('C')
|
136
|
+
else buf.eof?
|
137
|
+
chunk = (packed_size ? (opcode + packed_size) : (opcode)) + buf.read(len)
|
138
|
+
s.chunks << chunk
|
132
139
|
end
|
133
140
|
else
|
134
141
|
if Opcodes.defined?(opcode.ord)
|
@@ -141,8 +148,12 @@ module Bitcoin
|
|
141
148
|
s
|
142
149
|
end
|
143
150
|
|
144
|
-
|
145
|
-
|
151
|
+
# Output script payload.
|
152
|
+
# @param [Boolean] length_prefixed Flag whether the length of the payload should be given at the beginning.(default: false)
|
153
|
+
# @return [String] payload
|
154
|
+
def to_payload(length_prefixed = false)
|
155
|
+
p = chunks.join
|
156
|
+
length_prefixed ? (Bitcoin.pack_var_int(p.length) << p) : p
|
146
157
|
end
|
147
158
|
|
148
159
|
def empty?
|
@@ -170,27 +181,40 @@ module Bitcoin
|
|
170
181
|
|
171
182
|
# check whether standard script.
|
172
183
|
def standard?
|
173
|
-
p2pkh? | p2sh? | p2wpkh? | p2wsh? | multisig? | standard_op_return?
|
184
|
+
p2pkh? | p2sh? | p2wpkh? | p2wsh? | p2tr? | multisig? | standard_op_return?
|
174
185
|
end
|
175
186
|
|
176
|
-
# whether this script is a P2PKH format script.
|
187
|
+
# Check whether this script is a P2PKH format script.
|
188
|
+
# @return [Boolean] if P2PKH return true, otherwise false
|
177
189
|
def p2pkh?
|
178
190
|
return false unless chunks.size == 5
|
179
191
|
[OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG] ==
|
180
192
|
(chunks[0..1]+ chunks[3..4]).map(&:ord) && chunks[2].bytesize == 21
|
181
193
|
end
|
182
194
|
|
183
|
-
# whether this script is a P2WPKH format script.
|
195
|
+
# Check whether this script is a P2WPKH format script.
|
196
|
+
# @return [Boolean] if P2WPKH return true, otherwise false
|
184
197
|
def p2wpkh?
|
185
198
|
return false unless chunks.size == 2
|
186
|
-
chunks[0].ord ==
|
199
|
+
chunks[0].ord == WITNESS_VERSION_V0 && chunks[1].bytesize == 21
|
187
200
|
end
|
188
201
|
|
202
|
+
# Check whether this script is a P2WPSH format script.
|
203
|
+
# @return [Boolean] if P2WPSH return true, otherwise false
|
189
204
|
def p2wsh?
|
190
205
|
return false unless chunks.size == 2
|
191
|
-
chunks[0].ord ==
|
206
|
+
chunks[0].ord == WITNESS_VERSION_V0 && chunks[1].bytesize == 33
|
207
|
+
end
|
208
|
+
|
209
|
+
# Check whether this script is a P2TR format script.
|
210
|
+
# @return [Boolean] if P2TR return true, otherwise false
|
211
|
+
def p2tr?
|
212
|
+
return false unless chunks.size == 2
|
213
|
+
chunks[0].ord == WITNESS_VERSION_V1 && chunks[1].bytesize == 33
|
192
214
|
end
|
193
215
|
|
216
|
+
# Check whether this script is a P2SH format script.
|
217
|
+
# @return [Boolean] if P2SH return true, otherwise false
|
194
218
|
def p2sh?
|
195
219
|
return false unless chunks.size == 3
|
196
220
|
OP_HASH160 == chunks[0].ord && OP_EQUAL == chunks[2].ord && chunks[1].bytesize == 21
|
@@ -242,7 +266,7 @@ module Bitcoin
|
|
242
266
|
return false if opcode != OP_0 && (opcode < OP_1 || opcode > OP_16)
|
243
267
|
return false unless chunks[1].pushdata?
|
244
268
|
|
245
|
-
if size == (chunks[1][0].
|
269
|
+
if size == (chunks[1][0].unpack1('C') + 2)
|
246
270
|
program_size = chunks[1].pushed_data.bytesize
|
247
271
|
return program_size >= 2 && program_size <= 40
|
248
272
|
end
|
@@ -343,10 +367,14 @@ module Bitcoin
|
|
343
367
|
v
|
344
368
|
else
|
345
369
|
data = c.pushed_data
|
346
|
-
if data
|
347
|
-
|
370
|
+
if data
|
371
|
+
if data.bytesize <= 4
|
372
|
+
Script.decode_number(data.bth) # for scriptnum
|
373
|
+
else
|
374
|
+
data.bth
|
375
|
+
end
|
348
376
|
else
|
349
|
-
|
377
|
+
c.bth
|
350
378
|
end
|
351
379
|
end
|
352
380
|
else
|
@@ -393,8 +421,8 @@ module Bitcoin
|
|
393
421
|
hex = '0' + hex unless (hex.length % 2).zero?
|
394
422
|
v = hex.htb.reverse # change endian
|
395
423
|
|
396
|
-
v = v << (negative ? 0x80 : 0x00) unless (v[-1].
|
397
|
-
v[-1] = [v[-1].
|
424
|
+
v = v << (negative ? 0x80 : 0x00) unless (v[-1].unpack1('C') & 0x80) == 0
|
425
|
+
v[-1] = [v[-1].unpack1('C') | 0x80].pack('C') if negative
|
398
426
|
v.bth
|
399
427
|
end
|
400
428
|
|
@@ -402,7 +430,7 @@ module Bitcoin
|
|
402
430
|
def self.decode_number(s)
|
403
431
|
v = s.htb.reverse
|
404
432
|
return 0 if v.length.zero?
|
405
|
-
mbs = v[0].
|
433
|
+
mbs = v[0].unpack1('C')
|
406
434
|
v[0] = [mbs - 0x80].pack('C') unless (mbs & 0x80) == 0
|
407
435
|
result = v.bth.to_i(16)
|
408
436
|
result = -result unless (mbs & 0x80) == 0
|
@@ -487,7 +515,7 @@ module Bitcoin
|
|
487
515
|
end
|
488
516
|
|
489
517
|
def ==(other)
|
490
|
-
return false unless other
|
518
|
+
return false unless other.is_a?(Script)
|
491
519
|
chunks == other.chunks
|
492
520
|
end
|
493
521
|
|
@@ -553,6 +581,28 @@ module Bitcoin
|
|
553
581
|
segwit_addr.addr
|
554
582
|
end
|
555
583
|
|
584
|
+
# Check whether push data length is valid.
|
585
|
+
# @return [Boolean] if valid return true, otherwise false.
|
586
|
+
def valid_pushdata_length?(chunk)
|
587
|
+
buf = StringIO.new(chunk)
|
588
|
+
opcode = buf.read(1).ord
|
589
|
+
offset = 1
|
590
|
+
len = case opcode
|
591
|
+
when OP_PUSHDATA1
|
592
|
+
offset += 1
|
593
|
+
buf.read(1).unpack1('C')
|
594
|
+
when OP_PUSHDATA2
|
595
|
+
offset += 2
|
596
|
+
buf.read(2).unpack1('v')
|
597
|
+
when OP_PUSHDATA4
|
598
|
+
offset += 4
|
599
|
+
buf.read(4).unpack1('V')
|
600
|
+
else
|
601
|
+
opcode
|
602
|
+
end
|
603
|
+
chunk.bytesize == len + offset
|
604
|
+
end
|
605
|
+
|
556
606
|
end
|
557
607
|
|
558
608
|
end
|