bitcoinrb 1.6.0 → 1.8.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: f2697b9fbcca175453c80fc67d70466845c7141e6b9e9f193b5825d34bd7ffa3
4
- data.tar.gz: 61a5c26a7cfaf7abcc7c692386ff08091ea0249c9e8ea9ccb4f3231049319f47
3
+ metadata.gz: a0cc5450016d6ffbb76d36dcccd415a485a8532ab8414d9beddb9a7e25305dba
4
+ data.tar.gz: 75d509c36069ce2dee667a0330962ae310c8062f24aa01d299e5692f6f069461
5
5
  SHA512:
6
- metadata.gz: 40574931606fc1ff074f638bda7042f86c0225a5661f37622971e0b9502e039c5010d31663c34846c80f0bff2d25a02122eb4773e30a6fadad72bb170260b114
7
- data.tar.gz: aaefa2672459d1d133191aecf8cbe0e8391210647e109c6e5205e349e264ad2303b25c36d4812daf5f5f0e88fb15cc4b0bfc54653ba0a2b4a8bac9fd5fd9b5d6
6
+ metadata.gz: c2457464548bd24c937eaf55aa53ff2b7ca01b5db0b57b66d7e6fef60142cde6ff2752d670e06bd6052470b6eef25af9f3b676ffe1842fd98c1fd9d590888556
7
+ data.tar.gz: 95d5649e3cacc1a155b0e033f0275d9f1806a9e39aad2bbe0c9b61809a10b76d117dddf94c0c68eae50e9dd52aa8e3f266244e2a400821d9172e2706a371c654
@@ -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', '3.3']
22
+ ruby-version: ['3.1', '3.2', '3.3', '3.4']
23
23
 
24
24
  steps:
25
25
  - uses: actions/checkout@v4
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-3.3.0
1
+ ruby-3.4.1
data/Gemfile CHANGED
@@ -4,3 +4,11 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  gem 'parallel_tests'
7
+ gem 'leveldb-native'
8
+ gem 'bundler'
9
+ gem 'rake', '>= 12.3.3'
10
+ gem 'rspec', '~> 3.0'
11
+ gem 'timecop'
12
+ gem 'webmock', '>= 3.11.1'
13
+ gem 'parallel', '>= 1.20.1'
14
+ gem 'csv', '~> 3.3'
data/bitcoinrb.gemspec CHANGED
@@ -26,22 +26,14 @@ Gem::Specification.new do |spec|
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'
29
- spec.add_runtime_dependency 'ffi'
30
29
  spec.add_runtime_dependency 'leb128', '~> 1.0.0'
31
30
  spec.add_runtime_dependency 'eventmachine_httpserver'
32
31
  spec.add_runtime_dependency 'iniparse'
33
32
  spec.add_runtime_dependency 'siphash'
34
- spec.add_runtime_dependency 'json_pure', '>= 2.3.1'
33
+ spec.add_runtime_dependency 'json_pure', '>= 2.3.1', '< 2.8.0'
35
34
  spec.add_runtime_dependency 'bip-schnorr', '>= 0.7.0'
36
35
  spec.add_runtime_dependency 'base32', '>= 0.3.4'
37
-
38
- # for options
39
- spec.add_development_dependency 'leveldb-native'
40
-
41
- spec.add_development_dependency 'bundler'
42
- spec.add_development_dependency 'rake', '>= 12.3.3'
43
- spec.add_development_dependency 'rspec', '~> 3.0'
44
- spec.add_development_dependency 'timecop'
45
- spec.add_development_dependency 'webmock', '>= 3.11.1'
46
- spec.add_development_dependency 'parallel', '>= 1.20.1'
36
+ spec.add_runtime_dependency 'base64', '~> 0.2.0'
37
+ spec.add_runtime_dependency 'observer', '~> 0.1.2'
38
+ spec.add_runtime_dependency 'secp256k1rb', '0.1.1'
47
39
  end
@@ -19,7 +19,7 @@ module Bitcoin
19
19
  # Decode to public key.
20
20
  # @return [Bitcoin::Key] Decoded public key.
21
21
  def decode
22
- if Bitcoin.secp_impl.is_a?(Bitcoin::Secp256k1::Native)
22
+ if Bitcoin.secp_impl.native?
23
23
  pubkey = Bitcoin.secp_impl.ellswift_decode(key)
24
24
  Bitcoin::Key.new(pubkey: pubkey, key_type: Bitcoin::Key::TYPES[:compressed])
25
25
  else
@@ -131,8 +131,8 @@ module Bitcoin
131
131
  raise ArgumentError, "ellswift_theirs must be a Bitcoin::BIP324::EllSwiftPubkey" unless ellswift_theirs.is_a?(Bitcoin::BIP324::EllSwiftPubkey)
132
132
  raise ArgumentError, "ellswift_ours must be a Bitcoin::BIP324::EllSwiftPubkey" unless ellswift_ours.is_a?(Bitcoin::BIP324::EllSwiftPubkey)
133
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)
134
+ if Bitcoin.secp_impl.native?
135
+ Bitcoin::Secp256k1::Native.ellswift_ecdh_xonly(ellswift_theirs.key, ellswift_ours.key, priv_key, initiating)
136
136
  else
137
137
  ecdh_point_x32 = ellswift_ecdh_xonly(ellswift_theirs, priv_key).htb
138
138
  content = initiating ? ellswift_ours.key + ellswift_theirs.key + ecdh_point_x32 :
data/lib/bitcoin/block.rb CHANGED
@@ -4,11 +4,38 @@ module Bitcoin
4
4
  attr_accessor :header
5
5
  attr_accessor :transactions
6
6
 
7
+ # Constructor
8
+ # @param [Bitcoin::BlockHeader] header
9
+ # @param [Array] transactions An array of transaction.
10
+ # @raise [ArgumentError]
7
11
  def initialize(header, transactions = [])
12
+ raise ArgumentError, "header must be Bitcoin::BlockHeader." unless header.is_a?(Bitcoin::BlockHeader)
13
+ raise ArgumentError, "transactions must be an Array." unless transactions.is_a?(Array)
8
14
  @header = header
9
15
  @transactions = transactions
10
16
  end
11
17
 
18
+ # Create genesis block.
19
+ # @param [String] msg Message embedded in coinbase transaction.
20
+ # @param [Bitcoin::Script] script Coinbase transaction scriptPubkey.
21
+ # @param [Integer] time Block time.
22
+ # @param [Integer] nonce nonce.
23
+ # @param [Integer] bits nBits
24
+ # @param [Integer] version nVersion.
25
+ # @param [Integer] rewards Block rewards(satoshi).
26
+ def self.create_genesis(msg, script, time, nonce, bits, version, rewards = 50 * 100000000)
27
+ coinbase = Bitcoin::Tx.create_coinbase(msg, script, rewards)
28
+ header = BlockHeader.new(
29
+ version,
30
+ '00' * 32,
31
+ MerkleTree.build_from_leaf([coinbase.txid]).merkle_root.rhex,
32
+ time,
33
+ bits,
34
+ nonce
35
+ )
36
+ Block.new(header, [coinbase])
37
+ end
38
+
12
39
  def self.parse_from_payload(payload)
