tapyrus 0.2.7 → 0.2.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +37 -0
  3. data/.prettierignore +3 -0
  4. data/.prettierrc.yaml +3 -0
  5. data/.ruby-version +1 -1
  6. data/CODE_OF_CONDUCT.md +7 -7
  7. data/README.md +14 -17
  8. data/Rakefile +3 -3
  9. data/lib/openassets/marker_output.rb +0 -4
  10. data/lib/openassets/payload.rb +4 -10
  11. data/lib/openassets.rb +0 -2
  12. data/lib/schnorr/sign_to_contract.rb +51 -0
  13. data/lib/schnorr/signature.rb +3 -6
  14. data/lib/schnorr.rb +14 -9
  15. data/lib/tapyrus/base58.rb +7 -6
  16. data/lib/tapyrus/bip175.rb +78 -0
  17. data/lib/tapyrus/block.rb +1 -2
  18. data/lib/tapyrus/block_header.rb +15 -9
  19. data/lib/tapyrus/bloom_filter.rb +5 -3
  20. data/lib/tapyrus/chain_params.rb +1 -4
  21. data/lib/tapyrus/chainparams/dev.yml +3 -2
  22. data/lib/tapyrus/chainparams/prod.yml +3 -2
  23. data/lib/tapyrus/constants.rb +29 -23
  24. data/lib/tapyrus/errors.rb +1 -3
  25. data/lib/tapyrus/ext/ecdsa.rb +4 -4
  26. data/lib/tapyrus/ext/json_parser.rb +1 -4
  27. data/lib/tapyrus/ext.rb +1 -1
  28. data/lib/tapyrus/ext_key.rb +44 -32
  29. data/lib/tapyrus/key.rb +31 -35
  30. data/lib/tapyrus/key_path.rb +15 -12
  31. data/lib/tapyrus/logger.rb +20 -16
  32. data/lib/tapyrus/merkle_tree.rb +22 -20
  33. data/lib/tapyrus/message/addr.rb +1 -7
  34. data/lib/tapyrus/message/base.rb +0 -3
  35. data/lib/tapyrus/message/block.rb +2 -9
  36. data/lib/tapyrus/message/block_transaction_request.rb +3 -6
  37. data/lib/tapyrus/message/block_transactions.rb +2 -6
  38. data/lib/tapyrus/message/block_txn.rb +0 -4
  39. data/lib/tapyrus/message/cmpct_block.rb +1 -7
  40. data/lib/tapyrus/message/error.rb +1 -4
  41. data/lib/tapyrus/message/fee_filter.rb +1 -4
  42. data/lib/tapyrus/message/filter_add.rb +0 -4
  43. data/lib/tapyrus/message/filter_clear.rb +0 -4
  44. data/lib/tapyrus/message/filter_load.rb +2 -5
  45. data/lib/tapyrus/message/get_addr.rb +0 -4
  46. data/lib/tapyrus/message/get_block_txn.rb +0 -4
  47. data/lib/tapyrus/message/get_blocks.rb +0 -3
  48. data/lib/tapyrus/message/get_data.rb +1 -4
  49. data/lib/tapyrus/message/get_headers.rb +1 -3
  50. data/lib/tapyrus/message/header_and_short_ids.rb +3 -9
  51. data/lib/tapyrus/message/headers.rb +0 -4
  52. data/lib/tapyrus/message/headers_parser.rb +3 -8
  53. data/lib/tapyrus/message/inv.rb +1 -4
  54. data/lib/tapyrus/message/inventories_parser.rb +2 -7
  55. data/lib/tapyrus/message/inventory.rb +12 -5
  56. data/lib/tapyrus/message/mem_pool.rb +0 -4
  57. data/lib/tapyrus/message/merkle_block.rb +4 -9
  58. data/lib/tapyrus/message/network_addr.rb +7 -6
  59. data/lib/tapyrus/message/not_found.rb +0 -3
  60. data/lib/tapyrus/message/ping.rb +0 -3
  61. data/lib/tapyrus/message/pong.rb +0 -3
  62. data/lib/tapyrus/message/prefilled_tx.rb +0 -4
  63. data/lib/tapyrus/message/reject.rb +0 -3
  64. data/lib/tapyrus/message/send_cmpct.rb +1 -3
  65. data/lib/tapyrus/message/send_headers.rb +0 -3
  66. data/lib/tapyrus/message/tx.rb +0 -4
  67. data/lib/tapyrus/message/ver_ack.rb +1 -5
  68. data/lib/tapyrus/message/version.rb +2 -5
  69. data/lib/tapyrus/message.rb +14 -16
  70. data/lib/tapyrus/mnemonic.rb +17 -15
  71. data/lib/tapyrus/network/connection.rb +0 -3
  72. data/lib/tapyrus/network/message_handler.rb +61 -60
  73. data/lib/tapyrus/network/peer.rb +13 -12
  74. data/lib/tapyrus/network/peer_discovery.rb +10 -9
  75. data/lib/tapyrus/network/pool.rb +12 -12
  76. data/lib/tapyrus/network.rb +0 -2
  77. data/lib/tapyrus/node/cli.rb +12 -14
  78. data/lib/tapyrus/node/configuration.rb +1 -3
  79. data/lib/tapyrus/node/spv.rb +2 -3
  80. data/lib/tapyrus/node.rb +1 -1
  81. data/lib/tapyrus/opcodes.rb +9 -7
  82. data/lib/tapyrus/out_point.rb +5 -5
  83. data/lib/tapyrus/rpc/http_server.rb +21 -22
  84. data/lib/tapyrus/rpc/request_handler.rb +16 -21
  85. data/lib/tapyrus/rpc/tapyrus_core_client.rb +67 -25
  86. data/lib/tapyrus/rpc.rb +1 -0
  87. data/lib/tapyrus/script/color.rb +10 -0
  88. data/lib/tapyrus/script/multisig.rb +13 -12
  89. data/lib/tapyrus/script/script.rb +93 -88
  90. data/lib/tapyrus/script/script_error.rb +1 -4
  91. data/lib/tapyrus/script/script_interpreter.rb +439 -399
  92. data/lib/tapyrus/script/tx_checker.rb +20 -10
  93. data/lib/tapyrus/secp256k1/native.rb +14 -15
  94. data/lib/tapyrus/secp256k1/rfc6979.rb +7 -4
  95. data/lib/tapyrus/secp256k1/ruby.rb +10 -12
  96. data/lib/tapyrus/secp256k1.rb +0 -4
  97. data/lib/tapyrus/slip39/share.rb +41 -29
  98. data/lib/tapyrus/slip39/sss.rb +92 -49
  99. data/lib/tapyrus/slip39.rb +20 -5
  100. data/lib/tapyrus/store/chain_entry.rb +0 -4
  101. data/lib/tapyrus/store/db/level_db.rb +5 -9
  102. data/lib/tapyrus/store/db.rb +0 -2
  103. data/lib/tapyrus/store/spv_chain.rb +11 -17
  104. data/lib/tapyrus/store.rb +1 -3
  105. data/lib/tapyrus/tx.rb +45 -37
  106. data/lib/tapyrus/tx_builder.rb +160 -0
  107. data/lib/tapyrus/tx_in.rb +1 -6
  108. data/lib/tapyrus/tx_out.rb +2 -7
  109. data/lib/tapyrus/util.rb +7 -9
  110. data/lib/tapyrus/validation.rb +12 -11
  111. data/lib/tapyrus/version.rb +1 -1
  112. data/lib/tapyrus/wallet/account.rb +22 -18
  113. data/lib/tapyrus/wallet/base.rb +12 -9
  114. data/lib/tapyrus/wallet/db.rb +6 -9
  115. data/lib/tapyrus/wallet/master_key.rb +2 -4
  116. data/lib/tapyrus.rb +7 -22
  117. data/tapyrusrb.gemspec +13 -14
  118. metadata +26 -7
  119. 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 = tx.sighash_for_input(input_index, script_code, hash_type: hash_type,
26
- amount: amount, sig_version: sig_version)
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 = pubkey.start_with?('02') || pubkey.start_with?('03') ? Key::TYPES[:compressed] : Key::TYPES[:uncompressed]
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 ((tx.lock_time < LOCKTIME_THRESHOLD && locktime < LOCKTIME_THRESHOLD) ||
53
- (tx.lock_time >= LOCKTIME_THRESHOLD && locktime >= LOCKTIME_THRESHOLD))
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 ((tx_sequence_masked < TxIn::SEQUENCE_LOCKTIME_TYPE_FLAG && sequence_masked < TxIn::SEQUENCE_LOCKTIME_TYPE_FLAG) ||
88
- (tx_sequence_masked >= TxIn::SEQUENCE_LOCKTIME_TYPE_FLAG && sequence_masked >= TxIn::SEQUENCE_LOCKTIME_TYPE_FLAG))
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 = priv_key.read_string(32).bth
84
- [private_key , generate_pubkey_in_context(context, private_key, compressed: compressed) ]
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 do |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 = if compressed
156
- pubkey_len.put_uint64(0, 33)
157
- secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_COMPRESSED)
158
- else
159
- pubkey_len.put_uint64(0, 65)
160
- secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_UNCOMPRESSED)
161
- end
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
- raise 'Failed to generate schnorr signature.' unless secp256k1_schnorr_sign(context, signature, msg32, secret, nil, nil) == 1
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
- 10000.times do
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 :valid_sig? :verify_sig
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 = ECDSA::Format::PointOctetString.decode(pubkey.htb, ECDSA::Group::Secp256k1, allow_hybrid: allow_hybrid)
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
- when "\x06", "\x07"
84
- p[0] = "\x04"
85
- p
86
- else
87
- pubkey.htb
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) # convert low-s
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
@@ -1,13 +1,9 @@
1
1
  module Tapyrus
