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 合约通信 —— 钱包本身不需要后端服务器。