tapyrus 0.2.0 → 0.2.5

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: 1625f752a1848cec88c87af856ade34417b9f8aa618ee31db66387e60d8e7b8b
4
- data.tar.gz: b30ed36b3f4e74f96b57866261b0be40c74883dcfa6605d68a73da99316bb746
3
+ metadata.gz: 6eacc6d47ecb8c8f955e1713d0245b95185857a313189e232102a24e9dcf43c6
4
+ data.tar.gz: a961c657873aa82e7f917f55c8b00ea80175b9eb2eb363d72c3c1621f7bee9dc
5
5
  SHA512:
6
- metadata.gz: 743afb61fcd03ab089bf3124e007d23789b87eac5f35176b3430966814b4ada178f49960dba7240df5f02d7bdfd4b9ef9639c0ca7a9c26d89f9abd498c0ee5ce
7
- data.tar.gz: 1300203703bd438cf1e5a54967f9dcc1ee04d7a698a1678ad24b4ef8f3c18b568993790c40de537655d8b20fb06dec35dfaaeef8fe7d1fcc2886366876e18501
6
+ metadata.gz: 32adab54eb182f5f24536a0b48a89c05756901ba623b054916d7ba5ccc3e034f70147e578917bf847eabdb93070db3b7c628fe3ecc60ebc810ad4603497ec367
7
+ data.tar.gz: e7c802d4cd4081f0dcbf10c317551c47c7ab90ae764e341dfb55e7f32f91bfb28a0680d1ef915ae322ed15ca1524824df690c9e32e7d29fe27df8e080164f5bf
data/README.md CHANGED
@@ -12,7 +12,7 @@ Tapyrusrb supports following feature:
12
12
  * Tapyrus script interpreter
13
13
  * De/serialization of Tapyrus protocol network messages
14
14
  * De/serialization of blocks and transactions
15
- * Key generation and verification for ECDSA, including [BIP-32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) and [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) supports.
15
+ * Key generation and verification for Schnorr and ECDSA (including [BIP-32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) and [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) supports).
16
16
  * ECDSA signature(RFC6979 -Deterministic ECDSA, LOW-S, LOW-R support)
17
17
  * [WIP] SPV node
18
18
  * [WIP] 0ff-chain protocol
@@ -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]
@@ -95,10 +96,13 @@ module Tapyrus
95
96
  # @return [String] signature data with binary format
96
97
  def sign(data, low_r = true, extra_entropy = nil, algo: :ecdsa)
97
98
  raise ArgumentError, "Unsupported algorithm has been specified." unless SIG_ALGO.include?(algo)
98
- if algo == :ecdsa
99
+ case algo
100
+ when :ecdsa
99
101
  sign_ecdsa(data, low_r, extra_entropy)
102
+ when :schnorr
103
+ secp256k1_module.sign_data(data, priv_key, extra_entropy, algo: algo)
100
104
  else
101
- sign_schnorr(data)
105
+ false
102
106
  end
103
107
  end
104
108
 
@@ -111,11 +115,8 @@ module Tapyrus
111
115
  return false unless valid_pubkey?
112
116
  begin
113
117
  raise ArgumentError, "Unsupported algorithm has been specified." unless SIG_ALGO.include?(algo)
114
- if algo == :ecdsa
115
- verify_ecdsa_sig(sig, origin)
116
- else
117
- verify_schnorr_sig(sig, origin)
118
- end
118
+ sig = ecdsa_signature_parse_der_lax(sig) if algo == :ecdsa
119
+ secp256k1_module.verify_sig(origin, sig, pubkey, algo: algo)
119
120
  rescue Exception
120
121
  false
121
122
  end
@@ -223,14 +224,8 @@ 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
- return false unless valid_pubkey?
228
- begin
229
- point = ECDSA::Format::PointOctetString.decode(pubkey.htb, ECDSA::Group::Secp256k1)
230
- ECDSA::Group::Secp256k1.valid_public_key?(point)
231
- rescue ECDSA::Format::DecodeError
232
- false
233
- end
227
+ def fully_valid_pubkey?(allow_hybrid = false)
228
+ valid_pubkey? && secp256k1_module.parse_ec_pubkey?(pubkey, allow_hybrid)
234
229
  end
235
230
 
236
231
  private
@@ -291,34 +286,18 @@ module Tapyrus
291
286
 
292
287
  # generate ecdsa signature
293
288
  def sign_ecdsa(data, low_r, extra_entropy)
