tapyrus 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0742065204db9e10454dbdc9afc4401be70659e9998376d944b4e0f61a75cf27
4
- data.tar.gz: 578bd756abb67374d62951526638c9328c3f9e70f9925cd7e3f16f079093843b
3
+ metadata.gz: a1abedf80b636e2bfd80db4617ca9f92e703148f07e59c08b14860c13ac57689
4
+ data.tar.gz: 21b478051eb01354e410475e15761f24773fe164fcd9abf3307acb231b5c8ae3
5
5
  SHA512:
6
- metadata.gz: 584cf7b8ce40aeb94ecce56c393ab52805b5af76bf91439f5f1b39cf28ce15a723bc2b37862773e5c1cd994cb6d72f270b4f77c828708384e9fe70990efbd19c
7
- data.tar.gz: cdc17bfad7962a3900ad567c07d0d53550f0db4eacf8b10c80e76017206aeed702c336fee9a7283502ae3ebdcf68de6da45e90a22ea0044e22e80839fa6cd43d
6
+ metadata.gz: 61c8cc2439074666abffc6ff85ffaa40811e8e0992edb19846876dabf9a300f4822af522a6f621271f310a9b1ea3f9429f30e86452d29b0f2216052568a72a6a
7
+ data.tar.gz: f3845015c030c7ccf915700eb45ac2f743c3f0c19dbe077c72c4afe19327252d1f3a64ffb29c1f5790997c8f6f2317930ab5ae99a894687a86f6381154c5429d
@@ -76,7 +76,7 @@ module Schnorr
76
76
  # @return (Integer) digest e.
77
77
  def create_challenge(x, p, message)
78
78
  r_x = ECDSA::Format::IntegerOctetString.encode(x, GROUP.byte_length)
79
- p_str= ECDSA::Format::PointOctetString.encode(p, compression:true)
79
+ p_str= p.to_hex.htb
80
80
  (ECDSA.normalize_digest(Digest::SHA256.digest(r_x + p_str + message), GROUP.bit_length)) % GROUP.order
81
81
  end
82
82
 
@@ -48,8 +48,10 @@ module Tapyrus
48
48
  autoload :KeyPath, 'tapyrus/key_path'
49
49
  autoload :SLIP39, 'tapyrus/slip39'
50
50
  autoload :Color, 'tapyrus/script/color'
51
+ autoload :Errors, 'tapyrus/errors'
51
52
 
52
53
  require_relative 'tapyrus/constants'
54
+ require_relative 'tapyrus/ext/ecdsa'
53
55
 
54
56
  extend Util
55
57
 
@@ -207,11 +209,4 @@ module Tapyrus
207
209
  end
208
210
  end
209
211
 
210
- class ::ECDSA::Signature
211
- # convert signature to der string.
212
- def to_der
213
- ECDSA::Format::SignatureDerString.encode(self)
214
- end
215
- end
216
-
217
212
  end
@@ -1,5 +1,6 @@
1
1
  module Tapyrus
2
2
  class Block
3
+ include Tapyrus::Util
3
4
 
4
5
  attr_accessor :header
5
6
  attr_accessor :transactions
@@ -32,14 +33,9 @@ module Tapyrus
32
33
  end
33
34
 
34
35
  # return this block height. block height is included in coinbase.
35
- # if block version under 1, height does not include in coinbase, so return nil.
36
+ # @return [Integer] block height.
36
37
  def height
37
- return nil if header.features < 2
38
- coinbase_tx = transactions[0]
39
- return nil unless coinbase_tx.coinbase_tx?
40
- buf = StringIO.new(coinbase_tx.inputs[0].script_sig.to_payload)
41
- len = Tapyrus.unpack_var_int_from_io(buf)
42
- buf.read(len).reverse.bth.to_i(16)
38
+ transactions.first.in.first.out_point.index
43
39
  end
44
40
 
45
41
  end
@@ -3,61 +3,69 @@ module Tapyrus
3
3
  # Block Header
4
4
  class BlockHeader
5
5
  include Tapyrus::HexConverter
6
+ extend Tapyrus::Util
7
+ include Tapyrus::Util
8
+
9
+ X_FILED_TYPES = {none: 0, aggregate_pubkey: 1}
6
10
 
7
11
  attr_accessor :features
8
12
  attr_accessor :prev_hash
9
13
  attr_accessor :merkle_root
10
- attr_accessor :time # unix timestamp
11
- attr_accessor :bits
12
- attr_accessor :nonce
14
+ attr_accessor :im_merkle_root # merkel root of immulable merkle tree which consist of immutable txid.
15
+ attr_accessor :time # unix timestamp
16
+ attr_accessor :x_field_type
17
+ attr_accessor :x_field
18
+ attr_accessor :proof
13
19
 
14
- def initialize(features, prev_hash, merkle_root, time, bits, nonce)
20
+ def initialize(features, prev_hash, merkle_root, im_merkle_root, time, x_field_type, x_field, proof = nil)
15
21
  @features = features
16
22
  @prev_hash = prev_hash
17
23
  @merkle_root = merkle_root
