ic_agent 0.1.4 → 0.2.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: 872963d331d311c6a940f64445ef0583397885eb9455464f4d865d325f24ee97
4
- data.tar.gz: 8b11f6cc71864468b52e66a6577fb3002bcf14cfde2ce93b1dfb6f819eb1df84
3
+ metadata.gz: 8c87eb2ad8a0305a051b52137d44398b743540432609f7468eb61318b183caf4
4
+ data.tar.gz: c51a300fd8596bce7e064f3f47cd4d025bff47af4dfe84113f55bf70a8d4c5bb
5
5
  SHA512:
6
- metadata.gz: 5ead16aeb03ac87caa4620ee9f813e7d79399f95d9cb2424cbedd2af828f9dd06f14a1f9d200ac596783ea3b47fdd88f500256608e972011e1dae01e69452cf4
7
- data.tar.gz: d6ebbda49c03f1e2df8fe3e28ffd2b53424a83e04b386841f8874c6b888f4089d8c0150194930ffdab836e81961cfc657168a6e225e0ddd32c665d03159e1cc3
6
+ metadata.gz: 432a2d181f6bd503f87c0cca4b60086aa059032c88da35382b78b2f587116fea18c7c9692e533cdffa39889dc44325e603320c245ecb5c8038ecd971068b12a0
7
+ data.tar.gz: 7ce801835791d320a52d9f0f35e494d8c2e7c5482cc9cddf71742bda0b69ed0fa6d10d05afbc95c90eb30f690f914851baf3487ad94bc8c6ac81d680b1156502
data/Gemfile CHANGED
@@ -7,6 +7,7 @@ gemspec
7
7
 
8
8
  gem 'base32', '~> 0.3.4'
9
9
  gem 'bitcoin-ruby', '~> 0.0.20'
10
+ gem 'bls12-381', '~> 0.3.0'
10
11
  gem 'byebug', '~> 11.1', '>= 11.1.3'
11
12
  gem 'cbor', '~> 0.5.9.6'
12
13
  gem 'ctf-party', '~> 2.3'
data/Gemfile.lock CHANGED
@@ -1,9 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ic_agent (0.1.4)
4
+ ic_agent (0.2.0)
5
5
  base32 (~> 0.3.4)
6
6
  bitcoin-ruby (~> 0.0.20)
7
+ bls12-381 (~> 0.3.0)
7
8
  cbor (~> 0.5.9.6)
8
9
  ctf-party (~> 2.3)
9
10
  ecdsa (~> 1.2)
@@ -23,6 +24,8 @@ GEM
23
24
  eventmachine
24
25
  ffi
25
26
  scrypt
27
+ bls12-381 (0.3.0)
28
+ h2c (~> 0.2.0)
26
29
  byebug (11.1.3)
27
30
  cbor (0.5.9.6)
28
31
  coderay (1.1.3)
@@ -34,7 +37,7 @@ GEM
34
37
  ecdsa (1.2.0)
35
38
  ed25519 (1.3.0)
36
39
  eventmachine (1.2.7)
37
- faraday (2.7.4)
40
+ faraday (2.7.10)
38
41
  faraday-net_http (>= 2.0, < 3.1)
39
42
  ruby2_keywords (>= 0.0.4)
40
43
  faraday-net_http (3.0.2)
@@ -42,13 +45,15 @@ GEM
42
45
  ffi-compiler (1.0.1)
43
46
  ffi (>= 1.0.0)
44
47
  rake
45
- i18n (1.12.0)
48
+ h2c (0.2.0)
49
+ ecdsa (~> 1.2.0)
50
+ i18n (1.14.1)
46
51
  concurrent-ruby (~> 1.0)
47
52
  json (2.6.3)
48
53
  leb128 (1.0.0)
49
54
  method_source (1.0.0)
50
- mini_portile2 (2.8.2)
51
- pkg-config (1.5.1)
55
+ mini_portile2 (2.8.4)
56
+ pkg-config (1.5.2)
52
57
  polyglot (0.3.5)
53
58
  pry (0.14.2)
54
59
  coderay (~> 1.1)
@@ -62,19 +67,19 @@ GEM
62
67
  rspec-core (~> 3.12.0)
63
68
  rspec-expectations (~> 3.12.0)
64
69
  rspec-mocks (~> 3.12.0)
65
- rspec-core (3.12.1)
70
+ rspec-core (3.12.2)
66
71
  rspec-support (~> 3.12.0)
67
- rspec-expectations (3.12.2)
72
+ rspec-expectations (3.12.3)
68
73
  diff-lcs (>= 1.2.0, < 2.0)
69
74
  rspec-support (~> 3.12.0)
70
- rspec-mocks (3.12.5)
75
+ rspec-mocks (3.12.6)
71
76
  diff-lcs (>= 1.2.0, < 2.0)
72
77
  rspec-support (~> 3.12.0)
73
- rspec-support (3.12.0)
78
+ rspec-support (3.12.1)
74
79
  ruby-enum (0.9.0)
75
80
  i18n
76
81
  ruby2_keywords (0.0.5)
77
- rubytree (2.0.0)
82
+ rubytree (2.0.2)
78
83
  json (~> 2.0, > 2.3.1)
79
84
  rubyzip (2.3.2)
80
85
  scrypt (3.0.7)
@@ -88,6 +93,7 @@ PLATFORMS
88
93
  DEPENDENCIES
89
94
  base32 (~> 0.3.4)
90
95
  bitcoin-ruby (~> 0.0.20)
96
+ bls12-381 (~> 0.3.0)
91
97
  byebug (~> 11.1, >= 11.1.3)
92
98
  cbor (~> 0.5.9.6)
93
99
  ctf-party (~> 2.3)
data/ic_agent.gemspec CHANGED
@@ -17,6 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.metadata['homepage_uri'] = spec.homepage
18
18
  spec.metadata['source_code_uri'] = spec.homepage
19
19
  spec.metadata['changelog_uri'] = spec.homepage
20
+ spec.metadata['documentation_uri'] = 'https://tuminfei.github.io/ic_agent.github.com/'
20
21
 
21
22
  # Specify which files should be added to the gem when it is released.
22
23
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -33,6 +34,7 @@ Gem::Specification.new do |spec|
33
34
  # spec.add_dependency "example-gem", "~> 1.0"
34
35
  spec.add_dependency 'base32', '~> 0.3.4'
35
36
  spec.add_dependency 'bitcoin-ruby', '~> 0.0.20'
37
+ spec.add_dependency 'bls12-381', '~> 0.3.0'
36
38
  spec.add_dependency 'cbor', '~> 0.5.9.6'