294
- sig = secp256k1_module.sign_data(data, priv_key, extra_entropy)
289
+ sig = secp256k1_module.sign_data(data, priv_key, extra_entropy, algo: :ecdsa)
295
290
  if low_r && !sig_has_low_r?(sig)
296
291
  counter = 1
297
292
  until sig_has_low_r?(sig)
298
293
  extra_entropy = [counter].pack('I*').bth.ljust(64, '0').htb
299
- sig = secp256k1_module.sign_data(data, priv_key, extra_entropy)
294
+ sig = secp256k1_module.sign_data(data, priv_key, extra_entropy, algo: :ecdsa)
300
295
  counter += 1
301
296
  end
302
297
  end
303
298
  sig
304
299
  end
305
300
 
306
- # generate schnorr signature
307
- def sign_schnorr(msg)
308
- Schnorr.sign(msg, priv_key.to_i(16)).encode
309
- end
310
-
311
- # verify ecdsa signature
312
- def verify_ecdsa_sig(sig, message)
313
- sig = ecdsa_signature_parse_der_lax(sig)
314
- secp256k1_module.verify_sig(message, sig, pubkey)
315
- end
316
-
317
- # verify schnorr signature
318
- def verify_schnorr_sig(sig, message)
319
- Schnorr.valid_sig?(message, sig, pubkey.htb)
320
- end
321
-
322
301
  end
323
302
 
324
303
  end
@@ -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
  }
@@ -57,14 +57,14 @@ module Tapyrus
57
57
  uri = URI.parse(server_url)
58
58
  http = Net::HTTP.new(uri.hostname, uri.port)
59
59
  http.use_ssl = uri.scheme === "https"
60
- request = Net::HTTP::Post.new('/')
60
+ request = Net::HTTP::Post.new(uri.path.empty? ? '/' : uri.path)
61
61
  request.basic_auth(uri.user, uri.password)
62
62
  request.content_type = 'application/json'
63
63
  request.body = data.to_json
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
 
@@ -53,11 +53,11 @@ module Tapyrus
53
53
 
54
54
  module ColoredOutput
55
55
  def colored?
56
- script_pubkey.cp2pkh? || script_pubkey.cp2sh?
56
+ script_pubkey.colored?
57
57
  end
58
58
 
59
59
  def color_id
60
- @color_id ||= ColorIdentifier.parse_from_payload(script_pubkey.chunks[0].pushed_data)
60
+ @color_id ||= script_pubkey.color_id
61
61
  end
62
62
 
63
63
  def reissuable?
@@ -75,6 +75,18 @@ module Tapyrus
75
75
  end
76
76
  end
77
77
 
78
+ # Remove color identifier from cp2pkh or cp2sh
79
+ # @param [ColorIdentifier] color identifier
80
+ # @return [Script] P2PKH or P2SH script
81
+ # @raise [RuntimeError] if script is neither cp2pkh nor cp2sh
82
+ def remove_color
83
+ raise RuntimeError, 'Only cp2pkh and cp2sh can remove color' unless cp2pkh? or cp2sh?
84
+
85
+ Tapyrus::Script.new.tap do |s|
86
+ s.chunks = self.chunks[2..-1]
87
+ end
88
+ end
89
+
78
90
  def get_multisig_pubkeys
79
91
  num = Tapyrus::Opcodes.opcode_to_small_int(chunks[-2].bth.to_i(16))
80
92
  (1..num).map{ |i| chunks[i].pushed_data }
@@ -214,6 +226,8 @@ module Tapyrus
214
226
  (chunks.size == 1 || chunks[1].opcode <= OP_16)
215
227
  end
216
228
 
229
+ # Return whether this script is a CP2PKH format script or not.
230
+ # @return [Boolean] true if this script is cp2pkh, otherwise false.
217
231
  def cp2pkh?
218
232
  return false unless chunks.size == 7
219
233
  return false unless chunks[0].bytesize == 34
@@ -223,6 +237,8 @@ module Tapyrus
223
237
  (chunks[2..3]+ chunks[5..6]).map(&:ord) && chunks[4].bytesize == 21
224
238
  end
225
239
 
240
+ # Return whether this script is a CP2SH format script or not.
241
+ # @return [Boolean] true if this script is cp2pkh, otherwise false.
226
242
  def cp2sh?
227
243
  return false unless chunks.size == 5
228
244
  return false unless chunks[0].bytesize == 34
@@ -230,6 +246,20 @@ module Tapyrus
230
246
  return false unless chunks[1].ord == OP_COLOR
