btcruby 1.6 → 1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/RELEASE_NOTES.md +6 -0
- data/lib/btcruby/address.rb +1 -1
- data/lib/btcruby/keychain.rb +13 -13
- data/lib/btcruby/mnemonic.rb +1 -1
- data/lib/btcruby/script/script.rb +17 -9
- data/lib/btcruby/script/script_interpreter.rb +19 -19
- data/lib/btcruby/script/script_version.rb +54 -0
- data/lib/btcruby/script/versioned_script.rb +64 -0
- data/lib/btcruby/secp256k1.rb +5 -3
- data/lib/btcruby/transaction_builder.rb +10 -5
- data/lib/btcruby/transaction_builder/errors.rb +6 -1
- data/lib/btcruby/version.rb +1 -1
- data/spec/currency_formatter_spec.rb +0 -2
- data/spec/keychain_spec.rb +5 -0
- data/spec/script_number_spec.rb +4 -0
- data/spec/secp256k1_spec.rb +13 -1
- data/spec/transaction_builder_spec.rb +12 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ffee95a66b5f287235a52944ff6f92e3335b22df
|
4
|
+
data.tar.gz: 550a1d62ecea9b95a15294f3b65477566c9baa90
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 517e12449e58e4866826c6f28230f854bc26b04e788f9d7d3bd864dd15aeb60ccc948da70a4c2d69bfdeef84bd1475f5ac5d78aee599ea67ba52674a30703cb0
|
7
|
+
data.tar.gz: e175176b5a85c1eb40e17134179bf29acc04b2077228c26089b308321d17f94e29960a4f66f0c216e124c108173e40f353125b2e596cef0117687f82098b31bb
|
data/RELEASE_NOTES.md
CHANGED
data/lib/btcruby/address.rb
CHANGED
data/lib/btcruby/keychain.rb
CHANGED
@@ -24,7 +24,7 @@ module BTC
|
|
24
24
|
|
25
25
|
PUBLIC_MAINNET_VERSION = 0x0488B21E # xpub
|
26
26
|
PRIVATE_MAINNET_VERSION = 0x0488ADE4 # xprv
|
27
|
-
|
27
|
+
PUBLIC_TESTNET_VERSION = 0x043587CF # tpub
|
28
28
|
PRIVATE_TESTNET_VERSION = 0x04358394 # tprv
|
29
29
|
|
30
30
|
# Instance of BTC::Key that is a "head" of this keychain.
|
@@ -107,9 +107,9 @@ module BTC
|
|
107
107
|
|
108
108
|
def key
|
109
109
|
@key ||= if @private_key
|
110
|
-
BTC::Key.new(private_key: @private_key, public_key_compressed: true)
|
110
|
+
BTC::Key.new(private_key: @private_key, public_key_compressed: true, network: @network)
|
111
111
|
else
|
112
|
-
BTC::Key.new(public_key: @public_key)
|
112
|
+
BTC::Key.new(public_key: @public_key, network: @network)
|
113
113
|
end
|
114
114
|
end
|
115
115
|
|
@@ -127,7 +127,7 @@ module BTC
|
|
127
127
|
|
128
128
|
def extended_public_key
|
129
129
|
@extended_public_key ||= begin
|
130
|
-
prefix = _extended_key_prefix(mainnet? ? PUBLIC_MAINNET_VERSION :
|
130
|
+
prefix = _extended_key_prefix(mainnet? ? PUBLIC_MAINNET_VERSION : PUBLIC_TESTNET_VERSION)
|
131
131
|
BTC::Base58.base58check_from_data(prefix + @public_key)
|
132
132
|
end
|
133
133
|
end
|
@@ -205,7 +205,7 @@ module BTC
|
|
205
205
|
elsif _components
|
206
206
|
init_with_components(*_components)
|
207
207
|
else
|
208
|
-
raise ArgumentError, "Either seed or an extended
|
208
|
+
raise ArgumentError, "Either seed or an extended key must be provided"
|
209
209
|
end
|
210
210
|
end
|
211
211
|
|
@@ -251,13 +251,13 @@ module BTC
|
|
251
251
|
@network = Network.testnet
|
252
252
|
end
|
253
253
|
|
254
|
-
elsif version == PUBLIC_MAINNET_VERSION || version ==
|
254
|
+
elsif version == PUBLIC_MAINNET_VERSION || version == PUBLIC_TESTNET_VERSION
|
255
255
|
# Should have a 33-byte public key with non-zero first byte.
|
256
256
|
if keyprefix == 0x00
|
257
257
|
raise BTCError, "Extended public key must have non-zero first byte (received #{keyprefix})"
|
258
258
|
end
|
259
259
|
@public_key = xkeydata[45, 33]
|
260
|
-
if version ==
|
260
|
+
if version == PUBLIC_TESTNET_VERSION
|
261
261
|
@network = Network.testnet
|
262
262
|
end
|
263
263
|
else
|
@@ -452,25 +452,25 @@ module BTC
|
|
452
452
|
end
|
453
453
|
end
|
454
454
|
end
|
455
|
-
|
455
|
+
|
456
456
|
# BIP44 Support
|
457
|
-
|
457
|
+
|
458
458
|
def bip44_keychain(network: Network.mainnet)
|
459
459
|
network_index = network.mainnet? ? 0 : 1
|
460
460
|
derived_keychain(44, hardened: true).derived_keychain(network_index, hardened: true)
|
461
461
|
end
|
462
|
-
|
462
|
+
|
463
463
|
def bip44_account_keychain(account_index)
|
464
464
|
derived_keychain(account_index, hardened: true)
|
465
465
|
end
|
466
|
-
|
466
|
+
|
467
467
|
def bip44_external_keychain
|
468
468
|
derived_keychain(0, hardened: false)
|
469
469
|
end
|
470
|
-
|
470
|
+
|
471
471
|
def bip44_internal_keychain
|
472
472
|
derived_keychain(1, hardened: false)
|
473
473
|
end
|
474
|
-
|
474
|
+
|
475
475
|
end # Keychain
|
476
476
|
end # BTC
|
data/lib/btcruby/mnemonic.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# BTC::Mnemonic implements
|
1
|
+
# BTC::Mnemonic implements BIP39: mnemonic-based hierarchical deterministic wallets.
|
2
2
|
# Currently only supports restoring keychain from words. Generating sentence.
|
3
3
|
require 'openssl'
|
4
4
|
require 'openssl/digest'
|
@@ -89,6 +89,14 @@ module BTC
|
|
89
89
|
public_key_script? ||
|
90
90
|
standard_op_return_script?
|
91
91
|
end
|
92
|
+
|
93
|
+
# Returns true if this is an output script wrapped in a versioned pushdata for segwit softfork.
|
94
|
+
def versioned_script?
|
95
|
+
return chunks.size == 1 &&
|
96
|
+
chunks[0].pushdata? &&
|
97
|
+
chunks[0].canonical? &&
|
98
|
+
chunks[0].pushdata.bytesize > 2
|
99
|
+
end
|
92
100
|
|
93
101
|
# Returns true if the script is a pay-to-pubkey script:
|
94
102
|
# "<pubkey> OP_CHECKSIG"
|
@@ -248,7 +256,7 @@ module BTC
|
|
248
256
|
# and a new script instance is returned.
|
249
257
|
def without_dropped_prefix_data
|
250
258
|
if dropped_prefix_data
|
251
|
-
return
|
259
|
+
return Script.new << @chunks[2..-1]
|
252
260
|
end
|
253
261
|
self
|
254
262
|
end
|
@@ -288,7 +296,7 @@ module BTC
|
|
288
296
|
|
289
297
|
# Complete copy of a script.
|
290
298
|
def dup
|
291
|
-
|
299
|
+
Script.new(data: self.data)
|
292
300
|
end
|
293
301
|
|
294
302
|
def ==(other)
|
@@ -324,7 +332,7 @@ module BTC
|
|
324
332
|
# Wraps the recipient into an output P2SH script
|
325
333
|
# (OP_HASH160 <20-byte hash of the recipient> OP_EQUAL).
|
326
334
|
def p2sh_script
|
327
|
-
|
335
|
+
Script.new << OP_HASH160 << BTC.hash160(self.data) << OP_EQUAL
|
328
336
|
end
|
329
337
|
|
330
338
|
|
@@ -336,18 +344,18 @@ module BTC
|
|
336
344
|
def simulated_signature_script(strict: true)
|
337
345
|
if public_key_hash_script?
|
338
346
|
# assuming non-compressed pubkeys to be conservative
|
339
|
-
return
|
347
|
+
return Script.new << Script.simulated_signature(hashtype: SIGHASH_ALL) << Script.simulated_uncompressed_pubkey
|
340
348
|
|
341
349
|
elsif public_key_script?
|
342
|
-
return
|
350
|
+
return Script.new << Script.simulated_signature(hashtype: SIGHASH_ALL)
|
343
351
|
|
344
352
|
elsif script_hash_script? && !strict
|
345
353
|
# This is a wild approximation, but works well if most p2sh scripts are 2-of-3 multisig scripts.
|
346
354
|
# If you have a very particular smart contract scheme you should not use TransactionBuilder which estimates fees this way.
|
347
|
-
return
|
355
|
+
return Script.new << OP_0 << [Script.simulated_signature(hashtype: SIGHASH_ALL)]*2 << Script.simulated_multisig_script(2,3).data
|
348
356
|
|
349
357
|
elsif multisig_script?
|
350
|
-
return
|
358
|
+
return Script.new << OP_0 << [Script.simulated_signature(hashtype: SIGHASH_ALL)]*self.multisig_signatures_required
|
351
359
|
else
|
352
360
|
return nil
|
353
361
|
end
|
@@ -463,7 +471,7 @@ module BTC
|
|
463
471
|
|
464
472
|
# Same arguments as with Array#[].
|
465
473
|
def subscript(*args)
|
466
|
-
|
474
|
+
Script.new << chunks[*args]
|
467
475
|
end
|
468
476
|
alias_method :[], :subscript
|
469
477
|
|
@@ -478,7 +486,7 @@ module BTC
|
|
478
486
|
subscriptsize = subscript.chunks.size
|
479
487
|
buf = []
|
480
488
|
i = 0
|
481
|
-
result =
|
489
|
+
result = Script.new
|
482
490
|
chunks.each do |chunk|
|
483
491
|
if chunk == subscript.chunks[i]
|
484
492
|
buf << chunk
|
@@ -36,7 +36,7 @@ module BTC
|
|
36
36
|
# (required if the scripts use signature-checking opcodes).
|
37
37
|
# Checker can be transaction checker or block checker
|
38
38
|
def initialize(flags: SCRIPT_VERIFY_NONE,
|
39
|
-
extensions:
|
39
|
+
extensions: nil,
|
40
40
|
signature_checker: nil,
|
41
41
|
raise_on_failure: false,
|
42
42
|
max_pushdata_size: MAX_SCRIPT_ELEMENT_SIZE,
|
@@ -144,8 +144,8 @@ module BTC
|
|
144
144
|
@altstack = []
|
145
145
|
@run_script_chunks = script.chunks.to_a.dup # can be modified by `insert_script`
|
146
146
|
|
147
|
-
number_zero = ScriptNumber.new(integer: 0)
|
148
|
-
number_one = ScriptNumber.new(integer: 1)
|
147
|
+
number_zero = BTC::ScriptNumber.new(integer: 0)
|
148
|
+
number_one = BTC::ScriptNumber.new(integer: 1)
|
149
149
|
zero_value = "".b
|
150
150
|
false_value = "".b
|
151
151
|
true_value = "\x01".b
|
@@ -216,7 +216,7 @@ module BTC
|
|
216
216
|
case opcode
|
217
217
|
when OP_1NEGATE, OP_1..OP_16
|
218
218
|
# ( -- value)
|
219
|
-
num = ScriptNumber.new(integer: opcode - (OP_1 - 1))
|
219
|
+
num = BTC::ScriptNumber.new(integer: opcode - (OP_1 - 1))
|
220
220
|
stack_push(num.data)
|
221
221
|
# The result of these opcodes should always be the minimal way to push the data
|
222
222
|
# they push, so no need for a CheckMinimalPush here.
|
@@ -408,7 +408,7 @@ module BTC
|
|
408
408
|
|
409
409
|
when OP_DEPTH
|
410
410
|
# -- stacksize
|
411
|
-
sn = ScriptNumber.new(integer: @stack.size)
|
411
|
+
sn = BTC::ScriptNumber.new(integer: @stack.size)
|
412
412
|
stack_push(sn.data)
|
413
413
|
|
414
414
|
when OP_DROP
|
@@ -489,7 +489,7 @@ module BTC
|
|
489
489
|
if @stack.size < 1
|
490
490
|
return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION)
|
491
491
|
end
|
492
|
-
sn = ScriptNumber.new(integer: @stack.last.size)
|
492
|
+
sn = BTC::ScriptNumber.new(integer: @stack.last.size)
|
493
493
|
stack_push(sn.data)
|
494
494
|
|
495
495
|
|
@@ -548,9 +548,9 @@ module BTC
|
|
548
548
|
when OP_ABS
|
549
549
|
bn = -bn if bn < 0
|
550
550
|
when OP_NOT
|
551
|
-
bn = ScriptNumber.new(boolean: (bn == 0))
|
551
|
+
bn = BTC::ScriptNumber.new(boolean: (bn == 0))
|
552
552
|
when OP_0NOTEQUAL
|
553
|
-
bn = ScriptNumber.new(boolean: (bn != 0))
|
553
|
+
bn = BTC::ScriptNumber.new(boolean: (bn != 0))
|
554
554
|
else
|
555
555
|
raise "invalid opcode"
|
556
556
|
end
|
@@ -566,7 +566,7 @@ module BTC
|
|
566
566
|
|
567
567
|
bn1 = cast_to_number(@stack[-2])
|
568
568
|
bn2 = cast_to_number(@stack[-1])
|
569
|
-
bn = ScriptNumber.new(integer: 0)
|
569
|
+
bn = BTC::ScriptNumber.new(integer: 0)
|
570
570
|
|
571
571
|
case opcode
|
572
572
|
when OP_ADD
|
@@ -574,21 +574,21 @@ module BTC
|
|
574
574
|
when OP_SUB
|
575
575
|
bn = bn1 - bn2
|
576
576
|
when OP_BOOLAND
|
577
|
-
bn = ScriptNumber.new(boolean: ((bn1 != 0) && (bn2 != 0)))
|
577
|
+
bn = BTC::ScriptNumber.new(boolean: ((bn1 != 0) && (bn2 != 0)))
|
578
578
|
when OP_BOOLOR
|
579
|
-
bn = ScriptNumber.new(boolean: ((bn1 != 0) || (bn2 != 0)))
|
579
|
+
bn = BTC::ScriptNumber.new(boolean: ((bn1 != 0) || (bn2 != 0)))
|
580
580
|
when OP_NUMEQUAL, OP_NUMEQUALVERIFY
|
581
|
-
bn = ScriptNumber.new(boolean: (bn1 == bn2))
|
581
|
+
bn = BTC::ScriptNumber.new(boolean: (bn1 == bn2))
|
582
582
|
when OP_NUMNOTEQUAL
|
583
|
-
bn = ScriptNumber.new(boolean: (bn1 != bn2))
|
583
|
+
bn = BTC::ScriptNumber.new(boolean: (bn1 != bn2))
|
584
584
|
when OP_LESSTHAN
|
585
|
-
bn = ScriptNumber.new(boolean: (bn1 < bn2))
|
585
|
+
bn = BTC::ScriptNumber.new(boolean: (bn1 < bn2))
|
586
586
|
when OP_GREATERTHAN
|
587
|
-
bn = ScriptNumber.new(boolean: (bn1 > bn2))
|
587
|
+
bn = BTC::ScriptNumber.new(boolean: (bn1 > bn2))
|
588
588
|
when OP_LESSTHANOREQUAL
|
589
|
-
bn = ScriptNumber.new(boolean: (bn1 <= bn2))
|
589
|
+
bn = BTC::ScriptNumber.new(boolean: (bn1 <= bn2))
|
590
590
|
when OP_GREATERTHANOREQUAL
|
591
|
-
bn = ScriptNumber.new(boolean: (bn1 >= bn2))
|
591
|
+
bn = BTC::ScriptNumber.new(boolean: (bn1 >= bn2))
|
592
592
|
when OP_MIN
|
593
593
|
bn = (bn1 < bn2 ? bn1 : bn2)
|
594
594
|
when OP_MAX
|
@@ -815,7 +815,7 @@ module BTC
|
|
815
815
|
end
|
816
816
|
|
817
817
|
return true
|
818
|
-
rescue ScriptNumberError => e
|
818
|
+
rescue BTC::ScriptNumberError => e
|
819
819
|
return set_error(SCRIPT_ERR_UNKNOWN_ERROR, e.message)
|
820
820
|
end # run_script
|
821
821
|
|
@@ -914,7 +914,7 @@ module BTC
|
|
914
914
|
def cast_to_number(data,
|
915
915
|
require_minimal: flag?(SCRIPT_VERIFY_MINIMALDATA),
|
916
916
|
max_size: @integer_max_size)
|
917
|
-
ScriptNumber.new(data: data, require_minimal: require_minimal, max_size: max_size)
|
917
|
+
BTC::ScriptNumber.new(data: data, require_minimal: require_minimal, max_size: max_size)
|
918
918
|
end
|
919
919
|
|
920
920
|
def cast_to_bool(data)
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module BTC
|
2
|
+
class ScriptVersion
|
3
|
+
VERSION_DEFAULT = 0
|
4
|
+
VERSION_P2SH = 1
|
5
|
+
|
6
|
+
attr_reader :version # binary string containing the version
|
7
|
+
attr_reader :sighash_version
|
8
|
+
|
9
|
+
def initialize(version)
|
10
|
+
@version = version
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns a matching signature hash version for the given script version.
|
14
|
+
# All currently known script versions use sighash version 1.
|
15
|
+
def sighash_version
|
16
|
+
return 1
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns true if version is known.
|
20
|
+
# Unknown versions are supported too, but scripts are not even parsed and interpreted as "anyone can spend".
|
21
|
+
def known?
|
22
|
+
default? ||
|
23
|
+
p2sh?
|
24
|
+
end
|
25
|
+
|
26
|
+
def default?
|
27
|
+
@version == VERSION_DEFAULT
|
28
|
+
end
|
29
|
+
|
30
|
+
def p2sh?
|
31
|
+
@version == VERSION_P2SH
|
32
|
+
end
|
33
|
+
|
34
|
+
def name
|
35
|
+
case version
|
36
|
+
when VERSION_DEFAULT
|
37
|
+
"Default script"
|
38
|
+
when VERSION_P2SH
|
39
|
+
"P2SH v1"
|
40
|
+
else
|
41
|
+
"Unknown script version"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_i
|
46
|
+
@version
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_s
|
50
|
+
"v#{@version} (#{name})"
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module BTC
|
2
|
+
# Versioned script is a wrapper around a regular script with a version prefix.
|
3
|
+
# Note that we support version of any length, but do not parse and interpret unknown versions.
|
4
|
+
class VersionedScript
|
5
|
+
|
6
|
+
attr_reader :version # Integer
|
7
|
+
attr_reader :data # Raw data representing script wrapped in VersionedScript. For 256-bit P2SH it's just a hash, not a script.
|
8
|
+
attr_reader :wrapper_script # BTC::Script that wraps version + data
|
9
|
+
attr_reader :script_version # BTC::ScriptVersion
|
10
|
+
attr_reader :inner_script # BTC::Script derived from data, if it makes sense for the given version.
|
11
|
+
|
12
|
+
# Initializers:
|
13
|
+
# - `VersionedScript.new(wrapper_script:)` that wraps the version and inner script data.
|
14
|
+
# - `VersionedScript.new(version:, data:)` where version is one of ScriptVersion::VERSION_* and data is a raw inner script data (hash for p2sh256).
|
15
|
+
# - `VersionedScript.new(p2sh_redeem_script:)` creates a versioned script with p2sh hash of the redeem script.
|
16
|
+
def initialize(wrapper_script: nil,
|
17
|
+
version: nil, data: nil,
|
18
|
+
script: nil,
|
19
|
+
p2sh_redeem_script: nil,
|
20
|
+
)
|
21
|
+
if wrapper_script
|
22
|
+
if !wrapper_script.versioned_script?
|
23
|
+
raise ArgumentError, "Script is not a canonical versioned script with minimal pushdata encoding"
|
24
|
+
end
|
25
|
+
@wrapper_script = wrapper_script
|
26
|
+
fulldata = BTC::Data.ensure_binary_encoding(wrapper_script.chunks.first.pushdata)
|
27
|
+
@version = fulldata.bytes[0]
|
28
|
+
@data = fulldata[1..-1]
|
29
|
+
elsif script
|
30
|
+
@version = ScriptVersion::VERSION_DEFAULT
|
31
|
+
@data = script.data
|
32
|
+
elsif p2sh_redeem_script
|
33
|
+
@version = ScriptVersion::VERSION_P2SH256
|
34
|
+
@data = BTC.hash256(p2sh_redeem_script.data)
|
35
|
+
else
|
36
|
+
@version = version or raise ArgumentError, "Version is missing"
|
37
|
+
@data = data
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def wrapper_script
|
42
|
+
@wrapper_script ||= BTC::Script.new << (@version.chr.b + @data)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns inner BTC::Script if it's a default script
|
46
|
+
def inner_script
|
47
|
+
if script_version.default?
|
48
|
+
BTC::Script.new(data: data)
|
49
|
+
else
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def script_version
|
55
|
+
@script_version ||= ScriptVersion.new(@version)
|
56
|
+
end
|
57
|
+
|
58
|
+
def known_version?
|
59
|
+
script_version.known?
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
data/lib/btcruby/secp256k1.rb
CHANGED
@@ -10,8 +10,10 @@ module BTC
|
|
10
10
|
|
11
11
|
ffi_lib 'secp256k1'
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
SECP256K1_FLAGS_TYPE_CONTEXT = (1 << 0)
|
14
|
+
SECP256K1_FLAGS_BIT_CONTEXT_VERIFY = (1 << 8)
|
15
|
+
SECP256K1_FLAGS_BIT_CONTEXT_SIGN = (1 << 9)
|
16
|
+
SECP256K1_CONTEXT_SIGN = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN)
|
15
17
|
|
16
18
|
# Note: this struct is opaque, but public. Its size will eventually be guaranteed.
|
17
19
|
# See https://github.com/bitcoin/secp256k1/issues/288
|
@@ -42,7 +44,7 @@ module BTC
|
|
42
44
|
privkey_buf = FFI::MemoryPointer.new(:uchar, privkey.bytesize)
|
43
45
|
privkey_buf.put_bytes(0, privkey)
|
44
46
|
|
45
|
-
if secp256k1_ecdsa_sign(ctx,
|
47
|
+
if secp256k1_ecdsa_sign(ctx, sig.pointer, hash_buf, privkey_buf, nil, nil) == 1
|
46
48
|
# Serialize an ECDSA signature in DER format.
|
47
49
|
bufsize = 72
|
48
50
|
output_pointer = FFI::MemoryPointer.new(:uint8, bufsize)
|
@@ -60,6 +60,10 @@ module BTC
|
|
60
60
|
# Default is Transaction::DEFAULT_FEE_RATE
|
61
61
|
attr_accessor :fee_rate
|
62
62
|
|
63
|
+
# Miner's fee for this transaction.
|
64
|
+
# If `fee` is not nil, fee_rate is ignored and transaction uses the specified fee.
|
65
|
+
attr_accessor :fee
|
66
|
+
|
63
67
|
# Minimum amount of change below which transaction is not composed.
|
64
68
|
# If change amount is non-zero and below this value, more unspent outputs are used.
|
65
69
|
# If change amount is zero, change output is not even created and this attribute is not used.
|
@@ -92,10 +96,10 @@ module BTC
|
|
92
96
|
# A total size of all outputs in bytes (including change output).
|
93
97
|
attr_reader :outputs_size
|
94
98
|
|
95
|
-
|
96
99
|
# Implementation of the attributes declared above
|
97
100
|
# ===============================================
|
98
101
|
|
102
|
+
|
99
103
|
def network
|
100
104
|
@network ||= Network.default
|
101
105
|
end
|
@@ -223,7 +227,7 @@ module BTC
|
|
223
227
|
|
224
228
|
# Check if inputs cover the fees
|
225
229
|
if result.outputs_amount < 0
|
226
|
-
raise InsufficientFundsError
|
230
|
+
raise InsufficientFundsError.new(result)
|
227
231
|
end
|
228
232
|
|
229
233
|
# Warn if the output amount is relatively small.
|
@@ -282,7 +286,7 @@ module BTC
|
|
282
286
|
|
283
287
|
while true
|
284
288
|
if (sorted_utxos.size + mandatory_utxos.size) == 0
|
285
|
-
raise InsufficientFundsError
|
289
|
+
raise InsufficientFundsError.new(result)
|
286
290
|
end
|
287
291
|
|
288
292
|
utxo = nil
|
@@ -361,7 +365,7 @@ module BTC
|
|
361
365
|
else
|
362
366
|
# Change is negative, we need more funds for this transaction.
|
363
367
|
# Try adding more utxos on the next cycle.
|
364
|
-
|
368
|
+
result.fee = fee
|
365
369
|
end
|
366
370
|
|
367
371
|
end # if inputs_amount >= outputs_amount
|
@@ -374,6 +378,8 @@ module BTC
|
|
374
378
|
# Helper to compute total fee for a given transaction.
|
375
379
|
# Simulates signatures to estimate final size.
|
376
380
|
def compute_fee_for_transaction(tx, fee_rate)
|
381
|
+
# Return mining fee if set manually
|
382
|
+
return fee if fee
|
377
383
|
# Compute fees for this tx by composing a tx with properly sized dummy signatures.
|
378
384
|
simulated_tx = tx.dup
|
379
385
|
simulated_tx.inputs.each do |txin|
|
@@ -518,4 +524,3 @@ if $0 == __FILE__
|
|
518
524
|
|
519
525
|
|
520
526
|
end
|
521
|
-
|
@@ -10,6 +10,11 @@ module BTC
|
|
10
10
|
class MissingUnspentOutputsError < Error; end
|
11
11
|
|
12
12
|
# Unspent outputs are not sufficient to build the transaction.
|
13
|
-
class InsufficientFundsError < Error
|
13
|
+
class InsufficientFundsError < Error
|
14
|
+
attr_accessor :result
|
15
|
+
def initialize(result = nil)
|
16
|
+
@result = result
|
17
|
+
end
|
18
|
+
end
|
14
19
|
end
|
15
20
|
end
|
data/lib/btcruby/version.rb
CHANGED
@@ -14,7 +14,6 @@ describe BTC::CurrencyFormatter do
|
|
14
14
|
fm.string_from_number(42000*BTC::COIN + BTC::COIN/2).must_equal("42000.5")
|
15
15
|
|
16
16
|
fm.number_from_string("1").must_equal 1*BTC::COIN
|
17
|
-
fm.number_from_string("1.").must_equal 1*BTC::COIN
|
18
17
|
fm.number_from_string("1.0").must_equal 1*BTC::COIN
|
19
18
|
fm.number_from_string("42").must_equal 42*BTC::COIN
|
20
19
|
fm.number_from_string("42.123").must_equal 42*BTC::COIN + 12300000
|
@@ -35,7 +34,6 @@ describe BTC::CurrencyFormatter do
|
|
35
34
|
fm.string_from_number(42000*BTC::COIN + BTC::COIN/2).must_equal("42000.50000000")
|
36
35
|
|
37
36
|
fm.number_from_string("1").must_equal 1*BTC::COIN
|
38
|
-
fm.number_from_string("1.").must_equal 1*BTC::COIN
|
39
37
|
fm.number_from_string("1.0").must_equal 1*BTC::COIN
|
40
38
|
fm.number_from_string("42").must_equal 42*BTC::COIN
|
41
39
|
fm.number_from_string("42.123").must_equal 42*BTC::COIN + 12300000
|
data/spec/keychain_spec.rb
CHANGED
@@ -258,4 +258,9 @@ describe BTC::Keychain do
|
|
258
258
|
m0pub.extended_public_key.must_equal "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"
|
259
259
|
m0pub.extended_private_key.must_equal nil
|
260
260
|
end
|
261
|
+
it "should raise the ArgumentError when arguments are not passed" do
|
262
|
+
assert_raises ArgumentError do
|
263
|
+
BTC::Keychain.new
|
264
|
+
end
|
265
|
+
end
|
261
266
|
end
|
data/spec/script_number_spec.rb
CHANGED
@@ -22,6 +22,10 @@ describe BTC::ScriptNumber do
|
|
22
22
|
BTC::ScriptNumber.new(data: "\x01").to_i.must_equal 1
|
23
23
|
end
|
24
24
|
|
25
|
+
it "should parse 0x27 as 39" do
|
26
|
+
BTC::ScriptNumber.new(data: "\x27").to_i.must_equal 39
|
27
|
+
end
|
28
|
+
|
25
29
|
it "should parse 0xff as -127" do
|
26
30
|
BTC::ScriptNumber.new(data: "\xff").to_i.must_equal -127
|
27
31
|
end
|
data/spec/secp256k1_spec.rb
CHANGED
@@ -9,22 +9,34 @@ describe BTC::Secp256k1 do
|
|
9
9
|
sig.to_hex.must_equal sighex
|
10
10
|
end
|
11
11
|
|
12
|
-
it "should produce deterministic ECDSA signatures Bitcoin-canonical using nonce from RFC6979" do
|
12
|
+
it "should produce deterministic ECDSA signatures Bitcoin-canonical using nonce from RFC6979 01" do
|
13
13
|
verify_rfc6979_signature("cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50",
|
14
14
|
"sample",
|
15
15
|
"3045022100af340daf02cc15c8d5d08d7735dfe6b98a474ed373bdb5fbecf7571be52b384202205009fb27f37034a9b24b707b7c6b79ca23ddef9e25f7282e8a797efe53a8f124")
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should produce deterministic ECDSA signatures Bitcoin-canonical using nonce from RFC6979 02" do
|
16
19
|
verify_rfc6979_signature("0000000000000000000000000000000000000000000000000000000000000001",
|
17
20
|
"Satoshi Nakamoto",
|
18
21
|
"3045022100934b1ea10a4b3c1757e2b0c017d0b6143ce3c9a7e6a4a49860d7a6ab210ee3d802202442ce9d2b916064108014783e923ec36b49743e2ffa1c4496f01a512aafd9e5")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should produce deterministic ECDSA signatures Bitcoin-canonical using nonce from RFC6979 03" do
|
19
25
|
verify_rfc6979_signature("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
|
20
26
|
"Satoshi Nakamoto",
|
21
27
|
"3045022100fd567d121db66e382991534ada77a6bd3106f0a1098c231e47993447cd6af2d002206b39cd0eb1bc8603e159ef5c20a5c8ad685a45b06ce9bebed3f153d10d93bed5")
|
28
|
+
end
|
29
|
+
it "should produce deterministic ECDSA signatures Bitcoin-canonical using nonce from RFC6979 04" do
|
22
30
|
verify_rfc6979_signature("f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181",
|
23
31
|
"Alan Turing",
|
24
32
|
"304402207063ae83e7f62bbb171798131b4a0564b956930092b33b07b395615d9ec7e15c022058dfcc1e00a35e1572f366ffe34ba0fc47db1e7189759b9fb233c5b05ab388ea")
|
33
|
+
end
|
34
|
+
it "should produce deterministic ECDSA signatures Bitcoin-canonical using nonce from RFC6979 05" do
|
25
35
|
verify_rfc6979_signature("0000000000000000000000000000000000000000000000000000000000000001",
|
26
36
|
"All those moments will be lost in time, like tears in rain. Time to die...",
|
27
37
|
"30450221008600dbd41e348fe5c9465ab92d23e3db8b98b873beecd930736488696438cb6b0220547fe64427496db33bf66019dacbf0039c04199abb0122918601db38a72cfc21")
|
38
|
+
end
|
39
|
+
it "should produce deterministic ECDSA signatures Bitcoin-canonical using nonce from RFC6979 06" do
|
28
40
|
verify_rfc6979_signature("e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2",
|
29
41
|
"There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!",
|
30
42
|
"3045022100b552edd27580141f3b2a5463048cb7cd3e047b97c9f98076c32dbdf85a68718b0220279fa72dd19bfae05577e06c7c0c1900c371fcd5893f7e1d56a37d30174671f6")
|
@@ -68,6 +68,12 @@ describe BTC::TransactionBuilder do
|
|
68
68
|
result.fee.must_be :<, 0.01 * BTC::COIN
|
69
69
|
end
|
70
70
|
|
71
|
+
it "should be able to set mining fee" do
|
72
|
+
@builder.fee =50690
|
73
|
+
result = @builder.build()
|
74
|
+
result.fee.must_equal 50690
|
75
|
+
end
|
76
|
+
|
71
77
|
it "should have valid amounts" do
|
72
78
|
result = @builder.build
|
73
79
|
result.inputs_amount.must_equal mock_utxos.inject(0){|sum, out| sum + out.value}
|
@@ -190,6 +196,12 @@ describe BTC::TransactionBuilder do
|
|
190
196
|
result.fee.must_be :<, 0.01 * BTC::COIN
|
191
197
|
end
|
192
198
|
|
199
|
+
it "should be able to set mining fee" do
|
200
|
+
@builder.fee =50690
|
201
|
+
result = @builder.build()
|
202
|
+
result.fee.must_equal 50690
|
203
|
+
end
|
204
|
+
|
193
205
|
it "should have valid amounts" do
|
194
206
|
result = @builder.build
|
195
207
|
result.inputs_amount.must_equal 11*1000_00
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: btcruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '1.
|
4
|
+
version: '1.7'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Oleg Andreev
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2017-09-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ffi
|
@@ -115,10 +115,12 @@ files:
|
|
115
115
|
- lib/btcruby/script/script_interpreter.rb
|
116
116
|
- lib/btcruby/script/script_interpreter_extension.rb
|
117
117
|
- lib/btcruby/script/script_number.rb
|
118
|
+
- lib/btcruby/script/script_version.rb
|
118
119
|
- lib/btcruby/script/signature_checker.rb
|
119
120
|
- lib/btcruby/script/signature_hashtype.rb
|
120
121
|
- lib/btcruby/script/test_signature_checker.rb
|
121
122
|
- lib/btcruby/script/transaction_signature_checker.rb
|
123
|
+
- lib/btcruby/script/versioned_script.rb
|
122
124
|
- lib/btcruby/secp256k1.rb
|
123
125
|
- lib/btcruby/ssss.rb
|
124
126
|
- lib/btcruby/transaction.rb
|