@provablehq/sdk 0.10.1 → 0.10.2-rc.1
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.
- package/dist/mainnet/browser.d.ts +3 -2
- package/dist/mainnet/browser.js +866 -319
- package/dist/mainnet/browser.js.map +1 -1
- package/dist/mainnet/keys/keystore/indexeddb.d.ts +49 -0
- package/dist/mainnet/network-client.d.ts +16 -0
- package/dist/mainnet/node.js +2 -2
- package/dist/mainnet/program-manager.d.ts +62 -1
- package/dist/mainnet/wasm.d.ts +1 -1
- package/dist/testnet/browser.d.ts +3 -2
- package/dist/testnet/browser.js +866 -319
- package/dist/testnet/browser.js.map +1 -1
- package/dist/testnet/keys/keystore/indexeddb.d.ts +49 -0
- package/dist/testnet/network-client.d.ts +16 -0
- package/dist/testnet/node.js +2 -2
- package/dist/testnet/program-manager.d.ts +62 -1
- package/dist/testnet/wasm.d.ts +1 -1
- package/package.json +6 -2
package/dist/mainnet/browser.js
CHANGED
|
@@ -1,9 +1,360 @@
|
|
|
1
1
|
import 'core-js/proposals/json-parse-with-source.js';
|
|
2
|
-
import { ViewKey, ComputeKey, Address, PrivateKeyCiphertext, PrivateKey, RecordCiphertext, EncryptionToolkit, Group, Metadata,
|
|
3
|
-
export { Address, Authorization, BHP1024, BHP256, BHP512, BHP768, Boolean, Ciphertext, ComputeKey, DynamicRecord, EncryptionToolkit, ExecutionRequest, ExecutionResponse, Field, Execution as FunctionExecution, GraphKey, Group, I128, I16, I32, I64, I8, OfflineQuery, Pedersen128, Pedersen64, Plaintext, Poseidon2, Poseidon4, Poseidon8, PrivateKey, PrivateKeyCiphertext, Program, ProgramManager as ProgramManagerBase, Proof, ProvingKey, ProvingRequest, RecordCiphertext, RecordPlaintext, Scalar, Signature, Transaction, Transition, U128, U16, U32, U64, U8, Value, VerifyingKey, ViewKey, getOrInitConsensusVersionTestHeights, initThreadPool, snarkVerify, snarkVerifyBatch, stringToField, verifyFunctionExecution } from '@provablehq/wasm/mainnet.js';
|
|
2
|
+
import { ProvingKey, VerifyingKey, ViewKey, ComputeKey, Address, PrivateKeyCiphertext, PrivateKey, RecordCiphertext, EncryptionToolkit, Group, Metadata, Program, Plaintext, Transaction, ProvingRequest, RecordPlaintext, Field, Poseidon4, ProgramImports, ProgramManager as ProgramManager$1, ExecutionRequest, stringToField, verifyFunctionExecution, Value, Proof, Signature } from '@provablehq/wasm/mainnet.js';
|
|
3
|
+
export { Address, Authorization, BHP1024, BHP256, BHP512, BHP768, Boolean, Ciphertext, ComputeKey, DynamicRecord, EncryptionToolkit, ExecutionRequest, ExecutionResponse, Field, Execution as FunctionExecution, GraphKey, Group, I128, I16, I32, I64, I8, OfflineQuery, Pedersen128, Pedersen64, Plaintext, Poseidon2, Poseidon4, Poseidon8, PrivateKey, PrivateKeyCiphertext, Program, ProgramImports as ProgramImportsBuilder, ProgramManager as ProgramManagerBase, Proof, ProvingKey, ProvingRequest, RecordCiphertext, RecordPlaintext, Scalar, Signature, Transaction, Transition, U128, U16, U32, U64, U8, Value, VerifyingKey, ViewKey, getOrInitConsensusVersionTestHeights, initThreadPool, snarkVerify, snarkVerifyBatch, stringToField, verifyFunctionExecution } from '@provablehq/wasm/mainnet.js';
|
|
4
4
|
import sodium from 'libsodium-wrappers';
|
|
5
5
|
import { bech32m } from '@scure/base';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Error thrown when a key locator is invalid for filesystem use.
|
|
9
|
+
* Used to prevent path traversal and other filesystem injection when deriving paths from locators.
|
|
10
|
+
*
|
|
11
|
+
* @extends Error
|
|
12
|
+
*/
|
|
13
|
+
class InvalidLocatorError extends Error {
|
|
14
|
+
locator;
|
|
15
|
+
reason;
|
|
16
|
+
/**
|
|
17
|
+
* @param message - Human-readable description of the validation failure.
|
|
18
|
+
* @param locator - The invalid locator string that failed validation.
|
|
19
|
+
* @param reason - Machine-readable reason code for the failure.
|
|
20
|
+
*/
|
|
21
|
+
constructor(message, locator, reason) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.locator = locator;
|
|
24
|
+
this.reason = reason;
|
|
25
|
+
this.name = "InvalidLocatorError";
|
|
26
|
+
Object.setPrototypeOf(this, InvalidLocatorError.prototype);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Error thrown when there is a mismatch between expected and actual key metadata.
|
|
32
|
+
* This can occur during verification of either the checksum or size of a key.
|
|
33
|
+
*
|
|
34
|
+
* @extends Error
|
|
35
|
+
*/
|
|
36
|
+
class KeyVerificationError extends Error {
|
|
37
|
+
locator;
|
|
38
|
+
field;
|
|
39
|
+
expected;
|
|
40
|
+
actual;
|
|
41
|
+
/**
|
|
42
|
+
* Creates a new KeyVerificationError instance (error.name is "ChecksumMismatchError").
|
|
43
|
+
*
|
|
44
|
+
* @param {string} locator - The key locator where the mismatch occurred.
|
|
45
|
+
* @param {"checksum" | "size"} field - The field that failed verification (either "checksum" or "size").
|
|
46
|
+
* @param {string} expected - The expected value of the field.
|
|
47
|
+
* @param {string} actual - The actual value encountered.
|
|
48
|
+
*/
|
|
49
|
+
constructor(locator, field, expected, actual) {
|
|
50
|
+
super(`Key verification ${locator} ${field} mismatch: expected ${expected}, got ${actual}`);
|
|
51
|
+
this.locator = locator;
|
|
52
|
+
this.field = field;
|
|
53
|
+
this.expected = expected;
|
|
54
|
+
this.actual = actual;
|
|
55
|
+
this.name = "ChecksumMismatchError";
|
|
56
|
+
Object.setPrototypeOf(this, KeyVerificationError.prototype);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Computes the SHA-256 checksum of a given set of bytes.
|
|
61
|
+
*
|
|
62
|
+
* @param {Uint8Array} bytes - The bytes to compute the checksum of.
|
|
63
|
+
*/
|
|
64
|
+
async function sha256Hex(bytes) {
|
|
65
|
+
const hash = await crypto.subtle.digest("SHA-256", bytes);
|
|
66
|
+
return Array.from(new Uint8Array(hash))
|
|
67
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
68
|
+
.join("");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* In-memory implementation of KeyVerifier that stores and verifies key fingerprints.
|
|
73
|
+
* Provides functionality to compute and verify cryptographic checksums of keys, storing them
|
|
74
|
+
* in memory for subsequent verification. This implementation is primarily used for testing
|
|
75
|
+
* and development purposes where persistence is not required.
|
|
76
|
+
*
|
|
77
|
+
* Key features:
|
|
78
|
+
* - Computes SHA-256 checksums and sizes for key bytes.
|
|
79
|
+
* - Stores key fingerprints in memory using string locators.
|
|
80
|
+
* - Verifies key bytes against stored or provided fingerprints.
|
|
81
|
+
*
|
|
82
|
+
* @implements {KeyVerifier}
|
|
83
|
+
*/
|
|
84
|
+
class MemKeyVerifier {
|
|
85
|
+
keyStore = {};
|
|
86
|
+
/**
|
|
87
|
+
* Computes and optionally stores key metadata. If a keyFingerprint is provided, this function will verify the computed checksum against it before storing it.
|
|
88
|
+
*
|
|
89
|
+
* @param {KeyMetadata} keyMetadata - Object containing key bytes and optional verification data.
|
|
90
|
+
* @throws {KeyVerificationError} When provided keyFingerprint doesn't match computed values.
|
|
91
|
+
* @returns {Promise<KeyFingerprint>} Computed key metadata.
|
|
92
|
+
*/
|
|
93
|
+
async computeKeyMetadata(keyMetadata) {
|
|
94
|
+
// Compute the metadata from the key bytes
|
|
95
|
+
const computedFingerprint = {
|
|
96
|
+
checksum: await sha256Hex(keyMetadata.keyBytes),
|
|
97
|
+
size: keyMetadata.keyBytes.length
|
|
98
|
+
};
|
|
99
|
+
// If a KeyFingerprint is provided, verify it matches computed values.
|
|
100
|
+
if (keyMetadata.fingerprint) {
|
|
101
|
+
if (keyMetadata.fingerprint.size !== computedFingerprint.size) {
|
|
102
|
+
throw new KeyVerificationError(keyMetadata.locator ? keyMetadata.locator : "", "size", String(keyMetadata.fingerprint.size), String(computedFingerprint.size));
|
|
103
|
+
}
|
|
104
|
+
if (keyMetadata.fingerprint.checksum !== computedFingerprint.checksum) {
|
|
105
|
+
throw new KeyVerificationError(keyMetadata.locator ? keyMetadata.locator : "", "checksum", keyMetadata.fingerprint.checksum, computedFingerprint.checksum);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// If locator is provided, store only the fingerprint
|
|
109
|
+
if (keyMetadata.locator) {
|
|
110
|
+
this.keyStore[keyMetadata.locator] = computedFingerprint;
|
|
111
|
+
}
|
|
112
|
+
return computedFingerprint;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Verifies key bytes against stored or provided metadata. Follows a priority verification scheme:
|
|
116
|
+
* 1. If KeyFingerprint is provided in the metadata, this method verifies against that first.
|
|
117
|
+
* 2. If a locator is provided, attempts to verify against stored fingerprint.
|
|
118
|
+
* 3. If neither is available, throws an error.
|
|
119
|
+
*
|
|
120
|
+
* @param {KeyMetadata} keyMetadata - Object containing the key bytes and optional verification metadata.
|
|
121
|
+
* @throws {Error} When neither fingerprint nor valid locator is provided for verification.
|
|
122
|
+
* @throws {KeyVerificationError} When size or checksum verification fails.
|
|
123
|
+
* @returns {Promise<void>} Promise that resolves when verification succeeds.
|
|
124
|
+
*/
|
|
125
|
+
async verifyKeyBytes(keyMetadata) {
|
|
126
|
+
if (!keyMetadata.keyBytes) {
|
|
127
|
+
throw new Error("Key bytes must be provided for verification.");
|
|
128
|
+
}
|
|
129
|
+
// Compute the fingerprint for the provided bytes.
|
|
130
|
+
const computedFingerprint = await this.computeKeyMetadata({
|
|
131
|
+
keyBytes: keyMetadata.keyBytes
|
|
132
|
+
});
|
|
133
|
+
// Determine which fingerprint to verify against.
|
|
134
|
+
let fingerprintToVerify;
|
|
135
|
+
if (keyMetadata.fingerprint) {
|
|
136
|
+
// If a key fingerprint is provided, use it.
|
|
137
|
+
fingerprintToVerify = keyMetadata.fingerprint;
|
|
138
|
+
}
|
|
139
|
+
else if (keyMetadata.locator) {
|
|
140
|
+
// Otherwise try to get stored fingerprint by locator.
|
|
141
|
+
fingerprintToVerify = this.keyStore[keyMetadata.locator];
|
|
142
|
+
}
|
|
143
|
+
if (!fingerprintToVerify) {
|
|
144
|
+
throw new Error("Either fingerprint or a valid locator must be provided for verification.");
|
|
145
|
+
}
|
|
146
|
+
// Verify the key size.
|
|
147
|
+
if (fingerprintToVerify.size !== computedFingerprint.size) {
|
|
148
|
+
throw new KeyVerificationError(keyMetadata.locator || "", "size", String(fingerprintToVerify.size), String(computedFingerprint.size));
|
|
149
|
+
}
|
|
150
|
+
// Verify the key checksum.
|
|
151
|
+
if (fingerprintToVerify.checksum !== computedFingerprint.checksum) {
|
|
152
|
+
throw new KeyVerificationError(keyMetadata.locator || "", "checksum", fingerprintToVerify.checksum, computedFingerprint.checksum);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Browser-compatible {@link KeyStore} backed by IndexedDB.
|
|
159
|
+
*
|
|
160
|
+
* This is the browser counterpart to {@link LocalFileKeyStore} (which requires Node.js `fs`).
|
|
161
|
+
* It persists proving and verifying keys across page reloads and browser sessions using the
|
|
162
|
+
* IndexedDB API available in all modern browsers and Web Workers.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* import { IndexedDBKeyStore, ProgramManager } from "@provablehq/sdk";
|
|
167
|
+
*
|
|
168
|
+
* const keyStore = new IndexedDBKeyStore();
|
|
169
|
+
* const pm = new ProgramManager();
|
|
170
|
+
* pm.setKeyStore(keyStore);
|
|
171
|
+
* // Keys synthesized during execution are now cached in IndexedDB
|
|
172
|
+
* // and reloaded automatically on subsequent runs.
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
class IndexedDBKeyStore {
|
|
176
|
+
dbName;
|
|
177
|
+
storeName = "keys";
|
|
178
|
+
keyVerifier = new MemKeyVerifier();
|
|
179
|
+
dbPromise = null;
|
|
180
|
+
/**
|
|
181
|
+
* @param dbName IndexedDB database name. Defaults to `"aleo-keystore"`.
|
|
182
|
+
*/
|
|
183
|
+
constructor(dbName = "aleo-keystore") {
|
|
184
|
+
this.dbName = dbName;
|
|
185
|
+
}
|
|
186
|
+
// -------------------------------------------------------
|
|
187
|
+
// IndexedDB helpers
|
|
188
|
+
// -------------------------------------------------------
|
|
189
|
+
/** Opens (or creates) the database, returning a cached promise. */
|
|
190
|
+
openDB() {
|
|
191
|
+
if (this.dbPromise)
|
|
192
|
+
return this.dbPromise;
|
|
193
|
+
this.dbPromise = new Promise((resolve, reject) => {
|
|
194
|
+
const request = indexedDB.open(this.dbName, 1);
|
|
195
|
+
request.onupgradeneeded = () => {
|
|
196
|
+
const db = request.result;
|
|
197
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
198
|
+
db.createObjectStore(this.storeName, { keyPath: "locator" });
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
request.onsuccess = () => resolve(request.result);
|
|
202
|
+
request.onerror = () => {
|
|
203
|
+
this.dbPromise = null;
|
|
204
|
+
reject(request.error);
|
|
205
|
+
};
|
|
206
|
+
request.onblocked = () => {
|
|
207
|
+
this.dbPromise = null;
|
|
208
|
+
reject(new DOMException("Database open blocked", "AbortError"));
|
|
209
|
+
};
|
|
210
|
+
});
|
|
211
|
+
return this.dbPromise;
|
|
212
|
+
}
|
|
213
|
+
/** Runs a single read-write transaction and returns the request result. */
|
|
214
|
+
async tx(mode, fn) {
|
|
215
|
+
const db = await this.openDB();
|
|
216
|
+
return new Promise((resolve, reject) => {
|
|
217
|
+
const txn = db.transaction(this.storeName, mode);
|
|
218
|
+
const store = txn.objectStore(this.storeName);
|
|
219
|
+
const req = fn(store);
|
|
220
|
+
let result;
|
|
221
|
+
req.onsuccess = () => { result = req.result; };
|
|
222
|
+
txn.oncomplete = () => resolve(result);
|
|
223
|
+
txn.onerror = () => reject(txn.error);
|
|
224
|
+
txn.onabort = () => reject(txn.error ?? new DOMException("Transaction aborted", "AbortError"));
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
// -------------------------------------------------------
|
|
228
|
+
// Locator serialization (mirrors LocalFileKeyStore)
|
|
229
|
+
// -------------------------------------------------------
|
|
230
|
+
validateComponent(value, label) {
|
|
231
|
+
if (value === "" || value === ".") {
|
|
232
|
+
throw new InvalidLocatorError(`KeyLocator ${label} must not be empty or "." (got "${value}")`, value, "reserved_name");
|
|
233
|
+
}
|
|
234
|
+
if (value.includes("..")) {
|
|
235
|
+
throw new InvalidLocatorError(`KeyLocator ${label} must not contain ".." (got "${value}")`, value, "path_traversal");
|
|
236
|
+
}
|
|
237
|
+
if (value.includes("/") || value.includes("\\") || value.includes("\0")) {
|
|
238
|
+
throw new InvalidLocatorError(`KeyLocator ${label} must not contain path separators or null bytes (got "${value}")`, value, "path_separator");
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
validateNonNegative(value, label) {
|
|
242
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
243
|
+
throw new InvalidLocatorError(`KeyLocator ${label} must be a non-negative integer (got ${value})`, String(value), "negative_value");
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
serializeLocator(locator) {
|
|
247
|
+
this.validateComponent(locator.program, "program");
|
|
248
|
+
this.validateComponent(locator.functionName, "functionName");
|
|
249
|
+
this.validateComponent(locator.network, "network");
|
|
250
|
+
this.validateNonNegative(locator.edition, "edition");
|
|
251
|
+
this.validateNonNegative(locator.amendment, "amendment");
|
|
252
|
+
const base = `${locator.program}.${locator.functionName}.e${locator.edition}.a${locator.amendment}.${locator.network}.${locator.keyType}`;
|
|
253
|
+
if (locator.keyType === "translation") {
|
|
254
|
+
this.validateComponent(locator.recordName, "recordName");
|
|
255
|
+
this.validateNonNegative(locator.recordInputPosition, "recordInputPosition");
|
|
256
|
+
return `${base}.${locator.recordName}.${locator.recordInputPosition}`;
|
|
257
|
+
}
|
|
258
|
+
return base;
|
|
259
|
+
}
|
|
260
|
+
checksumToFingerprint(checksum, keyBytes) {
|
|
261
|
+
if (!checksum)
|
|
262
|
+
return undefined;
|
|
263
|
+
return { checksum, size: keyBytes.length };
|
|
264
|
+
}
|
|
265
|
+
// -------------------------------------------------------
|
|
266
|
+
// KeyStore interface
|
|
267
|
+
// -------------------------------------------------------
|
|
268
|
+
async getKeyBytes(locator) {
|
|
269
|
+
const key = this.serializeLocator(locator);
|
|
270
|
+
const record = await this.tx("readonly", (store) => store.get(key));
|
|
271
|
+
if (!record)
|
|
272
|
+
return null;
|
|
273
|
+
const fingerprint = this.checksumToFingerprint(locator.checksum, record.bytes) ?? record.metadata;
|
|
274
|
+
if (fingerprint) {
|
|
275
|
+
await this.keyVerifier.verifyKeyBytes({
|
|
276
|
+
keyBytes: record.bytes,
|
|
277
|
+
locator: key,
|
|
278
|
+
fingerprint,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
return record.bytes;
|
|
282
|
+
}
|
|
283
|
+
async getProvingKey(locator) {
|
|
284
|
+
const bytes = await this.getKeyBytes(locator);
|
|
285
|
+
if (!bytes)
|
|
286
|
+
return null;
|
|
287
|
+
return ProvingKey.fromBytes(bytes);
|
|
288
|
+
}
|
|
289
|
+
async getVerifyingKey(locator) {
|
|
290
|
+
const bytes = await this.getKeyBytes(locator);
|
|
291
|
+
if (!bytes)
|
|
292
|
+
return null;
|
|
293
|
+
return VerifyingKey.fromBytes(bytes);
|
|
294
|
+
}
|
|
295
|
+
async setKeys(proverLocator, verifierLocator, keys) {
|
|
296
|
+
const proverKey = this.serializeLocator(proverLocator);
|
|
297
|
+
const verifierKey = this.serializeLocator(verifierLocator);
|
|
298
|
+
const [provingKey, verifyingKey] = keys;
|
|
299
|
+
const [provingKeyBytes, verifyingKeyBytes] = [
|
|
300
|
+
provingKey.toBytes(),
|
|
301
|
+
verifyingKey.toBytes(),
|
|
302
|
+
];
|
|
303
|
+
const [proverFingerprint, verifierFingerprint] = await Promise.all([
|
|
304
|
+
this.keyVerifier.computeKeyMetadata({
|
|
305
|
+
keyBytes: provingKeyBytes,
|
|
306
|
+
locator: proverKey,
|
|
307
|
+
fingerprint: this.checksumToFingerprint(proverLocator.checksum, provingKeyBytes),
|
|
308
|
+
}),
|
|
309
|
+
this.keyVerifier.computeKeyMetadata({
|
|
310
|
+
keyBytes: verifyingKeyBytes,
|
|
311
|
+
locator: verifierKey,
|
|
312
|
+
fingerprint: this.checksumToFingerprint(verifierLocator.checksum, verifyingKeyBytes),
|
|
313
|
+
}),
|
|
314
|
+
]);
|
|
315
|
+
const proverRecord = { locator: proverKey, bytes: provingKeyBytes, metadata: proverFingerprint };
|
|
316
|
+
const verifierRecord = { locator: verifierKey, bytes: verifyingKeyBytes, metadata: verifierFingerprint };
|
|
317
|
+
// Write both in a single transaction for atomicity.
|
|
318
|
+
const db = await this.openDB();
|
|
319
|
+
await new Promise((resolve, reject) => {
|
|
320
|
+
const txn = db.transaction(this.storeName, "readwrite");
|
|
321
|
+
const store = txn.objectStore(this.storeName);
|
|
322
|
+
store.put(proverRecord);
|
|
323
|
+
store.put(verifierRecord);
|
|
324
|
+
txn.oncomplete = () => resolve();
|
|
325
|
+
txn.onerror = () => reject(txn.error);
|
|
326
|
+
txn.onabort = () => reject(txn.error ?? new DOMException("Transaction aborted", "AbortError"));
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
async setKeyBytes(keyBytes, locator) {
|
|
330
|
+
const key = this.serializeLocator(locator);
|
|
331
|
+
const computedMetadata = await this.keyVerifier.computeKeyMetadata({
|
|
332
|
+
keyBytes,
|
|
333
|
+
locator: key,
|
|
334
|
+
fingerprint: this.checksumToFingerprint(locator.checksum, keyBytes),
|
|
335
|
+
});
|
|
336
|
+
const record = { locator: key, bytes: keyBytes, metadata: computedMetadata };
|
|
337
|
+
await this.tx("readwrite", (store) => store.put(record));
|
|
338
|
+
}
|
|
339
|
+
async getKeyMetadata(locator) {
|
|
340
|
+
const key = this.serializeLocator(locator);
|
|
341
|
+
const record = await this.tx("readonly", (store) => store.get(key));
|
|
342
|
+
return record?.metadata ?? null;
|
|
343
|
+
}
|
|
344
|
+
async has(locator) {
|
|
345
|
+
const key = this.serializeLocator(locator);
|
|
346
|
+
const count = await this.tx("readonly", (store) => store.count(IDBKeyRange.only(key)));
|
|
347
|
+
return count > 0;
|
|
348
|
+
}
|
|
349
|
+
async delete(locator) {
|
|
350
|
+
const key = this.serializeLocator(locator);
|
|
351
|
+
await this.tx("readwrite", (store) => store.delete(key));
|
|
352
|
+
}
|
|
353
|
+
async clear() {
|
|
354
|
+
await this.tx("readwrite", (store) => store.clear());
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
7
358
|
await sodium.ready;
|
|
8
359
|
/**
|
|
9
360
|
* Encrypt an authorization with a libsodium cryptobox public key.
|
|
@@ -849,7 +1200,7 @@ class AleoNetworkClient {
|
|
|
849
1200
|
else {
|
|
850
1201
|
this.headers = {
|
|
851
1202
|
// This is replaced by the actual version by a Rollup plugin
|
|
852
|
-
"X-Aleo-SDK-Version": "0.10.1",
|
|
1203
|
+
"X-Aleo-SDK-Version": "0.10.2-rc.1",
|
|
853
1204
|
"X-Aleo-environment": environment(),
|
|
854
1205
|
};
|
|
855
1206
|
}
|
|
@@ -865,7 +1216,7 @@ class AleoNetworkClient {
|
|
|
865
1216
|
else {
|
|
866
1217
|
this.headers = {
|
|
867
1218
|
// This is replaced by the actual version by a Rollup plugin
|
|
868
|
-
"X-Aleo-SDK-Version": "0.10.1",
|
|
1219
|
+
"X-Aleo-SDK-Version": "0.10.2-rc.1",
|
|
869
1220
|
"X-Aleo-environment": environment(),
|
|
870
1221
|
};
|
|
871
1222
|
}
|
|
@@ -1679,6 +2030,30 @@ class AleoNetworkClient {
|
|
|
1679
2030
|
this.ctx = {};
|
|
1680
2031
|
}
|
|
1681
2032
|
}
|
|
2033
|
+
/**
|
|
2034
|
+
* Returns the current edition and amendment count for a program.
|
|
2035
|
+
*
|
|
2036
|
+
* @param {string} programId - The program ID (e.g. "hello_hello.aleo")
|
|
2037
|
+
* @returns {{ program_id: string, edition: number, amendment_count: number }}
|
|
2038
|
+
*
|
|
2039
|
+
* @example
|
|
2040
|
+
* const networkClient = new AleoNetworkClient("https://api.provable.com/v2");
|
|
2041
|
+
* const info = await networkClient.getProgramAmendmentCount("hello_hello.aleo");
|
|
2042
|
+
* console.log(info.edition, info.amendment_count);
|
|
2043
|
+
*/
|
|
2044
|
+
async getProgramAmendmentCount(programId) {
|
|
2045
|
+
try {
|
|
2046
|
+
this.ctx = { "X-ALEO-METHOD": "getProgramAmendmentCount" };
|
|
2047
|
+
const raw = await this.fetchRaw("/programs/" + programId + "/amendment_count");
|
|
2048
|
+
return JSON.parse(raw);
|
|
2049
|
+
}
|
|
2050
|
+
catch (error) {
|
|
2051
|
+
throw new Error(`Error fetching amendment count for ${programId}: ${error}`);
|
|
2052
|
+
}
|
|
2053
|
+
finally {
|
|
2054
|
+
this.ctx = {};
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
1682
2057
|
/**
|
|
1683
2058
|
* Returns a program object from a program ID or program source code.
|
|
1684
2059
|
*
|
|
@@ -2958,200 +3333,50 @@ class AleoKeyProvider {
|
|
|
2958
3333
|
return CREDITS_PROGRAM_KEYS.bond_validator.verifyingKey();
|
|
2959
3334
|
case CREDITS_PROGRAM_KEYS.claim_unbond_public.verifier:
|
|
2960
3335
|
return CREDITS_PROGRAM_KEYS.claim_unbond_public.verifyingKey();
|
|
2961
|
-
case CREDITS_PROGRAM_KEYS.fee_private.verifier:
|
|
2962
|
-
return CREDITS_PROGRAM_KEYS.fee_private.verifyingKey();
|
|
2963
|
-
case CREDITS_PROGRAM_KEYS.fee_public.verifier:
|
|
2964
|
-
return CREDITS_PROGRAM_KEYS.fee_public.verifyingKey();
|
|
2965
|
-
case CREDITS_PROGRAM_KEYS.inclusion.verifier:
|
|
2966
|
-
return CREDITS_PROGRAM_KEYS.inclusion.verifyingKey();
|
|
2967
|
-
case CREDITS_PROGRAM_KEYS.join.verifier:
|
|
2968
|
-
return CREDITS_PROGRAM_KEYS.join.verifyingKey();
|
|
2969
|
-
case CREDITS_PROGRAM_KEYS.set_validator_state.verifier:
|
|
2970
|
-
return CREDITS_PROGRAM_KEYS.set_validator_state.verifyingKey();
|
|
2971
|
-
case CREDITS_PROGRAM_KEYS.split.verifier:
|
|
2972
|
-
return CREDITS_PROGRAM_KEYS.split.verifyingKey();
|
|
2973
|
-
case CREDITS_PROGRAM_KEYS.transfer_private.verifier:
|
|
2974
|
-
return CREDITS_PROGRAM_KEYS.transfer_private.verifyingKey();
|
|
2975
|
-
case CREDITS_PROGRAM_KEYS.transfer_private_to_public.verifier:
|
|
2976
|
-
return CREDITS_PROGRAM_KEYS.transfer_private_to_public.verifyingKey();
|
|
2977
|
-
case CREDITS_PROGRAM_KEYS.transfer_public.verifier:
|
|
2978
|
-
return CREDITS_PROGRAM_KEYS.transfer_public.verifyingKey();
|
|
2979
|
-
case CREDITS_PROGRAM_KEYS.transfer_public_as_signer.verifier:
|
|
2980
|
-
return CREDITS_PROGRAM_KEYS.transfer_public_as_signer.verifyingKey();
|
|
2981
|
-
case CREDITS_PROGRAM_KEYS.transfer_public_to_private.verifier:
|
|
2982
|
-
return CREDITS_PROGRAM_KEYS.transfer_public_to_private.verifyingKey();
|
|
2983
|
-
case CREDITS_PROGRAM_KEYS.unbond_public.verifier:
|
|
2984
|
-
return CREDITS_PROGRAM_KEYS.unbond_public.verifyingKey();
|
|
2985
|
-
default:
|
|
2986
|
-
try {
|
|
2987
|
-
/// Try to fetch the verifying key from the network as a string
|
|
2988
|
-
const response = await get(verifierUri);
|
|
2989
|
-
const text = await response.text();
|
|
2990
|
-
return VerifyingKey.fromString(text);
|
|
2991
|
-
}
|
|
2992
|
-
catch (e) {
|
|
2993
|
-
/// If that fails, try to fetch the verifying key from the network as bytes
|
|
2994
|
-
try {
|
|
2995
|
-
return (VerifyingKey.fromBytes(await this.fetchBytes(verifierUri)));
|
|
2996
|
-
}
|
|
2997
|
-
catch (inner) {
|
|
2998
|
-
throw new Error("Invalid verifying key. Error: " + inner.message);
|
|
2999
|
-
}
|
|
3000
|
-
}
|
|
3001
|
-
}
|
|
3002
|
-
}
|
|
3003
|
-
unBondPublicKeys() {
|
|
3004
|
-
return this.fetchCreditsKeys(CREDITS_PROGRAM_KEYS.unbond_public);
|
|
3005
|
-
}
|
|
3006
|
-
}
|
|
3007
|
-
|
|
3008
|
-
/**
|
|
3009
|
-
* Error thrown when there is a mismatch between expected and actual key metadata.
|
|
3010
|
-
* This can occur during verification of either the checksum or size of a key.
|
|
3011
|
-
*
|
|
3012
|
-
* @extends Error
|
|
3013
|
-
*/
|
|
3014
|
-
class KeyVerificationError extends Error {
|
|
3015
|
-
locator;
|
|
3016
|
-
field;
|
|
3017
|
-
expected;
|
|
3018
|
-
actual;
|
|
3019
|
-
/**
|
|
3020
|
-
* Creates a new KeyVerificationError instance (error.name is "ChecksumMismatchError").
|
|
3021
|
-
*
|
|
3022
|
-
* @param {string} locator - The key locator where the mismatch occurred.
|
|
3023
|
-
* @param {"checksum" | "size"} field - The field that failed verification (either "checksum" or "size").
|
|
3024
|
-
* @param {string} expected - The expected value of the field.
|
|
3025
|
-
* @param {string} actual - The actual value encountered.
|
|
3026
|
-
*/
|
|
3027
|
-
constructor(locator, field, expected, actual) {
|
|
3028
|
-
super(`Key verification ${locator} ${field} mismatch: expected ${expected}, got ${actual}`);
|
|
3029
|
-
this.locator = locator;
|
|
3030
|
-
this.field = field;
|
|
3031
|
-
this.expected = expected;
|
|
3032
|
-
this.actual = actual;
|
|
3033
|
-
this.name = "ChecksumMismatchError";
|
|
3034
|
-
Object.setPrototypeOf(this, KeyVerificationError.prototype);
|
|
3035
|
-
}
|
|
3036
|
-
}
|
|
3037
|
-
/**
|
|
3038
|
-
* Computes the SHA-256 checksum of a given set of bytes.
|
|
3039
|
-
*
|
|
3040
|
-
* @param {Uint8Array} bytes - The bytes to compute the checksum of.
|
|
3041
|
-
*/
|
|
3042
|
-
async function sha256Hex(bytes) {
|
|
3043
|
-
const hash = await crypto.subtle.digest("SHA-256", bytes);
|
|
3044
|
-
return Array.from(new Uint8Array(hash))
|
|
3045
|
-
.map((b) => b.toString(16).padStart(2, "0"))
|
|
3046
|
-
.join("");
|
|
3047
|
-
}
|
|
3048
|
-
|
|
3049
|
-
/**
|
|
3050
|
-
* In-memory implementation of KeyVerifier that stores and verifies key fingerprints.
|
|
3051
|
-
* Provides functionality to compute and verify cryptographic checksums of keys, storing them
|
|
3052
|
-
* in memory for subsequent verification. This implementation is primarily used for testing
|
|
3053
|
-
* and development purposes where persistence is not required.
|
|
3054
|
-
*
|
|
3055
|
-
* Key features:
|
|
3056
|
-
* - Computes SHA-256 checksums and sizes for key bytes.
|
|
3057
|
-
* - Stores key fingerprints in memory using string locators.
|
|
3058
|
-
* - Verifies key bytes against stored or provided fingerprints.
|
|
3059
|
-
*
|
|
3060
|
-
* @implements {KeyVerifier}
|
|
3061
|
-
*/
|
|
3062
|
-
class MemKeyVerifier {
|
|
3063
|
-
keyStore = {};
|
|
3064
|
-
/**
|
|
3065
|
-
* Computes and optionally stores key metadata. If a keyFingerprint is provided, this function will verify the computed checksum against it before storing it.
|
|
3066
|
-
*
|
|
3067
|
-
* @param {KeyMetadata} keyMetadata - Object containing key bytes and optional verification data.
|
|
3068
|
-
* @throws {KeyVerificationError} When provided keyFingerprint doesn't match computed values.
|
|
3069
|
-
* @returns {Promise<KeyFingerprint>} Computed key metadata.
|
|
3070
|
-
*/
|
|
3071
|
-
async computeKeyMetadata(keyMetadata) {
|
|
3072
|
-
// Compute the metadata from the key bytes
|
|
3073
|
-
const computedFingerprint = {
|
|
3074
|
-
checksum: await sha256Hex(keyMetadata.keyBytes),
|
|
3075
|
-
size: keyMetadata.keyBytes.length
|
|
3076
|
-
};
|
|
3077
|
-
// If a KeyFingerprint is provided, verify it matches computed values.
|
|
3078
|
-
if (keyMetadata.fingerprint) {
|
|
3079
|
-
if (keyMetadata.fingerprint.size !== computedFingerprint.size) {
|
|
3080
|
-
throw new KeyVerificationError(keyMetadata.locator ? keyMetadata.locator : "", "size", String(keyMetadata.fingerprint.size), String(computedFingerprint.size));
|
|
3081
|
-
}
|
|
3082
|
-
if (keyMetadata.fingerprint.checksum !== computedFingerprint.checksum) {
|
|
3083
|
-
throw new KeyVerificationError(keyMetadata.locator ? keyMetadata.locator : "", "checksum", keyMetadata.fingerprint.checksum, computedFingerprint.checksum);
|
|
3084
|
-
}
|
|
3085
|
-
}
|
|
3086
|
-
// If locator is provided, store only the fingerprint
|
|
3087
|
-
if (keyMetadata.locator) {
|
|
3088
|
-
this.keyStore[keyMetadata.locator] = computedFingerprint;
|
|
3089
|
-
}
|
|
3090
|
-
return computedFingerprint;
|
|
3091
|
-
}
|
|
3092
|
-
/**
|
|
3093
|
-
* Verifies key bytes against stored or provided metadata. Follows a priority verification scheme:
|
|
3094
|
-
* 1. If KeyFingerprint is provided in the metadata, this method verifies against that first.
|
|
3095
|
-
* 2. If a locator is provided, attempts to verify against stored fingerprint.
|
|
3096
|
-
* 3. If neither is available, throws an error.
|
|
3097
|
-
*
|
|
3098
|
-
* @param {KeyMetadata} keyMetadata - Object containing the key bytes and optional verification metadata.
|
|
3099
|
-
* @throws {Error} When neither fingerprint nor valid locator is provided for verification.
|
|
3100
|
-
* @throws {KeyVerificationError} When size or checksum verification fails.
|
|
3101
|
-
* @returns {Promise<void>} Promise that resolves when verification succeeds.
|
|
3102
|
-
*/
|
|
3103
|
-
async verifyKeyBytes(keyMetadata) {
|
|
3104
|
-
if (!keyMetadata.keyBytes) {
|
|
3105
|
-
throw new Error("Key bytes must be provided for verification.");
|
|
3106
|
-
}
|
|
3107
|
-
// Compute the fingerprint for the provided bytes.
|
|
3108
|
-
const computedFingerprint = await this.computeKeyMetadata({
|
|
3109
|
-
keyBytes: keyMetadata.keyBytes
|
|
3110
|
-
});
|
|
3111
|
-
// Determine which fingerprint to verify against.
|
|
3112
|
-
let fingerprintToVerify;
|
|
3113
|
-
if (keyMetadata.fingerprint) {
|
|
3114
|
-
// If a key fingerprint is provided, use it.
|
|
3115
|
-
fingerprintToVerify = keyMetadata.fingerprint;
|
|
3116
|
-
}
|
|
3117
|
-
else if (keyMetadata.locator) {
|
|
3118
|
-
// Otherwise try to get stored fingerprint by locator.
|
|
3119
|
-
fingerprintToVerify = this.keyStore[keyMetadata.locator];
|
|
3120
|
-
}
|
|
3121
|
-
if (!fingerprintToVerify) {
|
|
3122
|
-
throw new Error("Either fingerprint or a valid locator must be provided for verification.");
|
|
3123
|
-
}
|
|
3124
|
-
// Verify the key size.
|
|
3125
|
-
if (fingerprintToVerify.size !== computedFingerprint.size) {
|
|
3126
|
-
throw new KeyVerificationError(keyMetadata.locator || "", "size", String(fingerprintToVerify.size), String(computedFingerprint.size));
|
|
3127
|
-
}
|
|
3128
|
-
// Verify the key checksum.
|
|
3129
|
-
if (fingerprintToVerify.checksum !== computedFingerprint.checksum) {
|
|
3130
|
-
throw new KeyVerificationError(keyMetadata.locator || "", "checksum", fingerprintToVerify.checksum, computedFingerprint.checksum);
|
|
3336
|
+
case CREDITS_PROGRAM_KEYS.fee_private.verifier:
|
|
3337
|
+
return CREDITS_PROGRAM_KEYS.fee_private.verifyingKey();
|
|
3338
|
+
case CREDITS_PROGRAM_KEYS.fee_public.verifier:
|
|
3339
|
+
return CREDITS_PROGRAM_KEYS.fee_public.verifyingKey();
|
|
3340
|
+
case CREDITS_PROGRAM_KEYS.inclusion.verifier:
|
|
3341
|
+
return CREDITS_PROGRAM_KEYS.inclusion.verifyingKey();
|
|
3342
|
+
case CREDITS_PROGRAM_KEYS.join.verifier:
|
|
3343
|
+
return CREDITS_PROGRAM_KEYS.join.verifyingKey();
|
|
3344
|
+
case CREDITS_PROGRAM_KEYS.set_validator_state.verifier:
|
|
3345
|
+
return CREDITS_PROGRAM_KEYS.set_validator_state.verifyingKey();
|
|
3346
|
+
case CREDITS_PROGRAM_KEYS.split.verifier:
|
|
3347
|
+
return CREDITS_PROGRAM_KEYS.split.verifyingKey();
|
|
3348
|
+
case CREDITS_PROGRAM_KEYS.transfer_private.verifier:
|
|
3349
|
+
return CREDITS_PROGRAM_KEYS.transfer_private.verifyingKey();
|
|
3350
|
+
case CREDITS_PROGRAM_KEYS.transfer_private_to_public.verifier:
|
|
3351
|
+
return CREDITS_PROGRAM_KEYS.transfer_private_to_public.verifyingKey();
|
|
3352
|
+
case CREDITS_PROGRAM_KEYS.transfer_public.verifier:
|
|
3353
|
+
return CREDITS_PROGRAM_KEYS.transfer_public.verifyingKey();
|
|
3354
|
+
case CREDITS_PROGRAM_KEYS.transfer_public_as_signer.verifier:
|
|
3355
|
+
return CREDITS_PROGRAM_KEYS.transfer_public_as_signer.verifyingKey();
|
|
3356
|
+
case CREDITS_PROGRAM_KEYS.transfer_public_to_private.verifier:
|
|
3357
|
+
return CREDITS_PROGRAM_KEYS.transfer_public_to_private.verifyingKey();
|
|
3358
|
+
case CREDITS_PROGRAM_KEYS.unbond_public.verifier:
|
|
3359
|
+
return CREDITS_PROGRAM_KEYS.unbond_public.verifyingKey();
|
|
3360
|
+
default:
|
|
3361
|
+
try {
|
|
3362
|
+
/// Try to fetch the verifying key from the network as a string
|
|
3363
|
+
const response = await get(verifierUri);
|
|
3364
|
+
const text = await response.text();
|
|
3365
|
+
return VerifyingKey.fromString(text);
|
|
3366
|
+
}
|
|
3367
|
+
catch (e) {
|
|
3368
|
+
/// If that fails, try to fetch the verifying key from the network as bytes
|
|
3369
|
+
try {
|
|
3370
|
+
return (VerifyingKey.fromBytes(await this.fetchBytes(verifierUri)));
|
|
3371
|
+
}
|
|
3372
|
+
catch (inner) {
|
|
3373
|
+
throw new Error("Invalid verifying key. Error: " + inner.message);
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3131
3376
|
}
|
|
3132
3377
|
}
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
/**
|
|
3136
|
-
* Error thrown when a key locator is invalid for filesystem use.
|
|
3137
|
-
* Used to prevent path traversal and other filesystem injection when deriving paths from locators.
|
|
3138
|
-
*
|
|
3139
|
-
* @extends Error
|
|
3140
|
-
*/
|
|
3141
|
-
class InvalidLocatorError extends Error {
|
|
3142
|
-
locator;
|
|
3143
|
-
reason;
|
|
3144
|
-
/**
|
|
3145
|
-
* @param message - Human-readable description of the validation failure.
|
|
3146
|
-
* @param locator - The invalid locator string that failed validation.
|
|
3147
|
-
* @param reason - Machine-readable reason code for the failure.
|
|
3148
|
-
*/
|
|
3149
|
-
constructor(message, locator, reason) {
|
|
3150
|
-
super(message);
|
|
3151
|
-
this.locator = locator;
|
|
3152
|
-
this.reason = reason;
|
|
3153
|
-
this.name = "InvalidLocatorError";
|
|
3154
|
-
Object.setPrototypeOf(this, InvalidLocatorError.prototype);
|
|
3378
|
+
unBondPublicKeys() {
|
|
3379
|
+
return this.fetchCreditsKeys(CREDITS_PROGRAM_KEYS.unbond_public);
|
|
3155
3380
|
}
|
|
3156
3381
|
}
|
|
3157
3382
|
|
|
@@ -5151,17 +5376,19 @@ class ProgramManager {
|
|
|
5151
5376
|
networkClient;
|
|
5152
5377
|
recordProvider;
|
|
5153
5378
|
inclusionKeysLoaded = false;
|
|
5379
|
+
_keyStore;
|
|
5154
5380
|
/** Create a new instance of the ProgramManager
|
|
5155
5381
|
*
|
|
5156
5382
|
* @param { string | undefined } host A host uri running the official Aleo API
|
|
5157
5383
|
* @param { FunctionKeyProvider | undefined } keyProvider A key provider that implements {@link FunctionKeyProvider} interface
|
|
5158
5384
|
* @param { RecordProvider | undefined } recordProvider A record provider that implements {@link RecordProvider} interface
|
|
5159
5385
|
*/
|
|
5160
|
-
constructor(host, keyProvider, recordProvider, networkClientOptions) {
|
|
5386
|
+
constructor(host, keyProvider, recordProvider, networkClientOptions, keyStore) {
|
|
5161
5387
|
this.host = host ? host : "https://api.provable.com/v2";
|
|
5162
5388
|
this.networkClient = new AleoNetworkClient(this.host, networkClientOptions);
|
|
5163
5389
|
this.keyProvider = keyProvider ? keyProvider : new AleoKeyProvider();
|
|
5164
5390
|
this.recordProvider = recordProvider;
|
|
5391
|
+
this._keyStore = keyStore;
|
|
5165
5392
|
}
|
|
5166
5393
|
/**
|
|
5167
5394
|
* Check if the fee is sufficient to pay for the transaction
|
|
@@ -5205,6 +5432,320 @@ class ProgramManager {
|
|
|
5205
5432
|
setRecordProvider(recordProvider) {
|
|
5206
5433
|
this.recordProvider = recordProvider;
|
|
5207
5434
|
}
|
|
5435
|
+
/**
|
|
5436
|
+
* Set the key store for automatic key caching across executions.
|
|
5437
|
+
*
|
|
5438
|
+
* @param {KeyStore} keyStore
|
|
5439
|
+
*/
|
|
5440
|
+
setKeyStore(keyStore) {
|
|
5441
|
+
this._keyStore = keyStore;
|
|
5442
|
+
}
|
|
5443
|
+
/**
|
|
5444
|
+
* Build a ProgramImportsBuilder from a program and its imports.
|
|
5445
|
+
* Fetches imports from the network if not provided, resolves transitive
|
|
5446
|
+
* dependencies, and optionally pre-loads cached keys from the KeyStore.
|
|
5447
|
+
*
|
|
5448
|
+
* @param loadKeys When true (default), loads cached proving/verifying keys
|
|
5449
|
+
* from the KeyStore into the builder. Set to false for authorization and
|
|
5450
|
+
* proving request paths where keys are not synthesized.
|
|
5451
|
+
*/
|
|
5452
|
+
async buildProgramImports(program, imports, loadKeys = true) {
|
|
5453
|
+
const builder = new ProgramImports();
|
|
5454
|
+
const programSource = typeof program === "string" ? program : program.toString();
|
|
5455
|
+
const programObj = Program.fromString(programSource);
|
|
5456
|
+
const importNames = programObj.getImports();
|
|
5457
|
+
if (importNames.length === 0 && (!imports || Object.keys(imports).length === 0)) {
|
|
5458
|
+
return { builder, importEditions: new Map() };
|
|
5459
|
+
}
|
|
5460
|
+
let resolvedImports = {};
|
|
5461
|
+
if (importNames.length > 0) {
|
|
5462
|
+
try {
|
|
5463
|
+
resolvedImports = await this.networkClient.getProgramImports(programSource);
|
|
5464
|
+
}
|
|
5465
|
+
catch (e) {
|
|
5466
|
+
console.warn(`Failed to resolve program imports from network: ${e}. Falling back to user-provided imports.`);
|
|
5467
|
+
}
|
|
5468
|
+
}
|
|
5469
|
+
if (imports) {
|
|
5470
|
+
resolvedImports = { ...resolvedImports, ...imports };
|
|
5471
|
+
}
|
|
5472
|
+
// Build a map of which functions each import actually calls,
|
|
5473
|
+
// so we only load keys for functions in the call chain.
|
|
5474
|
+
const calledFunctions = ProgramManager.getCalledFunctions(programSource);
|
|
5475
|
+
// Phase 1: Collect all programs via BFS, discovering transitive imports.
|
|
5476
|
+
// The initial getProgramImports call above already resolves the full
|
|
5477
|
+
// transitive closure via recursive DFS. The BFS loop here only needs
|
|
5478
|
+
// to handle user-provided imports whose transitive deps may not yet be
|
|
5479
|
+
// known. For each unknown import we issue a single getProgram() call
|
|
5480
|
+
// (not a full recursive getProgramImports) and fetch siblings in
|
|
5481
|
+
// parallel to minimize round-trips.
|
|
5482
|
+
const collected = new Map();
|
|
5483
|
+
const collectQueue = Object.entries(resolvedImports).map(([name, src]) => [name, typeof src === "string" ? src : src.toString()]);
|
|
5484
|
+
while (collectQueue.length > 0) {
|
|
5485
|
+
const [name, source] = collectQueue.shift();
|
|
5486
|
+
if (collected.has(name))
|
|
5487
|
+
continue;
|
|
5488
|
+
collected.set(name, source);
|
|
5489
|
+
// Discover transitive imports and collect called functions.
|
|
5490
|
+
// Use regex instead of Program.fromString to avoid a WASM
|
|
5491
|
+
// round-trip per import — we only need import names here.
|
|
5492
|
+
try {
|
|
5493
|
+
const subImports = ProgramManager.getImportNames(source);
|
|
5494
|
+
if (subImports.length > 0) {
|
|
5495
|
+
const transitiveCalls = ProgramManager.getCalledFunctions(source);
|
|
5496
|
+
for (const [tProgram, tFns] of transitiveCalls) {
|
|
5497
|
+
if (!calledFunctions.has(tProgram)) {
|
|
5498
|
+
calledFunctions.set(tProgram, tFns);
|
|
5499
|
+
}
|
|
5500
|
+
else {
|
|
5501
|
+
for (const fn of tFns)
|
|
5502
|
+
calledFunctions.get(tProgram).add(fn);
|
|
5503
|
+
}
|
|
5504
|
+
}
|
|
5505
|
+
// Fetch only unknown transitive imports — the source for
|
|
5506
|
+
// programs already in collected/resolvedImports is known, so
|
|
5507
|
+
// we skip them. Fetches within a BFS level run in parallel.
|
|
5508
|
+
const unknownImports = subImports.filter((id) => !collected.has(id) && !(id in resolvedImports));
|
|
5509
|
+
if (unknownImports.length > 0) {
|
|
5510
|
+
const fetched = await Promise.all(unknownImports.map(async (id) => {
|
|
5511
|
+
try {
|
|
5512
|
+
const src = await this.networkClient.getProgram(id);
|
|
5513
|
+
return [id, src];
|
|
5514
|
+
}
|
|
5515
|
+
catch {
|
|
5516
|
+
return null;
|
|
5517
|
+
}
|
|
5518
|
+
}));
|
|
5519
|
+
for (const entry of fetched) {
|
|
5520
|
+
if (entry && !collected.has(entry[0])) {
|
|
5521
|
+
collectQueue.push(entry);
|
|
5522
|
+
}
|
|
5523
|
+
}
|
|
5524
|
+
}
|
|
5525
|
+
}
|
|
5526
|
+
}
|
|
5527
|
+
catch {
|
|
5528
|
+
// Non-blocking — transitive resolution is best-effort
|
|
5529
|
+
}
|
|
5530
|
+
}
|
|
5531
|
+
// Phase 2: Add programs in topological order (leaves first).
|
|
5532
|
+
// A simple DFS post-order ensures dependencies are added before
|
|
5533
|
+
// dependents, regardless of the order collected entries arrived.
|
|
5534
|
+
const sorted = [];
|
|
5535
|
+
const visited = new Set();
|
|
5536
|
+
const visit = (name) => {
|
|
5537
|
+
if (visited.has(name))
|
|
5538
|
+
return;
|
|
5539
|
+
visited.add(name);
|
|
5540
|
+
const source = collected.get(name);
|
|
5541
|
+
if (!source)
|
|
5542
|
+
return;
|
|
5543
|
+
for (const dep of ProgramManager.getImportNames(source)) {
|
|
5544
|
+
if (collected.has(dep))
|
|
5545
|
+
visit(dep);
|
|
5546
|
+
}
|
|
5547
|
+
sorted.push([name, source]);
|
|
5548
|
+
};
|
|
5549
|
+
for (const [name] of collected)
|
|
5550
|
+
visit(name);
|
|
5551
|
+
// Resolve editions for all imports in parallel (each import may have
|
|
5552
|
+
// a different edition than the top-level program).
|
|
5553
|
+
const importEditions = new Map();
|
|
5554
|
+
await Promise.all(sorted.map(async ([name]) => {
|
|
5555
|
+
importEditions.set(name, await this.resolveEditionAndAmendment(name));
|
|
5556
|
+
}));
|
|
5557
|
+
for (const [name, source] of sorted) {
|
|
5558
|
+
if (builder.contains(name))
|
|
5559
|
+
continue;
|
|
5560
|
+
const { edition: importEdition, amendment: importAmendment } = importEditions.get(name);
|
|
5561
|
+
try {
|
|
5562
|
+
builder.addProgram(name, source, importEdition);
|
|
5563
|
+
}
|
|
5564
|
+
catch (e) {
|
|
5565
|
+
console.warn(`Failed to add import ${name} to builder: ${e}`);
|
|
5566
|
+
continue;
|
|
5567
|
+
}
|
|
5568
|
+
if (loadKeys) {
|
|
5569
|
+
const calledFns = calledFunctions.get(name);
|
|
5570
|
+
await this.loadKeysFromStore(builder, name, calledFns ? [...calledFns] : [], importEdition, importAmendment);
|
|
5571
|
+
}
|
|
5572
|
+
}
|
|
5573
|
+
return { builder, importEditions };
|
|
5574
|
+
}
|
|
5575
|
+
/**
|
|
5576
|
+
* Extract `import program_name.aleo;` names from program source via regex.
|
|
5577
|
+
* Avoids a WASM round-trip compared to Program.fromString + getImports.
|
|
5578
|
+
*/
|
|
5579
|
+
static getImportNames(programSource) {
|
|
5580
|
+
const result = [];
|
|
5581
|
+
const importPattern = /^\s*import\s+([a-zA-Z_][a-zA-Z0-9_]*\.aleo)\s*;/gm;
|
|
5582
|
+
let match;
|
|
5583
|
+
while ((match = importPattern.exec(programSource)) !== null) {
|
|
5584
|
+
result.push(match[1]);
|
|
5585
|
+
}
|
|
5586
|
+
return result;
|
|
5587
|
+
}
|
|
5588
|
+
/**
|
|
5589
|
+
* Parse `call program.aleo/function` instructions from program source
|
|
5590
|
+
* to determine which functions of each import are actually in the call chain.
|
|
5591
|
+
* Returns a map of program name -> set of called function names.
|
|
5592
|
+
*/
|
|
5593
|
+
static getCalledFunctions(programSource) {
|
|
5594
|
+
const result = new Map();
|
|
5595
|
+
const callPattern = /call\s+([a-zA-Z_][a-zA-Z0-9_]*\.aleo)\/([a-zA-Z_][a-zA-Z0-9_]*)/g;
|
|
5596
|
+
let match;
|
|
5597
|
+
while ((match = callPattern.exec(programSource)) !== null) {
|
|
5598
|
+
const programName = match[1];
|
|
5599
|
+
const functionName = match[2];
|
|
5600
|
+
if (!result.has(programName)) {
|
|
5601
|
+
result.set(programName, new Set());
|
|
5602
|
+
}
|
|
5603
|
+
result.get(programName).add(functionName);
|
|
5604
|
+
}
|
|
5605
|
+
return result;
|
|
5606
|
+
}
|
|
5607
|
+
/**
|
|
5608
|
+
* Resolve the active KeyStore, preferring the directly-set _keyStore
|
|
5609
|
+
* over the KeyProvider's keyStore().
|
|
5610
|
+
*/
|
|
5611
|
+
async resolveKeyStore() {
|
|
5612
|
+
if (this._keyStore)
|
|
5613
|
+
return this._keyStore;
|
|
5614
|
+
try {
|
|
5615
|
+
return await this.keyProvider?.keyStore?.();
|
|
5616
|
+
}
|
|
5617
|
+
catch {
|
|
5618
|
+
return undefined;
|
|
5619
|
+
}
|
|
5620
|
+
}
|
|
5621
|
+
/**
|
|
5622
|
+
* Resolve the edition and amendment count for a program from the network.
|
|
5623
|
+
* Returns `{ edition, amendment }` or falls back to `{ edition: 1, amendment: 0 }`.
|
|
5624
|
+
*/
|
|
5625
|
+
async resolveEditionAndAmendment(programName, fallbackEdition) {
|
|
5626
|
+
try {
|
|
5627
|
+
const info = await this.networkClient.getProgramAmendmentCount(programName);
|
|
5628
|
+
return { edition: info.edition, amendment: info.amendment_count };
|
|
5629
|
+
}
|
|
5630
|
+
catch (e) {
|
|
5631
|
+
console.warn(`Error finding edition/amendment for ${programName}. Network response: '${e.message}'. Defaulting to edition ${fallbackEdition ?? 1}, amendment 0.`);
|
|
5632
|
+
return { edition: fallbackEdition ?? 1, amendment: 0 };
|
|
5633
|
+
}
|
|
5634
|
+
}
|
|
5635
|
+
/**
|
|
5636
|
+
* Load cached proving/verifying keys from the KeyStore into a ProgramImportsBuilder.
|
|
5637
|
+
* Only loads keys for the specified functions — returns immediately if
|
|
5638
|
+
* functionNames is empty or undefined.
|
|
5639
|
+
* Resolves edition and amendment from the network for accurate key locator
|
|
5640
|
+
* construction when not explicitly provided.
|
|
5641
|
+
*/
|
|
5642
|
+
async loadKeysFromStore(builder, programName, functionNames, edition, amendment) {
|
|
5643
|
+
if (!functionNames || functionNames.length === 0)
|
|
5644
|
+
return;
|
|
5645
|
+
const keyStore = await this.resolveKeyStore();
|
|
5646
|
+
if (!keyStore)
|
|
5647
|
+
return;
|
|
5648
|
+
// Resolve edition and amendment from the network if not fully provided.
|
|
5649
|
+
if (edition === undefined || amendment === undefined) {
|
|
5650
|
+
const resolved = await this.resolveEditionAndAmendment(programName, edition);
|
|
5651
|
+
edition = edition ?? resolved.edition;
|
|
5652
|
+
amendment = amendment ?? resolved.amendment;
|
|
5653
|
+
}
|
|
5654
|
+
for (const fnName of functionNames) {
|
|
5655
|
+
const pkLocator = provingKeyLocator(programName, fnName, edition, amendment);
|
|
5656
|
+
const vkLocator = verifyingKeyLocator(programName, fnName, edition, amendment);
|
|
5657
|
+
try {
|
|
5658
|
+
const pk = await keyStore.getProvingKey(pkLocator);
|
|
5659
|
+
if (pk)
|
|
5660
|
+
builder.addProvingKey(programName, fnName, pk);
|
|
5661
|
+
const vk = await keyStore.getVerifyingKey(vkLocator);
|
|
5662
|
+
if (vk)
|
|
5663
|
+
builder.addVerifyingKey(programName, fnName, vk);
|
|
5664
|
+
}
|
|
5665
|
+
catch (e) {
|
|
5666
|
+
console.debug(`Failed to load keys for ${programName}/${fnName}: ${e}`);
|
|
5667
|
+
}
|
|
5668
|
+
}
|
|
5669
|
+
}
|
|
5670
|
+
/**
|
|
5671
|
+
* Persist newly synthesized keys from the returned ProgramImportsBuilder
|
|
5672
|
+
* into the KeyStore. Only writes keys that are not already in the store,
|
|
5673
|
+
* avoiding unnecessary writes of large proving keys.
|
|
5674
|
+
* Fetches each program's current edition and amendment count from the network
|
|
5675
|
+
* for accurate key locator construction.
|
|
5676
|
+
*/
|
|
5677
|
+
async persistExtractedKeys(builder, importEditions) {
|
|
5678
|
+
const keyStore = await this.resolveKeyStore();
|
|
5679
|
+
if (!keyStore)
|
|
5680
|
+
return;
|
|
5681
|
+
const programNames = Array.from(builder.programNames());
|
|
5682
|
+
// Reuse editions resolved during buildProgramImports when available,
|
|
5683
|
+
// falling back to parallel network resolution for any missing programs.
|
|
5684
|
+
const editionMap = importEditions ?? new Map();
|
|
5685
|
+
const missing = programNames.filter((name) => !editionMap.has(name));
|
|
5686
|
+
if (missing.length > 0) {
|
|
5687
|
+
await Promise.all(missing.map(async (name) => {
|
|
5688
|
+
editionMap.set(name, await this.resolveEditionAndAmendment(name));
|
|
5689
|
+
}));
|
|
5690
|
+
}
|
|
5691
|
+
for (const programName of programNames) {
|
|
5692
|
+
const { edition, amendment } = editionMap.get(programName);
|
|
5693
|
+
let fns;
|
|
5694
|
+
try {
|
|
5695
|
+
fns = Array.from(builder.functionKeysAvailable(programName));
|
|
5696
|
+
}
|
|
5697
|
+
catch (e) {
|
|
5698
|
+
console.debug(`Failed to query keys for ${programName}: ${e}`);
|
|
5699
|
+
continue;
|
|
5700
|
+
}
|
|
5701
|
+
for (const fnName of fns) {
|
|
5702
|
+
try {
|
|
5703
|
+
const pkLocator = provingKeyLocator(programName, fnName, edition, amendment);
|
|
5704
|
+
const vkLocator = verifyingKeyLocator(programName, fnName, edition, amendment);
|
|
5705
|
+
// Skip keys already in the store.
|
|
5706
|
+
if (await keyStore.has(pkLocator) && await keyStore.has(vkLocator)) {
|
|
5707
|
+
continue;
|
|
5708
|
+
}
|
|
5709
|
+
const pk = builder.getProvingKey(programName, fnName);
|
|
5710
|
+
const vk = builder.getVerifyingKey(programName, fnName);
|
|
5711
|
+
if (pk && vk) {
|
|
5712
|
+
await keyStore.setKeys(pkLocator, vkLocator, [pk, vk]);
|
|
5713
|
+
}
|
|
5714
|
+
}
|
|
5715
|
+
catch (e) {
|
|
5716
|
+
console.debug(`Failed to persist key for ${programName}/${fnName}: ${e}`);
|
|
5717
|
+
}
|
|
5718
|
+
}
|
|
5719
|
+
}
|
|
5720
|
+
}
|
|
5721
|
+
/**
|
|
5722
|
+
* Resolve top-level function keys, checking the KeyStore first and
|
|
5723
|
+
* falling back to the KeyProvider.
|
|
5724
|
+
*/
|
|
5725
|
+
async resolveTopLevelKeys(programName, functionName, keySearchParams, edition, amendment) {
|
|
5726
|
+
const keyStore = await this.resolveKeyStore();
|
|
5727
|
+
if (keyStore) {
|
|
5728
|
+
try {
|
|
5729
|
+
const pkLocator = provingKeyLocator(programName, functionName, edition, amendment);
|
|
5730
|
+
const vkLocator = verifyingKeyLocator(programName, functionName, edition, amendment);
|
|
5731
|
+
if (await keyStore.has(pkLocator) && await keyStore.has(vkLocator)) {
|
|
5732
|
+
const pk = await keyStore.getProvingKey(pkLocator);
|
|
5733
|
+
const vk = await keyStore.getVerifyingKey(vkLocator);
|
|
5734
|
+
if (pk && vk)
|
|
5735
|
+
return [pk, vk];
|
|
5736
|
+
}
|
|
5737
|
+
}
|
|
5738
|
+
catch {
|
|
5739
|
+
// Fall through to KeyProvider
|
|
5740
|
+
}
|
|
5741
|
+
}
|
|
5742
|
+
try {
|
|
5743
|
+
return (await this.keyProvider.functionKeys(keySearchParams));
|
|
5744
|
+
}
|
|
5745
|
+
catch {
|
|
5746
|
+
return undefined;
|
|
5747
|
+
}
|
|
5748
|
+
}
|
|
5208
5749
|
/**
|
|
5209
5750
|
* Set a header in the `AleoNetworkClient`s header map
|
|
5210
5751
|
*
|
|
@@ -5639,15 +6180,9 @@ class ProgramManager {
|
|
|
5639
6180
|
if (programName === undefined) {
|
|
5640
6181
|
programName = programObject.id();
|
|
5641
6182
|
}
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
}
|
|
5646
|
-
catch (e) {
|
|
5647
|
-
console.warn(`Error finding edition for ${programName}. Network response: '${e.message}'. Assuming edition 0.`);
|
|
5648
|
-
edition = 0;
|
|
5649
|
-
}
|
|
5650
|
-
}
|
|
6183
|
+
const resolved = await this.resolveEditionAndAmendment(programName, edition);
|
|
6184
|
+
edition = edition ?? resolved.edition;
|
|
6185
|
+
const { amendment } = resolved;
|
|
5651
6186
|
// Get the private key from the account if it is not provided in the parameters
|
|
5652
6187
|
let executionPrivateKey = privateKey;
|
|
5653
6188
|
if (typeof privateKey === "undefined" &&
|
|
@@ -5668,23 +6203,19 @@ class ProgramManager {
|
|
|
5668
6203
|
logAndThrow(`Error finding fee keys. Key finder response: '${e.message}'. Please ensure your key provider is configured correctly.`);
|
|
5669
6204
|
}
|
|
5670
6205
|
const [feeProvingKey, feeVerifyingKey] = feeKeys;
|
|
5671
|
-
//
|
|
6206
|
+
// Build the ProgramImportsBuilder for import resolution and key caching.
|
|
6207
|
+
const { builder: programImportsBuilder, importEditions } = await this.buildProgramImports(program, imports);
|
|
6208
|
+
// Include the top-level program's already-resolved edition so
|
|
6209
|
+
// persistExtractedKeys doesn't make a redundant network call for it.
|
|
6210
|
+
importEditions.set(programName, { edition: edition, amendment });
|
|
6211
|
+
// If the function proving and verifying keys are not provided, attempt to find them
|
|
5672
6212
|
if (!provingKey || !verifyingKey) {
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
catch (e) {
|
|
5677
|
-
console.log(`Function keys not found. Key finder response: '${e}'. The function keys will be synthesized`);
|
|
5678
|
-
}
|
|
5679
|
-
}
|
|
5680
|
-
// Resolve the program imports if they exist
|
|
5681
|
-
const numberOfImports = programObject.getImports().length;
|
|
5682
|
-
if (numberOfImports > 0 && !imports) {
|
|
5683
|
-
try {
|
|
5684
|
-
imports = (await this.networkClient.getProgramImports(programName));
|
|
6213
|
+
const keys = await this.resolveTopLevelKeys(programName, functionName, keySearchParams, edition, amendment);
|
|
6214
|
+
if (keys) {
|
|
6215
|
+
[provingKey, verifyingKey] = keys;
|
|
5685
6216
|
}
|
|
5686
|
-
|
|
5687
|
-
|
|
6217
|
+
else {
|
|
6218
|
+
console.log("Function keys not found in KeyStore or KeyProvider. The function keys will be synthesized");
|
|
5688
6219
|
}
|
|
5689
6220
|
}
|
|
5690
6221
|
// Get the fee record from the account if it is not provided in the parameters
|
|
@@ -5720,8 +6251,20 @@ class ProgramManager {
|
|
|
5720
6251
|
}
|
|
5721
6252
|
// Auto-convert bare string inputs to field elements where the function expects field type.
|
|
5722
6253
|
const preparedInputs = this.prepareInputs(program, functionName, inputs);
|
|
5723
|
-
// Build an execution transaction
|
|
5724
|
-
|
|
6254
|
+
// Build an execution transaction. Always pass the builder so synthesized
|
|
6255
|
+
// keys (including the top-level program's) are captured via Rc<RefCell<>>
|
|
6256
|
+
// interior mutability and can be persisted to the KeyStore.
|
|
6257
|
+
const result = await ProgramManager$1.buildExecutionTransaction(executionPrivateKey, program, functionName, preparedInputs, priorityFee, feeRecord, this.host, undefined, // imports (legacy Object path — builder handles resolution)
|
|
6258
|
+
provingKey, verifyingKey, feeProvingKey, feeVerifyingKey, offlineQuery, edition, programImportsBuilder?.clone());
|
|
6259
|
+
// The clone passed to WASM shares state with the original via
|
|
6260
|
+
// Rc<RefCell<>> — synthesized keys are already visible.
|
|
6261
|
+
try {
|
|
6262
|
+
await this.persistExtractedKeys(programImportsBuilder, importEditions);
|
|
6263
|
+
}
|
|
6264
|
+
catch (e) {
|
|
6265
|
+
console.debug(`Failed to persist extracted keys: ${e}`);
|
|
6266
|
+
}
|
|
6267
|
+
return result;
|
|
5725
6268
|
}
|
|
5726
6269
|
/**
|
|
5727
6270
|
* Builds an execution transaction for submission to the Aleo network from an Authorization and Fee Authorization.
|
|
@@ -5799,6 +6342,10 @@ class ProgramManager {
|
|
|
5799
6342
|
const feeAuthorization = options.feeAuthorization;
|
|
5800
6343
|
const keySearchParams = options.keySearchParams;
|
|
5801
6344
|
const offlineQuery = options.offlineQuery;
|
|
6345
|
+
let edition = options.edition;
|
|
6346
|
+
const resolved = await this.resolveEditionAndAmendment(programName, edition);
|
|
6347
|
+
edition = edition ?? resolved.edition;
|
|
6348
|
+
const { amendment } = resolved;
|
|
5802
6349
|
let provingKey = options.provingKey;
|
|
5803
6350
|
let verifyingKey = options.verifyingKey;
|
|
5804
6351
|
let program = options.program;
|
|
@@ -5827,24 +6374,17 @@ class ProgramManager {
|
|
|
5827
6374
|
logAndThrow(`Error finding fee keys. Key finder response: '${e.message}'. Please ensure your key provider is configured correctly.`);
|
|
5828
6375
|
}
|
|
5829
6376
|
const [feeProvingKey, feeVerifyingKey] = feeKeys;
|
|
5830
|
-
//
|
|
6377
|
+
// Build the ProgramImportsBuilder for import resolution and key caching.
|
|
6378
|
+
const { builder: programImportsBuilder, importEditions } = await this.buildProgramImports(program, imports);
|
|
6379
|
+
importEditions.set(programName, { edition: edition, amendment });
|
|
6380
|
+
// If the function proving and verifying keys are not provided, attempt to find them.
|
|
5831
6381
|
if (!provingKey || !verifyingKey) {
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
catch (e) {
|
|
5836
|
-
console.log(`Function keys not found. Key finder response: '${e}'. The function keys will be synthesized`);
|
|
5837
|
-
}
|
|
5838
|
-
}
|
|
5839
|
-
// Resolve the program imports if they exist.
|
|
5840
|
-
console.log("Resolving program imports");
|
|
5841
|
-
const numberOfImports = Program.fromString(program).getImports().length;
|
|
5842
|
-
if (numberOfImports > 0 && !imports) {
|
|
5843
|
-
try {
|
|
5844
|
-
imports = (await this.networkClient.getProgramImports(programName));
|
|
6382
|
+
const keys = await this.resolveTopLevelKeys(programName, authorization.functionName(), keySearchParams, edition, amendment);
|
|
6383
|
+
if (keys) {
|
|
6384
|
+
[provingKey, verifyingKey] = keys;
|
|
5845
6385
|
}
|
|
5846
|
-
|
|
5847
|
-
|
|
6386
|
+
else {
|
|
6387
|
+
console.log("Function keys not found in KeyStore or KeyProvider. The function keys will be synthesized");
|
|
5848
6388
|
}
|
|
5849
6389
|
}
|
|
5850
6390
|
// If the offline query exists, add the inclusion key.
|
|
@@ -5860,9 +6400,20 @@ class ProgramManager {
|
|
|
5860
6400
|
logAndThrow(`Inclusion key bytes not loaded, please ensure the program manager is initialized with a KeyProvider that includes the inclusion key.`);
|
|
5861
6401
|
}
|
|
5862
6402
|
}
|
|
5863
|
-
// Build an execution transaction from the authorization.
|
|
5864
|
-
|
|
5865
|
-
|
|
6403
|
+
// Build an execution transaction from the authorization. The
|
|
6404
|
+
// ProgramImportsBuilder is consumed by value (ownership transfers to WASM)
|
|
6405
|
+
// and returned enriched with any keys synthesized during execution.
|
|
6406
|
+
const result = await ProgramManager$1.executeAuthorization(authorization, feeAuthorization, program, provingKey, verifyingKey, feeProvingKey, feeVerifyingKey, undefined, // imports (legacy Object path — builder handles resolution)
|
|
6407
|
+
this.host, offlineQuery, programImportsBuilder?.clone(), edition);
|
|
6408
|
+
// The builder uses interior mutability (Rc<RefCell<>>) — synthesized
|
|
6409
|
+
// keys are already visible through the caller's reference.
|
|
6410
|
+
try {
|
|
6411
|
+
await this.persistExtractedKeys(programImportsBuilder, importEditions);
|
|
6412
|
+
}
|
|
6413
|
+
catch (e) {
|
|
6414
|
+
console.debug(`Failed to persist extracted keys: ${e}`);
|
|
6415
|
+
}
|
|
6416
|
+
return result;
|
|
5866
6417
|
}
|
|
5867
6418
|
/**
|
|
5868
6419
|
* Builds a SnarkVM `Authorization` for a specific function.
|
|
@@ -5925,29 +6476,17 @@ class ProgramManager {
|
|
|
5925
6476
|
if (typeof executionPrivateKey === "undefined") {
|
|
5926
6477
|
throw "No private key provided and no private key set in the ProgramManager";
|
|
5927
6478
|
}
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
edition = await this.networkClient.getLatestProgramEdition(programName);
|
|
5931
|
-
}
|
|
5932
|
-
catch (e) {
|
|
5933
|
-
console.warn(`Error finding edition for ${programName}. Network response: '${e.message}'. Assuming edition 0.`);
|
|
5934
|
-
edition = 0;
|
|
5935
|
-
}
|
|
5936
|
-
}
|
|
5937
|
-
// Resolve the program imports if they exist.
|
|
5938
|
-
const numberOfImports = Program.fromString(program).getImports().length;
|
|
5939
|
-
if (numberOfImports > 0 && !imports) {
|
|
5940
|
-
try {
|
|
5941
|
-
imports = (await this.networkClient.getProgramImports(programName));
|
|
5942
|
-
}
|
|
5943
|
-
catch (e) {
|
|
5944
|
-
logAndThrow(`Error finding program imports. Network response: '${e.message}'. Please ensure you're connected to a valid Aleo network and the program is deployed to the network.`);
|
|
5945
|
-
}
|
|
5946
|
-
}
|
|
6479
|
+
const resolved = await this.resolveEditionAndAmendment(programName, edition);
|
|
6480
|
+
edition = edition ?? resolved.edition;
|
|
5947
6481
|
// Auto-convert bare string inputs to field elements where the function expects field type.
|
|
5948
6482
|
const preparedInputs = this.prepareInputs(program, functionName, inputs);
|
|
6483
|
+
// Build ProgramImportsBuilder for import resolution (no key loading —
|
|
6484
|
+
// authorizations don't synthesize keys).
|
|
6485
|
+
const { builder } = await this.buildProgramImports(program, imports, false);
|
|
6486
|
+
const hasImports = !builder.isEmpty();
|
|
5949
6487
|
// Build and return an `Authorization` for the desired function.
|
|
5950
|
-
|
|
6488
|
+
const authorization = await ProgramManager$1.authorize(executionPrivateKey, program, functionName, preparedInputs, hasImports ? undefined : imports, edition, hasImports ? builder?.clone() : undefined);
|
|
6489
|
+
return authorization;
|
|
5951
6490
|
}
|
|
5952
6491
|
/**
|
|
5953
6492
|
* Builds a SnarkVM `Authorization` for a specific function without building a circuit first. This should be used when fast authorization generation is needed and the invoker is confident inputs are coorect.
|
|
@@ -6010,29 +6549,17 @@ class ProgramManager {
|
|
|
6010
6549
|
if (typeof executionPrivateKey === "undefined") {
|
|
6011
6550
|
throw "No private key provided and no private key set in the ProgramManager";
|
|
6012
6551
|
}
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
if (numberOfImports > 0 && !imports) {
|
|
6016
|
-
try {
|
|
6017
|
-
imports = (await this.networkClient.getProgramImports(programName));
|
|
6018
|
-
}
|
|
6019
|
-
catch (e) {
|
|
6020
|
-
logAndThrow(`Error finding program imports. Network response: '${e.message}'. Please ensure you're connected to a valid Aleo network and the program is deployed to the network.`);
|
|
6021
|
-
}
|
|
6022
|
-
}
|
|
6023
|
-
if (edition == undefined) {
|
|
6024
|
-
try {
|
|
6025
|
-
edition = await this.networkClient.getLatestProgramEdition(programName);
|
|
6026
|
-
}
|
|
6027
|
-
catch (e) {
|
|
6028
|
-
console.warn(`Error finding edition for ${programName}. Network response: '${e.message}'. Assuming edition 0.`);
|
|
6029
|
-
edition = 0;
|
|
6030
|
-
}
|
|
6031
|
-
}
|
|
6552
|
+
const resolved = await this.resolveEditionAndAmendment(programName, edition);
|
|
6553
|
+
edition = edition ?? resolved.edition;
|
|
6032
6554
|
// Auto-convert bare string inputs to field elements where the function expects field type.
|
|
6033
6555
|
const preparedInputs = this.prepareInputs(program, functionName, inputs);
|
|
6556
|
+
// Build ProgramImportsBuilder for import resolution (no key loading —
|
|
6557
|
+
// authorizations don't synthesize keys).
|
|
6558
|
+
const { builder } = await this.buildProgramImports(program, imports, false);
|
|
6559
|
+
const hasImports = !builder.isEmpty();
|
|
6034
6560
|
// Build and return an `Authorization` for the desired function.
|
|
6035
|
-
|
|
6561
|
+
const authorization = await ProgramManager$1.buildAuthorizationUnchecked(executionPrivateKey, program, functionName, preparedInputs, hasImports ? undefined : imports, edition, hasImports ? builder?.clone() : undefined);
|
|
6562
|
+
return authorization;
|
|
6036
6563
|
}
|
|
6037
6564
|
/**
|
|
6038
6565
|
* Builds a `ProvingRequest` for submission to a prover for execution. If building a proving request with an ExecutionRequest, a private key must be explicitly provided.
|
|
@@ -6095,15 +6622,8 @@ class ProgramManager {
|
|
|
6095
6622
|
if (programName === undefined) {
|
|
6096
6623
|
programName = Program.fromString(program).id();
|
|
6097
6624
|
}
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
edition = await this.networkClient.getLatestProgramEdition(programName);
|
|
6101
|
-
}
|
|
6102
|
-
catch (e) {
|
|
6103
|
-
console.warn(`Error finding edition for ${programName}. Network response: '${e.message}'. Assuming edition 0.`);
|
|
6104
|
-
edition = 0;
|
|
6105
|
-
}
|
|
6106
|
-
}
|
|
6625
|
+
const resolved = await this.resolveEditionAndAmendment(programName, edition);
|
|
6626
|
+
edition = edition ?? resolved.edition;
|
|
6107
6627
|
// Get the private key from the account if it is not provided in the parameters.
|
|
6108
6628
|
let executionPrivateKey = privateKey;
|
|
6109
6629
|
if (typeof privateKey === "undefined" &&
|
|
@@ -6141,8 +6661,30 @@ class ProgramManager {
|
|
|
6141
6661
|
catch (e) {
|
|
6142
6662
|
logAndThrow(`Error finding fee record. Record finder response: '${e.message}'. Please ensure you're connected to a valid Aleo network and a record with enough balance exists.`);
|
|
6143
6663
|
}
|
|
6664
|
+
// Build ProgramImportsBuilder for import resolution (no key loading —
|
|
6665
|
+
// proving requests don't synthesize keys).
|
|
6666
|
+
// Note: imports is kept for the Rust-side fee estimation fallback
|
|
6667
|
+
// (estimate_fee_for_authorization uses the legacy imports Object to avoid
|
|
6668
|
+
// a RefCell double-borrow — see proving_request.rs).
|
|
6669
|
+
const { builder } = await this.buildProgramImports(program, imports, false);
|
|
6670
|
+
const hasImports = !builder.isEmpty();
|
|
6671
|
+
// Normalize imports to the full merged set from the builder so the
|
|
6672
|
+
// Rust fee estimator sees all transitive imports, not just the
|
|
6673
|
+
// caller's partial set. Use programNames + getProgram to avoid
|
|
6674
|
+
// serializing key bytes (toObject would serialize all proving/
|
|
6675
|
+
// verifying keys, which can be tens of MB).
|
|
6676
|
+
if (hasImports) {
|
|
6677
|
+
const merged = {};
|
|
6678
|
+
for (const name of Array.from(builder.programNames())) {
|
|
6679
|
+
const src = builder.getProgram(name);
|
|
6680
|
+
if (src)
|
|
6681
|
+
merged[name] = src;
|
|
6682
|
+
}
|
|
6683
|
+
imports = merged;
|
|
6684
|
+
}
|
|
6144
6685
|
if (options.executionRequest instanceof ExecutionRequest) {
|
|
6145
|
-
return await ProgramManager$1.buildProvingRequestFromExecutionRequest(options.executionRequest, program, unchecked, broadcast, edition, imports,
|
|
6686
|
+
return await ProgramManager$1.buildProvingRequestFromExecutionRequest(options.executionRequest, program, unchecked, broadcast, edition, imports, // kept for fee estimation in Rust
|
|
6687
|
+
executionPrivateKey, hasImports ? builder?.clone() : undefined);
|
|
6146
6688
|
}
|
|
6147
6689
|
else {
|
|
6148
6690
|
// Ensure the private key exists.
|
|
@@ -6156,7 +6698,8 @@ class ProgramManager {
|
|
|
6156
6698
|
// Auto-convert bare string inputs to field elements where the function expects field type.
|
|
6157
6699
|
const preparedInputs = this.prepareInputs(program, functionName, inputs);
|
|
6158
6700
|
// Build and return the `ProvingRequest`.
|
|
6159
|
-
return await ProgramManager$1.buildProvingRequest(executionPrivateKey, program, functionName, preparedInputs, baseFee, priorityFee, feeRecord, imports,
|
|
6701
|
+
return await ProgramManager$1.buildProvingRequest(executionPrivateKey, program, functionName, preparedInputs, baseFee, priorityFee, feeRecord, imports, // kept for fee estimation in Rust
|
|
6702
|
+
broadcast, unchecked, edition, useFeeMaster, hasImports ? builder?.clone() : undefined);
|
|
6160
6703
|
}
|
|
6161
6704
|
}
|
|
6162
6705
|
/**
|
|
@@ -6329,6 +6872,10 @@ class ProgramManager {
|
|
|
6329
6872
|
* assert(result === ["10u32"]);
|
|
6330
6873
|
*/
|
|
6331
6874
|
async run(program, function_name, inputs, proveExecution, imports, keySearchParams, provingKey, verifyingKey, privateKey, offlineQuery, edition) {
|
|
6875
|
+
const programName = Program.fromString(program).id();
|
|
6876
|
+
const resolved = await this.resolveEditionAndAmendment(programName, edition);
|
|
6877
|
+
edition = edition ?? resolved.edition;
|
|
6878
|
+
const { amendment } = resolved;
|
|
6332
6879
|
// Get the private key from the account if it is not provided in the parameters
|
|
6333
6880
|
let executionPrivateKey = privateKey;
|
|
6334
6881
|
if (typeof privateKey === "undefined" &&
|
|
@@ -6338,22 +6885,33 @@ class ProgramManager {
|
|
|
6338
6885
|
if (typeof executionPrivateKey === "undefined") {
|
|
6339
6886
|
throw "No private key provided and no private key set in the ProgramManager";
|
|
6340
6887
|
}
|
|
6341
|
-
//
|
|
6888
|
+
// Build the ProgramImportsBuilder for import resolution and key caching.
|
|
6889
|
+
const { builder, importEditions } = await this.buildProgramImports(program, imports);
|
|
6890
|
+
importEditions.set(programName, { edition: edition, amendment });
|
|
6342
6891
|
if (!provingKey || !verifyingKey) {
|
|
6343
|
-
|
|
6344
|
-
|
|
6892
|
+
const keys = await this.resolveTopLevelKeys(programName, function_name, keySearchParams, edition, amendment);
|
|
6893
|
+
if (keys) {
|
|
6894
|
+
[provingKey, verifyingKey] = keys;
|
|
6345
6895
|
}
|
|
6346
|
-
|
|
6347
|
-
console.log(
|
|
6896
|
+
else {
|
|
6897
|
+
console.log("Function keys not found in KeyStore or KeyProvider. The function keys will be synthesized");
|
|
6348
6898
|
}
|
|
6349
6899
|
}
|
|
6350
6900
|
// Auto-convert bare string inputs to field elements where the function expects field type.
|
|
6351
6901
|
const preparedInputs = this.prepareInputs(program, function_name, inputs);
|
|
6352
|
-
// Run the program offline
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6902
|
+
// Run the program offline. Always pass the builder so synthesized
|
|
6903
|
+
// keys (including the top-level program's) are captured and persisted.
|
|
6904
|
+
const result = await ProgramManager$1.executeFunctionOffline(executionPrivateKey, program, function_name, preparedInputs, proveExecution, false, undefined, // imports (legacy Object path — builder handles resolution)
|
|
6905
|
+
provingKey, verifyingKey, this.host, offlineQuery, edition, builder?.clone());
|
|
6906
|
+
// The clone passed to WASM shares state with the original via
|
|
6907
|
+
// Rc<RefCell<>> — synthesized keys are already visible.
|
|
6908
|
+
try {
|
|
6909
|
+
await this.persistExtractedKeys(builder, importEditions);
|
|
6910
|
+
}
|
|
6911
|
+
catch (e) {
|
|
6912
|
+
console.debug(`Failed to persist extracted keys: ${e}`);
|
|
6913
|
+
}
|
|
6914
|
+
return result;
|
|
6357
6915
|
}
|
|
6358
6916
|
/**
|
|
6359
6917
|
* Join two credits records into a single credits record
|
|
@@ -7455,12 +8013,8 @@ class ProgramManager {
|
|
|
7455
8013
|
throw new Error("Authorization must be provided if estimating fee for Authorization.");
|
|
7456
8014
|
}
|
|
7457
8015
|
const programSource = program ? program.toString() : await this.networkClient.getProgram(programName, edition);
|
|
7458
|
-
const programImports = imports
|
|
7459
|
-
|
|
7460
|
-
if (Object.keys(programImports)) {
|
|
7461
|
-
return ProgramManager$1.estimateFeeForAuthorization(authorization, programSource, programImports, edition);
|
|
7462
|
-
}
|
|
7463
|
-
return ProgramManager$1.estimateFeeForAuthorization(authorization, programSource, imports, edition);
|
|
8016
|
+
const programImports = imports ?? await this.networkClient.getProgramImports(programSource);
|
|
8017
|
+
return ProgramManager$1.estimateFeeForAuthorization(authorization, programSource, programImports, edition);
|
|
7464
8018
|
}
|
|
7465
8019
|
/**
|
|
7466
8020
|
* Estimate the execution fee for an Aleo function.
|
|
@@ -7606,15 +8160,8 @@ class ProgramManager {
|
|
|
7606
8160
|
if (programName === undefined) {
|
|
7607
8161
|
programName = programObject.id();
|
|
7608
8162
|
}
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
edition = await this.networkClient.getLatestProgramEdition(programName);
|
|
7612
|
-
}
|
|
7613
|
-
catch (e) {
|
|
7614
|
-
console.warn(`Error finding edition for ${programName}. Network response: '${e.message}'. Assuming edition 0.`);
|
|
7615
|
-
edition = 0;
|
|
7616
|
-
}
|
|
7617
|
-
}
|
|
8163
|
+
const resolved = await this.resolveEditionAndAmendment(programName, edition);
|
|
8164
|
+
edition = edition ?? resolved.edition;
|
|
7618
8165
|
// Get the private key from the account if it is not provided in the parameters.
|
|
7619
8166
|
let executionPrivateKey = privateKey;
|
|
7620
8167
|
if (typeof privateKey === "undefined" &&
|
|
@@ -8199,5 +8746,5 @@ async function initializeWasm() {
|
|
|
8199
8746
|
console.warn("initializeWasm is deprecated, you no longer need to use it");
|
|
8200
8747
|
}
|
|
8201
8748
|
|
|
8202
|
-
export { Account, AleoKeyProvider, AleoKeyProviderParams, AleoNetworkClient, BlockHeightSearch, CREDITS_PROGRAM_KEYS, KeyVerificationError as ChecksumMismatchError, DecryptionNotEnabledError, InvalidLocatorError, KEY_STORE, KeyVerificationError, MemKeyVerifier, NetworkRecordProvider, OfflineKeyProvider, OfflineSearchParams, PRIVATE_TO_PUBLIC_TRANSFER, PRIVATE_TRANSFER, PRIVATE_TRANSFER_TYPES, PUBLIC_TO_PRIVATE_TRANSFER, PUBLIC_TRANSFER, PUBLIC_TRANSFER_AS_SIGNER, ProgramManager, RECORD_DOMAIN, RecordNotFoundError, RecordScanner, RecordScannerRequestError, SealanceMerkleTree, UUIDError, VALID_TRANSFER_TYPES, ViewKeyNotStoredError, buildExecutionRequestFromExternallySignedData, computeExternalSigningInputs, encryptAuthorization, encryptProvingRequest, encryptRegistrationRequest, encryptViewKey, initializeWasm, inputsToFields, isInputIdStrategy, isProveApiErrorBody, isProvingResponse, isRecordViewKeyStrategy, isViewKeyStrategy, logAndThrow, provingKeyLocator, sha256Hex, toAddress, toField, toGroup, toSignature, toViewKey, translationKeyLocator, verifyBatchProof, verifyProof, verifyingKeyLocator, zeroizeBytes };
|
|
8749
|
+
export { Account, AleoKeyProvider, AleoKeyProviderParams, AleoNetworkClient, BlockHeightSearch, CREDITS_PROGRAM_KEYS, KeyVerificationError as ChecksumMismatchError, DecryptionNotEnabledError, IndexedDBKeyStore, InvalidLocatorError, KEY_STORE, KeyVerificationError, MemKeyVerifier, NetworkRecordProvider, OfflineKeyProvider, OfflineSearchParams, PRIVATE_TO_PUBLIC_TRANSFER, PRIVATE_TRANSFER, PRIVATE_TRANSFER_TYPES, PUBLIC_TO_PRIVATE_TRANSFER, PUBLIC_TRANSFER, PUBLIC_TRANSFER_AS_SIGNER, ProgramManager, RECORD_DOMAIN, RecordNotFoundError, RecordScanner, RecordScannerRequestError, SealanceMerkleTree, UUIDError, VALID_TRANSFER_TYPES, ViewKeyNotStoredError, buildExecutionRequestFromExternallySignedData, computeExternalSigningInputs, encryptAuthorization, encryptProvingRequest, encryptRegistrationRequest, encryptViewKey, initializeWasm, inputsToFields, isInputIdStrategy, isProveApiErrorBody, isProvingResponse, isRecordViewKeyStrategy, isViewKeyStrategy, logAndThrow, provingKeyLocator, sha256Hex, toAddress, toField, toGroup, toSignature, toViewKey, translationKeyLocator, verifyBatchProof, verifyProof, verifyingKeyLocator, zeroizeBytes };
|
|
8203
8750
|
//# sourceMappingURL=browser.js.map
|