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 +4 -4
- data/Gemfile +1 -0
- data/Gemfile.lock +16 -10
- data/ic_agent.gemspec +2 -0
- data/lib/ic_agent/agent.rb +149 -4
- data/lib/ic_agent/candid.rb +26 -5
- data/lib/ic_agent/certificate.rb +98 -0
- data/lib/ic_agent/client.rb +32 -0
- data/lib/ic_agent/identity.rb +52 -0
- data/lib/ic_agent/principal.rb +104 -6
- data/lib/ic_agent/system_state.rb +37 -0
- data/lib/ic_agent/utils.rb +30 -2
- data/lib/ic_agent/version.rb +1 -1
- data/lib/ic_agent.rb +7 -2
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8c87eb2ad8a0305a051b52137d44398b743540432609f7468eb61318b183caf4
|
4
|
+
data.tar.gz: c51a300fd8596bce7e064f3f47cd4d025bff47af4dfe84113f55bf70a8d4c5bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 432a2d181f6bd503f87c0cca4b60086aa059032c88da35382b78b2f587116fea18c7c9692e533cdffa39889dc44325e603320c245ecb5c8038ecd971068b12a0
|
7
|
+
data.tar.gz: 7ce801835791d320a52d9f0f35e494d8c2e7c5482cc9cddf71742bda0b69ed0fa6d10d05afbc95c90eb30f690f914851baf3487ad94bc8c6ac81d680b1156502
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
ic_agent (0.
|
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.
|
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
|
-
|
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.
|
51
|
-
pkg-config (1.5.
|
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.
|
70
|
+
rspec-core (3.12.2)
|
66
71
|
rspec-support (~> 3.12.0)
|
67
|
-
rspec-expectations (3.12.
|
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.
|
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.
|
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.
|
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'
|
data/lib/ic_agent/agent.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
data/lib/ic_agent/candid.rb
CHANGED
@@ -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
|
data/lib/ic_agent/certificate.rb
CHANGED
@@ -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
|
data/lib/ic_agent/client.rb
CHANGED
@@ -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)
|
data/lib/ic_agent/identity.rb
CHANGED
@@ -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)
|
data/lib/ic_agent/principal.rb
CHANGED
@@ -7,16 +7,21 @@ module IcAgent
|
|
7
7
|
MAX_LENGTH_IN_BYTES = 29
|
8
8
|
|
9
9
|
class PrincipalSort
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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::
|
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])
|
data/lib/ic_agent/utils.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
|
data/lib/ic_agent/version.rb
CHANGED
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"
|
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"
|
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.
|
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-
|
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:
|