tapyrus 0.2.7 → 0.2.8
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/CODE_OF_CONDUCT.md +7 -7
- data/README.md +14 -17
- data/Rakefile +3 -3
- data/lib/openassets.rb +0 -2
- data/lib/openassets/marker_output.rb +0 -4
- data/lib/openassets/payload.rb +4 -10
- data/lib/schnorr.rb +2 -3
- data/lib/schnorr/signature.rb +3 -6
- data/lib/tapyrus.rb +6 -22
- data/lib/tapyrus/base58.rb +7 -6
- 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.rb +1 -1
- data/lib/tapyrus/ext/ecdsa.rb +4 -4
- data/lib/tapyrus/ext/json_parser.rb +1 -4
- data/lib/tapyrus/ext_key.rb +38 -34
- 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 +19 -20
- data/lib/tapyrus/message.rb +14 -16
- 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/mnemonic.rb +17 -15
- data/lib/tapyrus/network.rb +0 -2
- 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 +3 -5
- data/lib/tapyrus/network/pool.rb +12 -12
- data/lib/tapyrus/node.rb +1 -1
- 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/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 +42 -44
- data/lib/tapyrus/rpc/tapyrus_core_client.rb +53 -25
- data/lib/tapyrus/script/color.rb +10 -0
- data/lib/tapyrus/script/multisig.rb +13 -12
- data/lib/tapyrus/script/script.rb +72 -71
- 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.rb +0 -4
- 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/slip39.rb +20 -5
- data/lib/tapyrus/slip39/share.rb +41 -29
- data/lib/tapyrus/slip39/sss.rb +101 -57
- data/lib/tapyrus/store.rb +1 -3
- data/lib/tapyrus/store/chain_entry.rb +0 -4
- data/lib/tapyrus/store/db.rb +0 -2
- data/lib/tapyrus/store/db/level_db.rb +5 -9
- data/lib/tapyrus/store/spv_chain.rb +11 -17
- data/lib/tapyrus/tx.rb +45 -37
- data/lib/tapyrus/tx_builder.rb +158 -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/tapyrusrb.gemspec +13 -14
- metadata +21 -4
- 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
|
data/lib/tapyrus/secp256k1.rb
CHANGED
@@ -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/slip39.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
module Tapyrus
|
2
2
|
module SLIP39
|
3
|
-
|
4
3
|
WORDS = File.readlines("#{__dir__}/slip39/wordlist/english.txt").map(&:strip)
|
5
4
|
|
6
5
|
module_function
|
@@ -15,37 +14,53 @@ module Tapyrus
|
|
15
14
|
|
16
15
|
# The length of the radix in bits.
|
17
16
|
RADIX_BITS = 10
|
17
|
+
|
18
18
|
# The number of words in the wordlist.
|
19
|
-
RADIX = 2
|
19
|
+
RADIX = 2**RADIX_BITS
|
20
|
+
|
20
21
|
# The length of the random identifier in bits.
|
21
22
|
ID_LENGTH_BITS = 15
|
23
|
+
|
22
24
|
# The length of the iteration exponent in bits.
|
23
25
|
ITERATION_EXP_LENGTH_BITS = 5
|
26
|
+
|
24
27
|
# The length of the random identifier and iteration exponent in words.
|
25
28
|
ID_EXP_LENGTH_WORDS = bits_to_words(ID_LENGTH_BITS + ITERATION_EXP_LENGTH_BITS)
|
29
|
+
|
26
30
|
# The maximum number of shares that can be created.
|
27
31
|
MAX_SHARE_COUNT = 16
|
32
|
+
|
28
33
|
# The length of the RS1024 checksum in words.
|
29
34
|
CHECKSUM_LENGTH_WORDS = 3
|
35
|
+
|
30
36
|
# The length of the digest of the shared secret in bytes.
|
31
37
|
DIGEST_LENGTH_BYTES = 4
|
38
|
+
|
32
39
|
# The customization string used in the RS1024 checksum and in the PBKDF2 salt.
|
33
40
|
CUSTOMIZATION_STRING = 'shamir'.bytes
|
41
|
+
|
34
42
|
# The length of the mnemonic in words without the share value.
|
35
43
|
METADATA_LENGTH_WORDS = ID_EXP_LENGTH_WORDS + 2 + CHECKSUM_LENGTH_WORDS
|
44
|
+
|
36
45
|
# The minimum allowed entropy of the master secret.
|
37
46
|
MIN_STRENGTH_BITS = 128
|
47
|
+
|
38
48
|
# The minimum allowed length of the mnemonic in words.
|
39
49
|
MIN_MNEMONIC_LENGTH_WORDS = METADATA_LENGTH_WORDS + bits_to_words(MIN_STRENGTH_BITS)
|
50
|
+
|
40
51
|
# The minimum number of iterations to use in PBKDF2.
|
41
|
-
BASE_ITERATION_COUNT =
|
52
|
+
BASE_ITERATION_COUNT = 10_000
|
53
|
+
|
42
54
|
# The number of rounds to use in the Feistel cipher.
|
43
55
|
ROUND_COUNT = 4
|
56
|
+
|
44
57
|
# The index of the share containing the shared secret.
|
45
58
|
SECRET_INDEX = 255
|
59
|
+
|
46
60
|
# The index of the share containing the digest of the shared secret.
|
47
61
|
DIGEST_INDEX = 254
|
48
62
|
|
63
|
+
# prettier-ignore
|
49
64
|
EXP_TABLE = [
|
50
65
|
1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19,
|
51
66
|
53, 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34,
|
@@ -66,6 +81,7 @@ module Tapyrus
|
|
66
81
|
57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246
|
67
82
|
]
|
68
83
|
|
84
|
+
# prettier-ignore
|
69
85
|
LOG_TABLE = [
|
70
86
|
0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3,
|
71
87
|
100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28,
|
@@ -88,6 +104,5 @@ module Tapyrus
|
|
88
104
|
|
89
105
|
autoload :SSS, 'tapyrus/slip39/sss'
|
90
106
|
autoload :Share, 'tapyrus/slip39/share'
|
91
|
-
|
92
107
|
end
|
93
|
-
end
|
108
|
+
end
|
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
|