IvanAnishchuk/NozKash

GitHub: IvanAnishchuk/NozKash

NozKash 是一种基于 BLS 盲签名、无需零知识证明的 EVM 链隐私电子现金方案,以极低 gas 成本实现不可链接的代币存兑。

Stars: 1 | Forks: 1

# 👻 NozKash **aleph-hackathon-m2026** 默认测试网:**Ethereum Sepolia**(链 ID 11155111)。 [Simoneth Arianna Gomez](https://github.com/Simonethg)、[Fabio Laura](https://github.com/raptor0929)、[Ivan Anishchuk](https://github.com/IvanAnishchuk) **适用于 EVM 链的隐私保护 eCash —— 无需零知识证明。** nozkash 在 BN254 上使用 BLS 盲签名,以极低的 zk-SNARK 隐私协议 gas 成本提供不可链接的代币转账。用户存入固定面额,从 mint 处接收加密盲签名的代币,并将其兑换到任何地址 —— mint 永远不会知道哪笔存款对应哪笔兑换。 无需电路。无需可信设置。无需链下中继器基础设施。只需 EVM 已经理解的椭圆曲线数学。 ## 为什么选择 nozkash? 如今 EVM 上的隐私保护处于两个极端: | 方案 | 隐私性 | 信任假设 | Gas 成本 | 复杂度 | |----------|---------|-------|----------|------------| | **托管混币器** | 弱(运营者能看到所有信息) | 完全信任运营者 | 低 | 低 | | **zk-SNARK 池** | 强(零知识) | 无需信任 | 非常高(~1M+ gas) | 非常高(电路、可信设置、证明生成) | | **nozkash** | 强(盲签名) | 最低 — mint 仅进行盲签名 | **~50k gas 存款,~120k gas 兑换** | 低(标准 EVM 预编译) | nozkash 占据了一个实用的中间地带:**隐私性可与暗池媲美,成本可与代币转账相当,复杂度与多重签名相当。** ### 权衡取舍 nozkash 引入了一个 **mint** —— 一个对存款代币进行盲签名的链下签名者。该 mint: - ✅ **无法链接** 存款与兑换(盲因子 `r` 是秘密) - ✅ **无法伪造** 代币(BLS 签名在链上验证) - ✅ **无法窃取** 资金(兑换直接转入用户选择的地址) - ⚠️ **可以拒绝** 签名(活跃度依赖) - ⚠️ **可能串谋**,如果记录了时间元数据,观察者可借此去匿名化 这些信任假设**严格弱于**托管池(在托管池中,运营者完全控制资金),并且可以进一步最小化: - **门限盲签名** — 将 mint 分布在 N-of-M 签名者中,从而没有任何单一方能拒绝服务或关联存款 - **TEE 认证** — 在具有远程认证的可信执行环境中运行 mint,证明它不会记录元数据 - **多个独立 mint** — 用户选择使用哪个 mint,防止单点审查 在所有情况下,**验证完全通过 EVM `ecPairing` 预编译在链上执行** —— 在兑换时不需要任何信任。 ## 工作原理 ``` Client NozkVault (on-chain) Mint Server │ │ │ │ derive spend + blind keys │ │ │ Y = H(spendAddress) │ │ │ B = r · Y │ │ │ │ │ │── deposit(depositId, B) ─────▶│ │ │ + 0.001 ETH │── DepositLocked(id, B) ──▶│ │ │ │ S' = sk · B │ │◀── announce(id, S') ──────│ │ │ │ │ S = S' · r⁻¹ (unblind) │ │ │ verify e(S,G2)==e(Y,PK) │ │ │ │ │ │── redeem(dest, sig, null, S)─▶│ │ │ │ ecrecover → verify sig │ │ │ nullifier → double-spend │ │ │ ecPairing → BLS verify │ │ │── 0.001 ETH ─────────────▶ dest ``` **盲化:** 客户端计算 `B = r · H(spendAddress)`,其中 `r` 是一个秘密标量。mint 只能看到 `B` —— 它无法恢复支出地址或将其与任何未来的兑换联系起来。 **签名:** mint 在不知道自己签署了什么的情况下计算 `S' = sk · B`。客户端移除盲化:`S = S' · r⁻¹ = sk · H(spendAddress)`。 **验证:** 合约使用 EVM `ecPairing` 预编译 (0x08) 检查 `e(S, G2) == e(H(nullifier), PK_mint)`。这是一次单一的配对检查 —— 没有 SNARK 验证,没有 Groth16,没有电路编译。 **MEV 保护:** 兑换包含一个基于 `keccak256("Pay to RAW: " || recipient_address)` 的 ECDSA 签名。抢跑者(front-runner)如果没有支出私钥,就无法重定向资金。 **无状态恢复:** 所有秘密都由一个主种子 + 代币索引确定性推导得出。丢失设备后,可从种子恢复。 ## Gas 效率 nozkash 仅使用标准 EVM 预编译 —— 没有自定义验证器合约,没有庞大的证明调用数据。 | 操作 | Gas 成本 | 具体过程 | |-----------|----------|--------------| | `deposit()` | ~50,000 | 存储盲化点 + 触发事件 | | `announce()` | ~55,000 | mint 发布盲签名 | | `redeem()` | ~120,000 | ecrecover + ecPairing + ETH 转账 | 作为比较,由于链上证明验证,zk-SNARK 隐私池通常每次操作需要 500k-1.5M gas。nozkash 的兑换成本低于一次 Uniswap 兑换。 ## 前置条件 | 工具 | 版本 | 用途 | |------|---------|---------| | Python | 3.13+ | 库、mint 服务器、CLI 钱包 | | Node.js | 20+ | TypeScript 库、CLI 客户端、测试套件 | | [uv](https://docs.astral.sh/uv/) | 最新版 | Python 包管理 | | npm | 随 Node 一起捆绑 | TypeScript 包管理 | | [Foundry](https://book.getfoundry.sh/) | 最新版 | Solidity 测试和部署 | ## 快速开始 ``` # 安装依赖 cd nozk_py && uv venv && uv sync # Python cd nozk_ts && npm install # TypeScript (viem, mcl-wasm, @noble/curves, etc.) # 生成密钥和 .env cd nozk_py && uv run generate_keys.py # 派生 BLS 公钥并添加至 .env cd nozk_py && uv run derive_bls.py 0x # 运行测试 cd nozk_py && uv run pytest -v # Python unit + vector tests cd nozk_ts && npx vitest run # TypeScript vector parity tests cd sol && forge test # Solidity contract tests (forks Sepolia) # 生成跨语言测试向量 cd nozk_py && uv run generate_vectors.py ``` ## 仓库布局 ``` ├── README.md # This file ├── LICENSE.md # CC0 1.0 — public domain dedication ├── example.env # Template for .env configuration ├── nozk_flow.sh # Full lifecycle runner script │ ├── nozk_py/ # Python: crypto library, mint, CLI wallet │ ├── nozk_library.py # Cryptographic library (source of truth) │ ├── client.py # CLI wallet (deposit/scan/redeem/status/balance) │ ├── mint_server.py # Production mint daemon (WebSocket) │ ├── mint_mock.py # Offline mock mint for testing │ ├── redeem_mock.py # Offline mock redeemer for testing │ ├── contract_errors.py # Decodes NozkVault revert selectors │ ├── generate_keys.py # Keypair + .env generator │ ├── generate_vectors.py # Cross-language test vector generator │ ├── derive_bls.py # BLS pubkey derivation tool │ ├── nozk_library_test.py # Python unit tests │ ├── test_vectors.py # Python parametrized vector tests │ ├── nozk_tip_test.py # Python end-to-end smoke test │ ├── test_vectors/ # Generated vector files (JSON) │ ├── pyproject.toml # Python dependencies │ └── README.md # Python-specific documentation │ ├── nozk_ts/ # TypeScript: crypto library, CLI client, tests │ ├── nozk-library.ts # TypeScript crypto port (byte-for-byte parity) │ ├── bn254-crypto.ts # Low-level BN254 primitives (mcl-wasm) │ ├── client.ts # TypeScript CLI wallet (deposit/scan/redeem/balance) │ ├── test-vectors.test.ts # TypeScript parametrized vector tests │ ├── test.ts # TypeScript end-to-end smoke test │ ├── package.json # Node dependencies │ └── tsconfig.json # TypeScript config │ ├── sol/ # Solidity: smart contract + Foundry project │ ├── src/ │ │ └── NozkVault.sol # Solidity smart contract │ ├── test/ │ │ └── NozkVault.t.sol # Foundry test suite (forks Sepolia) │ ├── script/ │ │ └── NozkVault.s.sol # Deployment script │ ├── scripts/ │ │ ├── generate_vectors.py # Vector generator for Solidity tests │ │ ├── nozk_library.py # Standalone copy for sol/scripts │ │ └── forge_test_generated_vectors.sh │ ├── foundry.toml # Foundry configuration │ ├── lib/forge-std/ # Forge standard library (git submodule) │ └── README.md # Solidity-specific documentation │ └── app/ # Frontend: React wallet UI ├── src/ │ ├── crypto/ # Browser-bundled BN254 + nozk-library │ ├── components/ # React components (Layout, DepositConfirmModal, Splash) │ ├── context/ # NozkMasterSeedProvider, PrivacyProvider │ ├── hooks/ # useWallet, useRedeemSign │ ├── lib/ # nozkVault scanner, RPC helpers, ethereum utils │ ├── pages/ # Dashboard, Deposit, Redeem, Recovery │ └── styles/ # enozkash.css (full custom theme) └── ... ``` ## 智能合约 NozkVault 合约 (`sol/src/NozkVault.sol`) 仅使用标准 EVM 预编译来处理完整的代币生命周期: | 函数 | 描述 | |----------|-------------| | `deposit(address depositId, uint256[2] B)` | 使用一个盲化的 G1 点锁定 0.001 ETH | | `announce(address depositId, uint256[2] S')` | mint 发布盲签名(仅限授权调用者) | | `redeem(address recipient, bytes sig, address nullifier, uint256[2] S)` | 验证 BLS + ECDSA,转移 ETH | 链上验证: 1. **ecrecover** — 从 ECDSA 签名中恢复签名者,并对照 nullifier 进行验证 2. **Nullifier 检查** — 通过 `spentNullifiers` 映射防止双花 3. **哈希到曲线** — `keccak256(nullifier || counter)` 采用尝试并递增(try-and-increment)方式映射到 BN254 G1 4. **ecPairing** — 在单次预编译调用中验证 `e(S, G2) == e(H(nullifier), PK_mint)` 自定义错误:`InvalidValue`、`InvalidECDSA`、`AlreadySpent`、`InvalidBLS`、`InvalidSignatureLength`、`EthSendFailed`、`HashToCurveFailed`、`NotMintAuthority`、`DepositNotFound`、`DepositIdAlreadyUsed`、`AlreadyFulfilled`、`InvalidDepositId`。 ## CLI 钱包 Python 和 TypeScript 客户端均实现了相同的功能,共享相同的钱包状态文件 (`.nozk_wallet.json`),并使用相同的合约 ABI。 ### Python ``` cd nozk_py uv run client.py deposit --index 0 # Lock 0.001 ETH uv run client.py scan # Recover signed tokens (incremental) uv run client.py redeem --index 0 --to 0xAddr # Redeem to any address uv run client.py status # Token lifecycle overview uv run client.py balance # On-chain ETH balance ``` 附加标志:`--mock`(完全离线)、`--dry-run`(通过 RPC 模拟)、`--verbosity verbose|debug|quiet`、`--relayer `(无 gas 兑换)。 ### TypeScript ``` cd nozk_ts npx tsx client.ts deposit --index 0 npx tsx client.ts scan npx tsx client.ts redeem --index 0 --to 0xAddr npx tsx client.ts balance ``` 从 RPC 自动检测链 ID —— 适用于任何 EVM 链。 ### 代币生命周期 ``` FRESH → AWAITING_MINT → READY_TO_REDEEM → SPENT ``` 扫描是增量的(从上次的区块恢复)并跳过具有缓存签名的代币。两个客户端在提交到链上之前都会在本地验证 `e(S, G2) == e(Y, PK_mint)` —— 提前捕获密钥不匹配的情况并节省 gas。 ## Mint 服务器 无状态异步守护进程。通过 WebSocket 连接,监听 `DepositLocked` 事件,进行盲签名,并调用 `announce()`。 ``` cd nozk_py uv run mint_server.py uv run mint_server.py --verbosity verbose # Intermediate values uv run mint_server.py --verbosity debug # Raw event data ``` mint 在签名之前会验证 G1 点 —— 非曲线上的输入将被拒绝,不会浪费 gas。 ## 环境变量 | 变量 | 使用者 | 描述 | |----------|---------|-------------| | `MASTER_SEED` | 客户端 | 十六进制种子 — 所有钱包秘密均由此推导 | | `MINT_BLS_PRIVKEY` | mint, 客户端 | 十六进制 BLS 标量 | | `MINT_BLS_PUBKEY` | 客户端 | 用于本地验证的 G2 公钥(4 个十六进制 uint256,EIP-197 顺序) | | `CONTRACT_ADDRESS` | 全部 | 已部署的 NozkVault 地址 | | `WALLET_ADDRESS` / `WALLET_KEY` | 客户端 | 支付 gas 的钱包 | | `MINT_WALLET_ADDRESS` / `MINT_WALLET_KEY` | mint | mint 用于支付 gas 的钱包 | | `RPC_HTTP_URL` | 客户端 | HTTP RPC 端点 | | `RPC_WS_URL` | mint | WebSocket RPC 端点 | | `SCAN_FROM_BLOCK` | 客户端 | 事件扫描的起始区块 | ## 跨语言一致性 Python 库 (`nozk_py/nozk_library.py`) 是密码学的真实来源。TypeScript 移植版 (`nozk_ts/nozk-library.ts` + `nozk_ts/bn254-crypto.ts`) 在每次操作中都会产生逐字节相同的输出。 两种语言都使用: - 相同的哈希到曲线算法(使用 `keccak256(msg || counter_be32)` 的 try-and-increment 方法) - 相同的代币推导算法(`keccak256(seed || index_be32)` → 域分离密钥对) - 相同的消息格式(`"Pay to RAW: " || raw_20_byte_address`) - 标准的 BN254 G2 生成元(EIP-197 / `py_ecc.bn128.G2`) 一致性通过共享测试向量强制保证: ``` cd nozk_py && uv run generate_vectors.py # Generate (Python) cd nozk_py && uv run pytest test_vectors.py -v # Verify (Python) cd nozk_ts && npx vitest run # Verify (TypeScript) ``` 每个向量测试:G2 密钥推导、秘密推导、哈希到曲线、盲化、盲签名、去盲化、ECDSA 证明和完整的 BLS 配对。 ## 密码学设计 **曲线:** BN254 (`alt_bn128`) —— 唯一具有原生 EVM 预编译支持(`ecAdd` 0x06、`ecMul` 0x07、`ecPairing` 0x08)的配对友好曲线。ECDSA 通过 `ecrecover` 使用 secp256k1。 **哈希到曲线:** 对 `keccak256(address_20_bytes || counter_be32)` 采用 try-and-increment。通过 `y = rhs^((p+1)/4) mod p` 计算平方根(因为 `p ≡ 3 mod 4` 所以有效)。 **盲签名方案:** 在 BN254 标量域中的乘法盲化。代数恒等式 `S = S'·r⁻¹ = sk·r·Y·r⁻¹ = sk·Y` 确保配对等式成立,而 mint 永远不会看到 `Y`。 **代币索引编码:** 4 字节大端序(`DataView.setUint32` / `int.to_bytes(4, 'big')`)。不使用 `Uint8Array` 构造函数模式,因为它会静默截断 ≥ 256 的值。 **Nullifier 设计:** 支出地址(由支出密钥对推导)用作 nullifier。它被显式传递给 `redeem()` 并与 `spentNullifiers` 进行比对以防止双花。ECDSA 签名将 nullifier 绑定到特定接收者。 **G2 公钥格式:** EIP-197 段顺序为 `[X_imag, X_real, Y_imag, Y_real]`。`py_ecc` 的内部顺序是 `FQ2([real, imag])` —— 所有转换代码都正确处理了这一点。 ## 测试 ``` # Python 单元测试 cd nozk_py && uv run pytest nozk_library_test.py -v # 跨语言向量测试 cd nozk_py && uv run pytest test_vectors.py -v # Python cd nozk_ts && npx vitest run # TypeScript # Solidity 合约测试 (分叉 Ethereum Sepolia) cd sol && forge test # 端到端冒烟测试 cd nozk_py && uv run nozk_tip_test.py # Python (or --mock for full offline flow) cd nozk_ts && npx tsx test.ts # TypeScript # 完整生命周期 (链上或模拟) ./nozk_flow.sh --to 0xRecipient # On-chain ./nozk_flow.sh --to 0xRecipient --mock # Offline ./nozk_flow.sh --to 0xRecipient --dry-run # Simulate ``` ## 前端应用 NozKash 在 `app/` 目录中附带了一个移动优先的 React 钱包 UI。它连接到 MetaMask,在客户端推导金库秘密,并直接与已部署的 NozkVault 合约通信 —— 钱包本身不需要后端服务器。