@zbase-protocol/core 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.
@@ -0,0 +1,172 @@
1
+ /**
2
+ * @zbase-protocol/core — Chain-agnostic types for ZK privacy payments
3
+ */
4
+ export interface AccountSecrets {
5
+ nullifier: string;
6
+ secret: string;
7
+ commitment: string;
8
+ value: string;
9
+ label: string;
10
+ }
11
+ export interface ZX402AccountData {
12
+ secrets: AccountSecrets;
13
+ depositTxHash?: string;
14
+ chain: ChainType;
15
+ withdrawn: boolean;
16
+ createdAt: number;
17
+ }
18
+ export type ChainType = "evm" | "svm";
19
+ export interface DepositResult {
20
+ txHash: string;
21
+ commitment: string;
22
+ label: string;
23
+ value: string;
24
+ secrets: AccountSecrets;
25
+ chain: ChainType;
26
+ }
27
+ export interface WithdrawResult {
28
+ txHash: string;
29
+ recipient: string;
30
+ amount: string;
31
+ remainingValue?: string;
32
+ nextDeposit?: AccountSecrets;
33
+ proofTimeMs: number;
34
+ chain: ChainType;
35
+ }
36
+ export interface PayResult extends WithdrawResult {
37
+ provider: string;
38
+ }
39
+ export interface PoolStats {
40
+ depositsCount: number;
41
+ totalCommittedValue: string;
42
+ yieldEarned: string;
43
+ anonymitySetSize: number;
44
+ chain: ChainType;
45
+ }
46
+ export interface ZX402Pool {
47
+ readonly chain: ChainType;
48
+ /** Deposit tokens into the privacy pool */
49
+ deposit(amount: string): Promise<DepositResult>;
50
+ /** Withdraw from pool to recipient via ZK proof */
51
+ withdraw(secrets: AccountSecrets, recipient: string, amountAtomic?: string): Promise<WithdrawResult>;
52
+ /** Pay a provider privately via ZK proof */
53
+ pay(secrets: AccountSecrets, provider: string, amount?: string): Promise<PayResult>;
54
+ /** Get pool statistics */
55
+ getPoolStats(): Promise<PoolStats>;
56
+ }
57
+ export interface VerifyRequest {
58
+ paymentDetails: {
59
+ scheme: string;
60
+ payTo: string;
61
+ maxAmountRequired: string;
62
+ networkId: string;
63
+ };
64
+ deposit: AccountSecrets;
65
+ agentId?: string;
66
+ }
67
+ export interface VerifyResponse {
68
+ valid: boolean;
69
+ reason?: string;
70
+ anonymitySetSize?: number;
71
+ agent?: AgentInfo;
72
+ }
73
+ export interface SettleRequest {
74
+ paymentDetails: {
75
+ payTo: string;
76
+ maxAmountRequired?: string;
77
+ tokenMint?: string;
78
+ };
79
+ deposit: AccountSecrets;
80
+ agentId?: string;
81
+ }
82
+ export interface SettleResponse {
83
+ settled: boolean;
84
+ txHash?: string;
85
+ network: string;
86
+ amount?: string;
87
+ remainingValue?: string;
88
+ nextDeposit?: AccountSecrets;
89
+ proofTimeMs?: number;
90
+ agent?: AgentInfo;
91
+ }
92
+ export interface ZX402Facilitator {
93
+ readonly chain: ChainType;
94
+ /** Verify a payment can be settled */
95
+ verify(request: VerifyRequest): Promise<VerifyResponse>;
96
+ /** Generate ZK proof and settle payment */
97
+ settle(request: SettleRequest): Promise<SettleResponse>;
98
+ /** Get supported capabilities */
99
+ getSupported(): SupportedCapabilities;
100
+ }
101
+ export interface AgentPermissions {
102
+ maxSpendPerTx: string;
103
+ maxSpendPerDay: string;
104
+ allowedProviders: string[];
105
+ allowedCategories: string[];
106
+ }
107
+ export interface AgentInfo {
108
+ id: string;
109
+ name: string;
110
+ owner: string;
111
+ publicKey?: string;
112
+ hotKey?: string;
113
+ coldKey?: string;
114
+ permissions: AgentPermissions;
115
+ totalSpent: string;
116
+ dailySpent: string;
117
+ txCount: number;
118
+ registeredAt: number;
119
+ }
120
+ export interface RegisterAgentRequest {
121
+ name: string;
122
+ owner: string;
123
+ publicKey?: string;
124
+ hotKey?: string;
125
+ coldKey?: string;
126
+ permissions?: Partial<AgentPermissions>;
127
+ }
128
+ export interface ExposureReport {
129
+ address: string;
130
+ totalTransfers: number;
131
+ x402PaymentsDetected: number;
132
+ servicesExposed: string[];
133
+ totalSpendVisible: string;
134
+ riskScore: number;
135
+ riskLevel: "HIGH" | "MEDIUM" | "LOW";
136
+ providerBreakdown: Array<{
137
+ provider: string;
138
+ payments: number;
139
+ totalAmount: string;
140
+ }>;
141
+ recommendations: string[];
142
+ chain: ChainType;
143
+ }
144
+ export interface SupportedCapabilities {
145
+ facilitator: string;
146
+ version: string;
147
+ chain: ChainType;
148
+ networks: string[];
149
+ /**
150
+ * `false` while the cryptographic core (Poseidon, Merkle, on-chain Groth16,
151
+ * ASP enforcement, recipient binding) is not yet implemented for this chain.
152
+ * Consumers MUST treat `privacy` and `compliance` as advisory until this is
153
+ * `true`. See STATUS.md for the per-chain status matrix.
154
+ */
155
+ ready: boolean;
156
+ privacy: {
157
+ method: string;
158
+ compliance: string;
159
+ /** True only when on-chain proof verification is live. */
160
+ enforced: boolean;
161
+ };
162
+ yield: {
163
+ enabled: boolean;
164
+ protocol: string;
165
+ estimatedAPY: string;
166
+ };
167
+ agentRegistry: boolean;
168
+ encryptedMessaging: boolean;
169
+ bulkRelay: boolean;
170
+ /** Human-readable note describing any non-ready capabilities. */
171
+ disclosure?: string;
172
+ }
package/dist/types.js ADDED
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @zbase-protocol/core — Chain-agnostic types for ZK privacy payments
3
+ */
4
+ export {};
@@ -0,0 +1,108 @@
1
+ /**
2
+ * @zbase-protocol/core — BIP32 HD derivation for viewing keys (Phase 1B)
3
+ *
4
+ * Replaces the per-session random viewing keys produced by
5
+ * `generateViewingKey()` in `notes.ts` with a deterministic HD scheme rooted
6
+ * in a BIP39 mnemonic. The mnemonic IS the recovery secret: if a user clears
7
+ * localStorage but kept the 12/24-word phrase, they can re-derive every
8
+ * viewing key and rescan the chain to rebuild their note set.
9
+ *
10
+ * Derivation path: m/44'/60'/0'/0'/<index>
11
+ * - 44' BIP44
12
+ * - 60' coin_type = Ethereum (matches the on-chain UTXOPool domain)
13
+ * - 0' account
14
+ * - 0 external chain
15
+ * - i index (0 for primary viewing key; supports per-app sub-keys later)
16
+ *
17
+ * Output: an X25519 keypair compatible with `encryptNote` / `decryptNote` in
18
+ * notes.ts. The X25519 private key is taken as the SHA-512 hash of the BIP32
19
+ * private key (NOT the BIP32 key itself — different curves; X25519 clamps
20
+ * scalars). The first 32 bytes of the SHA-512 are used; the remaining 32 are
21
+ * discarded. This matches the X25519 key-generation recipe in RFC 7748 §5
22
+ * which mandates clamping but accepts any uniformly-distributed 32-byte seed.
23
+ *
24
+ * NOTE: `@scure/bip32` and `@scure/bip39` are added as direct dependencies of
25
+ * @zbase-protocol/core in Phase 1B because they are needed for recovery.
26
+ */
27
+ import type { ViewingKeyPair } from "./notes.js";
28
+ /**
29
+ * BIP44 path prefix used by zBase viewing keys. The trailing index is
30
+ * appended by the derivation helpers below.
31
+ *
32
+ * m / 44' / 60' / 0' / 0' / index
33
+ *
34
+ * The fourth segment is hardened (`0'`) — diverging from the canonical
35
+ * Ethereum address path (`m/44'/60'/0'/0/index`) — so a leaked viewing key
36
+ * (which is intentionally read-only) cannot be combined with a sibling
37
+ * Ethereum address public key to attempt key recovery.
38
+ */
39
+ export declare const ZBASE_VIEWING_KEY_PATH_PREFIX = "m/44'/60'/0'/0'";
40
+ /**
41
+ * Default mnemonic strength: 128 bits = 12 words. Matches MetaMask /
42
+ * Rabby / most consumer wallets. 256-bit (24-word) generation is supported
43
+ * via `generateNewMnemonic(256)`.
44
+ */
45
+ export declare const DEFAULT_MNEMONIC_STRENGTH_BITS = 128;
46
+ /**
47
+ * HD-derived viewing key. Adds `mnemonic` + `derivationPath` provenance on top
48
+ * of the bare X25519 keypair so the wallet UI can show the user which path
49
+ * a key came from, and so the recovery flow can confirm the mnemonic is the
50
+ * one that generated a given on-chain key before re-importing.
51
+ */
52
+ export interface ViewingKey extends ViewingKeyPair {
53
+ /** Mnemonic that generated this key. Undefined for seed-derived keys. */
54
+ mnemonic?: string;
55
+ /** Full BIP32 path, e.g., "m/44'/60'/0'/0'/0". */
56
+ derivationPath: string;
57
+ /** Index appended to the path prefix. */
58
+ index: number;
59
+ }
60
+ /**
61
+ * Generate a fresh BIP39 mnemonic suitable for viewing-key derivation.
62
+ *
63
+ * `strengthBits` must be a multiple of 32 in [128, 256]:
64
+ * 128 → 12 words (default)
65
+ * 160 → 15 words
66
+ * 192 → 18 words
67
+ * 224 → 21 words
68
+ * 256 → 24 words
69
+ */
70
+ export declare function generateNewMnemonic(strengthBits?: number): string;
71
+ /**
72
+ * Derive a viewing key from a BIP39 mnemonic at the given index.
73
+ *
74
+ * The same mnemonic + same index always produces the same viewing key, which
75
+ * is exactly what makes recovery possible: re-running this with the user's
76
+ * 12 words reproduces every viewing-key the wallet has ever used.
77
+ *
78
+ * @throws if `mnemonic` does not validate against the BIP39 English wordlist.
79
+ */
80
+ export declare function deriveViewingKeyFromMnemonic(mnemonic: string, index?: number): ViewingKey;
81
+ /**
82
+ * Derive a viewing key from a raw seed (32+ bytes, typically the 64-byte
83
+ * BIP39 seed but any high-entropy bytes will do). Useful when the wallet has
84
+ * already stretched the mnemonic with a passphrase and wants to keep the
85
+ * passphrase out of the call site.
86
+ *
87
+ * The mnemonic field on the returned ViewingKey will be `undefined`.
88
+ */
89
+ export declare function deriveViewingKeyFromSeed(seed: Uint8Array, index?: number): ViewingKey;
90
+ /**
91
+ * Return the mnemonic that produced the given viewing key. Returns
92
+ * `undefined` for seed-derived keys (the seed-derivation path is one-way).
93
+ *
94
+ * UI flow: present this to the user during "Show recovery phrase". Never log
95
+ * it; never persist it beyond the wallet's seed-vault component.
96
+ */
97
+ export declare function exportMnemonic(viewingKey: ViewingKey): string | undefined;
98
+ /**
99
+ * Round-trip a raw entropy buffer (16 / 20 / 24 / 28 / 32 bytes) to its BIP39
100
+ * mnemonic. Useful for tests and for wallets that want to derive the mnemonic
101
+ * from a hardware-generated entropy source.
102
+ */
103
+ export declare function mnemonicFromEntropy(entropy: Uint8Array): string;
104
+ /**
105
+ * Validate a BIP39 mnemonic against the English wordlist. Returns false for
106
+ * checksum failures, non-wordlist words, and wrong word counts.
107
+ */
108
+ export declare function isValidMnemonic(mnemonic: string): boolean;
@@ -0,0 +1,176 @@
1
+ /**
2
+ * @zbase-protocol/core — BIP32 HD derivation for viewing keys (Phase 1B)
3
+ *
4
+ * Replaces the per-session random viewing keys produced by
5
+ * `generateViewingKey()` in `notes.ts` with a deterministic HD scheme rooted
6
+ * in a BIP39 mnemonic. The mnemonic IS the recovery secret: if a user clears
7
+ * localStorage but kept the 12/24-word phrase, they can re-derive every
8
+ * viewing key and rescan the chain to rebuild their note set.
9
+ *
10
+ * Derivation path: m/44'/60'/0'/0'/<index>
11
+ * - 44' BIP44
12
+ * - 60' coin_type = Ethereum (matches the on-chain UTXOPool domain)
13
+ * - 0' account
14
+ * - 0 external chain
15
+ * - i index (0 for primary viewing key; supports per-app sub-keys later)
16
+ *
17
+ * Output: an X25519 keypair compatible with `encryptNote` / `decryptNote` in
18
+ * notes.ts. The X25519 private key is taken as the SHA-512 hash of the BIP32
19
+ * private key (NOT the BIP32 key itself — different curves; X25519 clamps
20
+ * scalars). The first 32 bytes of the SHA-512 are used; the remaining 32 are
21
+ * discarded. This matches the X25519 key-generation recipe in RFC 7748 §5
22
+ * which mandates clamping but accepts any uniformly-distributed 32-byte seed.
23
+ *
24
+ * NOTE: `@scure/bip32` and `@scure/bip39` are added as direct dependencies of
25
+ * @zbase-protocol/core in Phase 1B because they are needed for recovery.
26
+ */
27
+ import { HDKey } from "@scure/bip32";
28
+ import { generateMnemonic, mnemonicToSeedSync, validateMnemonic, entropyToMnemonic, } from "@scure/bip39";
29
+ import { wordlist } from "@scure/bip39/wordlists/english";
30
+ import { x25519 } from "@noble/curves/ed25519";
31
+ import { sha512 } from "@noble/hashes/sha2";
32
+ // -----------------------------------------------------------------------------
33
+ // Constants
34
+ // -----------------------------------------------------------------------------
35
+ /**
36
+ * BIP44 path prefix used by zBase viewing keys. The trailing index is
37
+ * appended by the derivation helpers below.
38
+ *
39
+ * m / 44' / 60' / 0' / 0' / index
40
+ *
41
+ * The fourth segment is hardened (`0'`) — diverging from the canonical
42
+ * Ethereum address path (`m/44'/60'/0'/0/index`) — so a leaked viewing key
43
+ * (which is intentionally read-only) cannot be combined with a sibling
44
+ * Ethereum address public key to attempt key recovery.
45
+ */
46
+ export const ZBASE_VIEWING_KEY_PATH_PREFIX = "m/44'/60'/0'/0'";
47
+ /**
48
+ * Default mnemonic strength: 128 bits = 12 words. Matches MetaMask /
49
+ * Rabby / most consumer wallets. 256-bit (24-word) generation is supported
50
+ * via `generateNewMnemonic(256)`.
51
+ */
52
+ export const DEFAULT_MNEMONIC_STRENGTH_BITS = 128;
53
+ // -----------------------------------------------------------------------------
54
+ // Generation
55
+ // -----------------------------------------------------------------------------
56
+ /**
57
+ * Generate a fresh BIP39 mnemonic suitable for viewing-key derivation.
58
+ *
59
+ * `strengthBits` must be a multiple of 32 in [128, 256]:
60
+ * 128 → 12 words (default)
61
+ * 160 → 15 words
62
+ * 192 → 18 words
63
+ * 224 → 21 words
64
+ * 256 → 24 words
65
+ */
66
+ export function generateNewMnemonic(strengthBits = DEFAULT_MNEMONIC_STRENGTH_BITS) {
67
+ return generateMnemonic(wordlist, strengthBits);
68
+ }
69
+ // -----------------------------------------------------------------------------
70
+ // Derivation
71
+ // -----------------------------------------------------------------------------
72
+ function pathFor(index) {
73
+ if (!Number.isInteger(index) || index < 0) {
74
+ throw new Error(`deriveViewingKey: index must be a non-negative integer, got ${index}`);
75
+ }
76
+ return `${ZBASE_VIEWING_KEY_PATH_PREFIX}/${index}`;
77
+ }
78
+ /**
79
+ * Convert a 32-byte BIP32 private key into an X25519 keypair.
80
+ *
81
+ * The BIP32 key is on the secp256k1 curve; X25519 expects a 32-byte scalar
82
+ * that will be clamped to Curve25519. We hash the BIP32 key with SHA-512 and
83
+ * take the first 32 bytes — this preserves uniform distribution and never
84
+ * exposes the secp256k1 scalar directly (so the same mnemonic can sign EVM
85
+ * txs *and* serve as a viewing-key source without one compromising the other).
86
+ */
87
+ /**
88
+ * Domain-separation tag (audit F10). Prefixing the SHA-512 input ensures this
89
+ * X25519 viewing-key derivation can never collide with any other SHA-512 use of
90
+ * the same BIP32 key — an attacker who learns one derived value cannot re-hash
91
+ * to confirm or correlate it without also knowing this exact label.
92
+ *
93
+ * NOTE: changing this tag changes ALL derived viewing keys. Safe to set now
94
+ * (UTXO/viewing-key path is pre-mainnet, no production keys exist yet); MUST be
95
+ * frozen before any mainnet viewing key is derived, or recovery breaks.
96
+ */
97
+ const X25519_VIEWING_KEY_DST = new TextEncoder().encode("zBase/viewing-key/x25519/v1");
98
+ function bip32PrivToX25519(bip32PrivateKey) {
99
+ const domainSeparated = new Uint8Array(X25519_VIEWING_KEY_DST.length + bip32PrivateKey.length);
100
+ domainSeparated.set(X25519_VIEWING_KEY_DST, 0);
101
+ domainSeparated.set(bip32PrivateKey, X25519_VIEWING_KEY_DST.length);
102
+ const seed = sha512(domainSeparated).slice(0, 32);
103
+ const privateKey = new Uint8Array(seed); // copy so we own the bytes
104
+ const publicKey = x25519.getPublicKey(privateKey);
105
+ return { privateKey, publicKey };
106
+ }
107
+ /**
108
+ * Derive a viewing key from a BIP39 mnemonic at the given index.
109
+ *
110
+ * The same mnemonic + same index always produces the same viewing key, which
111
+ * is exactly what makes recovery possible: re-running this with the user's
112
+ * 12 words reproduces every viewing-key the wallet has ever used.
113
+ *
114
+ * @throws if `mnemonic` does not validate against the BIP39 English wordlist.
115
+ */
116
+ export function deriveViewingKeyFromMnemonic(mnemonic, index = 0) {
117
+ if (!validateMnemonic(mnemonic, wordlist)) {
118
+ throw new Error("deriveViewingKeyFromMnemonic: invalid BIP39 mnemonic");
119
+ }
120
+ const seed = mnemonicToSeedSync(mnemonic);
121
+ const derivationPath = pathFor(index);
122
+ const node = HDKey.fromMasterSeed(seed).derive(derivationPath);
123
+ if (!node.privateKey) {
124
+ throw new Error(`deriveViewingKeyFromMnemonic: HDKey at ${derivationPath} has no private key`);
125
+ }
126
+ const { privateKey, publicKey } = bip32PrivToX25519(node.privateKey);
127
+ return { privateKey, publicKey, mnemonic, derivationPath, index };
128
+ }
129
+ /**
130
+ * Derive a viewing key from a raw seed (32+ bytes, typically the 64-byte
131
+ * BIP39 seed but any high-entropy bytes will do). Useful when the wallet has
132
+ * already stretched the mnemonic with a passphrase and wants to keep the
133
+ * passphrase out of the call site.
134
+ *
135
+ * The mnemonic field on the returned ViewingKey will be `undefined`.
136
+ */
137
+ export function deriveViewingKeyFromSeed(seed, index = 0) {
138
+ if (seed.length < 32) {
139
+ throw new Error(`deriveViewingKeyFromSeed: seed must be ≥ 32 bytes, got ${seed.length}`);
140
+ }
141
+ const derivationPath = pathFor(index);
142
+ const node = HDKey.fromMasterSeed(seed).derive(derivationPath);
143
+ if (!node.privateKey) {
144
+ throw new Error(`deriveViewingKeyFromSeed: HDKey at ${derivationPath} has no private key`);
145
+ }
146
+ const { privateKey, publicKey } = bip32PrivToX25519(node.privateKey);
147
+ return { privateKey, publicKey, derivationPath, index };
148
+ }
149
+ // -----------------------------------------------------------------------------
150
+ // Recovery helpers
151
+ // -----------------------------------------------------------------------------
152
+ /**
153
+ * Return the mnemonic that produced the given viewing key. Returns
154
+ * `undefined` for seed-derived keys (the seed-derivation path is one-way).
155
+ *
156
+ * UI flow: present this to the user during "Show recovery phrase". Never log
157
+ * it; never persist it beyond the wallet's seed-vault component.
158
+ */
159
+ export function exportMnemonic(viewingKey) {
160
+ return viewingKey.mnemonic;
161
+ }
162
+ /**
163
+ * Round-trip a raw entropy buffer (16 / 20 / 24 / 28 / 32 bytes) to its BIP39
164
+ * mnemonic. Useful for tests and for wallets that want to derive the mnemonic
165
+ * from a hardware-generated entropy source.
166
+ */
167
+ export function mnemonicFromEntropy(entropy) {
168
+ return entropyToMnemonic(entropy, wordlist);
169
+ }
170
+ /**
171
+ * Validate a BIP39 mnemonic against the English wordlist. Returns false for
172
+ * checksum failures, non-wordlist words, and wrong word counts.
173
+ */
174
+ export function isValidMnemonic(mnemonic) {
175
+ return validateMnemonic(mnemonic, wordlist);
176
+ }
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@zbase-protocol/core",
3
+ "version": "0.1.0",
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
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./experimental": {
15
+ "types": "./dist/experimental.d.ts",
16
+ "import": "./dist/experimental.js",
17
+ "default": "./dist/experimental.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "LICENSE",
23
+ "NOTICE"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsc",
27
+ "dev": "tsc --watch",
28
+ "prepublishOnly": "rm -rf dist && tsc"
29
+ },
30
+ "dependencies": {
31
+ "@noble/ciphers": "^1.0.0",
32
+ "@noble/curves": "^1.9.0",
33
+ "@noble/hashes": "^1.3.0",
34
+ "@scure/bip32": "^1.7.0",
35
+ "@scure/bip39": "^1.6.0",
36
+ "@zk-kit/lean-imt": "2.2.3",
37
+ "poseidon-lite": "^0.3.0"
38
+ },
39
+ "optionalDependencies": {
40
+ "snarkjs": "^0.7.6"
41
+ },
42
+ "devDependencies": {
43
+ "typescript": "^5"
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/goheesheng/zBase",
51
+ "directory": "packages/core"
52
+ },
53
+ "homepage": "https://zbase.app",
54
+ "bugs": {
55
+ "url": "https://github.com/goheesheng/zBase/issues"
56
+ },
57
+ "keywords": [
58
+ "zk",
59
+ "zero-knowledge",
60
+ "privacy",
61
+ "x402",
62
+ "agents",
63
+ "ai-agents",
64
+ "base",
65
+ "solana",
66
+ "groth16",
67
+ "poseidon",
68
+ "privacy-pools"
69
+ ],
70
+ "license": "Apache-2.0"
71
+ }