bitcoinrb 1.14.0 → 1.15.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/lib/bitcoin/key.rb +10 -3
- data/lib/bitcoin/message_sign.rb +23 -9
- data/lib/bitcoin/slip39/share.rb +7 -3
- data/lib/bitcoin/slip39/sss.rb +13 -7
- data/lib/bitcoin/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5da84e4a456d4b5a47fba7e5cfcfceabbfbdb9fe757f6080519130d2ebabc41c
|
|
4
|
+
data.tar.gz: 903301aa0eb0f349bc08c7133ebe80b9c731646fece6a407b75ace1bf6161aa8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 39a539b3380c6dba3ac091a2b3706508e790d16d4367ff91f715c9804ac5803dc56893f5aaf4c3bc4dec71d4a1c03a2d4da481e0b060c291a913a96cc3a7f1db
|
|
7
|
+
data.tar.gz: dec61d2d90e67665aa4277de65b68a86c00779b820d5980e8d2facc85d0a0a4ba5c10989a43982fafc88463f40c02dfbfd3421b6f89f3e08620af4c008fa22fd
|
data/lib/bitcoin/key.rb
CHANGED
|
@@ -192,10 +192,17 @@ module Bitcoin
|
|
|
192
192
|
Bitcoin::Script.to_p2wpkh(hash160).to_addr
|
|
193
193
|
end
|
|
194
194
|
|
|
195
|
-
# Get pay to taproot address
|
|
195
|
+
# Get pay to taproot address.
|
|
196
|
+
# By default the x-only public key of this key becomes the output key as it is, which is
|
|
197
|
+
# what the rawtr() descriptor does. With +as_internal+ this key is the internal key P and
|
|
198
|
+
# the output key is Q = P + hash_TapTweak(P)G, which is what the tr() descriptor does and
|
|
199
|
+
# what BIP-86 defines for a single key address. Spending such an output needs the tweaked
|
|
200
|
+
# private key, see Bitcoin::Taproot.tweak_private_key.
|
|
201
|
+
# @param [Boolean] as_internal Whether to treat this key as the internal key. Default is false.
|
|
196
202
|
# @return [String] address
|
|
197
|
-
def to_p2tr
|
|
198
|
-
Bitcoin::
|
|
203
|
+
def to_p2tr(as_internal: false)
|
|
204
|
+
output_key = as_internal ? Bitcoin::Taproot.tweak_public_key(self, '') : self
|
|
205
|
+
Bitcoin::Script.to_p2tr(output_key).to_addr
|
|
199
206
|
end
|
|
200
207
|
|
|
201
208
|
# get p2wpkh address nested in p2sh.
|
data/lib/bitcoin/message_sign.rb
CHANGED
|
@@ -33,6 +33,7 @@ module Bitcoin
|
|
|
33
33
|
# @param [String] format Format of signature data. Default is +FORMAT_LEGACY+.
|
|
34
34
|
# @param [String] prefix (Optional) Prefix used in legacy format.
|
|
35
35
|
# @return [String] Signature, base64 encoded.
|
|
36
|
+
# @raise [ArgumentError] If +address+ is not supported, or +key+ does not correspond to it.
|
|
36
37
|
def sign_message(key, message, prefix: Bitcoin.chain_params.message_magic, format: FORMAT_LEGACY, address: nil)
|
|
37
38
|
validate_format!(format)
|
|
38
39
|
digest = message_hash(message, prefix: prefix, legacy: format == FORMAT_LEGACY)
|
|
@@ -43,20 +44,15 @@ module Bitcoin
|
|
|
43
44
|
else
|
|
44
45
|
validate_address!(address)
|
|
45
46
|
addr = Bitcoin::Script.parse_from_addr(address)
|
|
46
|
-
sig_ver, algo = if addr.p2wpkh? || addr.p2wsh?
|
|
47
|
-
[:witness_v0, :ecdsa]
|
|
48
|
-
elsif addr.p2tr?
|
|
49
|
-
[:taproot, :schnorr]
|
|
50
|
-
else
|
|
51
|
-
|
|
52
|
-
end
|
|
53
47
|
tx = to_sign_tx(digest, address)
|
|
54
48
|
prev_out = Bitcoin::TxOut.new(script_pubkey: addr, value: 0)
|
|
55
49
|
if addr.p2tr?
|
|
56
|
-
sighash = tx.sighash_for_input(0, addr, sig_version: :taproot, prevouts: [prev_out], hash_type: Bitcoin::SIGHASH_TYPE[:default])
|
|
57
50
|
tweaked = Bitcoin::Taproot.tweak_private_key(key, '')
|
|
51
|
+
validate_key!(Bitcoin::Script.to_p2tr(tweaked), addr, address)
|
|
52
|
+
sighash = tx.sighash_for_input(0, addr, sig_version: :taproot, prevouts: [prev_out], hash_type: Bitcoin::SIGHASH_TYPE[:default])
|
|
58
53
|
tx.in[0].script_witness.stack << tweaked.sign(sighash, algo: :schnorr)
|
|
59
|
-
elsif addr.p2wpkh?
|
|
54
|
+
elsif addr.p2wpkh?
|
|
55
|
+
validate_key!(Bitcoin::Script.to_p2wpkh(key.hash160), addr, address)
|
|
60
56
|
sighash = tx.sighash_for_input(0, addr, sig_version: :witness_v0, amount: 0, prevouts: [prev_out])
|
|
61
57
|
ecdsa = key.sign(sighash, algo: :ecdsa) + [Bitcoin::SIGHASH_TYPE[:all]].pack('C')
|
|
62
58
|
tx.in[0].script_witness.stack << ecdsa
|
|
@@ -128,6 +124,23 @@ module Bitcoin
|
|
|
128
124
|
Bitcoin::Script.parse_from_addr(address)
|
|
129
125
|
end
|
|
130
126
|
|
|
127
|
+
# Ensure the signing key can satisfy +address+.
|
|
128
|
+
# A key the address does not commit to still produces a signature, and BIP-322 carries
|
|
129
|
+
# nothing that would report it verifies against no one.
|
|
130
|
+
# @param [Bitcoin::Script] derived The script the signing key produces.
|
|
131
|
+
# @param [Bitcoin::Script] expected The script of the address to be signed for.
|
|
132
|
+
# @param [String] address The address to be signed for, for the error message.
|
|
133
|
+
# @raise [ArgumentError] If the key does not correspond to +address+.
|
|
134
|
+
def validate_key!(derived, expected, address)
|
|
135
|
+
return if derived == expected
|
|
136
|
+
message = "Key does not correspond to #{address}."
|
|
137
|
+
if expected.p2tr?
|
|
138
|
+
message += ' A P2TR address holds the output key, which is the internal key this key' \
|
|
139
|
+
' holds tweaked with an empty merkle root. See Bitcoin::Key#to_p2tr.'
|
|
140
|
+
end
|
|
141
|
+
raise ArgumentError, message
|
|
142
|
+
end
|
|
143
|
+
|
|
131
144
|
def validate_format!(format)
|
|
132
145
|
unless [FORMAT_LEGACY, FORMAT_FULL, FORMAT_SIMPLE].include?(format)
|
|
133
146
|
raise ArgumentError "Invalid format specified."
|
|
@@ -197,6 +210,7 @@ module Bitcoin
|
|
|
197
210
|
end
|
|
198
211
|
|
|
199
212
|
private_class_method :validate_address!
|
|
213
|
+
private_class_method :validate_key!
|
|
200
214
|
private_class_method :validate_format!
|
|
201
215
|
private_class_method :validate_to_sign_tx!
|
|
202
216
|
private_class_method :run_interpreter
|
data/lib/bitcoin/slip39/share.rb
CHANGED
|
@@ -46,7 +46,7 @@ module Bitcoin
|
|
|
46
46
|
end_index = start_index + value_length - padding_length
|
|
47
47
|
padding_value = data[40...(40 + padding_length)]
|
|
48
48
|
raise ArgumentError, "Invalid mnemonic. padding must only zero." unless padding_value.to_i(2) == 0
|
|
49
|
-
s.value = data[start_index...end_index].to_i(2).
|
|
49
|
+
s.value = data[start_index...end_index].to_i(2).to_s(16).rjust((value_length - padding_length) / 4, '0')
|
|
50
50
|
s.checksum = data[(40 + value_length)..-1].to_i(2)
|
|
51
51
|
s
|
|
52
52
|
end
|
|
@@ -92,8 +92,12 @@ module Bitcoin
|
|
|
92
92
|
raise StandardError, "Group threshold(#{group_threshold}) cannot be greater than group count(#{group_count})." if group_threshold > group_count
|
|
93
93
|
s << member_index.to_bits(4)
|
|
94
94
|
s << (member_threshold - 1).to_bits(4)
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
# from_words derives the padding length from the number of value words modulo 16, which only
|
|
96
|
+
# matches the padding written here when the value is an even number of bytes. An odd one is
|
|
97
|
+
# read back as a different value instead of being rejected.
|
|
98
|
+
raise ArgumentError, 'The length of the share value in bytes must be an even number.' unless value.htb.bytesize.even?
|
|
99
|
+
value_length = value.htb.bytesize * 8
|
|
100
|
+
padding_length = (RADIX_BITS - (value_length % RADIX_BITS)) % RADIX_BITS
|
|
97
101
|
s << value.to_i(16).to_bits(value_length + padding_length)
|
|
98
102
|
s << checksum.to_bits(30) if include_checksum
|
|
99
103
|
s.chars.each_slice(10).map{|index| index.join.to_i(2)}
|
data/lib/bitcoin/slip39/sss.rb
CHANGED
|
@@ -81,8 +81,10 @@ module Bitcoin
|
|
|
81
81
|
#
|
|
82
82
|
# @param [Array[Bitcoin::SLIP30::Share]] shares an array of shares.
|
|
83
83
|
# @param [String] passphrase the passphrase using decrypt master secret.
|
|
84
|
+
# @param [Boolean] legacy_salt if true, build the PBKDF2 salt with the minimum number of bytes for the
|
|
85
|
+
# identifier. Shares generated by bitcoinrb 1.14.0 and earlier need this to be recovered.
|
|
84
86
|
# @return [String] a master secret.
|
|
85
|
-
def self.recover_secret(shares, passphrase: '')
|
|
87
|
+
def self.recover_secret(shares, passphrase: '', legacy_salt: false)
|
|
86
88
|
raise ArgumentError, 'share is empty.' if shares.nil? || shares.empty?
|
|
87
89
|
groups = {}
|
|
88
90
|
id = shares[0].id
|
|
@@ -127,7 +129,7 @@ module Bitcoin
|
|
|
127
129
|
end
|
|
128
130
|
end
|
|
129
131
|
|
|
130
|
-
return decrypt(group_shares.values.first, passphrase, exp, id) if group_threshold == 1
|
|
132
|
+
return decrypt(group_shares.values.first, passphrase, exp, id, legacy_salt) if group_threshold == 1
|
|
131
133
|
|
|
132
134
|
raise ArgumentError, "Wrong number of mnemonics. Group threshold is #{group_threshold}, but share count is #{group_shares.length}" if group_shares.length < group_threshold
|
|
133
135
|
|
|
@@ -138,7 +140,7 @@ module Bitcoin
|
|
|
138
140
|
recover_digest = create_digest(secret, random_value)
|
|
139
141
|
raise ArgumentError, 'Invalid digest of the shared secret.' unless digest == recover_digest
|
|
140
142
|
|
|
141
|
-
decrypt(secret, passphrase, exp, id)
|
|
143
|
+
decrypt(secret, passphrase, exp, id, legacy_salt)
|
|
142
144
|
end
|
|
143
145
|
|
|
144
146
|
private
|
|
@@ -168,9 +170,10 @@ module Bitcoin
|
|
|
168
170
|
# @param [String] passphrase the passphrase when using encrypt master secret with binary format.
|
|
169
171
|
# @param [Integer] exp iteration exponent
|
|
170
172
|
# @param [Integer] id identifier
|
|
171
|
-
|
|
173
|
+
# @param [Boolean] legacy_salt if true, build the salt with the minimum number of bytes for the identifier.
|
|
174
|
+
def self.decrypt(ems, passphrase, exp, id, legacy_salt = false)
|
|
172
175
|
l, r = ems[0...(ems.length / 2)].htb, ems[(ems.length / 2)..-1].htb
|
|
173
|
-
salt = get_salt(id)
|
|
176
|
+
salt = get_salt(id, legacy_salt)
|
|
174
177
|
e = (Bitcoin::SLIP39::BASE_ITERATION_COUNT << exp) / Bitcoin::SLIP39::ROUND_COUNT
|
|
175
178
|
Bitcoin::SLIP39::ROUND_COUNT.times.to_a.reverse.each do |i|
|
|
176
179
|
f = OpenSSL::PKCS5.pbkdf2_hmac((i.itb + passphrase), salt + r, e, r.bytesize, 'sha256')
|
|
@@ -208,9 +211,12 @@ module Bitcoin
|
|
|
208
211
|
|
|
209
212
|
# get salt using encryption/decryption form id.
|
|
210
213
|
# @param [Integer] id id
|
|
214
|
+
# @param [Boolean] legacy if true, encode the identifier with the minimum number of bytes. SLIP-39 requires
|
|
215
|
+
# two bytes, so a salt built this way only matches shares generated by bitcoinrb 1.14.0 and earlier.
|
|
211
216
|
# @return [String] salt with binary format.
|
|
212
|
-
def self.get_salt(id)
|
|
213
|
-
|
|
217
|
+
def self.get_salt(id, legacy = false)
|
|
218
|
+
identifier = legacy ? id.itb : [id].pack('n')
|
|
219
|
+
(Bitcoin::SLIP39::CUSTOMIZATION_STRING.pack('c*') + identifier)
|
|
214
220
|
end
|
|
215
221
|
|
|
216
222
|
# Split the share into +count+ with threshold +threshold+.
|
data/lib/bitcoin/version.rb
CHANGED