Module: QuorumSdk::Utils

Defined in:
lib/quorum_sdk/utils.rb

Overview

Wrapper for some useful methods

Constant Summary collapse

TRX_VERSION =
'2.0.0'
ARGUMENTS_FOR_ENCRYPT_TRX =
%i[private_key group_id cipher_key data].freeze

Class Method Summary collapse

Class Method Details

.aes_decrypt(cipher, key:) ⇒ Object



155
156
157
158
159
160
161
# File 'lib/quorum_sdk/utils.rb', line 155

def aes_decrypt(cipher, key:)
  decipher = OpenSSL::Cipher.new('aes-256-gcm').decrypt
  decipher.key = [key].pack('H*')
  decipher.iv = cipher[...12]
  decipher.auth_tag = cipher[-16...]
  decipher.update(cipher[12...-16]) + decipher.final
end

.aes_encrypt(data, key:) ⇒ Object



142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/quorum_sdk/utils.rb', line 142

def aes_encrypt(data, key:)
  encipher = OpenSSL::Cipher.new('aes-256-gcm').encrypt
  encipher.key = [key].pack('H*')
  iv = encipher.random_iv
  encipher.iv = iv
  encrypted = encipher.update(data) + encipher.final
  [
    iv,
    encrypted,
    encipher.auth_tag
  ].join
end

.build_trx(**kwargs) ⇒ Object

Raises:



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/quorum_sdk/utils.rb', line 53

def build_trx(**kwargs)
  raise ArgumentError, "Keyword arguments #{ARGUMENTS_FOR_ENCRYPT_TRX} must be provided" unless ARGUMENTS_FOR_ENCRYPT_TRX.all?(&->(arg) { arg.in? kwargs.keys })

  data =
    case kwargs[:data]
    when Hash
      kwargs[:data].to_json
    else
      kwargs[:data].to_s
    end
  encrypted_data = aes_encrypt(data, key: kwargs[:cipher_key])

   = QuorumSdk::Account.new priv: kwargs[:private_key]

  trx =
    Quorum::Pb::Trx.new(
      TrxId: (kwargs[:trx_id] || SecureRandom.uuid),
      GroupId: kwargs[:group_id],
      Data: encrypted_data,
      TimeStamp: (kwargs[:timestamp].presence || (Time.now.to_f * 1e9)).to_i,
      Version: (kwargs[:version] || TRX_VERSION),
      SenderPubkey: Base64.urlsafe_encode64(.public_bytes_compressed, padding: false)
    )

  hash = Digest::SHA256.hexdigest trx.to_proto
  signature = .sign [hash].pack('H*')
  trx.SenderSign = [signature].pack('H*')

  json = Quorum::Pb::Trx.encode_json trx
  JSON.parse(json).deep_transform_keys! { |key| key.to_s.underscore.to_sym }
end

.decrypt_trx(cipher, key:) ⇒ Object

Raises:



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/quorum_sdk/utils.rb', line 111

def decrypt_trx(cipher, key:)
  cipher = Base64.strict_decode64 cipher
  trx_json = JSON.parse aes_decrypt(cipher, key:)
  trx_bytes = Base64.strict_decode64 trx_json['TrxBytes']
  trx = Quorum::Pb::Trx.decode trx_bytes

  trx_without_sig = trx.dup
  trx_without_sig.clear_SenderSign
  hash = Digest::SHA256.hexdigest Quorum::Pb::Trx.encode(trx_without_sig)

  signature = trx.SenderSign.unpack1('H*')
  public_key = Secp256k1::PublicKey.from_data(Base64.urlsafe_decode64(trx.SenderPubkey)).uncompressed.unpack1('H*')
  recover_key = Eth::Signature.recover [hash].pack('H*'), signature
  raise QuorumSdk::Error, "Signature not verified: #{public_key} != #{recover_key}" unless public_key == recover_key

  data = decrypt_trx_data(trx.Data, key:)

  trx = JSON.parse Quorum::Pb::Trx.encode_json(trx)
  trx['Data'] = data
  trx.with_indifferent_access
end

.decrypt_trx_data(cipher, key:) ⇒ Object



133
134
135
136
137
138
139
140
# File 'lib/quorum_sdk/utils.rb', line 133

def decrypt_trx_data(cipher, key:)
  decrypted_data = aes_decrypt(cipher, key:)
  begin
    JSON.parse(decrypted_data).with_indifferent_access
  rescue JSON::ParserError
    decrypted_data
  end
end

.parse_seed(url) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/quorum_sdk/utils.rb', line 9

def parse_seed(url)
  url = Addressable::URI.parse(url.gsub(/\\u([a-f0-9]{4})/i) { [::Regexp.last_match(1).hex].pack('U') })
  query_values = url.query_values

  group_name = query_values['a']
  group_id = uuid_from_base64 query_values['g']
  block_id = uuid_from_base64 query_values['b']
  signature = Base64.strict_encode64 Base64.urlsafe_decode64(query_values['s'])
  owner_pubkey = query_values['k']
  cipher_key = Base64.urlsafe_decode64(query_values['c']).unpack1('H*')
  app_key = query_values['y']
  consensus_type = query_values['n'].to_s == '1' ? 'pos' : 'poa'
  encryption_type = query_values['e'].to_s == '0' ? 'public' : 'private'
  chain_url = query_values['u'].split('|')

  {
    group_id:,
    group_name:,
    block_id:,
    signature:,
    owner_pubkey:,
    cipher_key:,
    app_key:,
    consensus_type:,
    encryption_type:,
    chain_url:
  }
end

.uuid_from_base64(str) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/quorum_sdk/utils.rb', line 38

def uuid_from_base64(str)
  return if str.blank?

  hex = Base64.urlsafe_decode64(str).unpack1('H*')
  format(
    '%<first>s-%<second>s-%<third>s-%<forth>s-%<fifth>s',
    first: hex[0..7],
    second: hex[8..11],
    third: hex[12..15],
    forth: hex[16..19],
    fifth: hex[20..]
  )
end

.verify_trx(trx = nil, **kwargs) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/quorum_sdk/utils.rb', line 85

def verify_trx(trx = nil, **kwargs)
  trx ||= kwargs
  trx.deep_transform_keys! { |key| key.to_s.camelize.to_sym }
  trx = trx.with_indifferent_access
  trx['TimeStamp'] = trx['TimeStamp'].to_i if trx['TimeStamp'].present? && trx['TimeStamp'].is_a?(String)
  trx['Expired'] = trx['Expired'].to_i if trx['Expired'].present? && trx['Expired'].is_a?(String)

  signature = Base64.urlsafe_decode64(trx['SenderSign']).unpack1('H*')

  trx = Quorum::Pb::Trx.new(
    TrxId: trx['TrxId'],
    GroupId: trx['GroupId'],
    Data: Base64.decode64(trx['Data']),
    TimeStamp: trx['TimeStamp'],
    Expired: trx['Expired'],
    Version: trx['Version'],
    SenderPubkey: trx['SenderPubkey']
  )

  hash = Digest::SHA256.hexdigest trx.to_proto
  public_key = Secp256k1::PublicKey.from_data(Base64.urlsafe_decode64(trx.SenderPubkey)).uncompressed.unpack1('H*')
  recover_key = Eth::Signature.recover [hash].pack('H*'), signature

  public_key == recover_key
end