24
+ @im_merkle_root = im_merkle_root
18
25
  @time = time
19
- @bits = bits
20
- @nonce = nonce
26
+ @x_field_type = x_field_type
27
+ @x_field = x_field
28
+ @proof = proof
21
29
  end
22
30
 
23
31
  def self.parse_from_payload(payload)
24
- features, prev_hash, merkle_root, time, bits, nonce = payload.unpack('Va32a32VVV')
25
- new(features, prev_hash.bth, merkle_root.bth, time, bits, nonce)
26
- end
27
-
28
- def to_payload
29
- [features, prev_hash.htb, merkle_root.htb, time, bits, nonce].pack('Va32a32VVV')
32
+ buf = payload.is_a?(String) ? StringIO.new(payload) : payload
33
+ features, prev_hash, merkle_root, im_merkle_root, time, x_filed_type = buf.read(105).unpack('Va32a32a32Vc')
34
+ x_field = buf.read(unpack_var_int_from_io(buf)) unless x_filed_type == X_FILED_TYPES[:none]
35
+ proof = buf.read(unpack_var_int_from_io(buf))
36
+ new(features, prev_hash.bth, merkle_root.bth, im_merkle_root.bth, time, x_filed_type, x_field ? x_field.bth : x_field, proof.bth)
30
37
  end
31
38
 
32
- # compute difficulty target from bits.
33
- def difficulty_target
34
- exponent = ((bits >> 24) & 0xff)
35
- mantissa = bits & 0x7fffff
36
- mantissa *= -1 if (bits & 0x800000) > 0
37
- (mantissa * 2 ** (8 * (exponent - 3)))
39
+ def to_payload(skip_proof = false)
40
+ payload = [features, prev_hash.htb, merkle_root.htb, im_merkle_root.htb, time, x_field_type].pack('Va32a32a32Vc')
41
+ payload << pack_var_string(x_field.htb) unless x_field_type == X_FILED_TYPES[:none]
42
+ payload << pack_var_string(proof.htb) if proof && !skip_proof
43
+ payload
38
44
  end
39
45
 
40
- def hash
41
- calc_hash.to_i(16)
46
+ # Calculate hash using sign which does not contains proof.
47
+ # @return [String] hash of block without proof.
48
+ def hash_for_sign
49
+ Tapyrus.double_sha256(to_payload(true)).bth
42
50
  end
43
51
 
52
+ # Calculate block hash
53
+ # @return [String] hash of block with hex format.
44
54
  def block_hash
45
- calc_hash
55
+ Tapyrus.double_sha256(to_payload).bth
46
56
  end
47
57
 
48
58
  # block hash(big endian)
59
+ # @return [String] block id which is reversed version of block hash.
49
60
  def block_id
50
61
  block_hash.rhex
51
62
  end
52
63
 
53
64
  # evaluate block header
54
- def valid?
55
- valid_pow? && valid_timestamp?
56
- end
57
-
58
- # evaluate valid proof of work.
59
- def valid_pow?
60
- block_id.hex < difficulty_target
65
+ # @param [String] agg_pubkey aggregated public key for signers with hex format.
66
+ # @return [Boolean] result.
67
+ def valid?(agg_pubkey)
68
+ valid_timestamp? && valid_proof?(agg_pubkey) && valid_x_field?
61
69
  end
62
70
 
63
71
  # evaluate valid timestamp.
@@ -66,22 +74,42 @@ module Tapyrus
66
74
  time <= Time.now.to_i + Tapyrus::MAX_FUTURE_BLOCK_TIME
67
75
  end
68
76
 
69
- # compute chain work of this block.
70
- # @return [Integer] a chain work.
71
- def work
72
- target = difficulty_target
73
- return 0 if target < 1
74
- 115792089237316195423570985008687907853269984665640564039457584007913129639936.div(target + 1) # 115792089237316195423570985008687907853269984665640564039457584007913129639936 is 2**256
77
+ # Check whether proof is valid.
78
+ # @param [String] agg_pubkey aggregated public key for signers with hex format.
79
+ # @return [Boolean] Return true if proof is valid, otherwise return false.
80
+ def valid_proof?(agg_pubkey)
81
+ pubkey = Tapyrus::Key.new(pubkey: agg_pubkey)
82
+ msg = hash_for_sign.htb
83
+ pubkey.verify(proof.htb, msg, algo: :schnorr)
84
+ end
85
+
86
+ # Check whether x_field is valid.
87
+ # @return [Boolean] if valid return true, otherwise false
88
+ def valid_x_field?
89
+ case x_field_type
90
+ when X_FILED_TYPES[:none] then
91
+ x_field.nil?
92
+ when X_FILED_TYPES[:aggregate_pubkey] then
93
+ Tapyrus::Key.new(pubkey: x_field).fully_valid_pubkey?
94
+ else
95
+ false
96
+ end
97
+ end
98
+
99
+ # Check this header contains upgrade aggregated publiec key.
100
+ # @return [Boolean] if contains return true, otherwise false.
101
+ def upgrade_agg_pubkey?
102
+ x_field_type == X_FILED_TYPES[:aggregate_pubkey]
75
103
  end
