tapyrus 0.2.7 → 0.2.12
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 +37 -0
- data/.prettierignore +3 -0
- data/.prettierrc.yaml +3 -0
- data/.ruby-version +1 -1
- data/CODE_OF_CONDUCT.md +7 -7
- data/README.md +14 -17
- data/Rakefile +3 -3
- data/lib/openassets/marker_output.rb +0 -4
- data/lib/openassets/payload.rb +4 -10
- data/lib/openassets.rb +0 -2
- data/lib/schnorr/sign_to_contract.rb +51 -0
- data/lib/schnorr/signature.rb +3 -6
- data/lib/schnorr.rb +14 -9
- data/lib/tapyrus/base58.rb +7 -6
- data/lib/tapyrus/bip175.rb +78 -0
- data/lib/tapyrus/block.rb +1 -2
- data/lib/tapyrus/block_header.rb +15 -9
- data/lib/tapyrus/bloom_filter.rb +5 -3
- data/lib/tapyrus/chain_params.rb +1 -4
- data/lib/tapyrus/chainparams/dev.yml +3 -2
- data/lib/tapyrus/chainparams/prod.yml +3 -2
- data/lib/tapyrus/constants.rb +29 -23
- data/lib/tapyrus/errors.rb +1 -3
- data/lib/tapyrus/ext/ecdsa.rb +4 -4
- data/lib/tapyrus/ext/json_parser.rb +1 -4
- data/lib/tapyrus/ext.rb +1 -1
- data/lib/tapyrus/ext_key.rb +44 -32
- data/lib/tapyrus/key.rb +31 -35
- data/lib/tapyrus/key_path.rb +15 -12
- data/lib/tapyrus/logger.rb +20 -16
- data/lib/tapyrus/merkle_tree.rb +22 -20
- data/lib/tapyrus/message/addr.rb +1 -7
- data/lib/tapyrus/message/base.rb +0 -3
- data/lib/tapyrus/message/block.rb +2 -9
- data/lib/tapyrus/message/block_transaction_request.rb +3 -6
- data/lib/tapyrus/message/block_transactions.rb +2 -6
- data/lib/tapyrus/message/block_txn.rb +0 -4
- data/lib/tapyrus/message/cmpct_block.rb +1 -7
- data/lib/tapyrus/message/error.rb +1 -4
- data/lib/tapyrus/message/fee_filter.rb +1 -4
- data/lib/tapyrus/message/filter_add.rb +0 -4
- data/lib/tapyrus/message/filter_clear.rb +0 -4
- data/lib/tapyrus/message/filter_load.rb +2 -5
- data/lib/tapyrus/message/get_addr.rb +0 -4
- data/lib/tapyrus/message/get_block_txn.rb +0 -4
- data/lib/tapyrus/message/get_blocks.rb +0 -3
- data/lib/tapyrus/message/get_data.rb +1 -4
- data/lib/tapyrus/message/get_headers.rb +1 -3
- data/lib/tapyrus/message/header_and_short_ids.rb +3 -9
- data/lib/tapyrus/message/headers.rb +0 -4
- data/lib/tapyrus/message/headers_parser.rb +3 -8
- data/lib/tapyrus/message/inv.rb +1 -4
- data/lib/tapyrus/message/inventories_parser.rb +2 -7
- data/lib/tapyrus/message/inventory.rb +12 -5
- data/lib/tapyrus/message/mem_pool.rb +0 -4
- data/lib/tapyrus/message/merkle_block.rb +4 -9
- data/lib/tapyrus/message/network_addr.rb +7 -6
- data/lib/tapyrus/message/not_found.rb +0 -3
- data/lib/tapyrus/message/ping.rb +0 -3
- data/lib/tapyrus/message/pong.rb +0 -3
- data/lib/tapyrus/message/prefilled_tx.rb +0 -4
- data/lib/tapyrus/message/reject.rb +0 -3
- data/lib/tapyrus/message/send_cmpct.rb +1 -3
- data/lib/tapyrus/message/send_headers.rb +0 -3
- data/lib/tapyrus/message/tx.rb +0 -4
- data/lib/tapyrus/message/ver_ack.rb +1 -5
- data/lib/tapyrus/message/version.rb +2 -5
- data/lib/tapyrus/message.rb +14 -16
- data/lib/tapyrus/mnemonic.rb +17 -15
- data/lib/tapyrus/network/connection.rb +0 -3
- data/lib/tapyrus/network/message_handler.rb +61 -60
- data/lib/tapyrus/network/peer.rb +13 -12
- data/lib/tapyrus/network/peer_discovery.rb +10 -9
- data/lib/tapyrus/network/pool.rb +12 -12
- data/lib/tapyrus/network.rb +0 -2
- data/lib/tapyrus/node/cli.rb +12 -14
- data/lib/tapyrus/node/configuration.rb +1 -3
- data/lib/tapyrus/node/spv.rb +2 -3
- data/lib/tapyrus/node.rb +1 -1
- data/lib/tapyrus/opcodes.rb +9 -7
- data/lib/tapyrus/out_point.rb +5 -5
- data/lib/tapyrus/rpc/http_server.rb +21 -22
- data/lib/tapyrus/rpc/request_handler.rb +16 -21
- data/lib/tapyrus/rpc/tapyrus_core_client.rb +67 -25
- data/lib/tapyrus/rpc.rb +1 -0
- data/lib/tapyrus/script/color.rb +10 -0
- data/lib/tapyrus/script/multisig.rb +13 -12
- data/lib/tapyrus/script/script.rb +93 -88
- data/lib/tapyrus/script/script_error.rb +1 -4
- data/lib/tapyrus/script/script_interpreter.rb +439 -399
- data/lib/tapyrus/script/tx_checker.rb +20 -10
- data/lib/tapyrus/secp256k1/native.rb +14 -15
- data/lib/tapyrus/secp256k1/rfc6979.rb +7 -4
- data/lib/tapyrus/secp256k1/ruby.rb +10 -12
- data/lib/tapyrus/secp256k1.rb +0 -4
- data/lib/tapyrus/slip39/share.rb +41 -29
- data/lib/tapyrus/slip39/sss.rb +92 -49
- data/lib/tapyrus/slip39.rb +20 -5
- data/lib/tapyrus/store/chain_entry.rb +0 -4
- data/lib/tapyrus/store/db/level_db.rb +5 -9
- data/lib/tapyrus/store/db.rb +0 -2
- data/lib/tapyrus/store/spv_chain.rb +11 -17
- data/lib/tapyrus/store.rb +1 -3
- data/lib/tapyrus/tx.rb +45 -37
- data/lib/tapyrus/tx_builder.rb +160 -0
- data/lib/tapyrus/tx_in.rb +1 -6
- data/lib/tapyrus/tx_out.rb +2 -7
- data/lib/tapyrus/util.rb +7 -9
- data/lib/tapyrus/validation.rb +12 -11
- data/lib/tapyrus/version.rb +1 -1
- data/lib/tapyrus/wallet/account.rb +22 -18
- data/lib/tapyrus/wallet/base.rb +12 -9
- data/lib/tapyrus/wallet/db.rb +6 -9
- data/lib/tapyrus/wallet/master_key.rb +2 -4
- data/lib/tapyrus.rb +7 -22
- data/tapyrusrb.gemspec +13 -14
- metadata +26 -7
- data/.travis.yml +0 -14
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
module Tapyrus
|
|
2
2
|
class TxChecker
|
|
3
|
-
|
|
4
3
|
attr_reader :tx
|
|
5
4
|
attr_reader :input_index
|
|
6
5
|
attr_reader :amount
|
|
@@ -22,8 +21,8 @@ module Tapyrus
|
|
|
22
21
|
script_sig = script_sig.htb
|
|
23
22
|
hash_type = script_sig[-1].unpack('C').first
|
|
24
23
|
sig = script_sig[0..-2]
|
|
25
|
-
sighash =
|
|
26
|
-
|
|
24
|
+
sighash =
|
|
25
|
+
tx.sighash_for_input(input_index, script_code, hash_type: hash_type, amount: amount, sig_version: sig_version)
|
|
27
26
|
verify_sig(sig.bth, pubkey, sighash)
|
|
28
27
|
end
|
|
29
28
|
|
|
@@ -33,7 +32,8 @@ module Tapyrus
|
|
|
33
32
|
# @param [String] digest a message digest with binary format to be verified.
|
|
34
33
|
# @return [Boolean] if check is passed return true, otherwise false.
|
|
35
34
|
def verify_sig(sig, pubkey, digest, allow_hybrid: false)
|
|
36
|
-
key_type =
|
|
35
|
+
key_type =
|
|
36
|
+
pubkey.start_with?('02') || pubkey.start_with?('03') ? Key::TYPES[:compressed] : Key::TYPES[:uncompressed]
|
|
37
37
|
sig = sig.htb
|
|
38
38
|
algo = sig.bytesize == 64 ? :schnorr : :ecdsa
|
|
39
39
|
begin
|
|
@@ -49,8 +49,10 @@ module Tapyrus
|
|
|
49
49
|
# distinguished by whether nLockTime < LOCKTIME_THRESHOLD.
|
|
50
50
|
|
|
51
51
|
# We want to compare apples to apples, so fail the script unless the type of nLockTime being tested is the same as the nLockTime in the transaction.
|
|
52
|
-
unless (
|
|
53
|
-
|
|
52
|
+
unless (
|
|
53
|
+
(tx.lock_time < LOCKTIME_THRESHOLD && locktime < LOCKTIME_THRESHOLD) ||
|
|
54
|
+
(tx.lock_time >= LOCKTIME_THRESHOLD && locktime >= LOCKTIME_THRESHOLD)
|
|
55
|
+
)
|
|
54
56
|
return false
|
|
55
57
|
end
|
|
56
58
|
|
|
@@ -68,6 +70,7 @@ module Tapyrus
|
|
|
68
70
|
|
|
69
71
|
def check_sequence(sequence)
|
|
70
72
|
tx_sequence = tx.inputs[input_index].sequence
|
|
73
|
+
|
|
71
74
|
# Fail if the transaction's version number is not set high enough to trigger BIP 68 rules.
|
|
72
75
|
return false if tx.features < 2
|
|
73
76
|
|
|
@@ -84,14 +87,21 @@ module Tapyrus
|
|
|
84
87
|
# distinguished by whether sequence_masked < TxIn#SEQUENCE_LOCKTIME_TYPE_FLAG.
|
|
85
88
|
# We want to compare apples to apples, so fail the script
|
|
86
89
|
# unless the type of nSequenceMasked being tested is the same as the nSequenceMasked in the transaction.
|
|
87
|
-
unless (
|
|
88
|
-
|
|
90
|
+
unless (
|
|
91
|
+
(
|
|
92
|
+
tx_sequence_masked < TxIn::SEQUENCE_LOCKTIME_TYPE_FLAG &&
|
|
93
|
+
sequence_masked < TxIn::SEQUENCE_LOCKTIME_TYPE_FLAG
|
|
94
|
+
) ||
|
|
95
|
+
(
|
|
96
|
+
tx_sequence_masked >= TxIn::SEQUENCE_LOCKTIME_TYPE_FLAG &&
|
|
97
|
+
sequence_masked >= TxIn::SEQUENCE_LOCKTIME_TYPE_FLAG
|
|
98
|
+
)
|
|
99
|
+
)
|
|
89
100
|
return false
|
|
90
101
|
end
|
|
91
102
|
|
|
92
103
|
# Now that we know we're comparing apples-to-apples, the comparison is a simple numeric one.
|
|
93
104
|
sequence_masked <= tx_sequence_masked
|
|
94
105
|
end
|
|
95
|
-
|
|
96
106
|
end
|
|
97
|
-
end
|
|
107
|
+
end
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
|
|
4
4
|
module Tapyrus
|
|
5
5
|
module Secp256k1
|
|
6
|
-
|
|
7
6
|
# binding for secp256k1 (https://github.com/chaintope/tapyrus-core/tree/v0.4.0/src/secp256k1)
|
|
8
7
|
# tag: v0.4.0
|
|
9
8
|
# this is not included by default, to enable set shared object path to ENV['SECP256K1_LIB_PATH']
|
|
@@ -80,8 +79,8 @@ module Tapyrus
|
|
|
80
79
|
priv_key = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, SecureRandom.random_bytes(32))
|
|
81
80
|
ret = secp256k1_ec_seckey_verify(context, priv_key)
|
|
82
81
|
end
|
|
83
|
-
private_key =
|
|
84
|
-
[private_key
|
|
82
|
+
private_key = priv_key.read_string(32).bth
|
|
83
|
+
[private_key, generate_pubkey_in_context(context, private_key, compressed: compressed)]
|
|
85
84
|
end
|
|
86
85
|
end
|
|
87
86
|
|
|
@@ -92,9 +91,7 @@ module Tapyrus
|
|
|
92
91
|
end
|
|
93
92
|
|
|
94
93
|
def generate_pubkey(priv_key, compressed: true)
|
|
95
|
-
with_context
|
|
96
|
-
generate_pubkey_in_context(context, priv_key, compressed: compressed)
|
|
97
|
-
end
|
|
94
|
+
with_context { |context| generate_pubkey_in_context(context, priv_key, compressed: compressed) }
|
|
98
95
|
end
|
|
99
96
|
|
|
100
97
|
# sign data.
|
|
@@ -152,13 +149,14 @@ module Tapyrus
|
|
|
152
149
|
|
|
153
150
|
pubkey = FFI::MemoryPointer.new(:uchar, 65)
|
|
154
151
|
pubkey_len = FFI::MemoryPointer.new(:uint64)
|
|
155
|
-
result =
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
152
|
+
result =
|
|
153
|
+
if compressed
|
|
154
|
+
pubkey_len.put_uint64(0, 33)
|
|
155
|
+
secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_COMPRESSED)
|
|
156
|
+
else
|
|
157
|
+
pubkey_len.put_uint64(0, 65)
|
|
158
|
+
secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_UNCOMPRESSED)
|
|
159
|
+
end
|
|
162
160
|
raise 'error serialize pubkey' unless result || pubkey_len.read_uint64 > 0
|
|
163
161
|
pubkey.read_string(pubkey_len.read_uint64).bth
|
|
164
162
|
end
|
|
@@ -196,7 +194,9 @@ module Tapyrus
|
|
|
196
194
|
|
|
197
195
|
signature = FFI::MemoryPointer.new(:uchar, 64)
|
|
198
196
|
msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
|
|
199
|
-
|
|
197
|
+
unless secp256k1_schnorr_sign(context, signature, msg32, secret, nil, nil) == 1
|
|
198
|
+
raise 'Failed to generate schnorr signature.'
|
|
199
|
+
end
|
|
200
200
|
signature.read_string(64)
|
|
201
201
|
end
|
|
202
202
|
end
|
|
@@ -241,7 +241,6 @@ module Tapyrus
|
|
|
241
241
|
result == 1
|
|
242
242
|
end
|
|
243
243
|
end
|
|
244
|
-
|
|
245
244
|
end
|
|
246
245
|
end
|
|
247
246
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
module Tapyrus
|
|
2
2
|
module Secp256k1
|
|
3
3
|
module RFC6979
|
|
4
|
-
|
|
5
4
|
INITIAL_V = '0101010101010101010101010101010101010101010101010101010101010101'.htb
|
|
6
5
|
INITIAL_K = '0000000000000000000000000000000000000000000000000000000000000000'.htb
|
|
7
6
|
ZERO_B = '00'.htb
|
|
@@ -17,17 +16,22 @@ module Tapyrus
|
|
|
17
16
|
def generate_rfc6979_nonce(key_data, extra_entropy)
|
|
18
17
|
v = INITIAL_V # 3.2.b
|
|
19
18
|
k = INITIAL_K # 3.2.c
|
|
19
|
+
|
|
20
20
|
# 3.2.d
|
|
21
21
|
k = Tapyrus.hmac_sha256(k, v + ZERO_B + key_data + extra_entropy)
|
|
22
|
+
|
|
22
23
|
# 3.2.e
|
|
23
24
|
v = Tapyrus.hmac_sha256(k, v)
|
|
25
|
+
|
|
24
26
|
# 3.2.f
|
|
25
27
|
k = Tapyrus.hmac_sha256(k, v + ONE_B + key_data + extra_entropy)
|
|
28
|
+
|
|
26
29
|
# 3.2.g
|
|
27
30
|
v = Tapyrus.hmac_sha256(k, v)
|
|
31
|
+
|
|
28
32
|
# 3.2.h
|
|
29
33
|
t = ''
|
|
30
|
-
|
|
34
|
+
10_000.times do
|
|
31
35
|
v = Tapyrus.hmac_sha256(k, v)
|
|
32
36
|
t = (t + v)
|
|
33
37
|
t_num = t.bth.to_i(16)
|
|
@@ -37,7 +41,6 @@ module Tapyrus
|
|
|
37
41
|
end
|
|
38
42
|
raise 'A valid nonce was not found.'
|
|
39
43
|
end
|
|
40
|
-
|
|
41
44
|
end
|
|
42
45
|
end
|
|
43
|
-
end
|
|
46
|
+
end
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
module Tapyrus
|
|
2
2
|
module Secp256k1
|
|
3
|
-
|
|
4
3
|
# secp256 module using ecdsa gem
|
|
5
4
|
# https://github.com/DavidEGrayson/ruby_ecdsa
|
|
6
5
|
module Ruby
|
|
7
|
-
|
|
8
6
|
module_function
|
|
9
7
|
|
|
10
8
|
# generate ec private key and public key
|
|
@@ -58,7 +56,7 @@ module Tapyrus
|
|
|
58
56
|
end
|
|
59
57
|
end
|
|
60
58
|
|
|
61
|
-
alias
|
|
59
|
+
alias valid_sig? verify_sig
|
|
62
60
|
|
|
63
61
|
module_function :valid_sig?
|
|
64
62
|
|
|
@@ -68,7 +66,8 @@ module Tapyrus
|
|
|
68
66
|
# @return [Boolean] If valid public key return true, otherwise false.
|
|
69
67
|
def parse_ec_pubkey?(pubkey, allow_hybrid = false)
|
|
70
68
|
begin
|
|
71
|
-
point =
|
|
69
|
+
point =
|
|
70
|
+
ECDSA::Format::PointOctetString.decode(pubkey.htb, ECDSA::Group::Secp256k1, allow_hybrid: allow_hybrid)
|
|
72
71
|
ECDSA::Group::Secp256k1.valid_public_key?(point)
|
|
73
72
|
rescue ECDSA::Format::DecodeError
|
|
74
73
|
false
|
|
@@ -80,11 +79,11 @@ module Tapyrus
|
|
|
80
79
|
def repack_pubkey(pubkey)
|
|
81
80
|
p = pubkey.htb
|
|
82
81
|
case p[0]
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
when "\x06", "\x07"
|
|
83
|
+
p[0] = "\x04"
|
|
84
|
+
p
|
|
85
|
+
else
|
|
86
|
+
pubkey.htb
|
|
88
87
|
end
|
|
89
88
|
end
|
|
90
89
|
|
|
@@ -104,7 +103,8 @@ module Tapyrus
|
|
|
104
103
|
e = ECDSA.normalize_digest(data, GROUP.bit_length)
|
|
105
104
|
s = point_field.mod(point_field.inverse(nonce) * (e + r * private_key))
|
|
106
105
|
|
|
107
|
-
if s > (GROUP.order / 2)
|
|
106
|
+
if s > (GROUP.order / 2)
|
|
107
|
+
# convert low-s
|
|
108
108
|
s = GROUP.order - s
|
|
109
109
|
end
|
|
110
110
|
|
|
@@ -125,8 +125,6 @@ module Tapyrus
|
|
|
125
125
|
false
|
|
126
126
|
end
|
|
127
127
|
end
|
|
128
|
-
|
|
129
128
|
end
|
|
130
|
-
|
|
131
129
|
end
|
|
132
130
|
end
|
data/lib/tapyrus/secp256k1.rb
CHANGED
data/lib/tapyrus/slip39/share.rb
CHANGED
|
@@ -1,36 +1,35 @@
|
|
|
1
1
|
module Tapyrus
|
|
2
2
|
module SLIP39
|
|
3
|
-
|
|
4
3
|
# Share of Shamir's Secret Sharing Scheme
|
|
5
4
|
class Share
|
|
6
|
-
|
|
7
|
-
attr_accessor :
|
|
8
|
-
attr_accessor :
|
|
9
|
-
attr_accessor :
|
|
10
|
-
attr_accessor :
|
|
11
|
-
attr_accessor :
|
|
12
|
-
attr_accessor :member_index # 4 bits, Integer
|
|
5
|
+
attr_accessor :id # 15 bits, Integer
|
|
6
|
+
attr_accessor :iteration_exp # 5 bits, Integer
|
|
7
|
+
attr_accessor :group_index # 4 bits, Integer
|
|
8
|
+
attr_accessor :group_threshold # 4 bits, Integer
|
|
9
|
+
attr_accessor :group_count # 4 bits, Integer
|
|
10
|
+
attr_accessor :member_index # 4 bits, Integer
|
|
13
11
|
attr_accessor :member_threshold # 4 bits, Integer
|
|
14
|
-
attr_accessor :value
|
|
15
|
-
attr_accessor :checksum
|
|
12
|
+
attr_accessor :value # 8n bits, hex string.
|
|
13
|
+
attr_accessor :checksum # 30 bits, Integer
|
|
16
14
|
|
|
17
15
|
# Recover Share from the mnemonic words
|
|
18
16
|
# @param [Array{String}] words the mnemonic words
|
|
19
17
|
# @return [Tapyrus::SLIP39::Share] a share
|
|
20
18
|
def self.from_words(words)
|
|
21
19
|
raise ArgumentError, 'Mnemonics should be an array of strings' unless words.is_a?(Array)
|
|
22
|
-
indices =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
indices =
|
|
21
|
+
words.map do |word|
|
|
22
|
+
index = Tapyrus::SLIP39::WORDS.index(word.downcase)
|
|
23
|
+
raise IndexError, 'word not found in words list.' unless index
|
|
24
|
+
index
|
|
25
|
+
end
|
|
27
26
|
|
|
28
27
|
raise ArgumentError, 'Invalid mnemonic length.' if indices.size < MIN_MNEMONIC_LENGTH_WORDS
|
|
29
28
|
raise ArgumentError, 'Invalid mnemonic checksum.' unless verify_rs1024_checksum(indices)
|
|
30
29
|
|
|
31
30
|
padding_length = (RADIX_BITS * (indices.size - METADATA_LENGTH_WORDS)) % 16
|
|
32
31
|
raise ArgumentError, 'Invalid mnemonic length.' if padding_length > 8
|
|
33
|
-
data = indices.map{|i|i.to_s(2).rjust(10, '0')}.join
|
|
32
|
+
data = indices.map { |i| i.to_s(2).rjust(10, '0') }.join
|
|
34
33
|
|
|
35
34
|
s = self.new
|
|
36
35
|
s.id = data[0...ID_LENGTH_BITS].to_i(2)
|
|
@@ -38,14 +37,17 @@ module Tapyrus
|
|
|
38
37
|
s.group_index = data[20...24].to_i(2)
|
|
39
38
|
s.group_threshold = data[24...28].to_i(2) + 1
|
|
40
39
|
s.group_count = data[28...32].to_i(2) + 1
|
|
41
|
-
|
|
40
|
+
if s.group_threshold > s.group_count
|
|
41
|
+
raise ArgumentError,
|
|
42
|
+
"Invalid mnemonic. Group threshold(#{s.group_threshold}) cannot be greater than group count(#{s.group_count})."
|
|
43
|
+
end
|
|
42
44
|
s.member_index = data[32...36].to_i(2)
|
|
43
45
|
s.member_threshold = data[36...40].to_i(2) + 1
|
|
44
46
|
value_length = data.length - 70
|
|
45
47
|
start_index = 40 + padding_length
|
|
46
48
|
end_index = start_index + value_length - padding_length
|
|
47
49
|
padding_value = data[40...(40 + padding_length)]
|
|
48
|
-
raise ArgumentError,
|
|
50
|
+
raise ArgumentError, 'Invalid mnemonic. padding must only zero.' unless padding_value.to_i(2) == 0
|
|
49
51
|
s.value = data[start_index...end_index].to_i(2).to_even_length_hex
|
|
50
52
|
s.checksum = data[(40 + value_length)..-1].to_i(2)
|
|
51
53
|
s
|
|
@@ -55,25 +57,34 @@ module Tapyrus
|
|
|
55
57
|
# @return [Array[String]] array of mnemonic word.
|
|
56
58
|
def to_words
|
|
57
59
|
indices = build_word_indices
|
|
58
|
-
indices.map{|index| Tapyrus::SLIP39::WORDS[index]}
|
|
60
|
+
indices.map { |index| Tapyrus::SLIP39::WORDS[index] }
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
# Calculate checksum using current fields
|
|
62
64
|
# @return [Integer] checksum
|
|
63
65
|
def calculate_checksum
|
|
64
66
|
indices = build_word_indices(false)
|
|
65
|
-
create_rs1024_checksum(indices).map{|i|i.to_bits(10)}.join.to_i(2)
|
|
67
|
+
create_rs1024_checksum(indices).map { |i| i.to_bits(10) }.join.to_i(2)
|
|
66
68
|
end
|
|
67
69
|
|
|
68
70
|
def self.rs1024_polymod(values)
|
|
69
|
-
gen = [
|
|
71
|
+
gen = [
|
|
72
|
+
0xe0e040,
|
|
73
|
+
0x1c1c080,
|
|
74
|
+
0x3838100,
|
|
75
|
+
0x7070200,
|
|
76
|
+
0xe0e0009,
|
|
77
|
+
0x1c0c2412,
|
|
78
|
+
0x38086c24,
|
|
79
|
+
0x3090fc48,
|
|
80
|
+
0x21b1f890,
|
|
81
|
+
0x3f3f120
|
|
82
|
+
]
|
|
70
83
|
chk = 1
|
|
71
84
|
values.each do |v|
|
|
72
85
|
b = (chk >> 20)
|
|
73
86
|
chk = (chk & 0xfffff) << 10 ^ v
|
|
74
|
-
10.times
|
|
75
|
-
chk ^= (((b >> i) & 1 == 1) ? gen[i] : 0)
|
|
76
|
-
end
|
|
87
|
+
10.times { |i| chk ^= (((b >> i) & 1 == 1) ? gen[i] : 0) }
|
|
77
88
|
end
|
|
78
89
|
chk
|
|
79
90
|
end
|
|
@@ -89,14 +100,16 @@ module Tapyrus
|
|
|
89
100
|
s << group_index.to_bits(4)
|
|
90
101
|
s << (group_threshold - 1).to_bits(4)
|
|
91
102
|
s << (group_count - 1).to_bits(4)
|
|
92
|
-
|
|
103
|
+
if group_threshold > group_count
|
|
104
|
+
raise StandardError, "Group threshold(#{group_threshold}) cannot be greater than group count(#{group_count})."
|
|
105
|
+
end
|
|
93
106
|
s << member_index.to_bits(4)
|
|
94
107
|
s << (member_threshold - 1).to_bits(4)
|
|
95
108
|
value_length = value.to_i(16).bit_length
|
|
96
109
|
padding_length = RADIX_BITS - (value_length % RADIX_BITS)
|
|
97
110
|
s << value.to_i(16).to_bits(value_length + padding_length)
|
|
98
111
|
s << checksum.to_bits(30) if include_checksum
|
|
99
|
-
s.chars.each_slice(10).map{|index| index.join.to_i(2)}
|
|
112
|
+
s.chars.each_slice(10).map { |index| index.join.to_i(2) }
|
|
100
113
|
end
|
|
101
114
|
|
|
102
115
|
# Verify RS1024 checksum
|
|
@@ -112,11 +125,10 @@ module Tapyrus
|
|
|
112
125
|
def create_rs1024_checksum(data)
|
|
113
126
|
values = CUSTOMIZATION_STRING + data + Array.new(CHECKSUM_LENGTH_WORDS, 0)
|
|
114
127
|
polymod = Tapyrus::SLIP39::Share.rs1024_polymod(values) ^ 1
|
|
115
|
-
CHECKSUM_LENGTH_WORDS.times.to_a.reverse.map {|i|(polymod >> (10 * i)) & 1023 }
|
|
128
|
+
CHECKSUM_LENGTH_WORDS.times.to_a.reverse.map { |i| (polymod >> (10 * i)) & 1023 }
|
|
116
129
|
end
|
|
117
130
|
|
|
118
131
|
private_class_method :verify_rs1024_checksum
|
|
119
|
-
|
|
120
132
|
end
|
|
121
133
|
end
|
|
122
|
-
end
|
|
134
|
+
end
|
data/lib/tapyrus/slip39/sss.rb
CHANGED
|
@@ -2,10 +2,8 @@ require 'securerandom'
|
|
|
2
2
|
|
|
3
3
|
module Tapyrus
|
|
4
4
|
module SLIP39
|
|
5
|
-
|
|
6
5
|
# Shamir's Secret Sharing
|
|
7
6
|
class SSS
|
|
8
|
-
|
|
9
7
|
include Tapyrus::Util
|
|
10
8
|
extend Tapyrus::Util
|
|
11
9
|
|
|
@@ -37,39 +35,56 @@ module Tapyrus
|
|
|
37
35
|
raise ArgumentError, 'Groups is empty.' if groups.empty?
|
|
38
36
|
raise ArgumentError, 'Group threshold must be greater than 0.' if group_threshold.nil? || group_threshold < 1
|
|
39
37
|
raise ArgumentError, 'Master secret does not specified.' unless secret
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
if (secret.htb.bytesize * 8) < MIN_STRENGTH_BITS
|
|
39
|
+
raise ArgumentError,
|
|
40
|
+
"The length of the master secret (#{secret.htb.bytesize} bytes) must be at least #{MIN_STRENGTH_BITS / 8} bytes."
|
|
41
|
+
end
|
|
42
|
+
unless secret.bytesize.even?
|
|
43
|
+
raise ArgumentError, 'The length of the master secret in bytes must be an even number.'
|
|
44
|
+
end
|
|
45
|
+
unless passphrase.ascii_only?
|
|
46
|
+
raise ArgumentError, 'The passphrase must contain only printable ASCII characters (code points 32-126).'
|
|
47
|
+
end
|
|
48
|
+
if group_threshold > groups.length
|
|
49
|
+
raise ArgumentError,
|
|
50
|
+
"The requested group threshold (#{group_threshold}) must not exceed the number of groups (#{groups.length})."
|
|
51
|
+
end
|
|
44
52
|
groups.each do |threshold, count|
|
|
45
53
|
raise ArgumentError, 'Group threshold must be greater than 0.' if threshold.nil? || threshold < 1
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
if threshold > count
|
|
55
|
+
raise ArgumentError,
|
|
56
|
+
"The requested member threshold (#{threshold}) must not exceed the number of share (#{count})."
|
|
57
|
+
end
|
|
58
|
+
if threshold == 1 && count > 1
|
|
59
|
+
raise ArgumentError,
|
|
60
|
+
'Creating multiple member shares with member threshold 1 is not allowed. Use 1-of-1 member sharing instead.'
|
|
61
|
+
end
|
|
48
62
|
end
|
|
49
63
|
|
|
50
|
-
id = SecureRandom.random_number(
|
|
64
|
+
id = SecureRandom.random_number(32_767) # 32767 is max number for 15 bits.
|
|
51
65
|
ems = encrypt(secret, passphrase, exp, id)
|
|
52
66
|
|
|
53
67
|
group_shares = split_secret(group_threshold, groups.length, ems)
|
|
54
68
|
|
|
55
|
-
shares =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
shares =
|
|
70
|
+
group_shares.map.with_index do |s, i|
|
|
71
|
+
group_index, group_share = s[0], s[1]
|
|
72
|
+
member_threshold, member_count = groups[i][0], groups[i][1]
|
|
73
|
+
shares = split_secret(member_threshold, member_count, group_share)
|
|
74
|
+
shares.map do |member_index, member_share|
|
|
75
|
+
share = Tapyrus::SLIP39::Share.new
|
|
76
|
+
share.id = id
|
|
77
|
+
share.iteration_exp = exp
|
|
78
|
+
share.group_index = group_index
|
|
79
|
+
share.group_threshold = group_threshold
|
|
80
|
+
share.group_count = groups.length
|
|
81
|
+
share.member_index = member_index
|
|
82
|
+
share.member_threshold = member_threshold
|
|
83
|
+
share.value = member_share
|
|
84
|
+
share.checksum = share.calculate_checksum
|
|
85
|
+
share
|
|
86
|
+
end
|
|
71
87
|
end
|
|
72
|
-
end
|
|
73
88
|
shares
|
|
74
89
|
end
|
|
75
90
|
|
|
@@ -92,9 +107,15 @@ module Tapyrus
|
|
|
92
107
|
|
|
93
108
|
shares.each do |share|
|
|
94
109
|
raise ArgumentError, 'Invalid set of shares. All shares must have the same id.' unless id == share.id
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
110
|
+
unless group_threshold == share.group_threshold
|
|
111
|
+
raise ArgumentError, 'Invalid set of shares. All shares must have the same group threshold.'
|
|
112
|
+
end
|
|
113
|
+
unless group_count == share.group_count
|
|
114
|
+
raise ArgumentError, 'Invalid set of shares. All shares must have the same group count.'
|
|
115
|
+
end
|
|
116
|
+
unless exp == share.iteration_exp
|
|
117
|
+
raise ArgumentError, 'Invalid set of shares. All Shares must have the same iteration exponent.'
|
|
118
|
+
end
|
|
98
119
|
groups[share.group_index] ||= []
|
|
99
120
|
groups[share.group_index] << share
|
|
100
121
|
end
|
|
@@ -102,20 +123,29 @@ module Tapyrus
|
|
|
102
123
|
group_shares = {}
|
|
103
124
|
groups.each do |group_index, shares|
|
|
104
125
|
member_threshold = shares.first.member_threshold
|
|
105
|
-
|
|
126
|
+
if shares.length < member_threshold
|
|
127
|
+
raise ArgumentError,
|
|
128
|
+
"Wrong number of mnemonics. Threshold is #{member_threshold}, but share count is #{shares.length}"
|
|
129
|
+
end
|
|
106
130
|
if shares.length == 1 && member_threshold == 1
|
|
107
131
|
group_shares[group_index] = shares.first.value
|
|
108
132
|
else
|
|
109
133
|
value_length = shares.first.value.length
|
|
110
134
|
x_coordinates = []
|
|
111
135
|
shares.each do |share|
|
|
112
|
-
|
|
113
|
-
|
|
136
|
+
unless member_threshold == share.member_threshold
|
|
137
|
+
raise ArgumentError, 'Invalid set of shares. All shares in a group must have the same member threshold.'
|
|
138
|
+
end
|
|
139
|
+
unless value_length == share.value.length
|
|
140
|
+
raise ArgumentError, 'Invalid set of shares. All share values must have the same length.'
|
|
141
|
+
end
|
|
114
142
|
x_coordinates << share.member_index
|
|
115
143
|
end
|
|
116
144
|
x_coordinates.uniq!
|
|
117
|
-
|
|
118
|
-
|
|
145
|
+
unless x_coordinates.size == shares.size
|
|
146
|
+
raise ArgumentError, 'Invalid set of shares. Share indices must be unique.'
|
|
147
|
+
end
|
|
148
|
+
interpolate_shares = shares.map { |s| [s.member_index, s.value] }
|
|
119
149
|
|
|
120
150
|
secret = interpolate(interpolate_shares, SECRET_INDEX)
|
|
121
151
|
digest_value = interpolate(interpolate_shares, DIGEST_INDEX).htb
|
|
@@ -129,9 +159,12 @@ module Tapyrus
|
|
|
129
159
|
|
|
130
160
|
return decrypt(group_shares.values.first, passphrase, exp, id) if group_threshold == 1
|
|
131
161
|
|
|
132
|
-
|
|
162
|
+
if group_shares.length < group_threshold
|
|
163
|
+
raise ArgumentError,
|
|
164
|
+
"Wrong number of mnemonics. Group threshold is #{group_threshold}, but share count is #{group_shares.length}"
|
|
165
|
+
end
|
|
133
166
|
|
|
134
|
-
interpolate_shares = group_shares.map{|k, v|[k, v]}
|
|
167
|
+
interpolate_shares = group_shares.map { |k, v| [k, v] }
|
|
135
168
|
secret = interpolate(interpolate_shares, SECRET_INDEX)
|
|
136
169
|
digest_value = interpolate(interpolate_shares, DIGEST_INDEX).htb
|
|
137
170
|
digest, random_value = digest_value[0...DIGEST_LENGTH_BYTES].bth, digest_value[DIGEST_LENGTH_BYTES..-1].bth
|
|
@@ -148,17 +181,24 @@ module Tapyrus
|
|
|
148
181
|
# @param [Integer] x the x coordinate of the result.
|
|
149
182
|
# @return [String] f(x) value with hex format.
|
|
150
183
|
def self.interpolate(shares, x)
|
|
151
|
-
s = shares.find{|s|s[0] == x}
|
|
184
|
+
s = shares.find { |s| s[0] == x }
|
|
152
185
|
return s[1] if s
|
|
153
186
|
|
|
154
|
-
log_prod = shares.sum{|s|LOG_TABLE[s[0] ^ x]}
|
|
187
|
+
log_prod = shares.sum { |s| LOG_TABLE[s[0] ^ x] }
|
|
155
188
|
|
|
156
189
|
result = ('00' * shares.first[1].length).htb
|
|
157
190
|
shares.each do |share|
|
|
158
|
-
log_basis_eval = (log_prod - LOG_TABLE[share[0] ^ x] - shares.sum{|s|LOG_TABLE[share[0] ^ s[0]]}) % 255
|
|
159
|
-
result =
|
|
160
|
-
|
|
161
|
-
|
|
191
|
+
log_basis_eval = (log_prod - LOG_TABLE[share[0] ^ x] - shares.sum { |s| LOG_TABLE[share[0] ^ s[0]] }) % 255
|
|
192
|
+
result =
|
|
193
|
+
share[1]
|
|
194
|
+
.htb
|
|
195
|
+
.bytes
|
|
196
|
+
.each
|
|
197
|
+
.map
|
|
198
|
+
.with_index do |v, i|
|
|
199
|
+
(result[i].bti ^ (v == 0 ? 0 : (EXP_TABLE[(LOG_TABLE[v] + log_basis_eval) % 255]))).itb
|
|
200
|
+
end
|
|
201
|
+
.join
|
|
162
202
|
end
|
|
163
203
|
result.bth
|
|
164
204
|
end
|
|
@@ -220,26 +260,29 @@ module Tapyrus
|
|
|
220
260
|
# @return [Array[Integer, String]] the array of split secret.
|
|
221
261
|
def self.split_secret(threshold, count, secret)
|
|
222
262
|
raise ArgumentError, "The requested threshold (#{threshold}) must be a positive integer." if threshold < 1
|
|
223
|
-
|
|
224
|
-
|
|
263
|
+
if threshold > count
|
|
264
|
+
raise ArgumentError, "The requested threshold (#{threshold}) must not exceed the number of shares (#{count})."
|
|
265
|
+
end
|
|
266
|
+
if count > MAX_SHARE_COUNT
|
|
267
|
+
raise ArgumentError, "The requested number of shares (#{count}) must not exceed #{MAX_SHARE_COUNT}."
|
|
268
|
+
end
|
|
225
269
|
|
|
226
|
-
return count.times.map{|i|[i, secret]} if threshold == 1 # if the threshold is 1, digest of the share is not used.
|
|
270
|
+
return count.times.map { |i| [i, secret] } if threshold == 1 # if the threshold is 1, digest of the share is not used.
|
|
227
271
|
|
|
228
272
|
random_share_count = threshold - 2
|
|
229
273
|
|
|
230
|
-
shares = random_share_count.times.map{|i|[i, SecureRandom.hex(secret.htb.bytesize)]}
|
|
274
|
+
shares = random_share_count.times.map { |i| [i, SecureRandom.hex(secret.htb.bytesize)] }
|
|
231
275
|
random_part = SecureRandom.hex(secret.htb.bytesize - DIGEST_LENGTH_BYTES)
|
|
232
276
|
digest = create_digest(secret, random_part)
|
|
233
277
|
|
|
234
278
|
base_shares = shares + [[DIGEST_INDEX, digest + random_part], [SECRET_INDEX, secret]]
|
|
235
279
|
|
|
236
|
-
(random_share_count...count).each { |i| shares << [i, interpolate(base_shares, i)]}
|
|
280
|
+
(random_share_count...count).each { |i| shares << [i, interpolate(base_shares, i)] }
|
|
237
281
|
|
|
238
282
|
shares
|
|
239
283
|
end
|
|
240
284
|
|
|
241
285
|
private_class_method :split_secret, :get_salt, :interpolate, :encrypt, :decrypt, :create_digest
|
|
242
|
-
|
|
243
286
|
end
|
|
244
287
|
end
|
|
245
|
-
end
|
|
288
|
+
end
|