dbchain_client 0.0.1 → 0.7.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 173ee91c28a540f7ff7b59d983f343f0d8cefc6a72e75f781dc520105bbe7ec0
4
- data.tar.gz: b3421de0f7cf1165c838636954fcd81ce29411bd40576dcec00f60812b098005
3
+ metadata.gz: 8bbc767c04668e2a61263dca178a4b957c754859e4319dec14048d230d639c26
4
+ data.tar.gz: 5f5eb7aed816a764524b3c50f0ae74fa54626595eefdfcf6f228bceb238c196e
5
5
  SHA512:
6
- metadata.gz: f0c5d9e16423d59ce77d2e3831527d0bf6ec80a19f89a95eb418f6d39b80c1e62d48b35de74a47976ac949f326624686606176d7ac50791b337ec7f78737c198
7
- data.tar.gz: c2cdae17fea8f1dfa6fde586aa0a2e58a7901de4c32bc6abb6fa45d826365c0ee34f325c29fbc2d3dbfba937d8e536e71da784a67a496913a614aba1b34add7d
6
+ metadata.gz: eadc63b8c3a2a771bcb1845103bf86a35ae9a2af2273f89a1a680f779d95ca2732752957314874be431072c7c10dd0e7ed4f548334091c4b86fb50fd1b270ef2
7
+ data.tar.gz: 5996ecbe45d93d40a9b9beee5fdb1c3f621af2585d70dde2b9fb9bdde8b2748685f2b2b36ac4e0ffc9d20231def53cf656fef982db6543f54f4f3815acd01600
@@ -0,0 +1,49 @@
1
+ require 'base64'
2
+ require 'digest'
3
+ require 'openssl'
4
+
5
+ module DbchainClient
6
+ class AESCrypt
7
+ def initialize
8
+ @cipher = OpenSSL::Cipher.new('AES-256-CBC')
9
+ end
10
+
11
+ def encrypt(password, clear_data)
12
+ iv = generate_iv
13
+
14
+ @cipher.encrypt
15
+ @cipher.key = Digest::SHA256.digest(password)
16
+ @cipher.iv = iv
17
+
18
+ encrypted = @cipher.update(clear_data) + @cipher.final
19
+ encrypted_with_iv = prefix_iv(iv, encrypted)
20
+ Base64.strict_encode64(encrypted_with_iv)
21
+ end
22
+
23
+ def decrypt(password, secret_data_with_iv)
24
+ iv_encrypted = Base64::decode64(secret_data_with_iv)
25
+ iv, encrypted = extract_iv_and_encrypted(iv_encrypted)
26
+
27
+ @cipher.decrypt
28
+ @cipher.key = Digest::SHA256.digest(password)
29
+ @cipher.iv = iv
30
+ @cipher.update(encrypted) + @cipher.final
31
+ end
32
+
33
+ private
34
+
35
+ def prefix_iv(iv, encrypted)
36
+ iv + encrypted
37
+ end
38
+
39
+ def generate_iv
40
+ OpenSSL::Random.random_bytes(16)
41
+ end
42
+
43
+ def extract_iv_and_encrypted(iv_encrypted)
44
+ iv = iv_encrypted[0..15]
45
+ encrypted = iv_encrypted[16..-1]
46
+ [iv, encrypted]
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,72 @@
1
+ #require "bitcoin"
2
+ require "secp256k1"
3
+
4
+ module DbchainClient
5
+ class PublicKey
6
+ def initialize(pub_key) # hex or raw public key
7
+ if pub_key.instance_of?(Secp256k1::PublicKey)
8
+ @public_key = pub_key
9
+ else
10
+ raw_pub_key = Secp256k1::Utils.decode_hex(pub_key)
11
+ @public_key = Secp256k1::PublicKey.new(pubkey: raw_pub_key, raw: true)
12
+ end
13
+ end
14
+
15
+ def public_key_hex
16
+ @public_key.serialize.unpack('H*')[0]
17
+ end
18
+
19
+ def to_raw
20
+ @public_key.serialize
21
+ end
22
+
23
+ def to_s
24
+ public_key_hex
25
+ end
26
+
27
+ def address
28
+ @address ||= Mnemonics.public_key_to_address(public_key_hex)
29
+ end
30
+
31
+ def verify(message, signature)
32
+ raw_sig = signature.raw
33
+ @public_key.ecdsa_verify(message, raw_sig)
34
+ end
35
+ end
36
+
37
+ class PrivateKey
38
+ def initialize(private_key_hex)
39
+ raw_key = Secp256k1::Utils.decode_hex(private_key_hex)
40
+ @private_key = Secp256k1::PrivateKey.new(privkey: raw_key)
41
+ end
42
+
43
+ def public_key
44
+ @public_key ||= PublicKey.new(@private_key.pubkey)
45
+ end
46
+
47
+ def sign(message)
48
+ raw_sig = @private_key.ecdsa_sign(message)
49
+ Signature.new(raw_sig)
50
+ end
51
+ end
52
+
53
+ class Signature
54
+ include Secp256k1::ECDSA
55
+
56
+ def initialize(raw_sig)
57
+ @raw_sig = raw_sig
58
+ end
59
+
60
+ def raw
61
+ @raw_sig
62
+ end
63
+
64
+ def compact
65
+ ecdsa_serialize_compact(@raw_sig)
66
+ end
67
+
68
+ def to_hex
69
+ compact.unpack("H*")
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,97 @@
1
+ require 'singleton'
2
+ require 'securerandom'
3
+ require 'digest'
4
+
5
+ module DbchainClient
6
+ class KeyEscrow
7
+ include Singleton
8
+
9
+ SUFFIX_SECRET = "secret"
10
+ SUFFIX_PRIVATE = "private"
11
+
12
+ def create_and_save_private_key_with_password(username, password, key_store_obj)
13
+ private_key = DbchainClient::Mnemonics.generate_private_key
14
+ save_private_key(username, password, private_key, key_store_obj)
15
+ end
16
+
17
+ def load_private_key_with_password(username, password, key_store_obj)
18
+ load_private_key(username, password, key_store_obj)
19
+ end
20
+
21
+ def save_private_key_with_recovery_phrase(username, recovery_phrase, private_key, key_store_obj)
22
+ save_private_key(username, recovery_phrase, private_key, key_store_obj)
23
+ end
24
+
25
+ def load_private_key_by_recovery_phrase(username, recovery_phrase, key_store_obj)
26
+ load_private_key(username, recovery_phrase, key_store_obj)
27
+ end
28
+
29
+ def reset_password_from_recovery_phrase(username, recovery_phrase, new_password, key_store_obj)
30
+ private_key = load_private_key(username, recovery_phrase, key_store_obj) or raise "Failed to retrieve private key"
31
+ save_private_key(username, new_password, private_key, key_store_obj)
32
+ end
33
+
34
+ def reset_password_from_old(username, old_password, new_password, key_store_obj)
35
+ private_key = load_private_key(username, old_password, key_store_obj) or raise "Failed to retrive private key"
36
+ save_private_key(username, new_password, private_key, key_store_obj)
37
+ end
38
+
39
+ private
40
+
41
+ def save_private_key(username, password_or_recovery_phrase, private_key, key_store_obj)
42
+ seed = random_seed()
43
+ aes = DbchainClient::AESCrypt.new
44
+ encrypted_private_key = aes.encrypt(f1(seed, password_or_recovery_phrase), private_key)
45
+ secret = aes.encrypt(f2(username, password_or_recovery_phrase), seed)
46
+ key_of_secret = hash1(username, password_or_recovery_phrase)
47
+ key_of_private = hash2(username, password_or_recovery_phrase)
48
+ key_store_obj.save(key_of_private, encrypted_private_key) && key_store_obj.save(key_of_secret, secret)
49
+ end
50
+
51
+ def load_private_key(username, password_or_recovery_phrase, key_store_obj)
52
+ key_of_secret = hash1(username, password_or_recovery_phrase)
53
+ key_of_private = hash2(username, password_or_recovery_phrase)
54
+ secret = key_store_obj.load(key_of_secret)
55
+ encrypted_private_key = key_store_obj.load(key_of_private)
56
+
57
+ aes = DbchainClient::AESCrypt.new
58
+ seed = aes.decrypt(f2(username, password_or_recovery_phrase), secret)
59
+ aes.decrypt(f1(seed, password_or_recovery_phrase), encrypted_private_key)
60
+ end
61
+
62
+ def f1(str1, str2)
63
+ Digest::SHA256.digest(str1 + str2)
64
+ end
65
+
66
+ def f2(str1, str2)
67
+ f1(str1, str2)
68
+ end
69
+
70
+ def hash1(str1, str2)
71
+ Digest::SHA256.digest(str1 + str2 + SUFFIX_SECRET)
72
+ end
73
+
74
+ def hash2(str1, str2)
75
+ Digest::SHA256.digest(str1 + str2 + SUFFIX_PRIVATE)
76
+ end
77
+
78
+ def random_seed()
79
+ SecureRandom.random_bytes(32)
80
+ end
81
+ end
82
+
83
+ # This key store is for test purpose. Developers are supposed to implement their own key store for production.
84
+ class KeyStore
85
+ def initialize
86
+ @h = {}
87
+ end
88
+
89
+ def save(key, value)
90
+ @h[key] = value
91
+ end
92
+
93
+ def load(key)
94
+ @h[key]
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,19 @@
1
+ module DbchainClient
2
+ class MessageGenerator
3
+ def initialize(from_address)
4
+ @from_address = from_address
5
+ end
6
+
7
+ def run(message_name, message_hash)
8
+ if message_name == 'MsgSend'
9
+ type = 'cosmos-sdk/MsgSend'
10
+ message_hash[:from_address] = @from_address
11
+ else
12
+ type = "dbchain/#{message_name}"
13
+ message_hash[:owner] = @from_address
14
+ end
15
+
16
+ return { type: type, value: message_hash }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,34 @@
1
+ require "bitcoin"
2
+
3
+ module DbchainClient
4
+ class Mnemonics
5
+ class << self
6
+ def generate_mnemonic(strength_bits = 128)
7
+ Bitcoin::Trezor::Mnemonic.generate(strength_bits)
8
+ end
9
+
10
+ def mnemonic_to_master_key(mnemonic)
11
+ seed = Bitcoin::Trezor::Mnemonic.to_seed(mnemonic)
12
+ Bitcoin::ExtKey.generate_master(seed.htb)
13
+ end
14
+
15
+ def master_key_to_cosmos_key_pair(master_key)
16
+ # m/44'/118'/0'/0/0"
17
+ key = master_key.derive(2**31 + 44).derive(2**31 + 118).derive(2**31).derive(0 ).derive(0)
18
+ [key.priv, key.pub]
19
+ end
20
+
21
+ def generate_private_key
22
+ mnemonic = generate_mnemonic
23
+ master_key = mnemonic_to_master_key(mnemonic)
24
+ master_key_to_cosmos_key_pair(master_key)[0]
25
+ end
26
+
27
+ def public_key_to_address(pub_key)
28
+ hash160 = Bitcoin.hash160(pub_key)
29
+ words = Bitcoin::Bech32.convert_bits(hash160.htb.unpack("C*"), from_bits: 8, to_bits: 5, pad: true)
30
+ Bitcoin::Bech32.encode("cosmos", words)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,44 @@
1
+ require 'json'
2
+ require 'base58'
3
+
4
+ module DbchainClient
5
+ class Reader
6
+ QueryRoot = '/dbchain'
7
+
8
+ def initialize(base_url, private_key, address=nil)
9
+ @rest_lib = DbchainClient::RestLib.new(base_url)
10
+
11
+ if private_key.instance_of? String
12
+ @private_key = PrivateKey.new(private_key)
13
+ else
14
+ @private_key = private_key
15
+ end
16
+
17
+ @public_key = @private_key.public_key
18
+ @from_address = address || @public_key.address
19
+ end
20
+
21
+ def get_row(app_code, table_name, id)
22
+ uri = uri_builder("find", app_code, table_name, id)
23
+ response = @rest_lib.rest_get(uri)
24
+ h = JSON.parse(response.body)
25
+ return h['result']
26
+ end
27
+
28
+ def generate_access_code(time=nil)
29
+ encoded_public_key = Base58.binary_to_base58(@public_key.to_raw, :bitcoin)
30
+ time ||= (Time.now.to_f * 1000).to_i.to_s
31
+ signature = @private_key.sign(time)
32
+ encoded_signature = Base58.binary_to_base58(signature.compact, :bitcoin)
33
+ "#{encoded_public_key}:#{time}:#{encoded_signature}"
34
+ end
35
+
36
+ def uri_builder(*args)
37
+ raise "At least one parameter is needed!" if args.size < 1
38
+ access_token = generate_access_code
39
+ args.insert(1, access_token)
40
+ args.unshift(QueryRoot)
41
+ args.join('/')
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,27 @@
1
+ require 'json'
2
+ require 'base64'
3
+ require 'net/http'
4
+ require_relative 'message_generator'
5
+
6
+ module DbchainClient
7
+ class RestLib
8
+ def initialize(base_url)
9
+ @base_url = base_url
10
+ end
11
+
12
+ def rest_get(url)
13
+ uri = URI(@base_url + url)
14
+ res = Net::HTTP.get_response(uri)
15
+ res
16
+ end
17
+
18
+ def rest_post(url, data)
19
+ uri = URI(@base_url + url)
20
+ req = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
21
+ req.body = data
22
+ http = Net::HTTP.new(uri.host, uri.port)
23
+ res = http.request(req)
24
+ res.body
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,87 @@
1
+ require 'json'
2
+ require 'base64'
3
+ require 'net/http'
4
+ require_relative 'message_generator'
5
+
6
+ module DbchainClient
7
+ class Transaction
8
+ def initialize(base_url, chain_id, private_key_hex)
9
+ @rest_lib = DbchainClient::RestLib.new(base_url)
10
+ @chain_id = chain_id
11
+ @private_key = PrivateKey.new(private_key_hex)
12
+ @from_address = @private_key.public_key.address
13
+ end
14
+
15
+ def sign_and_broadcast(messages, gas: '99999999', memo: '')
16
+ tx = {
17
+ fee: {
18
+ amount: [],
19
+ gas: gas
20
+ },
21
+ memo: memo,
22
+ msg: messages
23
+ }
24
+
25
+ sign_message = make_sign_message(tx, messages)
26
+ signature = @private_key.sign(sign_message)
27
+ signed_tx = {
28
+ signature: Base64.strict_encode64(signature.compact),
29
+ pub_key: {
30
+ type: 'tendermint/PubKeySecp256k1',
31
+ value: Base64.strict_encode64([@private_key.public_key.public_key_hex].pack("H*"))
32
+ }
33
+ }
34
+
35
+ tx[:signatures] = [signed_tx]
36
+ broadcastBody = {
37
+ tx: tx,
38
+ mode: 'async'
39
+ }.to_json
40
+
41
+ response = rest_post("/txs", broadcastBody)
42
+ response#.data.txhash
43
+ end
44
+
45
+ private
46
+
47
+ def make_sign_message(tx, messages)
48
+ account = get_account
49
+ sign_obj = {
50
+ account_number: account["account_number"],
51
+ chain_id: @chain_id,
52
+ fee: tx[:fee],
53
+ memo: tx[:memo],
54
+ msgs: tx[:msg],
55
+ sequence: account["sequence"]
56
+ }
57
+ to_deep_sorted_json(sign_obj)
58
+ end
59
+
60
+ def get_account
61
+ response = rest_get("/auth/accounts/#{@from_address}")
62
+ account = JSON.parse(response.body)
63
+ return account['result']['value']
64
+ end
65
+
66
+ def rest_get(url)
67
+ @rest_lib.rest_get(url)
68
+ end
69
+
70
+ def rest_post(url, data)
71
+ @rest_lib.rest_post(url, data)
72
+ end
73
+
74
+ def to_deep_sorted_json(obj)
75
+ if obj.instance_of?(Array)
76
+ return '[' + obj.map{|o|to_deep_sorted_json(o)}.join(',') + ']'
77
+ end
78
+
79
+ if obj.instance_of?(Hash)
80
+ keys = obj.keys.sort
81
+ return '{' + keys.map{|k| JSON.generate(k) + ':' + to_deep_sorted_json(obj[k])}.join(',') + '}'
82
+ end
83
+
84
+ JSON.generate(obj)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,42 @@
1
+ require 'json'
2
+ require 'base64'
3
+ require 'net/http'
4
+ require_relative 'message_generator'
5
+
6
+ module DbchainClient
7
+ class Writer
8
+ def initialize(base_url, chain_id, private_key_hex, address=nil)
9
+ @transaction = DbchainClient::Transaction.new(base_url, chain_id, private_key_hex)
10
+ from_address = address || PrivateKey.new(private_key_hex).public_key.address
11
+ @message_generator = DbchainClient::MessageGenerator.new(from_address)
12
+ end
13
+
14
+ def send_token(to_address, amount)
15
+ message = generate_message('MsgSend',
16
+ to_address: to_address,
17
+ amount: [{denom: 'dbctoken', amount: amount.to_string}]
18
+ )
19
+ sign_and_broadcast([message])
20
+ end
21
+
22
+ def insert_row(app_code, table_name, fields)
23
+ fields_str = Base64.strict_encode64(fields.to_json)
24
+ message = generate_message('InsertRow',
25
+ app_code: app_code,
26
+ table_name: table_name,
27
+ fields: fields_str
28
+ )
29
+ sign_and_broadcast([message])
30
+ end
31
+
32
+ private
33
+
34
+ def generate_message(message_type, message_data)
35
+ @message_generator.run(message_type, message_data)
36
+ end
37
+
38
+ def sign_and_broadcast(messages)
39
+ @transaction.sign_and_broadcast(messages)
40
+ end
41
+ end
42
+ end
@@ -1,26 +1,13 @@
1
- require "bitcoin"
2
-
3
- module DbchainRubyClient
4
- class Key
5
- def self.generate_mnemonic(strength_bits = 128)
6
- Bitcoin::Trezor::Mnemonic.generate(strength_bits)
7
- end
8
-
9
- def self.mnemonic_to_master_key(mnemonic)
10
- seed = Bitcoin::Trezor::Mnemonic.to_seed(mnemonic)
11
- Bitcoin::ExtKey.generate_master(seed.htb)
12
- end
13
-
14
- def self.master_key_to_cosmos_key_pair(master_key)
15
- # m/44'/118'/0'/0/0"
16
- key = master_key.derive(2**31 + 44).derive(2**31 + 118).derive(2**31).derive(0 ).derive(0)
17
- [key.priv, key.pub]
18
- end
19
-
20
- def self.public_key_to_address(pub_key)
21
- hash160 = Bitcoin.hash160(pub_key)
22
- words = Bitcoin::Bech32.convert_bits(hash160.htb.unpack("C*"), from_bits: 8, to_bits: 5, pad: true)
23
- Bitcoin::Bech32.encode("cosmos", words)
24
- end
25
- end
1
+ module DbchainClient
2
+ autoload :Mnemonics, "dbchain_client/mnemonics"
3
+ autoload :PrivateKey, "dbchain_client/key"
4
+ autoload :PublicKey, "dbchain_client/key"
5
+ autoload :KeyStore, "dbchain_client/key_escrow"
6
+ autoload :MessageGenerator, "dbchain_client/message_generator"
7
+ autoload :RestLib, "dbchain_client/rest_lib"
8
+ autoload :Transaction, "dbchain_client/transaction"
9
+ autoload :Writer, "dbchain_client/writer"
10
+ autoload :Reader, "dbchain_client/reader"
11
+ autoload :KeyEscrow, "dbchain_client/key_escrow"
12
+ autoload :AESCrypt, "dbchain_client/aes"
26
13
  end