76
104
 
77
105
  def ==(other)
78
106
  other && other.to_payload == to_payload
79
107
  end
80
108
 
81
- private
82
-
83
- def calc_hash
84
- Tapyrus.double_sha256(to_payload).bth
109
+ # get bytesize.
110
+ # @return [Integer] bytesize.
111
+ def size
112
+ to_payload.bytesize
85
113
  end
86
114
 
87
115
  end
@@ -33,12 +33,10 @@ module Tapyrus
33
33
  attr_reader :bip34_height
34
34
  attr_reader :proof_of_work_limit
35
35
  attr_reader :dns_seeds
36
- attr_reader :genesis
37
36
  attr_reader :bip44_coin_type
38
37
 
39
38
  attr_accessor :dust_relay_fee
40
39
 
41
-
42
40
  # production genesis
43
41
  def self.prod
44
42
  init('prod')
@@ -57,14 +55,6 @@ module Tapyrus
57
55
  network == 'dev'
58
56
  end
59
57
 
60
- def genesis_block
61
- header = Tapyrus::BlockHeader.new(
62
- genesis['version'], genesis['prev_hash'].rhex, genesis['merkle_root'].rhex,
63
- genesis['time'], genesis['bits'], genesis['nonce'])
64
- Tapyrus::Block.new(header)
65
- end
66
-
67
-
68
58
  def self.init(name)
69
59
  i = YAML.load(File.open("#{__dir__}/chainparams/#{name}.yml"))
70
60
  i.dust_relay_fee ||= Tapyrus::DUST_RELAY_TX_FEE
@@ -25,15 +25,5 @@ retarget_time: 1209600 # 2 weeks
25
25
  target_spacing: 600 # block interval
26
26
  max_money: 21000000
27
27
  bip34_height: 227931
28
- genesis_hash: "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
29
- proof_of_work_limit: 0x1d00ffff
30
28
  dns_seeds:
31
- genesis:
32
- hash: "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
33
- merkle_root: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
34
- time: 1296688602
35
- nonce: 414098458
36
- bits: 0x1d00ffff
37
- version: 1
38
- prev_hash: "0000000000000000000000000000000000000000000000000000000000000000"
39
29
  bip44_coin_type: 1
@@ -25,14 +25,5 @@ retarget_time: 1209600 # 2 weeks
25
25
  target_spacing: 600 # block interval
26
26
  max_money: 21000000
27
27
  bip34_height: 227931
28
- proof_of_work_limit: 0x1d00ffff
29
28
  dns_seeds:
30
- genesis:
31
- hash: "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
32
- merkle_root: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
33
- time: 1231006505
34
- nonce: 2083236893
35
- bits: 0x1d00ffff
36
- version: 1
37
- prev_hash: "0000000000000000000000000000000000000000000000000000000000000000"
38
29
  bip44_coin_type: 0
@@ -0,0 +1,17 @@
1
+ module Tapyrus
2
+ module Errors
3
+ module Messages
4
+
5
+ INVALID_PUBLIC_KEY = 'Invalid public key.'
6
+ INVALID_BIP32_PRIV_PREFIX = 'Invalid BIP32 private key prefix. prefix must be 0x00.'
7
+ INVALID_BIP32_FINGERPRINT = 'Invalid parent fingerprint.'
8
+ INVALID_BIP32_ZERO_INDEX = 'Invalid index. Depth 0 must have 0 index.'
9
+ INVALID_BIP32_ZERO_DEPTH = 'Invalid depth. Master key must have 0 depth.'
10
+ INVALID_BIP32_VERSION = 'An unsupported version byte was specified.'
11
+
12
+ INVALID_PRIV_KEY = 'Private key is not in range [1..n-1].'
13
+ INVALID_CHECKSUM = 'Invalid checksum.'
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,39 @@
1
+ class ::ECDSA::Signature
2
+ # convert signature to der string.
3
+ def to_der
4
+ ECDSA::Format::SignatureDerString.encode(self)
5
+ end
6
+ end
7
+
8
+ class ::ECDSA::Point
9
+ def to_hex(compression = true)
10
+ ECDSA::Format::PointOctetString.encode(self, compression: compression).bth
11
+ end
12
+ end
13
+
14
+ module ::ECDSA::Format::PointOctetString
15
+
16
+ def self.decode(string, group, allow_hybrid: false)
17
+ string = string.dup.force_encoding('BINARY')
18
+
19
+ raise ECDSA::Format::DecodeError, 'Point octet string is empty.' if string.empty?
20
+
21
+ case string[0].ord
22
+ when 0
23
+ check_length string, 1
24
+ return group.infinity
25
+ when 2
26
+ decode_compressed string, group, 0
27
+ when 3
28
+ decode_compressed string, group, 1
29
+ when 4
30
+ decode_uncompressed string, group
31
+ when 6..7
32
+ raise DecodeError, 'Unrecognized start byte for point octet string: 0x%x' % string[0].ord unless allow_hybrid
33
+ decode_uncompressed string, group if allow_hybrid
34
+ else
35
+ raise ECDSA::Format::DecodeError, 'Unrecognized start byte for point octet string: 0x%x' % string[0].ord
36
+ end
37
+ end
38
+
39
+ end
@@ -7,6 +7,9 @@ module Tapyrus
7
7
  class ExtKey