13
40
  Bitcoin::Message::Block.parse_from_payload(payload).to_block
14
41
  end
@@ -56,6 +56,11 @@ module Bitcoin
56
56
  init('signet')
57
57
  end
58
58
 
59
+ # testnet 4 genesis
60
+ def self.testnet4
61
+ init('testnet4')
62
+ end
63
+
59
64
  def mainnet?
60
65
  network == 'mainnet'
61
66
  end
@@ -72,6 +77,10 @@ module Bitcoin
72
77
  network == 'signet'
73
78
  end
74
79
 
80
+ def testnet4?
81
+ network == 'testnet4'
82
+ end
83
+
75
84
  def genesis_block
76
85
  header = Bitcoin::BlockHeader.new(
77
86
  genesis['version'], genesis['prev_hash'].rhex, genesis['merkle_root'].rhex,
@@ -27,9 +27,13 @@ proof_of_work_limit: 0x1d00ffff
27
27
  dns_seeds:
28
28
  - "seed.bitcoin.sipa.be"
29
29
  - "dnsseed.bluematt.me"
30
- - "dnsseed.bitcoin.dashjr.org"
31
- - "seed.bitcoinstats.com"
30
+ - "dnsseed.bitcoin.dashjr-list-of-p2p-nodes.us"
32
31
  - "seed.bitcoin.jonasschnelli.ch"
32
+ - "seed.btc.petertodd.net"
33
+ - "seed.bitcoin.sprovoost.nl"
34
+ - "dnsseed.emzy.de"
35
+ - "seed.bitcoin.wiz.biz"
36
+ - "seed.mainnet.achownodes.xyz"
33
37
  genesis:
34
38
  hash: "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
35
39
  merkle_root: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
@@ -22,8 +22,7 @@ retarget_interval: 2016
22
22
  retarget_time: 1209600 # 2 weeks
23
23
  target_spacing: 600 # block interval
24
24
  max_money: 21000000
25
- bip34_height: 0
26
- genesis_hash: "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"
25
+ bip34_height: 1
27
26
  proof_of_work_limit: 0x207fffff
28
27
  dns_seeds:
29
28
  genesis:
@@ -22,12 +22,11 @@ retarget_interval: 2016
22
22
  retarget_time: 1209600 # 2 weeks
23
23
  target_spacing: 600 # block interval
24
24
  max_money: 21000000
25
- bip34_height: 227931
26
- genesis_hash: "00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6"
25
+ bip34_height: 1
27
26
  proof_of_work_limit: 0x1d00ffff
28
27
  dns_seeds:
29
- - "178.128.221.177"
30
- - "2a01:7c8:d005:390::5"
28
+ - "seed.signet.bitcoin.sprovoost.nl"
29
+ - "seed.signet.achownodes.xyz"
31
30
  genesis:
32
31
  hash: "00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6"
33
32
  merkle_root: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
@@ -22,14 +22,14 @@ retarget_interval: 2016
22
22
  retarget_time: 1209600 # 2 weeks
23
23
  target_spacing: 600 # block interval
24
24
  max_money: 21000000
25
- bip34_height: 227931
26
- genesis_hash: "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
25
+ bip34_height: 21111
27
26
  proof_of_work_limit: 0x1d00ffff
28
27
  dns_seeds:
29
28
  - "testnet-seed.bitcoin.jonasschnelli.ch"
30
- - "seed.tbtc.petertodd.org"
29
+ - "seed.tbtc.petertodd.net"
30
+ - "seed.testnet.bitcoin.sprovoost.nl"
31
31
  - "testnet-seed.bluematt.me"
32
- - "testnet-seed.bitcoin.schildbach.de"
32
+ - "seed.testnet.achownodes.xyz"
33
33
  genesis:
34
34
  hash: "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
35
35
  merkle_root: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
@@ -0,0 +1,38 @@
1
+ --- !ruby/object:Bitcoin::ChainParams
2
+ network: "testnet4"
3
+ magic_head: "1c163f28"
4
+ message_magic: "Bitcoin Signed Message:\n"
5
+ address_version: "6f"
6
+ p2sh_version: "c4"
7
+ bech32_hrp: 'tb'
8
+ privkey_version: "ef"
9
+ extended_privkey_version: "04358394"
10
+ extended_pubkey_version: "043587cf"
11
+ bip49_pubkey_p2wpkh_p2sh_version: "044a5262"
12
+ bip49_pubkey_p2wsh_p2sh_version: "024289ef"
13
+ bip49_privkey_p2wpkh_p2sh_version: "044a4e28"
14
+ bip49_privkey_p2wsh_p2sh_version: "024285b5"
15
+ bip84_pubkey_p2wpkh_version: "045f1cf6"
16
+ bip84_pubkey_p2wsh_version: "02575483"
17
+ bip84_privkey_p2wpkh_version: "045f18bc"
18
+ bip84_privkey_p2wsh_version: "02575048"
19
+ default_port: 48333
20
+ protocol_version: 70013
21
+ retarget_interval: 2016
22
+ retarget_time: 1209600 # 2 weeks
23
+ target_spacing: 600 # block interval
24
+ max_money: 21000000
25
+ bip34_height: 1
26
+ proof_of_work_limit: 0x1d00ffff
27
+ dns_seeds:
28
+ - "seed.testnet4.bitcoin.sprovoost.nl"
29
+ - "seed.testnet4.wiz.biz"
30
+ genesis:
31
+ hash: "00000000da84f2bafbbc53dee25a72ae507ff4914b867c565be350b0da8bf043"
32
+ merkle_root: "7aa0a7ae1e223414cb807e40cd57e667b718e42aaf9306db9102fe28912b7b4e"
33
+ time: 1714777860
34
+ nonce: 393743547
35
+ bits: 0x1d00ffff
36
+ version: 1
37
+ prev_hash: "0000000000000000000000000000000000000000000000000000000000000000"
38
+ bip44_coin_type: 1
@@ -9,7 +9,7 @@ module Bitcoin
9
9
 
10
10
  def to_scripts
11
11
  candidates = [Pk.new(key), Pkh.new(key)]
12
- pubkey = extract_pubkey(key)
12
+ pubkey = extracted_key
13
13
  if pubkey.compressed?
14
14
  candidates << Wpkh.new(pubkey.pubkey)
15
15
  candidates << Sh.new(candidates.last)
@@ -18,6 +18,13 @@ module Bitcoin
18
18
  def top_level?
19
19
  false
20
20
  end
21
+
22
+ # Get extracted key.
23
+ # @return [Bitcoin::Key] Extracted key.
24
+ def extracted_key
25
+ extract_pubkey(key)
26
+ end
27
+
21
28
  end
22
29
  end
23
30
  end
@@ -18,7 +18,7 @@ module Bitcoin
18
18
  # Convert to bitcoin script.
19
19
  # @return [Bitcoin::Script]
20
20
  def to_script
21
- k = extract_pubkey(key)
21
+ k = extracted_key
22
22
  target_key = xonly ? k.xonly_pubkey : k.pubkey
23
23
  Bitcoin::Script.new << target_key << OP_CHECKSIG
24
24
  end
@@ -8,7 +8,7 @@ module Bitcoin
8
8
  end
9
9
 
10
10
  def to_script
11
- Script.to_p2pkh(extract_pubkey(key).hash160)
11
+ Script.to_p2pkh(extracted_key.hash160)
12
12
  end
13
13
  end
14
14
  end
@@ -0,0 +1,20 @@
1
+ module Bitcoin
2
+ module Descriptor
3
+ # rawtr() expression
4
+ class RawTr < KeyExpression
5
+ include Bitcoin::Opcodes
6
+
7
+ def type
8
+ :rawtr
9
+ end
10
+
11
+ def top_level?
12
+ true
13
+ end
14
+
15
+ def to_script
16
+ Bitcoin::Script.new << OP_1 << extract_pubkey(key).xonly_pubkey
17
+ end
18
+ end
19
+ end
20
+ end
@@ -12,7 +12,7 @@ module Bitcoin
12
12
  end
13
13
 
14
14
  def to_script
15
- Script.to_p2wpkh(extract_pubkey(key).hash160)
15
+ Script.to_p2wpkh(extracted_key.hash160)
16
16
  end
17
17
  end
18
18
  end
@@ -19,6 +19,7 @@ module Bitcoin
19
19
  autoload :Tr, 'bitcoin/descriptor/tr'
20
20
  autoload :MultiA, 'bitcoin/descriptor/multi_a'
21
21
  autoload :SortedMultiA, 'bitcoin/descriptor/sorted_multi_a'
22
+ autoload :RawTr, 'bitcoin/descriptor/raw_tr'
22
23
  autoload :Checksum, 'bitcoin/descriptor/checksum'
23
24
 
24
25
  module_function
@@ -104,6 +105,13 @@ module Bitcoin
104
105
  Tr.new(key, tree)
105
106
  end
106
107
 
108
+ # Generate taproot output script descriptor.
109
+ # @param [String] key
110
+ # @return [Bitcoin::Descriptor::RawTr]
111
+ def rawtr(key)
112
+ RawTr.new(key)
113
+ end
114
+
107
115
  # Generate tapscript multisig output for given keys.
108
116
  # @param [Integer] threshold the threshold of multisig.
109
117
  # @param [Array[String]] keys an array of keys.
@@ -169,6 +177,8 @@ module Bitcoin
169
177
  else
170
178
  tr(key, parse(rest, false))
171
179
  end
180
+ when 'rawtr'
181
+ rawtr(args_str)
172
182
  else
173
183
  raise ArgumentError, "Parse failed: #{string}"
174
184
  end
data/lib/bitcoin/key.rb CHANGED
@@ -146,11 +146,8 @@ 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) & 3
150
- raise ArgumentError, 'Invalid signature parameter' if rec < 0 || rec > 3
151
-
152
149
  compressed = (rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE) & 4 != 0
