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.
@@ -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