8
8
  include Tapyrus::HexConverter
9
9
 
10
+ MAX_DEPTH = 255
11
+ MASTER_FINGERPRINT = '00000000'
12
+
10
13
  attr_accessor :ver
11
14
  attr_accessor :depth
12
15
  attr_accessor :number
@@ -20,7 +23,7 @@ module Tapyrus
20
23
  ext_key = ExtKey.new
21
24
  ext_key.depth = ext_key.number = 0
22
25
  ext_key.parent_fingerprint = '00000000'
23
- l = Tapyrus.hmac_sha512('Bitcoin seed', seed.htb)
26
+ l = Tapyrus.hmac_sha512('Tapyrus seed', seed.htb)
24
27
  left = l[0..31].bth.to_i(16)
25
28
  raise 'invalid key' if left >= CURVE_ORDER || left == 0
26
29
  ext_key.key = Tapyrus::Key.new(priv_key: l[0..31].bth, key_type: Tapyrus::Key::TYPES[:compressed])
@@ -48,9 +51,7 @@ module Tapyrus
48
51
 
49
52
  # Base58 encoded extended private key
50
53
  def to_base58
51
- h = to_hex
52
- hex = h + Tapyrus.calc_checksum(h)
53
- Base58.encode(hex)
54
+ ExtPubkey.encode_base58(to_hex)
54
55
  end
55
56
 
56
57
  # get private key(hex)
@@ -141,19 +142,24 @@ module Tapyrus
141
142
  buf = StringIO.new(payload)
142
143
  ext_key = ExtKey.new
143
144
  ext_key.ver = buf.read(4).bth # version
144
- raise 'An unsupported version byte was specified.' unless ExtKey.support_version?(ext_key.ver)
145
+ raise ArgumentError, Errors::Messages::INVALID_BIP32_VERSION unless ExtKey.support_version?(ext_key.ver)
145
146
  ext_key.depth = buf.read(1).unpack('C').first
146
147
  ext_key.parent_fingerprint = buf.read(4).bth
147
148
  ext_key.number = buf.read(4).unpack('N').first
149
+ if ext_key.depth == 0
150
+ raise ArgumentError, Errors::Messages::INVALID_BIP32_FINGERPRINT unless ext_key.parent_fingerprint == ExtKey::MASTER_FINGERPRINT
151
+ raise ArgumentError, Errors::Messages::INVALID_BIP32_ZERO_INDEX if ext_key.number > 0
152
+ end
153
+ raise ArgumentError, Errors::Messages:: INVALID_BIP32_ZERO_DEPTH if ext_key.parent_fingerprint == ExtKey::MASTER_FINGERPRINT && ext_key.depth > 0
148
154
  ext_key.chain_code = buf.read(32)
149
- buf.read(1) # 0x00
155
+ raise ArgumentError, Errors::Messages::INVALID_BIP32_PRIV_PREFIX unless buf.read(1).bth == '00' # 0x00
150
156
  ext_key.key = Tapyrus::Key.new(priv_key: buf.read(32).bth, key_type: Tapyrus::Key::TYPES[:compressed])
151
157
  ext_key
152
158
  end
153
159
 
154
160
  # import private key from Base58 private key address
155
161
  def self.from_base58(address)
156
- ExtKey.parse_from_payload(Base58.decode(address).htb)
162
+ ExtKey.parse_from_payload(ExtPubkey.validate_checksum(address))
157
163
  end
158
164
 
159
165
  # get version bytes from purpose' value.
@@ -237,9 +243,14 @@ module Tapyrus
237
243
 
238
244
  # Base58 encoded extended pubkey
239
245
  def to_base58
240
- h = to_hex
241
- hex = h + Tapyrus.calc_checksum(h)
242
- Base58.encode(hex)
246
+ ExtPubkey.encode_base58(to_hex)
247
+ end
248
+
249
+ # Generate Base58 encoded key from BIP32 payload with hex format.
250
+ # @param [String] hex BIP32 payload with hex format.
251
+ # @return [String] Base58 encoded extended key.
252
+ def self.encode_base58(hex)
253
+ Base58.encode(hex + Tapyrus.calc_checksum(hex))
243
254
  end
244
255
 
245
256
  # whether hardened key.
@@ -260,7 +271,7 @@ module Tapyrus
260
271
  raise 'invalid key' if left >= CURVE_ORDER
261
272
  p1 = Tapyrus::Secp256k1::GROUP.generator.multiply_by_scalar(left)
262
273
  p2 = Tapyrus::Key.new(pubkey: pubkey, key_type: key_type).to_point
263
- new_key.pubkey = ECDSA::Format::PointOctetString.encode(p1 + p2, compression: true).bth
274
+ new_key.pubkey = (p1 + p2).to_hex
264
275
  new_key.chain_code = l[32..-1]