153
- Bitcoin.secp_impl.recover_compact(data, signature, rec, compressed)
150
+ Bitcoin.secp_impl.recover_compact(data, signature, compressed)
154
151
  end
155
152
 
156
153
  # verify signature using public key
@@ -349,7 +346,7 @@ module Bitcoin
349
346
  # @raise ArgumentError If ent32 does not 32 bytes.
350
347
  def create_ell_pubkey
351
348
  raise ArgumentError, "private_key required." unless priv_key
352
- if secp256k1_module.is_a?(Bitcoin::Secp256k1::Native)
349
+ if secp256k1_module.native?
353
350
  Bitcoin::BIP324::EllSwiftPubkey.new(secp256k1_module.ellswift_create(priv_key))
354
351
  else
355
352
  Bitcoin::BIP324::EllSwiftPubkey.new(Bitcoin::BIP324.xelligatorswift(xonly_pubkey))
@@ -62,16 +62,27 @@ module Bitcoin
62
62
  pubkey = Bitcoin::Key.recover_compact(message_hash(message, prefix: prefix, legacy: true), sig)
63
63
  return false unless pubkey
64
64
  pubkey.to_p2pkh == address
65
- rescue RuntimeError
66
- return false
65
+ rescue Exception
66
+ false
67
67
  end
68
68
  elsif addr_script.witness_program?
69
69
  # BIP322 verification
70
- tx = to_sign_tx(message_hash(message, prefix: prefix, legacy: false), address)
71
- tx.in[0].script_witness = Bitcoin::ScriptWitness.parse_from_payload(sig)
70
+ digest = message_hash(message, prefix: prefix, legacy: false)
71
+ begin
72
+ # Full
73
+ tx = Bitcoin::Tx.parse_from_payload(sig)
74
+ validate_to_sign_tx!(tx)
75
+ to_spend = to_spend_tx(digest, address)
76
+ return false unless tx.in[0].out_point.tx_hash == to_spend.tx_hash
77
+ rescue Exception
78
+ # Simple
79
+ tx = to_sign_tx(digest, address)
80
+ tx.in[0].script_witness = Bitcoin::ScriptWitness.parse_from_payload(sig)
81
+ end
72
82
  script_pubkey = Bitcoin::Script.parse_from_addr(address)
73
83
  tx_out = Bitcoin::TxOut.new(script_pubkey: script_pubkey)
74
- interpreter = Bitcoin::ScriptInterpreter.new(checker: Bitcoin::TxChecker.new(tx: tx, input_index: 0, prevouts: [tx_out]))
84
+ flags = Bitcoin::STANDARD_SCRIPT_VERIFY_FLAGS
85
+ interpreter = Bitcoin::ScriptInterpreter.new(flags: flags, checker: Bitcoin::TxChecker.new(tx: tx, input_index: 0, prevouts: [tx_out]))
75
86
  interpreter.verify_script(Bitcoin::Script.new, script_pubkey, tx.in[0].script_witness)
76
87
  else
77
88
  raise ArgumentError, "This address unsupported."
@@ -98,6 +109,14 @@ module Bitcoin
98
109
  end
99
110
  end
100
111
 
112
+ def validate_to_sign_tx!(tx)
113
+ raise ArgumentError, "Multiple inputs (proof of funds) are not supported." unless tx.in.length == 1
114
+ raise ArgumentError, "vin[0].prevout.n must be 0." unless tx.in[0].out_point.index == 0
115
+ raise ArgumentError, "Multiple outputs are not supported." unless tx.out.length == 1
116
+ raise ArgumentError, "vout[0].nValue must be 0." unless tx.out[0].value == 0
117
+ raise ArgumentError, "vout[0].scriptPubKey must be OP_RETURN." unless tx.out[0].script_pubkey == Bitcoin::Script.new << Bitcoin::Opcodes::OP_RETURN
118
+ end
119
+
101
120
  def to_spend_tx(digest, addr)
102
121
  validate_address!(addr)
103
122
  message_challenge = Bitcoin::Script.parse_from_addr(addr)
@@ -123,5 +142,6 @@ module Bitcoin
123
142
 
124
143
  private_class_method :validate_address!
125
144
  private_class_method :validate_format!
145
+ private_class_method :validate_to_sign_tx!
126
146
  end
127
147
  end
data/lib/bitcoin/psbt.rb CHANGED
@@ -1,5 +1,3 @@
1
- require 'base64'
2
-
3
1
  module Bitcoin
4
2
 
5
3
  module PSBT
@@ -11,6 +11,7 @@ module Bitcoin
11
11
 
12
12
  def self.parse_from_payload(payload)
13
13
  buf = payload.is_a?(StringIO) ? payload : StringIO.new(payload)
14
+ return self.new if buf.eof?
14
15
  size = Bitcoin.unpack_var_int_from_io(buf)
15
16
  stack = size.times.map do
16
17
  buf.read(Bitcoin.unpack_var_int_from_io(buf))
@@ -1,5 +1,6 @@
1
1
  # Porting part of the code from bitcoin-ruby. see the license.
2
2
  # https://github.com/lian/bitcoin-ruby/blob/master/COPYING
3
+ require 'secp256k1'
3
4
 
4
5
  module Bitcoin
5
6
  module Secp256k1
@@ -10,91 +11,15 @@ module Bitcoin
10
11
  # for linux, ENV['SECP256K1_LIB_PATH'] = '/usr/local/lib/libsecp256k1.so' or '/usr/lib64/libsecp256k1.so'
11
12
  # for mac,
12
13
  module Native
13
- include ::FFI::Library
14
- extend self
15
-
16
- SECP256K1_FLAGS_TYPE_MASK = ((1 << 8) - 1)
17
- SECP256K1_FLAGS_TYPE_CONTEXT = (1 << 0)
18
- SECP256K1_FLAGS_TYPE_COMPRESSION = (1 << 1)
19
-
20
- SECP256K1_FLAGS_BIT_CONTEXT_VERIFY = (1 << 8)
21
- SECP256K1_FLAGS_BIT_CONTEXT_SIGN = (1 << 9)
22
- SECP256K1_FLAGS_BIT_COMPRESSION = (1 << 8)
23
-
24
- # Flags to pass to secp256k1_context_create.
25
- SECP256K1_CONTEXT_VERIFY = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_VERIFY)
26
- SECP256K1_CONTEXT_SIGN = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN)
27
-
28
- # Flag to pass to secp256k1_ec_pubkey_serialize and secp256k1_ec_privkey_export.
29
- SECP256K1_EC_COMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION)
30
- SECP256K1_EC_UNCOMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION)
31
14
 
32
15
  module_function
33
16
 
34
- def init
35
- raise 'secp256k1 library dose not found.' unless File.exist?(ENV['SECP256K1_LIB_PATH'])
36
- ffi_lib(ENV['SECP256K1_LIB_PATH'])
37
- load_functions
38
- end
39
-
40
- def load_functions
41
- attach_function(:secp256k1_context_create, [:uint], :pointer)
42
- attach_function(:secp256k1_context_destroy, [:pointer], :void)
43
- attach_function(:secp256k1_context_randomize, [:pointer, :pointer], :int)
44
- attach_function(:secp256k1_ec_pubkey_create, [:pointer, :pointer, :pointer], :int)
45
- attach_function(:secp256k1_ec_seckey_verify, [:pointer, :pointer], :int)
46
- attach_function(:secp256k1_ecdsa_sign, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
47
- attach_function(:secp256k1_ec_pubkey_serialize, [:pointer, :pointer, :pointer, :pointer, :uint], :int)
48
- attach_function(:secp256k1_ecdsa_signature_serialize_der, [:pointer, :pointer, :pointer, :pointer], :int)
49
- attach_function(:secp256k1_ec_pubkey_parse, [:pointer, :pointer, :pointer, :size_t], :int)
50
- attach_function(:secp256k1_ecdsa_signature_parse_der, [:pointer, :pointer, :pointer, :size_t], :int)
51
- attach_function(:secp256k1_ecdsa_signature_normalize, [:pointer, :pointer, :pointer], :int)
52
- attach_function(:secp256k1_ecdsa_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
- attach_function(:secp256k1_keypair_create, [:pointer, :pointer, :pointer], :int)
56
- attach_function(:secp256k1_xonly_pubkey_parse, [:pointer, :pointer, :pointer], :int)
57
- attach_function(:secp256k1_ecdsa_sign_recoverable, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
58
- attach_function(:secp256k1_ecdsa_recoverable_signature_serialize_compact, [:pointer, :pointer, :pointer, :pointer], :int)
59
- attach_function(:secp256k1_ecdsa_recover, [:pointer, :pointer, :pointer, :pointer], :int)
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)
67
- end
68
-
69
- def with_context(flags: (SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN))
70
- init
71
- begin
72
- context = secp256k1_context_create(flags)
73
- ret, tries, max = 0, 0, 20
74
- while ret != 1
75
- raise 'secp256k1_context_randomize failed.' if tries >= max
76
- tries += 1
77
- ret = secp256k1_context_randomize(context, FFI::MemoryPointer.from_string(SecureRandom.random_bytes(32)))
78
- end
79
- yield(context) if block_given?
80
- ensure
81
- secp256k1_context_destroy(context)
82
- end
83
- end
17
+ extend ::Secp256k1
84
18
 
85
- # generate ec private key and public key
86
- def generate_key_pair(compressed: true)
87
- with_context do |context|
88
- ret, tries, max = 0, 0, 20
89
- while ret != 1
90
- raise 'secp256k1_ec_seckey_verify in generate_key_pair failed.' if tries >= max
91
- tries += 1
92
- priv_key = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, SecureRandom.random_bytes(32))
93
- ret = secp256k1_ec_seckey_verify(context, priv_key)
94
- end
95
- private_key = priv_key.read_string(32).bth
96
- [private_key , generate_pubkey_in_context(context, private_key, compressed: compressed) ]
97
- end
19
+ # Whether this module is native c wrapper or not?
20
+ # @return [Boolean]
21
+ def native?
22
+ true
98
23
  end
99
24
 
100
25
  # generate bitcoin key object
@@ -103,295 +28,76 @@ module Bitcoin
103
28
  Bitcoin::Key.new(priv_key: privkey, pubkey: pubkey, compressed: compressed)
104
29
  end
105
30
 
106
- def generate_pubkey(priv_key, compressed: true)
107
- with_context do |context|
108
- generate_pubkey_in_context(context, priv_key, compressed: compressed)
109
- end
110
- end
111
-
112
- # sign data.
113
- # @param [String] data a data to be signed with binary format
114
- # @param [String] privkey a private key with hex format using sign
115
- # @param [String] extra_entropy a extra entropy with binary format for rfc6979
116
- # @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
117
- # @return [String] signature data with binary format
118
- def sign_data(data, privkey, extra_entropy = nil, algo: :ecdsa)
119
- case algo
120
- when :ecdsa
121
- sign_ecdsa(data, privkey, extra_entropy)
122
- when :schnorr
123
- sign_schnorr(data, privkey, extra_entropy)
124
- else
125
- nil
126
- end
127
- end
128
-
129
31
  # Sign data with compact format.
130
32
  # @param [String] data a data to be signed with binary format
131
33
  # @param [String] privkey a private key using sign with hex format
132
34
  # @return [Array[signature, recovery id]]
133
35
  def sign_compact(data, privkey)
134
- with_context do |context|
135
- sig = FFI::MemoryPointer.new(:uchar, 65)
136
- hash =FFI::MemoryPointer.new(:uchar, data.bytesize).put_bytes(0, data)
137
- priv_key = privkey.htb
138
- sec_key = FFI::MemoryPointer.new(:uchar, priv_key.bytesize).put_bytes(0, priv_key)
139
- result = secp256k1_ecdsa_sign_recoverable(context, sig, hash, sec_key, nil, nil)
140
- raise 'secp256k1_ecdsa_sign_recoverable failed.' if result == 0
141
-
142
- output = FFI::MemoryPointer.new(:uchar, 64)
143
- rec = FFI::MemoryPointer.new(:uint64)
144
- result = secp256k1_ecdsa_recoverable_signature_serialize_compact(context, output, rec, sig)
145
- raise 'secp256k1_ecdsa_recoverable_signature_serialize_compact failed.' unless result == 1
146
-
147
- raw_sig = output.read_string(64)
148
- [ECDSA::Signature.new(raw_sig[0...32].bti, raw_sig[32..-1].bti), rec.read(:int)]
149
- end
36
+ sig, rec_id = sign_recoverable(data, privkey)
37
+ [ECDSA::Signature.new(sig[0...64].to_i(16), sig[64..-1].to_i(16)), rec_id]
150
38
  end
151
39
 
152
40
  # Recover public key from compact signature.
153
41
  # @param [String] data message digest using signature.
154
- # @param [String] signature signature with binary format.
155
- # @param [Integer] rec recovery id.
42
+ # @param [String] signature signature with binary format(65 bytes).
156
43
  # @param [Boolean] compressed whether compressed public key or not.
157
44
  # @return [Bitcoin::Key] Recovered public key.
158
- def recover_compact(data, signature, rec, compressed)
159
- with_context do |context|
160
- sig = FFI::MemoryPointer.new(:uchar, 65)
161
- input = FFI::MemoryPointer.new(:uchar, 64).put_bytes(0, signature[1..-1])
162
- result = secp256k1_ecdsa_recoverable_signature_parse_compact(context, sig, input, rec)
163
- raise 'secp256k1_ecdsa_recoverable_signature_parse_compact failed.' unless result == 1
164
-
165
- pubkey = FFI::MemoryPointer.new(:uchar, 64)
166
- msg = FFI::MemoryPointer.new(:uchar, data.bytesize).put_bytes(0, data)
167
- result = secp256k1_ecdsa_recover(context, pubkey, sig, msg)
168
- raise 'secp256k1_ecdsa_recover failed.' unless result == 1
169
-
170
- pubkey = serialize_pubkey_internal(context, pubkey.read_string(64), compressed)
171
- Bitcoin::Key.new(pubkey: pubkey, compressed: compressed)
172
- end
45
+ def recover_compact(data, signature, compressed)
46
+ pubkey = recover(data, signature, compressed)
47
+ Bitcoin::Key.new(pubkey: pubkey, compressed: compressed)
173
48
  end
174
49
 
175
- # verify signature
176
- # @param [String] data a data with binary format.
177
- # @param [String] sig signature data with binary format
178
- # @param [String] pubkey a public key with hex format using verify.
179
- # # @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
180
- # @return [Boolean] verification result.
181
- def verify_sig(data, sig, pubkey, algo: :ecdsa)
50
+ # Sign to data.
51
+ # @param [String] data The 32-byte message hash being signed with binary format.
52
+ # @param [String] private_key a private key with hex format using sign.
53
+ # @param [String] extra_entropy a extra entropy with binary format for rfc6979.
54
+ # @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
55
+ # @return [String] signature data with binary format. If unsupported algorithm specified, return nil.
56
+ # @raise [ArgumentError] If invalid arguments specified.
57
+ def sign_data(data, private_key, extra_entropy = nil, algo: :ecdsa)
182
58
  case algo
183
59
  when :ecdsa
184
- verify_ecdsa(data, sig, pubkey)
60
+ begin
61
+ sign_ecdsa(data, private_key, extra_entropy)
62
+ rescue ArgumentError
63
+ false
64
+ end
185
65
  when :schnorr
186
- verify_schnorr(data, sig, pubkey)
66
+ begin
67
+ sign_schnorr(data, private_key, extra_entropy)
68
+ rescue ArgumentError
69
+ false
70
+ end
187
71
  else
188
- false
189
- end
190
- end
191
-
192
- # # validate whether this is a valid public key (more expensive than IsValid())
193
- # @param [String] pub_key public key with hex format.
194
- # @param [Boolean] allow_hybrid whether support hybrid public key.
195
- # @return [Boolean] If valid public key return true, otherwise false.
196
- def parse_ec_pubkey?(pub_key, allow_hybrid = false)
197
- pub_key = pub_key.htb
198
- return false if !allow_hybrid && ![0x02, 0x03, 0x04].include?(pub_key[0].ord)
199
- with_context do |context|
200
- pubkey = FFI::MemoryPointer.new(:uchar, pub_key.bytesize).put_bytes(0, pub_key)
201
- internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
202
- result = secp256k1_ec_pubkey_parse(context, internal_pubkey, pubkey, pub_key.bytesize)
203
- result == 1
72
+ raise ArgumentError, "unknown algo: #{algo}"
204
73
  end
205
74
  end
206
75
 
207
- # Create key pair data from private key.
208
- # @param [String] priv_key with hex format
209
- # @return [String] key pair data with hex format. data = private key(32 bytes) | public key(64 bytes).
210
- def create_keypair(priv_key)
211
- with_context do |context|
212
- priv_key = priv_key.htb
213
- secret = FFI::MemoryPointer.new(:uchar, priv_key.bytesize).put_bytes(0, priv_key)
214
- raise 'priv_key is invalid.' unless secp256k1_ec_seckey_verify(context, secret)
215
- keypair = FFI::MemoryPointer.new(:uchar, 96)
216
- raise 'priv_key is invalid.' unless secp256k1_keypair_create(context, keypair, secret) == 1
217
- keypair.read_string(96).bth
218
- end
219
- end
220
-
221
- # Check whether valid x-only public key or not.
222
- # @param [String] pub_key x-only public key with hex format(32 bytes).
223
- # @return [Boolean] result.
224
- def valid_xonly_pubkey?(pub_key)
225
- begin
226
- full_pubkey_from_xonly_pubkey(pub_key)
227
- rescue Exception
228
- return false
229
- end
230
- true
231
- end
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
-
283
- private
284
-
285
- # Calculate full public key(64 bytes) from public key(32 bytes).
286
- # @param [String] pub_key x-only public key with hex format(32 bytes).
287
- # @return [String] x-only public key with hex format(64 bytes).
288
- def full_pubkey_from_xonly_pubkey(pub_key)
289
- with_context do |context|
290
- pubkey = pub_key.htb
291
- raise ArgumentError, "Pubkey size must be #{X_ONLY_PUBKEY_SIZE} bytes." unless pubkey.bytesize == X_ONLY_PUBKEY_SIZE
292
- xonly_pubkey = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
293
- full_pubkey = FFI::MemoryPointer.new(:uchar, 64)
294
- raise ArgumentError, 'An invalid public key was specified.' unless secp256k1_xonly_pubkey_parse(context, full_pubkey, xonly_pubkey) == 1
295
- full_pubkey.read_string(64).bth
296
- end
297
- end
298
-
299
- def generate_pubkey_in_context(context, privkey, compressed: true)
300
- internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
301
- result = secp256k1_ec_pubkey_create(context, internal_pubkey, privkey.htb)
302
- raise 'error creating pubkey' unless result
303
- serialize_pubkey_internal(context, internal_pubkey, compressed)
304
- end
305
-
306
- def sign_ecdsa(data, privkey, extra_entropy)
307
- with_context do |context|
308
- secret = FFI::MemoryPointer.new(:uchar, privkey.htb.bytesize).put_bytes(0, privkey.htb)
309
- raise 'priv_key is invalid' unless secp256k1_ec_seckey_verify(context, secret)
310
-
311
- internal_signature = FFI::MemoryPointer.new(:uchar, 64)
312
- msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
313
- entropy = extra_entropy ? FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, extra_entropy) : nil
314
-
315
- ret, tries, max = 0, 0, 20
316
-
317
- while ret != 1
318
- raise 'secp256k1_ecdsa_sign failed.' if tries >= max
319
- tries += 1
320
- ret = secp256k1_ecdsa_sign(context, internal_signature, msg32, secret, nil, entropy)
76
+ # Verify signature.
77
+ # @param [String] data The 32-byte message hash assumed to be signed.
78
+ # @param [String] signature signature data with binary format
79
+ # @param [String] pubkey a public key with hex format using verify.
80
+ # @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
81
+ # @return [Boolean] verification result.
82
+ # @raise [ArgumentError] If invalid arguments specified.
83
+ def verify_sig(data, signature, pubkey, algo: :ecdsa)
84
+ case algo
85
+ when :ecdsa
86
+ begin
87
+ verify_ecdsa(data, signature, pubkey)
88
+ rescue ArgumentError
89
+ false
321
90
  end
322
-
323
- signature = FFI::MemoryPointer.new(:uchar, 72)
324
- signature_len = FFI::MemoryPointer.new(:uint64).put_uint64(0, 72)
325
- result = secp256k1_ecdsa_signature_serialize_der(context, signature, signature_len, internal_signature)
326
- raise 'secp256k1_ecdsa_signature_serialize_der failed' unless result
327
-
328
- signature.read_string(signature_len.read_uint64)
329
- end
330
- end
331
-
332
- def sign_schnorr(data, privkey, aux_rand = nil)
333
- with_context do |context|
334
- keypair = create_keypair(privkey).htb
335
- keypair = FFI::MemoryPointer.new(:uchar, 96).put_bytes(0, keypair)
336
- signature = FFI::MemoryPointer.new(:uchar, 64)
337
- msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
338
- aux_rand = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, aux_rand) if aux_rand
339
- raise 'Failed to generate schnorr signature.' unless secp256k1_schnorrsig_sign32(context, signature, msg32, keypair, aux_rand) == 1
340
- signature.read_string(64)
341
- end
342
- end
343
-
344
- def verify_ecdsa(data, sig, pubkey)
345
- with_context do |context|
346
- return false if data.bytesize == 0
347
- pubkey = pubkey.htb
348
- pubkey = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
349
- internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
350
- result = secp256k1_ec_pubkey_parse(context, internal_pubkey, pubkey, pubkey.size)
351
- return false unless result
352
-
353
- signature = FFI::MemoryPointer.new(:uchar, sig.bytesize).put_bytes(0, sig)
354
- internal_signature = FFI::MemoryPointer.new(:uchar, 64)
355
- result = secp256k1_ecdsa_signature_parse_der(context, internal_signature, signature, signature.size)
356
- return false unless result
357
-
358
- # libsecp256k1's ECDSA verification requires lower-S signatures, which have not historically been enforced in Bitcoin, so normalize them first.
359
- secp256k1_ecdsa_signature_normalize(context, internal_signature, internal_signature)
360
-
361
- msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
362
- result = secp256k1_ecdsa_verify(context, internal_signature, msg32, internal_pubkey)
363
-
364
- result == 1
365
- end
366
- end
367
-
368
- def verify_schnorr(data, sig, pubkey)
369
- with_context do |context|
370
- return false if data.bytesize == 0
371
- pubkey = full_pubkey_from_xonly_pubkey(pubkey).htb
372
- xonly_pubkey = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
373
- signature = FFI::MemoryPointer.new(:uchar, sig.bytesize).put_bytes(0, sig)
374
- msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
375
- result = secp256k1_schnorrsig_verify(context, signature, msg32, 32, xonly_pubkey)
376
- result == 1
91
+ when :schnorr
92
+ begin
93
+ verify_schnorr(data, signature, pubkey)
94
+ rescue ArgumentError
95
+ false
96
+ end
97
+ else
98
+ raise ArgumentError, "unknown algo: #{algo}"
377
99
  end
378
100
  end
379
-
380
- # Serialize public key.
381
- def serialize_pubkey_internal(context, pubkey_input, compressed)
382
- pubkey = FFI::MemoryPointer.new(:uchar, 65)
383
- pubkey_len = FFI::MemoryPointer.new(:uint64)
384
- result = if compressed
385
- pubkey_len.put_uint64(0, 33)
386
- secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, pubkey_input, SECP256K1_EC_COMPRESSED)
387
- else
388
- pubkey_len.put_uint64(0, 65)
389
- secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, pubkey_input, SECP256K1_EC_UNCOMPRESSED)
390
- end
391
- raise 'error serialize pubkey' unless result || pubkey_len.read_uint64 > 0
392
- pubkey.read_string(pubkey_len.read_uint64).bth
393
- end
394
-
395
101
  end
396
102
  end
397
103
  end
@@ -9,6 +9,13 @@ module Bitcoin
9
9
  module Ruby
10
10
 
11
11
  module_function
12
+ extend Schnorr::Util
13
+
14
+ # Whether this module is native c wrapper or not?
15
+ # @return [Boolean]
16
+ def native?
17
+ false
18
+ end
12
19
 
13
20
  # generate ec private key and public key
14
21
  def generate_key_pair(compressed: true)
@@ -72,11 +79,20 @@ module Bitcoin
72
79
 
73
80
  # Recover public key from compact signature.
74
81
  # @param [String] data message digest using signature.
75
- # @param [String] signature signature with binary format.
76
- # @param [Integer] rec recovery id.
82
+ # @param [String] signature signature with binary format(65 bytes).
77
83
  # @param [Boolean] compressed whether compressed public key or not.
78
84
  # @return [Bitcoin::Key] Recovered public key.
79
- def recover_compact(data, signature, rec, compressed)
85
+ # @raise [ArgumentError] If invalid arguments specified.
86
+ def recover_compact(data, signature, compressed)
87
+ raise ArgumentError, "data must be String." unless data.is_a?(String)
88
+ raise ArgumentError, "signature must be String." unless signature.is_a?(String)
89
+ signature = hex2bin(signature)
90
+ raise ArgumentError, "signature must be 64 bytes." unless signature.bytesize == 65
91
+ data = hex2bin(data)
92
+ raise ArgumentError, "data must be 32 bytes." unless data.bytesize == 32
93
+ rec = (signature[0].ord - 0x1b) & 3
94
+ raise ArgumentError, "rec must be between 0 and 3." if rec < 0 || rec > 3
95
+
80
96
  group = Bitcoin::Secp256k1::GROUP
81
97
  r = ECDSA::Format::IntegerOctetString.decode(signature[1...33])
82
98
  s = ECDSA::Format::IntegerOctetString.decode(signature[33..-1])
@@ -0,0 +1,5 @@
1
+ module Bitcoin
2
+ module SilentPayment
3
+ autoload :Addr, 'bitcoin/sp/addr'
4
+ end
5
+ end
@@ -0,0 +1,55 @@
1
+ module Bitcoin
2
+ module SilentPayment
3
+ class Addr
4
+
5
+ HRP_MAINNET = 'sp'
6
+ HRP_TESTNET = 'tsp'
7
+ MAX_CHARACTERS = 1023
8
+
9
+ attr_reader :version
10
+ attr_reader :scan_key
11
+ attr_reader :spend_key
12
+
13
+ # Constructor.
14
+ # @param [Bitcoin::Key] scan_key
15
+ # @param [Bitcoin::Key] spend_key
16
+ def initialize(version, scan_key:, spend_key:)
17
+ raise ArgumentError, "version must be integer." unless version.is_a?(Integer)
18
+ raise ArgumentError, "scan_key must be Bitcoin::Key." unless scan_key.is_a?(Bitcoin::Key)
19
+ raise ArgumentError, "spend_key must be Bitcoin::Key." unless spend_key.is_a?(Bitcoin::Key)
20
+ raise ArgumentError, "version '#{version}' is unsupported." unless version.zero?
21
+
22
+ @version = version
23
+ @scan_key = scan_key
24
+ @spend_key = spend_key
25
+ end
26
+
27
+ # Parse silent payment address.
28
+ # @param [String] A silent payment address.
29
+ # @return [Bitcoin::SilentPayment::Addr]
30
+ def self.from_string(addr)
31
+ raise ArgumentError, "addr must be string." unless addr.is_a?(String)
32
+ hrp, data, spec = Bech32.decode(addr, MAX_CHARACTERS)
33
+ unless hrp == Bitcoin.chain_params.mainnet? ? HRP_MAINNET : HRP_TESTNET
34
+ raise ArgumentError, "The specified hrp is different from the current network HRP."
35
+ end
36
+ raise ArgumentError, "spec must be bech32m." unless spec == Bech32::Encoding::BECH32M
37
+
38
+ ver = data[0]
39
+ payload = Bech32.convert_bits(data[1..-1], 5, 8, false).pack("C*")
40
+ scan_key = Bitcoin::Key.new(pubkey: payload[0...33].bth, key_type: Bitcoin::Key::TYPES[:compressed])
41
+ spend_key = Bitcoin::Key.new(pubkey: payload[33..-1].bth, key_type: Bitcoin::Key::TYPES[:compressed])
42
+ Addr.new(ver, scan_key: scan_key, spend_key: spend_key)
43
+ end
44
+
45
+ # Get silent payment address.
46
+ # @return [String]
47
+ def to_s
48
+ hrp = Bitcoin.chain_params.mainnet? ? HRP_MAINNET : HRP_TESTNET
49
+ payload = [scan_key.pubkey + spend_key.pubkey].pack("H*").unpack('C*')
50
+ Bech32.encode(hrp, [version] + Bech32.convert_bits(payload, 8, 5), Bech32::Encoding::BECH32M)
51
+ end
52
+
53
+ end
54
+ end
55
+ end
data/lib/bitcoin/tx.rb CHANGED
@@ -33,6 +33,19 @@ module Bitcoin
33
33
  alias_method :in, :inputs
34
34
  alias_method :out, :outputs
35
35
 
36
+ # Create coinbase transaction.
37
+ # @param [String] msg Message embedded in coinbase transaction.
38
+ # @param [Bitcoin::Script] script Coinbase transaction scriptPubkey.
39
+ # @param [Integer] rewards Coinbase Transaction Rewards
40
+ # @return [Bitcoin::Tx] Coinbase transaction.
41
+ def self.create_coinbase(msg, script, rewards = 50 * 100000000)
42
+ coinbase = Tx.new
43
+ script_sig = Bitcoin::Script.new << 486604799 << "04" << msg.bth
44
+ coinbase.in << TxIn.new(out_point: OutPoint.create_coinbase_outpoint, script_sig: script_sig)
45
+ coinbase.out << TxOut.new(value: rewards, script_pubkey: script)
46
+ coinbase
47
+ end
48
+
36
49
  def self.parse_from_payload(payload, non_witness: false, strict: false)
37
50
  buf = payload.is_a?(String) ? StringIO.new(payload) : payload
38
51
  tx = new
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "1.6.0"
2
+ VERSION = "1.8.0"
3
3
  end
data/lib/bitcoin.rb CHANGED
@@ -7,7 +7,7 @@ require 'schnorr'
7
7
  require 'securerandom'
8
8
  require 'json'
9
9
  require 'bech32'
10
- require 'ffi'
10
+ require 'base64'
11
11
  require 'observer'
12
12
  require 'tmpdir'
13
13
  require_relative 'openassets'
@@ -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 :SilentPayment, 'bitcoin/silent_payment'
64
65
  autoload :BIP324, 'bitcoin/bip324'
65
66
 
66
67
  require_relative 'bitcoin/constants'
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bitcoinrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.0
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - azuchi
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-07-09 00:00:00.000000000 Z
10
+ date: 2025-02-03 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: ecdsa_ext
@@ -94,20 +93,6 @@ dependencies:
94
93
  - - ">="
95
94
  - !ruby/object:Gem::Version
96
95
  version: '0'
97
- - !ruby/object:Gem::Dependency
98
- name: ffi
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '0'
104
- type: :runtime
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
96
  - !ruby/object:Gem::Dependency
112
97
  name: leb128
113
98
  requirement: !ruby/object:Gem::Requirement
@@ -171,6 +156,9 @@ dependencies:
171
156
  - - ">="
172
157
  - !ruby/object:Gem::Version
173
158
  version: 2.3.1
159
+ - - "<"
160
+ - !ruby/object:Gem::Version
161
+ version: 2.8.0
174
162
  type: :runtime
175
163
  prerelease: false
