quorum_sdk 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,23 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'api/light_node'
4
-
5
3
  module QuorumSdk
6
4
  # HTTP Client wrapper
7
5
  class Client
8
6
  attr_reader :domains
9
7
 
10
- def initialize(domains)
11
- @domains = domains || []
12
- uri = Addressable::URI.parse @domains.first
8
+ def initialize(chain_url, jwt = nil)
9
+ chain_url =
10
+ case chain_url
11
+ when Array
12
+ chain_url.first
13
+ when String
14
+ chain_url
15
+ end
16
+
17
+ uri = Addressable::URI.parse chain_url
13
18
  url = Addressable::URI.new(scheme: uri.scheme, host: uri.host, port: uri.port).to_s
14
- jwt = uri.query_values['jwt']
19
+ jwt ||= uri.query_values['jwt']
15
20
 
16
- @conn = Faraday.new(url:, headers: { Authorization: "Bearer #{jwt}" }) do |f|
17
- f.request :json
18
- f.response :json
19
- f.response :logger
20
- end
21
+ @conn =
22
+ Faraday.new(
23
+ url:,
24
+ headers: { Authorization: "Bearer #{jwt}" }
25
+ ) do |f|
26
+ f.request :json
27
+ f.response :json
28
+ f.response :logger
29
+ end
21
30
  end
22
31
 
