bitcoin-ruby 0.0.7 → 0.0.8
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/Gemfile +1 -1
- data/Gemfile.lock +7 -7
- data/README.rdoc +1 -1
- data/examples/simple_network_monitor_and_util.rb +8 -0
- data/lib/bitcoin.rb +57 -17
- data/lib/bitcoin/builder.rb +15 -10
- data/lib/bitcoin/ffi/bitcoinconsensus.rb +3 -2
- data/lib/bitcoin/ffi/secp256k1.rb +143 -81
- data/lib/bitcoin/key.rb +4 -6
- data/lib/bitcoin/logger.rb +11 -0
- data/lib/bitcoin/protocol.rb +19 -6
- data/lib/bitcoin/protocol/aux_pow.rb +62 -62
- data/lib/bitcoin/protocol/block.rb +20 -15
- data/lib/bitcoin/protocol/handler.rb +4 -0
- data/lib/bitcoin/protocol/parser.rb +30 -29
- data/lib/bitcoin/protocol/reject.rb +38 -0
- data/lib/bitcoin/protocol/tx.rb +19 -3
- data/lib/bitcoin/protocol/txin.rb +14 -8
- data/lib/bitcoin/script.rb +6 -1
- data/lib/bitcoin/version.rb +1 -1
- data/spec/bitcoin/bitcoin_spec.rb +4 -0
- data/spec/bitcoin/builder_spec.rb +11 -0
- data/spec/bitcoin/dogecoin_spec.rb +6 -6
- data/spec/bitcoin/protocol/addr_spec.rb +11 -1
- data/spec/bitcoin/protocol/aux_pow_spec.rb +9 -9
- data/spec/bitcoin/protocol/block_spec.rb +1 -0
- data/spec/bitcoin/protocol/inv_spec.rb +10 -2
- data/spec/bitcoin/protocol/parser_spec.rb +50 -0
- data/spec/bitcoin/protocol/reject.rb +17 -0
- data/spec/bitcoin/protocol/tx_spec.rb +5 -1
- data/spec/bitcoin/script/script_spec.rb +1 -1
- data/spec/bitcoin/secp256k1_spec.rb +8 -0
- data/spec/bitcoin/spec_helper.rb +6 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: db1b92d6514cc112f7141841b658fe16fc23cdad
|
4
|
+
data.tar.gz: 2a4a5e0e7215cc7739d8208322ac361ef1beb988
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 63c618a352af13e722f280e84ef6801ded898c65db7f346eb410fa417c310c89c643d74e66474a975281326bae0d9e4b70316a6c2fb4b4e9a8f54c8462ce241c
|
7
|
+
data.tar.gz: 421b29bcb069e3f6de8d3c8484aece5d6bd09a9f0695866f47baa6308e02ef6fa5b267a350aea9741aaea19b5395fce35456111e24f787ec6819498df199b02a
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,20 +1,20 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
bitcoin-ruby (0.0.
|
4
|
+
bitcoin-ruby (0.0.8)
|
5
5
|
|
6
6
|
GEM
|
7
|
-
remote:
|
7
|
+
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
9
|
bacon (1.2.0)
|
10
|
-
eventmachine (1.0.
|
11
|
-
ffi (1.9.
|
10
|
+
eventmachine (1.0.8)
|
11
|
+
ffi (1.9.10)
|
12
12
|
ffi-compiler (0.1.3)
|
13
13
|
ffi (>= 1.0.0)
|
14
14
|
rake
|
15
|
-
minitest (5.
|
15
|
+
minitest (5.8.2)
|
16
16
|
rake (10.4.2)
|
17
|
-
scrypt (2.0.
|
17
|
+
scrypt (2.0.2)
|
18
18
|
ffi-compiler (>= 0.0.2)
|
19
19
|
rake
|
20
20
|
|
@@ -31,4 +31,4 @@ DEPENDENCIES
|
|
31
31
|
scrypt
|
32
32
|
|
33
33
|
BUNDLED WITH
|
34
|
-
1.10.
|
34
|
+
1.10.6
|
data/README.rdoc
CHANGED
@@ -127,7 +127,7 @@ If you want to control the Bitcoin::Script yourself, you can do so
|
|
127
127
|
You need to know the previous output you want to spend (tx hash and output index),
|
128
128
|
as well as the private key for the address required to sign for it.
|
129
129
|
|
130
|
-
# use testnet so you don't
|
130
|
+
# use testnet so you don't accidentally blow your whole money!
|
131
131
|
Bitcoin.network = :testnet3
|
132
132
|
|
133
133
|
# make the DSL methods available in your scope
|
@@ -9,6 +9,14 @@ class Bitcoin::Protocol::Parser; def log; stub=Object.new; def stub.method_missi
|
|
9
9
|
module SimpleNode
|
10
10
|
class Connection < EM::Connection
|
11
11
|
|
12
|
+
def on_ping(nonce)
|
13
|
+
send_data(Bitcoin::Protocol.pong_pkt(nonce)) if nonce
|
14
|
+
end
|
15
|
+
|
16
|
+
def on_reject(reject)
|
17
|
+
log.info { "reject #{reject}" }
|
18
|
+
end
|
19
|
+
|
12
20
|
def on_tx(tx)
|
13
21
|
log.info { "received transaction: #{tx.hash}" }
|
14
22
|
|
data/lib/bitcoin.rb
CHANGED
@@ -276,12 +276,23 @@ module Bitcoin
|
|
276
276
|
end
|
277
277
|
|
278
278
|
def sign_data(key, data)
|
279
|
-
sig =
|
280
|
-
|
281
|
-
sig
|
282
|
-
|
283
|
-
|
284
|
-
|
279
|
+
sig = nil
|
280
|
+
loop {
|
281
|
+
sig = key.dsa_sign_asn1(data)
|
282
|
+
sig = if Script.is_low_der_signature?(sig)
|
283
|
+
sig
|
284
|
+
else
|
285
|
+
Bitcoin::OpenSSL_EC.signature_to_low_s(sig)
|
286
|
+
end
|
287
|
+
|
288
|
+
buf = sig + [Script::SIGHASH_TYPE[:all]].pack("C") # is_der_signature expects sig + sighash_type format
|
289
|
+
if Script.is_der_signature?(buf)
|
290
|
+
break
|
291
|
+
else
|
292
|
+
p ["Bitcoin#sign_data: invalid der signature generated, trying again.", data.unpack("H*")[0], sig.unpack("H*")[0]]
|
293
|
+
end
|
294
|
+
}
|
295
|
+
return sig
|
285
296
|
end
|
286
297
|
|
287
298
|
def verify_signature(hash, signature, public_key)
|
@@ -310,9 +321,13 @@ module Bitcoin
|
|
310
321
|
end
|
311
322
|
|
312
323
|
def bitcoin_signed_message_hash(message)
|
313
|
-
|
314
|
-
|
315
|
-
|
324
|
+
message = message.dup.force_encoding('binary')
|
325
|
+
|
326
|
+
magic = "Bitcoin Signed Message:\n"
|
327
|
+
buf = Protocol.pack_var_int(magic.bytesize) + magic
|
328
|
+
buf << Protocol.pack_var_int(message.bytesize) + message
|
329
|
+
|
330
|
+
Digest::SHA256.digest(Digest::SHA256.digest(buf))
|
316
331
|
end
|
317
332
|
|
318
333
|
def sign_message(private_key_hex, public_key_hex, message)
|
@@ -552,11 +567,13 @@ module Bitcoin
|
|
552
567
|
free_tx_bytes: 1_000,
|
553
568
|
dust: CENT,
|
554
569
|
per_dust_fee: false,
|
570
|
+
bip34_height: 227931,
|
555
571
|
dns_seeds: [
|
556
572
|
"seed.bitcoin.sipa.be",
|
557
573
|
"dnsseed.bluematt.me",
|
558
574
|
"dnsseed.bitcoin.dashjr.org",
|
559
575
|
"bitseed.xf2.org",
|
576
|
+
"dnsseed.webbtc.com",
|
560
577
|
],
|
561
578
|
genesis_hash: "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
|
562
579
|
proof_of_work_limit: 0x1d00ffff,
|
@@ -566,6 +583,7 @@ module Bitcoin
|
|
566
583
|
'mining.bitcoin.cz',
|
567
584
|
'blockchain.info',
|
568
585
|
'blockexplorer.com',
|
586
|
+
'webbtc.com',
|
569
587
|
],
|
570
588
|
checkpoints: {
|
571
589
|
11111 => "0000000069e244f73d78e8fd29ba2fd2ed618bd6fa2ee92559f542fdb26e7c1d",
|
@@ -593,6 +611,7 @@ module Bitcoin
|
|
593
611
|
extended_privkey_version: "04358394",
|
594
612
|
extended_pubkey_version: "043587cf",
|
595
613
|
default_port: 18333,
|
614
|
+
bip34_height: 21111,
|
596
615
|
dns_seeds: [ ],
|
597
616
|
genesis_hash: "00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008",
|
598
617
|
proof_of_work_limit: 0x1d07fff8,
|
@@ -604,7 +623,8 @@ module Bitcoin
|
|
604
623
|
NETWORKS[:regtest] = NETWORKS[:testnet].merge({
|
605
624
|
default_port: 18444,
|
606
625
|
genesis_hash: "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206",
|
607
|
-
proof_of_work_limit:
|
626
|
+
proof_of_work_limit: 0x207fffff,
|
627
|
+
bip34_height: 0,
|
608
628
|
})
|
609
629
|
|
610
630
|
NETWORKS[:testnet3] = NETWORKS[:testnet].merge({
|
@@ -617,7 +637,9 @@ module Bitcoin
|
|
617
637
|
"testnet-seed.bitcoin.schildbach.de",
|
618
638
|
"testnet-seed.bitcoin.petertodd.org",
|
619
639
|
"testnet-seed.bluematt.me",
|
640
|
+
"dnsseed.test.webbtc.com",
|
620
641
|
],
|
642
|
+
known_nodes: ["test.webbtc.com"],
|
621
643
|
checkpoints: {
|
622
644
|
# 542 contains invalid transaction
|
623
645
|
542 => "0000000083c1f82cf72c6724f7a317325806384b06408bce7a4327f418dfd5ad",
|
@@ -811,11 +833,14 @@ module Bitcoin
|
|
811
833
|
protocol_version: 35000,
|
812
834
|
min_tx_fee: 50_000,
|
813
835
|
per_dust_fee: true,
|
814
|
-
dns_seeds: [
|
836
|
+
dns_seeds: [
|
837
|
+
"nmc.seed.quisquis.de",
|
838
|
+
"dnsseed.namecoin.webbtc.com",
|
839
|
+
],
|
815
840
|
genesis_hash: "000000000062b72c5e2ceb45fbc8587e807c155b0da735e6483dfba2f0a9c770",
|
816
841
|
known_nodes: [
|
817
842
|
"bitcoin.tunl.in",
|
818
|
-
"webbtc.com",
|
843
|
+
"namecoin.webbtc.com",
|
819
844
|
"178.32.31.41",
|
820
845
|
"78.47.86.43",
|
821
846
|
"69.164.206.88",
|
@@ -833,11 +858,26 @@ module Bitcoin
|
|
833
858
|
NETWORKS[:namecoin_testnet] = NETWORKS[:namecoin].merge({
|
834
859
|
magic_head: "\xFA\xBF\xB5\xFE",
|
835
860
|
default_port: 18334,
|
836
|
-
genesis_hash: "
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
861
|
+
genesis_hash: "00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008",
|
862
|
+
dns_seeds: [
|
863
|
+
"dnsseed.test.namecoin.webbtc.com",
|
864
|
+
],
|
865
|
+
known_nodes: [
|
866
|
+
"test.namecoin.webbtc.com",
|
867
|
+
"nmctest.net",
|
868
|
+
"192.99.247.234"],
|
869
|
+
checkpoints: { }
|
870
|
+
})
|
871
|
+
|
872
|
+
NETWORKS[:namecoin_regtest] = NETWORKS[:namecoin_testnet].merge({
|
873
|
+
magic_head: "\xFA\xBF\xB5\xDA",
|
874
|
+
default_port: 18445,
|
875
|
+
genesis_hash: "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206",
|
876
|
+
dns_seeds: [], known_nodes: [],
|
877
|
+
proof_of_work_limit: 0x207fffff,
|
878
|
+
checkpoints: { },
|
879
|
+
address_versions: { pubkey_hash: "6f", script_hash: "c4" },
|
880
|
+
privkey_version: "ef",
|
841
881
|
})
|
842
882
|
|
843
883
|
end
|
data/lib/bitcoin/builder.rb
CHANGED
@@ -14,7 +14,6 @@ module Bitcoin
|
|
14
14
|
yield c
|
15
15
|
c.block(target)
|
16
16
|
end
|
17
|
-
alias :blk :build_block
|
18
17
|
|
19
18
|
# build a Bitcoin::Protocol::Tx.
|
20
19
|
# see TxBuilder for details.
|
@@ -23,7 +22,6 @@ module Bitcoin
|
|
23
22
|
yield c
|
24
23
|
c.tx opts
|
25
24
|
end
|
26
|
-
alias :tx :build_tx
|
27
25
|
|
28
26
|
# build a Bitcoin::Script.
|
29
27
|
# see ScriptBuilder for details.
|
@@ -95,7 +93,7 @@ module Bitcoin
|
|
95
93
|
@block.bits = Bitcoin.encode_compact_bits(target)
|
96
94
|
t = Time.now
|
97
95
|
@block.recalc_block_hash
|
98
|
-
until @block.hash < target
|
96
|
+
until @block.hash.to_i(16) < target.to_i(16)
|
99
97
|
@block.nonce += 1
|
100
98
|
@block.recalc_block_hash
|
101
99
|
if @block.nonce == 100000
|
@@ -156,9 +154,11 @@ module Bitcoin
|
|
156
154
|
end
|
157
155
|
|
158
156
|
# add an output to the transaction (see TxOutBuilder).
|
159
|
-
def output
|
157
|
+
def output value = nil, recipient = nil, type = :address
|
160
158
|
c = TxOutBuilder.new
|
161
|
-
|
159
|
+
c.value(value) if value
|
160
|
+
c.to(recipient, type) if recipient
|
161
|
+
yield c if block_given?
|
162
162
|
@outs << c
|
163
163
|
end
|
164
164
|
|
@@ -173,18 +173,20 @@ module Bitcoin
|
|
173
173
|
# case to specify a tx fee that should be left unclaimed by the
|
174
174
|
# change output.
|
175
175
|
def tx opts = {}
|
176
|
+
return @tx if @tx.hash
|
177
|
+
|
176
178
|
if opts[:change_address] && !opts[:input_value]
|
177
179
|
raise "Must give 'input_value' when auto-generating change output!"
|
178
180
|
end
|
179
181
|
@ins.each {|i| @tx.add_in(i.txin) }
|
180
182
|
@outs.each {|o| @tx.add_out(o.txout) }
|
181
|
-
|
182
183
|
if opts[:change_address]
|
183
|
-
output_value = @tx.out.map(&:value).inject(:+)
|
184
|
+
output_value = @tx.out.map(&:value).inject(:+) || 0
|
184
185
|
change_value = opts[:input_value] - output_value
|
185
186
|
if opts[:leave_fee]
|
186
|
-
|
187
|
-
|
187
|
+
fee = @tx.minimum_block_fee + (opts[:extra_fee] || 0)
|
188
|
+
if change_value >= fee
|
189
|
+
change_value -= fee
|
188
190
|
else
|
189
191
|
change_value = 0
|
190
192
|
end
|
@@ -219,7 +221,7 @@ module Bitcoin
|
|
219
221
|
def sig_hash_and_all_keys_exist?(inc, sig_script)
|
220
222
|
return false unless @sig_hash && inc.has_keys?
|
221
223
|
script = Bitcoin::Script.new(sig_script)
|
222
|
-
return true if script.is_hash160? || script.is_pubkey?
|
224
|
+
return true if script.is_hash160? || script.is_pubkey? || (Bitcoin.namecoin? && script.is_namecoin?)
|
223
225
|
if script.is_multisig?
|
224
226
|
return inc.has_multiple_keys? && inc.key.size >= script.get_signatures_required
|
225
227
|
end
|
@@ -426,6 +428,9 @@ module Bitcoin
|
|
426
428
|
|
427
429
|
# Create a Bitcoin::Protocol::TxOut used by TxBuilder#output.
|
428
430
|
#
|
431
|
+
# t.output 12345, address
|
432
|
+
# t.output 12345, p2sh_address, :script_hash
|
433
|
+
#
|
429
434
|
# t.output {|o| o.value 12345; o.to address }
|
430
435
|
#
|
431
436
|
# t.output do |o|
|
@@ -2,8 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'ffi'
|
4
4
|
|
5
|
-
#
|
6
|
-
#
|
5
|
+
# binding for src/.libs/bitcoinconsensus.so (https://github.com/bitcoin/bitcoin)
|
6
|
+
# tag: v0.11.0
|
7
|
+
# commit: d26f951802c762de04fb68e1a112d611929920ba
|
7
8
|
|
8
9
|
module Bitcoin
|
9
10
|
module BitcoinConsensus
|
@@ -1,7 +1,8 @@
|
|
1
1
|
# encoding: ascii-8bit
|
2
2
|
|
3
|
-
#
|
4
|
-
#
|
3
|
+
# bindings for secp256k1 inside bitcoin (https://github.com/bitcoin/bitcoin/tree/v0.11.0/src/secp256k1)
|
4
|
+
# tag: v0.11.0
|
5
|
+
# commit: d26f951802c762de04fb68e1a112d611929920ba
|
5
6
|
|
6
7
|
require 'ffi'
|
7
8
|
|
@@ -16,128 +17,189 @@ module Bitcoin
|
|
16
17
|
class_eval <<-RUBY
|
17
18
|
ffi_lib [ %[#{file}] ]
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
attach_function :secp256k1_ec_pubkey_verify, [:pointer, :int], :int
|
23
|
-
attach_function :secp256k1_ec_pubkey_create, [:pointer, :pointer, :pointer, :int], :int
|
20
|
+
##
|
21
|
+
# source: https://github.com/bitcoin/bitcoin/blob/v0.11.0/src/secp256k1/include/secp256k1.h
|
22
|
+
##
|
24
23
|
|
25
|
-
#
|
26
|
-
attach_function :
|
24
|
+
# secp256k1_context_t* secp256k1_context_create(int flags)
|
25
|
+
attach_function :secp256k1_context_create, [:int], :pointer
|
27
26
|
|
28
|
-
#
|
29
|
-
attach_function :
|
27
|
+
# secp256k1_context_t* secp256k1_context_clone(const secp256k1_context_t* ctx)
|
28
|
+
attach_function :secp256k1_context_clone, [:pointer], :pointer
|
30
29
|
|
31
|
-
#
|
32
|
-
attach_function :
|
30
|
+
# void secp256k1_context_destroy(secp256k1_context_t* ctx)
|
31
|
+
attach_function :secp256k1_context_destroy, [:pointer], :void
|
33
32
|
|
34
|
-
# int
|
35
|
-
attach_function :
|
33
|
+
# int secp256k1_ecdsa_verify(const secp256k1_context_t* ctx, const unsigned char *msg32, const unsigned char *sig, int siglen, const unsigned char *pubkey, int pubkeylen)
|
34
|
+
attach_function :secp256k1_ecdsa_verify, [:pointer, :pointer, :pointer, :int, :pointer, :int], :int
|
35
|
+
|
36
|
+
# int secp256k1_ecdsa_sign(const secp256k1_context_t* ctx, const unsigned char *msg32, unsigned char *sig, int *siglen, const unsigned char *seckey, secp256k1_nonce_function_t noncefp, const void *ndata)
|
37
|
+
attach_function :secp256k1_ecdsa_sign, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int
|
38
|
+
|
39
|
+
# int secp256k1_ecdsa_sign_compact(const secp256k1_context_t* ctx, const unsigned char *msg32, unsigned char *sig64, const unsigned char *seckey, secp256k1_nonce_function_t noncefp, const void *ndata, int *recid)
|
40
|
+
attach_function :secp256k1_ecdsa_sign_compact, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int
|
41
|
+
|
42
|
+
# int secp256k1_ecdsa_recover_compact(const secp256k1_context_t* ctx, const unsigned char *msg32, const unsigned char *sig64, unsigned char *pubkey, int *pubkeylen, int compressed, int recid)
|
43
|
+
attach_function :secp256k1_ecdsa_recover_compact, [:pointer, :pointer, :pointer, :pointer, :pointer, :int, :int], :int
|
44
|
+
|
45
|
+
# int secp256k1_ec_seckey_verify(const secp256k1_context_t* ctx, const unsigned char *seckey)
|
46
|
+
attach_function :secp256k1_ec_seckey_verify, [:pointer, :pointer], :int
|
47
|
+
|
48
|
+
# int secp256k1_ec_pubkey_verify(const secp256k1_context_t* ctx, const unsigned char *pubkey, int pubkeylen)
|
49
|
+
attach_function :secp256k1_ec_pubkey_verify, [:pointer, :pointer, :int], :int
|
50
|
+
|
51
|
+
# int secp256k1_ec_pubkey_create(const secp256k1_context_t* ctx, unsigned char *pubkey, int *pubkeylen, const unsigned char *seckey, int compressed)
|
52
|
+
attach_function :secp256k1_ec_pubkey_create, [:pointer, :pointer, :pointer, :pointer, :int], :int
|
53
|
+
|
54
|
+
# int secp256k1_ec_pubkey_decompress(const secp256k1_context_t* ctx, unsigned char *pubkey, int *pubkeylen)
|
55
|
+
attach_function :secp256k1_ec_pubkey_decompress, [:pointer, :pointer, :pointer], :int
|
56
|
+
|
57
|
+
# int secp256k1_ec_privkey_export(const secp256k1_context_t* ctx, const unsigned char *seckey, unsigned char *privkey, int *privkeylen, int compressed)
|
58
|
+
attach_function :secp256k1_ec_privkey_export, [:pointer, :pointer, :pointer, :pointer, :int], :int
|
59
|
+
|
60
|
+
# int secp256k1_ec_privkey_import(const secp256k1_context_t* ctx, unsigned char *seckey, const unsigned char *privkey, int privkeylen)
|
61
|
+
attach_function :secp256k1_ec_privkey_import, [:pointer, :pointer, :pointer, :pointer], :int
|
62
|
+
|
63
|
+
# int secp256k1_ec_privkey_tweak_add(const secp256k1_context_t* ctx, unsigned char *seckey, const unsigned char *tweak)
|
64
|
+
attach_function :secp256k1_ec_privkey_tweak_add, [:pointer, :pointer, :pointer], :int
|
65
|
+
|
66
|
+
# int secp256k1_ec_pubkey_tweak_add(const secp256k1_context_t* ctx, unsigned char *pubkey, int pubkeylen, const unsigned char *tweak)
|
67
|
+
attach_function :secp256k1_ec_pubkey_tweak_add, [:pointer, :pointer, :int, :pointer], :int
|
68
|
+
|
69
|
+
# int secp256k1_ec_privkey_tweak_mul(const secp256k1_context_t* ctx, unsigned char *seckey, const unsigned char *tweak)
|
70
|
+
attach_function :secp256k1_ec_privkey_tweak_mul, [:pointer, :pointer, :pointer], :int
|
71
|
+
|
72
|
+
# int secp256k1_ec_pubkey_tweak_mul(const secp256k1_context_t* ctx, unsigned char *pubkey, int pubkeylen, const unsigned char *tweak)
|
73
|
+
attach_function :secp256k1_ec_pubkey_tweak_mul, [:pointer, :pointer, :int, :pointer], :int
|
74
|
+
|
75
|
+
# int secp256k1_context_randomize(secp256k1_context_t* ctx, const unsigned char *seed32)
|
76
|
+
attach_function :secp256k1_context_randomize, [:pointer, :pointer], :int
|
36
77
|
RUBY
|
37
78
|
end
|
38
79
|
|
39
80
|
def self.init
|
40
|
-
return if @
|
41
|
-
|
42
|
-
lib_path = [ ENV['SECP256K1_LIB_PATH'], 'vendor/secp256k1/.libs/libsecp256k1.so' ].find{|f| File.exists?(f.to_s) }
|
43
|
-
lib_path = 'libsecp256k1.so' unless lib_path
|
81
|
+
return if @loaded
|
82
|
+
lib_path = [ ENV['SECP256K1_LIB_PATH'], 'vendor/bitcoin/src/secp256k1/.libs/libsecp256k1.so' ].find{|f| File.exists?(f.to_s) }
|
44
83
|
ffi_load_functions(lib_path)
|
45
|
-
|
46
|
-
secp256k1_start(SECP256K1_START_VERIFY | SECP256K1_START_SIGN)
|
47
|
-
@secp256k1_started = true
|
84
|
+
@loaded = true
|
48
85
|
end
|
49
86
|
|
50
|
-
def self.
|
87
|
+
def self.with_context(flags=nil, seed=nil)
|
51
88
|
init
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
89
|
+
flags = flags || (SECP256K1_START_VERIFY | SECP256K1_START_SIGN )
|
90
|
+
context = secp256k1_context_create(flags)
|
91
|
+
|
92
|
+
ret, tries, max = 0, 0, 20
|
93
|
+
while ret != 1
|
94
|
+
raise "secp256k1_context_randomize failed." if tries >= max
|
95
|
+
tries += 1
|
96
|
+
ret = secp256k1_context_randomize(context, FFI::MemoryPointer.from_string(seed || SecureRandom.random_bytes(32)))
|
57
97
|
end
|
58
98
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
raise "error creating pubkey" unless result
|
63
|
-
|
64
|
-
[ priv_key, pub_key_buf.read_string(pub_key_size.read_int) ]
|
99
|
+
yield(context) if block_given?
|
100
|
+
ensure
|
101
|
+
secp256k1_context_destroy(context)
|
65
102
|
end
|
66
103
|
|
67
|
-
def self.
|
68
|
-
|
104
|
+
def self.generate_key_pair(compressed=true)
|
105
|
+
with_context do |context|
|
106
|
+
|
107
|
+
ret, tries, max = 0, 0, 20
|
108
|
+
while ret != 1
|
109
|
+
raise "secp256k1_ec_seckey_verify in generate_key_pair failed." if tries >= max
|
110
|
+
tries += 1
|
69
111
|
|
70
|
-
|
71
|
-
|
72
|
-
|
112
|
+
priv_key = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, SecureRandom.random_bytes(32))
|
113
|
+
ret = secp256k1_ec_seckey_verify(context, priv_key)
|
114
|
+
end
|
73
115
|
|
74
|
-
|
116
|
+
pub_key, pub_key_length = FFI::MemoryPointer.new(:uchar, 65), FFI::MemoryPointer.new(:int)
|
117
|
+
result = secp256k1_ec_pubkey_create(context, pub_key, pub_key_length, priv_key, compressed ? 1 : 0)
|
118
|
+
raise "error creating pubkey" unless result
|
75
119
|
|
76
|
-
|
77
|
-
break if secp256k1_ecdsa_sign(msg32, sig, siglen, seckey, nil, nil)
|
120
|
+
[ priv_key.read_string(32), pub_key.read_string(pub_key_length.read_int) ]
|
78
121
|
end
|
122
|
+
end
|
79
123
|
|
80
|
-
|
124
|
+
def self.generate_key(compressed=true)
|
125
|
+
priv, pub = generate_key_pair(compressed)
|
126
|
+
Bitcoin::Key.new(priv.unpack("H*")[0], pub.unpack("H*")[0])
|
81
127
|
end
|
82
128
|
|
83
|
-
def self.
|
84
|
-
|
129
|
+
def self.sign(data, priv_key)
|
130
|
+
with_context do |context|
|
131
|
+
msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
|
132
|
+
seckey = FFI::MemoryPointer.new(:uchar, priv_key.bytesize).put_bytes(0, priv_key)
|
133
|
+
raise "priv_key invalid" unless secp256k1_ec_seckey_verify(context, seckey)
|
85
134
|
|
86
|
-
|
87
|
-
sig_buf = FFI::MemoryPointer.new(:uchar, signature.bytesize).put_bytes(0, signature)
|
88
|
-
pub_buf = FFI::MemoryPointer.new(:uchar, pub_key.bytesize).put_bytes(0, pub_key)
|
135
|
+
sig, siglen = FFI::MemoryPointer.new(:uchar, 72), FFI::MemoryPointer.new(:int).write_int(72)
|
89
136
|
|
90
|
-
|
137
|
+
while true do
|
138
|
+
break if secp256k1_ecdsa_sign(context, msg32, sig, siglen, seckey, nil, nil)
|
139
|
+
end
|
91
140
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
141
|
+
sig.read_string(siglen.read_int)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.verify(data, signature, pub_key)
|
146
|
+
with_context do |context|
|
147
|
+
data_buf = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
|
148
|
+
sig_buf = FFI::MemoryPointer.new(:uchar, signature.bytesize).put_bytes(0, signature)
|
149
|
+
pub_buf = FFI::MemoryPointer.new(:uchar, pub_key.bytesize).put_bytes(0, pub_key)
|
150
|
+
|
151
|
+
result = secp256k1_ecdsa_verify(context, data_buf, sig_buf, sig_buf.size, pub_buf, pub_buf.size)
|
152
|
+
|
153
|
+
case result
|
154
|
+
when 0; false
|
155
|
+
when 1; true
|
156
|
+
when -1; raise "error invalid pubkey"
|
157
|
+
when -2; raise "error invalid signature"
|
158
|
+
else ; raise "error invalid result"
|
159
|
+
end
|
98
160
|
end
|
99
161
|
end
|
100
162
|
|
101
163
|
def self.sign_compact(message, priv_key, compressed=true)
|
102
|
-
|
164
|
+
with_context do |context|
|
165
|
+
msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, message)
|
166
|
+
sig64 = FFI::MemoryPointer.new(:uchar, 64)
|
167
|
+
rec_id = FFI::MemoryPointer.new(:int)
|
103
168
|
|
104
|
-
|
105
|
-
|
106
|
-
rec_id = FFI::MemoryPointer.new(:int)
|
169
|
+
seckey = FFI::MemoryPointer.new(:uchar, priv_key.bytesize).put_bytes(0, priv_key)
|
170
|
+
raise "priv_key invalid" unless secp256k1_ec_seckey_verify(context, seckey)
|
107
171
|
|
108
|
-
|
109
|
-
|
172
|
+
while true do
|
173
|
+
break if secp256k1_ecdsa_sign_compact(context, msg32, sig64, seckey, nil, nil, rec_id)
|
174
|
+
end
|
110
175
|
|
111
|
-
|
112
|
-
|
176
|
+
header = [27 + rec_id.read_int + (compressed ? 4 : 0)].pack("C")
|
177
|
+
[ header, sig64.read_string(64) ].join
|
113
178
|
end
|
114
|
-
|
115
|
-
header = [27 + rec_id.read_int + (compressed ? 4 : 0)].pack("C")
|
116
|
-
[ header, sig64.read_string(64) ].join
|
117
179
|
end
|
118
180
|
|
119
181
|
def self.recover_compact(message, signature)
|
120
|
-
|
182
|
+
with_context do |context|
|
183
|
+
return nil if signature.bytesize != 65
|
121
184
|
|
122
|
-
|
185
|
+
version = signature.unpack('C')[0]
|
186
|
+
return nil if version < 27 || version > 34
|
123
187
|
|
124
|
-
|
125
|
-
|
188
|
+
compressed = version >= 31 ? true : false
|
189
|
+
version -= 4 if compressed
|
126
190
|
|
127
|
-
|
128
|
-
|
191
|
+
recid = version - 27
|
192
|
+
msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, message)
|
193
|
+
sig64 = FFI::MemoryPointer.new(:uchar, 64).put_bytes(0, signature[1..-1])
|
194
|
+
pubkey = FFI::MemoryPointer.new(:uchar, pub_key_len = compressed ? 33 : 65)
|
195
|
+
pubkeylen = FFI::MemoryPointer.new(:int).write_int(pub_key_len)
|
129
196
|
|
130
|
-
|
131
|
-
msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, message)
|
132
|
-
sig64 = FFI::MemoryPointer.new(:uchar, 64).put_bytes(0, signature[1..-1])
|
133
|
-
pubkey = FFI::MemoryPointer.new(:uchar, pub_key_len = compressed ? 33 : 65)
|
134
|
-
pubkeylen = FFI::MemoryPointer.new(:int).write_int(pub_key_len)
|
197
|
+
result = secp256k1_ecdsa_recover_compact(context, msg32, sig64, pubkey, pubkeylen, (compressed ? 1 : 0), recid)
|
135
198
|
|
136
|
-
|
199
|
+
return nil unless result
|
137
200
|
|
138
|
-
|
139
|
-
|
140
|
-
pubkey.read_bytes(pubkeylen.read_int)
|
201
|
+
pubkey.read_bytes(pubkeylen.read_int)
|
202
|
+
end
|
141
203
|
end
|
142
204
|
|
143
205
|
end
|