tapyrus 0.2.1 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.