bitcoinrb 1.4.0 → 1.5.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/.ruby-version +1 -1
- data/README.md +9 -0
- data/bitcoinrb.gemspec +2 -2
- data/lib/bitcoin/bip324/cipher.rb +113 -0
- data/lib/bitcoin/bip324/ell_swift_pubkey.rb +42 -0
- data/lib/bitcoin/bip324/fs_chacha20.rb +132 -0
- data/lib/bitcoin/bip324/fs_chacha_poly1305.rb +129 -0
- data/lib/bitcoin/bip324.rb +144 -0
- data/lib/bitcoin/ext/ecdsa.rb +0 -6
- data/lib/bitcoin/key.rb +15 -3
- data/lib/bitcoin/message_sign.rb +13 -8
- data/lib/bitcoin/secp256k1/native.rb +61 -5
- data/lib/bitcoin/secp256k1/ruby.rb +21 -4
- data/lib/bitcoin/util.rb +11 -3
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin.rb +1 -0
- metadata +12 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 900806837ee6cd6b011a50d6a58c37ea233079e8b86a594c6a5031c338f9a490
|
4
|
+
data.tar.gz: 34b437fee384d630d9b7f1a47faa8175692d9ebe21408d4720828140461b152c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 87bcfdb59403593475f90b49f9f67299bee3bfd8cfae7089080e95ab8e1ffddcdc21ebbb72f8ca49f1caea9d8df2ea5ca613eb97b698ddab3fb3d3c617611165
|
7
|
+
data.tar.gz: 55dcbb9ce3f10a5dd822e98605eb00fa74f3034a8852cdcd714b4dff5cd347cbdc55bffd49ae2f74cc4aeaad2bf86d01d635e7146207ccb6557f55ae0849ff0e
|
data/.github/workflows/ruby.yml
CHANGED
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby-3.
|
1
|
+
ruby-3.3.0
|
data/README.md
CHANGED
@@ -105,6 +105,15 @@ Bitcoin.chain_params = :signet
|
|
105
105
|
|
106
106
|
This parameter is described in https://github.com/chaintope/bitcoinrb/blob/master/lib/bitcoin/chainparams/signet.yml.
|
107
107
|
|
108
|
+
## Test
|
109
|
+
|
110
|
+
This library can use the [libsecp256k1](https://github.com/bitcoin-core/secp256k1/) dynamic library.
|
111
|
+
Therefore, some tests require this library. In a Linux environment, `spec/lib/libsecp256k1.so` is already located,
|
112
|
+
so there is no need to do anything. If you want to test in another environment,
|
113
|
+
please set the library path in the environment variable `TEST_LIBSECP256K1_PATH`.
|
114
|
+
|
115
|
+
The libsecp256k1 library currently tested for operation with this library is `v0.4.0`.
|
116
|
+
|
108
117
|
## Contributing
|
109
118
|
|
110
119
|
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/bitcoinrb. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
data/bitcoinrb.gemspec
CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
21
|
spec.require_paths = ["lib"]
|
22
22
|
|
23
|
-
spec.add_runtime_dependency 'ecdsa_ext', '~> 0.5.
|
23
|
+
spec.add_runtime_dependency 'ecdsa_ext', '~> 0.5.1'
|
24
24
|
spec.add_runtime_dependency 'eventmachine'
|
25
25
|
spec.add_runtime_dependency 'murmurhash3', '~> 0.1.7'
|
26
26
|
spec.add_runtime_dependency 'bech32', '>= 1.3.0'
|
@@ -32,7 +32,7 @@ Gem::Specification.new do |spec|
|
|
32
32
|
spec.add_runtime_dependency 'iniparse'
|
33
33
|
spec.add_runtime_dependency 'siphash'
|
34
34
|
spec.add_runtime_dependency 'json_pure', '>= 2.3.1'
|
35
|
-
spec.add_runtime_dependency 'bip-schnorr', '>= 0.
|
35
|
+
spec.add_runtime_dependency 'bip-schnorr', '>= 0.7.0'
|
36
36
|
spec.add_runtime_dependency 'base32', '>= 0.3.4'
|
37
37
|
|
38
38
|
# for options
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module BIP324
|
3
|
+
# The BIP324 packet cipher, encapsulating its key derivation, stream cipher, and AEAD.
|
4
|
+
class Cipher
|
5
|
+
include Bitcoin::Util
|
6
|
+
|
7
|
+
HEADER = [1 << 7].pack('C')
|
8
|
+
HEADER_LEN = 1
|
9
|
+
LENGTH_LEN = 3
|
10
|
+
EXPANSION = LENGTH_LEN + HEADER_LEN + 16
|
11
|
+
|
12
|
+
attr_reader :key
|
13
|
+
attr_reader :our_pubkey
|
14
|
+
|
15
|
+
attr_accessor :session_id
|
16
|
+
attr_accessor :send_garbage_terminator
|
17
|
+
attr_accessor :recv_garbage_terminator
|
18
|
+
attr_accessor :send_l_cipher
|
19
|
+
attr_accessor :send_p_cipher
|
20
|
+
attr_accessor :recv_l_cipher
|
21
|
+
attr_accessor :recv_p_cipher
|
22
|
+
|
23
|
+
# Constructor
|
24
|
+
# @param [Bitcoin::Key] key Private key.
|
25
|
+
# @param [Bitcoin::BIP324::EllSwiftPubkey] our_pubkey Ellswift public key for testing.
|
26
|
+
# @raise ArgumentError
|
27
|
+
def initialize(key, our_pubkey = nil)
|
28
|
+
raise ArgumentError, "key must be Bitcoin::Key" unless key.is_a?(Bitcoin::Key)
|
29
|
+
raise ArgumentError, "our_pubkey must be Bitcoin::BIP324::EllSwiftPubkey" if our_pubkey && !our_pubkey.is_a?(Bitcoin::BIP324::EllSwiftPubkey)
|
30
|
+
@our_pubkey = our_pubkey ? our_pubkey : key.create_ell_pubkey
|
31
|
+
@key = key
|
32
|
+
end
|
33
|
+
|
34
|
+
# Setup when the other side's public key is received.
|
35
|
+
# @param [Bitcoin::BIP324::EllSwiftPubkey] their_pubkey
|
36
|
+
# @param [Boolean] initiator Set true if we are the initiator establishing the v2 P2P connection.
|
37
|
+
# @param [Boolean] self_decrypt only for testing, and swaps encryption/decryption keys, so that encryption
|
38
|
+
# and decryption can be tested without knowing the other side's private key.
|
39
|
+
def setup(their_pubkey, initiator, self_decrypt = false)
|
40
|
+
salt = 'bitcoin_v2_shared_secret' + Bitcoin.chain_params.magic_head.htb
|
41
|
+
ecdh_secret = BIP324.v2_ecdh(key.priv_key, their_pubkey, our_pubkey, initiator).htb
|
42
|
+
terminator = hkdf_sha256(ecdh_secret, salt, 'garbage_terminators')
|
43
|
+
side = initiator != self_decrypt
|
44
|
+
if side
|
45
|
+
self.send_l_cipher = FSChaCha20.new(hkdf_sha256(ecdh_secret, salt, 'initiator_L'))
|
46
|
+
self.send_p_cipher = FSChaCha20Poly1305.new(hkdf_sha256(ecdh_secret, salt, 'initiator_P'))
|
47
|
+
self.recv_l_cipher = FSChaCha20.new(hkdf_sha256(ecdh_secret, salt, 'responder_L'))
|
48
|
+
self.recv_p_cipher = FSChaCha20Poly1305.new(hkdf_sha256(ecdh_secret, salt, 'responder_P'))
|
49
|
+
else
|
50
|
+
self.recv_l_cipher = FSChaCha20.new(hkdf_sha256(ecdh_secret, salt, 'initiator_L'))
|
51
|
+
self.recv_p_cipher = FSChaCha20Poly1305.new(hkdf_sha256(ecdh_secret, salt, 'initiator_P'))
|
52
|
+
self.send_l_cipher = FSChaCha20.new(hkdf_sha256(ecdh_secret, salt, 'responder_L'))
|
53
|
+
self.send_p_cipher = FSChaCha20Poly1305.new(hkdf_sha256(ecdh_secret, salt, 'responder_P'))
|
54
|
+
end
|
55
|
+
if initiator
|
56
|
+
self.send_garbage_terminator = terminator[0...16].bth
|
57
|
+
self.recv_garbage_terminator = terminator[16..-1].bth
|
58
|
+
else
|
59
|
+
self.recv_garbage_terminator = terminator[0...16].bth
|
60
|
+
self.send_garbage_terminator = terminator[16..-1].bth
|
61
|
+
end
|
62
|
+
self.session_id = hkdf_sha256(ecdh_secret, salt, 'session_id').bth
|
63
|
+
end
|
64
|
+
|
65
|
+
# Encrypt a packet. Only after setup.
|
66
|
+
# @param [String] contents Packet with binary format.
|
67
|
+
# @param [String] aad AAD
|
68
|
+
# @param [Boolean] ignore Whether contains ignore bit or not.
|
69
|
+
# @raise Bitcoin::BIP324::TooLargeContent
|
70
|
+
def encrypt(contents, aad: '', ignore: false)
|
71
|
+
raise Bitcoin::BIP324::TooLargeContent unless contents.bytesize <= (2**24 - 1)
|
72
|
+
|
73
|
+
# encrypt length
|
74
|
+
len = Array.new(3)
|
75
|
+
len[0] = contents.bytesize & 0xff
|
76
|
+
len[1] = (contents.bytesize >> 8) & 0xff
|
77
|
+
len[2] = (contents.bytesize >> 16) & 0xff
|
78
|
+
enc_plaintext_len = send_l_cipher.encrypt(len.pack('C*'))
|
79
|
+
|
80
|
+
# encrypt contents
|
81
|
+
header = ignore ? HEADER : "00".htb
|
82
|
+
plaintext = header + contents
|
83
|
+
aead_ciphertext = send_p_cipher.encrypt(aad, plaintext)
|
84
|
+
enc_plaintext_len + aead_ciphertext
|
85
|
+
end
|
86
|
+
|
87
|
+
# Decrypt a packet. Only after setup.
|
88
|
+
# @param [String] input Packet to be decrypt.
|
89
|
+
# @param [String] aad AAD
|
90
|
+
# @param [Boolean] ignore Whether contains ignore bit or not.
|
91
|
+
# @return [String] Plaintext
|
92
|
+
# @raise Bitcoin::BIP324::InvalidPaketLength
|
93
|
+
def decrypt(input, aad: '', ignore: false)
|
94
|
+
len = decrypt_length(input[0...Bitcoin::BIP324::Cipher::LENGTH_LEN])
|
95
|
+
raise Bitcoin::BIP324::InvalidPaketLength unless input.bytesize == len + EXPANSION
|
96
|
+
recv_p_cipher.decrypt(aad, input[Bitcoin::BIP324::Cipher::LENGTH_LEN..-1])
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
# Decrypt the length of a packet. Only after setup.
|
102
|
+
# @param [String] input Length packet with binary format.
|
103
|
+
# @return [Integer] length
|
104
|
+
# @raise Bitcoin::BIP324::InvalidPaketLength
|
105
|
+
def decrypt_length(input)
|
106
|
+
raise Bitcoin::BIP324::InvalidPaketLength unless input.bytesize == LENGTH_LEN
|
107
|
+
ret = recv_l_cipher.decrypt(input)
|
108
|
+
b0, b1, b2 = ret.unpack('CCC')
|
109
|
+
b0 + (b1 << 8) + (b2 << 16)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module BIP324
|
3
|
+
# An ElligatorSwift-encoded public key.
|
4
|
+
class EllSwiftPubkey
|
5
|
+
include Schnorr::Util
|
6
|
+
|
7
|
+
SIZE = 64
|
8
|
+
|
9
|
+
attr_reader :key
|
10
|
+
|
11
|
+
# Constructor
|
12
|
+
# @param [String] key 64 bytes of key data.
|
13
|
+
# @raise Bitcoin::BIP324::InvalidEllSwiftKey If key is invalid.
|
14
|
+
def initialize(key)
|
15
|
+
@key = hex2bin(key)
|
16
|
+
raise Bitcoin::BIP324::InvalidEllSwiftKey, 'key must be 64 bytes.' unless @key.bytesize == SIZE
|
17
|
+
end
|
18
|
+
|
19
|
+
# Decode to public key.
|
20
|
+
# @return [Bitcoin::Key] Decoded public key.
|
21
|
+
def decode
|
22
|
+
if Bitcoin.secp_impl.is_a?(Bitcoin::Secp256k1::Native)
|
23
|
+
pubkey = Bitcoin.secp_impl.ellswift_decode(key)
|
24
|
+
Bitcoin::Key.new(pubkey: pubkey, key_type: Bitcoin::Key::TYPES[:compressed])
|
25
|
+
else
|
26
|
+
u = key[0...32].bth
|
27
|
+
t = key[32..-1].bth
|
28
|
+
x = BIP324.xswiftec(u, t)
|
29
|
+
Bitcoin::Key.new(pubkey: "03#{x}")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Check whether same public key or not?
|
34
|
+
# @param [Bitcoin::BIP324::EllSwiftPubkey] other
|
35
|
+
# @return [Boolean]
|
36
|
+
def ==(other)
|
37
|
+
return false unless other.is_a?(EllSwiftPubkey)
|
38
|
+
key == other.key
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module BIP324
|
3
|
+
|
4
|
+
module ChaCha20
|
5
|
+
module_function
|
6
|
+
|
7
|
+
INDICES = [
|
8
|
+
[0, 4, 8, 12], [1, 5, 9, 13], [2, 6, 10, 14], [3, 7, 11, 15],
|
9
|
+
[0, 5, 10, 15], [1, 6, 11, 12], [2, 7, 8, 13], [3, 4, 9, 14]
|
10
|
+
]
|
11
|
+
|
12
|
+
CONSTANTS = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]
|
13
|
+
|
14
|
+
# Rotate the 32-bit value v left by bits bits.
|
15
|
+
# @param [Integer] v
|
16
|
+
# @param [Integer] bits
|
17
|
+
# @return [Integer]
|
18
|
+
def rotl32(v, bits)
|
19
|
+
raise Bitcoin::BIP324::Error, "v must be integer" unless v.is_a?(Integer)
|
20
|
+
raise Bitcoin::BIP324::Error, "bits must be integer" unless bits.is_a?(Integer)
|
21
|
+
((v << bits) & 0xffffffff) | (v >> (32 - bits))
|
22
|
+
end
|
23
|
+
|
24
|
+
# Apply a ChaCha20 double round to 16-element state array +s+.
|
25
|
+
# @param [Array[Integer]] s
|
26
|
+
# @return
|
27
|
+
def double_round(s)
|
28
|
+
raise Bitcoin::BIP324::Error, "s must be Array" unless s.is_a?(Array)
|
29
|
+
INDICES.each do |a, b, c, d|
|
30
|
+
s[a] = (s[a] + s[b]) & 0xffffffff
|
31
|
+
s[d] = rotl32(s[d] ^ s[a], 16)
|
32
|
+
s[c] = (s[c] + s[d]) & 0xffffffff
|
33
|
+
s[b] = rotl32(s[b] ^ s[c], 12)
|
34
|
+
s[a] = (s[a] + s[b]) & 0xffffffff
|
35
|
+
s[d] = rotl32(s[d] ^ s[a], 8)
|
36
|
+
s[c] = (s[c] + s[d]) & 0xffffffff
|
37
|
+
s[b] = rotl32(s[b] ^ s[c], 7)
|
38
|
+
end
|
39
|
+
s
|
40
|
+
end
|
41
|
+
|
42
|
+
# Compute the 64-byte output of the ChaCha20 block function.
|
43
|
+
# @param [String] key 32-bytes key with binary format.
|
44
|
+
# @param [String] nonce 12-byte nonce with binary format.
|
45
|
+
# @param [Integer] count 32-bit integer counter.
|
46
|
+
# @return [String] 64-byte output.
|
47
|
+
def block(key, nonce, count)
|
48
|
+
raise Bitcoin::BIP324::Error, "key must be 32 byte string" if !key.is_a?(String) || key.bytesize != 32
|
49
|
+
raise Bitcoin::BIP324::Error, "nonce must be 12 byte string" if !nonce.is_a?(String) || nonce.bytesize != 12
|
50
|
+
raise Bitcoin::BIP324::Error, "count must be integer" unless count.is_a?(Integer)
|
51
|
+
# Initialize state
|
52
|
+
init = Array.new(16, 0)
|
53
|
+
4.times {|i| init[i] = CONSTANTS[i]}
|
54
|
+
key = key.unpack("V*")
|
55
|
+
8.times {|i| init[4 + i] = key[i]}
|
56
|
+
init[12] = count
|
57
|
+
nonce = nonce.unpack("V*")
|
58
|
+
3.times {|i| init[13 + i] = nonce[i]}
|
59
|
+
# Perform 20 rounds
|
60
|
+
state = init.dup
|
61
|
+
10.times do
|
62
|
+
state = double_round(state)
|
63
|
+
end
|
64
|
+
# Add initial values back into state.
|
65
|
+
16.times do |i|
|
66
|
+
state[i] = (state[i] + init[i]) & 0xffffffff
|
67
|
+
end
|
68
|
+
state.pack("V16")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Rekeying wrapper stream cipher around ChaCha20.
|
73
|
+
class FSChaCha20
|
74
|
+
attr_accessor :key
|
75
|
+
attr_reader :rekey_interval
|
76
|
+
attr_accessor :chunk_counter
|
77
|
+
attr_accessor :block_counter
|
78
|
+
attr_accessor :key_stream
|
79
|
+
|
80
|
+
def initialize(initial_key, rekey_interval = BIP324::REKEY_INTERVAL)
|
81
|
+
@block_counter = 0
|
82
|
+
@chunk_counter = 0
|
83
|
+
@key = initial_key
|
84
|
+
@rekey_interval = rekey_interval
|
85
|
+
@key_stream = ''
|
86
|
+
end
|
87
|
+
|
88
|
+
# Encrypt a chunk
|
89
|
+
# @param [String] chunk Chunk data with binary format.
|
90
|
+
# @return [String] Encrypted data with binary format.
|
91
|
+
def encrypt(chunk)
|
92
|
+
crypt(chunk)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Decrypt a chunk
|
96
|
+
# @param [String] chunk Chunk data with binary format.
|
97
|
+
# @return [String] Decrypted data with binary format.
|
98
|
+
def decrypt(chunk)
|
99
|
+
crypt(chunk)
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def key_stream_bytes(n_bytes)
|
105
|
+
while key_stream.bytesize < n_bytes
|
106
|
+
nonce = [0, (chunk_counter / REKEY_INTERVAL)].pack("VQ<")
|
107
|
+
self.key_stream << ChaCha20.block(key, nonce, block_counter)
|
108
|
+
self.block_counter += 1
|
109
|
+
end
|
110
|
+
ret = self.key_stream[0...n_bytes]
|
111
|
+
self.key_stream = self.key_stream[n_bytes..-1]
|
112
|
+
ret
|
113
|
+
end
|
114
|
+
|
115
|
+
# Encrypt or decrypt a chunk.
|
116
|
+
# @param [String] chunk Chunk data with binary format.
|
117
|
+
# @return [String]
|
118
|
+
def crypt(chunk)
|
119
|
+
ks = key_stream_bytes(chunk.bytesize)
|
120
|
+
ret = chunk.unpack("C*").zip(ks.unpack("C*")).map do |c, k|
|
121
|
+
c ^ k
|
122
|
+
end.pack("C*")
|
123
|
+
if (self.chunk_counter + 1) % rekey_interval == 0
|
124
|
+
self.key = key_stream_bytes(32)
|
125
|
+
self.block_counter = 0
|
126
|
+
end
|
127
|
+
self.chunk_counter += 1
|
128
|
+
ret
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module BIP324
|
3
|
+
# Class representing a running poly1305 computation.
|
4
|
+
class Poly1305
|
5
|
+
|
6
|
+
MODULUS = 2**130 - 5
|
7
|
+
TAG_LEN = 16
|
8
|
+
|
9
|
+
attr_reader :r
|
10
|
+
attr_reader :s
|
11
|
+
attr_accessor :acc
|
12
|
+
|
13
|
+
# Constructor
|
14
|
+
#
|
15
|
+
def initialize(key)
|
16
|
+
@r = key[0...16].reverse.bti & 0xffffffc0ffffffc0ffffffc0fffffff
|
17
|
+
@s = key[16..-1].reverse.bti
|
18
|
+
@acc = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
# Add a message of any length. Input so far must be a multiple of 16 bytes.
|
22
|
+
# @param [String] msg A message with binary format.
|
23
|
+
# @return [Poly1305] self
|
24
|
+
def add(msg, length: nil, padding: false)
|
25
|
+
len = length ? length : msg.bytesize
|
26
|
+
((len + 15) / 16).times do |i|
|
27
|
+
chunk = msg[(i * 16)...(i * 16 + [16, len - i * 16].min)]
|
28
|
+
val = chunk.reverse.bti + 256**(padding ? 16 : chunk.bytesize)
|
29
|
+
self.acc = r * (acc + val) % MODULUS
|
30
|
+
end
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
# Compute the poly1305 tag.
|
35
|
+
# @return Poly1305 tag wit binary format.
|
36
|
+
def tag
|
37
|
+
ECDSA::Format::IntegerOctetString.encode((acc + s) & 0xffffffffffffffffffffffffffffffff, TAG_LEN).reverse
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Forward-secure wrapper around AEADChaCha20Poly1305.
|
42
|
+
class FSChaCha20Poly1305
|
43
|
+
attr_accessor :aead
|
44
|
+
attr_reader :rekey_interval
|
45
|
+
attr_accessor :packet_counter
|
46
|
+
attr_accessor :key
|
47
|
+
|
48
|
+
def initialize(initial_key, rekey_interval = REKEY_INTERVAL)
|
49
|
+
@packet_counter = 0
|
50
|
+
@rekey_interval = rekey_interval
|
51
|
+
@key = initial_key
|
52
|
+
end
|
53
|
+
|
54
|
+
# Encrypt a +plaintext+ with a specified +aad+.
|
55
|
+
# @param [String] aad AAD
|
56
|
+
# @param [String] plaintext Data to be encrypted with binary format.
|
57
|
+
# @return [String] Ciphertext
|
58
|
+
def encrypt(aad, plaintext)
|
59
|
+
crypt(aad, plaintext, false)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Decrypt a *ciphertext* with a specified +aad+.
|
63
|
+
# @param [String] aad AAD
|
64
|
+
# @param [String] ciphertext Data to be decrypted with binary format.
|
65
|
+
# @return [Array] [header, plaintext]
|
66
|
+
def decrypt(aad, ciphertext)
|
67
|
+
contents = crypt(aad, ciphertext, true)
|
68
|
+
[contents[0], contents[1..-1]]
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# Encrypt or decrypt the specified (plain/cipher)text.
|
74
|
+
def crypt(aad, text, is_decrypt)
|
75
|
+
nonce = [packet_counter % rekey_interval, packet_counter / rekey_interval].pack("VQ<")
|
76
|
+
ret = if is_decrypt
|
77
|
+
chacha20_poly1305_decrypt(key, nonce, aad, text)
|
78
|
+
else
|
79
|
+
chacha20_poly1305_encrypt(key, nonce, aad, text)
|
80
|
+
end
|
81
|
+
if (packet_counter + 1) % rekey_interval == 0
|
82
|
+
rekey_nonce = "ffffffff".htb + nonce[4..-1]
|
83
|
+
newkey1 = chacha20_poly1305_encrypt(key, rekey_nonce, "", "00".htb * 32)[0...32]
|
84
|
+
newkey2 = ChaCha20.block(key, rekey_nonce, 1)[0...32]
|
85
|
+
raise Bitcoin::BIP324::Error, "newkey1 != newkey2" unless newkey1 == newkey2
|
86
|
+
self.key = newkey1
|
87
|
+
end
|
88
|
+
self.packet_counter += 1
|
89
|
+
ret
|
90
|
+
end
|
91
|
+
|
92
|
+
# Encrypt a plaintext using ChaCha20Poly1305.
|
93
|
+
def chacha20_poly1305_encrypt(key, nonce, aad, plaintext)
|
94
|
+
msg_len = plaintext.bytesize
|
95
|
+
ret = ((msg_len + 63) / 64).times.map do |i|
|
96
|
+
now = [64, msg_len - 64 * i].min
|
97
|
+
keystream = ChaCha20.block(key, nonce, i + 1)
|
98
|
+
now.times.map do |j|
|
99
|
+
plaintext[j + 64 * i].unpack1('C') ^ keystream[j].unpack1('C')
|
100
|
+
end
|
101
|
+
end
|
102
|
+
ret = ret.flatten.pack('C*')
|
103
|
+
poly1305 = Poly1305.new(ChaCha20.block(key, nonce, 0)[0...32])
|
104
|
+
poly1305.add(aad, padding: true).add(ret, padding: true)
|
105
|
+
poly1305.add([aad.bytesize, msg_len].pack("Q<Q<"))
|
106
|
+
ret + poly1305.tag
|
107
|
+
end
|
108
|
+
|
109
|
+
# Decrypt a ChaCha20Poly1305 ciphertext.
|
110
|
+
def chacha20_poly1305_decrypt(key, nonce, aad, ciphertext)
|
111
|
+
return nil if ciphertext.bytesize < 16
|
112
|
+
msg_len = ciphertext.bytesize - 16
|
113
|
+
poly1305 = Poly1305.new(ChaCha20.block(key, nonce, 0)[0...32])
|
114
|
+
poly1305.add(aad, padding: true)
|
115
|
+
poly1305.add(ciphertext, length: msg_len, padding: true)
|
116
|
+
poly1305.add([aad.bytesize, msg_len].pack("Q<Q<"))
|
117
|
+
return nil unless ciphertext[-16..-1] == poly1305.tag
|
118
|
+
ret = ((msg_len + 63) / 64).times.map do |i|
|
119
|
+
now = [64, msg_len - 64 * i].min
|
120
|
+
keystream = ChaCha20.block(key, nonce, i + 1)
|
121
|
+
now.times.map do |j|
|
122
|
+
ciphertext[j + 64 * i].unpack1('C') ^ keystream[j].unpack1('C')
|
123
|
+
end
|
124
|
+
end
|
125
|
+
ret.flatten.pack('C*')
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
# BIP 324 module
|
3
|
+
# https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki
|
4
|
+
module BIP324
|
5
|
+
|
6
|
+
class Error < StandardError; end
|
7
|
+
class InvalidPaketLength < Error; end
|
8
|
+
class TooLargeContent < Error; end
|
9
|
+
class InvalidEllSwiftKey < Error; end
|
10
|
+
|
11
|
+
autoload :EllSwiftPubkey, 'bitcoin/bip324/ell_swift_pubkey'
|
12
|
+
autoload :Cipher, 'bitcoin/bip324/cipher'
|
13
|
+
autoload :FSChaCha20, 'bitcoin/bip324/fs_chacha20'
|
14
|
+
autoload :FSChaCha20Poly1305, 'bitcoin/bip324/fs_chacha_poly1305'
|
15
|
+
|
16
|
+
FIELD_SIZE = 2**256 - 2**32 - 977
|
17
|
+
FIELD = ECDSA::PrimeField.new(FIELD_SIZE)
|
18
|
+
|
19
|
+
REKEY_INTERVAL = 224 # packets
|
20
|
+
|
21
|
+
module_function
|
22
|
+
|
23
|
+
def sqrt(n)
|
24
|
+
candidate = FIELD.power(n, (FIELD.prime + 1) / 4)
|
25
|
+
return nil unless FIELD.square(candidate) == n
|
26
|
+
candidate
|
27
|
+
end
|
28
|
+
|
29
|
+
MINUS_3_SQRT = sqrt(FIELD.mod(-3))
|
30
|
+
|
31
|
+
# Decode field elements (u, t) to an X coordinate on the curve.
|
32
|
+
# @param [String] u u of ElligatorSwift encoding with hex format.
|
33
|
+
# @param [String] t t of ElligatorSwift encoding with hex format.
|
34
|
+
# @return [String] x coordinate with hex format.
|
35
|
+
def xswiftec(u, t)
|
36
|
+
u = FIELD.mod(u.hex)
|
37
|
+
t = FIELD.mod(t.hex)
|
38
|
+
u = 1 if u == 0
|
39
|
+
t = 1 if t == 0
|
40
|
+
t = FIELD.mod(2 * t) if FIELD.mod(FIELD.power(u, 3) + FIELD.power(t, 2) + 7) == 0
|
41
|
+
x = FIELD.mod(FIELD.mod(FIELD.power(u, 3) + 7 - FIELD.power(t, 2)) * FIELD.inverse(2 * t))
|
42
|
+
y = FIELD.mod((x + t) * FIELD.inverse(MINUS_3_SQRT * u))
|
43
|
+
x1 = FIELD.mod(u + 4 * FIELD.power(y, 2))
|
44
|
+
x2 = FIELD.mod(FIELD.mod(FIELD.mod(-x) * FIELD.inverse(y) - u) * FIELD.inverse(2))
|
45
|
+
x3 = FIELD.mod(FIELD.mod(x * FIELD.inverse(y) - u) * FIELD.inverse(2))
|
46
|
+
[x1, x2, x3].each do |x|
|
47
|
+
unless ECDSA::Group::Secp256k1.solve_for_y(x).empty?
|
48
|
+
return ECDSA::Format::IntegerOctetString.encode(x, 32).bth
|
49
|
+
end
|
50
|
+
end
|
51
|
+
raise ArgumentError, 'Decode failed.'
|
52
|
+
end
|
53
|
+
|
54
|
+
# Inverse map for ElligatorSwift. Given x and u, find t such that xswiftec(u, t) = x, or return nil.
|
55
|
+
# @param [String] x x coordinate with hex format
|
56
|
+
# @param [String] u u of ElligatorSwift encoding with hex format
|
57
|
+
# @param [Integer] c Case selects which of the up to 8 results to return.
|
58
|
+
# @return [String] Inverse of xswiftec(u, t) with hex format or nil.
|
59
|
+
def xswiftec_inv(x, u, c)
|
60
|
+
x = FIELD.mod(x.hex)
|
61
|
+
u = FIELD.mod(u.hex)
|
62
|
+
if c & 2 == 0
|
63
|
+
return nil unless ECDSA::Group::Secp256k1.solve_for_y(FIELD.mod(-x - u)).empty?
|
64
|
+
v = x
|
65
|
+
s = FIELD.mod(
|
66
|
+
-FIELD.mod(FIELD.power(u, 3) + 7) *
|
67
|
+
FIELD.inverse(FIELD.mod(FIELD.power(u, 2) + u * v + FIELD.power(v, 2))))
|
68
|
+
else
|
69
|
+
s = FIELD.mod(x - u)
|
70
|
+
return nil if s == 0
|
71
|
+
r = sqrt(FIELD.mod(-s * (4 * (FIELD.power(u, 3) + 7) + 3 * s * FIELD.power(u, 2))))
|
72
|
+
return nil if r.nil?
|
73
|
+
return nil if c & 1 == 1 && r == 0
|
74
|
+
v = FIELD.mod(FIELD.mod(-u + r * FIELD.inverse(s)) * FIELD.inverse(2))
|
75
|
+
end
|
76
|
+
w = sqrt(s)
|
77
|
+
return nil if w.nil?
|
78
|
+
result = if c & 5 == 0
|
79
|
+
FIELD.mod(-w * FIELD.mod(u * (1 - MINUS_3_SQRT) * FIELD.inverse(2) + v))
|
80
|
+
elsif c & 5 == 1
|
81
|
+
FIELD.mod(w * FIELD.mod(u * (1 + MINUS_3_SQRT) * FIELD.inverse(2) + v))
|
82
|
+
elsif c & 5 == 4
|
83
|
+
FIELD.mod(w * FIELD.mod(u * (1 - MINUS_3_SQRT) * FIELD.inverse(2) + v))
|
84
|
+
elsif c & 5 == 5
|
85
|
+
FIELD.mod(-w * FIELD.mod(u * (1 + MINUS_3_SQRT) * FIELD.inverse(2) + v))
|
86
|
+
else
|
87
|
+
return nil
|
88
|
+
end
|
89
|
+
ECDSA::Format::IntegerOctetString.encode(result, 32).bth
|
90
|
+
end
|
91
|
+
|
92
|
+
# Given a field element X on the curve, find (u, t) that encode them.
|
93
|
+
# @param [String] x coordinate with hex format.
|
94
|
+
# @return [String] ElligatorSwift public key with hex format.
|
95
|
+
def xelligatorswift(x)
|
96
|
+
loop do
|
97
|
+
u = SecureRandom.random_number(1..ECDSA::Group::Secp256k1.order).to_s(16)
|
98
|
+
c = Random.rand(0..8)
|
99
|
+
t = xswiftec_inv(x, u, c)
|
100
|
+
unless t.nil?
|
101
|
+
return (ECDSA::Format::IntegerOctetString.encode(u.hex, 32) +
|
102
|
+
ECDSA::Format::IntegerOctetString.encode(t.hex, 32)).bth
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Compute x coordinate of shared ECDH point between +ellswift_theirs+ and +priv_key+.
|
108
|
+
# @param [Bitcoin::BIP324::EllSwiftPubkey] ellswift_theirs Their EllSwift public key.
|
109
|
+
# @param [String] priv_key Private key with hex format.
|
110
|
+
# @return [String] x coordinate of shared ECDH point with hex format.
|
111
|
+
# @raise ArgumentError
|
112
|
+
def ellswift_ecdh_xonly(ellswift_theirs, priv_key)
|
113
|
+
raise ArgumentError, "ellswift_theirs must be a Bitcoin::BIP324::EllSwiftPubkey" unless ellswift_theirs.is_a?(Bitcoin::BIP324::EllSwiftPubkey)
|
114
|
+
d = priv_key.hex
|
115
|
+
x = ellswift_theirs.decode.to_point.x
|
116
|
+
field = BIP324::FIELD
|
117
|
+
y = BIP324.sqrt(field.mod(field.power(x, 3) + 7))
|
118
|
+
return nil unless y
|
119
|
+
point = ECDSA::Point.new(ECDSA::Group::Secp256k1, x, y) * d
|
120
|
+
ECDSA::Format::FieldElementOctetString.encode(point.x, point.group.field).bth
|
121
|
+
end
|
122
|
+
|
123
|
+
# Compute BIP324 shared secret.
|
124
|
+
# @param [String] priv_key Private key with hex format.
|
125
|
+
# @param [Bitcoin::BIP324::EllSwiftPubkey] ellswift_theirs Their EllSwift public key.
|
126
|
+
# @param [Bitcoin::BIP324::EllSwiftPubkey] ellswift_ours Our EllSwift public key.
|
127
|
+
# @param [Boolean] initiating Whether your initiator or not.
|
128
|
+
# @return [String] Shared secret with hex format.
|
129
|
+
# @raise ArgumentError
|
130
|
+
def v2_ecdh(priv_key, ellswift_theirs, ellswift_ours, initiating)
|
131
|
+
raise ArgumentError, "ellswift_theirs must be a Bitcoin::BIP324::EllSwiftPubkey" unless ellswift_theirs.is_a?(Bitcoin::BIP324::EllSwiftPubkey)
|
132
|
+
raise ArgumentError, "ellswift_ours must be a Bitcoin::BIP324::EllSwiftPubkey" unless ellswift_ours.is_a?(Bitcoin::BIP324::EllSwiftPubkey)
|
133
|
+
|
134
|
+
if Bitcoin.secp_impl.is_a?(Bitcoin::Secp256k1::Native)
|
135
|
+
Bitcoin::Secp256k1::Native.ellswift_ecdh_xonly(ellswift_theirs, ellswift_ours, priv_key, initiating)
|
136
|
+
else
|
137
|
+
ecdh_point_x32 = ellswift_ecdh_xonly(ellswift_theirs, priv_key).htb
|
138
|
+
content = initiating ? ellswift_ours.key + ellswift_theirs.key + ecdh_point_x32 :
|
139
|
+
ellswift_theirs.key + ellswift_ours.key + ecdh_point_x32
|
140
|
+
Bitcoin.tagged_hash('bip324_ellswift_xonly_ecdh', content).bth
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
data/lib/bitcoin/ext/ecdsa.rb
CHANGED
@@ -10,12 +10,6 @@ class ::ECDSA::Signature
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
class ::ECDSA::Point
|
14
|
-
def to_hex(compression = true)
|
15
|
-
ECDSA::Format::PointOctetString.encode(self, compression: compression).bth
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
13
|
module ::ECDSA::Format::PointOctetString
|
20
14
|
|
21
15
|
class << self
|
data/lib/bitcoin/key.rb
CHANGED
@@ -146,9 +146,9 @@ module Bitcoin
|
|
146
146
|
# @return [Bitcoin::Key] Recovered public key.
|
147
147
|
def self.recover_compact(data, signature)
|
148
148
|
rec_id = signature.unpack1('C')
|
149
|
-
rec = rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE
|
150
|
-
raise ArgumentError, 'Invalid signature parameter' if rec < 0 || rec >
|
151
|
-
|
149
|
+
rec = (rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE) & 3
|
150
|
+
raise ArgumentError, 'Invalid signature parameter' if rec < 0 || rec > 3
|
151
|
+
|
152
152
|
compressed = (rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE) & 4 != 0
|
153
153
|
Bitcoin.secp_impl.recover_compact(data, signature, rec, compressed)
|
154
154
|
end
|
@@ -344,6 +344,18 @@ module Bitcoin
|
|
344
344
|
valid_pubkey? && secp256k1_module.parse_ec_pubkey?(pubkey, allow_hybrid)
|
345
345
|
end
|
346
346
|
|
347
|
+
# Create an ellswift-encoded public key for this key, with specified entropy.
|
348
|
+
# @return [Bitcoin::BIP324::EllSwiftPubkey]
|
349
|
+
# @raise ArgumentError If ent32 does not 32 bytes.
|
350
|
+
def create_ell_pubkey
|
351
|
+
raise ArgumentError, "private_key required." unless priv_key
|
352
|
+
if secp256k1_module.is_a?(Bitcoin::Secp256k1::Native)
|
353
|
+
Bitcoin::BIP324::EllSwiftPubkey.new(secp256k1_module.ellswift_create(priv_key))
|
354
|
+
else
|
355
|
+
Bitcoin::BIP324::EllSwiftPubkey.new(Bitcoin::BIP324.xelligatorswift(xonly_pubkey))
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
347
359
|
private
|
348
360
|
|
349
361
|
def self.compare_big_endian(c1, c2)
|
data/lib/bitcoin/message_sign.rb
CHANGED
@@ -50,18 +50,22 @@ module Bitcoin
|
|
50
50
|
# @param [String] message The message that was signed.
|
51
51
|
# @return [Boolean] Verification result.
|
52
52
|
def verify_message(address, signature, message, prefix: Bitcoin.chain_params.message_magic)
|
53
|
-
|
53
|
+
addr_script = Bitcoin::Script.parse_from_addr(address)
|
54
54
|
begin
|
55
55
|
sig = Base64.strict_decode64(signature)
|
56
56
|
rescue ArgumentError
|
57
57
|
raise ArgumentError, 'Invalid signature'
|
58
58
|
end
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
59
|
+
if addr_script.p2pkh?
|
60
|
+
begin
|
61
|
+
# Legacy verification
|
62
|
+
pubkey = Bitcoin::Key.recover_compact(message_hash(message, prefix: prefix, legacy: true), sig)
|
63
|
+
return false unless pubkey
|
64
|
+
pubkey.to_p2pkh == address
|
65
|
+
rescue RuntimeError
|
66
|
+
return false
|
67
|
+
end
|
68
|
+
elsif addr_script.witness_program?
|
65
69
|
# BIP322 verification
|
66
70
|
tx = to_sign_tx(message_hash(message, prefix: prefix, legacy: false), address)
|
67
71
|
tx.in[0].script_witness = Bitcoin::ScriptWitness.parse_from_payload(sig)
|
@@ -69,6 +73,8 @@ module Bitcoin
|
|
69
73
|
tx_out = Bitcoin::TxOut.new(script_pubkey: script_pubkey)
|
70
74
|
interpreter = Bitcoin::ScriptInterpreter.new(checker: Bitcoin::TxChecker.new(tx: tx, input_index: 0, prevouts: [tx_out]))
|
71
75
|
interpreter.verify_script(Bitcoin::Script.new, script_pubkey, tx.in[0].script_witness)
|
76
|
+
else
|
77
|
+
raise ArgumentError, "This address unsupported."
|
72
78
|
end
|
73
79
|
end
|
74
80
|
|
@@ -82,7 +88,6 @@ module Bitcoin
|
|
82
88
|
end
|
83
89
|
|
84
90
|
def validate_address!(address)
|
85
|
-
raise ArgumentError, 'Invalid address' unless Bitcoin.valid_address?(address)
|
86
91
|
script = Bitcoin::Script.parse_from_addr(address)
|
87
92
|
raise ArgumentError, 'This address unsupported' if script.p2sh? || script.p2wsh?
|
88
93
|
end
|
@@ -5,7 +5,7 @@ module Bitcoin
|
|
5
5
|
module Secp256k1
|
6
6
|
|
7
7
|
# binding for secp256k1 (https://github.com/bitcoin-core/secp256k1/)
|
8
|
-
#
|
8
|
+
# tag: v0.4.0
|
9
9
|
# this is not included by default, to enable set shared object path to ENV['SECP256K1_LIB_PATH']
|
10
10
|
# for linux, ENV['SECP256K1_LIB_PATH'] = '/usr/local/lib/libsecp256k1.so'
|
11
11
|
# for mac,
|
@@ -50,14 +50,20 @@ module Bitcoin
|
|
50
50
|
attach_function(:secp256k1_ecdsa_signature_parse_der, [:pointer, :pointer, :pointer, :size_t], :int)
|
51
51
|
attach_function(:secp256k1_ecdsa_signature_normalize, [:pointer, :pointer, :pointer], :int)
|
52
52
|
attach_function(:secp256k1_ecdsa_verify, [:pointer, :pointer, :pointer, :pointer], :int)
|
53
|
-
attach_function(:
|
54
|
-
attach_function(:secp256k1_schnorrsig_verify, [:pointer, :pointer, :pointer, :pointer], :int)
|
53
|
+
attach_function(:secp256k1_schnorrsig_sign32, [:pointer, :pointer, :pointer, :pointer, :pointer], :int)
|
54
|
+
attach_function(:secp256k1_schnorrsig_verify, [:pointer, :pointer, :pointer, :size_t, :pointer], :int)
|
55
55
|
attach_function(:secp256k1_keypair_create, [:pointer, :pointer, :pointer], :int)
|
56
56
|
attach_function(:secp256k1_xonly_pubkey_parse, [:pointer, :pointer, :pointer], :int)
|
57
57
|
attach_function(:secp256k1_ecdsa_sign_recoverable, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
|
58
58
|
attach_function(:secp256k1_ecdsa_recoverable_signature_serialize_compact, [:pointer, :pointer, :pointer, :pointer], :int)
|
59
59
|
attach_function(:secp256k1_ecdsa_recover, [:pointer, :pointer, :pointer, :pointer], :int)
|
60
60
|
attach_function(:secp256k1_ecdsa_recoverable_signature_parse_compact, [:pointer, :pointer, :pointer, :int], :int)
|
61
|
+
attach_function(:secp256k1_ellswift_decode, [:pointer, :pointer, :pointer], :int)
|
62
|
+
attach_function(:secp256k1_ellswift_create, [:pointer, :pointer, :pointer, :pointer], :int)
|
63
|
+
# Define function pointer
|
64
|
+
callback(:secp256k1_ellswift_xdh_hash_function, [:pointer, :pointer, :pointer, :pointer, :pointer], :int)
|
65
|
+
attach_variable(:secp256k1_ellswift_xdh_hash_function_bip324, :secp256k1_ellswift_xdh_hash_function)
|
66
|
+
attach_function(:secp256k1_ellswift_xdh, [:pointer, :pointer, :pointer, :pointer, :pointer, :int, :pointer, :pointer], :int)
|
61
67
|
end
|
62
68
|
|
63
69
|
def with_context(flags: (SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN))
|
@@ -224,6 +230,56 @@ module Bitcoin
|
|
224
230
|
true
|
225
231
|
end
|
226
232
|
|
233
|
+
# Decode ellswift public key.
|
234
|
+
# @param [String] ell_key ElligatorSwift key with binary format.
|
235
|
+
# @return [String] Decoded public key with hex format
|
236
|
+
def ellswift_decode(ell_key)
|
237
|
+
with_context do |context|
|
238
|
+
ell64 = FFI::MemoryPointer.new(:uchar, ell_key.bytesize).put_bytes(0, ell_key)
|
239
|
+
internal = FFI::MemoryPointer.new(:uchar, 64)
|
240
|
+
result = secp256k1_ellswift_decode(context, internal, ell64)
|
241
|
+
raise ArgumentError, 'Decode failed.' unless result == 1
|
242
|
+
serialize_pubkey_internal(context, internal, true)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Compute an ElligatorSwift public key for a secret key.
|
247
|
+
# @param [String] priv_key private key with hex format
|
248
|
+
# @return [String] ElligatorSwift public key with hex format.
|
249
|
+
def ellswift_create(priv_key)
|
250
|
+
with_context(flags: SECP256K1_CONTEXT_SIGN) do |context|
|
251
|
+
ell64 = FFI::MemoryPointer.new(:uchar, 64)
|
252
|
+
seckey32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, priv_key.htb)
|
253
|
+
result = secp256k1_ellswift_create(context, ell64, seckey32, nil)
|
254
|
+
raise ArgumentError, 'Failed to create ElligatorSwift public key.' unless result == 1
|
255
|
+
ell64.read_string(64).bth
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Compute X coordinate of shared ECDH point between elswift pubkey and privkey.
|
260
|
+
# @param [Bitcoin::BIP324::EllSwiftPubkey] their_ell_pubkey Their EllSwift public key.
|
261
|
+
# @param [Bitcoin::BIP324::EllSwiftPubkey] our_ell_pubkey Our EllSwift public key.
|
262
|
+
# @param [String] priv_key private key with hex format.
|
263
|
+
# @param [Boolean] initiating Whether your initiator or not.
|
264
|
+
# @return [String] x coordinate with hex format.
|
265
|
+
def ellswift_ecdh_xonly(their_ell_pubkey, our_ell_pubkey, priv_key, initiating)
|
266
|
+
with_context(flags: SECP256K1_CONTEXT_SIGN) do |context|
|
267
|
+
output = FFI::MemoryPointer.new(:uchar, 32)
|
268
|
+
our_ell_ptr = FFI::MemoryPointer.new(:uchar, 64).put_bytes(0, our_ell_pubkey.key)
|
269
|
+
their_ell_ptr = FFI::MemoryPointer.new(:uchar, 64).put_bytes(0, their_ell_pubkey.key)
|
270
|
+
seckey32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, priv_key.htb)
|
271
|
+
hashfp = secp256k1_ellswift_xdh_hash_function_bip324
|
272
|
+
result = secp256k1_ellswift_xdh(context, output,
|
273
|
+
initiating ? our_ell_ptr : their_ell_ptr,
|
274
|
+
initiating ? their_ell_ptr : our_ell_ptr,
|
275
|
+
seckey32,
|
276
|
+
initiating ? 0 : 1,
|
277
|
+
hashfp, nil)
|
278
|
+
raise ArgumentError, "secret was invalid or hashfp returned 0" unless result == 1
|
279
|
+
output.read_string(32).bth
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
227
283
|
private
|
228
284
|
|
229
285
|
# Calculate full public key(64 bytes) from public key(32 bytes).
|
@@ -280,7 +336,7 @@ module Bitcoin
|
|
280
336
|
signature = FFI::MemoryPointer.new(:uchar, 64)
|
281
337
|
msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
|
282
338
|
aux_rand = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, aux_rand) if aux_rand
|
283
|
-
raise 'Failed to generate schnorr signature.' unless
|
339
|
+
raise 'Failed to generate schnorr signature.' unless secp256k1_schnorrsig_sign32(context, signature, msg32, keypair, aux_rand) == 1
|
284
340
|
signature.read_string(64)
|
285
341
|
end
|
286
342
|
end
|
@@ -316,7 +372,7 @@ module Bitcoin
|
|
316
372
|
xonly_pubkey = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
|
317
373
|
signature = FFI::MemoryPointer.new(:uchar, sig.bytesize).put_bytes(0, sig)
|
318
374
|
msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
|
319
|
-
result = secp256k1_schnorrsig_verify(context, signature, msg32, xonly_pubkey)
|
375
|
+
result = secp256k1_schnorrsig_verify(context, signature, msg32, 32, xonly_pubkey)
|
320
376
|
result == 1
|
321
377
|
end
|
322
378
|
end
|
@@ -77,13 +77,30 @@ module Bitcoin
|
|
77
77
|
# @param [Boolean] compressed whether compressed public key or not.
|
78
78
|
# @return [Bitcoin::Key] Recovered public key.
|
79
79
|
def recover_compact(data, signature, rec, compressed)
|
80
|
+
group = Bitcoin::Secp256k1::GROUP
|
80
81
|
r = ECDSA::Format::IntegerOctetString.decode(signature[1...33])
|
81
82
|
s = ECDSA::Format::IntegerOctetString.decode(signature[33..-1])
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
83
|
+
return nil if r.zero?
|
84
|
+
return nil if s.zero?
|
85
|
+
|
86
|
+
digest = ECDSA.normalize_digest(data, group.bit_length)
|
87
|
+
field = ECDSA::PrimeField.new(group.order)
|
88
|
+
|
89
|
+
unless rec & 2 == 0
|
90
|
+
r = field.mod(r + group.order)
|
86
91
|
end
|
92
|
+
|
93
|
+
is_odd = (rec & 1 == 1)
|
94
|
+
y_coordinate = group.solve_for_y(r).find{|y| is_odd ? y.odd? : y.even?}
|
95
|
+
|
96
|
+
p = group.new_point([r, y_coordinate])
|
97
|
+
|
98
|
+
inv_r = field.inverse(r)
|
99
|
+
u1 = field.mod(inv_r * digest)
|
100
|
+
u2 = field.mod(inv_r * s)
|
101
|
+
q = p * u2 + (group.new_point(u1)).negate
|
102
|
+
return nil if q.infinity?
|
103
|
+
Bitcoin::Key.from_point(q, compressed: compressed)
|
87
104
|
end
|
88
105
|
|
89
106
|
# verify signature using public key
|
data/lib/bitcoin/util.rb
CHANGED
@@ -131,10 +131,18 @@ module Bitcoin
|
|
131
131
|
double_sha256(hex.htb).bth[0..7]
|
132
132
|
end
|
133
133
|
|
134
|
-
DIGEST_NAME_SHA256 = 'sha256'
|
135
|
-
|
136
134
|
def hmac_sha256(key, data)
|
137
|
-
|
135
|
+
Bitcoin.hmac_sha256(key, data)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Run HKDF.
|
139
|
+
# @param [String] ikm The input keying material with binary format.
|
140
|
+
# @param [String] salt The salt with binary format.
|
141
|
+
# @param [String] info The context and application specific information with binary format.
|
142
|
+
# @param [Integer] length The output length in octets.
|
143
|
+
# @return [String] The result of HKDF with binary format.
|
144
|
+
def hkdf_sha256(ikm, salt, info, length = 32)
|
145
|
+
OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: length, hash: "SHA256")
|
138
146
|
end
|
139
147
|
|
140
148
|
# check whether +addr+ is valid address.
|
data/lib/bitcoin/version.rb
CHANGED
data/lib/bitcoin.rb
CHANGED
@@ -61,6 +61,7 @@ module Bitcoin
|
|
61
61
|
autoload :SigHashGenerator, 'bitcoin/sighash_generator'
|
62
62
|
autoload :MessageSign, 'bitcoin/message_sign'
|
63
63
|
autoload :Taproot, 'bitcoin/taproot'
|
64
|
+
autoload :BIP324, 'bitcoin/bip324'
|
64
65
|
|
65
66
|
require_relative 'bitcoin/constants'
|
66
67
|
require_relative 'bitcoin/ext/ecdsa'
|
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: 1.
|
4
|
+
version: 1.5.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: 2024-03-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ecdsa_ext
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.5.
|
19
|
+
version: 0.5.1
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.5.
|
26
|
+
version: 0.5.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: eventmachine
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -184,14 +184,14 @@ dependencies:
|
|
184
184
|
requirements:
|
185
185
|
- - ">="
|
186
186
|
- !ruby/object:Gem::Version
|
187
|
-
version: 0.
|
187
|
+
version: 0.7.0
|
188
188
|
type: :runtime
|
189
189
|
prerelease: false
|
190
190
|
version_requirements: !ruby/object:Gem::Requirement
|
191
191
|
requirements:
|
192
192
|
- - ">="
|
193
193
|
- !ruby/object:Gem::Version
|
194
|
-
version: 0.
|
194
|
+
version: 0.7.0
|
195
195
|
- !ruby/object:Gem::Dependency
|
196
196
|
name: base32
|
197
197
|
requirement: !ruby/object:Gem::Requirement
|
@@ -332,6 +332,11 @@ files:
|
|
332
332
|
- exe/bitcoinrbd
|
333
333
|
- lib/bitcoin.rb
|
334
334
|
- lib/bitcoin/base58.rb
|
335
|
+
- lib/bitcoin/bip324.rb
|
336
|
+
- lib/bitcoin/bip324/cipher.rb
|
337
|
+
- lib/bitcoin/bip324/ell_swift_pubkey.rb
|
338
|
+
- lib/bitcoin/bip324/fs_chacha20.rb
|
339
|
+
- lib/bitcoin/bip324/fs_chacha_poly1305.rb
|
335
340
|
- lib/bitcoin/bip85_entropy.rb
|
336
341
|
- lib/bitcoin/bit_stream.rb
|
337
342
|
- lib/bitcoin/block.rb
|
@@ -496,7 +501,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
496
501
|
- !ruby/object:Gem::Version
|
497
502
|
version: '0'
|
498
503
|
requirements: []
|
499
|
-
rubygems_version: 3.
|
504
|
+
rubygems_version: 3.5.3
|
500
505
|
signing_key:
|
501
506
|
specification_version: 4
|
502
507
|
summary: The implementation of Bitcoin Protocol for Ruby.
|