231
247
  OP_HASH160 == chunks[2].ord && OP_EQUAL == chunks[4].ord && chunks[3].bytesize == 21
232
248
  end
249
+
250
+ # Return whether this script represents colored coin.
251
+ # @return [Boolean] true if this script is colored, otherwise false.
252
+ def colored?
253
+ cp2pkh? || cp2sh?
254
+ end
255
+
256
+ # Return color identifier for this script.
257
+ # @return [ColorIdentifer] color identifier for this script if this script is colored. return nil if this script is not colored.
258
+ def color_id
259
+ return nil unless colored?
260
+
261
+ Tapyrus::Color::ColorIdentifier.parse_from_payload(chunks[0].pushed_data)
262
+ end
233
263
 
234
264
  def op_return_data
235
265
  return nil unless op_return?
@@ -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)
@@ -4,8 +4,8 @@
4
4
  module Tapyrus
5
5
  module Secp256k1
6
6
 
7
- # binding for secp256k1 (https://github.com/bitcoin/bitcoin/tree/v0.14.2/src/secp256k1)
8
- # tag: v0.14.2
7
+ # binding for secp256k1 (https://github.com/chaintope/tapyrus-core/tree/v0.4.0/src/secp256k1)
8
+ # tag: v0.4.0
9
9
  # this is not included by default, to enable set shared object path to ENV['SECP256K1_LIB_PATH']
10
10
  # for linux, ENV['SECP256K1_LIB_PATH'] = '/usr/local/lib/libsecp256k1.so'
11
11
  # for mac,
@@ -50,6 +50,8 @@ module Tapyrus
50
50
  attach_function(:secp256k1_ecdsa_signature_parse_der, [:pointer, :pointer, :pointer, :size_t], :int)
51
51
  attach_function(:secp256k1_ecdsa_signature_normalize, [:pointer, :pointer, :pointer], :int)
52
52
  attach_function(:secp256k1_ecdsa_verify, [:pointer, :pointer, :pointer, :pointer], :int)
