tapyrus 0.2.1 → 0.2.6

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: ba1dec1b7532b15985c9233604dc63ad38b4a11e61d4bbc63d54cebdb291b3d6
4
+ data.tar.gz: 3b7cb3c9e41df3693ac45b22084108b1ec682fd753a10ff95cf0d50133bff959
5
5
  SHA512:
6
- metadata.gz: 584cf7b8ce40aeb94ecce56c393ab52805b5af76bf91439f5f1b39cf28ce15a723bc2b37862773e5c1cd994cb6d72f270b4f77c828708384e9fe70990efbd19c
7
- data.tar.gz: cdc17bfad7962a3900ad567c07d0d53550f0db4eacf8b10c80e76017206aeed702c336fee9a7283502ae3ebdcf68de6da45e90a22ea0044e22e80839fa6cd43d
6
+ metadata.gz: f82c7815830b1eb9aba88b0247acf36cb64f9ab47802e6b05c6dc4e167503baf85fa1696bfc59fe79cd78c8866b0638b2022cb52b3c924e2db34f09c06d01370
7
+ data.tar.gz: 8cdeec0c475d32f905b245f3b2a8ef9551d4ee55a1e21abe9a5b1f38b1dfe6fb317cfa85387899b5d0d5667bf19278654fee800b0b90234fa1407cd9f1703f8b
@@ -1,8 +1,10 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.4.6
4
- - 2.5.5
5
- - 2.6.3
3
+ - 2.4.10
4
+ - 2.5.8
5
+ - 2.6.6
6
+ - 2.7.2
7
+ - 3.0.0
6
8
  addons:
7
9
  apt:
8
10
  packages:
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 ECDSA::Format::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.