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 +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:
|