23
32
  def post(path, **body)
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module QuorumSdk
4
+ class Node
5
+ # Announce module
6
+ module Announce
7
+ def announce(**kwargs)
8
+ group_id = kwargs[:group_id] || @group_id
9
+ raise ArgumentError, 'group_id must be provided' if group_id.blank?
10
+
11
+ payload = {
12
+ data: {
13
+ Action: kwargs[:action],
14
+ AnnouncerSignature: kwargs[:announce_signature],
15
+ EncryptPubkey: kwargs[:encrypt_pubkey],
16
+ GroupId: group_id,
17
+ Memo: kwargs[:memo],
18
+ OwnerPubkey: kwargs[:owner_pubkey],
19
+ OwnerSignature: kwargs[:owner_signature],
20
+ Result: kwargs[:result],
21
+ SignPubkey: kwargs[:sign_pubkey],
22
+ TimeStamp: kwargs[:timestamp],
23
+ Type: kwargs[:type]
24
+ }
25
+ }
26
+
27
+ path = "/api/v1/node/#{group_id}/announce"
28
+ client.post(path, **payload).body
29
+ end
30
+
31
+ def announced_producers(group_id: nil)
32
+ group_id ||= @group_id
33
+ raise ArgumentError, 'group_id must be provided' if group_id.blank?
34
+
35
+ path = "/api/v1/node/#{group_id}/announced/producer"
36
+ client.get(path).body
37
+ end
38
+
39
+ def announced_users(group_id: nil, pubkey: nil)
40
+ group_id ||= @group_id
41
+ raise ArgumentError, 'group_id must be provided' if group_id.blank?
42
+
43
+ params = {
44
+ pubkey:
45
+ }.compact
46
+
47
+ path = "/api/v1/node/#{group_id}/announced/user"
48
+ client.get(path, **params).body
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module QuorumSdk
4
+ class Node
5
+ # Appconfig module
6
+ module AppConfig
7
+ def app_config(key, group_id: nil)
8
+ group_id ||= @group_id
9
+ raise ArgumentError, 'group_id must be provided' if group_id.blank?
10
+ raise ArgumentError, 'key must be provided' if key.blank?
11
+
12
+ path = "api/v1/node/#{group_id}/appconfig/by/#{key}"
13
+ client.get(path).body
14
+ end
15
+
16
+ def app_config_keys(group_id: nil)
17
+ group_id ||= @group_id
18
+ raise ArgumentError, 'group_id must be provided' if group_id.blank?
19
+
20
+ path = "api/v1/node/#{group_id}/appconfig/keylist"
21
+ client.get(path).body
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module QuorumSdk
4
+ class Node
5
+ # Auth module
6
+ module Auth
7
+ def allow_list(group_id: nil)
8
+ group_id ||= @group_id
9
+ raise ArgumentError, 'group_id must be provided' if group_id.blank?
10
+
11
+ path = "api/v1/node/#{group_id}/auth/alwlist"
12
+ client.get(path).body
13
+ end
14
+
15
+ def deny_list(group_id: nil)
16
+ group_id ||= @group_id
17
+ raise ArgumentError, 'group_id must be provided' if group_id.blank?
18
+
19
+ path = "api/v1/node/#{group_id}/auth/denylist"
20
+ client.get(path).body
21
+ end
22
+
23
+ def auth_type(group_id: nil, trx_type: 'POST')
24
+ group_id ||= @group_id
25
+ raise ArgumentError, 'group_id must be provided' if group_id.blank?
26
+
27
+ path = "api/v1/node/#{group_id}/auth/by/#{trx_type}"
28
+ client.get(path).body
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module QuorumSdk
4
+ class Node
5
+ # group module
6
+ module Group
7
+ def info(group_id: nil)
8
+ group_id ||= @group_id
9
+ raise ArgumentError, 'group_id must be provided' if group_id.blank?
10
+
11
+ path = "api/v1/node/#{group_id}/info"
12
+ client.get(path).body
13
+ end
14
+
15
+ def producers(group_id: nil)
16
+ group_id ||= @group_id
17
+ raise ArgumentError, 'group_id must be provided' if group_id.blank?
18
+
19
+ path = "api/v1/node/#{group_id}/producers"
20
+ client.get(path).body
21
+ end
22
+
23
+ def user_encrypt_pubkeys(group_id: nil)
24
+ group_id ||= @group_id
25
+ raise ArgumentError, 'group_id must be provided' if group_id.blank?
26
+
27
+ path = "api/v1/node/#{group_id}/userencryptpubkeys"
28
+ client.get(path).body
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module QuorumSdk
4
+ class Node
5
+ # Wrapper for HTTP APIs as light node client
6
+ module Trx
7
+ ARGUMENTS_FOR_BUILD_TRX = %i[private_key data].freeze
8
+ def build_trx(**kwargs)
9
+ group_id = kwargs[:group_id] || @group_id
10
+ raise ArgumentError, 'group_id must be provided' if group_id.blank?
11
+
12
+ cipher_key = kwargs[:cipher_key] || @cipher_key
13
+ raise ArgumentError, 'cipher_key must be provided' if cipher_key.blank?
14
+
15
+ raise ArgumentError, "Keyword arguments #{ARGUMENTS_FOR_BUILD_TRX} must be provided" unless ARGUMENTS_FOR_BUILD_TRX.all?(&->(arg) { arg.in? kwargs.keys })
16
+
17
+ kwargs = kwargs.merge(
18
+ group_id:,
19
+ cipher_key:
20
+ )
21
+ QuorumSdk::Utils.build_trx(**kwargs)
22
+ end
23
+
24
+ ARGUMENTS_FOR_SEND_TRX = %i[data sender_pubkey sender_sign trx_id version].freeze
25
+ def send_trx(trx = nil, **kwargs)
26
+ trx ||= kwargs
27
+ raise ArgumentError, "Keyword arguments #{ARGUMENTS_FOR_SEND_TRX} must be provided" unless ARGUMENTS_FOR_SEND_TRX.all?(&->(arg) { arg.in? trx.keys })
28
+
29
+ group_id = trx[:group_id] || @group_id
30
+ raise ArgumentError, 'group_id must be provided' if group_id.blank?
31
+
32
+ payload = {
33
+ data: trx[:data],
34
+ sender_pubkey: trx[:sender_pubkey],
35
+ sender_sign: trx[:sender_sign],
36
+ timestamp: trx[:time_stamp] || trx[:timestamp],
37
+ trx_id: trx[:trx_id],
38
+ version: trx[:version]
39
+ }.compact
40
+ payload.deep_transform_values!(&->(v) { v.is_a?(String) ? v : v.to_s })
41
+
42
+ path = "api/v1/node/#{group_id}/trx"
43
+ client.post(path, **payload).body
44
+ end
45
+
46
+ def list_trx(**kwargs)
47
+ group_id = kwargs[:group_id] || @group_id
48
+ raise ArgumentError, 'group_id must be provided' if group_id.blank?
49
+
50
+ cipher_key = kwargs[:cipher_key] || @cipher_key
51
+
52
+ params = {
53
+ group_id:,
54
+ start_trx: kwargs[:start_trx],
55
+ num: kwargs[:num] || 100,
56
+ senders: kwargs[:senders],
57
+ reverse: kwargs[:reverse],
58
+ include_start_trx: kwargs[:include_start_trx]
59
+ }.compact
60
+
61
+ path = "api/v1/node/#{group_id}/groupctn"
62
+ list = client.get(path, **params).body
63
+
64
+ return list unless list.is_a?(Array)
65
+ return list if cipher_key.blank?
66
+
67
+ list.each do |trx|
68
+ next if trx['Data'].blank?
69
+
70
+ data = Base64.strict_decode64 trx['Data']
71
+ trx['DecryptedData'] = QuorumSdk::Utils.decrypt_trx_data data, key: cipher_key
72
+ end
73
+
74
+ list
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'client'
4
+ require_relative 'node/announce'
5
+ require_relative 'node/app_config'
6
+ require_relative 'node/auth'
7
+ require_relative 'node/group'
8
+ require_relative 'node/trx'
9
+
10
+ module QuorumSdk
11
+ # Wrapper for HTTP APIs as light node client
12
+ class Node
13
+ attr_reader :group_id, :group_name, :consensus_type, :encryption_type, :app_key, :owner_pubkey, :signature,
14
+ :cipher_key, :chain_url, :jwt, :client
15
+
16
+ def initialize(**kwargs)
17
+ config =
18
+ if kwargs[:seed].present?
19
+ QuorumSdk::Utils.parse_seed kwargs[:seed]
20
+ else
21
+ kwargs
22
+ end
23
+
24
+ @group_id = config[:group_id]
25
+ @group_name = config[:group_name]
26
+ @consensus_type = config[:consensus_type]
27
+ @encryption_type = config[:encryption_type]
28
+ @app_key = config[:app_key]
29
+ @owner_pubkey = config[:owner_pubkey]
30
+ @signature = config[:signature]
31
+ @cipher_key = config[:cipher_key]
32
+ @chain_url = config[:chain_url]
33
+ @jwt = config[:jwt]
34
+
35
+ @client = QuorumSdk::Client.new(@chain_url, @jwt)
36
+ end
37
+
38
+ include QuorumSdk::Node::Announce
39
+ include QuorumSdk::Node::AppConfig
40
+ include QuorumSdk::Node::Auth
41
+ include QuorumSdk::Node::Group
42
+ include QuorumSdk::Node::Trx
43
+ end
44
+ end
@@ -3,8 +3,10 @@
3
3
  module QuorumSdk
