@veilo/sdk-core 0.1.17 → 0.3.3
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/README.md +1257 -272
- package/dist/cjs/client.d.ts +407 -0
- package/dist/cjs/client.js +914 -0
- package/dist/cjs/config.d.ts +82 -0
- package/dist/cjs/config.js +57 -0
- package/dist/cjs/events.d.ts +77 -0
- package/dist/cjs/events.js +167 -0
- package/dist/cjs/idl/privacy_pool.json +10313 -0
- package/dist/cjs/index.d.ts +13 -0
- package/dist/cjs/index.js +57 -0
- package/dist/cjs/merkle.d.ts +64 -0
- package/dist/cjs/merkle.js +133 -0
- package/dist/cjs/poseidon.d.ts +29 -0
- package/dist/cjs/poseidon.js +100 -0
- package/dist/cjs/program.d.ts +26 -0
- package/dist/cjs/program.js +38 -0
- package/dist/cjs/proof.d.ts +183 -0
- package/dist/cjs/proof.js +292 -0
- package/dist/cjs/prover.d.ts +54 -0
- package/dist/cjs/prover.js +112 -0
- package/dist/cjs/relayer.d.ts +295 -0
- package/dist/cjs/relayer.js +246 -0
- package/dist/cjs/retry.d.ts +32 -0
- package/dist/cjs/retry.js +75 -0
- package/dist/cjs/utxo.d.ts +215 -0
- package/dist/cjs/utxo.js +394 -0
- package/dist/esm/client.js +887 -0
- package/dist/esm/config.js +51 -0
- package/dist/esm/events.js +129 -0
- package/dist/esm/idl/privacy_pool.json +10313 -0
- package/dist/esm/index.js +22 -0
- package/dist/esm/merkle.js +129 -0
- package/dist/esm/poseidon.js +87 -0
- package/dist/esm/program.js +31 -0
- package/dist/esm/proof.js +281 -0
- package/dist/esm/prover.js +75 -0
- package/dist/esm/relayer.js +238 -0
- package/dist/esm/retry.js +71 -0
- package/dist/esm/utxo.js +372 -0
- package/package.json +47 -11
- package/src/client.ts +0 -352
- package/src/config.ts +0 -13
- package/src/index.ts +0 -6
- package/src/merkle.ts +0 -178
- package/src/note.ts +0 -193
- package/src/poseidon.ts +0 -62
- package/src/proof.ts +0 -170
- package/test/script.js +0 -0
- package/test-tsconfig.json +0 -19
- package/tests/note.test.ts +0 -50
- package/tests/sdk.integration.test.ts +0 -210
- package/tsconfig.json +0 -18
package/src/note.ts
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
// sdk-core/src/note.ts
|
|
2
|
-
import { PublicKey } from "@solana/web3.js";
|
|
3
|
-
import {
|
|
4
|
-
createCipheriv,
|
|
5
|
-
createDecipheriv,
|
|
6
|
-
createHash,
|
|
7
|
-
randomBytes,
|
|
8
|
-
} from "crypto";
|
|
9
|
-
import { frToBigInt, getPoseidon } from "./poseidon";
|
|
10
|
-
|
|
11
|
-
export type Note = {
|
|
12
|
-
value: bigint; // u64 on-chain, bigint off-chain
|
|
13
|
-
owner: PublicKey; // owner public key (later: spend key)
|
|
14
|
-
rho: Uint8Array; // 32 bytes random
|
|
15
|
-
r: Uint8Array; // 32 bytes random blinding
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export type SerializedNote = {
|
|
19
|
-
value: bigint;
|
|
20
|
-
owner: PublicKey;
|
|
21
|
-
rho: Uint8Array;
|
|
22
|
-
r: Uint8Array;
|
|
23
|
-
commitment: Uint8Array; // 32 bytes, BN254 Fr element
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export type EncryptedNotePlaintext = {
|
|
27
|
-
secret: Uint8Array; // 32 bytes
|
|
28
|
-
nullifier: Uint8Array; // 32 bytes
|
|
29
|
-
denomIndex: number; // 1 byte
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export function encodeEncryptedNote(note: EncryptedNotePlaintext): Uint8Array {
|
|
33
|
-
const buf = new Uint8Array(32 + 32 + 1);
|
|
34
|
-
buf.set(note.secret, 0);
|
|
35
|
-
buf.set(note.nullifier, 32);
|
|
36
|
-
buf[64] = note.denomIndex;
|
|
37
|
-
return buf;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Decode back
|
|
41
|
-
export function decodeEncryptedNote(buf: Uint8Array): EncryptedNotePlaintext {
|
|
42
|
-
return {
|
|
43
|
-
secret: buf.slice(0, 32),
|
|
44
|
-
nullifier: buf.slice(32, 64),
|
|
45
|
-
denomIndex: buf[64],
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function encryptNote(
|
|
50
|
-
plaintext: EncryptedNotePlaintext,
|
|
51
|
-
sharedKey: Uint8Array // 32 bytes symmetric key (derived from recipient pubkey)
|
|
52
|
-
): Uint8Array {
|
|
53
|
-
const iv = randomBytes(12); // ChaCha20/Poly1305 nonce
|
|
54
|
-
const cipher = createCipheriv("chacha20-poly1305", sharedKey, iv, {
|
|
55
|
-
authTagLength: 16,
|
|
56
|
-
});
|
|
57
|
-
const encoded = encodeEncryptedNote(plaintext);
|
|
58
|
-
const encrypted = Buffer.concat([cipher.update(encoded), cipher.final()]);
|
|
59
|
-
const authTag = cipher.getAuthTag();
|
|
60
|
-
|
|
61
|
-
// Return iv + ciphertext + tag
|
|
62
|
-
return Buffer.concat([iv, encrypted, authTag]);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function decryptNote(
|
|
66
|
-
ciphertext: Uint8Array,
|
|
67
|
-
sharedKey: Uint8Array
|
|
68
|
-
): EncryptedNotePlaintext {
|
|
69
|
-
const iv = ciphertext.slice(0, 12);
|
|
70
|
-
const authTag = ciphertext.slice(ciphertext.length - 16);
|
|
71
|
-
const enc = ciphertext.slice(12, ciphertext.length - 16);
|
|
72
|
-
|
|
73
|
-
const decipher = createDecipheriv("chacha20-poly1305", sharedKey, iv, {
|
|
74
|
-
authTagLength: 16,
|
|
75
|
-
});
|
|
76
|
-
decipher.setAuthTag(authTag);
|
|
77
|
-
const decrypted = Buffer.concat([decipher.update(enc), decipher.final()]);
|
|
78
|
-
|
|
79
|
-
return decodeEncryptedNote(decrypted);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function createOwnerHint(pubkey: PublicKey): Uint8Array {
|
|
83
|
-
// Simple: hash of recipient encryption pubkey
|
|
84
|
-
return createHash("sha256").update(pubkey.toBytes()).digest();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// -----------------------------------------------------------------------------
|
|
88
|
-
// BN254 scalar field helpers (shared with merkle/proof)
|
|
89
|
-
// -----------------------------------------------------------------------------
|
|
90
|
-
|
|
91
|
-
// Standard BN254 scalar field modulus (same Fr as circom / Groth16)
|
|
92
|
-
export const BN254_FR_MODULUS = BigInt(
|
|
93
|
-
"21888242871839275222246405745257275088548364400416034343698204186575808495617"
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
export function bytesToBigIntBE(bytes: Uint8Array): bigint {
|
|
97
|
-
let x = 0n;
|
|
98
|
-
for (const b of bytes) {
|
|
99
|
-
x = (x << 8n) | BigInt(b);
|
|
100
|
-
}
|
|
101
|
-
return x;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function bigIntToBytesBE(x: bigint): Uint8Array {
|
|
105
|
-
const out = new Uint8Array(32);
|
|
106
|
-
let v = x;
|
|
107
|
-
for (let i = 31; i >= 0; i--) {
|
|
108
|
-
out[i] = Number(v & 0xffn);
|
|
109
|
-
v >>= 8n;
|
|
110
|
-
}
|
|
111
|
-
return out;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// -----------------------------------------------------------------------------
|
|
115
|
-
// Note construction
|
|
116
|
-
// -----------------------------------------------------------------------------
|
|
117
|
-
|
|
118
|
-
export function createRandomNote(params: {
|
|
119
|
-
value: bigint;
|
|
120
|
-
owner: PublicKey;
|
|
121
|
-
}): Note {
|
|
122
|
-
return {
|
|
123
|
-
value: params.value,
|
|
124
|
-
owner: params.owner,
|
|
125
|
-
rho: new Uint8Array(randomBytes(32)),
|
|
126
|
-
r: new Uint8Array(randomBytes(32)),
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Optional helper: deterministic encoding of a note into bytes:
|
|
132
|
-
* value (8 bytes, LE) || owner (32 bytes) || rho (32) || r (32)
|
|
133
|
-
* Kept for compatibility / debugging.
|
|
134
|
-
*/
|
|
135
|
-
export function encodeNoteToBytes(note: Note): Uint8Array {
|
|
136
|
-
const valueBuf = Buffer.alloc(8);
|
|
137
|
-
valueBuf.writeBigUInt64LE(note.value);
|
|
138
|
-
|
|
139
|
-
const ownerBuf = note.owner.toBytes();
|
|
140
|
-
if (ownerBuf.length !== 32) {
|
|
141
|
-
throw new Error("Owner pubkey must be 32 bytes");
|
|
142
|
-
}
|
|
143
|
-
if (note.rho.length !== 32 || note.r.length !== 32) {
|
|
144
|
-
throw new Error("rho and r must be 32 bytes each");
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return Buffer.concat([
|
|
148
|
-
valueBuf,
|
|
149
|
-
Buffer.from(ownerBuf),
|
|
150
|
-
Buffer.from(note.rho),
|
|
151
|
-
Buffer.from(note.r),
|
|
152
|
-
]);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Circom-compatible commitment:
|
|
157
|
-
* commitment = Poseidon(value, owner, rho, r)
|
|
158
|
-
*
|
|
159
|
-
* All arguments are reduced into BN254 Fr before hashing.
|
|
160
|
-
* This must match your Circom `NoteCommitment` template.
|
|
161
|
-
*/
|
|
162
|
-
export function commitNote(note: Note): Uint8Array {
|
|
163
|
-
const poseidon = getPoseidon();
|
|
164
|
-
|
|
165
|
-
const valueField = note.value % BN254_FR_MODULUS;
|
|
166
|
-
|
|
167
|
-
const ownerField = bytesToBigIntBE(note.owner.toBytes()) % BN254_FR_MODULUS;
|
|
168
|
-
|
|
169
|
-
const rhoField = bytesToBigIntBE(note.rho) % BN254_FR_MODULUS;
|
|
170
|
-
|
|
171
|
-
const rField = bytesToBigIntBE(note.r) % BN254_FR_MODULUS;
|
|
172
|
-
|
|
173
|
-
const fe = poseidon([valueField, ownerField, rhoField, rField]);
|
|
174
|
-
const outBig = frToBigInt(fe) % BN254_FR_MODULUS;
|
|
175
|
-
|
|
176
|
-
return bigIntToBytesBE(outBig);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Convenience: build a random note and its Poseidon commitment.
|
|
181
|
-
*/
|
|
182
|
-
export function createNoteWithCommitment(params: {
|
|
183
|
-
value: bigint;
|
|
184
|
-
owner: PublicKey;
|
|
185
|
-
}): SerializedNote {
|
|
186
|
-
const note = createRandomNote(params);
|
|
187
|
-
const commitment = commitNote(note);
|
|
188
|
-
|
|
189
|
-
return {
|
|
190
|
-
...note,
|
|
191
|
-
commitment,
|
|
192
|
-
};
|
|
193
|
-
}
|
package/src/poseidon.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
// sdk-core/src/poseidon.ts
|
|
2
|
-
import {buildPoseidonReference} from "circomlibjs";
|
|
3
|
-
|
|
4
|
-
export type PoseidonFn = ((inputs: bigint[]) => any) & {
|
|
5
|
-
F: { toObject: (e: any) => bigint };
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
let poseidonInstance: PoseidonFn | null = null;
|
|
9
|
-
|
|
10
|
-
export async function initPoseidon() {
|
|
11
|
-
poseidonInstance = (await buildPoseidonReference()) as PoseidonFn;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function getPoseidon(): PoseidonFn {
|
|
15
|
-
if (!poseidonInstance) {
|
|
16
|
-
throw new Error(
|
|
17
|
-
"Poseidon not initialized. Call initPoseidon() once before using note/merkle/proof helpers."
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
return poseidonInstance;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Helpers for BN254 field
|
|
24
|
-
export const BN254_FR_MODULUS = BigInt(
|
|
25
|
-
"21888242871839275222246405745257275088548364400416034343698204186575808495617"
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
export function bytesToBigIntBE(bytes: Uint8Array): bigint {
|
|
29
|
-
let x = 0n;
|
|
30
|
-
for (const b of bytes) {
|
|
31
|
-
x = (x << 8n) | BigInt(b);
|
|
32
|
-
}
|
|
33
|
-
return x;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function bigIntToBytesBE(x: bigint): Uint8Array {
|
|
37
|
-
const out = new Uint8Array(32);
|
|
38
|
-
let v = x;
|
|
39
|
-
for (let i = 31; i >= 0; i--) {
|
|
40
|
-
out[i] = Number(v & 0xffn);
|
|
41
|
-
v >>= 8n;
|
|
42
|
-
}
|
|
43
|
-
return out;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Convert ffjavascript field element → bigint safely */
|
|
47
|
-
export function frToBigInt(fe: any): bigint {
|
|
48
|
-
const poseidon = getPoseidon();
|
|
49
|
-
const F = (poseidon as any).F;
|
|
50
|
-
|
|
51
|
-
if (typeof fe === "bigint") {
|
|
52
|
-
return fe;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (F && typeof F.toObject === "function") {
|
|
56
|
-
// This is how ffjavascript exposes the underlying BigInt
|
|
57
|
-
return F.toObject(fe) as bigint;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Fallback: treat as decimal string
|
|
61
|
-
return BigInt(fe.toString());
|
|
62
|
-
}
|
package/src/proof.ts
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
// sdk-core/src/proof.ts
|
|
2
|
-
import { PublicKey } from "@solana/web3.js";
|
|
3
|
-
import {
|
|
4
|
-
SerializedNote,
|
|
5
|
-
BN254_FR_MODULUS,
|
|
6
|
-
bytesToBigIntBE,
|
|
7
|
-
bigIntToBytesBE,
|
|
8
|
-
} from "./note";
|
|
9
|
-
import { MerklePath } from "./merkle";
|
|
10
|
-
import {frToBigInt, getPoseidon} from "./poseidon";
|
|
11
|
-
|
|
12
|
-
// -----------------------------------------------------------------------------
|
|
13
|
-
// Circuit inputs
|
|
14
|
-
// -----------------------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
export type WithdrawCircuitInputs = {
|
|
17
|
-
root: Uint8Array;
|
|
18
|
-
note: SerializedNote;
|
|
19
|
-
merklePath: MerklePath;
|
|
20
|
-
nullifier: Uint8Array;
|
|
21
|
-
denomIndex: number;
|
|
22
|
-
recipient: PublicKey;
|
|
23
|
-
relayer: PublicKey;
|
|
24
|
-
feeBps: number;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// -----------------------------------------------------------------------------
|
|
28
|
-
// Nullifier helper – MUST match Circom:
|
|
29
|
-
// nullifier = Poseidon(owner, rho)
|
|
30
|
-
// -----------------------------------------------------------------------------
|
|
31
|
-
|
|
32
|
-
export function deriveNullifier(note: SerializedNote): Uint8Array {
|
|
33
|
-
const poseidon = getPoseidon();
|
|
34
|
-
|
|
35
|
-
const ownerField =
|
|
36
|
-
bytesToBigIntBE(note.owner.toBytes()) % BN254_FR_MODULUS;
|
|
37
|
-
const rhoField =
|
|
38
|
-
bytesToBigIntBE(note.rho) % BN254_FR_MODULUS;
|
|
39
|
-
|
|
40
|
-
const fe = poseidon([ownerField, rhoField]);
|
|
41
|
-
const outBig = frToBigInt(fe) % BN254_FR_MODULUS;
|
|
42
|
-
|
|
43
|
-
return bigIntToBytesBE(outBig);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// -----------------------------------------------------------------------------
|
|
47
|
-
// Groth16 proof types & packing (shared with relayer & dapps)
|
|
48
|
-
// -----------------------------------------------------------------------------
|
|
49
|
-
|
|
50
|
-
// Shape of a Groth16 proof produced by snarkjs.groth16
|
|
51
|
-
export type RawProof = {
|
|
52
|
-
pi_a: [string, string, string];
|
|
53
|
-
pi_b: [[string, string], [string, string], [string, string]];
|
|
54
|
-
pi_c: [string, string, string];
|
|
55
|
-
protocol: string;
|
|
56
|
-
curve: string;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* This matches the Rust struct:
|
|
61
|
-
*
|
|
62
|
-
* pub struct WithdrawProof {
|
|
63
|
-
* pub proof_a: [u8; 64],
|
|
64
|
-
* pub proof_b: [u8; 128],
|
|
65
|
-
* pub proof_c: [u8; 64],
|
|
66
|
-
* }
|
|
67
|
-
*
|
|
68
|
-
* In Anchor TS, this is just an object with three number[] fields.
|
|
69
|
-
*/
|
|
70
|
-
export type WithdrawProofStruct = {
|
|
71
|
-
proofA: number[]; // 64 bytes
|
|
72
|
-
proofB: number[]; // 128 bytes
|
|
73
|
-
proofC: number[]; // 64 bytes
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
// Builder now returns a RawProof (snarkjs output).
|
|
77
|
-
export type ProofBuilder = (inputs: WithdrawCircuitInputs) => Promise<RawProof>;
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Convert bigint -> 32-byte little-endian representation.
|
|
81
|
-
* Used to pack proof coords into the 256-byte layout expected on-chain.
|
|
82
|
-
*/
|
|
83
|
-
function bigintTo32BytesLE(x: bigint): Uint8Array {
|
|
84
|
-
const out = new Uint8Array(32);
|
|
85
|
-
let v = x;
|
|
86
|
-
for (let i = 0; i < 32; i++) {
|
|
87
|
-
out[i] = Number(v & 0xffn);
|
|
88
|
-
v >>= 8n;
|
|
89
|
-
}
|
|
90
|
-
return out;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Turn a snarkjs Groth16 proof into 256 bytes:
|
|
95
|
-
* A(G1) | B(G2) | C(G1)
|
|
96
|
-
* where:
|
|
97
|
-
* A = (ax, ay)
|
|
98
|
-
* B = (bx0, bx1; by0, by1)
|
|
99
|
-
* C = (cx, cy)
|
|
100
|
-
*
|
|
101
|
-
* Each field element is encoded as 32-byte LE.
|
|
102
|
-
* We ignore the extra projective / z components snarkjs keeps.
|
|
103
|
-
*/
|
|
104
|
-
export function packProofToBytes(proof: RawProof): Uint8Array {
|
|
105
|
-
const chunks: Uint8Array[] = [];
|
|
106
|
-
|
|
107
|
-
// A: [ax, ay, az]
|
|
108
|
-
const ax = BigInt(proof.pi_a[0]);
|
|
109
|
-
const ay = BigInt(proof.pi_a[1]);
|
|
110
|
-
chunks.push(bigintTo32BytesLE(ax), bigintTo32BytesLE(ay));
|
|
111
|
-
|
|
112
|
-
// B: [[bx0, bx1], [by0, by1], [bz0, bz1]]
|
|
113
|
-
// we only use x, y
|
|
114
|
-
const bx = proof.pi_b[0];
|
|
115
|
-
const by = proof.pi_b[1];
|
|
116
|
-
|
|
117
|
-
const bx0 = BigInt(bx[0]);
|
|
118
|
-
const bx1 = BigInt(bx[1]);
|
|
119
|
-
const by0 = BigInt(by[0]);
|
|
120
|
-
const by1 = BigInt(by[1]);
|
|
121
|
-
|
|
122
|
-
chunks.push(
|
|
123
|
-
bigintTo32BytesLE(bx0),
|
|
124
|
-
bigintTo32BytesLE(bx1),
|
|
125
|
-
bigintTo32BytesLE(by0),
|
|
126
|
-
bigintTo32BytesLE(by1),
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
// C: [cx, cy, cz]
|
|
130
|
-
const cx = BigInt(proof.pi_c[0]);
|
|
131
|
-
const cy = BigInt(proof.pi_c[1]);
|
|
132
|
-
chunks.push(bigintTo32BytesLE(cx), bigintTo32BytesLE(cy));
|
|
133
|
-
|
|
134
|
-
// Flatten into single Uint8Array (256 bytes)
|
|
135
|
-
const out = new Uint8Array(32 * (2 + 4 + 2)); // 256
|
|
136
|
-
let offset = 0;
|
|
137
|
-
for (const c of chunks) {
|
|
138
|
-
out.set(c, offset);
|
|
139
|
-
offset += c.length;
|
|
140
|
-
}
|
|
141
|
-
return out;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Take a snarkjs proof and produce the JSON shape the Anchor program expects:
|
|
146
|
-
*
|
|
147
|
-
* pub struct WithdrawProof {
|
|
148
|
-
* pub proof_a: [u8; 64],
|
|
149
|
-
* pub proof_b: [u8; 128],
|
|
150
|
-
* pub proof_c: [u8; 64],
|
|
151
|
-
* }
|
|
152
|
-
*/
|
|
153
|
-
export function encodeSnarkjsProofToWithdrawProof(
|
|
154
|
-
proof: RawProof,
|
|
155
|
-
): WithdrawProofStruct {
|
|
156
|
-
const full = packProofToBytes(proof);
|
|
157
|
-
if (full.length !== 256) {
|
|
158
|
-
throw new Error(`Expected 256 proof bytes, got ${full.length}`);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const proofA = full.slice(0, 64);
|
|
162
|
-
const proofB = full.slice(64, 192);
|
|
163
|
-
const proofC = full.slice(192, 256);
|
|
164
|
-
|
|
165
|
-
return {
|
|
166
|
-
proofA: Array.from(proofA),
|
|
167
|
-
proofB: Array.from(proofB),
|
|
168
|
-
proofC: Array.from(proofC),
|
|
169
|
-
};
|
|
170
|
-
}
|
package/test/script.js
DELETED
|
File without changes
|
package/test-tsconfig.json
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "commonjs",
|
|
5
|
-
"moduleResolution": "node",
|
|
6
|
-
"declaration": true,
|
|
7
|
-
"outDir": "dist",
|
|
8
|
-
"rootDir": ".",
|
|
9
|
-
"strict": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"forceConsistentCasingInFileNames": true,
|
|
13
|
-
"resolveJsonModule": true // 👈 add this
|
|
14
|
-
},
|
|
15
|
-
"include": [
|
|
16
|
-
"src/**/*.ts",
|
|
17
|
-
"tests/**/*.test.ts"
|
|
18
|
-
]
|
|
19
|
-
}
|
package/tests/note.test.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
// tests/note.test.ts
|
|
2
|
-
import "mocha";
|
|
3
|
-
import { strict as assert } from "assert";
|
|
4
|
-
import { Keypair } from "@solana/web3.js";
|
|
5
|
-
import {
|
|
6
|
-
createRandomNote,
|
|
7
|
-
encodeNoteToBytes,
|
|
8
|
-
commitNote,
|
|
9
|
-
createNoteWithCommitment,
|
|
10
|
-
} from "../src";
|
|
11
|
-
|
|
12
|
-
describe("note helpers", () => {
|
|
13
|
-
it("creates a random note with correct sizes", () => {
|
|
14
|
-
const owner = Keypair.generate().publicKey;
|
|
15
|
-
const value = 123n;
|
|
16
|
-
|
|
17
|
-
const note = createRandomNote({ value, owner });
|
|
18
|
-
|
|
19
|
-
assert.equal(note.value, value);
|
|
20
|
-
assert.equal(note.owner.toBase58(), owner.toBase58());
|
|
21
|
-
assert.equal(note.rho.length, 32);
|
|
22
|
-
assert.equal(note.r.length, 32);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("encodes and commits deterministically", () => {
|
|
26
|
-
const owner = Keypair.generate().publicKey;
|
|
27
|
-
const value = 42n;
|
|
28
|
-
|
|
29
|
-
const note1 = createRandomNote({ value, owner });
|
|
30
|
-
const note2 = { ...note1 };
|
|
31
|
-
|
|
32
|
-
const enc1 = encodeNoteToBytes(note1);
|
|
33
|
-
const enc2 = encodeNoteToBytes(note2);
|
|
34
|
-
|
|
35
|
-
assert.equal(enc1.length, enc2.length);
|
|
36
|
-
assert.equal(enc1.length, 8 + 32 + 32 + 32);
|
|
37
|
-
|
|
38
|
-
const c1 = commitNote(note1);
|
|
39
|
-
const c2 = commitNote(note2);
|
|
40
|
-
|
|
41
|
-
assert.equal(c1.length, 32);
|
|
42
|
-
assert.equal(Buffer.compare(Buffer.from(c1), Buffer.from(c2)), 0);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("createNoteWithCommitment returns 32-byte commitment", () => {
|
|
46
|
-
const owner = Keypair.generate().publicKey;
|
|
47
|
-
const note = createNoteWithCommitment({ value: 10n, owner });
|
|
48
|
-
assert.equal(note.commitment.length, 32);
|
|
49
|
-
});
|
|
50
|
-
});
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import "mocha";
|
|
2
|
-
import { strict as assert } from "assert";
|
|
3
|
-
import * as anchor from "@coral-xyz/anchor";
|
|
4
|
-
import type { Idl, Program } from "@coral-xyz/anchor";
|
|
5
|
-
import {
|
|
6
|
-
Keypair,
|
|
7
|
-
LAMPORTS_PER_SOL,
|
|
8
|
-
PublicKey,
|
|
9
|
-
} from "@solana/web3.js";
|
|
10
|
-
import fs from "fs";
|
|
11
|
-
import os from "os";
|
|
12
|
-
import path from "path";
|
|
13
|
-
|
|
14
|
-
import {
|
|
15
|
-
initializePool,
|
|
16
|
-
addRelayer,
|
|
17
|
-
getPoolPdas,
|
|
18
|
-
createNoteAndDeposit,
|
|
19
|
-
withdrawViaRelayer,
|
|
20
|
-
sol,
|
|
21
|
-
} from "../src";
|
|
22
|
-
|
|
23
|
-
// ---- IDL + Provider helpers ----
|
|
24
|
-
|
|
25
|
-
function loadIdl(): Idl {
|
|
26
|
-
// NOTE: this path is relative to dist/tests at runtime
|
|
27
|
-
const idlPath = path.join(
|
|
28
|
-
__dirname,
|
|
29
|
-
"../../../privacy-pool/target/idl/privacy_pool.json"
|
|
30
|
-
);
|
|
31
|
-
const raw = fs.readFileSync(idlPath, "utf8");
|
|
32
|
-
return JSON.parse(raw);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function makeProvider(): anchor.AnchorProvider {
|
|
36
|
-
const url = process.env.ANCHOR_PROVIDER_URL ?? "http://127.0.0.1:8899";
|
|
37
|
-
const connection = new anchor.web3.Connection(url, "confirmed");
|
|
38
|
-
|
|
39
|
-
const keypairPath =
|
|
40
|
-
process.env.ANCHOR_WALLET ??
|
|
41
|
-
path.join(os.homedir(), ".config", "solana", "id.json");
|
|
42
|
-
|
|
43
|
-
const secret = JSON.parse(fs.readFileSync(keypairPath, "utf8"));
|
|
44
|
-
const kp = Keypair.fromSecretKey(Uint8Array.from(secret));
|
|
45
|
-
const wallet = new anchor.Wallet(kp);
|
|
46
|
-
|
|
47
|
-
return new anchor.AnchorProvider(connection, wallet, {
|
|
48
|
-
commitment: "confirmed",
|
|
49
|
-
preflightCommitment: "confirmed",
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
describe("privacy-pool SDK integration (Merkle v3, rolling root)", () => {
|
|
54
|
-
const provider = makeProvider();
|
|
55
|
-
anchor.setProvider(provider);
|
|
56
|
-
|
|
57
|
-
const idl = loadIdl();
|
|
58
|
-
const program = new anchor.Program(idl as Idl, provider) as Program<any>;
|
|
59
|
-
const wallet = provider.wallet as anchor.Wallet;
|
|
60
|
-
|
|
61
|
-
const connection = provider.connection;
|
|
62
|
-
|
|
63
|
-
// This MUST match what initializePool uses below.
|
|
64
|
-
const DENOM_INDEX = 0;
|
|
65
|
-
const DENOMS_LAMPORTS = [sol(1), sol(5)]; // 1 SOL, 5 SOL
|
|
66
|
-
const FEE_BPS = 50; // 0.5%
|
|
67
|
-
|
|
68
|
-
async function airdropAndConfirm(pubkey: PublicKey, lamports: number) {
|
|
69
|
-
const sig = await connection.requestAirdrop(pubkey, lamports);
|
|
70
|
-
await connection.confirmTransaction(sig, "confirmed");
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
it("initialize + deposit + withdraw via SDK", async () => {
|
|
74
|
-
const { config, vault, noteTree, nullifiers } = getPoolPdas(
|
|
75
|
-
program.programId
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
// 1) Make sure admin has SOL
|
|
79
|
-
await airdropAndConfirm(wallet.publicKey, 5 * LAMPORTS_PER_SOL);
|
|
80
|
-
|
|
81
|
-
// 2) Initialize pool if needed (idempotent-ish for localnet dev)
|
|
82
|
-
const existingConfig = await connection.getAccountInfo(config);
|
|
83
|
-
if (!existingConfig) {
|
|
84
|
-
await initializePool({
|
|
85
|
-
program,
|
|
86
|
-
admin: wallet,
|
|
87
|
-
denomsLamports: DENOMS_LAMPORTS,
|
|
88
|
-
feeBps: FEE_BPS,
|
|
89
|
-
});
|
|
90
|
-
} else {
|
|
91
|
-
console.log(
|
|
92
|
-
"Initialize skipped: config already exists on this cluster, continuing."
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Ground truth amounts come from what we passed into initializePool
|
|
97
|
-
const amountLamports = BigInt(DENOMS_LAMPORTS[DENOM_INDEX].toString());
|
|
98
|
-
const fee = (amountLamports * BigInt(FEE_BPS)) / 10_000n;
|
|
99
|
-
const toUser = amountLamports - fee;
|
|
100
|
-
|
|
101
|
-
// 3) Deposit one note via SDK helper
|
|
102
|
-
await createNoteAndDeposit({
|
|
103
|
-
program,
|
|
104
|
-
depositor: wallet,
|
|
105
|
-
denomIndex: DENOM_INDEX,
|
|
106
|
-
valueLamports: amountLamports,
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// 4) Prepare relayer + recipient
|
|
110
|
-
const relayer = Keypair.generate();
|
|
111
|
-
const recipient = Keypair.generate();
|
|
112
|
-
|
|
113
|
-
await airdropAndConfirm(relayer.publicKey, 2 * LAMPORTS_PER_SOL);
|
|
114
|
-
await airdropAndConfirm(recipient.publicKey, 0.2 * LAMPORTS_PER_SOL);
|
|
115
|
-
|
|
116
|
-
// Admin adds relayer
|
|
117
|
-
await addRelayer({
|
|
118
|
-
program,
|
|
119
|
-
admin: wallet,
|
|
120
|
-
newRelayer: relayer.publicKey,
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// 5) Fetch current Merkle root from NoteTree account.
|
|
124
|
-
const noteTreeAcc: any = await (program.account as any).noteTree.fetch(
|
|
125
|
-
noteTree
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
// Support all known shapes:
|
|
129
|
-
// - rolling history: { currentRootIndex, roots: number[][] }
|
|
130
|
-
// - single root (previous layout): { lastRoot: number[] }
|
|
131
|
-
// - current layout: { currentRoot: number[] }
|
|
132
|
-
let rootBytes: Uint8Array;
|
|
133
|
-
|
|
134
|
-
if (
|
|
135
|
-
noteTreeAcc &&
|
|
136
|
-
typeof noteTreeAcc.currentRootIndex !== "undefined" &&
|
|
137
|
-
typeof noteTreeAcc.roots !== "undefined"
|
|
138
|
-
) {
|
|
139
|
-
const idx: number = Number(noteTreeAcc.currentRootIndex);
|
|
140
|
-
const rootsArr: number[][] = noteTreeAcc.roots;
|
|
141
|
-
const rootArray: number[] = rootsArr[idx];
|
|
142
|
-
if (!rootArray) {
|
|
143
|
-
console.error("NoteTree account shape (roots):", noteTreeAcc);
|
|
144
|
-
throw new Error("NoteTree.roots[currentRootIndex] is undefined");
|
|
145
|
-
}
|
|
146
|
-
rootBytes = new Uint8Array(rootArray);
|
|
147
|
-
} else if (
|
|
148
|
-
noteTreeAcc &&
|
|
149
|
-
typeof noteTreeAcc.lastRoot !== "undefined"
|
|
150
|
-
) {
|
|
151
|
-
const rootArray: number[] = noteTreeAcc.lastRoot;
|
|
152
|
-
rootBytes = new Uint8Array(rootArray);
|
|
153
|
-
} else if (
|
|
154
|
-
noteTreeAcc &&
|
|
155
|
-
typeof noteTreeAcc.currentRoot !== "undefined"
|
|
156
|
-
) {
|
|
157
|
-
const rootArray: number[] = noteTreeAcc.currentRoot;
|
|
158
|
-
rootBytes = new Uint8Array(rootArray);
|
|
159
|
-
} else {
|
|
160
|
-
console.error("NoteTree account shape (unknown):", noteTreeAcc);
|
|
161
|
-
throw new Error("Unknown NoteTree layout in IDL / account");
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const nullifier = new Uint8Array(32).fill(3); // demo nullifier
|
|
165
|
-
|
|
166
|
-
const beforeVault = BigInt(await connection.getBalance(vault));
|
|
167
|
-
const beforeRelayer = BigInt(
|
|
168
|
-
await connection.getBalance(relayer.publicKey)
|
|
169
|
-
);
|
|
170
|
-
const beforeRecipient = BigInt(
|
|
171
|
-
await connection.getBalance(recipient.publicKey)
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
// 6) Withdraw via SDK
|
|
175
|
-
await withdrawViaRelayer({
|
|
176
|
-
program,
|
|
177
|
-
relayer,
|
|
178
|
-
recipient: recipient.publicKey,
|
|
179
|
-
denomIndex: DENOM_INDEX,
|
|
180
|
-
root: rootBytes,
|
|
181
|
-
nullifier,
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
const afterVault = BigInt(await connection.getBalance(vault));
|
|
185
|
-
const afterRelayer = BigInt(
|
|
186
|
-
await connection.getBalance(relayer.publicKey)
|
|
187
|
-
);
|
|
188
|
-
const afterRecipient = BigInt(
|
|
189
|
-
await connection.getBalance(recipient.publicKey)
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
const vaultDelta = beforeVault - afterVault;
|
|
193
|
-
const relayerDelta = afterRelayer - beforeRelayer;
|
|
194
|
-
const recipientDelta = afterRecipient - beforeRecipient;
|
|
195
|
-
|
|
196
|
-
// 7) Assertions
|
|
197
|
-
assert(
|
|
198
|
-
vaultDelta === amountLamports,
|
|
199
|
-
`Vault did not decrease by amount: got ${vaultDelta.toString()} expected ${amountLamports.toString()}`
|
|
200
|
-
);
|
|
201
|
-
assert(
|
|
202
|
-
relayerDelta === fee,
|
|
203
|
-
`Relayer did not receive correct fee: got ${relayerDelta.toString()} expected ${fee.toString()}`
|
|
204
|
-
);
|
|
205
|
-
assert(
|
|
206
|
-
recipientDelta === toUser,
|
|
207
|
-
`Recipient did not receive correct amount: got ${recipientDelta.toString()} expected ${toUser.toString()}`
|
|
208
|
-
);
|
|
209
|
-
});
|
|
210
|
-
});
|