@utxopia/sdk 0.1.0-alpha.5 → 0.1.0-alpha.7
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 +12 -19
- 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 +13 -17
- 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
|
@@ -25,6 +25,7 @@ import { setupKeysFromWallet, setupKeysFromSeed, setupKeysFromAuthSignature, rec
|
|
|
25
25
|
import { scanUnifiedNotesMulti, scanAnnouncementsViewOnlyMulti, computeNullifierHashForNote, computeNullifierBytes, isDepositForViewerHex, createDepositFromConfig, createTweakDeposit, depositViewingNode, createStealthOutputWithKeys, } from "./stealth";
|
|
26
26
|
import { selectUtxos } from "./psbt";
|
|
27
27
|
import { hexToBytes } from "./crypto";
|
|
28
|
+
import { getAddressEncoder } from "@solana/kit";
|
|
28
29
|
import { EventClient } from "./event-client";
|
|
29
30
|
// ─── Client ─────────────────────────────────────────────────────────
|
|
30
31
|
let _instance = null;
|
|
@@ -183,25 +184,17 @@ export class UTXOpiaClient {
|
|
|
183
184
|
const cached = this._tokenIdCache.get(mintAddress);
|
|
184
185
|
if (cached !== undefined)
|
|
185
186
|
return cached;
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
// Use the SDK's reduceToField which handles the conversion
|
|
198
|
-
const { PublicKey } = require("@solana/web3.js");
|
|
199
|
-
bytes = new PublicKey(mintAddress).toBytes();
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
catch {
|
|
203
|
-
bytes = mintBytes;
|
|
204
|
-
}
|
|
187
|
+
// 64 hex chars is a raw token id; anything else is a base58 mint.
|
|
188
|
+
//
|
|
189
|
+
// Decoded with getAddressEncoder, the same path pda.ts uses, because
|
|
190
|
+
// require("@solana/web3.js") is not callable from an ESM browser bundle —
|
|
191
|
+
// it threw for every base58 mint, and the hex fallback behind it threw
|
|
192
|
+
// again on the same input. There is no fallback now: both branches are
|
|
193
|
+
// total for their input, and a malformed mint should raise, not be padded
|
|
194
|
+
// into a different token's id.
|
|
195
|
+
const bytes = mintAddress.length === 64 && /^[0-9a-fA-F]+$/.test(mintAddress)
|
|
196
|
+
? hexToBytes(mintAddress)
|
|
197
|
+
: new Uint8Array(getAddressEncoder().encode(mintAddress));
|
|
205
198
|
const tokenId = computeTokenId(bytes);
|
|
206
199
|
this._tokenIdCache.set(mintAddress, tokenId);
|
|
207
200
|
return 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
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
} from "./stealth";
|
|
56
56
|
import { selectUtxos, type UtxoDescriptor } from "./psbt";
|
|
57
57
|
import { hexToBytes, bytesToHex, bigintToBytes } from "./crypto";
|
|
58
|
+
import { getAddressEncoder, type Address } from "@solana/kit";
|
|
58
59
|
import { EventClient } from "./event-client";
|
|
59
60
|
import { type DepositOpReturnContext, type BitcoinNetwork } from "./taproot";
|
|
60
61
|
|
|
@@ -269,23 +270,18 @@ export class UTXOpiaClient {
|
|
|
269
270
|
const cached = this._tokenIdCache.get(mintAddress);
|
|
270
271
|
if (cached !== undefined) return cached;
|
|
271
272
|
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
//
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
bytes = new PublicKey(mintAddress).toBytes();
|
|
285
|
-
}
|
|
286
|
-
} catch {
|
|
287
|
-
bytes = mintBytes;
|
|
288
|
-
}
|
|
273
|
+
// 64 hex chars is a raw token id; anything else is a base58 mint.
|
|
274
|
+
//
|
|
275
|
+
// Decoded with getAddressEncoder, the same path pda.ts uses, because
|
|
276
|
+
// require("@solana/web3.js") is not callable from an ESM browser bundle —
|
|
277
|
+
// it threw for every base58 mint, and the hex fallback behind it threw
|
|
278
|
+
// again on the same input. There is no fallback now: both branches are
|
|
279
|
+
// total for their input, and a malformed mint should raise, not be padded
|
|
280
|
+
// into a different token's id.
|
|
281
|
+
const bytes =
|
|
282
|
+
mintAddress.length === 64 && /^[0-9a-fA-F]+$/.test(mintAddress)
|
|
283
|
+
? hexToBytes(mintAddress)
|
|
284
|
+
: new Uint8Array(getAddressEncoder().encode(mintAddress as Address));
|
|
289
285
|
|
|
290
286
|
const tokenId = computeTokenId(bytes);
|
|
291
287
|
this._tokenIdCache.set(mintAddress, tokenId);
|
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
|
+
}
|