@lethenymous/sdk 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +171 -2
- package/dist/accounts.d.ts +6 -0
- package/dist/accounts.js +75 -0
- package/dist/client.d.ts +73 -0
- package/dist/client.js +357 -0
- package/dist/crypto.d.ts +24 -0
- package/dist/crypto.js +111 -0
- package/dist/encoding.d.ts +26 -0
- package/dist/encoding.js +41 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +13 -0
- package/dist/instructions.d.ts +10 -0
- package/dist/instructions.js +78 -0
- package/dist/math.d.ts +18 -0
- package/dist/math.js +76 -0
- package/dist/merkle.d.ts +4 -0
- package/dist/merkle.js +10 -0
- package/dist/pda.d.ts +18 -0
- package/dist/pda.js +21 -0
- package/dist/prover.d.ts +26 -0
- package/dist/prover.js +177 -0
- package/dist/store.d.ts +39 -0
- package/dist/store.js +662 -0
- package/dist/types.d.ts +203 -0
- package/dist/types.js +1 -0
- package/dist/wallet.d.ts +73 -0
- package/dist/wallet.js +491 -0
- package/dist/witness.d.ts +71 -0
- package/dist/witness.js +872 -0
- package/package.json +48 -4
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { PublicKey, SYSVAR_RENT_PUBKEY, SystemProgram, TransactionInstruction, } from "@solana/web3.js";
|
|
2
|
+
import { TOKEN_PROGRAM_ID, PROGRAM_ID, discriminator, concat, u16, u64, vec, bytes32, enumByte } from "./encoding.js";
|
|
3
|
+
import { pda } from "./pda.js";
|
|
4
|
+
const meta = (pubkey, isSigner = false, isWritable = false) => ({ pubkey, isSigner, isWritable });
|
|
5
|
+
const ro = (key) => meta(key);
|
|
6
|
+
const rw = (key) => meta(key, false, true);
|
|
7
|
+
const signer = (key) => meta(key, true, true);
|
|
8
|
+
function ix(name, args, accounts, programId) {
|
|
9
|
+
return new TransactionInstruction({ programId, keys: accounts, data: concat(discriminator(name), args) });
|
|
10
|
+
}
|
|
11
|
+
export function initializePool(payer, authority, creator, a, b, feeBps, programId = PROGRAM_ID) {
|
|
12
|
+
if (Buffer.compare(a.toBuffer(), b.toBuffer()) >= 0)
|
|
13
|
+
throw new Error("Mints must be ordered");
|
|
14
|
+
const [pool] = pda.pool(a, b, feeBps, programId);
|
|
15
|
+
return ix("initialize_pool", u16(feeBps), [
|
|
16
|
+
signer(payer), signer(authority), ro(creator), ro(a), ro(b), rw(pool),
|
|
17
|
+
rw(pda.vaultA(pool, programId)[0]), rw(pda.vaultB(pool, programId)[0]),
|
|
18
|
+
rw(pda.protocolFeeA(pool, programId)[0]), rw(pda.protocolFeeB(pool, programId)[0]),
|
|
19
|
+
rw(pda.creatorFeeA(pool, programId)[0]), rw(pda.creatorFeeB(pool, programId)[0]),
|
|
20
|
+
rw(pda.lpMint(pool, programId)[0]), rw(pda.lpLock(pool, programId)[0]),
|
|
21
|
+
ro(TOKEN_PROGRAM_ID), ro(SystemProgram.programId), ro(SYSVAR_RENT_PUBKEY),
|
|
22
|
+
], programId);
|
|
23
|
+
}
|
|
24
|
+
export function initializeShieldedState(payer, pool, state, programId = PROGRAM_ID) {
|
|
25
|
+
const s = state ?? { tokenAMint: PublicKey.default, tokenBMint: PublicKey.default };
|
|
26
|
+
return ix("initialize_shielded_state", Buffer.alloc(0), [
|
|
27
|
+
signer(payer), ro(pool), ro(s.tokenAMint), ro(s.tokenBMint),
|
|
28
|
+
rw(pda.shielded(pool, programId)[0]), rw(pda.tree(pool, programId)[0]),
|
|
29
|
+
rw(pda.custodyA(pool, programId)[0]), rw(pda.custodyB(pool, programId)[0]),
|
|
30
|
+
ro(TOKEN_PROGRAM_ID), ro(SystemProgram.programId), ro(SYSVAR_RENT_PUBKEY),
|
|
31
|
+
], programId);
|
|
32
|
+
}
|
|
33
|
+
export function addLiquidity(provider, pool, providerA, providerB, providerLp, a, b, minLp, programId = PROGRAM_ID) {
|
|
34
|
+
return ix("add_liquidity", concat(u64(a), u64(b), u64(minLp)), [
|
|
35
|
+
signer(provider), rw(pool.address), ro(pool.tokenAMint), ro(pool.tokenBMint),
|
|
36
|
+
rw(pool.tokenAVault), rw(pool.tokenBVault), rw(pool.lpMint), rw(pda.lpLock(pool.address, programId)[0]),
|
|
37
|
+
rw(providerA), rw(providerB), rw(providerLp), ro(TOKEN_PROGRAM_ID),
|
|
38
|
+
], programId);
|
|
39
|
+
}
|
|
40
|
+
export function shield(depositor, pool, state, asset, amount, owner, randomness, encrypted, depositorA, depositorB, programId = PROGRAM_ID) {
|
|
41
|
+
return ix("shield", concat(enumByte(asset), u64(amount), bytes32(owner), bytes32(randomness), vec(encrypted)), [
|
|
42
|
+
signer(depositor), ro(pool.address), rw(pda.shielded(pool.address, programId)[0]), rw(state.tree),
|
|
43
|
+
ro(pool.tokenAMint), ro(pool.tokenBMint), rw(state.custodyA), rw(state.custodyB),
|
|
44
|
+
rw(depositorA), rw(depositorB), ro(TOKEN_PROGRAM_ID),
|
|
45
|
+
], programId);
|
|
46
|
+
}
|
|
47
|
+
export function removeLiquidity(provider, pool, providerA, providerB, providerLp, lpAmount, minA, minB, programId = PROGRAM_ID) {
|
|
48
|
+
return ix("remove_liquidity", concat(u64(lpAmount), u64(minA), u64(minB)), [
|
|
49
|
+
signer(provider), rw(pool.address), ro(pool.tokenAMint), ro(pool.tokenBMint),
|
|
50
|
+
rw(pool.tokenAVault), rw(pool.tokenBVault), rw(pool.lpMint), rw(providerA), rw(providerB),
|
|
51
|
+
rw(providerLp), ro(TOKEN_PROGRAM_ID),
|
|
52
|
+
], programId);
|
|
53
|
+
}
|
|
54
|
+
export function swap(trader, pool, traderA, traderB, direction, amountIn, minAmountOut, programId = PROGRAM_ID) {
|
|
55
|
+
return ix("swap", concat(enumByte(direction), u64(amountIn), u64(minAmountOut)), [
|
|
56
|
+
signer(trader), rw(pool.address), ro(pool.tokenAMint), ro(pool.tokenBMint),
|
|
57
|
+
rw(pool.tokenAVault), rw(pool.tokenBVault), ro(pool.lpMint),
|
|
58
|
+
rw(pool.protocolFeeVaultA), rw(pool.protocolFeeVaultB), rw(pool.creatorFeeVaultA), rw(pool.creatorFeeVaultB),
|
|
59
|
+
rw(traderA), rw(traderB), ro(TOKEN_PROGRAM_ID),
|
|
60
|
+
], programId);
|
|
61
|
+
}
|
|
62
|
+
export function unshield(payer, pool, state, asset, amount, root, rootSequence, generation, nullifierValue, recipient, recipientA, recipientB, proof, publicInputs, programId = PROGRAM_ID) {
|
|
63
|
+
return ix("unshield", concat(enumByte(asset), bytes32(root), u64(rootSequence), u64(generation), u64(amount), bytes32(nullifierValue), vec(proof), vec(publicInputs)), [
|
|
64
|
+
signer(payer), ro(pool.address), rw(pda.shielded(pool.address, programId)[0]), rw(state.tree),
|
|
65
|
+
ro(pool.tokenAMint), ro(pool.tokenBMint), rw(state.custodyA), rw(state.custodyB), ro(recipient),
|
|
66
|
+
rw(recipientA), rw(recipientB), rw(pda.spent(pool.address, nullifierValue, programId)[0]),
|
|
67
|
+
ro(TOKEN_PROGRAM_ID), ro(SystemProgram.programId),
|
|
68
|
+
], programId);
|
|
69
|
+
}
|
|
70
|
+
export function privateSwap(payer, pool, state, direction, root, rootSequence, generation, nullifierValue, amountIn, amountOut, changeAmount, changeCommitment, outputCommitment, proof, programId = PROGRAM_ID) {
|
|
71
|
+
return ix("private_swap", concat(enumByte(direction), bytes32(root), u64(rootSequence), u64(generation), bytes32(nullifierValue), u64(amountIn), u64(amountOut), u64(changeAmount), bytes32(changeCommitment), bytes32(outputCommitment), vec(proof)), [
|
|
72
|
+
signer(payer), rw(pool.address), rw(pda.shielded(pool.address, programId)[0]), rw(state.tree),
|
|
73
|
+
rw(pool.tokenAMint), rw(pool.tokenBMint), ro(pool.lpMint), rw(pool.tokenAVault), rw(pool.tokenBVault),
|
|
74
|
+
rw(pool.protocolFeeVaultA), rw(pool.protocolFeeVaultB), rw(pool.creatorFeeVaultA), rw(pool.creatorFeeVaultB),
|
|
75
|
+
rw(state.custodyA), rw(state.custodyB), ro(TOKEN_PROGRAM_ID), ro(SystemProgram.programId),
|
|
76
|
+
rw(pda.spent(pool.address, nullifierValue, programId)[0]),
|
|
77
|
+
], programId);
|
|
78
|
+
}
|
package/dist/math.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const FEE_DENOMINATOR = 10000n;
|
|
2
|
+
export declare const MINIMUM_LIQUIDITY = 1000n;
|
|
3
|
+
export interface FeeBreakdown {
|
|
4
|
+
totalFee: bigint;
|
|
5
|
+
protocolFee: bigint;
|
|
6
|
+
creatorFee: bigint;
|
|
7
|
+
lpFee: bigint;
|
|
8
|
+
pricingInput: bigint;
|
|
9
|
+
}
|
|
10
|
+
export declare function validateFeeTier(feeBps: number): void;
|
|
11
|
+
export declare function feeBreakdown(amountIn: bigint, feeBps: number): FeeBreakdown;
|
|
12
|
+
export declare function minimumLpClaimReserve(totalLp: bigint, lockedLp?: bigint): bigint;
|
|
13
|
+
export declare function swapOutput(reserveIn: bigint, reserveOut: bigint, amountIn: bigint, feeBps: number): bigint;
|
|
14
|
+
export declare function swapOutputPreservingLpClaims(reserveIn: bigint, reserveOut: bigint, amountIn: bigint, feeBps: number, totalLp: bigint, lockedLp?: bigint): bigint;
|
|
15
|
+
export declare function initialLp(a: bigint, b: bigint): bigint;
|
|
16
|
+
export declare function proportional(amount: bigint, lp: bigint, supply: bigint): bigint;
|
|
17
|
+
export declare function optimalDeposit(maxA: bigint, maxB: bigint, reserveA: bigint, reserveB: bigint): [bigint, bigint];
|
|
18
|
+
export declare function sqrt(n: bigint): bigint;
|
package/dist/math.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const FEE_DENOMINATOR = 10000n;
|
|
2
|
+
export const MINIMUM_LIQUIDITY = 1000n;
|
|
3
|
+
export function validateFeeTier(feeBps) {
|
|
4
|
+
if (!Number.isInteger(feeBps) || ![100, 200, 300].includes(feeBps))
|
|
5
|
+
throw new Error("Invalid fee tier");
|
|
6
|
+
}
|
|
7
|
+
export function feeBreakdown(amountIn, feeBps) {
|
|
8
|
+
if (amountIn <= 0n)
|
|
9
|
+
throw new Error("Amount must be positive");
|
|
10
|
+
validateFeeTier(feeBps);
|
|
11
|
+
const totalFee = amountIn * BigInt(feeBps) / FEE_DENOMINATOR;
|
|
12
|
+
const protocolFee = totalFee * 5000n / 10000n;
|
|
13
|
+
const creatorFee = totalFee * 2000n / 10000n;
|
|
14
|
+
return {
|
|
15
|
+
totalFee,
|
|
16
|
+
protocolFee,
|
|
17
|
+
creatorFee,
|
|
18
|
+
lpFee: totalFee - protocolFee - creatorFee,
|
|
19
|
+
pricingInput: amountIn - totalFee,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function ceilDiv(numerator, denominator) {
|
|
23
|
+
if (numerator <= 0n || denominator <= 0n)
|
|
24
|
+
throw new Error("Insufficient liquidity");
|
|
25
|
+
return (numerator - 1n) / denominator + 1n;
|
|
26
|
+
}
|
|
27
|
+
export function minimumLpClaimReserve(totalLp, lockedLp = MINIMUM_LIQUIDITY) {
|
|
28
|
+
if (lockedLp <= 0n || lockedLp >= totalLp)
|
|
29
|
+
throw new Error("Insufficient liquidity");
|
|
30
|
+
return [ceilDiv(totalLp, lockedLp), ceilDiv(totalLp, totalLp - lockedLp)].reduce((a, b) => a > b ? a : b);
|
|
31
|
+
}
|
|
32
|
+
export function swapOutput(reserveIn, reserveOut, amountIn, feeBps) {
|
|
33
|
+
if (reserveIn <= 0n || reserveOut <= 0n)
|
|
34
|
+
throw new Error("Zero reserve");
|
|
35
|
+
const fee = feeBreakdown(amountIn, feeBps);
|
|
36
|
+
const effective = fee.pricingInput * FEE_DENOMINATOR;
|
|
37
|
+
const denominator = reserveIn * FEE_DENOMINATOR + effective;
|
|
38
|
+
const output = reserveOut * effective / denominator;
|
|
39
|
+
if (output <= 0n || output >= reserveOut)
|
|
40
|
+
throw new Error("Invalid output");
|
|
41
|
+
return output;
|
|
42
|
+
}
|
|
43
|
+
export function swapOutputPreservingLpClaims(reserveIn, reserveOut, amountIn, feeBps, totalLp, lockedLp = MINIMUM_LIQUIDITY) {
|
|
44
|
+
const output = swapOutput(reserveIn, reserveOut, amountIn, feeBps);
|
|
45
|
+
if (reserveOut - output < minimumLpClaimReserve(totalLp, lockedLp)) {
|
|
46
|
+
throw new Error("Reserve is too low for LP claims");
|
|
47
|
+
}
|
|
48
|
+
return output;
|
|
49
|
+
}
|
|
50
|
+
export function initialLp(a, b) {
|
|
51
|
+
return sqrt(a * b);
|
|
52
|
+
}
|
|
53
|
+
export function proportional(amount, lp, supply) {
|
|
54
|
+
if (supply <= 0n || lp > supply)
|
|
55
|
+
throw new Error("Insufficient liquidity");
|
|
56
|
+
return amount * lp / supply;
|
|
57
|
+
}
|
|
58
|
+
export function optimalDeposit(maxA, maxB, reserveA, reserveB) {
|
|
59
|
+
if (maxA <= 0n || maxB <= 0n || reserveA <= 0n || reserveB <= 0n)
|
|
60
|
+
throw new Error("Zero amount");
|
|
61
|
+
const b = maxA * reserveB / reserveA;
|
|
62
|
+
return b <= maxB ? [maxA, b] : [maxB * reserveA / reserveB, maxB];
|
|
63
|
+
}
|
|
64
|
+
export function sqrt(n) {
|
|
65
|
+
if (n < 0n)
|
|
66
|
+
throw new Error("Negative square root");
|
|
67
|
+
if (n < 2n)
|
|
68
|
+
return n;
|
|
69
|
+
let x = n;
|
|
70
|
+
let y = (x + 1n) / 2n;
|
|
71
|
+
while (y < x) {
|
|
72
|
+
x = y;
|
|
73
|
+
y = (x + n / x) / 2n;
|
|
74
|
+
}
|
|
75
|
+
return x;
|
|
76
|
+
}
|
package/dist/merkle.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { TreeState } from "./types.js";
|
|
2
|
+
export declare const TREE_DEPTH = 16, ROOT_HISTORY = 32, TREE_CAPACITY: bigint;
|
|
3
|
+
export declare function rootFromTree(tree: TreeState): Uint8Array;
|
|
4
|
+
export declare function verifyPath(leaf: Uint8Array, index: bigint, siblings: Uint8Array[], root: Uint8Array): boolean;
|
package/dist/merkle.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { hash2 } from "./crypto.js";
|
|
2
|
+
export const TREE_DEPTH = 16, ROOT_HISTORY = 32, TREE_CAPACITY = 1n << 16n;
|
|
3
|
+
export function rootFromTree(tree) { if (tree.nextIndex === TREE_CAPACITY) {
|
|
4
|
+
if (tree.frontierPresent[15] !== 1)
|
|
5
|
+
throw new Error("Invalid terminal tree");
|
|
6
|
+
return tree.frontier[15];
|
|
7
|
+
} let node = tree.emptySubtrees[0]; for (let i = 0; i < TREE_DEPTH; i++)
|
|
8
|
+
node = ((tree.nextIndex >> BigInt(i)) & 1n) === 1n ? hash2(tree.frontier[i], node) : hash2(node, tree.emptySubtrees[i]); return node; }
|
|
9
|
+
export function verifyPath(leaf, index, siblings, root) { let n = leaf; for (let i = 0; i < TREE_DEPTH; i++)
|
|
10
|
+
n = ((index >> BigInt(i)) & 1n) === 0n ? hash2(n, siblings[i]) : hash2(siblings[i], n); return Buffer.from(n).equals(Buffer.from(root)); }
|
package/dist/pda.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PublicKey } from "@solana/web3.js";
|
|
2
|
+
export declare const pda: {
|
|
3
|
+
pool: (a: PublicKey, b: PublicKey, feeBps: number, programId?: PublicKey) => [PublicKey, number];
|
|
4
|
+
vaultA: (pool: PublicKey, programId?: PublicKey) => [PublicKey, number];
|
|
5
|
+
vaultB: (pool: PublicKey, programId?: PublicKey) => [PublicKey, number];
|
|
6
|
+
protocolFeeA: (pool: PublicKey, programId?: PublicKey) => [PublicKey, number];
|
|
7
|
+
protocolFeeB: (pool: PublicKey, programId?: PublicKey) => [PublicKey, number];
|
|
8
|
+
creatorFeeA: (pool: PublicKey, programId?: PublicKey) => [PublicKey, number];
|
|
9
|
+
creatorFeeB: (pool: PublicKey, programId?: PublicKey) => [PublicKey, number];
|
|
10
|
+
lpMint: (pool: PublicKey, programId?: PublicKey) => [PublicKey, number];
|
|
11
|
+
lpLock: (pool: PublicKey, programId?: PublicKey) => [PublicKey, number];
|
|
12
|
+
shielded: (pool: PublicKey, programId?: PublicKey) => [PublicKey, number];
|
|
13
|
+
tree: (pool: PublicKey, programId?: PublicKey) => [PublicKey, number];
|
|
14
|
+
custodyA: (pool: PublicKey, programId?: PublicKey) => [PublicKey, number];
|
|
15
|
+
custodyB: (pool: PublicKey, programId?: PublicKey) => [PublicKey, number];
|
|
16
|
+
spent: (pool: PublicKey, nullifier: Uint8Array, programId?: PublicKey) => [PublicKey, number];
|
|
17
|
+
protocolConfig: (programId?: PublicKey) => [PublicKey, number];
|
|
18
|
+
};
|
package/dist/pda.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PublicKey } from "@solana/web3.js";
|
|
2
|
+
import { PROGRAM_ID } from "./encoding.js";
|
|
3
|
+
export const pda = {
|
|
4
|
+
pool: (a, b, feeBps, programId = PROGRAM_ID) => PublicKey.findProgramAddressSync([Buffer.from("pool"), a.toBuffer(), b.toBuffer(), u16le(feeBps)], programId),
|
|
5
|
+
vaultA: (pool, programId = PROGRAM_ID) => find("vault-a", pool, programId),
|
|
6
|
+
vaultB: (pool, programId = PROGRAM_ID) => find("vault-b", pool, programId),
|
|
7
|
+
protocolFeeA: (pool, programId = PROGRAM_ID) => find("protocol-fee-a", pool, programId),
|
|
8
|
+
protocolFeeB: (pool, programId = PROGRAM_ID) => find("protocol-fee-b", pool, programId),
|
|
9
|
+
creatorFeeA: (pool, programId = PROGRAM_ID) => find("creator-fee-a", pool, programId),
|
|
10
|
+
creatorFeeB: (pool, programId = PROGRAM_ID) => find("creator-fee-b", pool, programId),
|
|
11
|
+
lpMint: (pool, programId = PROGRAM_ID) => find("lp", pool, programId),
|
|
12
|
+
lpLock: (pool, programId = PROGRAM_ID) => find("lp-lock", pool, programId),
|
|
13
|
+
shielded: (pool, programId = PROGRAM_ID) => find("shielded", pool, programId),
|
|
14
|
+
tree: (pool, programId = PROGRAM_ID) => find("tree", pool, programId),
|
|
15
|
+
custodyA: (pool, programId = PROGRAM_ID) => find("custody-a", pool, programId),
|
|
16
|
+
custodyB: (pool, programId = PROGRAM_ID) => find("custody-b", pool, programId),
|
|
17
|
+
spent: (pool, nullifier, programId = PROGRAM_ID) => PublicKey.findProgramAddressSync([Buffer.from("spent"), pool.toBuffer(), Buffer.from(nullifier)], programId),
|
|
18
|
+
protocolConfig: (programId = PROGRAM_ID) => PublicKey.findProgramAddressSync([Buffer.from("protocol-config")], programId),
|
|
19
|
+
};
|
|
20
|
+
function find(seed, pool, programId) { return PublicKey.findProgramAddressSync([Buffer.from(seed), pool.toBuffer()], programId); }
|
|
21
|
+
function u16le(v) { const b = Buffer.alloc(2); b.writeUInt16LE(v); return b; }
|
package/dist/prover.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PrivateSwapProverInput, Prover, UnshieldProverInput } from "./types.js";
|
|
2
|
+
export declare const BN254_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
|
|
3
|
+
export declare const PRODUCTION_ARTIFACT_SHA256: {
|
|
4
|
+
readonly privateSwapPk: "26f9aaa5ff0924bc5c1d4a6fc618f70147d4f8b6d76acdfca3eabbd704cf51f5";
|
|
5
|
+
readonly unshieldPk: "156b0759e8819cb529c59249fbef6e075651655c8f8361f97616a8a1fc981563";
|
|
6
|
+
};
|
|
7
|
+
export declare function encodeUnshieldPublicInputs(input: Pick<UnshieldProverInput, "pool" | "asset" | "root" | "nullifier" | "amount" | "recipient">): Uint8Array;
|
|
8
|
+
export declare function encodePrivateSwapPublicInputs(input: Pick<PrivateSwapProverInput, "pool" | "assetIn" | "assetOut" | "root" | "rootSequence" | "generation" | "nullifier" | "reserveIn" | "reserveOut" | "feeBps" | "amountIn" | "amountOut" | "changeAmount" | "changeCommitment" | "outputCommitment" | "direction" | "swapNonce">): Uint8Array;
|
|
9
|
+
export interface ProductionProverConfig {
|
|
10
|
+
executablePath: string;
|
|
11
|
+
privateSwapPkPath: string;
|
|
12
|
+
unshieldPkPath: string;
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
}
|
|
15
|
+
export declare class ProductionProver implements Prover {
|
|
16
|
+
private readonly config;
|
|
17
|
+
constructor(config: ProductionProverConfig);
|
|
18
|
+
proveUnshield(input: UnshieldProverInput): Promise<{
|
|
19
|
+
proof: Uint8Array;
|
|
20
|
+
publicInputs: Uint8Array;
|
|
21
|
+
}>;
|
|
22
|
+
provePrivateSwap(input: PrivateSwapProverInput): Promise<{
|
|
23
|
+
proof: Uint8Array;
|
|
24
|
+
publicInputs: Uint8Array;
|
|
25
|
+
}>;
|
|
26
|
+
}
|
package/dist/prover.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { realpath, stat } from "node:fs/promises";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { platform, arch } from "node:process";
|
|
5
|
+
import { dirname } from "node:path";
|
|
6
|
+
const hex = (value) => Buffer.from(value).toString("hex");
|
|
7
|
+
const decimal = (value) => value.toString(10);
|
|
8
|
+
const MAX_U64 = 0xffffffffffffffffn;
|
|
9
|
+
export const BN254_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
|
|
10
|
+
const PROVER_VERSION = "production-prover 0.2.0 ipc-v1";
|
|
11
|
+
const PROVER_OUTPUT_MAGIC = Buffer.from("ZKCP");
|
|
12
|
+
const PRIVATE_PROVER_SHA256_BY_TARGET = {
|
|
13
|
+
// This digest is updated only when the authenticated release binary is built.
|
|
14
|
+
"darwin-arm64": "1772e91b6e6048e90eafee865e06097ebd15cf319a89263d4a0d6e8f2eca2eac",
|
|
15
|
+
};
|
|
16
|
+
export const PRODUCTION_ARTIFACT_SHA256 = {
|
|
17
|
+
privateSwapPk: "26f9aaa5ff0924bc5c1d4a6fc618f70147d4f8b6d76acdfca3eabbd704cf51f5",
|
|
18
|
+
unshieldPk: "156b0759e8819cb529c59249fbef6e075651655c8f8361f97616a8a1fc981563",
|
|
19
|
+
};
|
|
20
|
+
const PRODUCTION_ARTIFACT_SIZE = { privateSwapPk: 2_829_872, unshieldPk: 1_409_808 };
|
|
21
|
+
const requireBytes32 = (value, label) => {
|
|
22
|
+
if (value.length !== 32)
|
|
23
|
+
throw new Error(`${label} must be 32 bytes`);
|
|
24
|
+
return value;
|
|
25
|
+
};
|
|
26
|
+
const field = (value) => {
|
|
27
|
+
const number = typeof value === "bigint" ? value : BigInt(`0x${hex(requireBytes32(value, "field"))}`);
|
|
28
|
+
if (number < 0n || number >= BN254_MODULUS)
|
|
29
|
+
throw new Error("Noncanonical BN254 field value");
|
|
30
|
+
const output = Buffer.alloc(32);
|
|
31
|
+
let current = number;
|
|
32
|
+
for (let index = 31; index >= 0; index--) {
|
|
33
|
+
output[index] = Number(current & 255n);
|
|
34
|
+
current >>= 8n;
|
|
35
|
+
}
|
|
36
|
+
return output;
|
|
37
|
+
};
|
|
38
|
+
const u64Field = (value) => {
|
|
39
|
+
if (value < 0n || value > MAX_U64)
|
|
40
|
+
throw new Error("Invalid u64 public input");
|
|
41
|
+
return field(value);
|
|
42
|
+
};
|
|
43
|
+
const limb = (value) => {
|
|
44
|
+
if (value.length < 16)
|
|
45
|
+
throw new Error("Public-key limb must be at least 16 bytes");
|
|
46
|
+
return Buffer.concat([Buffer.alloc(16), Buffer.from(value.subarray(0, 16))]);
|
|
47
|
+
};
|
|
48
|
+
export function encodeUnshieldPublicInputs(input) {
|
|
49
|
+
return Buffer.concat([
|
|
50
|
+
u64Field(0x5a4b43504d4d0003n), limb(input.pool.toBytes()), limb(input.pool.toBytes().subarray(16)),
|
|
51
|
+
limb(input.asset.toBytes()), limb(input.asset.toBytes().subarray(16)), field(input.root), field(input.nullifier),
|
|
52
|
+
u64Field(input.amount), limb(input.recipient.toBytes()), limb(input.recipient.toBytes().subarray(16)),
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
export function encodePrivateSwapPublicInputs(input) {
|
|
56
|
+
if (!Number.isSafeInteger(input.feeBps) || input.feeBps < 0 || input.feeBps > 0xffff)
|
|
57
|
+
throw new Error("Invalid fee tier encoding");
|
|
58
|
+
if (!Number.isSafeInteger(input.direction) || input.direction < 0 || input.direction > 1)
|
|
59
|
+
throw new Error("Invalid swap direction");
|
|
60
|
+
return Buffer.concat([
|
|
61
|
+
u64Field(0x5a4b43504d4d0003n), limb(input.pool.toBytes()), limb(input.pool.toBytes().subarray(16)),
|
|
62
|
+
limb(input.assetIn.toBytes()), limb(input.assetIn.toBytes().subarray(16)), limb(input.assetOut.toBytes()), limb(input.assetOut.toBytes().subarray(16)),
|
|
63
|
+
field(input.root), u64Field(input.rootSequence), u64Field(input.generation), field(input.nullifier),
|
|
64
|
+
u64Field(input.reserveIn), u64Field(input.reserveOut), u64Field(BigInt(input.feeBps)), u64Field(input.amountIn), u64Field(input.amountOut),
|
|
65
|
+
u64Field(input.changeAmount), field(input.changeCommitment), field(input.outputCommitment), u64Field(BigInt(input.direction)), u64Field(1n), u64Field(input.swapNonce),
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
function targetKey() { return `${platform}-${arch}`; }
|
|
69
|
+
async function sha256File(path) {
|
|
70
|
+
return createHash("sha256").update(await import("node:fs/promises").then(fs => fs.readFile(path))).digest("hex");
|
|
71
|
+
}
|
|
72
|
+
async function verifyArtifact(path, circuit) {
|
|
73
|
+
const expected = circuit === "private" ? PRODUCTION_ARTIFACT_SHA256.privateSwapPk : PRODUCTION_ARTIFACT_SHA256.unshieldPk;
|
|
74
|
+
const expectedSize = circuit === "private" ? PRODUCTION_ARTIFACT_SIZE.privateSwapPk : PRODUCTION_ARTIFACT_SIZE.unshieldPk;
|
|
75
|
+
const info = await stat(path);
|
|
76
|
+
if (!info.isFile() || info.size !== expectedSize)
|
|
77
|
+
throw new Error(`Production ${circuit} proving-key size mismatch`);
|
|
78
|
+
if ((await sha256File(path)).toLowerCase() !== expected)
|
|
79
|
+
throw new Error(`Production ${circuit} proving-key hash mismatch`);
|
|
80
|
+
}
|
|
81
|
+
function boundedChild(executable, args, input, limits) {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
const child = spawn(executable, args, { env: { RUST_BACKTRACE: "0" }, shell: false, stdio: ["pipe", "pipe", "pipe"] });
|
|
84
|
+
const stdout = [];
|
|
85
|
+
const stderr = [];
|
|
86
|
+
let stdoutLength = 0;
|
|
87
|
+
let stderrLength = 0;
|
|
88
|
+
let settled = false;
|
|
89
|
+
const finish = (error) => { if (settled)
|
|
90
|
+
return; settled = true; clearTimeout(timer); if (error)
|
|
91
|
+
reject(error);
|
|
92
|
+
else
|
|
93
|
+
resolve({ stdout: Buffer.concat(stdout), stderr: Buffer.concat(stderr) }); };
|
|
94
|
+
const kill = (error) => { child.kill("SIGKILL"); finish(error); };
|
|
95
|
+
const timer = setTimeout(() => kill(new Error("Production prover timed out")), limits.timeoutMs);
|
|
96
|
+
child.on("error", error => finish(error));
|
|
97
|
+
child.stdout.on("data", chunk => { stdoutLength += chunk.length; if (stdoutLength > limits.stdout)
|
|
98
|
+
kill(new Error("Production prover stdout exceeded its limit"));
|
|
99
|
+
else
|
|
100
|
+
stdout.push(Buffer.from(chunk)); });
|
|
101
|
+
child.stderr.on("data", chunk => { stderrLength += chunk.length; if (stderrLength > limits.stderr)
|
|
102
|
+
kill(new Error("Production prover stderr exceeded its limit"));
|
|
103
|
+
else
|
|
104
|
+
stderr.push(Buffer.from(chunk)); });
|
|
105
|
+
child.on("close", code => { if (code === 0)
|
|
106
|
+
finish();
|
|
107
|
+
else
|
|
108
|
+
finish(new Error(`Production prover failed with exit code ${code ?? "unknown"}`)); });
|
|
109
|
+
if (input) {
|
|
110
|
+
child.stdin.end(input);
|
|
111
|
+
}
|
|
112
|
+
else
|
|
113
|
+
child.stdin.end();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
async function verifyExecutable(path) {
|
|
117
|
+
const resolved = await realpath(path);
|
|
118
|
+
const info = await stat(resolved);
|
|
119
|
+
const parent = await stat(dirname(resolved));
|
|
120
|
+
if (!info.isFile())
|
|
121
|
+
throw new Error("Production prover executable is not a regular file");
|
|
122
|
+
if (process.platform !== "win32" && ((info.mode | parent.mode) & 0o022) !== 0)
|
|
123
|
+
throw new Error("Production prover executable or directory is writable by group/other");
|
|
124
|
+
const expected = PRIVATE_PROVER_SHA256_BY_TARGET[targetKey()];
|
|
125
|
+
if (!expected || expected === "RELEASE_BUILD_REQUIRED")
|
|
126
|
+
throw new Error(`No authenticated production prover digest for ${targetKey()}`);
|
|
127
|
+
if ((await sha256File(resolved)).toLowerCase() !== expected)
|
|
128
|
+
throw new Error("Production prover executable hash mismatch");
|
|
129
|
+
const version = await boundedChild(resolved, ["--version"], undefined, { stdout: 256, stderr: 1024, timeoutMs: 2_000 });
|
|
130
|
+
if (version.stdout.toString("utf8") !== `${PROVER_VERSION}\n`)
|
|
131
|
+
throw new Error("Production prover version mismatch");
|
|
132
|
+
return resolved;
|
|
133
|
+
}
|
|
134
|
+
function pathTokens(witness) {
|
|
135
|
+
if (witness.index < 0n || witness.index >= 1n << 16n || witness.siblings.length !== 16)
|
|
136
|
+
throw new Error("Invalid Merkle witness");
|
|
137
|
+
return [decimal(witness.index), decimal(witness.generation), ...witness.siblings.map(value => hex(requireBytes32(value, "Merkle sibling")))];
|
|
138
|
+
}
|
|
139
|
+
function requestFrame(circuit, tokens) {
|
|
140
|
+
const frame = `zkcpmm-prover-ipc-v1\n${circuit}\n${tokens.length}\n${tokens.join("\n")}\n`;
|
|
141
|
+
const encoded = Buffer.from(frame, "utf8");
|
|
142
|
+
if (encoded.length > 16 * 1024)
|
|
143
|
+
throw new Error("Production prover request exceeds its limit");
|
|
144
|
+
return encoded;
|
|
145
|
+
}
|
|
146
|
+
function parseOutput(circuit, output) {
|
|
147
|
+
const inputLength = circuit === "private" ? 704 : 320;
|
|
148
|
+
if (output.length !== 16 + 256 + inputLength || !output.subarray(0, 4).equals(PROVER_OUTPUT_MAGIC) || output[4] !== 1 || output[5] !== (circuit === "private" ? 0 : 1) || output.readUInt16BE(6) !== 0 || output.readUInt32BE(8) !== 256 || output.readUInt32BE(12) !== inputLength)
|
|
149
|
+
throw new Error("Invalid production prover response frame");
|
|
150
|
+
return { proof: output.subarray(16, 272), publicInputs: output.subarray(272) };
|
|
151
|
+
}
|
|
152
|
+
async function prove(config, circuit, tokens) {
|
|
153
|
+
const executable = await verifyExecutable(config.executablePath);
|
|
154
|
+
await verifyArtifact(circuit === "private" ? config.privateSwapPkPath : config.unshieldPkPath, circuit);
|
|
155
|
+
const request = requestFrame(circuit, tokens);
|
|
156
|
+
try {
|
|
157
|
+
const result = await boundedChild(executable, ["--stdin-v1", circuit, "--pk", circuit === "private" ? config.privateSwapPkPath : config.unshieldPkPath], request, { stdout: circuit === "private" ? 977 : 593, stderr: 16 * 1024, timeoutMs: Math.min(Math.max(config.timeoutMs ?? 120_000, 1_000), 300_000) });
|
|
158
|
+
return parseOutput(circuit, result.stdout);
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
request.fill(0);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
export class ProductionProver {
|
|
165
|
+
config;
|
|
166
|
+
constructor(config) {
|
|
167
|
+
this.config = config;
|
|
168
|
+
}
|
|
169
|
+
async proveUnshield(input) {
|
|
170
|
+
const tokens = [hex(input.pool.toBytes()), hex(input.asset.toBytes()), hex(requireBytes32(input.root, "root")), hex(requireBytes32(input.nullifier, "nullifier")), decimal(input.amount), hex(input.recipient.toBytes()), hex(requireBytes32(input.spendSecret, "spend secret")), hex(requireBytes32(input.randomness, "randomness")), ...pathTokens(input.witness)];
|
|
171
|
+
return prove(this.config, "unshield", tokens);
|
|
172
|
+
}
|
|
173
|
+
async provePrivateSwap(input) {
|
|
174
|
+
const tokens = [hex(input.pool.toBytes()), hex(input.assetIn.toBytes()), hex(input.assetOut.toBytes()), hex(requireBytes32(input.root, "root")), decimal(input.rootSequence), decimal(input.generation), hex(requireBytes32(input.nullifier, "nullifier")), decimal(input.reserveIn), decimal(input.reserveOut), String(input.feeBps), decimal(input.amountIn), decimal(input.amountOut), decimal(input.changeAmount), hex(requireBytes32(input.changeCommitment, "change commitment")), hex(requireBytes32(input.outputCommitment, "output commitment")), String(input.direction), decimal(input.swapNonce), hex(requireBytes32(input.inputSpendSecret, "input spend secret")), hex(requireBytes32(input.inputRandomness, "input randomness")), ...pathTokens(input.witness), hex(requireBytes32(input.changeSpendSecret, "change spend secret")), hex(requireBytes32(input.changeRandomness, "change randomness")), hex(requireBytes32(input.outputSpendSecret, "output spend secret")), hex(requireBytes32(input.outputRandomness, "output randomness"))];
|
|
175
|
+
return prove(this.config, "private", tokens);
|
|
176
|
+
}
|
|
177
|
+
}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { JournaledNoteStore, MerkleCheckpointStore, Note, OperationRecord } from "./types.js";
|
|
2
|
+
export declare class EncryptedFileNoteStore implements JournaledNoteStore, MerkleCheckpointStore {
|
|
3
|
+
private readonly path;
|
|
4
|
+
private readonly encryptionKey;
|
|
5
|
+
private readonly owner;
|
|
6
|
+
private readonly lockPath;
|
|
7
|
+
private readonly notes;
|
|
8
|
+
private readonly operations;
|
|
9
|
+
private sequence;
|
|
10
|
+
private previousDigest;
|
|
11
|
+
constructor(path: string, encryptionKey: Uint8Array, owner: Uint8Array);
|
|
12
|
+
static fromSeed(path: string, seed: Uint8Array, owner: Uint8Array): EncryptedFileNoteStore;
|
|
13
|
+
private validateNote;
|
|
14
|
+
private apply;
|
|
15
|
+
private readJournal;
|
|
16
|
+
private acquireLock;
|
|
17
|
+
private append;
|
|
18
|
+
private mutate;
|
|
19
|
+
getNotes(ownerCommitment?: Uint8Array<ArrayBufferLike>): Promise<Note[]>;
|
|
20
|
+
saveNote(input: Note): Promise<void>;
|
|
21
|
+
reserveNote(commitment: Uint8Array, operationId: Uint8Array, ownerCommitment: Uint8Array): Promise<Note>;
|
|
22
|
+
reserveNoteAndBegin(commitment: Uint8Array, operationId: Uint8Array, ownerCommitment: Uint8Array, operation: OperationRecord): Promise<Note>;
|
|
23
|
+
markSubmitted(commitment: Uint8Array, operationId: Uint8Array, signature: string): Promise<void>;
|
|
24
|
+
markSpent(commitment: Uint8Array, operationId?: Uint8Array): Promise<void>;
|
|
25
|
+
releaseReservation(commitment: Uint8Array, operationId?: Uint8Array): Promise<void>;
|
|
26
|
+
beginOperation(operation: OperationRecord): Promise<void>;
|
|
27
|
+
updateOperation(id: Uint8Array, patch: Partial<Pick<OperationRecord, "metadata" | "outputNotes">>): Promise<void>;
|
|
28
|
+
markPrepared(id: Uint8Array, signedTransaction: Uint8Array, lastValidBlockHeight: bigint): Promise<void>;
|
|
29
|
+
markOperationSubmitted(id: Uint8Array, signature: string): Promise<void>;
|
|
30
|
+
markOperationFinalized(id: Uint8Array): Promise<void>;
|
|
31
|
+
markOperationFailed(id: Uint8Array, reason: string): Promise<void>;
|
|
32
|
+
markOperationUnknown(id: Uint8Array, reason: string): Promise<void>;
|
|
33
|
+
getPendingOperations(): Promise<OperationRecord[]>;
|
|
34
|
+
exportBackup(): Promise<Uint8Array>;
|
|
35
|
+
importBackup(backup: Uint8Array): Promise<void>;
|
|
36
|
+
private checkpointPath;
|
|
37
|
+
loadMerkleCheckpoint(identity: string): Promise<Uint8Array | undefined>;
|
|
38
|
+
saveMerkleCheckpoint(identity: string, checkpoint: Uint8Array): Promise<void>;
|
|
39
|
+
}
|