@utxopia/sdk 0.1.0-alpha.5 → 0.1.0-alpha.6
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/client.js +3 -3
- package/dist/config.js +5 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/prover/web.d.ts +8 -0
- package/dist/prover/web.js +22 -0
- package/dist/vk-registry.d.ts +21 -0
- package/dist/vk-registry.js +39 -0
- package/package.json +1 -1
- package/src/client.ts +3 -3
- package/src/config.ts +5 -2
- package/src/index.ts +1 -0
- package/src/prover/web.ts +23 -0
- package/src/vk-registry.ts +52 -0
package/dist/client.js
CHANGED
|
@@ -183,8 +183,6 @@ export class UTXOpiaClient {
|
|
|
183
183
|
const cached = this._tokenIdCache.get(mintAddress);
|
|
184
184
|
if (cached !== undefined)
|
|
185
185
|
return cached;
|
|
186
|
-
// Requires PublicKey — import dynamically to avoid hard dep
|
|
187
|
-
const mintBytes = hexToBytes(mintAddress.padStart(64, "0"));
|
|
188
186
|
// If it's a base58 address, convert via PublicKey
|
|
189
187
|
let bytes;
|
|
190
188
|
try {
|
|
@@ -200,7 +198,9 @@ export class UTXOpiaClient {
|
|
|
200
198
|
}
|
|
201
199
|
}
|
|
202
200
|
catch {
|
|
203
|
-
|
|
201
|
+
// Lazy on purpose: a base58 mint padded to 64 is not hex, so computing
|
|
202
|
+
// this eagerly threw before the branch above ever ran.
|
|
203
|
+
bytes = hexToBytes(mintAddress.padStart(64, "0"));
|
|
204
204
|
}
|
|
205
205
|
const tokenId = computeTokenId(bytes);
|
|
206
206
|
this._tokenIdCache.set(mintAddress, tokenId);
|
package/dist/config.js
CHANGED
|
@@ -175,8 +175,11 @@ export const MAINNET_CONFIG = {
|
|
|
175
175
|
// Bitcoin Network
|
|
176
176
|
bitcoinNetwork: "mainnet",
|
|
177
177
|
esploraUrl: "https://mempool.space/api",
|
|
178
|
-
// Circuit CDN
|
|
179
|
-
|
|
178
|
+
// Circuit CDN. Must carry the full versioned path: `resolveCircuitPath` turns a bare host
|
|
179
|
+
// into `<host>/circuits/groth16`, which is a DIFFERENT, stale build that still serves 200s
|
|
180
|
+
// for every shape with a different delta. Proofs built from it verify locally and are
|
|
181
|
+
// rejected on chain as "proof invalid".
|
|
182
|
+
circuitCdnUrl: "https://circuit.utxopia.com/circuits/v2/groth16",
|
|
180
183
|
// Groth16 Verifier (placeholder)
|
|
181
184
|
groth16VerifierProgramId: address("11111111111111111111111111111111"),
|
|
182
185
|
// VK Hashes (placeholder - update when deployed)
|
package/dist/index.d.ts
CHANGED
|
@@ -54,7 +54,7 @@ export { createFetchConnectionAdapter, createConnectionAdapterFromWeb3, createCo
|
|
|
54
54
|
export { resolveSnsName, resolveStealthName, parseSnsStealthData, isSnsStealthAddress, isAuditorDisclosable, SnsComplianceFlags, SNS_COMPLIANCE_AUDITOR_OFFSET, SNS_COMPLIANCE_AUDITOR_BYTES, deriveParentDomainKey, SNS_STEALTH_DATA_SIZE, type SnsStealthAddress, } from "./sns-resolver";
|
|
55
55
|
export { COMMITMENT_TREE_DISCRIMINATOR, parseCommitmentTreeData, isValidRoot, fetchCommitmentTree, getCommitmentIndex, saveCommitmentIndex, CommitmentTreeIndex, buildCommitmentTreeFromChain, fetchLeafIndexForCommitment, fetchMerkleProofForCommitment, getMerkleProofFromTree, type CommitmentTreeState, type RpcClient, type OnChainMerkleProof, } from "./commitment-tree";
|
|
56
56
|
export { INSTRUCTION_DISCRIMINATORS, buildShieldInstructionData, buildShieldInstruction, type ShieldInstructionOptions, buildApproveRedemptionSigningInstructionData, buildApproveRedemptionSigningInstruction, buildCancelRedemptionInstructionData, buildCancelRedemptionInstruction, type CancelRedemptionInstructionOptions, bigintTo32Bytes, bytes32ToBigint, buildTransactInstructionData, buildTransactInstruction, buildRedeemInstructionData, buildUnshieldInstructionData, buildUnshieldInstruction, buildProposePoolUpdateInstructionData, buildProposePoolUpdateInstruction, buildExecutePoolUpdateInstructionData, buildExecutePoolUpdateInstruction, buildCancelPoolUpdateInstructionData, buildCancelPoolUpdateInstruction, buildRotateTreeInstructionData, buildRotateTreeInstruction, type RotateTreeOptions, buildMagicBlockDelegateInstructionData, buildMagicBlockDelegateInstruction, buildMagicBlockCommitInstructionData, buildMagicBlockCommitInstruction, buildMagicBlockPerPermissionInstructionData, buildMagicBlockPerPermissionInstruction, buildPolicyRequestHash, buildPolicyIntentParts, buildRegisterExitDestinationInstruction, buildRegisterExitDestinationInstructionData, MAX_POLICY_INTENT_PARTS, buildInitializePolicyApprovalInstructionData, buildInitializePolicyApprovalInstruction, buildPolicyApprovalDecisionInstruction, buildPolicyApprovalCommitInstruction, buildCompleteDepositPermissionedInstructionData, buildCompleteDepositPermissionedInstruction, buildShieldPermissionedInstructionData, buildShieldPermissionedInstruction, buildRotateAuditorInstructionData, buildRotateAuditorInstruction, type MagicBlockDelegateTarget, type MagicBlockDelegateInstructionOptions, type MagicBlockCommitInstructionOptions, type MagicBlockPerPermissionOperation, type MagicBlockPerPermissionMember, type MagicBlockPerPermissionInstructionOptions, type PolicyApprovalDecision, type InitializePolicyApprovalOptions, type CompleteDepositPermissionedOptions, type ShieldPermissionedInstructionOptions, type RotateAuditorOptions, buildVerifyTransactionInstructionData, buildCompleteDepositInstructionData, buildVerifyDepositInstructionData, buildVerifyDepositPermissionedInstructionData, buildSetPoolConfigInstructionData, parsePoolConfig, type ParsedPoolConfig, POOL_CONFIG_DISCRIMINATOR, POOL_CONFIG_LEN, POOL_SCRIPT_MAX_LEN, type Instruction, type ApproveRedemptionSigningInstructionOptions, type TransactInstructionOptions, type UnshieldInstructionOptions, type ProposePoolUpdateOptions, type ExecutePoolUpdateOptions, type CancelPoolUpdateOptions, } from "./instructions";
|
|
57
|
-
export { VK_REGISTRY_DISCRIMINATOR, VK_REGISTRY_LEN, MAX_IC_POINTS, MAX_SAFE_JOINSPLIT_SIZE, INIT_VK_REGISTRY_DISCRIMINATOR, UPDATE_VK_REGISTRY_DISCRIMINATOR, joinSplitNumPublicInputs, computeVkHash, vkeyJsonToVkMaterial, buildVkRegistryData, parseVkRegistry, assertVkRegistryForShape, isVkRegistryReady, type JoinSplitVkMaterial, type SnarkjsVkeyJson, type ParsedVkRegistry, } from "./vk-registry";
|
|
57
|
+
export { VK_REGISTRY_DISCRIMINATOR, VK_REGISTRY_LEN, MAX_IC_POINTS, MAX_SAFE_JOINSPLIT_SIZE, INIT_VK_REGISTRY_DISCRIMINATOR, UPDATE_VK_REGISTRY_DISCRIMINATOR, joinSplitNumPublicInputs, computeVkHash, vkeyJsonToVkMaterial, buildVkRegistryData, parseVkRegistry, assertVkRegistryForShape, assertVkeyMatchesRegistry, isVkRegistryReady, type JoinSplitVkMaterial, type SnarkjsVkeyJson, type ParsedVkRegistry, } from "./vk-registry";
|
|
58
58
|
export { fetchExplorerDeposits, fetchExplorerTransfers, fetchExplorerRedemptions, parseNullifierRecord, parseRedemptionRequest, NULLIFIER_RECORD_SIZE, REDEMPTION_REQUEST_SIZE, NULLIFIER_RECORD_DISCRIMINATOR, REDEMPTION_REQUEST_DISCRIMINATOR, OPERATION_TYPE_LABELS, type ExplorerDeposit, type ExplorerTransferEvent, type ExplorerRedemption, type IndexerLeaf, } from "./explorer";
|
|
59
59
|
export { parseProgramEvents, parseNullifierSpentEvent, parseStealthAnnouncementEvent, parseSenderMemoEvent, parseBtcOriginAttestationEvent, parseAuditorCiphertextEvent, EVENT_NULLIFIER_SPENT, EVENT_STEALTH_ANNOUNCEMENT, EVENT_NULLIFIERS_BATCH, EVENT_ANNOUNCEMENTS_BATCH, EVENT_SENDER_MEMO, EVENT_BTC_ORIGIN_ATTESTATION, EVENT_AUDITOR_CIPHERTEXT, type NullifierSpentEvent, type StealthAnnouncementEvent, type SenderMemoEvent, type BtcOriginAttestationEvent, type AuditorCiphertextEvent, type ProgramEvent, } from "./events";
|
|
60
60
|
export { AnnouncementClient, type AnnouncementClientConfig, type AnnouncementListener, } from "./announcement-client";
|
package/dist/index.js
CHANGED
|
@@ -187,7 +187,7 @@ buildSetPoolConfigInstructionData, parsePoolConfig, POOL_CONFIG_DISCRIMINATOR, P
|
|
|
187
187
|
// ==========================================================================
|
|
188
188
|
// VK Registry (JoinSplit Groth16 on-chain verification keys)
|
|
189
189
|
// ==========================================================================
|
|
190
|
-
export { VK_REGISTRY_DISCRIMINATOR, VK_REGISTRY_LEN, MAX_IC_POINTS, MAX_SAFE_JOINSPLIT_SIZE, INIT_VK_REGISTRY_DISCRIMINATOR, UPDATE_VK_REGISTRY_DISCRIMINATOR, joinSplitNumPublicInputs, computeVkHash, vkeyJsonToVkMaterial, buildVkRegistryData, parseVkRegistry, assertVkRegistryForShape, isVkRegistryReady, } from "./vk-registry";
|
|
190
|
+
export { VK_REGISTRY_DISCRIMINATOR, VK_REGISTRY_LEN, MAX_IC_POINTS, MAX_SAFE_JOINSPLIT_SIZE, INIT_VK_REGISTRY_DISCRIMINATOR, UPDATE_VK_REGISTRY_DISCRIMINATOR, joinSplitNumPublicInputs, computeVkHash, vkeyJsonToVkMaterial, buildVkRegistryData, parseVkRegistry, assertVkRegistryForShape, assertVkeyMatchesRegistry, isVkRegistryReady, } from "./vk-registry";
|
|
191
191
|
// ==========================================================================
|
|
192
192
|
// ChadBuffer Relay
|
|
193
193
|
// ==========================================================================
|
package/dist/prover/web.d.ts
CHANGED
|
@@ -19,6 +19,14 @@ export interface ProofData {
|
|
|
19
19
|
verificationKey?: Uint8Array;
|
|
20
20
|
}
|
|
21
21
|
export type CircuitType = `joinsplit_${number}x${number}`;
|
|
22
|
+
/**
|
|
23
|
+
* Fetch a circuit's `*.vkey.json` from the same base path its zkey comes from.
|
|
24
|
+
*
|
|
25
|
+
* Deliberately reads `circuitBasePath` rather than taking a URL: the point of the check this
|
|
26
|
+
* feeds (`assertVkeyMatchesRegistry`) is that the vkey describes the artifacts actually being
|
|
27
|
+
* proved with, and a caller-supplied URL could describe a different build entirely.
|
|
28
|
+
*/
|
|
29
|
+
export declare function fetchCircuitVkey(circuitType: CircuitType): Promise<unknown>;
|
|
22
30
|
/**
|
|
23
31
|
* Set the base path for circuit artifacts
|
|
24
32
|
*/
|
package/dist/prover/web.js
CHANGED
|
@@ -41,6 +41,26 @@ const isBrowser = typeof window !== "undefined";
|
|
|
41
41
|
const isNode = typeof process !== "undefined" && process.versions?.node;
|
|
42
42
|
// Configurable circuit paths
|
|
43
43
|
let circuitBasePath = isBrowser ? "/circuits/groth16" : "./circuits";
|
|
44
|
+
/**
|
|
45
|
+
* Fetch a circuit's `*.vkey.json` from the same base path its zkey comes from.
|
|
46
|
+
*
|
|
47
|
+
* Deliberately reads `circuitBasePath` rather than taking a URL: the point of the check this
|
|
48
|
+
* feeds (`assertVkeyMatchesRegistry`) is that the vkey describes the artifacts actually being
|
|
49
|
+
* proved with, and a caller-supplied URL could describe a different build entirely.
|
|
50
|
+
*/
|
|
51
|
+
export async function fetchCircuitVkey(circuitType) {
|
|
52
|
+
const cached = vkeyCache.get(circuitType);
|
|
53
|
+
if (cached)
|
|
54
|
+
return cached;
|
|
55
|
+
const url = `${circuitBasePath}/${circuitType}/${circuitType}.vkey.json`;
|
|
56
|
+
const res = await fetch(url);
|
|
57
|
+
if (!res.ok) {
|
|
58
|
+
throw new Error(`Could not fetch circuit vkey ${url}: ${res.status} ${res.statusText}`);
|
|
59
|
+
}
|
|
60
|
+
const vkey = await res.json();
|
|
61
|
+
vkeyCache.set(circuitType, vkey);
|
|
62
|
+
return vkey;
|
|
63
|
+
}
|
|
44
64
|
/**
|
|
45
65
|
* Set the base path for circuit artifacts
|
|
46
66
|
*/
|
|
@@ -49,12 +69,14 @@ export function setCircuitPath(path) {
|
|
|
49
69
|
return;
|
|
50
70
|
circuitBasePath = path;
|
|
51
71
|
circuitCache.clear();
|
|
72
|
+
vkeyCache.clear();
|
|
52
73
|
artifactBytesCache.clear();
|
|
53
74
|
artifactDownloadCache.clear();
|
|
54
75
|
}
|
|
55
76
|
// Lazy-loaded snarkjs module
|
|
56
77
|
let snarkjs = null;
|
|
57
78
|
const circuitCache = new Map();
|
|
79
|
+
const vkeyCache = new Map();
|
|
58
80
|
let proverInitialized = false;
|
|
59
81
|
/**
|
|
60
82
|
* Load snarkjs module
|
package/dist/vk-registry.d.ts
CHANGED
|
@@ -98,3 +98,24 @@ export declare function parseVkRegistry(data: Uint8Array): ParsedVkRegistry;
|
|
|
98
98
|
export declare function assertVkRegistryForShape(data: Uint8Array | null | undefined, nInputs: number, nOutputs: number): ParsedVkRegistry;
|
|
99
99
|
/** True if `data` is an initialized `VkRegistry` for the given shape. */
|
|
100
100
|
export declare function isVkRegistryReady(data: Uint8Array | null | undefined, nInputs: number, nOutputs: number): boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Assert the circuit artifacts this client loaded correspond to the verifying key the chain
|
|
103
|
+
* will check against.
|
|
104
|
+
*
|
|
105
|
+
* `assertVkRegistryForShape` proves the registry exists and names the right shape;
|
|
106
|
+
* `setCircuitArtifactDigests` proves the artifacts are the bytes this build expects. Neither
|
|
107
|
+
* ties the two together, and that gap is a real one: a zkey from a different phase-2 setup
|
|
108
|
+
* proves fine, downloads fine, passes every integrity check, and is then rejected on chain as
|
|
109
|
+
* "proof invalid" — with nothing pointing at the version mismatch.
|
|
110
|
+
*
|
|
111
|
+
* It is not hypothetical. `https://circuit.utxopia.com/circuits/groth16` and
|
|
112
|
+
* `.../circuits/v2/groth16` both serve 200s for every shape, with different `delta` — same
|
|
113
|
+
* ceremony, different per-circuit setup. Both therefore satisfy the program's own vk_hash
|
|
114
|
+
* recomputation, which binds delta+IC to the compiled-in alpha/beta/gamma and so catches a
|
|
115
|
+
* foreign ceremony but NOT a stale phase-2 from the right one. This is the check that does.
|
|
116
|
+
*
|
|
117
|
+
* @param vkey The `*.vkey.json` fetched from the SAME base path as the zkey. Fetching
|
|
118
|
+
* it from anywhere else makes this assertion meaningless.
|
|
119
|
+
* @param registryData Raw `VkRegistry` account data for the shape.
|
|
120
|
+
*/
|
|
121
|
+
export declare function assertVkeyMatchesRegistry(vkey: SnarkjsVkeyJson, registryData: Uint8Array | null | undefined, nInputs: number, nOutputs: number): void;
|
package/dist/vk-registry.js
CHANGED
|
@@ -210,3 +210,42 @@ export function isVkRegistryReady(data, nInputs, nOutputs) {
|
|
|
210
210
|
return false;
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Assert the circuit artifacts this client loaded correspond to the verifying key the chain
|
|
215
|
+
* will check against.
|
|
216
|
+
*
|
|
217
|
+
* `assertVkRegistryForShape` proves the registry exists and names the right shape;
|
|
218
|
+
* `setCircuitArtifactDigests` proves the artifacts are the bytes this build expects. Neither
|
|
219
|
+
* ties the two together, and that gap is a real one: a zkey from a different phase-2 setup
|
|
220
|
+
* proves fine, downloads fine, passes every integrity check, and is then rejected on chain as
|
|
221
|
+
* "proof invalid" — with nothing pointing at the version mismatch.
|
|
222
|
+
*
|
|
223
|
+
* It is not hypothetical. `https://circuit.utxopia.com/circuits/groth16` and
|
|
224
|
+
* `.../circuits/v2/groth16` both serve 200s for every shape, with different `delta` — same
|
|
225
|
+
* ceremony, different per-circuit setup. Both therefore satisfy the program's own vk_hash
|
|
226
|
+
* recomputation, which binds delta+IC to the compiled-in alpha/beta/gamma and so catches a
|
|
227
|
+
* foreign ceremony but NOT a stale phase-2 from the right one. This is the check that does.
|
|
228
|
+
*
|
|
229
|
+
* @param vkey The `*.vkey.json` fetched from the SAME base path as the zkey. Fetching
|
|
230
|
+
* it from anywhere else makes this assertion meaningless.
|
|
231
|
+
* @param registryData Raw `VkRegistry` account data for the shape.
|
|
232
|
+
*/
|
|
233
|
+
export function assertVkeyMatchesRegistry(vkey, registryData, nInputs, nOutputs) {
|
|
234
|
+
const registry = assertVkRegistryForShape(registryData, nInputs, nOutputs);
|
|
235
|
+
const expectedIc = joinSplitNumPublicInputs(nInputs, nOutputs) + 1;
|
|
236
|
+
if (vkey.IC.length !== expectedIc) {
|
|
237
|
+
throw new Error(`Circuit vkey for JoinSplit ${nInputs}x${nOutputs} has ${vkey.IC.length} IC points, ` +
|
|
238
|
+
`expected ${expectedIc} — the artifacts are for a different shape.`);
|
|
239
|
+
}
|
|
240
|
+
const local = computeVkHash(vkey);
|
|
241
|
+
if (!local.every((b, i) => b === registry.vkHash[i])) {
|
|
242
|
+
throw new Error(`Circuit artifacts for JoinSplit ${nInputs}x${nOutputs} do not match the on-chain ` +
|
|
243
|
+
`verifying key (artifact vkHash ${bytesToHex(local)}, registry ${bytesToHex(registry.vkHash)}). ` +
|
|
244
|
+
`A proof built from these would be rejected as invalid. Usually the circuit base URL ` +
|
|
245
|
+
`points at a stale build — check it resolves to the same version the registry was ` +
|
|
246
|
+
`registered from.`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function bytesToHex(b) {
|
|
250
|
+
return Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
251
|
+
}
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -269,8 +269,6 @@ export class UTXOpiaClient {
|
|
|
269
269
|
const cached = this._tokenIdCache.get(mintAddress);
|
|
270
270
|
if (cached !== undefined) return cached;
|
|
271
271
|
|
|
272
|
-
// Requires PublicKey — import dynamically to avoid hard dep
|
|
273
|
-
const mintBytes = hexToBytes(mintAddress.padStart(64, "0"));
|
|
274
272
|
// If it's a base58 address, convert via PublicKey
|
|
275
273
|
let bytes: Uint8Array;
|
|
276
274
|
try {
|
|
@@ -284,7 +282,9 @@ export class UTXOpiaClient {
|
|
|
284
282
|
bytes = new PublicKey(mintAddress).toBytes();
|
|
285
283
|
}
|
|
286
284
|
} catch {
|
|
287
|
-
|
|
285
|
+
// Lazy on purpose: a base58 mint padded to 64 is not hex, so computing
|
|
286
|
+
// this eagerly threw before the branch above ever ran.
|
|
287
|
+
bytes = hexToBytes(mintAddress.padStart(64, "0"));
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
const tokenId = computeTokenId(bytes);
|
package/src/config.ts
CHANGED
|
@@ -358,8 +358,11 @@ export const MAINNET_CONFIG: NetworkConfig = {
|
|
|
358
358
|
bitcoinNetwork: "mainnet",
|
|
359
359
|
esploraUrl: "https://mempool.space/api",
|
|
360
360
|
|
|
361
|
-
// Circuit CDN
|
|
362
|
-
|
|
361
|
+
// Circuit CDN. Must carry the full versioned path: `resolveCircuitPath` turns a bare host
|
|
362
|
+
// into `<host>/circuits/groth16`, which is a DIFFERENT, stale build that still serves 200s
|
|
363
|
+
// for every shape with a different delta. Proofs built from it verify locally and are
|
|
364
|
+
// rejected on chain as "proof invalid".
|
|
365
|
+
circuitCdnUrl: "https://circuit.utxopia.com/circuits/v2/groth16",
|
|
363
366
|
|
|
364
367
|
// Groth16 Verifier (placeholder)
|
|
365
368
|
groth16VerifierProgramId: address("11111111111111111111111111111111"),
|
package/src/index.ts
CHANGED
package/src/prover/web.ts
CHANGED
|
@@ -67,6 +67,27 @@ const isNode = typeof process !== "undefined" && process.versions?.node;
|
|
|
67
67
|
// Configurable circuit paths
|
|
68
68
|
let circuitBasePath = isBrowser ? "/circuits/groth16" : "./circuits";
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Fetch a circuit's `*.vkey.json` from the same base path its zkey comes from.
|
|
72
|
+
*
|
|
73
|
+
* Deliberately reads `circuitBasePath` rather than taking a URL: the point of the check this
|
|
74
|
+
* feeds (`assertVkeyMatchesRegistry`) is that the vkey describes the artifacts actually being
|
|
75
|
+
* proved with, and a caller-supplied URL could describe a different build entirely.
|
|
76
|
+
*/
|
|
77
|
+
export async function fetchCircuitVkey(circuitType: CircuitType): Promise<unknown> {
|
|
78
|
+
const cached = vkeyCache.get(circuitType);
|
|
79
|
+
if (cached) return cached;
|
|
80
|
+
|
|
81
|
+
const url = `${circuitBasePath}/${circuitType}/${circuitType}.vkey.json`;
|
|
82
|
+
const res = await fetch(url);
|
|
83
|
+
if (!res.ok) {
|
|
84
|
+
throw new Error(`Could not fetch circuit vkey ${url}: ${res.status} ${res.statusText}`);
|
|
85
|
+
}
|
|
86
|
+
const vkey = await res.json();
|
|
87
|
+
vkeyCache.set(circuitType, vkey);
|
|
88
|
+
return vkey;
|
|
89
|
+
}
|
|
90
|
+
|
|
70
91
|
/**
|
|
71
92
|
* Set the base path for circuit artifacts
|
|
72
93
|
*/
|
|
@@ -74,6 +95,7 @@ export function setCircuitPath(path: string): void {
|
|
|
74
95
|
if (path === circuitBasePath) return;
|
|
75
96
|
circuitBasePath = path;
|
|
76
97
|
circuitCache.clear();
|
|
98
|
+
vkeyCache.clear();
|
|
77
99
|
artifactBytesCache.clear();
|
|
78
100
|
artifactDownloadCache.clear();
|
|
79
101
|
}
|
|
@@ -87,6 +109,7 @@ interface CircuitArtifact {
|
|
|
87
109
|
}
|
|
88
110
|
|
|
89
111
|
const circuitCache = new Map<CircuitType, CircuitArtifact>();
|
|
112
|
+
const vkeyCache = new Map<CircuitType, unknown>();
|
|
90
113
|
let proverInitialized = false;
|
|
91
114
|
|
|
92
115
|
/**
|
package/src/vk-registry.ts
CHANGED
|
@@ -293,3 +293,55 @@ export function isVkRegistryReady(
|
|
|
293
293
|
return false;
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Assert the circuit artifacts this client loaded correspond to the verifying key the chain
|
|
299
|
+
* will check against.
|
|
300
|
+
*
|
|
301
|
+
* `assertVkRegistryForShape` proves the registry exists and names the right shape;
|
|
302
|
+
* `setCircuitArtifactDigests` proves the artifacts are the bytes this build expects. Neither
|
|
303
|
+
* ties the two together, and that gap is a real one: a zkey from a different phase-2 setup
|
|
304
|
+
* proves fine, downloads fine, passes every integrity check, and is then rejected on chain as
|
|
305
|
+
* "proof invalid" — with nothing pointing at the version mismatch.
|
|
306
|
+
*
|
|
307
|
+
* It is not hypothetical. `https://circuit.utxopia.com/circuits/groth16` and
|
|
308
|
+
* `.../circuits/v2/groth16` both serve 200s for every shape, with different `delta` — same
|
|
309
|
+
* ceremony, different per-circuit setup. Both therefore satisfy the program's own vk_hash
|
|
310
|
+
* recomputation, which binds delta+IC to the compiled-in alpha/beta/gamma and so catches a
|
|
311
|
+
* foreign ceremony but NOT a stale phase-2 from the right one. This is the check that does.
|
|
312
|
+
*
|
|
313
|
+
* @param vkey The `*.vkey.json` fetched from the SAME base path as the zkey. Fetching
|
|
314
|
+
* it from anywhere else makes this assertion meaningless.
|
|
315
|
+
* @param registryData Raw `VkRegistry` account data for the shape.
|
|
316
|
+
*/
|
|
317
|
+
export function assertVkeyMatchesRegistry(
|
|
318
|
+
vkey: SnarkjsVkeyJson,
|
|
319
|
+
registryData: Uint8Array | null | undefined,
|
|
320
|
+
nInputs: number,
|
|
321
|
+
nOutputs: number,
|
|
322
|
+
): void {
|
|
323
|
+
const registry = assertVkRegistryForShape(registryData, nInputs, nOutputs);
|
|
324
|
+
|
|
325
|
+
const expectedIc = joinSplitNumPublicInputs(nInputs, nOutputs) + 1;
|
|
326
|
+
if (vkey.IC.length !== expectedIc) {
|
|
327
|
+
throw new Error(
|
|
328
|
+
`Circuit vkey for JoinSplit ${nInputs}x${nOutputs} has ${vkey.IC.length} IC points, ` +
|
|
329
|
+
`expected ${expectedIc} — the artifacts are for a different shape.`,
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const local = computeVkHash(vkey);
|
|
334
|
+
if (!local.every((b, i) => b === registry.vkHash[i])) {
|
|
335
|
+
throw new Error(
|
|
336
|
+
`Circuit artifacts for JoinSplit ${nInputs}x${nOutputs} do not match the on-chain ` +
|
|
337
|
+
`verifying key (artifact vkHash ${bytesToHex(local)}, registry ${bytesToHex(registry.vkHash)}). ` +
|
|
338
|
+
`A proof built from these would be rejected as invalid. Usually the circuit base URL ` +
|
|
339
|
+
`points at a stale build — check it resolves to the same version the registry was ` +
|
|
340
|
+
`registered from.`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function bytesToHex(b: Uint8Array): string {
|
|
346
|
+
return Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
347
|
+
}
|