@zbase-protocol/core 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/experimental.d.ts +16 -8
- package/dist/experimental.js +16 -8
- package/dist/facilitatorClient.d.ts +26 -1
- package/dist/facilitatorClient.js +28 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/notes.js +22 -2
- package/dist/viewingKeyHD.js +11 -2
- package/package.json +1 -1
package/dist/experimental.d.ts
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @zbase-protocol/core/experimental — UNFINISHED UTXO surface. NOT PRODUCTION.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* pool is not deployed (routes return 501) and
|
|
6
|
-
* run. The 2026-07-09 SDK
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* ⚠️⚠️ DO NOT USE WITH REAL FUNDS. These variable-amount UTXO primitives are a
|
|
5
|
+
* PRE-DEPLOYMENT scaffold. The UTXO pool is not deployed (routes return 501) and
|
|
6
|
+
* the trusted-setup ceremony has not run. The 2026-07-09 SDK + black-hat audits
|
|
7
|
+
* confirmed integrator FUND-LOSS footguns here, still present by design until the
|
|
8
|
+
* post-ceremony rewrite:
|
|
9
|
+
* - WIRE-FORMAT BREAK (HIGH): encryptNoteForTransfer produces a ~330-byte
|
|
10
|
+
* length-prefixed envelope (packCiphertext), but the scanner's
|
|
11
|
+
* decodeTransferredLog reassembles a FIXED 128-byte bytes32[4] blob →
|
|
12
|
+
* decryption returns null → a recipient CANNOT see or spend their own inbound
|
|
13
|
+
* note. Building a wallet on createScanner today will LOSE received funds.
|
|
10
14
|
* - createNote defaults NPK material to 0 → a note addressable to nobody;
|
|
11
|
-
* -
|
|
12
|
-
*
|
|
15
|
+
* - deserializeNote now range-validates (fixed), but malformed notes throw;
|
|
16
|
+
* - deriveViewingKeyFromSeed now requires the exact 64-byte BIP39 seed (fixed).
|
|
17
|
+
*
|
|
18
|
+
* The wire-format break is NOT fixed here (it needs the frozen post-ceremony
|
|
19
|
+
* schema); it is quarantined behind this subpath so no main-entry integrator hits
|
|
20
|
+
* it. If you import this, you accept these are unfinished and fund-unsafe.
|
|
13
21
|
*
|
|
14
22
|
* This surface lives on a SEPARATE subpath (not the main entry) so no integrator
|
|
15
23
|
* reaches it by accident: you must explicitly `import ... from
|
package/dist/experimental.js
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @zbase-protocol/core/experimental — UNFINISHED UTXO surface. NOT PRODUCTION.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* pool is not deployed (routes return 501) and
|
|
6
|
-
* run. The 2026-07-09 SDK
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* ⚠️⚠️ DO NOT USE WITH REAL FUNDS. These variable-amount UTXO primitives are a
|
|
5
|
+
* PRE-DEPLOYMENT scaffold. The UTXO pool is not deployed (routes return 501) and
|
|
6
|
+
* the trusted-setup ceremony has not run. The 2026-07-09 SDK + black-hat audits
|
|
7
|
+
* confirmed integrator FUND-LOSS footguns here, still present by design until the
|
|
8
|
+
* post-ceremony rewrite:
|
|
9
|
+
* - WIRE-FORMAT BREAK (HIGH): encryptNoteForTransfer produces a ~330-byte
|
|
10
|
+
* length-prefixed envelope (packCiphertext), but the scanner's
|
|
11
|
+
* decodeTransferredLog reassembles a FIXED 128-byte bytes32[4] blob →
|
|
12
|
+
* decryption returns null → a recipient CANNOT see or spend their own inbound
|
|
13
|
+
* note. Building a wallet on createScanner today will LOSE received funds.
|
|
10
14
|
* - createNote defaults NPK material to 0 → a note addressable to nobody;
|
|
11
|
-
* -
|
|
12
|
-
*
|
|
15
|
+
* - deserializeNote now range-validates (fixed), but malformed notes throw;
|
|
16
|
+
* - deriveViewingKeyFromSeed now requires the exact 64-byte BIP39 seed (fixed).
|
|
17
|
+
*
|
|
18
|
+
* The wire-format break is NOT fixed here (it needs the frozen post-ceremony
|
|
19
|
+
* schema); it is quarantined behind this subpath so no main-entry integrator hits
|
|
20
|
+
* it. If you import this, you accept these are unfinished and fund-unsafe.
|
|
13
21
|
*
|
|
14
22
|
* This surface lives on a SEPARATE subpath (not the main entry) so no integrator
|
|
15
23
|
* reaches it by accident: you must explicitly `import ... from
|
|
@@ -19,13 +19,38 @@
|
|
|
19
19
|
*/
|
|
20
20
|
export type FacilitatorNetwork = "eip155:84532" | "eip155:8453";
|
|
21
21
|
export interface FacilitatorClientConfig {
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Base URL of the zBase deployment, e.g. "https://zbase.app".
|
|
24
|
+
*
|
|
25
|
+
* ⚠️ SECURITY (CRITICAL, 2026-07-09): `baseUrl` is FULLY TRUSTED WITH YOUR SPEND
|
|
26
|
+
* SECRETS. In the current server-side-proving model, `settlePrivately` sends the
|
|
27
|
+
* deposit's `{nullifier, secret}` — the COMPLETE spend authority for the pool
|
|
28
|
+
* note — to this URL. A malicious or man-in-the-middle'd `baseUrl` can withdraw
|
|
29
|
+
* your funds. Therefore:
|
|
30
|
+
* - `https:` is REQUIRED (an on-path attacker cannot read an HTTPS body).
|
|
31
|
+
* - `http://localhost` / `http://127.0.0.1` is allowed for local dev only.
|
|
32
|
+
* - any other `http:` URL is REJECTED unless you explicitly set
|
|
33
|
+
* `allowInsecureHttp: true` (do this ONLY on a trusted private network).
|
|
34
|
+
* - Always PIN this URL to a deployment you control/trust; never take it from
|
|
35
|
+
* an untrusted discovery response or user input.
|
|
36
|
+
*/
|
|
23
37
|
baseUrl: string;
|
|
24
38
|
/** CAIP-2 network. Defaults to Base Sepolia. */
|
|
25
39
|
network?: FacilitatorNetwork;
|
|
26
40
|
/** Optional fetch override (for tests / non-browser runtimes). */
|
|
27
41
|
fetchImpl?: typeof fetch;
|
|
42
|
+
/**
|
|
43
|
+
* Explicitly allow a non-localhost `http://` baseUrl. Only for trusted private
|
|
44
|
+
* networks — this transmits spend secrets in cleartext. Default false.
|
|
45
|
+
*/
|
|
46
|
+
allowInsecureHttp?: boolean;
|
|
28
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Validate the facilitator baseUrl before we ever send spend secrets to it.
|
|
50
|
+
* Throws on a URL that would leak secrets to an on-path attacker. Exported for
|
|
51
|
+
* testing the guard.
|
|
52
|
+
*/
|
|
53
|
+
export declare function assertSafeFacilitatorBaseUrl(baseUrl: string, allowInsecureHttp?: boolean): URL;
|
|
29
54
|
/** Deposit secrets the payer keeps locally; the input to every later settle. */
|
|
30
55
|
export interface DepositSecrets {
|
|
31
56
|
nullifier: string;
|
|
@@ -18,11 +18,39 @@
|
|
|
18
18
|
* mainnet). Zero new dependencies — fetch only.
|
|
19
19
|
*/
|
|
20
20
|
import { generateDepositSecrets, computePrecommitment } from "./account.js";
|
|
21
|
+
/**
|
|
22
|
+
* Validate the facilitator baseUrl before we ever send spend secrets to it.
|
|
23
|
+
* Throws on a URL that would leak secrets to an on-path attacker. Exported for
|
|
24
|
+
* testing the guard.
|
|
25
|
+
*/
|
|
26
|
+
export function assertSafeFacilitatorBaseUrl(baseUrl, allowInsecureHttp = false) {
|
|
27
|
+
let u;
|
|
28
|
+
try {
|
|
29
|
+
u = new URL(baseUrl);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
throw new Error(`FacilitatorClient: baseUrl is not a valid URL: ${baseUrl}`);
|
|
33
|
+
}
|
|
34
|
+
if (u.protocol === "https:")
|
|
35
|
+
return u;
|
|
36
|
+
if (u.protocol === "http:") {
|
|
37
|
+
const host = u.hostname.toLowerCase();
|
|
38
|
+
const isLocal = host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
39
|
+
if (isLocal || allowInsecureHttp)
|
|
40
|
+
return u;
|
|
41
|
+
throw new Error(`FacilitatorClient: refusing an insecure http:// baseUrl (${baseUrl}). settlePrivately sends your spend secrets to this host — use https, or set allowInsecureHttp:true only on a trusted private network.`);
|
|
42
|
+
}
|
|
43
|
+
throw new Error(`FacilitatorClient: unsupported baseUrl protocol '${u.protocol}' — use https.`);
|
|
44
|
+
}
|
|
21
45
|
export class FacilitatorClient {
|
|
22
46
|
base;
|
|
23
47
|
network;
|
|
24
48
|
doFetch;
|
|
25
49
|
constructor(cfg) {
|
|
50
|
+
// CRITICAL C3 fix (2026-07-09): validate baseUrl BEFORE any secret is sent to
|
|
51
|
+
// it. settlePrivately transmits the deposit spend secrets; an http:// or
|
|
52
|
+
// malformed baseUrl would leak them to an on-path attacker → fund theft.
|
|
53
|
+
assertSafeFacilitatorBaseUrl(cfg.baseUrl, cfg.allowInsecureHttp ?? false);
|
|
26
54
|
this.base = cfg.baseUrl.replace(/\/$/, "");
|
|
27
55
|
this.network = cfg.network ?? "eip155:84532";
|
|
28
56
|
const f = cfg.fetchImpl ?? globalThis.fetch;
|
package/dist/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export type { ProofInput, Groth16Proof, ProofResult } from "./proofs.js";
|
|
|
18
18
|
export { generateWithdrawalProof, verifyProofLocally } from "./proofs.js";
|
|
19
19
|
export { buildMerkleTree, generateMerkleProof, getTreeDepth } from "./merkle.js";
|
|
20
20
|
export type { FacilitatorClientConfig, FacilitatorNetwork, DepositSecrets, PreparedDeposit, VerifyResult, SettleResult, } from "./facilitatorClient.js";
|
|
21
|
-
export { FacilitatorClient, createFacilitatorClient } from "./facilitatorClient.js";
|
|
21
|
+
export { FacilitatorClient, createFacilitatorClient, assertSafeFacilitatorBaseUrl } from "./facilitatorClient.js";
|
|
22
22
|
export type { BuildSwapCallPlanOpts } from "./swapPlan.js";
|
|
23
23
|
export { buildSwapCallPlan, EXACT_INPUT_SINGLE_SELECTOR, } from "./swapPlan.js";
|
|
24
24
|
export type { ForwardingNoteSecrets } from "./forwardingNotes.js";
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ export { generateDepositSecrets, computeCommitment, computePrecommitment, comput
|
|
|
17
17
|
export { generateWithdrawalProof, verifyProofLocally } from "./proofs.js";
|
|
18
18
|
// Merkle tree
|
|
19
19
|
export { buildMerkleTree, generateMerkleProof, getTreeDepth } from "./merkle.js";
|
|
20
|
-
export { FacilitatorClient, createFacilitatorClient } from "./facilitatorClient.js";
|
|
20
|
+
export { FacilitatorClient, createFacilitatorClient, assertSafeFacilitatorBaseUrl } from "./facilitatorClient.js";
|
|
21
21
|
export { buildSwapCallPlan, EXACT_INPUT_SINGLE_SELECTOR, } from "./swapPlan.js";
|
|
22
22
|
export { deriveForwardingNote, precommitmentForRelayer, recoverForwardingNotes, ZBASE_FORWARDING_PATH_PREFIX, } from "./forwardingNotes.js";
|
|
23
23
|
export { STEALTH_SCHEME_ID, DEFAULT_CHAIN_TAG, generateMetaAddress, parseMetaAddress, isStealthMetaAddress, deriveStealthAddress, scanForPayments, computeStealthPrivateKey, } from "./stealth.js";
|
package/dist/notes.js
CHANGED
|
@@ -29,7 +29,7 @@ import { xchacha20poly1305 } from "@noble/ciphers/chacha";
|
|
|
29
29
|
import { keccak_256 } from "@noble/hashes/sha3";
|
|
30
30
|
import { randomBytes } from "@noble/hashes/utils";
|
|
31
31
|
// Shared, unbiased (rejection-sampling) field sampler — see account.ts (audit F7).
|
|
32
|
-
import { randomFieldElement } from "./account.js";
|
|
32
|
+
import { randomFieldElement, SNARK_SCALAR_FIELD } from "./account.js";
|
|
33
33
|
// -----------------------------------------------------------------------------
|
|
34
34
|
// Note creation
|
|
35
35
|
// -----------------------------------------------------------------------------
|
|
@@ -290,7 +290,7 @@ export function serializeNote(n) {
|
|
|
290
290
|
}
|
|
291
291
|
export function deserializeNote(s) {
|
|
292
292
|
const o = JSON.parse(s);
|
|
293
|
-
|
|
293
|
+
const note = {
|
|
294
294
|
amount: BigInt(o.amount),
|
|
295
295
|
label: BigInt(o.label),
|
|
296
296
|
// Tolerate pre-v1 serialized notes (no NPK fields) by defaulting to 0 —
|
|
@@ -300,6 +300,26 @@ export function deserializeNote(s) {
|
|
|
300
300
|
nullifier: BigInt(o.nullifier),
|
|
301
301
|
secret: BigInt(o.secret),
|
|
302
302
|
};
|
|
303
|
+
// HIGH fix (SDK audit 2026-07-09): RANGE-VALIDATE on deserialize. A corrupted /
|
|
304
|
+
// malicious serialized note with an out-of-range amount (>= 2^64) or an
|
|
305
|
+
// out-of-field element silently produces a commitment the circuit can never
|
|
306
|
+
// accept → funds locked (or, unchecked, value-confusion). Reject early with a
|
|
307
|
+
// clear error instead of failing opaquely at proof time.
|
|
308
|
+
if (note.amount < 0n || note.amount >= MAX_NOTE_AMOUNT) {
|
|
309
|
+
throw new NoteValueError(`deserializeNote: amount out of range [0, 2^64): ${note.amount}`);
|
|
310
|
+
}
|
|
311
|
+
for (const [name, v] of [
|
|
312
|
+
["label", note.label],
|
|
313
|
+
["nullifier", note.nullifier],
|
|
314
|
+
["secret", note.secret],
|
|
315
|
+
["spendingPK", note.spendingPK],
|
|
316
|
+
["viewingPKBlind", note.viewingPKBlind],
|
|
317
|
+
]) {
|
|
318
|
+
if (v < 0n || v >= SNARK_SCALAR_FIELD) {
|
|
319
|
+
throw new NoteValueError(`deserializeNote: ${name} is not a valid field element (>= SNARK field): ${v}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return note;
|
|
303
323
|
}
|
|
304
324
|
// -----------------------------------------------------------------------------
|
|
305
325
|
// Viewing-key cryptography
|
package/dist/viewingKeyHD.js
CHANGED
|
@@ -135,8 +135,17 @@ export function deriveViewingKeyFromMnemonic(mnemonic, index = 0) {
|
|
|
135
135
|
* The mnemonic field on the returned ViewingKey will be `undefined`.
|
|
136
136
|
*/
|
|
137
137
|
export function deriveViewingKeyFromSeed(seed, index = 0) {
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
// HIGH fix (SDK audit 2026-07-09): require EXACTLY the 64-byte BIP39 seed
|
|
139
|
+
// (`mnemonicToSeedSync(mnemonic)`). The prior `>= 32` check let a caller pass
|
|
140
|
+
// 32-byte entropy (or any 32+ bytes) that derives a DIFFERENT viewing key than
|
|
141
|
+
// the mnemonic path — so a wallet "recovering" from the wrong-length seed scans
|
|
142
|
+
// the chain, finds none of its own notes, and concludes funds are lost. Only
|
|
143
|
+
// the 64-byte BIP39 seed matches deriveViewingKeyFromMnemonic. Enforce it.
|
|
144
|
+
if (seed.length !== 64) {
|
|
145
|
+
throw new Error(`deriveViewingKeyFromSeed: seed must be the 64-byte BIP39 seed (output of ` +
|
|
146
|
+
`mnemonicToSeedSync), got ${seed.length} bytes. Passing raw entropy or a ` +
|
|
147
|
+
`different length derives a DIFFERENT key than the mnemonic path and ` +
|
|
148
|
+
`breaks note recovery. Use deriveViewingKeyFromMnemonic if you have the phrase.`);
|
|
140
149
|
}
|
|
141
150
|
const derivationPath = pathFor(index);
|
|
142
151
|
const node = HDKey.fromMasterSeed(seed).derive(derivationPath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zbase-protocol/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Chain-agnostic ZK privacy core for x402 AI agent payments. Poseidon hashing, Merkle trees, Groth16 proof generation, account/secrets management. Used by @zbase-protocol/svm and @zbase-protocol/mcp.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|