metadata CHANGED
@@ -1,23 +1,34 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dbchain_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ethan Zhang
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-01 00:00:00.000000000 Z
11
+ date: 2021-12-16 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: This is the ruby client of dbchain.
13
+ description: This is the ruby client of DBChain. DBChain is a blockchain based relational
14
+ database. Developers can use traditional ways to quickly develop blockchain applications
15
+ with DBChain.
14
16
  email: yzhang.wa@gmail.com
15
17
  executables: []
16
18
  extensions: []
17
19
  extra_rdoc_files: []
18
20
  files:
19
21
  - lib/dbchain_client.rb
20
- homepage: https://rubygems.org/gems/dbchain_client
22
+ - lib/dbchain_client/aes.rb
23
+ - lib/dbchain_client/key.rb
24
+ - lib/dbchain_client/key_escrow.rb
25
+ - lib/dbchain_client/message_generator.rb
26
+ - lib/dbchain_client/mnemonics.rb
27
+ - lib/dbchain_client/reader.rb
28
+ - lib/dbchain_client/rest_lib.rb
29
+ - lib/dbchain_client/transaction.rb
30
+ - lib/dbchain_client/writer.rb
31
+ homepage: https://github.com/dbchaincloud/ruby-client
21
32
  licenses:
22
33
  - MIT
23
34
  metadata: {}
@@ -36,7 +47,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
36
47
  - !ruby/object:Gem::Version
37
48
  version: '0'
38
49
  requirements: []
39
- rubygems_version: 3.0.3.1
50
+ rubygems_version: 3.2.22
40
51
  signing_key:
41
52
  specification_version: 4
42
53
  summary: ruby client of dbchain.