tapyrus 0.2.7 → 0.2.12
Sign up to get free protection for your applications and to get access to all the features.
- 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
|