265
276
  new_key.ver = version
266
277
  new_key
@@ -293,19 +304,32 @@ module Tapyrus
293
304
  buf = StringIO.new(payload)
294
305
  ext_pubkey = ExtPubkey.new
295
306
  ext_pubkey.ver = buf.read(4).bth # version
296
- raise 'An unsupported version byte was specified.' unless ExtPubkey.support_version?(ext_pubkey.ver)
307
+ raise ArgumentError, Errors::Messages::INVALID_BIP32_VERSION unless ExtPubkey.support_version?(ext_pubkey.ver)
297
308
  ext_pubkey.depth = buf.read(1).unpack('C').first
298
309
  ext_pubkey.parent_fingerprint = buf.read(4).bth
299
310
  ext_pubkey.number = buf.read(4).unpack('N').first
311
+ if ext_pubkey.depth == 0
312
+ raise ArgumentError, Errors::Messages::INVALID_BIP32_FINGERPRINT unless ext_pubkey.parent_fingerprint == ExtKey::MASTER_FINGERPRINT
313
+ raise ArgumentError, Errors::Messages::INVALID_BIP32_ZERO_INDEX if ext_pubkey.number > 0
314
+ end
315
+ raise ArgumentError, Errors::Messages::INVALID_BIP32_ZERO_DEPTH if ext_pubkey.parent_fingerprint == ExtKey::MASTER_FINGERPRINT && ext_pubkey.depth > 0
300
316
  ext_pubkey.chain_code = buf.read(32)
301
- ext_pubkey.pubkey = buf.read(33).bth
317
+ ext_pubkey.pubkey = Tapyrus::Key.new(pubkey: buf.read(33).bth).pubkey
302
318
  ext_pubkey
303
319
  end
304
320
 
305
-
306
321
  # import pub key from Base58 private key address
307
322
  def self.from_base58(address)
308
- ExtPubkey.parse_from_payload(Base58.decode(address).htb)
323
+ ExtPubkey.parse_from_payload(ExtPubkey.validate_checksum(address))
324
+ end
325
+
326
+ # Validate address checksum and return payload.
327
+ # @param [String] BIP32 Base58 address
328
+ # @return [String] BIP32 payload with binary format
329
+ def self.validate_checksum(base58)
330
+ raw = Base58.decode(base58)
331
+ raise ArgumentError, Errors::Messages::INVALID_CHECKSUM unless Tapyrus.calc_checksum(raw[0...-8]) == raw[-8..-1]
332
+ raw[0...-8].htb
309
333
  end
310
334
 
311
335
  # get version bytes from purpose' value.
@@ -30,7 +30,7 @@ module Tapyrus
30
30
  # @param [Integer] key_type a key type which determine address type.
31
31
  # @param [Boolean] compressed [Deprecated] whether public key is compressed.
32
32
  # @return [Tapyrus::Key] a key object.
33
- def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true)
33
+ def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true, allow_hybrid: false)
34
34
  puts "[Warning] Use key_type parameter instead of compressed. compressed parameter removed in the future." if key_type.nil? && !compressed.nil? && pubkey.nil?
35
35
  if key_type
36
36
  @key_type = key_type
@@ -41,13 +41,14 @@ module Tapyrus
41
41
  @secp256k1_module = Tapyrus.secp_impl
42
42
  @priv_key = priv_key
43
43
  if @priv_key
44
- raise ArgumentError, 'private key is not on curve' unless validate_private_key_range(@priv_key)
44
+ raise ArgumentError, Errors::Messages::INVALID_PRIV_KEY unless validate_private_key_range(@priv_key)
45
45
  end
46
46
  if pubkey
47
47
  @pubkey = pubkey
48
48
  else
49
49
  @pubkey = generate_pubkey(priv_key, compressed: compressed) if priv_key
50
50
  end
51
+ raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless fully_valid_pubkey?(allow_hybrid)
51
52
  end
52
53
 
53
54
  # generate key pair
@@ -65,7 +66,7 @@ module Tapyrus
65
66
  data = hex[2...-8].htb
66
67
  checksum = hex[-8..-1]
67
68
  raise ArgumentError, 'invalid version' unless version == Tapyrus.chain_params.privkey_version
68
- raise ArgumentError, 'invalid checksum' unless Tapyrus.calc_checksum(version + data.bth) == checksum
69
+ raise ArgumentError, Errors::Messages::INVALID_CHECKSUM unless Tapyrus.calc_checksum(version + data.bth) == checksum
69
70
  key_len = data.bytesize
70
71
  if key_len == COMPRESSED_PUBLIC_KEY_SIZE && data[-1].unpack('C').first == 1
71
72
  key_type = TYPES[:compressed]
@@ -223,10 +224,10 @@ module Tapyrus
223
224
  end
224
225
 
225
226
  # fully validate whether this is a valid public key (more expensive than IsValid())
226
- def fully_valid_pubkey?
227
+ def fully_valid_pubkey?(allow_hybrid = false)
227
228
  return false unless valid_pubkey?