2
-
3
2
  module Secp256k1
4
-
5
3
  GROUP = ECDSA::Group::Secp256k1
6
4
 
7
5
  autoload :Ruby, 'tapyrus/secp256k1/ruby'
8
6
  autoload :Native, 'tapyrus/secp256k1/native'
9
7
  autoload :RFC6979, 'tapyrus/secp256k1/rfc6979'
10
-
11
8
  end
12
-
13
9
  end
@@ -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 :id # 15 bits, Integer
8
- attr_accessor :iteration_exp # 5 bits, Integer
9
- attr_accessor :group_index # 4 bits, Integer
10
- attr_accessor :group_threshold # 4 bits, Integer
11
- attr_accessor :group_count # 4 bits, Integer
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 # 8n bits, hex string.
15
- attr_accessor :checksum # 30 bits, Integer
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 = words.map do |word|
23
- index = Tapyrus::SLIP39::WORDS.index(word.downcase)
24
- raise IndexError, 'word not found in words list.' unless index
25
- index
26
- end
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
- raise ArgumentError, "Invalid mnemonic. Group threshold(#{s.group_threshold}) cannot be greater than group count(#{s.group_count})." if s.group_threshold > s.group_count
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, "Invalid mnemonic. padding must only zero." unless padding_value.to_i(2) == 0
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 = [0xe0e040, 0x1c1c080, 0x3838100, 0x7070200, 0xe0e0009, 0x1c0c2412, 0x38086c24, 0x3090fc48, 0x21b1f890, 0x3f3f120]
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 do |i|
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
- raise StandardError, "Group threshold(#{group_threshold}) cannot be greater than group count(#{group_count})." if group_threshold > group_count
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
@@ -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
- raise ArgumentError, "The length of the master secret (#{secret.htb.bytesize} bytes) must be at least #{MIN_STRENGTH_BITS / 8} bytes." if (secret.htb.bytesize * 8) < MIN_STRENGTH_BITS
41
- raise ArgumentError, 'The length of the master secret in bytes must be an even number.' unless secret.bytesize.even?
42
- raise ArgumentError, 'The passphrase must contain only printable ASCII characters (code points 32-126).' unless passphrase.ascii_only?
43
- raise ArgumentError, "The requested group threshold (#{group_threshold}) must not exceed the number of groups (#{groups.length})." if group_threshold > groups.length
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
- raise ArgumentError, "The requested member threshold (#{threshold}) must not exceed the number of share (#{count})." if threshold > count
47
- raise ArgumentError, "Creating multiple member shares with member threshold 1 is not allowed. Use 1-of-1 member sharing instead." if threshold == 1 && count > 1
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(32767) # 32767 is max number for 15 bits.
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 = group_shares.map.with_index do |s, i|
56
- group_index, group_share = s[0], s[1]
57
- member_threshold, member_count = groups[i][0], groups[i][1]
58
- shares = split_secret(member_threshold, member_count, group_share)
59
- shares.map do |member_index, member_share|
60
- share = Tapyrus::SLIP39::Share.new
61
- share.id = id
62
- share.iteration_exp = exp
63
- share.group_index = group_index
64
- share.group_threshold = group_threshold
65
- share.group_count = groups.length
66
- share.member_index = member_index
67
- share.member_threshold = member_threshold
68
- share.value = member_share
69
- share.checksum = share.calculate_checksum
70
- share
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
- raise ArgumentError, 'Invalid set of shares. All shares must have the same group threshold.' unless group_threshold == share.group_threshold
96
- raise ArgumentError, 'Invalid set of shares. All shares must have the same group count.' unless group_count == share.group_count
97
- raise ArgumentError, 'Invalid set of shares. All Shares must have the same iteration exponent.' unless exp == share.iteration_exp
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
- raise ArgumentError, "Wrong number of mnemonics. Threshold is #{member_threshold}, but share count is #{shares.length}" if shares.length < member_threshold
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
- raise ArgumentError, 'Invalid set of shares. All shares in a group must have the same member threshold.' unless member_threshold == share.member_threshold
113
- raise ArgumentError, 'Invalid set of shares. All share values must have the same length.' unless value_length == share.value.length
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
- raise ArgumentError, 'Invalid set of shares. Share indices must be unique.' unless x_coordinates.size == shares.size
118
- interpolate_shares = shares.map{|s|[s.member_index, s.value]}
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
- raise ArgumentError, "Wrong number of mnemonics. Group threshold is #{group_threshold}, but share count is #{group_shares.length}" if group_shares.length < group_threshold
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 = share[1].htb.bytes.each.map.with_index do |v, i|
160
- (result[i].bti ^ (v == 0 ? 0 : (EXP_TABLE[(LOG_TABLE[v] + log_basis_eval) % 255]))).itb
161
- end.join
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
- raise ArgumentError, "The requested threshold (#{threshold}) must not exceed the number of shares (#{count})." if threshold > count
224
- raise ArgumentError, "The requested number of shares (#{count}) must not exceed #{MAX_SHARE_COUNT}." if count > MAX_SHARE_COUNT
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