53
+ attach_function(:secp256k1_schnorr_sign, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
54
+ attach_function(:secp256k1_schnorr_verify, [:pointer, :pointer, :pointer, :pointer], :int)
53
55
  end
54
56
 
55
57
  def with_context(flags: (SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN))
@@ -100,7 +102,68 @@ module Tapyrus
100
102
  # @param [String] privkey a private key using sign
101
103
  # @param [String] extra_entropy a extra entropy for rfc6979
102
104
  # @return [String] signature data with binary format
103
- def sign_data(data, privkey, extra_entropy)
105
+ def sign_data(data, privkey, extra_entropy, algo: :ecdsa)
106
+ case algo
107
+ when :ecdsa
108
+ sign_ecdsa(data, privkey, extra_entropy)
109
+ when :schnorr
110
+ sign_schnorr(data, privkey)
111
+ else
112
+ nil
113
+ end
114
+ end
115
+
116
+ # verify signature.
117
+ # @param[String] data a data.
118
+ # @param [String] sig signature data with binary format
119
+ # @param [String] pub_key a public key using verify.
120
+ def verify_sig(data, sig, pub_key, algo: :ecdsa)
121
+ case algo
122
+ when :ecdsa
123
+ verify_ecdsa(data, sig, pub_key)
124
+ when :schnorr
125
+ verify_schnorr(data, sig, pub_key)
126
+ else
127
+ false
128
+ end
129
+ end
130
+
131
+ # # validate whether this is a valid public key (more expensive than IsValid())
132
+ # @param [String] pub_key public key with hex format.
133
+ # @param [Boolean] allow_hybrid whether support hybrid public key.
134
+ # @return [Boolean] If valid public key return true, otherwise false.
135
+ def parse_ec_pubkey?(pub_key, allow_hybrid = false)
136
+ pub_key = pub_key.htb
137
+ return false if !allow_hybrid && ![0x02, 0x03, 0x04].include?(pub_key[0].ord)
138
+ with_context do |context|
139
+ pubkey = FFI::MemoryPointer.new(:uchar, pub_key.bytesize).put_bytes(0, pub_key)
140
+ internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
141
+ result = secp256k1_ec_pubkey_parse(context, internal_pubkey, pubkey, pub_key.bytesize)
142
+ result == 1
143
+ end
144
+ end
145
+
146
+ private
147
+
148
+ def generate_pubkey_in_context(context, privkey, compressed: true)
149
+ internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
150
+ result = secp256k1_ec_pubkey_create(context, internal_pubkey, privkey.htb)
151
+ raise 'error creating pubkey' unless result
152
+
153
+ pubkey = FFI::MemoryPointer.new(:uchar, 65)
154
+ 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
162
+ raise 'error serialize pubkey' unless result || pubkey_len.read_uint64 > 0
163
+ pubkey.read_string(pubkey_len.read_uint64).bth
164
+ end
165
+
166
+ def sign_ecdsa(data, privkey, extra_entropy)
104
167
  with_context do |context|
105
168
  secret = FFI::MemoryPointer.new(:uchar, privkey.htb.bytesize).put_bytes(0, privkey.htb)
106
169
  raise 'priv_key invalid' unless secp256k1_ec_seckey_verify(context, secret)
@@ -126,7 +189,19 @@ module Tapyrus
126
189
  end
127
190
  end
128
191
 
129
- def verify_sig(data, sig, pub_key)
192
+ def sign_schnorr(data, privkey)
193
+ with_context do |context|
194
+ secret = FFI::MemoryPointer.new(:uchar, privkey.htb.bytesize).put_bytes(0, privkey.htb)
195
+ raise 'priv_key invalid' unless secp256k1_ec_seckey_verify(context, secret)
196
+
197
+ signature = FFI::MemoryPointer.new(:uchar, 64)
198
+ 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
200
+ signature.read_string(64)
201
+ end
202
+ end
203
+
204
+ def verify_ecdsa(data, sig, pub_key)
130
205
  with_context do |context|
131
206
  return false if data.bytesize == 0
132
207
 
@@ -150,25 +225,23 @@ module Tapyrus
150
225
  end
151
226
  end
152
227
 
153
- private
228
+ def verify_schnorr(data, sig, pub_key)
229
+ with_context do |context|
230
+ return false if data.bytesize == 0
231
+ pubkey = FFI::MemoryPointer.new(:uchar, pub_key.htb.bytesize).put_bytes(0, pub_key.htb)
232
+ internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
233
+ result = secp256k1_ec_pubkey_parse(context, internal_pubkey, pubkey, pubkey.size)
234
+ return false unless result
154
235
 
155
- def generate_pubkey_in_context(context, privkey, compressed: true)
156
- internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
157
- result = secp256k1_ec_pubkey_create(context, internal_pubkey, privkey.htb)
158
- raise 'error creating pubkey' unless result
236
+ signature = FFI::MemoryPointer.new(:uchar, sig.bytesize).put_bytes(0, sig)
159
237
 
160
- pubkey = FFI::MemoryPointer.new(:uchar, 65)
161
- pubkey_len = FFI::MemoryPointer.new(:uint64)
162
- result = if compressed
163
- pubkey_len.put_uint64(0, 33)
164
- secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_COMPRESSED)
165
- else
166
- pubkey_len.put_uint64(0, 65)
167
- secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_UNCOMPRESSED)
168
- end
169
- raise 'error serialize pubkey' unless result || pubkey_len.read_uint64 > 0
170
- pubkey.read_string(pubkey_len.read_uint64).bth
238
+ msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
239
+ result = secp256k1_schnorr_verify(context, signature, msg32, internal_pubkey)
240
+
241
+ result == 1
242
+ end
171
243
  end
244
+
172
245
  end
173
246
  end
174
247
  end
@@ -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,14 +24,71 @@ 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.
31
31
  # @param [String] data a data to be signed with binary format
32
32
  # @param [String] privkey a private key using sign
33
33
  # @return [String] signature data with binary format
