laksa 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,148 @@
1
+ require 'json'
2
+
3
+ module Laksa
4
+ module Contract
5
+ class Contract
6
+ include Account
7
+
8
+ NIL_ADDRESS = "0000000000000000000000000000000000000000";
9
+
10
+ attr_reader :factory, :provider, :signer, :code, :abi, :init, :state, :address, :status
11
+
12
+ def initialize(factory, code, abi, address, init, state)
13
+ @factory = factory
14
+ @provider = factory.provider
15
+ @signer = factory.signer
16
+
17
+ @code = code
18
+ @abi = abi
19
+ @init = init
20
+ @state = state
21
+
22
+ if address && !address.empty?
23
+ @address = address
24
+ @status = ContractStatus::DEPLOYED
25
+ else
26
+ @status = ContractStatus::INITIALISED
27
+ end
28
+ end
29
+
30
+ def initialised?
31
+ return @status === ContractStatus::INITIALISED
32
+ end
33
+
34
+ def deployed?
35
+ return @status === ContractStatus::DEPLOYED
36
+ end
37
+
38
+ def rejected?
39
+ return @status === ContractStatus::REJECTED
40
+ end
41
+
42
+ def deploy(deploy_params, attempts = 33, interval = 1000, to_ds = false)
43
+ raise 'Cannot deploy without code or initialisation parameters.' if @code == nil || @code == ''
44
+ raise 'Cannot deploy without code or initialisation parameters.' if @init == nil || @init.length == 0
45
+
46
+ tx_params = TxParams.new
47
+ tx_params.id = deploy_params.id
48
+ tx_params.version = deploy_params.version
49
+ tx_params.nonce = deploy_params.nonce
50
+ tx_params.sender_pub_key = deploy_params.sender_pub_key
51
+ tx_params.gas_price = deploy_params.gas_price
52
+ tx_params.gas_limit = deploy_params.gas_limit
53
+
54
+ tx_params.to_addr = NIL_ADDRESS
55
+ tx_params.amount = '0'
56
+ tx_params.code = @code.gsub("/\\", "")
57
+ tx_params.data = @init.to_json.gsub('\\"', '"')
58
+
59
+ tx = Transaction.new(tx_params, @provider)
60
+
61
+ tx = self.prepare_tx(tx, attempts, interval);
62
+
63
+ if tx.rejected?
64
+ @status = ContractStatus::REJECTED
65
+
66
+ return [tx, self]
67
+ end
68
+
69
+ @status = ContractStatus::DEPLOYED
70
+ @address = ContractFactory.get_address_for_contract(tx)
71
+
72
+ [tx, self]
73
+ end
74
+
75
+ def call(transition, args, params, attempts = 33, interval = 1000, to_ds = false)
76
+ data = {
77
+ _tag: transition,
78
+ params: args,
79
+ };
80
+
81
+ return 'Contract has not been deployed!' unless @address
82
+
83
+ tx_params = TxParams.new
84
+ tx_params.id = params['id'] if params.has_key?('id')
85
+ tx_params.version = params['version'] if params.has_key?('version')
86
+ tx_params.nonce = params['nonce'] if params.has_key?('nonce')
87
+ tx_params.sender_pub_key = params['sender_pub_key'] if params.has_key?('sender_pub_key')
88
+ tx_params.gas_price = params['gas_price'] if params.has_key?('gas_price')
89
+ tx_params.gas_limit = params['gas_limit'] if params.has_key?('gas_limit')
90
+
91
+ tx_params.to_addr = @address
92
+ tx_params.data = JSON.generate(data)
93
+
94
+ tx = Transaction.new(tx_params, @provider, TxStatus::INITIALIZED, to_ds)
95
+
96
+ tx = self.prepare_tx(tx, attempts, interval)
97
+ end
98
+
99
+ def state
100
+ return [] unless self.deployed
101
+
102
+ response = @provider.GetSmartContractState(@address)
103
+ return response.result
104
+ end
105
+
106
+ def prepare_tx(tx, attempts, interval)
107
+ tx = @signer.sign(tx);
108
+
109
+ response = @provider.CreateTransaction(tx.to_payload)
110
+
111
+ if response['error']
112
+ tx.status = TxStatus::REJECTED
113
+ else
114
+ tx.confirm(response['result']['TranID'], attempts, interval)
115
+ end
116
+
117
+ tx
118
+ end
119
+ end
120
+
121
+ class ContractStatus
122
+ DEPLOYED = 0
123
+ REJECTED = 1
124
+ INITIALISED = 2
125
+ end
126
+
127
+ class Value
128
+ attr_reader :vname, :type, :value
129
+ def initialize(vname, type, value)
130
+ @vname = vname
131
+ @type = type
132
+ @value = value
133
+ end
134
+ end
135
+
136
+ class DeployParams
137
+ attr_reader :id, :version, :nonce, :gas_price, :gas_limit, :sender_pub_key
138
+ def initialize(id, version, nonce, gas_price, gas_limit, sender_pub_key)
139
+ @id = id
140
+ @version = version
141
+ @nonce = nonce
142
+ @gas_price = gas_price
143
+ @gas_limit = gas_limit
144
+ @sender_pub_key = sender_pub_key
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,47 @@
1
+ require 'digest'
2
+
3
+ module Laksa
4
+ module Contract
5
+ # ContractFactory
6
+ #
7
+ # individual `Contract` instances are instead obtained by
8
+ # calling `ContractFactory.at` (for an already-deployed contract) and
9
+ # `ContractFactory.new` (to deploy a new contract).
10
+ class ContractFactory
11
+
12
+ attr_reader :provider, :signer
13
+
14
+ def initialize(provider, signer)
15
+ @provider = provider
16
+ @signer = signer
17
+ end
18
+
19
+ def self.get_address_for_contract(tx)
20
+ sha256 = Digest::SHA256.new
21
+
22
+ sender_address = Laksa::Crypto::KeyTool.get_address_from_public_key(tx.sender_pub_key)
23
+
24
+ sha256 << Util.decode_hex(sender_address)
25
+
26
+ nonce = 0;
27
+ if tx.nonce && !tx.nonce.empty?
28
+ nonce = tx.nonce.to_i - 1
29
+ end
30
+
31
+ nonce_hex = [nonce].pack('Q>*')
32
+
33
+ sha256 << nonce_hex
34
+
35
+ sha256.hexdigest[24..-1]
36
+ end
37
+
38
+ def new_contract(code, init, abi)
39
+ Contract.new(self, code, abi, nil, init, nil)
40
+ end
41
+
42
+ def at_contract(address, code, init, abi)
43
+ Contract.new(self, code, abi, address, init, nil)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,113 @@
1
+ require 'pbkdf2'
2
+ require 'scrypt'
3
+ require 'openssl'
4
+ require 'digest'
5
+ require 'json'
6
+
7
+ module Laksa
8
+ module Crypto
9
+ class KeyStore
10
+ T_PBKDF2 = 'pbkdf2'
11
+ T_SCRYPT = 'scrypt'
12
+
13
+ def initialize
14
+ end
15
+
16
+ # encryptPrivateKey
17
+ #
18
+ # Encodes and encrypts an account in the format specified by
19
+ # https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition.
20
+ # However, note that, in keeping with the hash function used by Zilliqa's
21
+ # core protocol, the MAC is generated using sha256 instead of keccak.
22
+ #
23
+ # NOTE: only scrypt and pbkdf2 are supported.
24
+ #
25
+ # @param {string} private_key - hex-encoded private key
26
+ # @param {string} password - a password used for encryption
27
+ # @param {KDF} kdf_type - the key derivation function to be used
28
+ def encrypt_private_key(private_key, password, kdf_type)
29
+ address = KeyTool.get_address_from_private_key(private_key)
30
+
31
+ iv = KeyTool.generate_random_bytes(16)
32
+ salt = KeyTool.generate_random_bytes(32)
33
+
34
+ case kdf_type
35
+ when T_PBKDF2
36
+ derived_key = PBKDF2.new(password: password, salt: salt, key_length: 32, iterations: 262144).value
37
+ when T_SCRYPT
38
+ derived_key = SCrypt::Engine.scrypt(password, salt, 8192, 8, 1, 32)
39
+ end
40
+
41
+ encrypt_key = derived_key[0..15]
42
+
43
+ cipher = OpenSSL::Cipher.new('aes-128-ctr')
44
+ cipher.encrypt
45
+ cipher.iv = iv
46
+ cipher.key = encrypt_key
47
+ cipher.padding = 0
48
+
49
+ ciphertext = cipher.update(Util.decode_hex(private_key)) + cipher.final
50
+
51
+ mac = generate_mac(derived_key, ciphertext)
52
+
53
+ datas = {address: address,
54
+ crypto: {
55
+ cipher: 'aes-128-ctr',
56
+ cipherparams: {'iv': Util.encode_hex(iv)},
57
+ ciphertext: Util.encode_hex(ciphertext),
58
+ kdf: kdf_type,
59
+ kdfparams: {n: 8192, c:262144, r:8, p:1, dklen: 32, salt: salt.bytes},
60
+ mac: mac
61
+ },
62
+ id: SecureRandom.uuid,
63
+ version: 3
64
+ }
65
+
66
+ datas.to_json
67
+ end
68
+
69
+ # decrypt_private_key
70
+ #
71
+ # Recovers the private key from a keystore file using the given passphrase.
72
+ #
73
+ # @param {KeystoreV3} encrypt_json
74
+ # @param {string} password
75
+ def decrypt_private_key(encrypt_json, password)
76
+ datas = JSON.parse(encrypt_json)
77
+
78
+ ciphertext = Util.decode_hex(datas['crypto']['ciphertext'])
79
+ iv = Util.decode_hex(datas['crypto']['cipherparams']['iv'])
80
+ kdfparams = datas['crypto']['kdfparams']
81
+ kdf_type = datas['crypto']['kdf']
82
+
83
+ case kdf_type
84
+ when T_PBKDF2
85
+ derived_key = PBKDF2.new(password: password, salt: kdfparams['salt'].pack('c*'), key_length: kdfparams['dklen'], iterations: kdfparams['c']).value
86
+ when T_SCRYPT
87
+ derived_key = SCrypt::Engine.scrypt(password, kdfparams['salt'].pack('c*'), kdfparams['n'], kdfparams['r'], kdfparams['p'], kdfparams['dklen'])
88
+ end
89
+
90
+ encrypt_key = derived_key[0..15]
91
+
92
+ mac = generate_mac(derived_key, ciphertext)
93
+
94
+ raise 'Failed to decrypt.' if mac.casecmp(datas['crypto']['mac']) != 0
95
+
96
+ cipher = OpenSSL::Cipher.new(datas['crypto']['cipher'])
97
+ cipher.decrypt
98
+ cipher.iv = iv
99
+ cipher.key = encrypt_key
100
+ cipher.padding = 0
101
+
102
+ private_key = cipher.update(ciphertext) + cipher.final
103
+
104
+ return Util.encode_hex private_key
105
+ end
106
+
107
+ private
108
+ def generate_mac(derived_key, ciphertext)
109
+ Digest::SHA256.hexdigest(derived_key[16..32] + ciphertext)
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,61 @@
1
+ require 'secp256k1'
2
+ require 'digest'
3
+
4
+ module Laksa
5
+ module Crypto
6
+ class KeyTool
7
+ include Secp256k1
8
+ def initialize(private_key)
9
+ is_raw = private_key.length == 32 ? true : false
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 ? true : false
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
+ orig_address[24..-1].downcase
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,146 @@
1
+ require 'secp256k1'
2
+ require 'digest'
3
+ require 'openssl'
4
+
5
+ module Laksa
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
+ while !sig
23
+ k = Util.encode_hex SecureRandom.random_bytes(32)
24
+ k_bn = OpenSSL::BN.new(k, 16)
25
+
26
+ sig = self.try_sign(message, private_key, k_bn, public_key)
27
+ end
28
+
29
+ sig
30
+ end
31
+
32
+ # trySign
33
+ #
34
+ # @param {String} message - the message to sign over
35
+ # @param {String} privateKey - the private key
36
+ # @param {BN} k_bn - output of the HMAC-DRBG
37
+ #
38
+ # @returns {Signature | null =>}
39
+ def self.try_sign(message, private_key, k_bn, public_key)
40
+ group = OpenSSL::PKey::EC::Group.new('secp256k1')
41
+
42
+ prikey_bn = OpenSSL::BN.new(private_key, 16)
43
+
44
+ pubkey_bn = OpenSSL::BN.new(public_key, 16)
45
+ pubkey_point = OpenSSL::PKey::EC::Point.new(group, pubkey_bn)
46
+
47
+ throw 'Bad private key.' if prikey_bn.zero? || prikey_bn >= N
48
+
49
+ # 1a. check that k is not 0
50
+ return nil if k_bn.zero?
51
+
52
+ # 1b. check that k is < the order of the group
53
+ return nil if k_bn >= N
54
+
55
+ # 2. Compute commitment Q = kG, where g is the base point
56
+ q_point = pubkey_point.mul(0, k_bn)
57
+
58
+ # 3. Compute the challenge r = H(Q || pubKey || msg)
59
+ # mod reduce the r value by the order of secp256k1, n
60
+ r_bn = hash(q_point, pubkey_point, message) % N
61
+
62
+ return nil if r_bn.zero?
63
+
64
+ # 4. Compute s = k - r * prv
65
+ # 4a. Compute r * prv
66
+ s_bn = r_bn * prikey_bn % N
67
+ # 4b. Compute s = k - r * prv mod n
68
+ s_bn = k_bn.mod_sub(s_bn, N)
69
+
70
+ return nil if s_bn.zero?
71
+
72
+ Signature.new(r_bn.to_s(16), s_bn.to_s(16))
73
+ end
74
+
75
+
76
+ # Verify signature.
77
+ #
78
+ # @param {Buffer} message
79
+ # @param {Buffer} sig
80
+ # @param {Buffer} public_key
81
+ #
82
+ # @returns {boolean}
83
+ #
84
+ # 1. Check if r,s is in [1, ..., order-1]
85
+ # 2. Compute Q = sG + r*kpub
86
+ # 3. If Q = O (the neutral point), return 0;
87
+ # 4. r' = H(Q, kpub, m)
88
+ # 5. return r' == r
89
+ def self.verify(message, sig, public_key)
90
+ pubkey = PublicKey.new
91
+ pubkey.deserialize Util.decode_hex(public_key)
92
+
93
+ r = sig.r
94
+ r_bn = OpenSSL::BN.new(r, 16)
95
+
96
+ s = sig.s
97
+ s_bn = OpenSSL::BN.new(s, 16)
98
+
99
+ throw 'Invalid signature' if (s_bn.zero? || r_bn.zero?)
100
+
101
+ throw 'Invalid signature' if (s_bn.negative? || r_bn.negative?)
102
+
103
+ throw 'Invalid signature' if (s_bn >= N || r_bn >= N)
104
+
105
+ group = OpenSSL::PKey::EC::Group.new('secp256k1')
106
+ pubkey_bn = OpenSSL::BN.new(public_key, 16)
107
+ pubkey_point = OpenSSL::PKey::EC::Point.new(group, pubkey_bn)
108
+
109
+ throw 'Invalid public key' unless pubkey_point.on_curve?
110
+
111
+ q_point = pubkey_point.mul(r_bn, s_bn)
112
+
113
+ throw 'Invalid intermediate point.' if q_point.infinity?
114
+
115
+ h_bn = self.hash(q_point, pubkey_point, message) % N
116
+
117
+ throw 'Invalid hash.' if (h_bn.zero?)
118
+
119
+ h_bn.eql?(r_bn)
120
+ end
121
+
122
+
123
+ # Hash (r | M).
124
+ def self.hash(q_point, pubkey_point, message)
125
+ sha256 = Digest::SHA256.new
126
+ sha256 << q_point.to_octet_string(:compressed)
127
+ sha256 << pubkey_point.to_octet_string(:compressed)
128
+ sha256 << Util.decode_hex(message)
129
+
130
+ OpenSSL::BN.new(sha256.hexdigest, 16)
131
+ end
132
+ end
133
+
134
+ class Signature
135
+ attr_reader :r, :s
136
+ def initialize(r, s)
137
+ @r = r
138
+ @s = s
139
+ end
140
+
141
+ def to_s
142
+ "#{@r}#{@s}"
143
+ end
144
+ end
145
+ end
146
+ end