37
39
  spec.add_dependency 'ctf-party', '~> 2.3'
38
40
  spec.add_dependency 'ecdsa', '~> 1.2'
@@ -1,8 +1,15 @@
1
1
  require 'cbor'
2
+ require 'bls'
2
3
  require 'ctf_party'
4
+ require 'bitcoin'
3
5
 
4
6
  module IcAgent
5
7
  class Request
8
+ # Signs a request with an identity's signature and encodes it using CBOR.
9
+ #
10
+ # @param req [Hash] The request to be signed.
11
+ # @param iden [Identity] The identity used for signing.
12
+ # @return [Array] The request ID and the encoded signed request.
6
13
  def self.sign_request(req, iden)
7
14
  req_id = IcAgent::Utils.to_request_id(req)
8
15
  msg = IcAgent::IC_REQUEST_DOMAIN_SEPARATOR + req_id
@@ -26,6 +33,13 @@ module IcAgent
26
33
  class Agent
27
34
  attr_accessor :identity, :client, :ingress_expiry, :root_key, :nonce_factory
28
35
 
36
+ # Initializes a new IC agent.
37
+ #
38
+ # @param identity [Identity] The identity associated with the agent.
39
+ # @param client [Client] The client used for communication with the IC network.
40
+ # @param nonce_factory [NonceFactory] The factory for generating nonces.
41
+ # @param ingress_expiry [Integer] The expiration time for ingress requests.
42
+ # @param root_key [String] The IC root key used for verification.
29
43
  def initialize(identity, client, nonce_factory = nil, ingress_expiry = 300, root_key = IcAgent::IC_ROOT_KEY)
30
44
  @identity = identity
31
45
  @client = client
@@ -34,14 +48,25 @@ module IcAgent
34
48
  @nonce_factory = nonce_factory
35
49
  end
36
50
 
51
+ # Retrieves the principal associated with the agent's identity.
52
+ #
53
+ # @return [Principal] The principal associated with the agent.
37
54
  def get_principal
38
55
  @identity.sender
39
56
  end
40
57
 
58
+ # Calculates the expiration date for ingress requests.
59
+ #
60
+ # @return [Integer] The expiration date in nanoseconds.
41
61
  def get_expiry_date
42
62
  ((Time.now.to_i + @ingress_expiry) * 10**9).to_i
43
63
  end
44
64
 
65
+ # Sends a query request to a canister and decodes the response using CBOR.
66
+ #
67
+ # @param canister_id [String] The ID of the target canister.
68
+ # @param data [Hash] The data to be sent in the query request.
69
+ # @return [Object] The decoded response from the canister.
45
70
  def query_endpoint(canister_id, data)
46
71
  ret = @client.query(canister_id, data)
47
72
  decode_ret = nil
@@ -54,16 +79,34 @@ module IcAgent
54
79
  decode_ret
55
80
  end
56
81
 
82
+ # Calls a method on a canister and returns the request ID.
83
+ #
84
+ # @param canister_id [String] The ID of the target canister.
85
+ # @param request_id [String] The ID of the request.
86
+ # @param data [Hash] The data to be sent in the call request.
87
+ # @return [String] The request ID.
57
88
  def call_endpoint(canister_id, request_id, data)
58
89
  @client.call(canister_id, request_id, data)
59
90
  request_id
60
91
  end
61
92
 
93
+ # Reads the state of a canister.
94
+ #
95
+ # @param canister_id [String] The ID of the target canister.
96
+ # @param data [Hash] The data to be sent in the read state request.
97
+ # @return [Object] The response from the canister.
62
98
  def read_state_endpoint(canister_id, data)
63
- result = @client.read_state(canister_id, data)
64
- result
99
+ @client.read_state(canister_id, data)
65
100
  end
66
101
 
102
+ # Sends a raw query request to a canister and handles the response.
103
+ #
104
+ # @param canister_id [String] The ID of the target canister.
105
+ # @param method_name [String] The name of the method to be called.
106
+ # @param arg [String] The argument to be passed to the method.
107
+ # @param return_type [Object] The expected type of the return value.
108
+ # @param effective_canister_id [String] The effective canister ID (optional).
109
+ # @return [Object] The decoded response from the canister.
67
110
  def query_raw(canister_id, method_name, arg, return_type = nil, effective_canister_id = nil)
68
111
  req_canister_id = canister_id.is_a?(String) ? Principal.from_str(canister_id).bytes : canister_id.bytes