228
229
  begin
229
- point = ECDSA::Format::PointOctetString.decode(pubkey.htb, ECDSA::Group::Secp256k1)
230
+ point = ECDSA::Format::PointOctetString.decode(pubkey.htb, ECDSA::Group::Secp256k1, allow_hybrid: allow_hybrid)
230
231
  ECDSA::Group::Secp256k1.valid_public_key?(point)
231
232
  rescue ECDSA::Format::DecodeError
232
233
  false
@@ -19,7 +19,7 @@ module Tapyrus
19
19
 
20
20
  def self.parse_from_payload(payload)
21
21
  buf = StringIO.new(payload)
22
- header = Tapyrus::BlockHeader.parse_from_payload(buf.read(80))
22
+ header = Tapyrus::BlockHeader.parse_from_payload(buf)
23
23
  transactions = []
24
24
  unless buf.eof?
25
25
  tx_count = Tapyrus.unpack_var_int_from_io(buf)
@@ -21,7 +21,7 @@ module Tapyrus
21
21
 
22
22
  def self.parse_from_payload(payload)
23
23
  buf = StringIO.new(payload)
24
- header = Tapyrus::BlockHeader.parse_from_payload(buf.read(80))
24
+ header = Tapyrus::BlockHeader.parse_from_payload(buf)
25
25
  nonce = buf.read(8).unpack('q*').first
26
26
  short_ids_len = Tapyrus.unpack_var_int_from_io(buf)
27
27
  short_ids = short_ids_len.times.map do
@@ -19,7 +19,7 @@ module Tapyrus
19
19
  header_count = Tapyrus.unpack_var_int_from_io(buf)
20
20
  h = new
21
21
  header_count.times do
22
- h.headers << Tapyrus::BlockHeader.parse_from_payload(buf.read(80))
22
+ h.headers << Tapyrus::BlockHeader.parse_from_payload(buf)
23
23
  buf.read(1) # read tx count 0x00 (headers message doesn't include any tx.)
24
24
  end
25
25
  h
@@ -19,7 +19,7 @@ module Tapyrus
19
19
  def self.parse_from_payload(payload)
20
20
  m = new
21
21
  buf = StringIO.new(payload)
22
- m.header = Tapyrus::BlockHeader.parse_from_payload(buf.read(80))
22
+ m.header = Tapyrus::BlockHeader.parse_from_payload(buf)
23
23
  m.tx_count = buf.read(4).unpack('V').first
24
24
  hash_count = Tapyrus.unpack_var_int_from_io(buf)
25
25
  hash_count.times do
@@ -11,7 +11,6 @@ module Tapyrus
11
11
  best_block = node.chain.latest_block
12
12
  h[:headers] = best_block.height
13
13
  h[:bestblockhash] = best_block.header.block_id
14
- h[:chainwork] = best_block.header.work
15
14
  h[:mediantime] = node.chain.mtp(best_block.block_hash)
16
15
  h
17
16
  end
@@ -32,12 +31,14 @@ module Tapyrus
32
31
  hash: block_id,
33
32
  height: entry.height,
34
33
  features: entry.header.features,
35
- featuresHex: entry.header.features.to_even_length_hex,
34
+ featuresHex: entry.header.features.to_even_length_hex.ljust(8, '0'),
36
35
  merkleroot: entry.header.merkle_root.rhex,
36
+ immutablemerkleroot: entry.header.im_merkle_root.rhex,
37
37
  time: entry.header.time,
38
38
  mediantime: node.chain.mtp(block_hash),
39
- nonce: entry.header.nonce,
40
- bits: entry.header.bits.to_even_length_hex,
39
+ xfield_type: entry.header.x_field_type,
40
+ xfield: entry.header.x_field,
41
+ proof: entry.header.proof,
41
42
  previousblockhash: entry.prev_hash.rhex,
42
43
  nextblockhash: node.chain.next_hash(block_hash).rhex
43
44
  }
@@ -64,7 +64,7 @@ module Tapyrus
64
64
  response = http.request(request)
65
65
  body = response.body
66
66
  response = Tapyrus::Ext::JsonParser.new(body.gsub(/\\u([\da-fA-F]{4})/) { [$1].pack('H*').unpack('n*').pack('U*').encode('ISO-8859-1').force_encoding('UTF-8') }).parse
67
- raise response['error'].to_s if response['error']
67
+ raise response['error'].to_json if response['error']
68
68
  response['result']
69
69
  end
70
70
 
@@ -32,12 +32,16 @@ module Tapyrus
32
32
  # @param [String] pubkey a public key with hex format.
33
33
  # @param [String] digest a message digest with binary format to be verified.
34
34
  # @return [Boolean] if check is passed return true, otherwise false.
35
- def verify_sig(sig, pubkey, digest)
35
+ def verify_sig(sig, pubkey, digest, allow_hybrid: false)
36
36
  key_type = 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
