tapyrus 0.2.0 → 0.2.5
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 +4 -4
- data/README.md +1 -1
- data/lib/schnorr.rb +1 -1
- data/lib/tapyrus.rb +2 -7
- data/lib/tapyrus/block.rb +3 -7
- data/lib/tapyrus/block_header.rb +66 -38
- data/lib/tapyrus/chain_params.rb +0 -10
- data/lib/tapyrus/chainparams/dev.yml +0 -10
- data/lib/tapyrus/chainparams/prod.yml +0 -9
- data/lib/tapyrus/errors.rb +17 -0
- data/lib/tapyrus/ext/ecdsa.rb +39 -0
- data/lib/tapyrus/ext_key.rb +39 -15
- data/lib/tapyrus/key.rb +15 -36
- data/lib/tapyrus/message/block.rb +1 -1
- data/lib/tapyrus/message/header_and_short_ids.rb +1 -1
- data/lib/tapyrus/message/headers.rb +1 -1
- data/lib/tapyrus/message/merkle_block.rb +1 -1
- data/lib/tapyrus/rpc/request_handler.rb +5 -4
- data/lib/tapyrus/rpc/tapyrus_core_client.rb +2 -2
- data/lib/tapyrus/script/color.rb +2 -2
- data/lib/tapyrus/script/script.rb +30 -0
- data/lib/tapyrus/script/tx_checker.rb +7 -3
- data/lib/tapyrus/secp256k1/native.rb +93 -20
- data/lib/tapyrus/secp256k1/ruby.rb +62 -27
- data/lib/tapyrus/store/chain_entry.rb +2 -2
- data/lib/tapyrus/store/db/level_db.rb +58 -0
- data/lib/tapyrus/store/spv_chain.rb +29 -11
- data/lib/tapyrus/version.rb +1 -1
- data/tapyrusrb.gemspec +2 -2
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6eacc6d47ecb8c8f955e1713d0245b95185857a313189e232102a24e9dcf43c6
|
4
|
+
data.tar.gz: a961c657873aa82e7f917f55c8b00ea80175b9eb2eb363d72c3c1621f7bee9dc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
data/lib/schnorr.rb
CHANGED
@@ -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=
|
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
|
|
data/lib/tapyrus.rb
CHANGED
@@ -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
|
data/lib/tapyrus/block.rb
CHANGED
@@ -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
|
-
#
|
36
|
+
# @return [Integer] block height.
|
36
37
|
def height
|
37
|
-
|
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
|
data/lib/tapyrus/block_header.rb
CHANGED
@@ -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 :
|
11
|
-
attr_accessor :
|
12
|
-
attr_accessor :
|
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,
|
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
|
-
@
|
20
|
-
@
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
41
|
-
|
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
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
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
|
-
#
|
70
|
-
# @
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
82
|
-
|
83
|
-
def
|
84
|
-
|
109
|
+
# get bytesize.
|
110
|
+
# @return [Integer] bytesize.
|
111
|
+
def size
|
112
|
+
to_payload.bytesize
|
85
113
|
end
|
86
114
|
|
87
115
|
end
|
data/lib/tapyrus/chain_params.rb
CHANGED
@@ -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
|
data/lib/tapyrus/ext_key.rb
CHANGED
@@ -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('
|
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
|
-
|
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
|
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(
|
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
|
-
|
241
|
-
|
242
|
-
|
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 =
|
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
|
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(
|
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.
|
data/lib/tapyrus/key.rb
CHANGED
@@ -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,
|
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,
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
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
|
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
|
-
|
40
|
-
|
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'].
|
67
|
+
raise response['error'].to_json if response['error']
|
68
68
|
response['result']
|
69
69
|
end
|
70
70
|
|
data/lib/tapyrus/script/color.rb
CHANGED
@@ -53,11 +53,11 @@ module Tapyrus
|
|
53
53
|
|
54
54
|
module ColoredOutput
|
55
55
|
def colored?
|
56
|
-
script_pubkey.
|
56
|
+
script_pubkey.colored?
|
57
57
|
end
|
58
58
|
|
59
59
|
def color_id
|
60
|
-
@color_id ||=
|
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
|
-
|
40
|
-
|
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/
|
8
|
-
# tag: v0.
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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 =
|
16
|
-
[privkey.bth, pubkey
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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',
|
7
|
-
height: 'h',
|
8
|
-
best: 'B',
|
9
|
-
next: 'n'
|
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
|
-
|
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
|
-
|
108
|
+
# @param [Tapyrus::Block] genesis genesis block
|
109
|
+
def initialize_block(genesis)
|
90
110
|
unless latest_block
|
91
|
-
|
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
|
|
data/lib/tapyrus/version.rb
CHANGED
data/tapyrusrb.gemspec
CHANGED
@@ -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{
|
14
|
-
spec.description = %q{
|
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.
|
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-
|
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:
|
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:
|
455
|
+
summary: The implementation of Tapyrus Protocol for Ruby.
|
454
456
|
test_files: []
|