4
4
  # Wrapper for some useful methods
5
5
  module Utils
6
+ TRX_VERSION = '2.0.0'
7
+
6
8
  class << self
7
- def parse_seed_url(url)
9
+ def parse_seed(url)
8
10
  url = Addressable::URI.parse(url.gsub(/\\u([a-f0-9]{4})/i) { [::Regexp.last_match(1).hex].pack('U') })
9
11
  query_values = url.query_values
10
12
 
@@ -17,7 +19,7 @@ module QuorumSdk
17
19
  app_key = query_values['y']
18
20
  consensus_type = query_values['n'].to_s == '1' ? 'pos' : 'poa'
19
21
  encryption_type = query_values['e'].to_s == '0' ? 'public' : 'private'
20
- chain_urls = query_values['u'].split('|')
22
+ chain_url = query_values['u'].split('|')
21
23
 
22
24
  {
23
25
  group_id:,
@@ -29,7 +31,7 @@ module QuorumSdk
29
31
  app_key:,
30
32
  consensus_type:,
31
33
  encryption_type:,
32
- chain_urls:
34
+ chain_url:
33
35
  }
34
36
  end
35
37
 
@@ -48,7 +50,7 @@ module QuorumSdk
48
50
  end
49
51
 
50
52
  ARGUMENTS_FOR_ENCRYPT_TRX = %i[private_key group_id cipher_key data].freeze
51
- def encrypt_trx(**kwargs)
53
+ def build_trx(**kwargs)
52
54
  raise ArgumentError, "Keyword arguments #{ARGUMENTS_FOR_ENCRYPT_TRX} must be provided" unless ARGUMENTS_FOR_ENCRYPT_TRX.all?(&->(arg) { arg.in? kwargs.keys })
53
55
 
54
56
  data =
@@ -58,29 +60,52 @@ module QuorumSdk
58
60
  else
59
61
  kwargs[:data].to_s
60
62
  end
61
- encrypted_data = aes_encrypt data, key: kwargs[:cipher_key]
63
+ encrypted_data = aes_encrypt(data, key: kwargs[:cipher_key])
62
64
 
63
65
  account = QuorumSdk::Account.new priv: kwargs[:private_key]
64
66
 
65
- msg =
67
+ trx =
66
68
  Quorum::Pb::Trx.new(
67
69
  TrxId: (kwargs[:trx_id] || SecureRandom.uuid),
68
70
  GroupId: kwargs[:group_id],
69
71
  Data: encrypted_data,
70
- TimeStamp: (kwargs[:timestamp].to_i || (Time.now.to_f * 1e9).to_i),
72
+ TimeStamp: (kwargs[:timestamp].presence || (Time.now.to_f * 1e9)).to_i,
71
73
  Version: (kwargs[:version] || TRX_VERSION),
72
- Expired: (kwargs[:expired].to_i || (30.seconds.from_now.to_f * 1e9).to_i),
73
74
  SenderPubkey: Base64.urlsafe_encode64(account.public_bytes_compressed, padding: false)
74
75
  )