- key = Key.new(pubkey: pubkey, key_type: key_type)
40
- key.verify(sig, digest, algo: algo)
39
+ begin
40
+ key = Key.new(pubkey: pubkey, key_type: key_type, allow_hybrid: allow_hybrid)
41
+ key.verify(sig, digest, algo: algo)
42
+ rescue Exception
43
+ false
44
+ end
41
45
  end
42
46
 
43
47
  def check_locktime(locktime)
@@ -12,8 +12,8 @@ module Tapyrus
12
12
  private_key = 1 + SecureRandom.random_number(GROUP.order - 1)
13
13
  public_key = GROUP.generator.multiply_by_scalar(private_key)
14
14
  privkey = ECDSA::Format::IntegerOctetString.encode(private_key, 32)
15
- pubkey = ECDSA::Format::PointOctetString.encode(public_key, compression: compressed)
16
- [privkey.bth, pubkey.bth]
15
+ pubkey = public_key.to_hex(compressed)
16
+ [privkey.bth, pubkey]
17
17
  end
18
18
 
19
19
  # generate tapyrus key object
@@ -24,7 +24,7 @@ module Tapyrus
24
24
 
25
25
  def generate_pubkey(privkey, compressed: true)
26
26
  public_key = ECDSA::Group::Secp256k1.generator.multiply_by_scalar(privkey.to_i(16))
27
- ECDSA::Format::PointOctetString.encode(public_key, compression: compressed).bth
27
+ public_key.to_hex(compressed)
28
28
  end
29
29
 
30
30
  # sign data.
@@ -36,7 +36,7 @@ module Tapyrus
36
36
 
37
37
  # whether genesis block
38
38
  def genesis?
39
- Tapyrus.chain_params.genesis_block.header == header
39
+ height == 0
40
40
  end
41
41
 
42
42
  # @param [String] payload a payload with binary format.
@@ -44,7 +44,7 @@ module Tapyrus
44
44
  buf = StringIO.new(payload)
45
45
  len = Tapyrus.unpack_var_int_from_io(buf)
46
46
  height = buf.read(len).reverse.bth.to_i(16)
47
- new(Tapyrus::BlockHeader.parse_from_payload(buf.read(80)), height)
47
+ new(Tapyrus::BlockHeader.parse_from_payload(buf), height)
48
48
  end
49
49
 
50
50
  # build next block +StoredBlock+ instance.
@@ -58,14 +58,64 @@ module Tapyrus
58
58
  db.get(KEY_PREFIX[:entry] + hash)
59
59
  end
60
60
 
61
+ # Save entry.
62
+ # @param [Tapyrus::Store::ChainEntry]
61
63
  def save_entry(entry)
62
64
  db.batch do
63
65
  db.put(entry.key ,entry.to_payload)
64
66
  db.put(height_key(entry.height), entry.block_hash)
67
+ add_agg_pubkey(entry.height == 0 ? 0 : entry.height + 1, entry.header.x_field) if entry.header.upgrade_agg_pubkey?
65
68
  connect_entry(entry)
66
69
  end
67
70
  end
68
71
 
72
+ # Add aggregated public key.
73
+ # @param [Integer] activate_height
74
+ # @param [String] agg_pubkey aggregated public key with hex format.
75
+ def add_agg_pubkey(activate_height, agg_pubkey)
76
+ payload = activate_height.to_even_length_hex + agg_pubkey
77
+ index = latest_agg_pubkey_index
78
+ next_index = (index.nil? ? 0 : index + 1).to_even_length_hex
79
+ db.batch do
80
+ db.put(KEY_PREFIX[:agg_pubkey] + next_index, payload)
81
+ db.put(KEY_PREFIX[:latest_agg_pubkey], next_index)
82
+ end
83
+ end
84
+
85
+ # Get aggregated public key by specifying +index+.
86
+ # @param [Integer] index
87
+ # @return [Array] tupple of activate height and aggregated public key.
88
+ def agg_pubkey(index)
89
+ payload = db.get(KEY_PREFIX[:agg_pubkey] + index.to_even_length_hex)
90
+ [payload[0...(payload.length - 66)].to_i(16), payload[(payload.length - 66)..-1]]
91
+ end
92
+
93
+ # Get aggregated public key by specifying block +height+.
94
+ # @param [Integer] height block height.
95
+ # @return [String] aggregated public key with hex format.
96
+ def agg_pubkey_with_height(height)
97
+ index = latest_agg_pubkey_index
98
+ index ||= 0
99
+ (index + 1).times do |i|
100
+ target = index - i
101
+ active_height, pubkey = agg_pubkey(target)
102
+ return pubkey unless active_height > height
103
+ end
104
+ end
105
+
106
+ # Get latest aggregated public key.
107
+ # @return [Array] aggregated public key with hex format.
108
+ def latest_agg_pubkey
109
+ agg_pubkey(latest_agg_pubkey_index)[1]
110
+ end
111
+
112
+ # Get aggregated public key list.
113
+ # @return [Array[Array]] list of public key and index
114
+ def agg_pubkeys
115
+ index = latest_agg_pubkey_index
116
+ (index + 1).times.map { |i| agg_pubkey(i) }
117
+ end
118
+
69
119
  def close
70
120
  db.close
