@utxopia/sdk 0.1.0-alpha.4 → 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/bitcoin/ika.d.ts +4 -3
- package/dist/bitcoin/ika.js +4 -7
- package/dist/client.d.ts +3 -3
- package/dist/client.js +3 -3
- package/dist/config.js +5 -2
- package/dist/crypto-babyjub.d.ts +0 -4
- package/dist/crypto-babyjub.js +0 -4
- package/dist/crypto.d.ts +2 -5
- package/dist/crypto.js +5 -23
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/instructions.d.ts +0 -113
- package/dist/instructions.js +0 -127
- package/dist/poseidon.d.ts +0 -4
- package/dist/poseidon.js +0 -8
- package/dist/prover/index.d.ts +0 -1
- package/dist/prover/web.d.ts +8 -15
- package/dist/prover/web.js +24 -49
- package/dist/psbt.d.ts +2 -1
- package/dist/psbt.js +6 -2
- package/dist/spend-doc.js +1 -1
- package/dist/stealth.d.ts +6 -12
- package/dist/stealth.js +0 -14
- package/dist/taproot.d.ts +32 -4
- package/dist/taproot.js +54 -32
- package/dist/vk-registry.d.ts +21 -0
- package/dist/vk-registry.js +39 -0
- package/package.json +1 -32
- package/src/bitcoin/ika.ts +6 -11
- package/src/client.ts +7 -7
- package/src/config.ts +5 -2
- package/src/crypto-babyjub.ts +0 -5
- package/src/crypto.ts +5 -24
- package/src/index.ts +5 -0
- package/src/instructions.ts +0 -214
- package/src/poseidon.ts +0 -9
- package/src/prover/index.ts +0 -8
- package/src/prover/web.ts +26 -72
- package/src/psbt.ts +8 -3
- package/src/spend-doc.ts +1 -2
- package/src/stealth.ts +7 -23
- package/src/taproot.ts +74 -39
- package/src/vk-registry.ts +52 -0
package/src/client.ts
CHANGED
|
@@ -56,7 +56,7 @@ import {
|
|
|
56
56
|
import { selectUtxos, type UtxoDescriptor } from "./psbt";
|
|
57
57
|
import { hexToBytes, bytesToHex, bigintToBytes } from "./crypto";
|
|
58
58
|
import { EventClient } from "./event-client";
|
|
59
|
-
import { type DepositOpReturnContext } from "./taproot";
|
|
59
|
+
import { type DepositOpReturnContext, type BitcoinNetwork } from "./taproot";
|
|
60
60
|
|
|
61
61
|
// ─── Types ──────────────────────────────────────────────────────────
|
|
62
62
|
|
|
@@ -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);
|
|
@@ -425,7 +425,7 @@ export class UTXOpiaClient {
|
|
|
425
425
|
depositIndex: number;
|
|
426
426
|
ikaXOnlyPubkey: Uint8Array;
|
|
427
427
|
recipient?: StealthMetaAddress;
|
|
428
|
-
network?:
|
|
428
|
+
network?: BitcoinNetwork;
|
|
429
429
|
}): Promise<TweakDepositResult> {
|
|
430
430
|
if (!this._keys) {
|
|
431
431
|
throw new Error("No keys (login first)");
|
|
@@ -457,7 +457,7 @@ export class UTXOpiaClient {
|
|
|
457
457
|
*/
|
|
458
458
|
async prepareDeposit(opts: {
|
|
459
459
|
recipient?: StealthMetaAddress;
|
|
460
|
-
network?:
|
|
460
|
+
network?: BitcoinNetwork;
|
|
461
461
|
opReturnContext: DepositOpReturnContext;
|
|
462
462
|
}): Promise<NonInteractiveDepositResult> {
|
|
463
463
|
const meta = opts.recipient ?? this._stealthAddress;
|
|
@@ -631,7 +631,7 @@ export class UTXOpiaClient {
|
|
|
631
631
|
}
|
|
632
632
|
}
|
|
633
633
|
|
|
634
|
-
function sdkBitcoinNetworkToAddressNetwork(network: NetworkConfig["bitcoinNetwork"]):
|
|
634
|
+
function sdkBitcoinNetworkToAddressNetwork(network: NetworkConfig["bitcoinNetwork"]): BitcoinNetwork {
|
|
635
635
|
if (network === "mainnet") return "mainnet";
|
|
636
636
|
if (network === "regtest") return "regtest";
|
|
637
637
|
return "testnet";
|
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/crypto-babyjub.ts
CHANGED
|
@@ -35,11 +35,6 @@ export const BABYJUB_D = 168696n;
|
|
|
35
35
|
export const BABYJUB_ORDER =
|
|
36
36
|
2736030358979909402780800718157159386076813972158567259200215660948447373041n;
|
|
37
37
|
|
|
38
|
-
/**
|
|
39
|
-
* Baby Jubjub cofactor
|
|
40
|
-
*/
|
|
41
|
-
export const BABYJUB_COFACTOR = 8n;
|
|
42
|
-
|
|
43
38
|
/**
|
|
44
39
|
* Generator point (BASE8) - matches circomlib's BabyPbk() generator
|
|
45
40
|
* This is the base point of the prime-order subgroup (cofactor-cleared).
|
package/src/crypto.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
17
|
+
import { bytesToHex as nobleBytesToHex, hexToBytes as nobleHexToBytes } from "@noble/hashes/utils.js";
|
|
17
18
|
|
|
18
19
|
// =============================================================================
|
|
19
20
|
// Field Constants
|
|
@@ -110,22 +111,15 @@ export function bytesToBigint(bytes: Uint8Array): bigint {
|
|
|
110
111
|
* Convert hex string to Uint8Array
|
|
111
112
|
*/
|
|
112
113
|
export function hexToBytes(hex: string): Uint8Array {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
bytes[i / 2] = parseInt(cleanHex.substr(i, 2), 16);
|
|
117
|
-
}
|
|
118
|
-
return bytes;
|
|
114
|
+
// The 0x prefix is ours; noble rejects it. Everything after is noble's, which
|
|
115
|
+
// also means malformed hex now throws instead of decoding to zero bytes.
|
|
116
|
+
return nobleHexToBytes(hex.startsWith("0x") ? hex.slice(2) : hex);
|
|
119
117
|
}
|
|
120
118
|
|
|
121
119
|
/**
|
|
122
120
|
* Convert Uint8Array to hex string
|
|
123
121
|
*/
|
|
124
|
-
export
|
|
125
|
-
return Array.from(bytes)
|
|
126
|
-
.map((b) => b.toString(16).padStart(2, "0"))
|
|
127
|
-
.join("");
|
|
128
|
-
}
|
|
122
|
+
export const bytesToHex = nobleBytesToHex;
|
|
129
123
|
|
|
130
124
|
// =============================================================================
|
|
131
125
|
// Hashing Utilities
|
|
@@ -184,16 +178,3 @@ export function scalarFromBytes(bytes: Uint8Array): bigint {
|
|
|
184
178
|
}
|
|
185
179
|
return mod(result, BABYJUB_ORDER);
|
|
186
180
|
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Convert a bigint scalar to 32 bytes (big-endian)
|
|
190
|
-
*/
|
|
191
|
-
export function scalarToBytes(scalar: bigint): Uint8Array {
|
|
192
|
-
const bytes = new Uint8Array(32);
|
|
193
|
-
let temp = mod(scalar, BABYJUB_ORDER);
|
|
194
|
-
for (let i = 31; i >= 0; i--) {
|
|
195
|
-
bytes[i] = Number(temp & 0xffn);
|
|
196
|
-
temp = temp >> 8n;
|
|
197
|
-
}
|
|
198
|
-
return bytes;
|
|
199
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -349,6 +349,10 @@ export {
|
|
|
349
349
|
DEPOSIT_OP_RETURN_VERSION,
|
|
350
350
|
DEPOSIT_POOL_TAG_SIZE,
|
|
351
351
|
DEPOSIT_OP_RETURN_SIZE,
|
|
352
|
+
bech32Hrp,
|
|
353
|
+
networkForHrp,
|
|
354
|
+
p2trAddress,
|
|
355
|
+
type BitcoinNetwork,
|
|
352
356
|
type DepositDestinationChain,
|
|
353
357
|
type DepositBitcoinNetwork,
|
|
354
358
|
type DepositOpReturnContext,
|
|
@@ -803,6 +807,7 @@ export {
|
|
|
803
807
|
buildVkRegistryData,
|
|
804
808
|
parseVkRegistry,
|
|
805
809
|
assertVkRegistryForShape,
|
|
810
|
+
assertVkeyMatchesRegistry,
|
|
806
811
|
isVkRegistryReady,
|
|
807
812
|
type JoinSplitVkMaterial,
|
|
808
813
|
type SnarkjsVkeyJson,
|
package/src/instructions.ts
CHANGED
|
@@ -2191,124 +2191,6 @@ export { hexToBytes, bytesToHex } from "./crypto";
|
|
|
2191
2191
|
// Permissioned Pool Builders
|
|
2192
2192
|
// =============================================================================
|
|
2193
2193
|
|
|
2194
|
-
// ---------------------------------------------------------------------------
|
|
2195
|
-
// initializePermissioned (disc=21)
|
|
2196
|
-
// ---------------------------------------------------------------------------
|
|
2197
|
-
|
|
2198
|
-
/** initializePermissioned instruction options */
|
|
2199
|
-
export interface InitializePermissionedOptions {
|
|
2200
|
-
/** PDA bump for pool state */
|
|
2201
|
-
poolBump: number;
|
|
2202
|
-
/** PDA bump for commitment tree */
|
|
2203
|
-
treeBump: number;
|
|
2204
|
-
/** Deposit fee in basis points (u16 LE) */
|
|
2205
|
-
depositFeeBps: number;
|
|
2206
|
-
/** Withdrawal fee in basis points (u16 LE) */
|
|
2207
|
-
withdrawalFeeBps: number;
|
|
2208
|
-
/** Auditor's Solana pubkey (32 bytes) */
|
|
2209
|
-
auditor: Uint8Array;
|
|
2210
|
-
/** Auditor's viewing public key (32 bytes) */
|
|
2211
|
-
auditorViewingPubkey: Uint8Array;
|
|
2212
|
-
/** Account addresses — same layout as initialize (disc=0) */
|
|
2213
|
-
accounts: {
|
|
2214
|
-
/** 0. pool_state (writable) */
|
|
2215
|
-
poolState: Address;
|
|
2216
|
-
/** 1. commitment_tree (writable) */
|
|
2217
|
-
commitmentTree: Address;
|
|
2218
|
-
/** 2. zkbtc_mint (writable) */
|
|
2219
|
-
zkbtcMint: Address;
|
|
2220
|
-
/** 3. pool_vault (writable) */
|
|
2221
|
-
poolVault: Address;
|
|
2222
|
-
/** 4. deposit_vault (writable) */
|
|
2223
|
-
depositVault: Address;
|
|
2224
|
-
/** 5. authority (signer, writable — pays for storage) */
|
|
2225
|
-
authority: Address;
|
|
2226
|
-
/** 6. system_program (readonly) */
|
|
2227
|
-
systemProgram: Address;
|
|
2228
|
-
};
|
|
2229
|
-
}
|
|
2230
|
-
|
|
2231
|
-
/**
|
|
2232
|
-
* Build initializePermissioned instruction data (disc=21).
|
|
2233
|
-
*
|
|
2234
|
-
* Layout (after disc byte — same as initialize plus two 32-byte fields):
|
|
2235
|
-
* pool_bump(1) + tree_bump(1) + deposit_fee_bps(2 LE) + withdrawal_fee_bps(2 LE)
|
|
2236
|
-
* + auditor(32) + auditor_viewing_pubkey(32)
|
|
2237
|
-
* = 70 bytes of payload; 71 bytes total with disc.
|
|
2238
|
-
*/
|
|
2239
|
-
export function buildInitializePermissionedInstructionData(options: {
|
|
2240
|
-
poolBump: number;
|
|
2241
|
-
treeBump: number;
|
|
2242
|
-
depositFeeBps: number;
|
|
2243
|
-
withdrawalFeeBps: number;
|
|
2244
|
-
auditor: Uint8Array;
|
|
2245
|
-
auditorViewingPubkey: Uint8Array;
|
|
2246
|
-
}): Uint8Array {
|
|
2247
|
-
if (options.auditor.length !== 32) {
|
|
2248
|
-
throw new Error(`auditor must be 32 bytes, got ${options.auditor.length}`);
|
|
2249
|
-
}
|
|
2250
|
-
if (options.auditorViewingPubkey.length !== 32) {
|
|
2251
|
-
throw new Error(`auditorViewingPubkey must be 32 bytes, got ${options.auditorViewingPubkey.length}`);
|
|
2252
|
-
}
|
|
2253
|
-
|
|
2254
|
-
// disc(1) + pool_bump(1) + tree_bump(1) + deposit_fee_bps(2) + withdrawal_fee_bps(2) + auditor(32) + auditor_viewing_pubkey(32) = 71
|
|
2255
|
-
const data = new Uint8Array(71);
|
|
2256
|
-
const view = new DataView(data.buffer);
|
|
2257
|
-
let offset = 0;
|
|
2258
|
-
|
|
2259
|
-
data[offset++] = PERMISSIONED_DISC.INITIALIZE_PERMISSIONED; // disc = 21
|
|
2260
|
-
data[offset++] = options.poolBump;
|
|
2261
|
-
data[offset++] = options.treeBump;
|
|
2262
|
-
view.setUint16(offset, options.depositFeeBps, true); offset += 2;
|
|
2263
|
-
view.setUint16(offset, options.withdrawalFeeBps, true); offset += 2;
|
|
2264
|
-
data.set(options.auditor, offset); offset += 32;
|
|
2265
|
-
data.set(options.auditorViewingPubkey, offset);
|
|
2266
|
-
|
|
2267
|
-
return data;
|
|
2268
|
-
}
|
|
2269
|
-
|
|
2270
|
-
/**
|
|
2271
|
-
* Build a complete initializePermissioned instruction (disc=21).
|
|
2272
|
-
*
|
|
2273
|
-
* Initializes a pool in permissioned mode; deposits/shields require auditor co-signing.
|
|
2274
|
-
*
|
|
2275
|
-
* Accounts (identical to initialize, disc=0):
|
|
2276
|
-
* 0. pool_state (writable)
|
|
2277
|
-
* 1. commitment_tree (writable)
|
|
2278
|
-
* 2. zkbtc_mint (writable)
|
|
2279
|
-
* 3. pool_vault (writable)
|
|
2280
|
-
* 4. deposit_vault (writable)
|
|
2281
|
-
* 5. authority (writable signer)
|
|
2282
|
-
* 6. system_program (readonly)
|
|
2283
|
-
*/
|
|
2284
|
-
export function buildInitializePermissionedInstruction(
|
|
2285
|
-
options: InitializePermissionedOptions,
|
|
2286
|
-
): Instruction {
|
|
2287
|
-
const config = getConfig();
|
|
2288
|
-
const data = buildInitializePermissionedInstructionData({
|
|
2289
|
-
poolBump: options.poolBump,
|
|
2290
|
-
treeBump: options.treeBump,
|
|
2291
|
-
depositFeeBps: options.depositFeeBps,
|
|
2292
|
-
withdrawalFeeBps: options.withdrawalFeeBps,
|
|
2293
|
-
auditor: options.auditor,
|
|
2294
|
-
auditorViewingPubkey: options.auditorViewingPubkey,
|
|
2295
|
-
});
|
|
2296
|
-
|
|
2297
|
-
return {
|
|
2298
|
-
programAddress: config.utxopiaProgramId,
|
|
2299
|
-
accounts: [
|
|
2300
|
-
{ address: options.accounts.poolState, role: AccountRole.WRITABLE },
|
|
2301
|
-
{ address: options.accounts.commitmentTree, role: AccountRole.WRITABLE },
|
|
2302
|
-
{ address: options.accounts.zkbtcMint, role: AccountRole.WRITABLE },
|
|
2303
|
-
{ address: options.accounts.poolVault, role: AccountRole.WRITABLE },
|
|
2304
|
-
{ address: options.accounts.depositVault, role: AccountRole.WRITABLE },
|
|
2305
|
-
{ address: options.accounts.authority, role: AccountRole.WRITABLE_SIGNER },
|
|
2306
|
-
{ address: options.accounts.systemProgram, role: AccountRole.READONLY },
|
|
2307
|
-
],
|
|
2308
|
-
data,
|
|
2309
|
-
};
|
|
2310
|
-
}
|
|
2311
|
-
|
|
2312
2194
|
// ---------------------------------------------------------------------------
|
|
2313
2195
|
// completeDepositPermissioned (disc=22)
|
|
2314
2196
|
// ---------------------------------------------------------------------------
|
|
@@ -2681,102 +2563,6 @@ export function buildRegisterExitDestinationInstruction(options: {
|
|
|
2681
2563
|
};
|
|
2682
2564
|
}
|
|
2683
2565
|
|
|
2684
|
-
// ---------------------------------------------------------------------------
|
|
2685
|
-
// setAuditorFrozen (disc=28)
|
|
2686
|
-
// ---------------------------------------------------------------------------
|
|
2687
|
-
|
|
2688
|
-
/** setAuditorFrozen instruction options */
|
|
2689
|
-
export interface SetAuditorFrozenOptions {
|
|
2690
|
-
/** true = freeze the auditor role; false = un-freeze */
|
|
2691
|
-
frozen: boolean;
|
|
2692
|
-
accounts: {
|
|
2693
|
-
/** 0. pool_state (writable) */
|
|
2694
|
-
poolState: Address;
|
|
2695
|
-
/** 1. auditor (signer) */
|
|
2696
|
-
auditor: Address;
|
|
2697
|
-
};
|
|
2698
|
-
}
|
|
2699
|
-
|
|
2700
|
-
/**
|
|
2701
|
-
* Build setAuditorFrozen instruction data (disc=28).
|
|
2702
|
-
*
|
|
2703
|
-
* Layout: disc(1) + frozen(1) — frozen byte: 0 = not frozen, 1 = frozen.
|
|
2704
|
-
*/
|
|
2705
|
-
export function buildSetAuditorFrozenInstructionData(frozen: boolean): Uint8Array {
|
|
2706
|
-
return new Uint8Array([INSTRUCTION.SET_AUDITOR_FROZEN, frozen ? 1 : 0]);
|
|
2707
|
-
}
|
|
2708
|
-
|
|
2709
|
-
/**
|
|
2710
|
-
* Build a complete setAuditorFrozen instruction (disc=28).
|
|
2711
|
-
*
|
|
2712
|
-
* Accounts:
|
|
2713
|
-
* 0. pool_state (writable)
|
|
2714
|
-
* 1. auditor (signer)
|
|
2715
|
-
*/
|
|
2716
|
-
export function buildSetAuditorFrozenInstruction(options: SetAuditorFrozenOptions): Instruction {
|
|
2717
|
-
const config = getConfig();
|
|
2718
|
-
return {
|
|
2719
|
-
programAddress: config.utxopiaProgramId,
|
|
2720
|
-
accounts: [
|
|
2721
|
-
{ address: options.accounts.poolState, role: AccountRole.WRITABLE },
|
|
2722
|
-
{ address: options.accounts.auditor, role: AccountRole.READONLY_SIGNER },
|
|
2723
|
-
],
|
|
2724
|
-
data: buildSetAuditorFrozenInstructionData(options.frozen),
|
|
2725
|
-
};
|
|
2726
|
-
}
|
|
2727
|
-
|
|
2728
|
-
// ---------------------------------------------------------------------------
|
|
2729
|
-
// setAuditorViewingPubkey (disc=29)
|
|
2730
|
-
// ---------------------------------------------------------------------------
|
|
2731
|
-
|
|
2732
|
-
/** setAuditorViewingPubkey instruction options */
|
|
2733
|
-
export interface SetAuditorViewingPubkeyOptions {
|
|
2734
|
-
/** New 32-byte viewing pubkey for the auditor */
|
|
2735
|
-
viewingPubkey: Uint8Array;
|
|
2736
|
-
accounts: {
|
|
2737
|
-
/** 0. pool_state (writable) */
|
|
2738
|
-
poolState: Address;
|
|
2739
|
-
/** 1. auditor (signer) */
|
|
2740
|
-
auditor: Address;
|
|
2741
|
-
};
|
|
2742
|
-
}
|
|
2743
|
-
|
|
2744
|
-
/**
|
|
2745
|
-
* Build setAuditorViewingPubkey instruction data (disc=29).
|
|
2746
|
-
*
|
|
2747
|
-
* Layout: disc(1) + viewing_pubkey(32) = 33 bytes.
|
|
2748
|
-
*/
|
|
2749
|
-
export function buildSetAuditorViewingPubkeyInstructionData(viewingPubkey: Uint8Array): Uint8Array {
|
|
2750
|
-
if (viewingPubkey.length !== 32) {
|
|
2751
|
-
throw new Error(`viewingPubkey must be 32 bytes, got ${viewingPubkey.length}`);
|
|
2752
|
-
}
|
|
2753
|
-
const data = new Uint8Array(33);
|
|
2754
|
-
data[0] = INSTRUCTION.SET_AUDITOR_VIEWING_PUBKEY;
|
|
2755
|
-
data.set(viewingPubkey, 1);
|
|
2756
|
-
return data;
|
|
2757
|
-
}
|
|
2758
|
-
|
|
2759
|
-
/**
|
|
2760
|
-
* Build a complete setAuditorViewingPubkey instruction (disc=29).
|
|
2761
|
-
*
|
|
2762
|
-
* Accounts:
|
|
2763
|
-
* 0. pool_state (writable)
|
|
2764
|
-
* 1. auditor (signer)
|
|
2765
|
-
*/
|
|
2766
|
-
export function buildSetAuditorViewingPubkeyInstruction(
|
|
2767
|
-
options: SetAuditorViewingPubkeyOptions,
|
|
2768
|
-
): Instruction {
|
|
2769
|
-
const config = getConfig();
|
|
2770
|
-
return {
|
|
2771
|
-
programAddress: config.utxopiaProgramId,
|
|
2772
|
-
accounts: [
|
|
2773
|
-
{ address: options.accounts.poolState, role: AccountRole.WRITABLE },
|
|
2774
|
-
{ address: options.accounts.auditor, role: AccountRole.READONLY_SIGNER },
|
|
2775
|
-
],
|
|
2776
|
-
data: buildSetAuditorViewingPubkeyInstructionData(options.viewingPubkey),
|
|
2777
|
-
};
|
|
2778
|
-
}
|
|
2779
|
-
|
|
2780
2566
|
// ---------------------------------------------------------------------------
|
|
2781
2567
|
// rotateAuditor (disc=35)
|
|
2782
2568
|
// ---------------------------------------------------------------------------
|
package/src/poseidon.ts
CHANGED
|
@@ -164,12 +164,3 @@ export function computeTokenId(mintBytes: Uint8Array): bigint {
|
|
|
164
164
|
const reduced = reduceToField(mintBytes);
|
|
165
165
|
return poseidonHashSync([reduced, 0n]);
|
|
166
166
|
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Convenience: compute token_id from a Solana address string
|
|
170
|
-
*/
|
|
171
|
-
export function computeTokenIdFromAddress(mintAddress: string): bigint {
|
|
172
|
-
// Base58 decode — import from @solana/kit if available, otherwise use raw bytes
|
|
173
|
-
// For now, caller should pass raw bytes via computeTokenId
|
|
174
|
-
throw new Error("Use computeTokenId(mintBytes) with raw pubkey bytes");
|
|
175
|
-
}
|
package/src/prover/index.ts
CHANGED
package/src/prover/web.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
computeJoinSplitCommitmentSync,
|
|
16
16
|
computeJoinSplitNullifierSync,
|
|
17
17
|
} from "../poseidon";
|
|
18
|
-
import { BN254_FIELD_PRIME } from "../crypto";
|
|
18
|
+
import { BN254_FIELD_PRIME, bytesToHex } from "../crypto";
|
|
19
19
|
import { TREE_DEPTH } from "../merkle";
|
|
20
20
|
import { getConfig } from "../config";
|
|
21
21
|
import { MAX_SAFE_JOINSPLIT_SIZE } from "../vk-registry";
|
|
@@ -60,13 +60,6 @@ export interface ProofData {
|
|
|
60
60
|
|
|
61
61
|
export type CircuitType = `joinsplit_${number}x${number}`;
|
|
62
62
|
|
|
63
|
-
/** Names of non-JoinSplit auxiliary circuits (selective disclosure). */
|
|
64
|
-
export type AuxCircuitName =
|
|
65
|
-
| "ownership"
|
|
66
|
-
| "range_sum"
|
|
67
|
-
| "range_sum_4"
|
|
68
|
-
| "range_sum_16";
|
|
69
|
-
|
|
70
63
|
// Environment detection
|
|
71
64
|
const isBrowser = typeof window !== "undefined";
|
|
72
65
|
const isNode = typeof process !== "undefined" && process.versions?.node;
|
|
@@ -74,6 +67,27 @@ const isNode = typeof process !== "undefined" && process.versions?.node;
|
|
|
74
67
|
// Configurable circuit paths
|
|
75
68
|
let circuitBasePath = isBrowser ? "/circuits/groth16" : "./circuits";
|
|
76
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
|
+
|
|
77
91
|
/**
|
|
78
92
|
* Set the base path for circuit artifacts
|
|
79
93
|
*/
|
|
@@ -81,17 +95,11 @@ export function setCircuitPath(path: string): void {
|
|
|
81
95
|
if (path === circuitBasePath) return;
|
|
82
96
|
circuitBasePath = path;
|
|
83
97
|
circuitCache.clear();
|
|
98
|
+
vkeyCache.clear();
|
|
84
99
|
artifactBytesCache.clear();
|
|
85
100
|
artifactDownloadCache.clear();
|
|
86
101
|
}
|
|
87
102
|
|
|
88
|
-
/**
|
|
89
|
-
* Get the current circuit base path
|
|
90
|
-
*/
|
|
91
|
-
export function getCircuitPath(): string {
|
|
92
|
-
return circuitBasePath;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
103
|
// Lazy-loaded snarkjs module
|
|
96
104
|
let snarkjs: any = null;
|
|
97
105
|
|
|
@@ -101,6 +109,7 @@ interface CircuitArtifact {
|
|
|
101
109
|
}
|
|
102
110
|
|
|
103
111
|
const circuitCache = new Map<CircuitType, CircuitArtifact>();
|
|
112
|
+
const vkeyCache = new Map<CircuitType, unknown>();
|
|
104
113
|
let proverInitialized = false;
|
|
105
114
|
|
|
106
115
|
/**
|
|
@@ -164,9 +173,6 @@ export function setCircuitArtifactDigests(digests: Record<string, string> | null
|
|
|
164
173
|
artifactBytesCache.clear();
|
|
165
174
|
}
|
|
166
175
|
|
|
167
|
-
const toHex = (buf: ArrayBuffer) =>
|
|
168
|
-
Array.from(new Uint8Array(buf), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
169
|
-
|
|
170
176
|
async function verifyArtifactBytes(url: string, bytes: Uint8Array): Promise<void> {
|
|
171
177
|
if (!artifactDigests) return;
|
|
172
178
|
// Key by path-under-base so one manifest works across origins (local dev vs CDN).
|
|
@@ -177,8 +183,8 @@ async function verifyArtifactBytes(url: string, bytes: Uint8Array): Promise<void
|
|
|
177
183
|
`Regenerate the artifact manifest so it covers every shape this origin serves.`,
|
|
178
184
|
);
|
|
179
185
|
}
|
|
180
|
-
const actual =
|
|
181
|
-
await crypto.subtle.digest("SHA-256", bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer),
|
|
186
|
+
const actual = bytesToHex(
|
|
187
|
+
new Uint8Array(await crypto.subtle.digest("SHA-256", bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer)),
|
|
182
188
|
);
|
|
183
189
|
if (actual !== artifactDigests[key]) {
|
|
184
190
|
throw new Error(
|
|
@@ -717,55 +723,3 @@ export async function cleanup(): Promise<void> {
|
|
|
717
723
|
// ==========================================================================
|
|
718
724
|
// Solana Instruction Building (for Groth16 on-chain verification)
|
|
719
725
|
// ==========================================================================
|
|
720
|
-
|
|
721
|
-
/**
|
|
722
|
-
* Groth16 verifier program ID (from current config)
|
|
723
|
-
*/
|
|
724
|
-
export function getGroth16VerifierProgramId(): Address {
|
|
725
|
-
const config = getConfig();
|
|
726
|
-
return config.groth16VerifierProgramId;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
/**
|
|
730
|
-
* Build instruction data for Groth16 verification
|
|
731
|
-
*/
|
|
732
|
-
export function buildVerifyInstructionData(
|
|
733
|
-
proof: Uint8Array,
|
|
734
|
-
publicSignals: string[],
|
|
735
|
-
vkHash: string
|
|
736
|
-
): Uint8Array {
|
|
737
|
-
const piBytes = publicSignals.flatMap((pi) => {
|
|
738
|
-
const bytes = new Array(32).fill(0);
|
|
739
|
-
const bigint = BigInt(pi);
|
|
740
|
-
for (let i = 31; i >= 0; i--) {
|
|
741
|
-
bytes[i] = Number((bigint >> BigInt((31 - i) * 8)) & 0xFFn);
|
|
742
|
-
}
|
|
743
|
-
return bytes;
|
|
744
|
-
});
|
|
745
|
-
|
|
746
|
-
const cleanHex = vkHash.startsWith("0x") ? vkHash.slice(2) : vkHash;
|
|
747
|
-
const vkHashBytes = new Uint8Array(cleanHex.length / 2);
|
|
748
|
-
for (let i = 0; i < vkHashBytes.length; i++) {
|
|
749
|
-
vkHashBytes[i] = parseInt(cleanHex.substr(i * 2, 2), 16);
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
const totalSize = proof.length + 4 + piBytes.length + 32;
|
|
753
|
-
const data = new Uint8Array(totalSize);
|
|
754
|
-
let offset = 0;
|
|
755
|
-
|
|
756
|
-
data.set(proof, offset);
|
|
757
|
-
offset += proof.length;
|
|
758
|
-
|
|
759
|
-
const piCount = publicSignals.length;
|
|
760
|
-
data[offset++] = piCount & 0xff;
|
|
761
|
-
data[offset++] = (piCount >> 8) & 0xff;
|
|
762
|
-
data[offset++] = (piCount >> 16) & 0xff;
|
|
763
|
-
data[offset++] = (piCount >> 24) & 0xff;
|
|
764
|
-
|
|
765
|
-
data.set(new Uint8Array(piBytes), offset);
|
|
766
|
-
offset += piBytes.length;
|
|
767
|
-
|
|
768
|
-
data.set(vkHashBytes, offset);
|
|
769
|
-
|
|
770
|
-
return data;
|
|
771
|
-
}
|
package/src/psbt.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import * as btc from "@scure/btc-signer";
|
|
14
14
|
import { hex } from "@scure/base";
|
|
15
|
-
import { DEPOSIT_OP_RETURN_SIZE, createOpReturnScriptFromPayload } from "./taproot";
|
|
15
|
+
import { DEPOSIT_OP_RETURN_SIZE, createOpReturnScriptFromPayload, bech32Hrp, type BitcoinNetwork } from "./taproot";
|
|
16
16
|
|
|
17
17
|
// =============================================================================
|
|
18
18
|
// Types
|
|
@@ -54,7 +54,7 @@ export interface BuildDepositPsbtParams {
|
|
|
54
54
|
/** Fee rate in sats/vbyte */
|
|
55
55
|
feeRate: number;
|
|
56
56
|
/** Bitcoin network */
|
|
57
|
-
network?:
|
|
57
|
+
network?: BitcoinNetwork;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/** Result of PSBT construction */
|
|
@@ -151,7 +151,12 @@ export function buildDepositPsbt(params: BuildDepositPsbtParams): BuildDepositPs
|
|
|
151
151
|
throw new Error(`OP_RETURN payload must be ${DEPOSIT_OP_RETURN_SIZE} bytes, got ${opReturnPayload.length}`);
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
|
|
154
|
+
// Regtest shares testnet's version bytes but not its bech32 hrp, so folding it
|
|
155
|
+
// into TEST_NETWORK leaves every bcrt1 address undecodable.
|
|
156
|
+
const btcNetwork =
|
|
157
|
+
network === "mainnet"
|
|
158
|
+
? btc.NETWORK
|
|
159
|
+
: { ...btc.TEST_NETWORK, bech32: bech32Hrp(network) };
|
|
155
160
|
|
|
156
161
|
// Calculate total input value
|
|
157
162
|
const totalInput = senderUtxos.reduce((sum, u) => sum + u.value, 0);
|
package/src/spend-doc.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
createUnshieldBoundParams,
|
|
19
19
|
type SolanaPrivacyDomainContext,
|
|
20
20
|
} from "./bound-params";
|
|
21
|
+
import { bytesToHex as hex } from "./crypto";
|
|
21
22
|
|
|
22
23
|
export interface SpendDoc {
|
|
23
24
|
mode: "transfer" | "unshield" | "redeem";
|
|
@@ -64,8 +65,6 @@ function fmt(raw: bigint, decimals: number): string {
|
|
|
64
65
|
return `${neg ? "-" : ""}${whole}${frac ? `.${frac}` : ""}`;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
const hex = (b: Uint8Array) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
68
|
-
|
|
69
68
|
function sortedValues(v: bigint[]): string {
|
|
70
69
|
return [...v].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)).join(",");
|
|
71
70
|
}
|