bitcoinrb 1.3.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 23f677d00426b94e0bdcee887901bb1e89a98c65ae61b0a025c7d95467dcb484
4
- data.tar.gz: 7426638ec33999d466026a09a91517fe698adc0a55f32db6d416dc3f4ad6a386
3
+ metadata.gz: 900806837ee6cd6b011a50d6a58c37ea233079e8b86a594c6a5031c338f9a490
4
+ data.tar.gz: 34b437fee384d630d9b7f1a47faa8175692d9ebe21408d4720828140461b152c
5
5
  SHA512:
6
- metadata.gz: 960aada8b3db4c610a2b206254f878af9c149f81ce42a53a2ebe8441e0a5179964a9a505807ae2b7519349c4a48125397acb20b412cfb84ea59d58957acd84e4
7
- data.tar.gz: 493c6e06d7fb686dda65fd4bc4075e3716004427beab0cce9e040060711ad4a7197d0fdece2838d130ed6b1b64cdc94a37957a8354d7629bf9bd68fc02d326e4
6
+ metadata.gz: 87bcfdb59403593475f90b49f9f67299bee3bfd8cfae7089080e95ab8e1ffddcdc21ebbb72f8ca49f1caea9d8df2ea5ca613eb97b698ddab3fb3d3c617611165
7
+ data.tar.gz: 55dcbb9ce3f10a5dd822e98605eb00fa74f3034a8852cdcd714b4dff5cd347cbdc55bffd49ae2f74cc4aeaad2bf86d01d635e7146207ccb6557f55ae0849ff0e
@@ -19,7 +19,7 @@ jobs:
19
19
  runs-on: ubuntu-latest
20
20
  strategy:
21
21
  matrix:
22
- ruby-version: ['3.0', '3.1', '3.2']
22
+ ruby-version: ['3.0', '3.1', '3.2', '3.3']
23
23
 
24
24
  steps:
25
25
  - uses: actions/checkout@v2
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-3.2.0
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,9 +20,9 @@ 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.0'
23
+ spec.add_runtime_dependency 'ecdsa_ext', '~> 0.5.1'
24
24
  spec.add_runtime_dependency 'eventmachine'
25
- spec.add_runtime_dependency 'murmurhash3'
25
+ spec.add_runtime_dependency 'murmurhash3', '~> 0.1.7'
26
26
  spec.add_runtime_dependency 'bech32', '>= 1.3.0'
27
27
  spec.add_runtime_dependency 'daemon-spawn'
28
28
  spec.add_runtime_dependency 'thor'
@@ -31,9 +31,8 @@ Gem::Specification.new do |spec|
31
31
  spec.add_runtime_dependency 'eventmachine_httpserver'
32
32
  spec.add_runtime_dependency 'iniparse'
33
33
  spec.add_runtime_dependency 'siphash'
34
- spec.add_runtime_dependency 'protobuf', '3.8.5'
35
34
  spec.add_runtime_dependency 'json_pure', '>= 2.3.1'
36
- spec.add_runtime_dependency 'bip-schnorr', '>= 0.5.0'
35
+ spec.add_runtime_dependency 'bip-schnorr', '>= 0.7.0'
37
36
  spec.add_runtime_dependency 'base32', '>= 0.3.4'
38
37
 
39
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
@@ -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
@@ -52,7 +52,7 @@ module Bitcoin
52
52
 
53
53
  # generate key pair
54
54
  def self.generate(key_type = TYPES[:compressed])
55
- priv_key, pubkey = Bitcoin.secp_impl.generate_key_pair
55
+ priv_key, pubkey = Bitcoin.secp_impl.generate_key_pair(compressed: key_type != TYPES[:uncompressed])
56
56
  new(priv_key: priv_key, pubkey: pubkey, key_type: key_type)
57
57
  end
58
58
 
@@ -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 > 15
151
- rec = rec & 3
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
@@ -261,6 +261,12 @@ module Bitcoin
261
261
  pubkey[2..65]
262
262
  end
263
263
 
264
+ # Convert this key to decompress key.
265
+ # @return [String] decompress public key with hex format.
266
+ def decompress_pubkey
267
+ pubkey.htb.bytesize == PUBLIC_KEY_SIZE ? pubkey : to_point.to_hex(false)
268
+ end
269
+
264
270
  # check +pubkey+ (hex) is compress or uncompress pubkey.
265
271
  def self.compress_or_uncompress_pubkey?(pubkey)