71
121
  end
@@ -91,6 +141,14 @@ module Tapyrus
91
141
  db.put(KEY_PREFIX[:best], entry.block_hash)
92
142
  db.put(KEY_PREFIX[:next] + entry.prev_hash, entry.block_hash)
93
143
  end
144
+
145
+ # Get latest aggregated public key index.
146
+ # @return [Integer] key index
147
+ def latest_agg_pubkey_index
148
+ index = db.get(KEY_PREFIX[:latest_agg_pubkey])
149
+ index&.to_i(16)
150
+ end
151
+
94
152
  end
95
153
 
96
154
  end
@@ -3,10 +3,12 @@ module Tapyrus
3
3
  module Store
4
4
 
5
5
  KEY_PREFIX = {
6
- entry: 'e', # key: block hash, value: Tapyrus::Store::ChainEntry payload
7
- height: 'h', # key: block height, value: block hash.
8
- best: 'B', # value: best block hash.
9
- next: 'n' # key: block hash, value: A hash of the next block of the specified hash
6
+ entry: 'e', # key: block hash, value: Tapyrus::Store::ChainEntry payload
7
+ height: 'h', # key: block height, value: block hash.
8
+ best: 'B', # value: best block hash.
9
+ next: 'n', # key: block hash, value: A hash of the next block of the specified hash
10
+ agg_pubkey: 'a', # key: index, value: Activated block height | aggregated public key.
11
+ latest_agg_pubkey: 'g' # value: latest agg pubkey index.
10
12
  }
11
13
 
12
14
  class SPVChain
@@ -14,10 +16,14 @@ module Tapyrus
14
16
  attr_reader :db
15
17
  attr_reader :logger
16
18
 
17
- def initialize(db = Tapyrus::Store::DB::LevelDB.new)
19
+ # initialize spv chain
20
+ # @param[Tapyrus::Store::DB::LevelDB] db
21
+ # @param[Tapyrus::Block] genesis genesis block
22
+ def initialize(db = Tapyrus::Store::DB::LevelDB.new, genesis: nil)
23
+ raise ArgumentError, 'genesis block should be specified.' unless genesis
18
24
  @db = db # TODO multiple db switch
19
25
  @logger = Tapyrus::Logger.create(:debug)
20
- initialize_block
26
+ initialize_block(genesis)
21
27
  end
22
28
 
23
29
  # get latest block in the store.
@@ -45,9 +51,9 @@ module Tapyrus
45
51
  # @return [Tapyrus::Store::ChainEntry] appended block header entry.
46
52
  def append_header(header)
47
53
  logger.info("append header #{header.block_id}")
48
- raise "this header is invalid. #{header.block_hash}" unless header.valid?
49
54
  best_block = latest_block
50
55
  current_height = best_block.height
56
+ raise "this header is invalid. #{header.block_hash}" unless header.valid?(db.agg_pubkey_with_height(current_height + 1))
51
57
  if best_block.block_hash == header.prev_hash
52
58
  entry = Tapyrus::Store::ChainEntry.new(header, current_height + 1)
53
59
  db.save_entry(entry)
@@ -83,14 +89,26 @@ module Tapyrus
83
89
  time[time.size / 2]
84
90
  end
85
91
 
92
+ # Add aggregated public key.
93
+ # @param [Integer] active_height
94
+ # @param [String] agg_pubkey aggregated public key with hex format.
95
+ def add_agg_pubkey(active_height, agg_pubkey)
96
+ db.add_agg_pubkey(active_height, agg_pubkey)
97
+ end
98
+
99
+ # get aggregated public key keys.
100
+ # @return [Array[Array(height, agg_pubkey)]] the list of public keys.
101
+ def agg_pubkeys
102
+ db.agg_pubkeys
103
+ end
104
+
86
105
  private
87
106
 
88
107
  # if database is empty, put genesis block.
89
- def initialize_block
108
+ # @param [Tapyrus::Block] genesis genesis block
109
+ def initialize_block(genesis)
90
110
  unless latest_block
91
- block = Tapyrus.chain_params.genesis_block
92
- genesis = ChainEntry.new(block.header, 0)
93
- db.save_entry(genesis)
111
+ db.save_entry(ChainEntry.new(genesis.header, 0))
94
112
  end
95
113
  end
96
114
 
@@ -1,3 +1,3 @@
1
1
  module Tapyrus
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.2"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tapyrus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - azuchi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-07-15 00:00:00.000000000 Z
11
+ date: 2020-09-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ecdsa
@@ -328,7 +328,9 @@ files:
328
328
  - lib/tapyrus/chainparams/dev.yml
329
329
  - lib/tapyrus/chainparams/prod.yml
330
330
  - lib/tapyrus/constants.rb
331
+ - lib/tapyrus/errors.rb
331
332
  - lib/tapyrus/ext.rb
333
+ - lib/tapyrus/ext/ecdsa.rb
332
334
  - lib/tapyrus/ext/json_parser.rb
333
335
  - lib/tapyrus/ext_key.rb
334
336
  - lib/tapyrus/key.rb