75
76
 
76
- hash = Digest::SHA256.hexdigest msg.to_proto
77
+ hash = Digest::SHA256.hexdigest trx.to_proto
77
78
  signature = account.sign [hash].pack('H*')
78
- msg.SenderSign = [signature].pack('H*')
79
- trx_json = {
80
- TrxBytes: Base64.strict_encode64(msg.to_proto)
81
- }
82
- encrypted = aes_encrypt trx_json.to_json, key: kwargs[:cipher_key]
83
- Base64.strict_encode64 encrypted
79
+ trx.SenderSign = [signature].pack('H*')
80
+
81
+ json = Quorum::Pb::Trx.encode_json trx
82
+ JSON.parse(json).deep_transform_keys! { |key| key.to_s.underscore.to_sym }
83
+ end
84
+
85
+ def verify_trx(trx = nil, **kwargs)
86
+ trx ||= kwargs
87
+ trx.deep_transform_keys! { |key| key.to_s.camelize.to_sym }
88
+ trx = trx.with_indifferent_access
89
+ trx['TimeStamp'] = trx['TimeStamp'].to_i if trx['TimeStamp'].present? && trx['TimeStamp'].is_a?(String)
90
+ trx['Expired'] = trx['Expired'].to_i if trx['Expired'].present? && trx['Expired'].is_a?(String)
91
+
92
+ signature = Base64.urlsafe_decode64(trx['SenderSign']).unpack1('H*')
93
+
94
+ trx = Quorum::Pb::Trx.new(
95
+ TrxId: trx['TrxId'],
96
+ GroupId: trx['GroupId'],
97
+ Data: Base64.decode64(trx['Data']),
98
+ TimeStamp: trx['TimeStamp'],
99
+ Expired: trx['Expired'],
100
+ Version: trx['Version'],
101
+ SenderPubkey: trx['SenderPubkey']
102
+ )
103
+
104
+ hash = Digest::SHA256.hexdigest trx.to_proto
105
+ public_key = Secp256k1::PublicKey.from_data(Base64.urlsafe_decode64(trx.SenderPubkey)).uncompressed.unpack1('H*')
106
+ recover_key = Eth::Signature.recover [hash].pack('H*'), signature
107
+
108
+ public_key == recover_key
84
109
  end
85
110
 
86
111
  def decrypt_trx(cipher, key:)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module QuorumSdk
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
data/lib/quorum_sdk.rb CHANGED
@@ -9,16 +9,14 @@ require 'faraday'
9
9
  require 'faraday/retry'
10
10
  require 'google/protobuf/well_known_types'
11
11
 
12
- require_relative 'proto/activity_stream_pb'
13
12
  require_relative 'proto/chain_pb'
14
13
  require_relative 'quorum_sdk/account'
15
- require_relative 'quorum_sdk/api'
14
+ require_relative 'quorum_sdk/chain'
15
+ require_relative 'quorum_sdk/node'
16
16
  require_relative 'quorum_sdk/utils'
17
17
  require_relative 'quorum_sdk/version'
18
18
 
19
19
  module QuorumSdk
20
- TRX_VERSION = '2.0.0'
21
-
22
20
  class Error < StandardError; end
23
21
  class ArgumentError < Error; end
24
22
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quorum_sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - an-lee
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-03-24 00:00:00.000000000 Z
11
+ date: 2023-04-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -123,18 +123,22 @@ files:
123
123
  - LICENSE.txt
124
124
  - README.md
125
125
  - Rakefile
126
- - lib/proto/activity_stream.proto
127
- - lib/proto/activity_stream_pb.rb
128
126
  - lib/proto/chain.proto
129
127
  - lib/proto/chain_pb.rb
130
128
  - lib/quorum_sdk.rb
131
129
  - lib/quorum_sdk/account.rb
132
- - lib/quorum_sdk/api.rb
133
- - lib/quorum_sdk/api/chain.rb
134
- - lib/quorum_sdk/api/group.rb
135
- - lib/quorum_sdk/api/light_node.rb
136
- - lib/quorum_sdk/api/management.rb
130
+ - lib/quorum_sdk/chain.rb
131
+ - lib/quorum_sdk/chain/chain.rb
132
+ - lib/quorum_sdk/chain/group.rb
133
+ - lib/quorum_sdk/chain/management.rb
134
+ - lib/quorum_sdk/chain/node.rb
137
135
  - lib/quorum_sdk/client.rb
