zilliqa 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,146 @@
1
+ require 'digest'
2
+
3
+ module Zilliqa
4
+ module Account
5
+ class Wallet
6
+ MAINNET = 65_537
7
+
8
+ # Takes an array of Account objects and instantiates a Wallet instance.
9
+ def initialize(provider = nil, accounts = {})
10
+ @provider = provider
11
+ @accounts = accounts
12
+ if accounts.length > 0
13
+ @default_account = accounts[0]
14
+ else
15
+ @default_account = nil
16
+ end
17
+ end
18
+
19
+ # Creates a new keypair with a randomly-generated private key. The new
20
+ # account is accessible by address.
21
+ def create
22
+ private_key = Zilliqa::Crypto::KeyTool.generate_private_key
23
+ account = Zilliqa::Account::Account.new(private_key)
24
+
25
+ @accounts[account.address] = account
26
+
27
+ @default_account = account unless @default_account
28
+
29
+ account.address
30
+ end
31
+
32
+ # Adds an account to the wallet by private key.
33
+ def add_by_private_key(private_key)
34
+ account = Zilliqa::Account::Account.new(private_key)
35
+
36
+ @accounts[account.address] = account
37
+
38
+ @default_account = account unless @default_account
39
+
40
+ account.address
41
+ end
42
+
43
+
44
+ # Adds an account by keystore
45
+ def add_by_keystore(keystore, passphrase)
46
+ account = Zilliqa::Account::Account.from_file(keystore, passphrase)
47
+
48
+ @accounts[account.address] = account
49
+
50
+ @default_account = account unless @default_account
51
+
52
+ account.address
53
+ end
54
+
55
+ # Removes an account from the wallet and returns boolean to indicate
56
+ # failure or success.
57
+
58
+ def remove(address)
59
+ if @accounts.has_key?(address)
60
+ @accounts.delete(address)
61
+
62
+ true
63
+ else
64
+ false
65
+ end
66
+ end
67
+
68
+ # Sets the default account of the wallet.
69
+ def set_default(address)
70
+ @default_account = @accounts[address]
71
+ end
72
+
73
+ # to_checksum_address
74
+ #
75
+ # takes hex-encoded string and returns the corresponding address
76
+ #
77
+ # @param {string} address
78
+ # @returns {string}
79
+ def self.to_checksum_address(address)
80
+ return Zilliqa::Util::Bech32.from_bech32(address) if Zilliqa::Util::Validator.bech32?(address)
81
+ address = address.downcase.gsub('0x', '')
82
+
83
+ s1 = Digest::SHA256.hexdigest(Util.decode_hex(address))
84
+ v = s1.to_i(base=16)
85
+
86
+ ret = ['0x']
87
+ address.each_char.each_with_index do |c, idx|
88
+ if '1234567890'.include?(c)
89
+ ret << c
90
+ else
91
+ ret << ((v & (2 ** (255 - 6 * idx))) < 1 ? c.downcase : c.upcase)
92
+ end
93
+ end
94
+
95
+ ret.join
96
+ end
97
+
98
+ def transfer(to_addr, amount)
99
+ gas_price = Integer(@provider.GetMinimumGasPrice)
100
+ gas_limit = 1
101
+
102
+ tx = sign(Zilliqa::Account::Transaction.new({
103
+ version: MAINNET,
104
+ amount: amount.to_s,
105
+ to_addr: to_addr,
106
+ gas_price: gas_price.to_s,
107
+ gas_limit: gas_limit
108
+ }, @provider))
109
+ tx.submit!
110
+ end
111
+
112
+ # signs an unsigned transaction with the default account.
113
+ def sign(tx)
114
+ if tx.sender_pub_key
115
+ # attempt to find the address
116
+ address = Zilliqa::Crypto::KeyTool.get_address_from_public_key(tx.sender_pub_key)
117
+ account = @accounts[address]
118
+ raise 'Could not sign the transaction with address as it does not exist' unless account
119
+
120
+ sign_with(tx, address)
121
+ else
122
+ raise 'This wallet has no default account.' unless @default_account
123
+
124
+ sign_with(tx, @default_account.address)
125
+ end
126
+ end
127
+
128
+ def sign_with(tx, address)
129
+ account = @accounts[address]
130
+ address = @provider.testnet? ? Zilliqa::Util::Bech32.from_bech32(account.address) : account.address
131
+
132
+ raise 'The selected account does not exist on this Wallet instance.' unless account
133
+
134
+ if tx.nonce.nil?
135
+ result = @provider.GetBalance(address)
136
+ tx.nonce = result['nonce'].to_i + 1
137
+ end
138
+
139
+ tx.sender_pub_key = account.public_key
140
+ sig = account.sign_transaction(tx)
141
+ tx.signature = sig.to_s
142
+ tx
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Zilliqa
6
+ module Contract
7
+ class Contract
8
+ include Account
9
+
10
+ NIL_ADDRESS = '0000000000000000000000000000000000000000'
11
+
12
+ attr_reader :factory, :provider, :signer, :code, :abi, :init, :state, :address, :status
13
+
14
+ def initialize(factory, code, abi, address, init, state)
15
+ @factory = factory
16
+ @provider = factory.provider
17
+ @signer = factory.signer
18
+
19
+ @code = code
20
+ @abi = abi
21
+ @init = init
22
+ @state = state
23
+
24
+ if address && !address.empty?
25
+ @address = address
26
+ @status = ContractStatus::DEPLOYED
27
+ else
28
+ @status = ContractStatus::INITIALISED
29
+ end
30
+ end
31
+
32
+ def initialised?
33
+ @status == ContractStatus::INITIALISED
34
+ end
35
+
36
+ def deployed?
37
+ @status == ContractStatus::DEPLOYED
38
+ end
39
+
40
+ def rejected?
41
+ @status == ContractStatus::REJECTED
42
+ end
43
+
44
+ def deploy(deploy_params, attempts = 33, interval = 1000, _to_ds = false)
45
+ raise 'Cannot deploy without code or initialisation parameters.' if @code.nil? || @code == ''
46
+ raise 'Cannot deploy without code or initialisation parameters.' if @init.nil? || @init.length.zero?
47
+
48
+ tx_params = {
49
+ id: deploy_params.id,
50
+ version: deploy_params.version,
51
+ nonce: deploy_params.nonce,
52
+ sender_pub_key: deploy_params.sender_pub_key,
53
+ gas_price: deploy_params.gas_price,
54
+ gas_limit: deploy_params.gas_limit,
55
+ to_addr: NIL_ADDRESS,
56
+ amount: '0',
57
+ code: @code.gsub('/\\', ''),
58
+ data: @init.to_json.gsub('\\"', '"')
59
+ }
60
+
61
+ tx = Transaction.new(tx_params, @provider)
62
+
63
+ tx = prepare_tx(tx, attempts, interval)
64
+
65
+ if tx.rejected?
66
+ @status = ContractStatus::REJECTED
67
+
68
+ return [tx, self]
69
+ end
70
+
71
+ @status = ContractStatus::DEPLOYED
72
+ @address = ContractFactory.get_address_for_contract(tx)
73
+
74
+ [tx, self]
75
+ end
76
+
77
+ def call(transition, args, params, attempts = 33, interval = 1000, to_ds = false)
78
+ data = {
79
+ _tag: transition,
80
+ params: args
81
+ }
82
+
83
+ return 'Contract has not been deployed!' unless @address
84
+
85
+ tx_params = {
86
+ id: params['id'],
87
+ version: params['version'],
88
+ nonce: params['nonce'],
89
+ sender_pub_key: params['sender_pub_key'],
90
+ gas_price: params['gas_price'],
91
+ gas_limit: params['gas_limit'],
92
+ to_addr: @address,
93
+ data: JSON.generate(data)
94
+ }
95
+
96
+ tx = Transaction.new(tx_params, @provider, Zilliqa::Account::Transaction::TX_STATUSES[:initialized], to_ds)
97
+
98
+ prepare_tx(tx, attempts, interval)
99
+ end
100
+
101
+ def state
102
+ return [] unless deployed
103
+
104
+ response = @provider.GetSmartContractState(@address)
105
+ response.result
106
+ end
107
+
108
+ def prepare_tx(tx, attempts, interval)
109
+ tx = @signer.sign(tx)
110
+
111
+ response = @provider.CreateTransaction(tx.to_payload)
112
+
113
+ if response['error']
114
+ tx.status = Zilliqa::Account::Transaction::TX_STATUSES[:rejected]
115
+ else
116
+ tx.confirm(response['result']['TranID'], attempts, interval)
117
+ end
118
+
119
+ tx
120
+ end
121
+ end
122
+
123
+ class ContractStatus
124
+ DEPLOYED = 0
125
+ REJECTED = 1
126
+ INITIALISED = 2
127
+ end
128
+
129
+ class Value
130
+ attr_reader :vname, :type, :value
131
+ def initialize(vname, type, value)
132
+ @vname = vname
133
+ @type = type
134
+ @value = value
135
+ end
136
+ end
137
+
138
+ class DeployParams
139
+ attr_reader :id, :version, :nonce, :gas_price, :gas_limit, :sender_pub_key
140
+ def initialize(id, version, nonce, gas_price, gas_limit, sender_pub_key)
141
+ @id = id
142
+ @version = version
143
+ @nonce = nonce
144
+ @gas_price = gas_price
145
+ @gas_limit = gas_limit
146
+ @sender_pub_key = sender_pub_key
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,47 @@
1
+ require 'digest'
2
+
3
+ module Zilliqa
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 = Zilliqa::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
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 Zilliqa
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