ic_agent 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: