bitcoinrb 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +1 -1
- data/.ruby-version +1 -1
- data/Gemfile +8 -0
- data/bitcoinrb.gemspec +4 -12
- data/lib/bitcoin/bip324/ell_swift_pubkey.rb +1 -1
- data/lib/bitcoin/bip324.rb +2 -2
- data/lib/bitcoin/block.rb +27 -0
- data/lib/bitcoin/chain_params.rb +9 -0
- data/lib/bitcoin/chainparams/mainnet.yml +6 -2
- data/lib/bitcoin/chainparams/regtest.yml +1 -2
- data/lib/bitcoin/chainparams/signet.yml +3 -4
- data/lib/bitcoin/chainparams/testnet.yml +4 -4
- data/lib/bitcoin/chainparams/testnet4.yml +38 -0
- data/lib/bitcoin/descriptor/combo.rb +1 -1
- data/lib/bitcoin/descriptor/key_expression.rb +7 -0
- data/lib/bitcoin/descriptor/pk.rb +1 -1
- data/lib/bitcoin/descriptor/pkh.rb +1 -1
- data/lib/bitcoin/descriptor/raw_tr.rb +20 -0
- data/lib/bitcoin/descriptor/wpkh.rb +1 -1
- data/lib/bitcoin/descriptor.rb +10 -0
- data/lib/bitcoin/key.rb +2 -5
- data/lib/bitcoin/message_sign.rb +25 -5
- data/lib/bitcoin/psbt.rb +0 -2
- data/lib/bitcoin/script_witness.rb +1 -0
- data/lib/bitcoin/secp256k1/native.rb +53 -347
- data/lib/bitcoin/secp256k1/ruby.rb +19 -3
- data/lib/bitcoin/silent_payment.rb +5 -0
- data/lib/bitcoin/sp/addr.rb +55 -0
- data/lib/bitcoin/tx.rb +13 -0
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin.rb +2 -1
- metadata +29 -92
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0cc5450016d6ffbb76d36dcccd415a485a8532ab8414d9beddb9a7e25305dba
|
4
|
+
data.tar.gz: 75d509c36069ce2dee667a0330962ae310c8062f24aa01d299e5692f6f069461
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c2457464548bd24c937eaf55aa53ff2b7ca01b5db0b57b66d7e6fef60142cde6ff2752d670e06bd6052470b6eef25af9f3b676ffe1842fd98c1fd9d590888556
|
7
|
+
data.tar.gz: 95d5649e3cacc1a155b0e033f0275d9f1806a9e39aad2bbe0c9b61809a10b76d117dddf94c0c68eae50e9dd52aa8e3f266244e2a400821d9172e2706a371c654
|
data/.github/workflows/ruby.yml
CHANGED
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby-3.
|
1
|
+
ruby-3.4.1
|
data/Gemfile
CHANGED
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
|
-
|
39
|
-
spec.
|
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.
|
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
|
data/lib/bitcoin/bip324.rb
CHANGED
@@ -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.
|
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
|
data/lib/bitcoin/chain_params.rb
CHANGED
@@ -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.
|
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:
|
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:
|
26
|
-
genesis_hash: "00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6"
|
25
|
+
bip34_height: 1
|
27
26
|
proof_of_work_limit: 0x1d00ffff
|
28
27
|
dns_seeds:
|
29
|
-
- "
|
30
|
-
- "
|
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:
|
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.
|
29
|
+
- "seed.tbtc.petertodd.net"
|
30
|
+
- "seed.testnet.bitcoin.sprovoost.nl"
|
31
31
|
- "testnet-seed.bluematt.me"
|
32
|
-
- "
|
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
|
@@ -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
|
data/lib/bitcoin/descriptor.rb
CHANGED
@@ -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,
|
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.
|
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))
|
data/lib/bitcoin/message_sign.rb
CHANGED
@@ -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
|
66
|
-
|
65
|
+
rescue Exception
|
66
|
+
false
|
67
67
|
end
|
68
68
|
elsif addr_script.witness_program?
|
69
69
|
# BIP322 verification
|
70
|
-
|
71
|
-
|
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
|
-
|
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
@@ -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
|
-
|
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
|
-
#
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
135
|
-
|
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,
|
159
|
-
|
160
|
-
|
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
|
-
#
|
176
|
-
# @param [String] data
|
177
|
-
# @param [String]
|
178
|
-
# @param [String]
|
179
|
-
#
|
180
|
-
# @return [
|
181
|
-
|
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
|
-
|
60
|
+
begin
|
61
|
+
sign_ecdsa(data, private_key, extra_entropy)
|
62
|
+
rescue ArgumentError
|
63
|
+
false
|
64
|
+
end
|
185
65
|
when :schnorr
|
186
|
-
|
66
|
+
begin
|
67
|
+
sign_schnorr(data, private_key, extra_entropy)
|
68
|
+
rescue ArgumentError
|
69
|
+
false
|
70
|
+
end
|
187
71
|
else
|
188
|
-
|
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
|
-
#
|
208
|
-
# @param [String]
|
209
|
-
# @
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
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
|
-
|
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,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
|
data/lib/bitcoin/version.rb
CHANGED
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 '
|
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.
|
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:
|
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:
|
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:
|
258
|
-
type: :
|
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:
|
213
|
+
version: 0.2.0
|
265
214
|
- !ruby/object:Gem::Dependency
|
266
|
-
name:
|
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:
|
286
|
-
type: :
|
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:
|
227
|
+
version: 0.1.2
|
293
228
|
- !ruby/object:Gem::Dependency
|
294
|
-
name:
|
229
|
+
name: secp256k1rb
|
295
230
|
requirement: !ruby/object:Gem::Requirement
|
296
231
|
requirements:
|
297
|
-
- -
|
232
|
+
- - '='
|
298
233
|
- !ruby/object:Gem::Version
|
299
|
-
version: 1.
|
300
|
-
type: :
|
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.
|
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.
|
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: []
|