176
164
  version_requirements: !ruby/object:Gem::Requirement
@@ -178,6 +166,9 @@ dependencies:
178
166
  - - ">="
179
167
  - !ruby/object:Gem::Version
180
168
  version: 2.3.1
169
+ - - "<"
170
+ - !ruby/object:Gem::Version
171
+ version: 2.8.0
181
172
  - !ruby/object:Gem::Dependency
182
173
  name: bip-schnorr
183
174
  requirement: !ruby/object:Gem::Requirement
@@ -207,103 +198,47 @@ dependencies:
207
198
  - !ruby/object:Gem::Version
208
199
  version: 0.3.4
209
200
  - !ruby/object:Gem::Dependency
210
- name: leveldb-native
211
- requirement: !ruby/object:Gem::Requirement
212
- requirements:
213
- - - ">="
214
- - !ruby/object:Gem::Version
215
- version: '0'
216
- type: :development
217
- prerelease: false
218
- version_requirements: !ruby/object:Gem::Requirement
219
- requirements:
220
- - - ">="
221
- - !ruby/object:Gem::Version
222
- version: '0'
223
- - !ruby/object:Gem::Dependency
224
- name: bundler
225
- requirement: !ruby/object:Gem::Requirement
226
- requirements:
227
- - - ">="
228
- - !ruby/object:Gem::Version
229
- version: '0'
230
- type: :development
231
- prerelease: false
232
- version_requirements: !ruby/object:Gem::Requirement
233
- requirements:
234
- - - ">="
235
- - !ruby/object:Gem::Version
236
- version: '0'
237
- - !ruby/object:Gem::Dependency
238
- name: rake
239
- requirement: !ruby/object:Gem::Requirement
240
- requirements:
241
- - - ">="
242
- - !ruby/object:Gem::Version
243
- version: 12.3.3
244
- type: :development
245
- prerelease: false
246
- version_requirements: !ruby/object:Gem::Requirement
247
- requirements:
248
- - - ">="
249
- - !ruby/object:Gem::Version
250
- version: 12.3.3
251
- - !ruby/object:Gem::Dependency
252
- name: rspec
201
+ name: base64
253
202
  requirement: !ruby/object:Gem::Requirement
254
203
  requirements:
255
204
  - - "~>"
256
205
  - !ruby/object:Gem::Version
257
- version: '3.0'
258
- type: :development
206
+ version: 0.2.0
207
+ type: :runtime
259
208
  prerelease: false
260
209
  version_requirements: !ruby/object:Gem::Requirement
261
210
  requirements:
262
211
  - - "~>"
263
212
  - !ruby/object:Gem::Version
264
- version: '3.0'
213
+ version: 0.2.0
265
214
  - !ruby/object:Gem::Dependency
266
- name: timecop
215
+ name: observer
267
216
  requirement: !ruby/object:Gem::Requirement
268
217
  requirements:
269
- - - ">="
270
- - !ruby/object:Gem::Version
271
- version: '0'
272
- type: :development
273
- prerelease: false
274
- version_requirements: !ruby/object:Gem::Requirement
275
- requirements:
276
- - - ">="
277
- - !ruby/object:Gem::Version
278
- version: '0'
279
- - !ruby/object:Gem::Dependency
280
- name: webmock
281
- requirement: !ruby/object:Gem::Requirement
282
- requirements:
283
- - - ">="
218
+ - - "~>"
284
219
  - !ruby/object:Gem::Version
285
- version: 3.11.1
286
- type: :development
220
+ version: 0.1.2
221
+ type: :runtime
287
222
  prerelease: false
288
223
  version_requirements: !ruby/object:Gem::Requirement
289
224
  requirements:
290
- - - ">="
225
+ - - "~>"
291
226
  - !ruby/object:Gem::Version
292
- version: 3.11.1
227
+ version: 0.1.2
293
228
  - !ruby/object:Gem::Dependency
294
- name: parallel
229
+ name: secp256k1rb
295
230
  requirement: !ruby/object:Gem::Requirement
296
231
  requirements:
297
- - - ">="
232
+ - - '='
298
233
  - !ruby/object:Gem::Version
299
- version: 1.20.1
300
- type: :development
234
+ version: 0.1.1
235
+ type: :runtime
301
236
  prerelease: false
302
237
  version_requirements: !ruby/object:Gem::Requirement
303
238
  requirements:
304
- - - ">="
239
+ - - '='
305
240
  - !ruby/object:Gem::Version
306
- version: 1.20.1
241
+ version: 0.1.1
307
242
  description: The implementation of Bitcoin Protocol for Ruby.
308
243
  email:
309
244
  - azuchi@chaintope.com
@@ -348,6 +283,7 @@ files:
348
283
  - lib/bitcoin/chainparams/regtest.yml
349
284
  - lib/bitcoin/chainparams/signet.yml
350
285
  - lib/bitcoin/chainparams/testnet.yml
286
+ - lib/bitcoin/chainparams/testnet4.yml
351
287
  - lib/bitcoin/constants.rb
352
288
  - lib/bitcoin/descriptor.rb
353
289
  - lib/bitcoin/descriptor/addr.rb
@@ -360,6 +296,7 @@ files:
360
296
  - lib/bitcoin/descriptor/pk.rb
361
297
  - lib/bitcoin/descriptor/pkh.rb
362
298
  - lib/bitcoin/descriptor/raw.rb
299
+ - lib/bitcoin/descriptor/raw_tr.rb
363
300
  - lib/bitcoin/descriptor/script_expression.rb
364
301
  - lib/bitcoin/descriptor/sh.rb
365
302
  - lib/bitcoin/descriptor/sorted_multi.rb
@@ -469,10 +406,12 @@ files:
469
406
  - lib/bitcoin/secp256k1/rfc6979.rb
470
407
  - lib/bitcoin/secp256k1/ruby.rb
471
408
  - lib/bitcoin/sighash_generator.rb
409
+ - lib/bitcoin/silent_payment.rb
472
410
  - lib/bitcoin/slip39.rb
473
411
  - lib/bitcoin/slip39/share.rb
474
412
  - lib/bitcoin/slip39/sss.rb
475
413
  - lib/bitcoin/slip39/wordlist/english.txt
414
+ - lib/bitcoin/sp/addr.rb
476
415
  - lib/bitcoin/store.rb
477
416
  - lib/bitcoin/store/chain_entry.rb
478
417
  - lib/bitcoin/store/db.rb
@@ -504,7 +443,6 @@ homepage: https://github.com/chaintope/bitcoinrb
504
443
  licenses:
505
444
  - MIT
506
445
  metadata: {}
507
- post_install_message:
508
446
  rdoc_options: []
509
447
  require_paths:
510
448
  - lib
@@ -519,8 +457,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
519
457
  - !ruby/object:Gem::Version
520
458
  version: '0'
521
459
  requirements: []
522
- rubygems_version: 3.5.3
523
- signing_key:
460
+ rubygems_version: 3.6.2
524
461
  specification_version: 4
525
462
  summary: The implementation of Bitcoin Protocol for Ruby.
526
463
  test_files: []