266
272
  p = pubkey.htb
@@ -338,6 +344,18 @@ module Bitcoin
338
344
  valid_pubkey? && secp256k1_module.parse_ec_pubkey?(pubkey, allow_hybrid)
339
345
  end
340
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
+
341
359
  private
342
360
 
343
361
  def self.compare_big_endian(c1, c2)
@@ -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
- validate_address!(address)
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
- begin
60
- # Legacy verification
61
- pubkey = Bitcoin::Key.recover_compact(message_hash(message, prefix: prefix, legacy: true), sig)
62
- return false unless pubkey
63
- pubkey.to_p2pkh == address
64
- rescue ArgumentError
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
@@ -233,11 +233,28 @@ module Bitcoin
233
233
  OP_HASH160 == chunks[0].ord && OP_EQUAL == chunks[2].ord && chunks[1].bytesize == 21
234
234
  end
235
235
 
236
+ # Check whether this script is a P2PK format script.
237
+ # @return [Boolean] if P2PK return true, otherwise false
238
+ def p2pk?
239
+ return false unless chunks.size == 2
240
+ return false unless chunks[0].pushdata?
241
+ key_type = chunks[0].pushed_data[0].ord
242
+ case key_type
243
+ when 0x02, 0x03
244
+ return false unless chunks[0].pushed_data.bytesize == 33
245
+ when 0x04
246
+ return false unless chunks[0].pushed_data.bytesize == 65
247
+ else
248
+ return false
249
+ end
250
+ chunks[1].ord == OP_CHECKSIG
251
+ end
252
+
236
253
  def multisig?
237
254
  return false if chunks.size < 4 || chunks.last.ord != OP_CHECKMULTISIG
238
255
  pubkey_count = Opcodes.opcode_to_small_int(chunks[-2].opcode)
239
256
  sig_count = Opcodes.opcode_to_small_int(chunks[0].opcode)
240
- return false unless pubkey_count || sig_count
257
+ return false if pubkey_count.nil? || sig_count.nil?
241
258
  sig_count <= pubkey_count
242
259
  end
243
260
 
@@ -5,7 +5,7 @@ module Bitcoin
5
5
  module Secp256k1
6
6
 
7
7
  # binding for secp256k1 (https://github.com/bitcoin-core/secp256k1/)