136
+ - lib/quorum_sdk/node.rb
137
+ - lib/quorum_sdk/node/announce.rb
138
+ - lib/quorum_sdk/node/app_config.rb
139
+ - lib/quorum_sdk/node/auth.rb
140
+ - lib/quorum_sdk/node/group.rb
141
+ - lib/quorum_sdk/node/trx.rb
138
142
  - lib/quorum_sdk/utils.rb
139
143
  - lib/quorum_sdk/version.rb
140
144
  - sig/quorum_sdk.rbs
@@ -1,134 +0,0 @@
1
- /* https://github.com/rumsystem/quorum/blob/main/pkg/pb/activity_stream.proto */
2
- syntax = "proto3";
3
- package quorum.pb;
4
- option go_package = "github.com/rumsystem/quorum/pkg/pb";
5
-
6
- import "google/protobuf/timestamp.proto";
7
- import "google/protobuf/any.proto";
8
-
9
- message AnyObj {
10
- google.protobuf.Any any=1;
11
- }
12
-
13
- message Object {
14
- string id = 1;
15
- string type = 2;
16
- repeated Object attachments = 3;
17
- repeated Object attributedTo = 4;
18
- Object audience = 5;
19
- string content = 6;
20
- Object context = 7;
21
- string name = 8;
22
- google.protobuf.Timestamp endtime = 9;
23
- Object generator= 10;
24
- repeated Object icon = 11;
25
- repeated Image image = 12;
26
- Reply inreplyto = 13;
27
- Object location = 14;
28
- Object preview = 15;
29
- google.protobuf.Timestamp published = 16;
30
- Object replies = 17;
31
- google.protobuf.Timestamp startTime = 18;
32
- string summary = 19;
33
- repeated Object tag = 20;
34
- google.protobuf.Timestamp updated = 21;
35
- repeated Link url = 22;
36
- repeated Object to = 23;
37
- repeated Object bto = 24;
38
- repeated Object cc = 25;
39
- repeated Object bcc = 26;
40
- string mediaType= 27;
41
- string duration = 28;
42
- File file = 29;
43
- }
44
-
45
- message Link {
46
- string href = 1;
47
- repeated string rel = 2;
48
- string mediaType= 3;
49
- string name = 4;
50
- string hreflang = 5;
51
- uint32 height = 6;
52
- uint32 width = 7;
53
- Object preview =8;
54
- }
55
-
56
- message Reply {
57
- string trxid = 1;
58
- string groupid = 2;
59
- }
60
-
61
- message Image {
62
- string id = 1;
63
- string name = 2;
64
- string mediaType= 3;
65
- bytes content = 4;
66
- string url = 5;
67
- }
68
-
69
- message File {
70
- string id = 1;
71
- string name = 2;
72
- string mediaType= 3;
73
- enum Compression {
74
- none = 0;
75
- gz = 1;
76
- zip = 2;
77
- zstd = 3;
78
- }
79
- Compression compression = 4;
80
- bytes content = 5;
81
- string url = 6;
82
- }
83
-
84
- message Person {
85
- string id = 1;
86
- string name = 2;
87
- Image image = 3;
88
- repeated Payment wallet = 11;
89
- }
90
-
91
- message Payment {
92
- string id = 1;
93
- string type = 2;
94
- string name = 3;
95
- }
96
-
97
- message Activity {
98
- string id = 1;
99
- string type = 2;
100
- repeated Object attachments = 3;
101
- repeated Object attributedTo = 4;
102
- Object audience = 5;
103
- string content = 6;
104
- Object context = 7;
105
- string name = 8;
106
- google.protobuf.Timestamp endtime = 9;
107
- Object generator= 10;
108
- repeated Object icon = 11;
109
- repeated Object image = 12;
110
- Object inReplyTo = 13;
111
- Object location = 14;
112
- Object preview = 15;
113
- google.protobuf.Timestamp published = 16;
114
- Object replies = 17;
115
- google.protobuf.Timestamp startTime = 18;
116
- string summary = 19;
117
- repeated Object tag = 20;
118
- google.protobuf.Timestamp updated = 21;
119
- repeated Link url = 22;
120
- repeated Object to = 23;
121
- repeated Object bto = 24;
122
- repeated Object cc = 25;
123
- repeated Object bcc = 26;
124
- string mediaType = 27;
125
- string duration = 28;
126
-
127
- Object actor = 29;
128
- Object object = 30;
129
- Object target = 31;
130
- Object result = 32;
131
- Object origin = 33;
132
- Object instrument = 34;
133
- Person person = 35;
134
- }