34
- def sign_data(data, privkey, extra_entropy)
34
+ def sign_data(data, privkey, extra_entropy, algo: :ecdsa)
35
+ case algo
36
+ when :ecdsa
37
+ sign_ecdsa(data, privkey, extra_entropy)
38
+ when :schnorr
39
+ Schnorr.sign(data, privkey.to_i(16)).encode
40
+ else
41
+ nil
42
+ end
43
+ end
44
+
45
+ # verify signature using public key
46
+ # @param [String] digest a SHA-256 message digest with binary format
47
+ # @param [String] sig a signature for +data+ with binary format
48
+ # @param [String] pubkey a public key corresponding to the private key used for sign
49
+ # @return [Boolean] verify result
50
+ def verify_sig(digest, sig, pubkey, algo: :ecdsa)
51
+ case algo
52
+ when :ecdsa
53
+ verify_ecdsa(digest, sig, pubkey)
54
+ when :schnorr
55
+ Schnorr.valid_sig?(digest, sig, pubkey.htb)
56
+ else
57
+ false
58
+ end
59
+ end
60
+
61
+ alias :valid_sig? :verify_sig
62
+
63
+ module_function :valid_sig?
64
+
65
+ # validate whether this is a valid public key (more expensive than IsValid())
66
+ # @param [String] pubkey public key with hex format.
67
+ # @param [Boolean] allow_hybrid whether support hybrid public key.
68
+ # @return [Boolean] If valid public key return true, otherwise false.
69
+ def parse_ec_pubkey?(pubkey, allow_hybrid = false)
70
+ begin
71
+ point = ECDSA::Format::PointOctetString.decode(pubkey.htb, ECDSA::Group::Secp256k1, allow_hybrid: allow_hybrid)
72
+ ECDSA::Group::Secp256k1.valid_public_key?(point)
73
+ rescue ECDSA::Format::DecodeError
74
+ false
75
+ end
76
+ end
77
+
78
+ # if +pubkey+ is hybrid public key format, it convert uncompressed format.
79
+ # https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2012-June/001578.html
80
+ def repack_pubkey(pubkey)
81
+ p = pubkey.htb
82
+ case p[0]
83
+ when "\x06", "\x07"
84
+ p[0] = "\x04"
85
+ p
86
+ else
87
+ pubkey.htb
88
+ end
89
+ end
90
+
91
+ def sign_ecdsa(data, privkey, extra_entropy)
35
92
  privkey = privkey.htb
36
93
  private_key = ECDSA::Format::IntegerOctetString.decode(privkey)
37
94
  extra_entropy ||= ''
@@ -59,12 +116,7 @@ module Tapyrus
59
116
  signature
60
117
  end
61
118
 
62
- # verify signature using public key
63
- # @param [String] digest a SHA-256 message digest with binary format
64
- # @param [String] sig a signature for +data+ with binary format
65
- # @param [String] pubkey a public key corresponding to the private key used for sign
66
- # @return [Boolean] verify result
67
- def verify_sig(digest, sig, pubkey)
119
+ def verify_ecdsa(digest, sig, pubkey)
68
120
  begin
69
121
  k = ECDSA::Format::PointOctetString.decode(repack_pubkey(pubkey), GROUP)
70
122
  signature = ECDSA::Format::SignatureDerString.decode(sig)
@@ -74,23 +126,6 @@ module Tapyrus
74
126
  end
75
127
  end
76
128
 
77
- alias :valid_sig? :verify_sig
78
-
79
- module_function :valid_sig?
80
-
81
- # if +pubkey+ is hybrid public key format, it convert uncompressed format.
82
- # https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2012-June/001578.html
83
- def repack_pubkey(pubkey)
84
- p = pubkey.htb
85
- case p[0]
86
- when "\x06", "\x07"
87
- p[0] = "\x04"
88
- p
89
- else
90
- pubkey.htb
91
- end
92
- end
93
-
94
129
  end
95
130
 
96
131
  end
@@ -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.0"
2
+ VERSION = "0.2.5"
3
3
  end
@@ -10,8 +10,8 @@ Gem::Specification.new do |spec|
10
10
  spec.authors = ["azuchi"]
11
11
  spec.email = ["azuchi@chaintope.com"]
12
12
 
13
- spec.summary = %q{[WIP]The implementation of Tapyrus Protocol for Ruby.}
14
- spec.description = %q{[WIP]The implementation of Tapyrus Protocol for Ruby.}
13
+ spec.summary = %q{The implementation of Tapyrus Protocol for Ruby.}
14
+ spec.description = %q{The implementation of Tapyrus Protocol for Ruby.}
15
15
  spec.homepage = 'https://github.com/chaintope/tapyrusrb'
16
16
  spec.license = "MIT"
17
17
 
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.0
4
+ version: 0.2.5
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-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ecdsa
@@ -290,7 +290,7 @@ dependencies:
290
290
  - - "~>"
291
291
  - !ruby/object:Gem::Version
292
292
  version: '3.0'
293
- description: "[WIP]The implementation of Tapyrus Protocol for Ruby."
293
+ description: The implementation of Tapyrus Protocol for Ruby.
294
294
  email:
295
295
  - azuchi@chaintope.com
296
296
  executables:
@@ -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
@@ -450,5 +452,5 @@ requirements: []
450
452
  rubygems_version: 3.0.3
451
453
  signing_key:
452
454
  specification_version: 4
453
- summary: "[WIP]The implementation of Tapyrus Protocol for Ruby."
455
+ summary: The implementation of Tapyrus Protocol for Ruby.
454
456
  test_files: []