8
- # commit: efad3506a8937162e8010f5839fdf3771dfcf516
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(:secp256k1_schnorrsig_sign, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
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 secp256k1_schnorrsig_sign(context, signature, msg32, keypair, nil, aux_rand) == 1
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
- ECDSA.recover_public_key(Bitcoin::Secp256k1::GROUP, data, ECDSA::Signature.new(r, s)).each do |p|
83
- if p.y & 1 == rec
84
- return Bitcoin::Key.from_point(p, compressed: compressed)
85
- end
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
- OpenSSL::HMAC.digest(DIGEST_NAME_SHA256, key, data)
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.
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "1.3.0"
2
+ VERSION = "1.5.0"
3
3
  end
data/lib/bitcoin.rb CHANGED
@@ -46,7 +46,6 @@ module Bitcoin
46
46
  autoload :RPC, 'bitcoin/rpc'
47
47
  autoload :Wallet, 'bitcoin/wallet'
48
48
  autoload :BloomFilter, 'bitcoin/bloom_filter'
49
- autoload :Payments, 'bitcoin/payments'
50
49
  autoload :PSBT, 'bitcoin/psbt'
51
50
  autoload :GCSFilter, 'bitcoin/gcs_filter'
52
51
  autoload :BlockFilter, 'bitcoin/block_filter'
@@ -62,6 +61,7 @@ module Bitcoin
62
61
  autoload :SigHashGenerator, 'bitcoin/sighash_generator'
63
62
  autoload :MessageSign, 'bitcoin/message_sign'
64
63
  autoload :Taproot, 'bitcoin/taproot'
64
+ autoload :BIP324, 'bitcoin/bip324'
65
65
 
66
66
  require_relative 'bitcoin/constants'
67
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.3.0
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: 2023-04-07 00:00:00.000000000 Z
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.0
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.0
26
+ version: 0.5.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: eventmachine
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -42,16 +42,16 @@ dependencies:
42
42
  name: murmurhash3
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: 0.1.7
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: 0.1.7
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: bech32
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -164,20 +164,6 @@ dependencies:
164
164
  - - ">="
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
- - !ruby/object:Gem::Dependency
168
- name: protobuf
169
- requirement: !ruby/object:Gem::Requirement
170
- requirements:
171
- - - '='
172
- - !ruby/object:Gem::Version
173
- version: 3.8.5
174
- type: :runtime
175
- prerelease: false
176
- version_requirements: !ruby/object:Gem::Requirement
177
- requirements:
178
- - - '='
179
- - !ruby/object:Gem::Version
180
- version: 3.8.5
181
167
  - !ruby/object:Gem::Dependency
182
168
  name: json_pure
183
169
  requirement: !ruby/object:Gem::Requirement
@@ -198,14 +184,14 @@ dependencies:
198
184
  requirements:
199
185
  - - ">="
200
186
  - !ruby/object:Gem::Version
201
- version: 0.5.0
187
+ version: 0.7.0
202
188
  type: :runtime
203
189
  prerelease: false
204
190
  version_requirements: !ruby/object:Gem::Requirement
205
191
  requirements:
206
192
  - - ">="
207
193
  - !ruby/object:Gem::Version
208
- version: 0.5.0
194
+ version: 0.7.0
209
195
  - !ruby/object:Gem::Dependency
210
196
  name: base32
211
197
  requirement: !ruby/object:Gem::Requirement
@@ -346,6 +332,11 @@ files:
346
332
  - exe/bitcoinrbd
347
333
  - lib/bitcoin.rb
348
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
349
340
  - lib/bitcoin/bip85_entropy.rb
350
341
  - lib/bitcoin/bit_stream.rb
351
342
  - lib/bitcoin/block.rb
@@ -439,13 +430,6 @@ files:
439
430
  - lib/bitcoin/opcodes.rb
440
431
  - lib/bitcoin/out_point.rb
441
432
  - lib/bitcoin/payment_code.rb
442
- - lib/bitcoin/payments.rb
443
- - lib/bitcoin/payments/output.pb.rb
444
- - lib/bitcoin/payments/payment.pb.rb
445
- - lib/bitcoin/payments/payment_ack.pb.rb
446
- - lib/bitcoin/payments/payment_details.pb.rb
447
- - lib/bitcoin/payments/payment_request.pb.rb
448
- - lib/bitcoin/payments/x509_certificates.pb.rb
449
433
  - lib/bitcoin/psbt.rb
450
434
  - lib/bitcoin/psbt/hd_key_path.rb
451
435
  - lib/bitcoin/psbt/input.rb
@@ -517,7 +501,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
517
501
  - !ruby/object:Gem::Version
518
502
  version: '0'
519
503
  requirements: []
520
- rubygems_version: 3.4.1
504
+ rubygems_version: 3.5.3
521
505
  signing_key:
522
506
  specification_version: 4
523
507
  summary: The implementation of Bitcoin Protocol for Ruby.
@@ -1,20 +0,0 @@
1
- module Bitcoin
2
- module Payments
3
-
4
- # https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki#Output
5
- class Output < Protobuf::Message
6
-
7
- optional :uint64, :amount, 1, {default: 0}
8
-
9
- required :bytes, :script, 2
10
-
11
- # convert to TxOut object.
12
- # @return [Bitcoin::TxOut]
13
- def to_tx_out
14
- Bitcoin::TxOut.new(value: amount, script_pubkey: Bitcoin::Script.parse_from_payload(script))
15
- end
16
-
17
- end
18
-
19
- end
20
- end
@@ -1,26 +0,0 @@
1
- module Bitcoin
2
- module Payments
3
-
4
- # https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki#Payment
5
- class Payment < Protobuf::Message
6
-
7
- optional :bytes, :merchant_data, 1
8
-
9
- repeated :bytes, :transactions, 2
10
-
11
- repeated Bitcoin::Payments::Output, :refund_to, 3
12
-
13
- optional :string, :memo, 4
14
-
15
- def self.parse_from_payload(payload)
16
- decode(payload)
17
- end
18
-
19
- def transactions
20
- @values[:transactions].map{|raw_tx|Bitcoin::Tx.parse_from_payload(raw_tx, strict: true)}
21
- end
22
-
23
- end
24
-
25
- end
26
- end
@@ -1,17 +0,0 @@
1
- module Bitcoin
2
- module Payments
3
-
4
- # https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki#PaymentACK
5
- class PaymentACK < Protobuf::Message
6
-
7
- required Bitcoin::Payments::Payment, :payment, 1
8
-
9
- optional :string, :memo, 2
10
-
11
- def self.parse_from_payload(payload)
12
- decode(payload)
13
- end
14
- end
15
-
16
- end
17
- end
@@ -1,24 +0,0 @@
1
- module Bitcoin
2
- module Payments
3
-
4
- # https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki#PaymentDetailsPaymentRequest
5
- class PaymentDetails < Protobuf::Message
6
-
7
- optional :string, :network, 1, {default: 'main'}
8
-
9
- repeated Bitcoin::Payments::Output, :outputs, 2
10
-
11
- required :uint64, :time, 3
12
-
13
- optional :uint64, :expires, 4
14
-
15
- optional :string, :memo, 5
16
-
17
- optional :string, :payment_url, 6
18
-
19
- optional :bytes, :merchant_data, 7
20
-
21
- end
22
-
23
- end
24
- end
@@ -1,79 +0,0 @@
1
- module Bitcoin
2
- module Payments
3
-
4
- # https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki#PaymentDetailsPaymentRequest
5
- class PaymentRequest < Protobuf::Message
6
-
7
- optional :uint32, :payment_details_version, 1, {default: 1}
8
-
9
- optional :string, :pki_type, 2, {default: 'none'}
10
-
11
- optional :bytes, :pki_data, 3
12
-
13
- required :bytes, :serialized_payment_details, 4
14
-
15
- optional :bytes, :signature, 5
16
-
17
- def self.parse_from_payload(payload)
18
- self.decode(payload)
19
- end
20
-
21
- # verify +pki_data+.
22
- # @return [Struct] pki information.
23
- def verify_pki_data
24
- d = Struct.new(:display_name, :merchant_sign_key, :root_auth, :root_auth_name)
25
- d
26
- end
27
-
28
- # get payment details
29
- # @return [Bitcoin::Payments:PaymentDetails]
30
- def details
31
- PaymentDetails.decode(serialized_payment_details)
32
- end
33
-
34
- # get certificates
35
- # @return [Array[OpenSSL::X509::Certificate]]
36
- def certs
37
- return [] unless has_pki?
38
- X509Certificates.decode(pki_data).certs
39
- end
40
-
41
- # whether exist +pki_data+.
42
- def has_pki?
43
- pki_type != 'none'
44
- end
45
-
46
- # verify signature.
47
- def valid_sig?
48
- return false unless has_pki?
49
- digest = case pki_type
50
- when 'x509+sha256'
51
- OpenSSL::Digest::SHA256.new
52
- when 'x509+sha1'
53
- OpenSSL::Digest::SHA1.new
54
- else
55
- raise "pki_type: #{pki_type} is invalid type."
56
- end
57
- certs.first.public_key.verify(digest, signature, sig_message)
58
- end
59
-
60
- # verify expire time for payment request.
61
- def valid_time?
62
- expires = details.expires
63
- return true if expires == 0
64
- Time.now.to_i <= expires
65
- end
66
-
67
- private
68
-
69
- # Generate data to be signed
70
- def sig_message
71
- PaymentRequest.new(payment_details_version: payment_details_version,
72
- pki_type: pki_type, pki_data: pki_data, signature: '',
73
- serialized_payment_details: serialized_payment_details).encode
74
- end
75
-
76
- end
77
-
78
- end
79
- end
@@ -1,18 +0,0 @@
1
- module Bitcoin
2
- module Payments
3
-
4
- # https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki#Certificates
5
- class X509Certificates < Protobuf::Message
6
-
7
- repeated :bytes, :certificate, 1
8
-
9
- # get certificates
10
- # @return [Array[OpenSSL::X509::Certificate]]
11
- def certs
12
- certificate.map{|v|OpenSSL::X509::Certificate.new(v)}
13
- end
14
-
15
- end
16
-
17
- end
18
- end
@@ -1,14 +0,0 @@
1
- require 'protobuf'
2
-
3
- module Bitcoin
4
- module Payments
5
-
6
- autoload :Output, 'bitcoin/payments/output.pb'
7
- autoload :Payment, 'bitcoin/payments/payment.pb'
8
- autoload :PaymentACK, 'bitcoin/payments/payment_ack.pb'
9
- autoload :PaymentDetails, 'bitcoin/payments/payment_details.pb'
10
- autoload :PaymentRequest, 'bitcoin/payments/payment_request.pb'
11
- autoload :X509Certificates, 'bitcoin/payments/x509_certificates.pb'
12
-
13
- end
14
- end