@veilo/sdk-core 0.3.3 → 0.4.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/README.md +42 -3
- package/SHIELD_INTEGRATION.md +143 -0
- package/config.d.ts +2 -0
- package/config.js +4 -0
- package/dist/cjs/client.d.ts +5 -5
- package/dist/cjs/client.js +79 -42
- package/dist/cjs/compactNote.d.ts +107 -0
- package/dist/cjs/compactNote.js +167 -0
- package/dist/cjs/events.d.ts +1 -1
- package/dist/cjs/events.js +2 -2
- package/dist/cjs/idl/privacy_pool.d.ts +5 -0
- package/dist/cjs/idl/privacy_pool.js +15218 -0
- package/dist/cjs/index.d.ts +16 -13
- package/dist/cjs/index.js +40 -30
- package/dist/cjs/merkle.d.ts +13 -0
- package/dist/cjs/merkle.js +31 -8
- package/dist/cjs/program.d.ts +11 -0
- package/dist/cjs/program.js +26 -3
- package/dist/cjs/proof.d.ts +1 -1
- package/dist/cjs/proof.js +37 -37
- package/dist/cjs/prover.d.ts +1 -1
- package/dist/cjs/prover.js +2 -2
- package/dist/cjs/random.d.ts +16 -0
- package/dist/cjs/random.js +28 -0
- package/dist/cjs/relayer.d.ts +27 -4
- package/dist/cjs/relayer.js +16 -5
- package/dist/cjs/shield/alt.d.ts +87 -0
- package/dist/cjs/shield/alt.js +194 -0
- package/dist/cjs/shield/computeBudget.d.ts +61 -0
- package/dist/cjs/shield/computeBudget.js +64 -0
- package/dist/cjs/shield/errors.d.ts +58 -0
- package/dist/cjs/shield/errors.js +121 -0
- package/dist/cjs/shield/finalize.d.ts +45 -0
- package/dist/cjs/shield/finalize.js +119 -0
- package/dist/cjs/shield/index.d.ts +35 -0
- package/dist/cjs/shield/index.js +68 -0
- package/dist/cjs/shield/ix.d.ts +54 -0
- package/dist/cjs/shield/ix.js +119 -0
- package/dist/cjs/shield/owner.d.ts +36 -0
- package/dist/cjs/shield/owner.js +126 -0
- package/dist/cjs/shield/ports.d.ts +43 -0
- package/dist/cjs/shield/ports.js +153 -0
- package/dist/cjs/shield/preflight.d.ts +30 -0
- package/dist/cjs/shield/preflight.js +154 -0
- package/dist/cjs/shield/shield.d.ts +68 -0
- package/dist/cjs/shield/shield.js +499 -0
- package/dist/cjs/shield/types.d.ts +202 -0
- package/dist/cjs/shield/types.js +2 -0
- package/dist/cjs/utxo.d.ts +20 -0
- package/dist/cjs/utxo.js +41 -28
- package/dist/esm/client.d.ts +407 -0
- package/dist/esm/client.js +9 -5
- package/dist/esm/compactNote.d.ts +107 -0
- package/dist/esm/compactNote.js +156 -0
- package/dist/esm/config.d.ts +82 -0
- package/dist/esm/events.d.ts +77 -0
- package/dist/esm/events.js +1 -1
- package/dist/esm/idl/privacy_pool.d.ts +5 -0
- package/dist/esm/idl/privacy_pool.js +15216 -0
- package/dist/esm/index.d.ts +16 -0
- package/dist/esm/index.js +18 -11
- package/dist/esm/merkle.d.ts +77 -0
- package/dist/esm/merkle.js +23 -1
- package/dist/esm/package.json +1 -0
- package/dist/esm/poseidon.d.ts +29 -0
- package/dist/esm/program.d.ts +37 -0
- package/dist/esm/program.js +23 -1
- package/dist/esm/proof.d.ts +183 -0
- package/dist/esm/proof.js +2 -2
- package/dist/esm/prover.d.ts +54 -0
- package/dist/esm/prover.js +1 -1
- package/dist/esm/random.d.ts +16 -0
- package/dist/esm/random.js +21 -0
- package/dist/esm/relayer.d.ts +318 -0
- package/dist/esm/relayer.js +16 -5
- package/dist/esm/retry.d.ts +32 -0
- package/dist/esm/shield/alt.d.ts +87 -0
- package/dist/esm/shield/alt.js +186 -0
- package/dist/esm/shield/computeBudget.d.ts +61 -0
- package/dist/esm/shield/computeBudget.js +61 -0
- package/dist/esm/shield/errors.d.ts +58 -0
- package/dist/esm/shield/errors.js +115 -0
- package/dist/esm/shield/finalize.d.ts +45 -0
- package/dist/esm/shield/finalize.js +83 -0
- package/dist/esm/shield/index.d.ts +35 -0
- package/dist/esm/shield/index.js +32 -0
- package/dist/esm/shield/ix.d.ts +54 -0
- package/dist/esm/shield/ix.js +82 -0
- package/dist/esm/shield/owner.d.ts +36 -0
- package/dist/esm/shield/owner.js +122 -0
- package/dist/esm/shield/ports.d.ts +43 -0
- package/dist/esm/shield/ports.js +147 -0
- package/dist/esm/shield/preflight.d.ts +30 -0
- package/dist/esm/shield/preflight.js +151 -0
- package/dist/esm/shield/shield.d.ts +68 -0
- package/dist/esm/shield/shield.js +492 -0
- package/dist/esm/shield/types.d.ts +202 -0
- package/dist/esm/shield/types.js +1 -0
- package/dist/esm/utxo.d.ts +235 -0
- package/dist/esm/utxo.js +18 -8
- package/dist/src/client.d.ts +407 -0
- package/dist/src/client.js +951 -0
- package/dist/src/compactNote.d.ts +107 -0
- package/dist/src/compactNote.js +167 -0
- package/dist/src/config.d.ts +82 -0
- package/dist/src/config.js +57 -0
- package/dist/src/events.d.ts +77 -0
- package/dist/src/events.js +167 -0
- package/dist/src/idl/privacy_pool.d.ts +5 -0
- package/dist/src/idl/privacy_pool.js +15218 -0
- package/dist/src/index.d.ts +16 -0
- package/dist/src/index.js +67 -0
- package/dist/src/merkle.d.ts +77 -0
- package/dist/src/merkle.js +156 -0
- package/dist/src/poseidon.d.ts +29 -0
- package/dist/src/poseidon.js +100 -0
- package/dist/src/program.d.ts +37 -0
- package/dist/src/program.js +61 -0
- package/dist/src/proof.d.ts +183 -0
- package/dist/src/proof.js +292 -0
- package/dist/src/prover.d.ts +54 -0
- package/dist/src/prover.js +112 -0
- package/dist/src/random.d.ts +16 -0
- package/dist/src/random.js +28 -0
- package/dist/src/relayer.d.ts +318 -0
- package/dist/src/relayer.js +257 -0
- package/dist/src/retry.d.ts +32 -0
- package/dist/src/retry.js +75 -0
- package/dist/src/shield/alt.d.ts +87 -0
- package/dist/src/shield/alt.js +194 -0
- package/dist/src/shield/computeBudget.d.ts +61 -0
- package/dist/src/shield/computeBudget.js +64 -0
- package/dist/src/shield/errors.d.ts +58 -0
- package/dist/src/shield/errors.js +121 -0
- package/dist/src/shield/finalize.d.ts +45 -0
- package/dist/src/shield/finalize.js +119 -0
- package/dist/src/shield/index.d.ts +35 -0
- package/dist/src/shield/index.js +68 -0
- package/dist/src/shield/ix.d.ts +54 -0
- package/dist/src/shield/ix.js +119 -0
- package/dist/src/shield/owner.d.ts +36 -0
- package/dist/src/shield/owner.js +126 -0
- package/dist/src/shield/ports.d.ts +43 -0
- package/dist/src/shield/ports.js +153 -0
- package/dist/src/shield/preflight.d.ts +30 -0
- package/dist/src/shield/preflight.js +154 -0
- package/dist/src/shield/shield.d.ts +68 -0
- package/dist/src/shield/shield.js +499 -0
- package/dist/src/shield/types.d.ts +202 -0
- package/dist/src/shield/types.js +2 -0
- package/dist/src/utxo.d.ts +235 -0
- package/dist/src/utxo.js +407 -0
- package/dist/tests/compact-note.test.d.ts +1 -0
- package/dist/tests/compact-note.test.js +173 -0
- package/dist/tests/config.test.d.ts +1 -0
- package/dist/tests/config.test.js +102 -0
- package/dist/tests/edge-cases.test.d.ts +1 -0
- package/dist/tests/edge-cases.test.js +220 -0
- package/dist/tests/encryption.test.d.ts +1 -0
- package/dist/tests/encryption.test.js +215 -0
- package/dist/tests/events.test.d.ts +1 -0
- package/dist/tests/events.test.js +78 -0
- package/dist/tests/multi-tree.test.d.ts +1 -0
- package/dist/tests/multi-tree.test.js +405 -0
- package/dist/tests/pda.test.d.ts +1 -0
- package/dist/tests/pda.test.js +229 -0
- package/dist/tests/poseidon-builder-parity.test.d.ts +1 -0
- package/dist/tests/poseidon-builder-parity.test.js +72 -0
- package/dist/tests/poseidon.test.d.ts +1 -0
- package/dist/tests/poseidon.test.js +142 -0
- package/dist/tests/proof.test.d.ts +1 -0
- package/dist/tests/proof.test.js +296 -0
- package/dist/tests/relayer.test.d.ts +1 -0
- package/dist/tests/relayer.test.js +271 -0
- package/dist/tests/sdk.integration.test.d.ts +1 -0
- package/dist/tests/sdk.integration.test.js +330 -0
- package/dist/tests/shield-owner.test.d.ts +1 -0
- package/dist/tests/shield-owner.test.js +89 -0
- package/dist/tests/shield-preflight.test.d.ts +1 -0
- package/dist/tests/shield-preflight.test.js +87 -0
- package/dist/tests/shield-realproof.test.d.ts +1 -0
- package/dist/tests/shield-realproof.test.js +272 -0
- package/dist/tests/shield.test.d.ts +1 -0
- package/dist/tests/shield.test.js +403 -0
- package/dist/tests/utxo.test.d.ts +1 -0
- package/dist/tests/utxo.test.js +140 -0
- package/package.json +69 -11
- package/poseidon.d.ts +2 -0
- package/poseidon.js +4 -0
- package/proof.d.ts +2 -0
- package/proof.js +4 -0
- package/prover.d.ts +2 -0
- package/prover.js +4 -0
- package/shield.d.ts +2 -0
- package/shield.js +4 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { PublicKey } from "@solana/web3.js";
|
|
2
|
+
import type { ShieldOwner } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a shield recipient into the pair of keys that must belong to the same
|
|
5
|
+
* account.
|
|
6
|
+
*
|
|
7
|
+
* This is the only mechanism that makes the owner invariant safe. A note's
|
|
8
|
+
* *spend* authority is a Poseidon field element and its *detection* authority is
|
|
9
|
+
* an Ed25519 wallet key; nothing on-chain relates them, and if an integrator
|
|
10
|
+
* sources them separately and gets them wrong, the note is spendable but
|
|
11
|
+
* permanently invisible — the funds are gone from the owner's view with no error
|
|
12
|
+
* raised anywhere in the stack.
|
|
13
|
+
*
|
|
14
|
+
* `GET /auth/veiloPublicKey` returns both halves in a single response, so
|
|
15
|
+
* routing through this function makes the pairing structural rather than a
|
|
16
|
+
* convention integrators are asked to remember.
|
|
17
|
+
*
|
|
18
|
+
* Prefer this over hand-building a {@link ShieldOwner} in all cases.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveShieldOwner(target: {
|
|
21
|
+
username: string;
|
|
22
|
+
} | {
|
|
23
|
+
publicKey: string | PublicKey;
|
|
24
|
+
}): Promise<ShieldOwner>;
|
|
25
|
+
/**
|
|
26
|
+
* Build a {@link ShieldOwner} from keys the caller already holds.
|
|
27
|
+
*
|
|
28
|
+
* Only correct when both keys provably belong to the same account. The explicit
|
|
29
|
+
* flag is not ceremony — it is the last checkpoint before a class of bug whose
|
|
30
|
+
* only symptom is a user's money quietly disappearing from their balance.
|
|
31
|
+
*/
|
|
32
|
+
export declare function unsafeShieldOwner(params: {
|
|
33
|
+
veiloPublicKey: bigint;
|
|
34
|
+
noteViewingKey: PublicKey;
|
|
35
|
+
acknowledgeOwnerInvariant: true;
|
|
36
|
+
}): ShieldOwner;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { PublicKey } from "@solana/web3.js";
|
|
2
|
+
import { VeiloApiError, VeiloRelayerClient } from "../relayer.js";
|
|
3
|
+
import { ShieldError } from "./errors.js";
|
|
4
|
+
/**
|
|
5
|
+
* Owner resolution is a Veilo protocol service, not an integrator-selected
|
|
6
|
+
* transport. Keep the production endpoint and public encryption key inside the
|
|
7
|
+
* SDK so callers cannot accidentally resolve the two owner keys from different
|
|
8
|
+
* relayers.
|
|
9
|
+
*
|
|
10
|
+
* These values are public network coordinates, not credentials. In particular,
|
|
11
|
+
* never put a secret API key here: browser/mobile package contents are always
|
|
12
|
+
* inspectable. The two owner endpoints used below are intentionally public and
|
|
13
|
+
* return public key material only.
|
|
14
|
+
*/
|
|
15
|
+
const OWNER_RELAYER = new VeiloRelayerClient({
|
|
16
|
+
apiKey: "",
|
|
17
|
+
relayerPublicKey: "utVxnA7zax09qJCZ7UJsa8PAOoWLRcCwOkdxg/ZGmD4=",
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a shield recipient into the pair of keys that must belong to the same
|
|
21
|
+
* account.
|
|
22
|
+
*
|
|
23
|
+
* This is the only mechanism that makes the owner invariant safe. A note's
|
|
24
|
+
* *spend* authority is a Poseidon field element and its *detection* authority is
|
|
25
|
+
* an Ed25519 wallet key; nothing on-chain relates them, and if an integrator
|
|
26
|
+
* sources them separately and gets them wrong, the note is spendable but
|
|
27
|
+
* permanently invisible — the funds are gone from the owner's view with no error
|
|
28
|
+
* raised anywhere in the stack.
|
|
29
|
+
*
|
|
30
|
+
* `GET /auth/veiloPublicKey` returns both halves in a single response, so
|
|
31
|
+
* routing through this function makes the pairing structural rather than a
|
|
32
|
+
* convention integrators are asked to remember.
|
|
33
|
+
*
|
|
34
|
+
* Prefer this over hand-building a {@link ShieldOwner} in all cases.
|
|
35
|
+
*/
|
|
36
|
+
export async function resolveShieldOwner(target) {
|
|
37
|
+
const query = "username" in target
|
|
38
|
+
? { username: target.username }
|
|
39
|
+
: {
|
|
40
|
+
publicKey: typeof target.publicKey === "string"
|
|
41
|
+
? target.publicKey
|
|
42
|
+
: target.publicKey.toBase58(),
|
|
43
|
+
};
|
|
44
|
+
let response;
|
|
45
|
+
try {
|
|
46
|
+
response = await OWNER_RELAYER.getVeiloPublicKey(query);
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
// A raw Solana address may not have a Veilo account yet. Provisioning is
|
|
50
|
+
// public and idempotent, and returns only the Poseidon spend key; the
|
|
51
|
+
// address we were given is the matching note-viewing key.
|
|
52
|
+
if ("publicKey" in target &&
|
|
53
|
+
(e?.status === 404 || (e instanceof VeiloApiError && e.status === 404))) {
|
|
54
|
+
const wallet = typeof target.publicKey === "string"
|
|
55
|
+
? new PublicKey(target.publicKey)
|
|
56
|
+
: target.publicKey;
|
|
57
|
+
const provisioned = await OWNER_RELAYER.preRegisterKey(wallet.toBase58());
|
|
58
|
+
if (!provisioned?.veiloPublicKey) {
|
|
59
|
+
throw new ShieldError("UNKNOWN", "The relayer pre-registered the recipient but returned no Veilo public key.", { context: { publicKey: wallet.toBase58(), response: provisioned }, cause: e });
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
veiloPublicKey: BigInt(provisioned.veiloPublicKey),
|
|
63
|
+
noteViewingKey: wallet,
|
|
64
|
+
acknowledgeOwnerInvariant: true,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// A pre-registered account is one someone else created on the recipient's
|
|
68
|
+
// behalf; the recipient has never proved ownership, so the relayer refuses
|
|
69
|
+
// to resolve it by username. Shielding to them still works, but the caller
|
|
70
|
+
// must address them by Solana address instead.
|
|
71
|
+
if (e?.body?.preRegistered ||
|
|
72
|
+
e?.details?.preRegistered ||
|
|
73
|
+
/preregistered/i.test(String(e?.message ?? ""))) {
|
|
74
|
+
throw new ShieldError("UNKNOWN", `"${"username" in target ? target.username : "that account"}" is pre-registered ` +
|
|
75
|
+
`and cannot be resolved by username. Resolve it by Solana address instead — ` +
|
|
76
|
+
`resolveShieldOwner({ publicKey }).`, { context: { preRegistered: true }, cause: e });
|
|
77
|
+
}
|
|
78
|
+
throw e;
|
|
79
|
+
}
|
|
80
|
+
const veiloPublicKeyRaw = response?.veiloPublicKey;
|
|
81
|
+
const walletRaw = response?.pubK;
|
|
82
|
+
if (!veiloPublicKeyRaw || !walletRaw) {
|
|
83
|
+
throw new ShieldError("UNKNOWN", `The relayer did not return both keys for this recipient ` +
|
|
84
|
+
`(veiloPublicKey=${!!veiloPublicKeyRaw}, pubK=${!!walletRaw}). ` +
|
|
85
|
+
`Shielding without both would create a note the owner cannot find.`, { context: { response } });
|
|
86
|
+
}
|
|
87
|
+
const noteViewingKey = new PublicKey(walletRaw);
|
|
88
|
+
if ("publicKey" in target &&
|
|
89
|
+
!noteViewingKey.equals(typeof target.publicKey === "string"
|
|
90
|
+
? new PublicKey(target.publicKey)
|
|
91
|
+
: target.publicKey)) {
|
|
92
|
+
throw new ShieldError("UNKNOWN", "The Veilo owner service returned a different wallet than the one requested. " +
|
|
93
|
+
"Shielding was stopped to prevent creating a note for the wrong recipient.", {
|
|
94
|
+
context: {
|
|
95
|
+
requested: typeof target.publicKey === "string"
|
|
96
|
+
? target.publicKey
|
|
97
|
+
: target.publicKey.toBase58(),
|
|
98
|
+
returned: noteViewingKey.toBase58(),
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
veiloPublicKey: BigInt(veiloPublicKeyRaw),
|
|
104
|
+
noteViewingKey,
|
|
105
|
+
// Safe to assert: both halves came from one response about one account.
|
|
106
|
+
acknowledgeOwnerInvariant: true,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Build a {@link ShieldOwner} from keys the caller already holds.
|
|
111
|
+
*
|
|
112
|
+
* Only correct when both keys provably belong to the same account. The explicit
|
|
113
|
+
* flag is not ceremony — it is the last checkpoint before a class of bug whose
|
|
114
|
+
* only symptom is a user's money quietly disappearing from their balance.
|
|
115
|
+
*/
|
|
116
|
+
export function unsafeShieldOwner(params) {
|
|
117
|
+
return {
|
|
118
|
+
veiloPublicKey: params.veiloPublicKey,
|
|
119
|
+
noteViewingKey: params.noteViewingKey,
|
|
120
|
+
acknowledgeOwnerInvariant: true,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Connection, PublicKey } from "@solana/web3.js";
|
|
2
|
+
import type { DepositTarget, ShieldCachePort, TreeSource } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Injection points for the shield path.
|
|
5
|
+
*
|
|
6
|
+
* The three production deposit implementations (extension, wallet-app,
|
|
7
|
+
* veilo-dapp) are ~85% identical and diverge on exactly these axes: where
|
|
8
|
+
* proofs come from, where the tree comes from, which lookup table to use, and
|
|
9
|
+
* what to cache into. Making each an explicit port is what lets one
|
|
10
|
+
* implementation serve all of them — and lets a third-party integrator supply
|
|
11
|
+
* their own without forking anything.
|
|
12
|
+
*
|
|
13
|
+
* Proof generation is deliberately NOT redefined here: `TransactionProofBuilder`
|
|
14
|
+
* from `proof.ts` already has the right shape, and a second prover abstraction
|
|
15
|
+
* would be exactly the kind of duplication this module exists to avoid.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Read the target tree and root directly from chain.
|
|
19
|
+
*
|
|
20
|
+
* Note the account decoding: `MerkleTreeAccount::EXPECTED_SIZE` (9107) does not
|
|
21
|
+
* match the size its declared fields sum to (8987), a 120-byte discrepancy that
|
|
22
|
+
* is unexplained. Until it is, hand-slicing this account at fixed byte offsets
|
|
23
|
+
* is unsafe. Anchor's coder reads declared fields from the front and ignores the
|
|
24
|
+
* tail, so `.fetch()` stays correct regardless.
|
|
25
|
+
*/
|
|
26
|
+
export declare function onChainTreeSource(connection: Connection, programId?: PublicKey): TreeSource;
|
|
27
|
+
/**
|
|
28
|
+
* Fixed values, for tests and offline builds.
|
|
29
|
+
*
|
|
30
|
+
* Also the escape hatch for an integrator who already tracks the tree state and
|
|
31
|
+
* does not want the SDK making RPC calls on their behalf.
|
|
32
|
+
*/
|
|
33
|
+
export declare function staticTreeSource(target: DepositTarget): TreeSource;
|
|
34
|
+
/**
|
|
35
|
+
* In-memory cache with TTL. The default.
|
|
36
|
+
*
|
|
37
|
+
* Persistent storage is deliberately the caller's problem — `localStorage`,
|
|
38
|
+
* `chrome.storage`, and `AsyncStorage` are three different answers in three
|
|
39
|
+
* different apps, and none of them belongs in a published SDK.
|
|
40
|
+
*/
|
|
41
|
+
export declare function memoryCache(): ShieldCachePort;
|
|
42
|
+
/** A cache that never stores anything. Useful for tests that assert fetch counts. */
|
|
43
|
+
export declare function noCache(): ShieldCachePort;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { createReadonlyVeiloProgram, PRIVACY_POOL_PROGRAM_ID } from "../program.js";
|
|
2
|
+
import { getNoteTreePda, getPoolPdas } from "../client.js";
|
|
3
|
+
import { MERKLE_TREE_DEPTH } from "../config.js";
|
|
4
|
+
import { ShieldError } from "./errors.js";
|
|
5
|
+
/**
|
|
6
|
+
* Injection points for the shield path.
|
|
7
|
+
*
|
|
8
|
+
* The three production deposit implementations (extension, wallet-app,
|
|
9
|
+
* veilo-dapp) are ~85% identical and diverge on exactly these axes: where
|
|
10
|
+
* proofs come from, where the tree comes from, which lookup table to use, and
|
|
11
|
+
* what to cache into. Making each an explicit port is what lets one
|
|
12
|
+
* implementation serve all of them — and lets a third-party integrator supply
|
|
13
|
+
* their own without forking anything.
|
|
14
|
+
*
|
|
15
|
+
* Proof generation is deliberately NOT redefined here: `TransactionProofBuilder`
|
|
16
|
+
* from `proof.ts` already has the right shape, and a second prover abstraction
|
|
17
|
+
* would be exactly the kind of duplication this module exists to avoid.
|
|
18
|
+
*/
|
|
19
|
+
// -- TreeSource ---------------------------------------------------------------
|
|
20
|
+
/**
|
|
21
|
+
* Read the target tree and root directly from chain.
|
|
22
|
+
*
|
|
23
|
+
* Note the account decoding: `MerkleTreeAccount::EXPECTED_SIZE` (9107) does not
|
|
24
|
+
* match the size its declared fields sum to (8987), a 120-byte discrepancy that
|
|
25
|
+
* is unexplained. Until it is, hand-slicing this account at fixed byte offsets
|
|
26
|
+
* is unsafe. Anchor's coder reads declared fields from the front and ignores the
|
|
27
|
+
* tail, so `.fetch()` stays correct regardless.
|
|
28
|
+
*/
|
|
29
|
+
export function onChainTreeSource(connection, programId = PRIVACY_POOL_PROGRAM_ID) {
|
|
30
|
+
return {
|
|
31
|
+
async getDepositTarget(mint) {
|
|
32
|
+
const program = createReadonlyVeiloProgram(connection);
|
|
33
|
+
const { config } = getPoolPdas(programId, mint);
|
|
34
|
+
const cfg = await program.account.privacyConfig.fetch(config);
|
|
35
|
+
const numTrees = Number(cfg.numTrees);
|
|
36
|
+
if (!Number.isInteger(numTrees) || numTrees <= 0) {
|
|
37
|
+
throw new ShieldError("TREE_FULL", `Pool ${mint.toBase58()} has no deposit tree.`, { context: { mint: mint.toBase58(), numTrees } });
|
|
38
|
+
}
|
|
39
|
+
// Fetch every tree in one RPC call. The older getBestTreeForDeposit path
|
|
40
|
+
// fetched them serially, then fetched the chosen trees a second time.
|
|
41
|
+
const treePdas = Array.from({ length: numTrees }, (_, treeId) => getNoteTreePda(programId, mint, treeId));
|
|
42
|
+
const infos = await connection.getMultipleAccountsInfo(treePdas, {
|
|
43
|
+
commitment: "confirmed",
|
|
44
|
+
});
|
|
45
|
+
const capacity = 2 ** MERKLE_TREE_DEPTH;
|
|
46
|
+
const trees = infos.map((info, treeId) => {
|
|
47
|
+
if (!info)
|
|
48
|
+
return null;
|
|
49
|
+
// Anchor camel-cases the IDL's `MerkleTreeAccount` name when it builds
|
|
50
|
+
// the Borsh account-layout map (the same key exposed as
|
|
51
|
+
// `program.account.merkleTreeAccount`).
|
|
52
|
+
const decoded = program.coder.accounts.decode("merkleTreeAccount", info.data);
|
|
53
|
+
const nextIndex = Number(decoded.nextIndex);
|
|
54
|
+
return {
|
|
55
|
+
treeId,
|
|
56
|
+
decoded,
|
|
57
|
+
nextIndex,
|
|
58
|
+
remainingCapacity: capacity - nextIndex,
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
const inputTree = trees[0];
|
|
62
|
+
if (!inputTree) {
|
|
63
|
+
throw new ShieldError("POOL_NOT_FOUND", `Pool ${mint.toBase58()} is missing its input Merkle tree (tree 0).`, { context: { mint: mint.toBase58(), tree: treePdas[0].toBase58() } });
|
|
64
|
+
}
|
|
65
|
+
const available = trees.filter((tree) => !!tree && tree.nextIndex < capacity);
|
|
66
|
+
if (available.length === 0) {
|
|
67
|
+
throw new ShieldError("TREE_FULL", `All ${numTrees} Merkle trees for ${mint.toBase58()} are full.`, { context: { mint: mint.toBase58(), numTrees, capacity } });
|
|
68
|
+
}
|
|
69
|
+
const minCapacity = Math.floor(capacity * 0.0001);
|
|
70
|
+
const preferred = available
|
|
71
|
+
.filter((tree) => tree.remainingCapacity >= minCapacity)
|
|
72
|
+
.sort((a, b) => a.treeId - b.treeId);
|
|
73
|
+
const outputTree = preferred[0] ??
|
|
74
|
+
available.sort((a, b) => b.remainingCapacity - a.remainingCapacity)[0];
|
|
75
|
+
return {
|
|
76
|
+
inputTreeId: 0,
|
|
77
|
+
inputRoot: extractRoot(inputTree.decoded),
|
|
78
|
+
outputTreeId: outputTree.treeId,
|
|
79
|
+
outputNextIndex: outputTree.nextIndex,
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Fixed values, for tests and offline builds.
|
|
86
|
+
*
|
|
87
|
+
* Also the escape hatch for an integrator who already tracks the tree state and
|
|
88
|
+
* does not want the SDK making RPC calls on their behalf.
|
|
89
|
+
*/
|
|
90
|
+
export function staticTreeSource(target) {
|
|
91
|
+
return { async getDepositTarget() { return target; } };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Read the root from a decoded `MerkleTreeAccount`.
|
|
95
|
+
*
|
|
96
|
+
* Uses `root_history[root_index]` rather than the `root` field. Both are written
|
|
97
|
+
* to the same value by `append` (merkle_tree.rs:108-111) so they agree, but the
|
|
98
|
+
* history read is what all three production implementations use and is therefore
|
|
99
|
+
* the one that is actually exercised against mainnet.
|
|
100
|
+
*/
|
|
101
|
+
function extractRoot(tree) {
|
|
102
|
+
const history = tree.rootHistory ?? tree.root_history;
|
|
103
|
+
const index = Number(tree.rootIndex ?? tree.root_index ?? 0);
|
|
104
|
+
if (Array.isArray(history) && history[index]) {
|
|
105
|
+
return new Uint8Array(history[index]);
|
|
106
|
+
}
|
|
107
|
+
const root = tree.root;
|
|
108
|
+
if (root)
|
|
109
|
+
return new Uint8Array(root);
|
|
110
|
+
throw new Error("could not read a Merkle root from the tree account");
|
|
111
|
+
}
|
|
112
|
+
// -- Cache --------------------------------------------------------------------
|
|
113
|
+
/**
|
|
114
|
+
* In-memory cache with TTL. The default.
|
|
115
|
+
*
|
|
116
|
+
* Persistent storage is deliberately the caller's problem — `localStorage`,
|
|
117
|
+
* `chrome.storage`, and `AsyncStorage` are three different answers in three
|
|
118
|
+
* different apps, and none of them belongs in a published SDK.
|
|
119
|
+
*/
|
|
120
|
+
export function memoryCache() {
|
|
121
|
+
const store = new Map();
|
|
122
|
+
return {
|
|
123
|
+
async get(key) {
|
|
124
|
+
const hit = store.get(key);
|
|
125
|
+
if (!hit)
|
|
126
|
+
return undefined;
|
|
127
|
+
if (hit.expiresAt !== 0 && hit.expiresAt < Date.now()) {
|
|
128
|
+
store.delete(key);
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
return hit.value;
|
|
132
|
+
},
|
|
133
|
+
async set(key, value, ttlMs) {
|
|
134
|
+
store.set(key, {
|
|
135
|
+
value,
|
|
136
|
+
expiresAt: ttlMs && ttlMs > 0 ? Date.now() + ttlMs : 0,
|
|
137
|
+
});
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/** A cache that never stores anything. Useful for tests that assert fetch counts. */
|
|
142
|
+
export function noCache() {
|
|
143
|
+
return {
|
|
144
|
+
async get() { return undefined; },
|
|
145
|
+
async set() { },
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Connection, PublicKey } from "@solana/web3.js";
|
|
2
|
+
import type { PreflightOptions, ShieldCachePort } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Checks that run before the prover.
|
|
5
|
+
*
|
|
6
|
+
* Proving costs 10–30 seconds. Every check here is a single RPC call, and each
|
|
7
|
+
* one pre-empts an on-chain failure whose message tells the user nothing they
|
|
8
|
+
* can act on. The delegate check is the clearest example: the program requires
|
|
9
|
+
* `user_token.delegate.is_none()`, so anyone who ever approved a DEX has a
|
|
10
|
+
* permanently broken deposit path that reports only "Invalid token account
|
|
11
|
+
* authority" — with no hint that revoking a delegate would fix it.
|
|
12
|
+
*
|
|
13
|
+
* These are advisory in the sense that the chain enforces them anyway. They are
|
|
14
|
+
* not advisory in the sense that skipping them produces a materially worse
|
|
15
|
+
* product.
|
|
16
|
+
*/
|
|
17
|
+
export declare function preflightShield(params: {
|
|
18
|
+
connection: Connection;
|
|
19
|
+
amount: bigint;
|
|
20
|
+
mint: PublicKey;
|
|
21
|
+
signer: {
|
|
22
|
+
publicKey: PublicKey;
|
|
23
|
+
tokenAccount: PublicKey;
|
|
24
|
+
};
|
|
25
|
+
programId?: PublicKey;
|
|
26
|
+
options?: PreflightOptions;
|
|
27
|
+
cache?: ShieldCachePort;
|
|
28
|
+
computeUnitLimit?: number;
|
|
29
|
+
computeUnitPriceMicroLamports?: number;
|
|
30
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { createRevokeInstruction, getAccount, getAssociatedTokenAddress, TokenAccountNotFoundError, } from "@solana/spl-token";
|
|
2
|
+
import { getPoolPdas } from "../client.js";
|
|
3
|
+
import { createReadonlyVeiloProgram, PRIVACY_POOL_PROGRAM_ID } from "../program.js";
|
|
4
|
+
import { NATIVE_SOL_MINT } from "../config.js";
|
|
5
|
+
import { ShieldError } from "./errors.js";
|
|
6
|
+
import { NULLIFIER_MARKERS_PER_TRANSACT, NULLIFIER_MARKER_ACCOUNT_BYTES, SHIELD_COMPUTE_UNIT_LIMIT, SHIELD_COMPUTE_UNIT_PRICE_MICRO_LAMPORTS, } from "./computeBudget.js";
|
|
7
|
+
/**
|
|
8
|
+
* Checks that run before the prover.
|
|
9
|
+
*
|
|
10
|
+
* Proving costs 10–30 seconds. Every check here is a single RPC call, and each
|
|
11
|
+
* one pre-empts an on-chain failure whose message tells the user nothing they
|
|
12
|
+
* can act on. The delegate check is the clearest example: the program requires
|
|
13
|
+
* `user_token.delegate.is_none()`, so anyone who ever approved a DEX has a
|
|
14
|
+
* permanently broken deposit path that reports only "Invalid token account
|
|
15
|
+
* authority" — with no hint that revoking a delegate would fix it.
|
|
16
|
+
*
|
|
17
|
+
* These are advisory in the sense that the chain enforces them anyway. They are
|
|
18
|
+
* not advisory in the sense that skipping them produces a materially worse
|
|
19
|
+
* product.
|
|
20
|
+
*/
|
|
21
|
+
export async function preflightShield(params) {
|
|
22
|
+
const { connection, amount, mint, signer, programId = PRIVACY_POOL_PROGRAM_ID, options = {}, } = params;
|
|
23
|
+
const isToken = !mint.equals(NATIVE_SOL_MINT);
|
|
24
|
+
// --- Pool state ------------------------------------------------------------
|
|
25
|
+
if (!options.skipPoolState) {
|
|
26
|
+
const program = createReadonlyVeiloProgram(connection);
|
|
27
|
+
const { config } = getPoolPdas(programId, mint);
|
|
28
|
+
let cfg;
|
|
29
|
+
try {
|
|
30
|
+
cfg = await program.account.privacyConfig.fetch(config);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
throw new ShieldError("POOL_NOT_FOUND", `No privacy pool exists for mint ${mint.toBase58()}.`, { context: { mint: mint.toBase58(), config: config.toBase58() }, cause: e });
|
|
34
|
+
}
|
|
35
|
+
if (cfg.paused) {
|
|
36
|
+
throw new ShieldError("POOL_PAUSED", "The pool is paused. Try again later.");
|
|
37
|
+
}
|
|
38
|
+
const min = BigInt(cfg.minDepositAmount?.toString() ?? "0");
|
|
39
|
+
const max = BigInt(cfg.maxDepositAmount?.toString() ?? "0");
|
|
40
|
+
if (min > 0n && amount < min) {
|
|
41
|
+
throw new ShieldError("AMOUNT_BELOW_MINIMUM", `Amount ${amount} is below the pool minimum of ${min}.`, { context: { amount: amount.toString(), minimum: min.toString() } });
|
|
42
|
+
}
|
|
43
|
+
if (max > 0n && amount > max) {
|
|
44
|
+
throw new ShieldError("AMOUNT_ABOVE_MAXIMUM", `Amount ${amount} exceeds the pool maximum of ${max}.`, { context: { amount: amount.toString(), maximum: max.toString() } });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// --- Source token account (SPL only) --------------------------------------
|
|
48
|
+
if (isToken && !options.skipTokenAccount) {
|
|
49
|
+
let account;
|
|
50
|
+
try {
|
|
51
|
+
account = await getAccount(connection, signer.tokenAccount);
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
if (e instanceof TokenAccountNotFoundError || /could not find/i.test(String(e))) {
|
|
55
|
+
// Deliberately no "create the ATA" remedy: an ATA that does not exist
|
|
56
|
+
// holds zero tokens, so creating it would only move the failure to
|
|
57
|
+
// INSUFFICIENT_TOKEN_BALANCE. Say the true thing instead.
|
|
58
|
+
throw new ShieldError("TOKEN_ACCOUNT_MISSING", `The signer has no token account for ${mint.toBase58()}, so there is nothing to shield.`, { context: { tokenAccount: signer.tokenAccount.toBase58() }, cause: e });
|
|
59
|
+
}
|
|
60
|
+
throw e;
|
|
61
|
+
}
|
|
62
|
+
if (!account.mint.equals(mint)) {
|
|
63
|
+
throw new ShieldError("TOKEN_ACCOUNT_WRONG_MINT", `Token account ${signer.tokenAccount.toBase58()} holds ${account.mint.toBase58()}, not ${mint.toBase58()}.`, { context: { expected: mint.toBase58(), actual: account.mint.toBase58() } });
|
|
64
|
+
}
|
|
65
|
+
if (!account.owner.equals(signer.publicKey)) {
|
|
66
|
+
throw new ShieldError("TOKEN_ACCOUNT_WRONG_OWNER", `Token account ${signer.tokenAccount.toBase58()} is owned by ${account.owner.toBase58()}, ` +
|
|
67
|
+
`not by the signing wallet. The program requires the signer to own the source account outright.`, {
|
|
68
|
+
context: {
|
|
69
|
+
tokenAccount: signer.tokenAccount.toBase58(),
|
|
70
|
+
owner: account.owner.toBase58(),
|
|
71
|
+
signer: signer.publicKey.toBase58(),
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (account.isFrozen) {
|
|
76
|
+
throw new ShieldError("TOKEN_ACCOUNT_FROZEN", `Token account ${signer.tokenAccount.toBase58()} is frozen.`, { context: { tokenAccount: signer.tokenAccount.toBase58() } });
|
|
77
|
+
}
|
|
78
|
+
// The program checks `delegate.is_none()`, NOT the delegated amount — so a
|
|
79
|
+
// stale delegate with zero allowance still fails. This is the single most
|
|
80
|
+
// common avoidable failure on this path, and it is entirely fixable.
|
|
81
|
+
if (account.delegate) {
|
|
82
|
+
throw new ShieldError("TOKEN_ACCOUNT_DELEGATED", `Token account ${signer.tokenAccount.toBase58()} has an active delegate ` +
|
|
83
|
+
`(${account.delegate.toBase58()}). The pool rejects deposits from delegated ` +
|
|
84
|
+
`accounts even when the delegated amount is zero. Revoke the delegate, then shield.`, {
|
|
85
|
+
context: {
|
|
86
|
+
tokenAccount: signer.tokenAccount.toBase58(),
|
|
87
|
+
delegate: account.delegate.toBase58(),
|
|
88
|
+
delegatedAmount: account.delegatedAmount.toString(),
|
|
89
|
+
},
|
|
90
|
+
remedy: {
|
|
91
|
+
description: `Revoke the delegate on ${signer.tokenAccount.toBase58()}`,
|
|
92
|
+
instructions: [
|
|
93
|
+
createRevokeInstruction(signer.tokenAccount, signer.publicKey),
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (account.amount < amount) {
|
|
99
|
+
throw new ShieldError("INSUFFICIENT_TOKEN_BALANCE", `Token account holds ${account.amount} but ${amount} is required.`, { context: { balance: account.amount.toString(), required: amount.toString() } });
|
|
100
|
+
}
|
|
101
|
+
// The vault's ATA must already exist — the program requires the canonical
|
|
102
|
+
// one and will not create it.
|
|
103
|
+
const { vault } = getPoolPdas(programId, mint);
|
|
104
|
+
const vaultAta = await getAssociatedTokenAddress(mint, vault, true);
|
|
105
|
+
const vaultInfo = await connection.getAccountInfo(vaultAta);
|
|
106
|
+
if (!vaultInfo) {
|
|
107
|
+
throw new ShieldError("VAULT_ATA_MISSING", `The pool vault has no associated token account for ${mint.toBase58()}. ` +
|
|
108
|
+
`This is a pool provisioning issue, not something the signer can fix.`, { context: { vault: vault.toBase58(), vaultAta: vaultAta.toBase58() } });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// --- Signer balance --------------------------------------------------------
|
|
112
|
+
if (!options.skipBalance) {
|
|
113
|
+
const rentPer = await cachedRent(connection, params.cache);
|
|
114
|
+
const rent = BigInt(rentPer) * BigInt(NULLIFIER_MARKERS_PER_TRANSACT);
|
|
115
|
+
const computeUnitLimit = params.computeUnitLimit ?? SHIELD_COMPUTE_UNIT_LIMIT;
|
|
116
|
+
const computeUnitPriceMicroLamports = params.computeUnitPriceMicroLamports ?? SHIELD_COMPUTE_UNIT_PRICE_MICRO_LAMPORTS;
|
|
117
|
+
const priorityFee = (BigInt(computeUnitLimit) * BigInt(computeUnitPriceMicroLamports)) /
|
|
118
|
+
1000000n;
|
|
119
|
+
const baseFee = 5000n;
|
|
120
|
+
// The signer pays the marker rent regardless of the mint — it is SOL either
|
|
121
|
+
// way, and it is burned, not refunded.
|
|
122
|
+
const required = rent + priorityFee + baseFee + (isToken ? 0n : amount);
|
|
123
|
+
const balance = BigInt(await connection.getBalance(signer.publicKey));
|
|
124
|
+
if (balance < required) {
|
|
125
|
+
throw new ShieldError("INSUFFICIENT_SOL", `The signing wallet has ${balance} lamports but needs ${required} ` +
|
|
126
|
+
`(${isToken ? "" : `${amount} shielded + `}${rent} nullifier-marker rent + ` +
|
|
127
|
+
`${priorityFee + baseFee} fees). Note that the marker rent is burned, not refunded, ` +
|
|
128
|
+
`and the program forces it onto the signer — it cannot be sponsored.`, {
|
|
129
|
+
context: {
|
|
130
|
+
balance: balance.toString(),
|
|
131
|
+
required: required.toString(),
|
|
132
|
+
breakdown: {
|
|
133
|
+
amount: isToken ? "0" : amount.toString(),
|
|
134
|
+
nullifierMarkerRent: rent.toString(),
|
|
135
|
+
priorityFee: priorityFee.toString(),
|
|
136
|
+
baseFee: baseFee.toString(),
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async function cachedRent(connection, cache) {
|
|
144
|
+
const key = `rent:${NULLIFIER_MARKER_ACCOUNT_BYTES}`;
|
|
145
|
+
const hit = await cache?.get(key);
|
|
146
|
+
if (hit !== undefined)
|
|
147
|
+
return hit;
|
|
148
|
+
const value = await connection.getMinimumBalanceForRentExemption(NULLIFIER_MARKER_ACCOUNT_BYTES);
|
|
149
|
+
await cache?.set(key, value, 60 * 60000);
|
|
150
|
+
return value;
|
|
151
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { AddressLookupTableAccount, Connection } from "@solana/web3.js";
|
|
2
|
+
import { bytesToBigIntBE, BN254_FR_MODULUS } from "../poseidon.js";
|
|
3
|
+
import type { ShieldParams, ShieldProofCache, ShieldResult } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Build an unsigned transaction that shields public funds into the privacy pool.
|
|
6
|
+
*
|
|
7
|
+
* ## The contract
|
|
8
|
+
*
|
|
9
|
+
* This function builds. It does not sign, submit, confirm, or retry — those
|
|
10
|
+
* belong to whoever holds the key, exactly as with Jupiter's `/swap`. The
|
|
11
|
+
* returned `transaction` is unsigned; the caller signs it with whatever wallet
|
|
12
|
+
* they have and broadcasts it themselves.
|
|
13
|
+
*
|
|
14
|
+
* ## Why this can be handed to a third party
|
|
15
|
+
*
|
|
16
|
+
* Three properties make it safe to build a shield on behalf of a signer you do
|
|
17
|
+
* not control, and to shield to an owner who is not the signer:
|
|
18
|
+
*
|
|
19
|
+
* 1. **Deposits are permissionless.** The program gates its relayer allowlist
|
|
20
|
+
* to `public_amount <= 0`, so any wallet can sign a deposit.
|
|
21
|
+
* 2. **No secrets are involved in building.** The proof for a deposit has no
|
|
22
|
+
* real inputs, and the output commitment binds only the recipient's *public*
|
|
23
|
+
* keys.
|
|
24
|
+
* 3. **The note rides on-chain.** `note_ciphers` travels inside the instruction,
|
|
25
|
+
* so the recipient can find the note by scanning even if the platform that
|
|
26
|
+
* submitted it never says a word.
|
|
27
|
+
*
|
|
28
|
+
* ## What the signer unavoidably pays
|
|
29
|
+
*
|
|
30
|
+
* The signer is the fee payer, the funding source, and the payer of two
|
|
31
|
+
* `NullifierMarker` rents (~0.00192 SOL, permanently burned — the program
|
|
32
|
+
* hardcodes `payer = relayer` on both). A wallet with zero SOL cannot shield
|
|
33
|
+
* through this path no matter who submits the transaction. Fee sponsorship is
|
|
34
|
+
* not achievable here: a second signature costs ~96 bytes against a budget with
|
|
35
|
+
* ~69 to spare, and would not cover the rent anyway.
|
|
36
|
+
*/
|
|
37
|
+
export declare function shield(params: ShieldParams): Promise<ShieldResult>;
|
|
38
|
+
/**
|
|
39
|
+
* Re-assemble a shield with a fresh blockhash and deadline, reusing the proof.
|
|
40
|
+
*
|
|
41
|
+
* The Groth16 proof commits to the root, the public amount, `ext_data_hash`, the
|
|
42
|
+
* mint, the nullifiers and the commitments — none of which is time-dependent.
|
|
43
|
+
* `deadline` is a positional instruction argument, outside `ext_data_hash` and
|
|
44
|
+
* therefore outside the proof, so it can be refreshed too. That last part
|
|
45
|
+
* matters: a shield built at T and signed at T+59min needs a new deadline, not
|
|
46
|
+
* just a new blockhash.
|
|
47
|
+
*
|
|
48
|
+
* Costs one `getLatestBlockhash` and a message compile — no proving, no
|
|
49
|
+
* Poseidon, no account resolution.
|
|
50
|
+
*
|
|
51
|
+
* Does NOT recover from `ROOT_STALE`: a stale root invalidates the proof, so
|
|
52
|
+
* that case needs a fresh `shield()` call.
|
|
53
|
+
*/
|
|
54
|
+
export declare function rebuild(result: ShieldResult, opts?: {
|
|
55
|
+
connection?: Connection;
|
|
56
|
+
alt?: AddressLookupTableAccount;
|
|
57
|
+
blockhash?: {
|
|
58
|
+
blockhash: string;
|
|
59
|
+
lastValidBlockHeight: number;
|
|
60
|
+
};
|
|
61
|
+
deadlineSeconds?: number;
|
|
62
|
+
computeUnitPriceMicroLamports?: number;
|
|
63
|
+
}): Promise<ShieldResult>;
|
|
64
|
+
/** Serialize a proof cache for transport between processes. Contains no secrets. */
|
|
65
|
+
export declare function serializeProofCache(cache: ShieldProofCache): string;
|
|
66
|
+
export declare function deserializeProofCache(json: string): ShieldProofCache;
|
|
67
|
+
/** Re-exported for callers that want the raw field arithmetic. */
|
|
68
|
+
export { bytesToBigIntBE, BN254_FR_MODULUS };
|