platon 0.2.7
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/.github/workflows/main.yml +18 -0
- data/.gitignore +15 -0
- data/.rspec +3 -0
- data/.rubocop.yml +10 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +69 -0
- data/LICENSE.txt +21 -0
- data/README.md +216 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/doc/zh-cn.md +1762 -0
- data/lib/bech32.rb +82 -0
- data/lib/platon.rb +77 -0
- data/lib/platon/abi.rb +32 -0
- data/lib/platon/address.rb +62 -0
- data/lib/platon/client.rb +175 -0
- data/lib/platon/contract.rb +351 -0
- data/lib/platon/contract_event.rb +24 -0
- data/lib/platon/contract_initializer.rb +54 -0
- data/lib/platon/decoder.rb +99 -0
- data/lib/platon/deployment.rb +49 -0
- data/lib/platon/encoder.rb +120 -0
- data/lib/platon/explorer_url_helper.rb +0 -0
- data/lib/platon/formatter.rb +142 -0
- data/lib/platon/function.rb +36 -0
- data/lib/platon/function_input.rb +13 -0
- data/lib/platon/function_output.rb +14 -0
- data/lib/platon/gas.rb +9 -0
- data/lib/platon/http_client.rb +55 -0
- data/lib/platon/initializer.rb +0 -0
- data/lib/platon/ipc_client.rb +45 -0
- data/lib/platon/key.rb +105 -0
- data/lib/platon/key/decrypter.rb +113 -0
- data/lib/platon/key/encrypter.rb +128 -0
- data/lib/platon/open_ssl.rb +267 -0
- data/lib/platon/ppos.rb +344 -0
- data/lib/platon/railtie.rb +0 -0
- data/lib/platon/secp256k1.rb +7 -0
- data/lib/platon/sedes.rb +40 -0
- data/lib/platon/segwit_addr.rb +66 -0
- data/lib/platon/singleton.rb +39 -0
- data/lib/platon/solidity.rb +40 -0
- data/lib/platon/transaction.rb +41 -0
- data/lib/platon/tx.rb +201 -0
- data/lib/platon/utils.rb +180 -0
- data/lib/platon/version.rb +5 -0
- data/lib/tasks/platon_contract.rake +27 -0
- data/platon-ruby-logo.png +0 -0
- data/platon.gemspec +50 -0
- metadata +235 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
module Platon
|
2
|
+
class Function
|
3
|
+
|
4
|
+
attr_accessor :name, :inputs, :outputs, :signature, :constant, :function_string
|
5
|
+
|
6
|
+
def initialize(data)
|
7
|
+
@name = data["name"]
|
8
|
+
@constant = data["constant"]
|
9
|
+
@inputs = data["inputs"].map do |input|
|
10
|
+
Platon::FunctionInput.new(input)
|
11
|
+
end
|
12
|
+
@outputs = data["outputs"].collect do |output|
|
13
|
+
Platon::FunctionOutput.new(output)
|
14
|
+
end
|
15
|
+
@function_string = self.class.calc_signature(@name, @inputs)
|
16
|
+
@signature = self.class.calc_id(@function_string)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.to_canonical_type(type)
|
20
|
+
type
|
21
|
+
.gsub(/(int)(\z|\D)/, '\1256\2')
|
22
|
+
.gsub(/(uint)(\z|\D)/, '\1256\2')
|
23
|
+
.gsub(/(fixed)(\z|\D)/, '\1128x128\2')
|
24
|
+
.gsub(/(ufixed)(\z|\D)/, '\1128x128\2')
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.calc_signature(name, inputs)
|
28
|
+
"#{name}(#{inputs.collect {|x| self.to_canonical_type(x.type) }.join(",")})"
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.calc_id(signature)
|
32
|
+
Digest::SHA3.hexdigest(signature, 256)[0..7]
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
data/lib/platon/gas.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'json'
|
3
|
+
module Platon
|
4
|
+
class HttpClient < Client
|
5
|
+
attr_accessor :host, :port, :uri, :ssl, :proxy
|
6
|
+
|
7
|
+
def initialize(host,chain_name, proxy = nil, log = false)
|
8
|
+
super(chain_name,log)
|
9
|
+
uri = URI.parse(host)
|
10
|
+
raise ArgumentError unless ['http', 'https'].include? uri.scheme
|
11
|
+
@host = uri.host
|
12
|
+
@port = uri.port
|
13
|
+
@proxy = proxy
|
14
|
+
|
15
|
+
@ssl = uri.scheme == 'https'
|
16
|
+
@uri = URI("#{uri.scheme}://#{@host}:#{@port}#{uri.path}")
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_setting(params) # :host,:proxy, :chain_id, :hrp
|
20
|
+
if params[:host]
|
21
|
+
uri = URI.parse(params[:host])
|
22
|
+
raise ArgumentError unless ['http', 'https'].include? uri.scheme
|
23
|
+
@host = uri.host
|
24
|
+
@port = uri.port
|
25
|
+
end
|
26
|
+
@proxy = proxy if params[:proxy]
|
27
|
+
super(params)
|
28
|
+
end
|
29
|
+
|
30
|
+
def send_single(payload)
|
31
|
+
if @proxy.present?
|
32
|
+
_, p_username, p_password, p_host, p_port = @proxy.gsub(/(:|\/|@)/,' ').squeeze(' ').split
|
33
|
+
http = ::Net::HTTP.new(@host, @port, p_host, p_port, p_username, p_password)
|
34
|
+
else
|
35
|
+
http = ::Net::HTTP.new(@host, @port)
|
36
|
+
end
|
37
|
+
|
38
|
+
if @ssl
|
39
|
+
http.use_ssl = true
|
40
|
+
end
|
41
|
+
header = {'Content-Type' => 'application/json'}
|
42
|
+
request = ::Net::HTTP::Post.new(uri, header)
|
43
|
+
request.body = payload
|
44
|
+
response = http.request(request)
|
45
|
+
response.body
|
46
|
+
end
|
47
|
+
|
48
|
+
def send_batch(batch)
|
49
|
+
result = send_single(batch.to_json)
|
50
|
+
result = JSON.parse(result)
|
51
|
+
result.sort_by! { |c| c['id'] }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
File without changes
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'socket'
|
2
|
+
module Platon
|
3
|
+
class IpcClient < Client
|
4
|
+
attr_accessor :ipcpath
|
5
|
+
|
6
|
+
IPC_PATHS = [
|
7
|
+
"#{ENV['HOME']}/platon-node/data/platon.ipc",
|
8
|
+
"#{ENV['HOME']}/alaya-node/data/platon.ipc"
|
9
|
+
]
|
10
|
+
|
11
|
+
def initialize(ipcpath = nil, log = true)
|
12
|
+
super(nil,log)
|
13
|
+
ipcpath ||= IpcClient.default_path
|
14
|
+
@ipcpath = ipcpath
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.default_path(paths = IPC_PATHS)
|
18
|
+
path = paths.select { |path| File.exist?(path) }.first
|
19
|
+
path || raise("Ipc file not found. Please pass in the file path explicitly to IpcClient initializer")
|
20
|
+
end
|
21
|
+
|
22
|
+
def send_single(payload)
|
23
|
+
socket = UNIXSocket.new(@ipcpath)
|
24
|
+
socket.puts(payload)
|
25
|
+
read = socket.recvmsg(nil)[0]
|
26
|
+
socket.close
|
27
|
+
return read
|
28
|
+
end
|
29
|
+
|
30
|
+
# Note: Guarantees the results are in the same order as defined in batch call.
|
31
|
+
# client.batch do
|
32
|
+
# client.platon_block_number
|
33
|
+
# client.platon_mining
|
34
|
+
# end
|
35
|
+
# => [{"jsonrpc"=>"2.0", "id"=>1, "result"=>"0x26"}, {"jsonrpc"=>"2.0", "id"=>2, "result"=>false}]
|
36
|
+
def send_batch(batch)
|
37
|
+
result = send_single(batch.to_json)
|
38
|
+
result = JSON.parse(result)
|
39
|
+
|
40
|
+
# Make sure the order is the same as it was when batching calls
|
41
|
+
# See 6 Batch here http://www.jsonrpc.org/specification
|
42
|
+
return result.sort_by! { |c| c['id'] }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/platon/key.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
module Platon
|
2
|
+
class Key
|
3
|
+
autoload :Decrypter, 'platon/key/decrypter'
|
4
|
+
autoload :Encrypter, 'platon/key/encrypter'
|
5
|
+
|
6
|
+
attr_reader :private_key, :public_key
|
7
|
+
|
8
|
+
def self.encrypt(key, password)
|
9
|
+
key = new(priv: key) unless key.is_a?(Key)
|
10
|
+
|
11
|
+
Encrypter.perform key.private_hex, password
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.decrypt(data, password)
|
15
|
+
priv = Decrypter.perform data, password
|
16
|
+
new priv: priv
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.encrypt_and_save(key,password,keypath=nil)
|
20
|
+
encrypted_key_info = encrypt(key,password)
|
21
|
+
## PlatON似乎打算兼容以太地址, 暂时默认以 0x 地址命名
|
22
|
+
if keypath==nil || keypath==""
|
23
|
+
address = key.address
|
24
|
+
dirname = "#{ENV['HOME']}/.platon/keystore/"
|
25
|
+
FileUtils.mkdir_p(dirname) unless Dir.exists?(dirname)
|
26
|
+
File.write File.join(dirname, "#{key.address}.json") , encrypted_key_info
|
27
|
+
else
|
28
|
+
File.write keypath , encrypted_key_info
|
29
|
+
end
|
30
|
+
return encrypted_key_info
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.list_wallets(keypath=nil)
|
34
|
+
if keypath==nil || keypath==""
|
35
|
+
Dir.glob("#{ENV['HOME']}/.platon/keystore/*").select { |e| File.file? e }
|
36
|
+
else
|
37
|
+
Dir.glob(keypath + "/*").select { |e| File.file? e }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.personal_recover(message, signature)
|
42
|
+
bin_signature = Utils.hex_to_bin(signature).bytes.rotate(-1).pack('c*')
|
43
|
+
OpenSsl.recover_compact(Utils.keccak256(Utils.prefix_message(message)), bin_signature)
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(priv: nil)
|
47
|
+
@private_key = MoneyTree::PrivateKey.new key: priv
|
48
|
+
@public_key = MoneyTree::PublicKey.new private_key, compressed: false
|
49
|
+
end
|
50
|
+
|
51
|
+
def private_hex
|
52
|
+
private_key.to_hex
|
53
|
+
end
|
54
|
+
|
55
|
+
def public_bytes
|
56
|
+
public_key.to_bytes
|
57
|
+
end
|
58
|
+
|
59
|
+
def public_hex
|
60
|
+
public_key.to_hex
|
61
|
+
end
|
62
|
+
|
63
|
+
def address
|
64
|
+
Utils.public_key_to_address public_hex
|
65
|
+
end
|
66
|
+
|
67
|
+
def bech32_address(hrp:"atp")
|
68
|
+
Utils.to_bech32_address(hrp,address) ##TODO
|
69
|
+
end
|
70
|
+
alias_method :to_address, :address
|
71
|
+
|
72
|
+
def sign(message)
|
73
|
+
sign_hash message_hash(message)
|
74
|
+
end
|
75
|
+
|
76
|
+
def sign_hash(hash)
|
77
|
+
loop do
|
78
|
+
signature = OpenSsl.sign_compact hash, private_hex, public_hex
|
79
|
+
return signature if valid_s? signature
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def verify_signature(message, signature)
|
84
|
+
hash = message_hash(message)
|
85
|
+
public_hex == OpenSsl.recover_compact(hash, signature)
|
86
|
+
end
|
87
|
+
|
88
|
+
def personal_sign(message)
|
89
|
+
Utils.bin_to_hex(sign(Utils.prefix_message(message)).bytes.rotate(1).pack('c*'))
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def message_hash(message)
|
96
|
+
Utils.keccak256 message
|
97
|
+
end
|
98
|
+
|
99
|
+
def valid_s?(signature)
|
100
|
+
s_value = Utils.v_r_s_for(signature).last
|
101
|
+
s_value <= Secp256k1::N/2 && s_value != 0
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'scrypt'
|
3
|
+
|
4
|
+
class Platon::Key::Decrypter
|
5
|
+
include Platon::Utils
|
6
|
+
|
7
|
+
def self.perform(data, password)
|
8
|
+
new(data, password).perform
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(data, password)
|
12
|
+
@data = JSON.parse(data)
|
13
|
+
@password = password
|
14
|
+
end
|
15
|
+
|
16
|
+
def perform
|
17
|
+
derive_key password
|
18
|
+
check_macs
|
19
|
+
bin_to_hex decrypted_data
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :data, :key, :password
|
26
|
+
|
27
|
+
def derive_key(password)
|
28
|
+
case kdf
|
29
|
+
when 'pbkdf2'
|
30
|
+
@key = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, key_length, digest)
|
31
|
+
when 'scrypt'
|
32
|
+
# OpenSSL 1.1 inclues OpenSSL::KDF.scrypt, but it is not available usually, otherwise we could do: OpenSSL::KDF.scrypt(password, salt: salt, N: n, r: r, p: p, length: key_length)
|
33
|
+
@key = SCrypt::Engine.scrypt(password, salt, n, r, p, key_length)
|
34
|
+
else
|
35
|
+
raise "Unsupported key derivation function: #{kdf}!"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def check_macs
|
40
|
+
mac1 = keccak256(key[(key_length/2), key_length] + ciphertext)
|
41
|
+
mac2 = hex_to_bin crypto_data['mac']
|
42
|
+
|
43
|
+
if mac1 != mac2
|
44
|
+
raise "Message Authentications Codes do not match!"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def decrypted_data
|
49
|
+
@decrypted_data ||= cipher.update(ciphertext) + cipher.final
|
50
|
+
end
|
51
|
+
|
52
|
+
def crypto_data
|
53
|
+
@crypto_data ||= data['crypto'] || data['Crypto']
|
54
|
+
end
|
55
|
+
|
56
|
+
def ciphertext
|
57
|
+
hex_to_bin crypto_data['ciphertext']
|
58
|
+
end
|
59
|
+
|
60
|
+
def cipher_name
|
61
|
+
"aes-128-ctr"
|
62
|
+
end
|
63
|
+
|
64
|
+
def cipher
|
65
|
+
@cipher ||= OpenSSL::Cipher.new(cipher_name).tap do |cipher|
|
66
|
+
cipher.decrypt
|
67
|
+
cipher.key = key[0, (key_length/2)]
|
68
|
+
cipher.iv = iv
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def iv
|
73
|
+
hex_to_bin crypto_data['cipherparams']['iv']
|
74
|
+
end
|
75
|
+
|
76
|
+
def salt
|
77
|
+
hex_to_bin crypto_data['kdfparams']['salt']
|
78
|
+
end
|
79
|
+
|
80
|
+
def iterations
|
81
|
+
crypto_data['kdfparams']['c'].to_i
|
82
|
+
end
|
83
|
+
|
84
|
+
def kdf
|
85
|
+
crypto_data['kdf']
|
86
|
+
end
|
87
|
+
|
88
|
+
def key_length
|
89
|
+
crypto_data['kdfparams']['dklen'].to_i
|
90
|
+
end
|
91
|
+
|
92
|
+
def n
|
93
|
+
crypto_data['kdfparams']['n'].to_i
|
94
|
+
end
|
95
|
+
|
96
|
+
def r
|
97
|
+
crypto_data['kdfparams']['r'].to_i
|
98
|
+
end
|
99
|
+
|
100
|
+
def p
|
101
|
+
crypto_data['kdfparams']['p'].to_i
|
102
|
+
end
|
103
|
+
|
104
|
+
def digest
|
105
|
+
OpenSSL::Digest.new digest_name
|
106
|
+
end
|
107
|
+
|
108
|
+
def digest_name
|
109
|
+
"sha256"
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
class Platon::Key::Encrypter
|
5
|
+
include Platon::Utils
|
6
|
+
|
7
|
+
def self.perform(key, password, options = {})
|
8
|
+
new(key, options).perform(password)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(key, options = {})
|
12
|
+
@key = key
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def perform(password)
|
17
|
+
derive_key password
|
18
|
+
encrypt
|
19
|
+
|
20
|
+
data.to_json
|
21
|
+
end
|
22
|
+
|
23
|
+
def data
|
24
|
+
{
|
25
|
+
crypto: {
|
26
|
+
cipher: cipher_name,
|
27
|
+
cipherparams: {
|
28
|
+
iv: bin_to_hex(iv),
|
29
|
+
},
|
30
|
+
ciphertext: bin_to_hex(encrypted_key),
|
31
|
+
kdf: "pbkdf2",
|
32
|
+
kdfparams: {
|
33
|
+
c: iterations,
|
34
|
+
dklen: 32,
|
35
|
+
prf: prf,
|
36
|
+
salt: bin_to_hex(salt),
|
37
|
+
},
|
38
|
+
mac: bin_to_hex(mac),
|
39
|
+
},
|
40
|
+
id: id,
|
41
|
+
version: 3,
|
42
|
+
}.tap do |data|
|
43
|
+
data[:address] = address unless options[:skip_address]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def id
|
48
|
+
@id ||= options[:id] || SecureRandom.uuid
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
attr_reader :derived_key, :encrypted_key, :key, :options
|
55
|
+
|
56
|
+
def cipher
|
57
|
+
@cipher ||= OpenSSL::Cipher.new(cipher_name).tap do |cipher|
|
58
|
+
cipher.encrypt
|
59
|
+
cipher.iv = iv
|
60
|
+
cipher.key = derived_key[0, (key_length/2)]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def digest
|
65
|
+
@digest ||= OpenSSL::Digest.new digest_name
|
66
|
+
end
|
67
|
+
|
68
|
+
def derive_key(password)
|
69
|
+
@derived_key = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, key_length, digest)
|
70
|
+
end
|
71
|
+
|
72
|
+
def encrypt
|
73
|
+
@encrypted_key = cipher.update(hex_to_bin key) + cipher.final
|
74
|
+
end
|
75
|
+
|
76
|
+
def mac
|
77
|
+
keccak256(derived_key[(key_length/2), key_length] + encrypted_key)
|
78
|
+
end
|
79
|
+
|
80
|
+
def cipher_name
|
81
|
+
"aes-128-ctr"
|
82
|
+
end
|
83
|
+
|
84
|
+
def digest_name
|
85
|
+
"sha256"
|
86
|
+
end
|
87
|
+
|
88
|
+
def prf
|
89
|
+
"hmac-#{digest_name}"
|
90
|
+
end
|
91
|
+
|
92
|
+
def key_length
|
93
|
+
32
|
94
|
+
end
|
95
|
+
|
96
|
+
def salt_length
|
97
|
+
32
|
98
|
+
end
|
99
|
+
|
100
|
+
def iv_length
|
101
|
+
16
|
102
|
+
end
|
103
|
+
|
104
|
+
def iterations
|
105
|
+
options[:iterations] || 262_144
|
106
|
+
end
|
107
|
+
|
108
|
+
def salt
|
109
|
+
@salt ||= if options[:salt]
|
110
|
+
hex_to_bin options[:salt]
|
111
|
+
else
|
112
|
+
SecureRandom.random_bytes(salt_length)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def iv
|
117
|
+
@iv ||= if options[:iv]
|
118
|
+
hex_to_bin options[:iv]
|
119
|
+
else
|
120
|
+
SecureRandom.random_bytes(iv_length)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def address
|
125
|
+
Platon::Key.new(priv: key).address
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|