69
112
  req = {
@@ -92,6 +135,15 @@ module IcAgent
92
135
  end
93
136
  end
94
137
 
138
+ # Sends a raw update request to a canister and handles the response.
139
+ #
140
+ # @param canister_id [String] The ID of the target canister.
141
+ # @param method_name [String] The name of the method to be called.
142
+ # @param arg [String] The argument to be passed to the method.
143
+ # @param return_type [Object] The expected type of the return value.
144
+ # @param effective_canister_id [String] The effective canister ID (optional).
145
+ # @param kwargs [Hash] Additional keyword arguments.
146
+ # @return [Object] The decoded response from the canister.
95
147
  def update_raw(canister_id, method_name, arg, return_type = nil, effective_canister_id = nil, **kwargs)
96
148
  req_canister_id = canister_id.is_a?(String) ? Principal.from_str(canister_id).bytes : canister_id.bytes
97
149
  req = {
@@ -120,7 +172,13 @@ module IcAgent
120
172
  end
121
173
  end
122
174
 
123
- def read_state_raw(canister_id, paths)
175
+ # Sends a raw read state request to a canister and handles the response.
176
+ #
177
+ # @param canister_id [String] The ID of the target canister.
178
+ # @param paths [Array] The paths to read from the canister's state.
179
+ # @param [TrueClass] bls_verify
180
+ # @return [Object] The decoded response from the canister.
181
+ def read_state_raw(canister_id, paths, bls_verify = true)
124
182
  req = {
125
183
  'request_type' => 'read_state',
126
184
  'sender' => @identity.sender.bytes,
@@ -140,9 +198,20 @@ module IcAgent
140
198
  rescue StandardError
141
199
  raise ValueError, "Unable to decode cbor value: #{ret}"
142
200
  end
143
- CBOR.decode(d.value['certificate'])
201
+ cert = CBOR.decode(d.value['certificate'])
202
+
203
+ if bls_verify
204
+ verify(cert, canister_id) ? cert : false
205
+ else
206
+ cert
207
+ end
144
208
  end
145
209
 
210
+ # Retrieves the status and certificate of a request from a canister.
211
+ #
212
+ # @param canister_id [String] The ID of the target canister.
213
+ # @param req_id [String] The ID of the request.
214
+ # @return [Array] The status and certificate of the request.
146
215
  def request_status_raw(canister_id, req_id)
147
216
  paths = [['request_status', req_id]]
148
217
  cert = read_state_raw(canister_id, paths)
@@ -150,6 +219,12 @@ module IcAgent
150
219
  [status, cert]
151
220
  end
152
221
 
222
+ # Polls a canister for the status of a request.
223
+ #
224
+ # @param canister_id [String] The ID of the target canister.
225
+ # @param req_id [String] The ID of the request.
226
+ # @param delay [Integer] The delay between each poll attempt (in seconds).
227
+ # @param timeout [Integer] The maximum timeout for polling.
153
228
  def poll(canister_id, req_id, delay = 1, timeout = IcAgent::DEFAULT_POLL_TIMEOUT_SECS)
154
229
  status = nil
155
230
  cert = nil
@@ -172,5 +247,75 @@ module IcAgent
172
247
  [status, _]
173
248
  end
174
249
  end
250
+
251
+ def verify(cert, canister_id)
252
+ signature_hex = IcAgent::Certificate.signature(cert).str2hex
253
+ tree = IcAgent::Certificate.tree(cert)
254
+ delegation = IcAgent::Certificate.delegation(cert)
255
+ root_hash = IcAgent::Certificate.reconstruct(tree).str2hex
256
+ msg = IcAgent::IC_STATE_ROOT_DOMAIN_SEPARATOR + root_hash
257
+ der_key = check_delegation(delegation, canister_id, true)
258
+ public_key_hash = extract_der(der_key).str2hex
259
+
260
+ public_key = BLS::PointG2.from_hex(public_key_hash)
261
+ signature = BLS::PointG1.from_hex(signature_hex)
262
+ BLS.verify(signature, msg, public_key)
263
+ end
264
+
265
+ def check_delegation(delegation, effective_canister_id, disable_range_check)
266
+ return @root_key unless delegation
267
+
268
+ begin
269
+ cert = CBOR.decode(delegation['certificate'])
270
+ rescue CBOR::MalformedFormatError => e
271
+ raise TypeError, "certificate CBOR::MalformedFormatError: #{delegation['certificate']}"
272
+ end
273
+
274
+ path = ['subnet', delegation['subnet_id'], 'canister_ranges']
275
+ canister_range = IcAgent::Certificate.lookup(path, cert)
276
+
277
+ begin
278
+ ranges = []
279
+ ranges_json = CBOR.decode(canister_range).values[1]
280
+
281
+ ranges_json.each do |range_json|
282
+ range = {}
283
+ range['low'] = Principal.from_hex(range_json[0])
284
+ range['high'] = Principal.from_hex(range_json[1])
285
+ ranges << range
286
+ end
287
+
288
+ if !disable_range_check && !principal_is_within_ranges(effective_canister_id, ranges)
289
+ raise AgentError 'certificate CERTIFICATE_NOT_AUTHORIZED'
290
+ end
291
+ rescue Exception => e
292
+ raise AgentError "certificate INVALID_CBOR_DATA, canister_range: #{canister_range.to_s}"
293
+ end
294
+
295
+ path = ['subnet', delegation['subnet_id'], 'public_key']
296
+ IcAgent::Certificate.lookup(path, cert)
297
+ end
298
+
299
+ def principal_is_within_ranges(principal, ranges)
300
+ ranges.each do |range|
301
+ return true if range['low'].lt_eq(principal) && range['high'].gt_eq(principal)
302
+ end
303
+ false
304
+ end
305
+
306
+ def extract_der(der_buf)
307
+ bls_der_prefix = OpenSSL::BN.from_hex(IcAgent::BLS_DER_PREFIX).to_s(2)
308
+ expected_length = bls_der_prefix.bytesize + IcAgent::BLS_KEY_LENGTH
309
+ if der_buf.bytesize != expected_length
310
+ raise TypeError, "BLS DER-encoded public key must be #{expected_length} bytes long"
311
+ end
312
+
313
+ prefix = der_buf.byteslice(0, bls_der_prefix.bytesize)
314
+ if prefix != bls_der_prefix
315
+ raise TypeError, "BLS DER-encoded public key is invalid. Expect the following prefix: #{bls_der_prefix}, but get #{prefix}"
316
+ end
317
+
318
+ der_buf.byteslice(bls_der_prefix.bytesize..-1)
319
+ end
175
320
  end
176
321
  end
@@ -174,6 +174,8 @@ module IcAgent
174
174
  end
175
175
  end
176
176
 
177
+ # Represents an IDL Null
178
+ # check None == Null ?
177
179
  class NullClass < PrimitiveType
178
180
  def initialize()
179
181
  super
@@ -205,6 +207,7 @@ module IcAgent
205
207
  end
206
208
  end
207
209
 
210
+ # Represents an IDL Empty, a type which has no inhabitants.
208
211
  class EmptyClass < PrimitiveType
209
212
  def initialize
210
213
  super
@@ -235,6 +238,7 @@ module IcAgent
235
238
  end
236
239
  end
237
240
 
241
+ # Represents an IDL Bool
238
242
  class BoolClass < PrimitiveType
239
243
  def initialize
240
244
  super
@@ -275,6 +279,7 @@ module IcAgent
275
279
  end
276
280
  end
277
281
 
282
+ # Represents an IDL Reserved
278
283
  class ReservedClass < PrimitiveType
279
284
  def initialize
280
285
  super
@@ -308,6 +313,7 @@ module IcAgent
308
313
  end
309
314
  end
310
315
 
316
+ # Represents an IDL Text
311
317
  class TextClass < PrimitiveType
312
318
  def initialize
313
319
  super
@@ -343,6 +349,7 @@ module IcAgent
343
349
  end
344
350
  end
345
351
 
352
+ # Represents an IDL Int
346
353
  class IntClass < PrimitiveType
347
354
  def initialize
348
355
  super
@@ -374,6 +381,7 @@ module IcAgent
374
381
  end
375
382
  end
376
383
 
384
+ # Represents an IDL Nat
377
385
  class NatClass < PrimitiveType
378
386
  def initialize
379
387
  super
@@ -405,6 +413,7 @@ module IcAgent
405
413
  end
406
414
  end
407
415
 
416
+ # Represents an IDL Float
408
417
  class FloatClass < PrimitiveType
409
418
  def initialize(bits)
410
419
  super()
@@ -460,6 +469,7 @@ module IcAgent
460
469
  end
461
470
  end
462
471
 
472
+ # Represents an IDL fixed-width Int(n)
463
473
  class FixedIntClass < PrimitiveType
464
474
  def initialize(bits)
465
475
  super()
@@ -535,6 +545,7 @@ module IcAgent
535
545
  end
536
546
  end
537
547
 
548
+ # Represents an IDL fixed-width Nat(n)
538
549
  class FixedNatClass < PrimitiveType
539
550
  def initialize(bits)
540
551
  super()
@@ -605,6 +616,7 @@ module IcAgent
605
616
  end
606
617
  end
607
618
 
619
+ # Represents an IDL principal reference
608
620
  class PrincipalClass < PrimitiveType
609
621
  def initialize
610
622
  super
@@ -658,6 +670,7 @@ module IcAgent
658
670
  end
659
671
  end
660
672
 
673
+ # Represents an IDL Array
661
674
  class VecClass < ConstructType
662
675
  def initialize(_type)
663
676
  super()
@@ -704,6 +717,7 @@ module IcAgent
704
717
  end
705
718
  end
706
719
 
720
+ # Represents an IDL Option
707
721
  class OptClass < ConstructType
708
722
  def initialize(_type)
709
723
  super()
@@ -756,6 +770,7 @@ module IcAgent
756
770
  end
757
771
  end
758
772
 
773
+ # Represents an IDL Record
759
774
  class RecordClass < ConstructType
760
775
  def initialize(field)
761
776
  super()
@@ -845,6 +860,7 @@ module IcAgent
845
860
  end
846
861
  end
847
862
 
863
+ # Represents Tuple, a syntactic sugar for Record.
848
864
  class TupleClass < RecordClass
849
865
  attr_accessor :components
850
866
 
@@ -908,6 +924,7 @@ module IcAgent
908
924
  end
909
925
  end
910
926
 
927
+ # Represents an IDL Variant
911
928
  class VariantClass < ConstructType
912
929
  attr_accessor :fields
913
930
 
@@ -996,6 +1013,7 @@ module IcAgent
996
1013
  end
997
1014
  end
998
1015
 
1016
+ # Represents a reference to an IDL type, used for defining recursive data types.
999
1017
  class RecClass < ConstructType
1000
1018
  @@counter = 0
1001
1019
 
@@ -1073,6 +1091,7 @@ module IcAgent
1073
1091
  end
1074
1092
  end
1075
1093
 
1094
+ #Represents an IDL Func reference
1076
1095
  class FuncClass < ConstructType
1077
1096
  attr_accessor :arg_types, :ret_types, :annotations
1078
1097
 
@@ -1170,6 +1189,7 @@ module IcAgent
1170
1189
  end
1171
1190
  end
1172
1191
 
1192
+ # Represents an IDL Service reference
1173
1193
  class ServiceClass < ConstructType
1174
1194
  def initialize(field)
1175
1195
  super()
@@ -1344,6 +1364,7 @@ module IcAgent
1344
1364
  end
1345
1365
  end
1346
1366
 
1367
+ # through Pipe to decode bytes
1347
1368
  def self.leb128u_decode(pipe)
1348
1369
  res = StringIO.new
1349
1370
  loop do
@@ -1575,8 +1596,8 @@ module IcAgent
1575
1596
  return table[t]
1576
1597
  end
1577
1598
 
1578
- # params = [{type, value}]
1579
- # data = b'DIDL' + len(params) + encoded types + encoded values
1599
+ # @param [Object] params = [{type, value}]
1600
+ # @return data = b'DIDL' + len(params) + encoded types + encoded values
1580
1601
  def self.encode(params)
1581
1602
  arg_types = []
1582
1603
  args = []
@@ -1617,7 +1638,8 @@ module IcAgent
1617
1638
  return pre + table + length + typs + vals
1618
1639
  end
1619
1640
 
1620
- # decode a bytes value
1641
+ # @param [Object] data: decode a bytes value
1642
+ # @param [nil] ret_types
1621
1643
  # def decode(retTypes, data):
1622
1644
  def self.decode(data, ret_types = nil)
1623
1645
  pipe = Pipe.new(data)
@@ -1664,8 +1686,7 @@ module IcAgent
1664
1686
  'value' => t.decode_value(pipe, types[i])
1665
1687
  })
1666
1688
  end
1667
-
1668
- return outputs
1689
+ outputs
1669
1690
  end
1670
1691
  end
1671
1692
  end
@@ -8,10 +8,56 @@ module IcAgent
8
8
  end
9
9
 
10
10
  class Certificate
11
+ # Performs a lookup operation in the certificate tree based on the given path.
12
+ #
13
+ # Parameters:
14
+ # - path: The path to lookup.
15
+ # - cert: The certificate object containing the tree.
16
+ #
17
+ # Returns: The value found at the specified path in the tree.
11
18
  def self.lookup(path, cert)
12
19
  lookup_path(path, cert.value['tree'])
13
20
  end
14
21
 
22
+ # Retrieves the signature from a certificate.
23
+ #
24
+ # Parameters:
25
+ # - cert: The certificate object.
26
+ #
27
+ # Returns: The signature value.
28
+ def self.signature(cert)
29
+ cert.value['signature']
30
+ end
31
+
32
+ # Retrieves the delegation from a certificate.
33
+ #
34
+ # Parameters:
35
+ # - cert: The certificate object.
36
+ #
37
+ # Returns: The delegation value.
38
+ def self.delegation(cert)
39
+ cert.value['delegation']
40
+ end
41
+
42
+ # Retrieves the tree from a certificate.
43
+ #
44
+ # Parameters:
45
+ # - cert: The certificate object.
46
+ #
47
+ # Returns: The tree value.
48
+ def self.tree(cert)
49
+ cert.value['tree']
50
+ end
51
+
52
+ private
53
+
54
+ # Recursive helper method for performing the lookup operation.
55
+ #
56
+ # Parameters:
57
+ # - path: The remaining path to lookup.
58
+ # - tree: The current tree node to search in.
59
+ #
60
+ # Returns: The value found at the specified path in the tree.
15
61
  def self.lookup_path(path, tree)
16
62
  offset = 0
17
63
  if path.length == 0
@@ -29,6 +75,12 @@ module IcAgent
29
75
  end
30
76
  end
31
77
 
78
+ # Flattens fork nodes in the tree into a single array.
79
+ #
80
+ # Parameters:
81
+ # - t: The tree node to flatten.
82
+ #
83
+ # Returns: The flattened array of tree nodes.
32
84
  def self.flatten_forks(t)
33
85
  if t[0] == NodeId::EMPTY
34
86
  []
@@ -42,6 +94,13 @@ module IcAgent
42
94
  end
43
95
  end
44
96
 
97
+ # Finds a labeled tree node with the specified label in the given array of trees.
98
+ #
99
+ # Parameters:
100
+ # - l: The label to search for.
101
+ # - trees: The array of trees to search in.
102
+ #
103
+ # Returns: The labeled tree node with the matching label, or nil if not found.
45
104
  def self.find_label(l, trees)
46
105
  trees.each do |t|
47
106
  if t[0] == NodeId::LABELED
@@ -51,5 +110,44 @@ module IcAgent
51
110
  end
52
111
  nil
53
112
  end
113
+
114
+ # Recursively reconstructs the hash value of a tree node.
115
+ #
116
+ # Parameters:
117
+ # - t: The tree node to reconstruct.
118
+ #
119
+ # Returns: The reconstructed hash value of the tree node.
120
+ def self.reconstruct(t)
121
+ case t[0]
122
+ when IcAgent::NodeId::EMPTY
123
+ domain_sep = domain_sep('ic-hashtree-empty')
124
+ Digest::SHA256.digest(domain_sep)
125
+ when IcAgent::NodeId::PRUNED
126
+ t[1]
127
+ when IcAgent::NodeId::LEAF
128
+ domain_sep = domain_sep('ic-hashtree-leaf')
129
+ Digest::SHA256.digest(domain_sep + t[1])
130
+ when IcAgent::NodeId::LABELED
131
+ domain_sep = domain_sep('ic-hashtree-labeled')
132
+ Digest::SHA256.digest(domain_sep + t[1] + reconstruct(t[2]))
133
+ when IcAgent::NodeId::FORK
134
+ domain_sep = domain_sep('ic-hashtree-fork')
135
+ Digest::SHA256.digest(domain_sep + reconstruct(t[1]) + reconstruct(t[2]))
136
+ else
137
+ raise 'unreachable'
138
+ end
139
+ end
140
+
141
+ # Generates the domain separation prefix for hash computations.
142
+ #
143
+ # Parameters:
144
+ # - s: The domain separation string.
145
+ #
146
+ # Returns: The domain separation prefix as a binary string.
147
+ def self.domain_sep(s)
148
+ len = [s.bytesize].pack('C')
149
+ str = s.encode(Encoding::UTF_8)
150
+ len + str
151
+ end
54
152
  end
55
153
  end
@@ -5,6 +5,10 @@ module IcAgent
5
5
  DEFAULT_TIMEOUT = 120
6
6
  DEFAULT_TIMEOUT_QUERY = 30
7
7
 
8
+ # Initializes a new instance of the Client class.
9
+ #
10
+ # Parameters:
11
+ # - url: The URL of the IC agent. Defaults to 'https://ic0.app'.
8
12
  def initialize(url = 'https://ic0.app')
9
13
  @url = url
10
14
  @conn = Faraday.new(url: url) do |faraday|
@@ -16,6 +20,13 @@ module IcAgent
16
20
  end
17
21
  end
18
22
 
23
+ # Sends a query to a canister.
24
+ #
25
+ # Parameters:
26
+ # - canister_id: The ID of the canister to query.
27
+ # - data: The data to send with the query.
28
+ #
29
+ # Returns: The response from the canister as a UTF-8 encoded string.
19
30
  def query(canister_id, data)
20
31
  endpoint = "/api/v2/canister/#{canister_id}/query"
21
32
  ret = @conn.post(endpoint, data)
@@ -23,6 +34,14 @@ module IcAgent
23
34
  ret.body
24
35
  end
25
36
 
37
+ # Calls a function on a canister.
38
+ #
39
+ # Parameters:
40
+ # - canister_id: The ID of the canister to call.
41
+ # - req_id: The request ID.
42
+ # - data: The data to send with the call.
43
+ #
44
+ # Returns: The request ID.
26
45
  def call(canister_id, req_id, data)
27
46
  endpoint = "/api/v2/canister/#{canister_id}/call"
28
47
  ret = @conn.post(endpoint, data)
@@ -30,6 +49,13 @@ module IcAgent
30
49
  req_id
31
50
  end
32
51
 
52
+ # Reads the state of a canister.
53
+ #
54
+ # Parameters:
55
+ # - canister_id: The ID of the canister to read the state from.
56
+ # - data: The data to send with the read_state request.
57
+ #
58
+ # Returns: The response from the canister as a UTF-8 encoded string.
33
59
  def read_state(canister_id, data)
34
60
  endpoint = "/api/v2/canister/#{canister_id}/read_state"
35
61
  ret = @conn.post(endpoint, data)
@@ -37,6 +63,12 @@ module IcAgent
37
63
  ret.body
38
64
  end
39
65
 
66
+ # Retrieves the status of the IC agent.
67
+ #
68
+ # Parameters:
69
+ # - timeout: The timeout for the status request. Defaults to DEFAULT_TIMEOUT_QUERY.
70
+ #
71
+ # Returns: The response from the status endpoint as a UTF-8 encoded string.
40
72
  def status(timeout: DEFAULT_TIMEOUT_QUERY)
41
73
  endpoint = '/api/v2/status'
42
74
  ret = @conn.get(endpoint, timeout: timeout)
@@ -11,6 +11,12 @@ module IcAgent
11
11
  class Identity
12
12
  attr_reader :privkey, :pubkey, :der_pubkey, :sk, :vk, :key_type
13
13
 
14
+ # Initializes a new instance of the Identity class.
15
+ #
16
+ # Parameters:
17
+ # - privkey: The private key of the identity in hexadecimal format. Defaults to an empty string.
18
+ # - type: The key type of the identity. Defaults to 'ed25519'.
19
+ # - anonymous: A flag indicating whether the identity is anonymous. Defaults to false.
14
20
  def initialize(privkey = '', type = 'ed25519', anonymous = false)
15
21
  privkey = [privkey].pack('H*')
16
22
  @anonymous = anonymous
@@ -37,6 +43,12 @@ module IcAgent
37
43
  end
38
44
  end
39
45
 
46
+ # Creates a new Identity instance from a seed phrase (mnemonic).
47
+ #
48
+ # Parameters:
49
+ # - mnemonic: The seed phrase (mnemonic) used to generate the identity.
50
+ #
51
+ # Returns: The Identity instance.
40
52
  def self.from_seed(mnemonic)
41
53
  seed = Bitcoin::Trezor::Mnemonic.to_seed(mnemonic)
42
54
  privkey = seed[0..63]
@@ -44,6 +56,9 @@ module IcAgent
44
56
  Identity.new(privkey = privkey, type = key_type)
45
57
  end
46
58
 
59
+ # Returns the sender Principal associated with the Identity.
60
+ #
61
+ # Returns: The sender Principal.
47
62
  def sender
48
63
  if @anonymous
49
64
  IcAgent::Principal.anonymous
@@ -52,6 +67,12 @@ module IcAgent
52
67
  end
53
68
  end
54
69
 
70
+ # Signs a message using the Identity.
71
+ #
72
+ # Parameters:
73
+ # - msg: The message to sign.
74
+ #
75
+ # Returns: An array containing the DER-encoded public key and the signature.
55
76
  def sign(msg)
56
77
  if @anonymous
57
78
  [nil, nil]
@@ -65,6 +86,13 @@ module IcAgent
65
86
  end
66
87
  end
67
88
 
89
+ # Verifies a message signature using the Identity.
90
+ #
91
+ # Parameters:
92
+ # - msg: The message to verify.
93
+ # - sig: The signature to verify.
94
+ #
95
+ # Returns: `true` if the signature is valid, otherwise `false`.
68
96
  def verify(msg, sig)
69
97
  if @anonymous
70
98
  false
@@ -73,6 +101,9 @@ module IcAgent
73
101
  end
74
102
  end
75
103
 
104
+ # Returns the PEM-encoded private key of the Identity.
105
+ #
106
+ # Returns: The PEM-encoded private key.
76
107
  def to_pem
77
108
  der = @key_type == 'secp256k1' ? "#{IcAgent::IC_PUBKEY_SECP_DER_HERD}#{@sk.data.unpack1('H*')}".hex2str : "#{IcAgent::IC_PUBKEY_ED_DER_HEAD}#{@sk.to_bytes.unpack1('H*')}".hex2str
78
109
  b64 = Base64.strict_encode64(der)
@@ -92,20 +123,41 @@ module IcAgent
92
123
  class DelegateIdentity
93
124
  attr_reader :identity, :delegations, :der_pubkey
94
125
 
126
+ # Initializes a new instance of the DelegateIdentity class.
127
+ #
128
+ # Parameters:
129
+ # - identity: The Identity associated with the DelegateIdentity.
130
+ # - delegation: The delegation JSON object containing the delegated keys.
95
131
  def initialize(identity, delegation)
96
132
  @identity = identity
97
133
  @delegations = delegation['delegations'].map { |d| d }
98
134
  @der_pubkey = [delegation['publicKey']].pack('H*')
99
135
  end
100
136
 
137
+ # Signs a message using the DelegateIdentity.
138
+ #
139
+ # Parameters:
140
+ # - msg: The message to sign.
141
+ #
142
+ # Returns: An array containing the DER-encoded public key and the signature.
101
143
  def sign(msg)
102
144
  @identity.sign(msg)
103
145
  end
104
146
 
147
+ # Returns the sender Principal associated with the DelegateIdentity.
148
+ #
149
+ # Returns: The sender Principal.
105
150
  def sender
106
151
  Principal.self_authenticating(@der_pubkey)
107
152
  end
108
153
 
154
+ # Creates a new DelegateIdentity instance from JSON representations of the Identity and delegation.
155
+ #
156
+ # Parameters:
157
+ # - ic_identity: The JSON representation of the Identity.
158
+ # - ic_delegation: The JSON representation of the delegation.
159
+ #
160
+ # Returns: The DelegateIdentity instance.
109
161
  def self.from_json(ic_identity, ic_delegation)
110
162
  parsed_ic_identity = JSON.parse(ic_identity)
111
163
  parsed_ic_delegation = JSON.parse(ic_delegation)
@@ -7,16 +7,21 @@ module IcAgent
7
7
  MAX_LENGTH_IN_BYTES = 29
8
8
 
9
9
  class PrincipalSort
10
- OpaqueId = 1
11
- SelfAuthenticating = 2
12
- DerivedId = 3
13
- Anonymous = 4
10
+ OPAQUE_ID = 1
11
+ SELF_AUTHENTICATING = 2
12
+ DERIVED_ID = 3
13
+ ANONYMOUS = 4
14
14
  # Unassigned
15
15
  end
16
16
 
17
+ # Base class for Principal.
17
18
  class Principal
18
19
  attr_reader :len, :bytes, :is_principal, :hex
19
20
 
21
+ # Initializes a new instance of the Principal class.
22
+ #
23
+ # Parameters:
24
+ # - bytes: The bytes representing the principal. Defaults to an empty string.
20
25
  def initialize(bytes: ''.b)
21
26
  @len = bytes.length
22
27
  @bytes = bytes
@@ -24,23 +29,41 @@ module IcAgent
24
29
  @is_principal = true
25
30
  end
26
31
 
32
+ # Creates a new Principal instance representing the management canister.
33
+ #
34
+ # Returns: The Principal instance representing the management canister.
27
35
  def self.management_canister
28
36
  Principal.new
29
37
  end
30
38
 
39
+ # Creates a new self-authenticating Principal.
40
+ #
41
+ # Parameters:
42
+ # - pubkey: The public key associated with the self-authenticating Principal.
43
+ #
44
+ # Returns: The self-authenticating Principal instance.
31
45
  def self.self_authenticating(pubkey)
32
46
  # check pubkey.size for is ed25519 or secp256k1
33
47
  pubkey = [pubkey].pack('H*') if pubkey.size != 44 && pubkey.size != 88
34
48
 
35
49
  hash_ = OpenSSL::Digest::SHA224.digest(pubkey)
36
- hash_ += [PrincipalSort::SelfAuthenticating].pack('C')
50
+ hash_ += [PrincipalSort::SELF_AUTHENTICATING].pack('C')
37
51
  Principal.new(bytes: hash_)
38
52
  end
39
53
 
54
+ # Creates a new anonymous Principal.
55
+ #
56
+ # Returns: The anonymous Principal instance.
40
57
  def self.anonymous
41
58
  Principal.new(bytes: "\x04".b)
42
59
  end
43
60
 
61
+ # Creates a new Principal from a string representation.
62
+ #
63
+ # Parameters:
64
+ # - s: The string representation of the Principal.
65
+ #
66
+ # Returns: The Principal instance.
44
67
  def self.from_str(s)
45
68
  s1 = s.delete('-')
46
69
  pad_len = ((s1.length / 8.0).ceil * 8) - s1.length
@@ -53,10 +76,19 @@ module IcAgent
53
76
  p
54
77
  end
55
78
 
79
+ # Creates a new Principal from a hexadecimal string representation.
80
+ #
81
+ # Parameters:
82
+ # - s: The hexadecimal string representation of the Principal.
83
+ #
84
+ # Returns: The Principal instance.
56
85
  def self.from_hex(s)
57
86
  Principal.new(bytes: [s].pack('H*'))
58
87
  end
59
88
 
89
+ # Converts the Principal to a string representation.
90
+ #
91
+ # Returns: The string representation of the Principal.
60
92
  def to_str
61
93
  checksum = Zlib.crc32(@bytes) & 0xFFFFFFFF
62
94
  b = ''
@@ -71,6 +103,12 @@ module IcAgent
71
103
  ret + s
72
104
  end
73
105
 
106
+ # Converts the Principal to an AccountIdentifier.
107
+ #
108
+ # Parameters:
109
+ # - sub_account: The sub-account identifier. Defaults to 0.
110
+ #
111
+ # Returns: The AccountIdentifier instance.
74
112
  def to_account_id(sub_account = 0)
75
113
  AccountIdentifier.generate(self, sub_account)
76
114
  end
@@ -78,17 +116,70 @@ module IcAgent
78
116
  def to_s
79
117
  to_str
80
118
  end
119
+
120
+ # Compares the Principal with another Principal.
121
+ #
122
+ # Parameters:
123
+ # - other: The other Principal to compare with.
124
+ #
125
+ # Returns: The comparison result as a string ('lt', 'eq', or 'gt').
126
+ def compare_to(other)
127
+ (0...[self.bytes.length, other.bytes.length].min).each do |i|
128
+ if self.bytes[i] < other.bytes[i]
129
+ return 'lt'
130
+ elsif self.bytes[i] > other.bytes[i]
131
+ return 'gt'
132
+ end
133
+ end
134
+
135
+ if self.bytes.length < other.bytes.length
136
+ 'lt'
137
+ elsif self.bytes.length > other.bytes.length
138
+ 'gt'
139
+ else
140
+ 'eq'
141
+ end
142
+ end
143
+
144
+ # Utility method checking whether a provided Principal is less than or equal to the current one using the `compare_to` method.
145
+ #
146
+ # Parameters:
147
+ # - other: The other Principal to compare with.
148
+ #
149
+ # Returns: `true` if the current Principal is less than or equal to the provided Principal, otherwise `false`.
150
+ def lt_eq(other)
151
+ cmp = compare_to(other)
152
+ %w[lt eq].include?(cmp)
153
+ end
154
+
155
+ # Utility method checking whether a provided Principal is greater than or equal to the current one using the `compare_to` method.
156
+ #
157
+ # Parameters:
158
+ # - other: The other Principal to compare with.
159
+ #
160
+ # Returns: `true` if the current Principal is greater than or equal to the provided Principal, otherwise `false`.
161
+ def gt_eq(other)
162
+ cmp = compare_to(other)
163
+ %w[gt eq].include?(cmp)
164
+ end
81
165
  end
82
166
 
83
167
  class AccountIdentifier
84
168
  attr_reader :bytes
85
169
 
170
+ # Initializes a new instance of the AccountIdentifier class.
171
+ #
172
+ # Parameters:
173
+ # - hash: The hash representing the AccountIdentifier.
86
174
  def initialize(hash)
87
175
  raise 'Invalid hash length' unless hash.length == 32
88
176
 
89
177
  @bytes = hash
90
178
  end
91
179
 
180
+ # Converts the AccountIdentifier to a string representation.
181
+ #
182
+ # Returns: The string representation of the AccountIdentifier.
92
183
  def to_str
93
184
  '0x' + @bytes.unpack1('H*')
94
185
  end
@@ -97,12 +188,19 @@ module IcAgent
97
188
  to_str
98
189
  end
99
190
 
191
+ # Generates a new AccountIdentifier from a Principal.
192
+ #
193
+ # Parameters:
194
+ # - principal: The Principal associated with the AccountIdentifier.
195
+ # - sub_account: The sub-account identifier. Defaults to 0.
196
+ #
197
+ # Returns: The AccountIdentifier instance.
100
198
  def self.generate(principal, sub_account = 0)
101
199
  sha224 = OpenSSL::Digest::SHA224.new
102
200
  sha224 << "\naccount-id"
103
201
  sha224 << principal.bytes
104
202
  format_sub_account = "%08d" % sub_account
105
- sub_account = format_sub_account.chars.map {|c| c.to_i}.pack('N*')
203
+ sub_account = format_sub_account.chars.map { |c| c.to_i }.pack('N*')
106
204
  sha224 << sub_account
107
205
  hash = sha224.digest
108
206
  checksum = Zlib.crc32(hash) & 0xFFFFFFFF
@@ -5,6 +5,13 @@ require 'cbor'
5
5
 
6
6
  module IcAgent
7
7
  class SyetemState
8
+ # Retrieves the system time from a canister's state.
9
+ #
10
+ # Parameters:
11
+ # - agent: The IcAgent::Client instance.
12
+ # - canister_id: The ID of the canister.
13
+ #
14
+ # Returns: The system time as a timestamp.
8
15
  def self.time(agent, canister_id)
9
16
  cert = agent.read_state_raw(canister_id, [['time']])
10
17
  timestamp = Certificate.lookup(['time'], cert)
@@ -12,6 +19,14 @@ module IcAgent
12
19
  LEB128.decode_signed(str_io)
13
20
  end
14
21
 
22
+ # Retrieves the public key of a subnet from a canister's state.
23
+ #
24
+ # Parameters:
25
+ # - agent: The IcAgent::Client instance.
26
+ # - canister_id: The ID of the canister.
27
+ # - subnet_id: The ID of the subnet.
28
+ #
29
+ # Returns: The public key of the subnet in hexadecimal format.
15
30
  def self.subnet_public_key(agent, canister_id, subnet_id)
16
31
  path = ['subnet', Principal.from_str(subnet_id).bytes, 'public_key']
17
32
  cert = agent.read_state_raw(canister_id, [path])
@@ -19,6 +34,14 @@ module IcAgent
19
34
  pubkey.str2hex
20
35
  end
21
36
 
37
+ # Retrieves the canister ranges of a subnet from a canister's state.
38
+ #
39
+ # Parameters:
40
+ # - agent: The IcAgent::Client instance.
41
+ # - canister_id: The ID of the canister.
42
+ # - subnet_id: The ID of the subnet.
43
+ #
44
+ # Returns: An array of canister ranges, where each range is represented as an array of Principal instances.
22
45
  def self.subnet_canister_ranges(agent, canister_id, subnet_id)
23
46
  path = ['subnet', Principal.from_str(subnet_id).bytes, 'canister_ranges']
24
47
  cert = agent.read_state_raw(canister_id, [path])
@@ -26,6 +49,13 @@ module IcAgent
26
49
  CBOR.decode(ranges).value.map { |range| range.map { |item| Principal.new(bytes: item) } }
27
50
  end
28
51
 
52
+ # Retrieves the module hash of a canister from a canister's state.
53
+ #
54
+ # Parameters:
55
+ # - agent: The IcAgent::Client instance.
56
+ # - canister_id: The ID of the canister.
57
+ #
58
+ # Returns: The module hash of the canister in hexadecimal format.
29
59
  def self.canister_module_hash(agent, canister_id)
30
60
  path = ['canister', Principal.from_str(canister_id).bytes, 'module_hash']
31
61
  cert = agent.read_state_raw(canister_id, [path])
@@ -33,6 +63,13 @@ module IcAgent
33
63
  module_hash.str2hex
34
64
  end
35
65
 
66
+ # Retrieves the controllers of a canister from a canister's state.
67
+ #
68
+ # Parameters:
69
+ # - agent: The IcAgent::Client instance.
70
+ # - canister_id: The ID of the canister.
71
+ #
72
+ # Returns: An array of Principal instances representing the controllers of the canister.
36
73
  def self.canister_controllers(agent, canister_id)
37
74
  path = ['canister', Principal.from_str(canister_id).bytes, 'controllers']
38
75
  cert = agent.read_state_raw(canister_id, [path])
@@ -3,6 +3,12 @@ require 'leb128'
3
3
 
4
4
  module IcAgent
5
5
  module Utils
6
+ # Encodes a list of items into a binary string.
7
+ #
8
+ # Parameters:
9
+ # - l: The list of items to encode.
10
+ #
11
+ # Returns: The binary string representation of the encoded list.
6
12
  def self.encode_list(l)
7
13
  ret = ''
8
14
  l.each do |item|
@@ -21,7 +27,12 @@ module IcAgent
21
27
  ret
22
28
  end
23
29
 
24
- # used for sort record by key
30
+ # Computes a hash value for sorting records by key.
31
+ #
32
+ # Parameters:
33
+ # - s: The key to hash.
34
+ #
35
+ # Returns: The computed hash value.
25
36
  def self.label_hash(s)
26
37
  if s =~ /(^_\d+_$)|(^_0x[0-9a-fA-F]+_$)/
27
38
  num = s[1..-2]
@@ -41,6 +52,12 @@ module IcAgent
41
52
  idl_hash(s)
42
53
  end
43
54
 
55
+ # Computes a hash value for an IDL string.
56
+ #
57
+ # Parameters:
58
+ # - s: The IDL string to hash.
59
+ #
60
+ # Returns: The computed hash value.
44
61
  def self.idl_hash(s)
45
62
  h = 0
46
63
  s.bytes.each do |c|
@@ -49,6 +66,12 @@ module IcAgent
49
66
  h
50
67
  end
51
68
 
69
+ # Converts a data structure into a request ID.
70
+ #
71
+ # Parameters:
72
+ # - d: The data structure to convert.
73
+ #
74
+ # Returns: The request ID as a binary string.
52
75
  def self.to_request_id(d)
53
76
  return nil unless d.is_a?(Hash)
54
77
 
@@ -67,9 +90,14 @@ module IcAgent
67
90
  Digest::SHA256.digest(s)
68
91
  end
69
92
 
93
+ # Decodes a binary blob into a string.
94
+ #
95
+ # Parameters:
96
+ # - blob_bytes: The binary blob to decode.
97
+ #
98
+ # Returns: The decoded string.
70
99
  def self.decode_blob(blob_bytes)
71
100
  blob_bytes.pack('C*')
72
101
  end
73
102
  end
74
103
  end
75
-
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IcAgent
4
- VERSION = '0.1.4'
4
+ VERSION = '0.2.0'
5
5
  end
data/lib/ic_agent.rb CHANGED
@@ -30,10 +30,15 @@ module IcAgent
30
30
  class Error < StandardError; end
31
31
  class ValueError < StandardError; end
32
32
  class TypeError < StandardError; end
33
+ class AgentError < StandardError; end
34
+ class BaseException < StandardError; end
33
35
 
34
- IC_REQUEST_DOMAIN_SEPARATOR = "\x0Aic-request".freeze
35
- IC_ROOT_KEY = "\x4E\x9A\xF9\x9F\x06\x13\x26\x81\xE7\xD2\x55\x2A\x26\x17\x98\x51\xE9\xC3\x79\xB3\xC7\xBE\x88\x27\xB8\x35\x17\xFC\x84\x4E\x4C\x4F".freeze
36
+ IC_REQUEST_DOMAIN_SEPARATOR = "\x0Aic-request"
37
+ IC_ROOT_KEY = "\x4E\x9A\xF9\x9F\x06\x13\x26\x81\xE7\xD2\x55\x2A\x26\x17\x98\x51\xE9\xC3\x79\xB3\xC7\xBE\x88\x27\xB8\x35\x17\xFC\x84\x4E\x4C\x4F"
36
38
  IC_PUBKEY_ED_DER_HEAD = '302a300506032b6570032100'
37
39
  IC_PUBKEY_SECP_DER_HERD = '3056301006072a8648ce3d020106052b8104000a034200'
38
40
  DEFAULT_POLL_TIMEOUT_SECS = 60
41
+ IC_STATE_ROOT_DOMAIN_SEPARATOR = "\ric-state-root".str2hex
42
+ BLS_KEY_LENGTH = 96
43
+ BLS_DER_PREFIX = '308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c05030201036100'
39
44
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ic_agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Terry.Tu
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-06-25 00:00:00.000000000 Z
11
+ date: 2023-07-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base32
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.0.20
41
+ - !ruby/object:Gem::Dependency
42
+ name: bls12-381
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.3.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.3.0
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: cbor
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -272,6 +286,7 @@ metadata:
272
286
  homepage_uri: https://github.com/tuminfei/ic_agent
273
287
  source_code_uri: https://github.com/tuminfei/ic_agent
274
288
  changelog_uri: https://github.com/tuminfei/ic_agent
289
+ documentation_uri: https://tuminfei.github.io/ic_agent.github.com/
275
290
  post_install_message:
276
291
  rdoc_options: []
277
292
  require_paths: