zilliqa 0.1.1
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 +7 -0
- data/.circleci/config.yml +30 -0
- data/.gitignore +8 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +50 -0
- data/LICENSE.txt +674 -0
- data/README.md +175 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/zilliqa.rb +21 -0
- data/lib/zilliqa/account/account.rb +34 -0
- data/lib/zilliqa/account/transaction.rb +158 -0
- data/lib/zilliqa/account/transaction_factory.rb +16 -0
- data/lib/zilliqa/account/wallet.rb +146 -0
- data/lib/zilliqa/contract/contract.rb +150 -0
- data/lib/zilliqa/contract/contract_factory.rb +47 -0
- data/lib/zilliqa/crypto/key_store.rb +113 -0
- data/lib/zilliqa/crypto/key_tool.rb +61 -0
- data/lib/zilliqa/crypto/schnorr.rb +147 -0
- data/lib/zilliqa/jsonrpc/provider.rb +31 -0
- data/lib/zilliqa/proto/message.proto +44 -0
- data/lib/zilliqa/proto/message_pb.rb +46 -0
- data/lib/zilliqa/util/bech32.rb +28 -0
- data/lib/zilliqa/util/unit.rb +37 -0
- data/lib/zilliqa/util/util.rb +17 -0
- data/lib/zilliqa/util/validator.rb +41 -0
- data/lib/zilliqa/version.rb +3 -0
- data/zilliqa.gemspec +46 -0
- metadata +204 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'secp256k1'
|
2
|
+
require 'digest'
|
3
|
+
|
4
|
+
module Zilliqa
|
5
|
+
module Crypto
|
6
|
+
class KeyTool
|
7
|
+
include Secp256k1
|
8
|
+
def initialize(private_key)
|
9
|
+
is_raw = private_key.length == 32
|
10
|
+
|
11
|
+
@pk = PrivateKey.new(privkey: private_key, raw: is_raw)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.generate_private_key
|
15
|
+
Util.encode_hex KeyTool.generate_random_bytes(32)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.generate_random_bytes(size)
|
19
|
+
SecureRandom.random_bytes(size)
|
20
|
+
end
|
21
|
+
|
22
|
+
# getPubKeyFromPrivateKey
|
23
|
+
#
|
24
|
+
# takes a hex-encoded string (private key) and returns its corresponding
|
25
|
+
# hex-encoded 33-byte public key.
|
26
|
+
#
|
27
|
+
# @param {string} privateKey
|
28
|
+
# @returns {string}
|
29
|
+
def self.get_public_key_from_private_key(private_key, is_compressed = true)
|
30
|
+
is_raw = private_key.length == 32
|
31
|
+
|
32
|
+
pk = PrivateKey.new(privkey: private_key, raw: is_raw)
|
33
|
+
|
34
|
+
(Util.encode_hex pk.pubkey.serialize(compressed: is_compressed)).downcase
|
35
|
+
end
|
36
|
+
|
37
|
+
# getAddressFromPrivateKey
|
38
|
+
#
|
39
|
+
# takes a hex-encoded string (private key) and returns its corresponding
|
40
|
+
# 20-byte hex-encoded address.
|
41
|
+
#
|
42
|
+
# @param {string} privateKey
|
43
|
+
# @returns {string}
|
44
|
+
def self.get_address_from_private_key(private_key)
|
45
|
+
public_key = KeyTool.get_public_key_from_private_key(private_key)
|
46
|
+
KeyTool.get_address_from_public_key(public_key)
|
47
|
+
end
|
48
|
+
|
49
|
+
# getAddressFromPublicKey
|
50
|
+
#
|
51
|
+
# takes hex-encoded string and returns the corresponding address
|
52
|
+
#
|
53
|
+
# @param {string} public_key
|
54
|
+
# @returns {string}
|
55
|
+
def self.get_address_from_public_key(public_key)
|
56
|
+
orig_address = Digest::SHA256.hexdigest Util.decode_hex public_key
|
57
|
+
Util::Bech32.to_bech32(orig_address[24..-1].downcase)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'secp256k1'
|
2
|
+
require 'digest'
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
module Zilliqa
|
6
|
+
module Crypto
|
7
|
+
class Schnorr
|
8
|
+
include Secp256k1
|
9
|
+
|
10
|
+
N = OpenSSL::BN.new('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16)
|
11
|
+
G = OpenSSL::BN.new('79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798', 16)
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
end
|
15
|
+
|
16
|
+
# sign
|
17
|
+
#
|
18
|
+
# @param {String} msg
|
19
|
+
# @param {String} key
|
20
|
+
def self.sign(message, private_key, public_key)
|
21
|
+
sig = nil
|
22
|
+
until sig
|
23
|
+
k = Util.encode_hex SecureRandom.random_bytes(32)
|
24
|
+
k_bn = OpenSSL::BN.new(k, 16)
|
25
|
+
|
26
|
+
sig = try_sign(message, private_key, k_bn, public_key)
|
27
|
+
sig = Zilliqa::Util::Validator.signature?(sig.to_s) ? sig : nil
|
28
|
+
end
|
29
|
+
|
30
|
+
sig
|
31
|
+
end
|
32
|
+
|
33
|
+
# trySign
|
34
|
+
#
|
35
|
+
# @param {String} message - the message to sign over
|
36
|
+
# @param {String} privateKey - the private key
|
37
|
+
# @param {BN} k_bn - output of the HMAC-DRBG
|
38
|
+
#
|
39
|
+
# @returns {Signature | null =>}
|
40
|
+
def self.try_sign(message, private_key, k_bn, public_key)
|
41
|
+
group = OpenSSL::PKey::EC::Group.new('secp256k1')
|
42
|
+
|
43
|
+
prikey_bn = OpenSSL::BN.new(private_key, 16)
|
44
|
+
|
45
|
+
pubkey_bn = OpenSSL::BN.new(public_key, 16)
|
46
|
+
pubkey_point = OpenSSL::PKey::EC::Point.new(group, pubkey_bn)
|
47
|
+
|
48
|
+
throw 'Bad private key.' if prikey_bn.zero? || prikey_bn >= N
|
49
|
+
|
50
|
+
# 1a. check that k is not 0
|
51
|
+
return nil if k_bn.zero?
|
52
|
+
|
53
|
+
# 1b. check that k is < the order of the group
|
54
|
+
return nil if k_bn >= N
|
55
|
+
|
56
|
+
# 2. Compute commitment Q = kG, where g is the base point
|
57
|
+
q_point = pubkey_point.mul(0, k_bn)
|
58
|
+
|
59
|
+
# 3. Compute the challenge r = H(Q || pubKey || msg)
|
60
|
+
# mod reduce the r value by the order of secp256k1, n
|
61
|
+
r_bn = hash(q_point, pubkey_point, message) % N
|
62
|
+
|
63
|
+
return nil if r_bn.zero?
|
64
|
+
|
65
|
+
# 4. Compute s = k - r * prv
|
66
|
+
# 4a. Compute r * prv
|
67
|
+
s_bn = r_bn * prikey_bn % N
|
68
|
+
# 4b. Compute s = k - r * prv mod n
|
69
|
+
s_bn = k_bn.mod_sub(s_bn, N)
|
70
|
+
|
71
|
+
return nil if s_bn.zero?
|
72
|
+
|
73
|
+
Signature.new(r_bn.to_s(16), s_bn.to_s(16))
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
# Verify signature.
|
78
|
+
#
|
79
|
+
# @param {Buffer} message
|
80
|
+
# @param {Buffer} sig
|
81
|
+
# @param {Buffer} public_key
|
82
|
+
#
|
83
|
+
# @returns {boolean}
|
84
|
+
#
|
85
|
+
# 1. Check if r,s is in [1, ..., order-1]
|
86
|
+
# 2. Compute Q = sG + r*kpub
|
87
|
+
# 3. If Q = O (the neutral point), return 0;
|
88
|
+
# 4. r' = H(Q, kpub, m)
|
89
|
+
# 5. return r' == r
|
90
|
+
def self.verify(message, sig, public_key)
|
91
|
+
pubkey = PublicKey.new
|
92
|
+
pubkey.deserialize Util.decode_hex(public_key)
|
93
|
+
|
94
|
+
r = sig.r
|
95
|
+
r_bn = OpenSSL::BN.new(r, 16)
|
96
|
+
|
97
|
+
s = sig.s
|
98
|
+
s_bn = OpenSSL::BN.new(s, 16)
|
99
|
+
|
100
|
+
throw 'Invalid signature' if (s_bn.zero? || r_bn.zero?)
|
101
|
+
|
102
|
+
throw 'Invalid signature' if (s_bn.negative? || r_bn.negative?)
|
103
|
+
|
104
|
+
throw 'Invalid signature' if (s_bn >= N || r_bn >= N)
|
105
|
+
|
106
|
+
group = OpenSSL::PKey::EC::Group.new('secp256k1')
|
107
|
+
pubkey_bn = OpenSSL::BN.new(public_key, 16)
|
108
|
+
pubkey_point = OpenSSL::PKey::EC::Point.new(group, pubkey_bn)
|
109
|
+
|
110
|
+
throw 'Invalid public key' unless pubkey_point.on_curve?
|
111
|
+
|
112
|
+
q_point = pubkey_point.mul(r_bn, s_bn)
|
113
|
+
|
114
|
+
throw 'Invalid intermediate point.' if q_point.infinity?
|
115
|
+
|
116
|
+
h_bn = self.hash(q_point, pubkey_point, message) % N
|
117
|
+
|
118
|
+
throw 'Invalid hash.' if (h_bn.zero?)
|
119
|
+
|
120
|
+
h_bn.eql?(r_bn)
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
# Hash (r | M).
|
125
|
+
def self.hash(q_point, pubkey_point, message)
|
126
|
+
sha256 = Digest::SHA256.new
|
127
|
+
sha256 << q_point.to_octet_string(:compressed)
|
128
|
+
sha256 << pubkey_point.to_octet_string(:compressed)
|
129
|
+
sha256 << Util.decode_hex(message)
|
130
|
+
|
131
|
+
OpenSSL::BN.new(sha256.hexdigest, 16)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class Signature
|
136
|
+
attr_reader :r, :s
|
137
|
+
def initialize(r, s)
|
138
|
+
@r = r
|
139
|
+
@s = s
|
140
|
+
end
|
141
|
+
|
142
|
+
def to_s
|
143
|
+
"#{@r}#{@s}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'jsonrpc-client'
|
2
|
+
|
3
|
+
module JSONRPC
|
4
|
+
class Base
|
5
|
+
def self.make_id
|
6
|
+
"1"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Zilliqa
|
12
|
+
module Jsonrpc
|
13
|
+
class Provider
|
14
|
+
def initialize(endpoint)
|
15
|
+
conn = Faraday.new { |connection|
|
16
|
+
connection.adapter Faraday.default_adapter
|
17
|
+
}
|
18
|
+
@client = JSONRPC::Client.new(endpoint, { connection: conn })
|
19
|
+
@endpoint = endpoint
|
20
|
+
end
|
21
|
+
|
22
|
+
def method_missing(sym, *args)
|
23
|
+
@client.invoke(sym.to_s, args)
|
24
|
+
end
|
25
|
+
|
26
|
+
def testnet?
|
27
|
+
@endpoint && !@endpoint.match('dev').nil?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
syntax = "proto2";
|
2
|
+
|
3
|
+
package laksa.proto;
|
4
|
+
|
5
|
+
// ============================================================================
|
6
|
+
// Primitives
|
7
|
+
// ============================================================================
|
8
|
+
|
9
|
+
message ByteArray
|
10
|
+
{
|
11
|
+
required bytes data = 1;
|
12
|
+
}
|
13
|
+
|
14
|
+
message ProtoTransactionCoreInfo
|
15
|
+
{
|
16
|
+
optional uint32 version = 1;
|
17
|
+
optional uint64 nonce = 2;
|
18
|
+
optional bytes toaddr = 3;
|
19
|
+
optional ByteArray senderpubkey = 4;
|
20
|
+
optional ByteArray amount = 5;
|
21
|
+
optional ByteArray gasprice = 6;
|
22
|
+
optional uint64 gaslimit = 7;
|
23
|
+
optional bytes code = 8;
|
24
|
+
optional bytes data = 9;
|
25
|
+
}
|
26
|
+
|
27
|
+
message ProtoTransaction
|
28
|
+
{
|
29
|
+
optional bytes tranid = 1;
|
30
|
+
optional ProtoTransactionCoreInfo info = 2;
|
31
|
+
optional ByteArray signature = 3;
|
32
|
+
}
|
33
|
+
|
34
|
+
message ProtoTransactionReceipt
|
35
|
+
{
|
36
|
+
optional bytes receipt = 1;
|
37
|
+
optional uint64 cumgas = 2;
|
38
|
+
}
|
39
|
+
|
40
|
+
message ProtoTransactionWithReceipt
|
41
|
+
{
|
42
|
+
optional ProtoTransaction transaction = 1;
|
43
|
+
optional ProtoTransactionReceipt receipt = 2;
|
44
|
+
}
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# source: message.proto
|
3
|
+
|
4
|
+
require 'google/protobuf'
|
5
|
+
|
6
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
7
|
+
add_file("message.proto", :syntax => :proto2) do
|
8
|
+
add_message "laksa.proto.ByteArray" do
|
9
|
+
required :data, :bytes, 1
|
10
|
+
end
|
11
|
+
add_message "laksa.proto.ProtoTransactionCoreInfo" do
|
12
|
+
optional :version, :uint32, 1
|
13
|
+
optional :nonce, :uint64, 2
|
14
|
+
optional :toaddr, :bytes, 3
|
15
|
+
optional :senderpubkey, :message, 4, "laksa.proto.ByteArray"
|
16
|
+
optional :amount, :message, 5, "laksa.proto.ByteArray"
|
17
|
+
optional :gasprice, :message, 6, "laksa.proto.ByteArray"
|
18
|
+
optional :gaslimit, :uint64, 7
|
19
|
+
optional :code, :bytes, 8
|
20
|
+
optional :data, :bytes, 9
|
21
|
+
end
|
22
|
+
add_message "laksa.proto.ProtoTransaction" do
|
23
|
+
optional :tranid, :bytes, 1
|
24
|
+
optional :info, :message, 2, "laksa.proto.ProtoTransactionCoreInfo"
|
25
|
+
optional :signature, :message, 3, "laksa.proto.ByteArray"
|
26
|
+
end
|
27
|
+
add_message "laksa.proto.ProtoTransactionReceipt" do
|
28
|
+
optional :receipt, :bytes, 1
|
29
|
+
optional :cumgas, :uint64, 2
|
30
|
+
end
|
31
|
+
add_message "laksa.proto.ProtoTransactionWithReceipt" do
|
32
|
+
optional :transaction, :message, 1, "laksa.proto.ProtoTransaction"
|
33
|
+
optional :receipt, :message, 2, "laksa.proto.ProtoTransactionReceipt"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module Zilliqa
|
39
|
+
module Proto
|
40
|
+
ByteArray = Google::Protobuf::DescriptorPool.generated_pool.lookup("laksa.proto.ByteArray").msgclass
|
41
|
+
ProtoTransactionCoreInfo = Google::Protobuf::DescriptorPool.generated_pool.lookup("laksa.proto.ProtoTransactionCoreInfo").msgclass
|
42
|
+
ProtoTransaction = Google::Protobuf::DescriptorPool.generated_pool.lookup("laksa.proto.ProtoTransaction").msgclass
|
43
|
+
ProtoTransactionReceipt = Google::Protobuf::DescriptorPool.generated_pool.lookup("laksa.proto.ProtoTransactionReceipt").msgclass
|
44
|
+
ProtoTransactionWithReceipt = Google::Protobuf::DescriptorPool.generated_pool.lookup("laksa.proto.ProtoTransactionWithReceipt").msgclass
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'bitcoin'
|
2
|
+
|
3
|
+
module Zilliqa
|
4
|
+
module Util
|
5
|
+
class Bech32
|
6
|
+
|
7
|
+
def self.to_bech32(address)
|
8
|
+
raise 'Invalid address format.' unless Validator.address?(address)
|
9
|
+
|
10
|
+
address = address.sub('0x','')
|
11
|
+
|
12
|
+
ret = Bitcoin::Bech32.convert_bits(Util.decode_hex(address).bytes, from_bits: 8, to_bits: 5, pad: false)
|
13
|
+
|
14
|
+
Bitcoin::Bech32.encode('zil', ret);
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.from_bech32(address)
|
18
|
+
data = Bitcoin::Bech32.decode(address)
|
19
|
+
|
20
|
+
raise 'Expected hrp to be zil' unless data[0] == 'zil'
|
21
|
+
|
22
|
+
ret = Bitcoin::Bech32.convert_bits(data[1], from_bits: 5, to_bits: 8, pad: false)
|
23
|
+
|
24
|
+
Zilliqa::Account::Wallet.to_checksum_address(Util.encode_hex(ret.pack('c*'))).sub('0x', '')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Zilliqa
|
2
|
+
module Util
|
3
|
+
class Unit
|
4
|
+
ZIL = 'zil'
|
5
|
+
LI = 'li'
|
6
|
+
QA = 'qa'
|
7
|
+
|
8
|
+
def self.from_qa(qa, unit, is_pack = false)
|
9
|
+
ret = case unit
|
10
|
+
when ZIL
|
11
|
+
qa / 1000000000000.0
|
12
|
+
when LI
|
13
|
+
qa / 1000000.0
|
14
|
+
when QA
|
15
|
+
qa
|
16
|
+
end
|
17
|
+
|
18
|
+
if is_pack
|
19
|
+
ret.round
|
20
|
+
else
|
21
|
+
ret
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.to_qa(qa, unit)
|
26
|
+
case unit
|
27
|
+
when ZIL
|
28
|
+
qa * 1000000000000
|
29
|
+
when LI
|
30
|
+
qa * 1000000
|
31
|
+
when QA
|
32
|
+
qa
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Zilliqa
|
2
|
+
module Util
|
3
|
+
class Validator
|
4
|
+
def self.public_key?(public_key)
|
5
|
+
m = /(0x)?\h{66}/ =~ public_key
|
6
|
+
m != nil
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.private_key?(private_key)
|
10
|
+
m = /(0x)?\h{64}/ =~ private_key
|
11
|
+
m != nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.address?(address)
|
15
|
+
return true if bech32?(address)
|
16
|
+
m = /(0x)?\h{40}/ =~ address
|
17
|
+
m != nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.signature?(signature)
|
21
|
+
m = /(0x)?\h{128}/ =~ signature
|
22
|
+
m != nil
|
23
|
+
end
|
24
|
+
|
25
|
+
# checksum_address?
|
26
|
+
#
|
27
|
+
# takes hex-encoded string and returns boolean if address is checksumed
|
28
|
+
#
|
29
|
+
# @param {string} address
|
30
|
+
# @returns {boolean}
|
31
|
+
def self.checksum_address?(address)
|
32
|
+
self.address?(address) && Zilliqa::Account::Wallet::to_checksum_address(address) == address
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.bech32?(address)
|
36
|
+
m = /^zil1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38}/ =~ address
|
37
|
+
m != nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|