引言
兄弟们,还记得我们在上一篇文章里,硬核实现的 EVM 链上 Zcash 隐私存证智能合约吗?
在 Solidity 侧,我们已经成功通过了单元测试,证明了链上存取
bytes密文、权限控制和合规事件触发完全行得通。 “铁皮保险箱”我们已经在链上造好了!但此时,整个架构还差最后、也是最关键的两个致命痛点:
- 密文从哪来? 总不能在前端裸奔传输明文到节点吧?如果数据在离开浏览器那一刻没有被死死锁住,智能合约写得再安全也是白搭!
- 审计方怎么看? 链上存的是一串乱码
bytes。审计方需要合规审查时,绝不可能在链上写合约去解密(因为 Gas 费能让你破产,且链上解密等于把明文公开给全网)。他们必须在链下、零 Gas 代价地还原明文。今天这篇,就是整个 Zcash 隐私复刻项目的 “最后一块拼图”——前端纯离线密码学与合规查看权验证!
不用动用复杂的 ZK 电路,不需要任何链上交互,我们用纯前端、不到 100 行底层 JavaScript 代码,直接在客户端构筑“密文兵工厂”,产出完美无缝对齐上一篇 Solidity 合约
bytes接收规范的 Hex 密文!
一、核心黑科技:链下重度计算,链上零感知存证
为什么说这套前端验证方案硬核?因为它完美践行了 “Zero-Trust(零信任)” 架构:核心资产明文和审计私钥,在整个生命周期里从未离开过客户端本地内存一步!
我们直接调用了原生硬件加速的 WebCrypto API 和现代密码学顶流库 @noble/curves v2.4.0,在本地跑通 ECIES(椭圆曲线集成加密方案) 混合加密模型:
- 非对称密钥协商 (ECDH) :前端利用以太坊原生的
secp256k1曲线,生成高阶“临时密钥”,跟审计方的公钥隔空计算出相同的共享秘密(Shared Secret)。 - 密钥派生 (HKDF) :通过
HKDF-SHA256算法把秘密打散,暴力派生出标准的AES-GCM高强度对称密钥。 - AEAD 认证加密:用自带防篡改校验(Tag)的
AES-GCM算法瞬间加密敏感明文。 - 数据结构完美对齐:把
[临时公钥(65B)] + [IV(12B)] + [密文+Tag]按严格边界拼接。这串 Hex 字符串字节边界无缝对齐 Solidity 的bytes类型,确保上链不报Invalid Length!
二、纯离线验证流水线:干货源码直接带走!
新建两个文件,直接在本地 Node.js 或前端运行。这套脚本不仅验证了加密,更模拟了审计端从上一篇合约里拿到 bytes 密文后,如何纯本地离线解码。
模块一:密码学重武器库 eciesCrypto.js
// ✅ 完美适配 @noble/curves v2.4.0
import { secp256k1 } from '@noble/curves/secp256k1.js';
import { sha256 } from '@noble/hashes/sha2.js';
import { hkdf } from '@noble/hashes/hkdf.js';
import { hexToBytes, bytesToHex } from 'viem';
// 选用环境自带的全局全局隐式 WebCrypto 引擎,保障底层跨线程稳健性
const cryptoProvider = typeof window !== 'undefined' ? window.crypto : globalThis.crypto;
/**
* 核心逻辑:从共享密钥中通过 HKDF 派生出 AES-GCM 对称密钥
*/
async function deriveKey(sharedSecret) {
const derivedBytes = hkdf(sha256, sharedSecret, undefined, undefined, 32);
return await cryptoProvider.subtle.importKey(
'raw',
derivedBytes,
{ name: 'AES-GCM' },
false,
['encrypt', 'decrypt']
);
}
/**
* 前端离线加密
*/
export async function encryptMessage(msgStr, recipientPublicKeyHex) {
// 1. ✅ 完美适配 v2.x 的 临时临时私钥生成逻辑
const ephemeralPrivKey = secp256k1.utils.randomSecretKey();
const ephemeralPubKey = secp256k1.getPublicKey(ephemeralPrivKey, false); // 65 字节非压缩公钥
const cleanPubKeyHex = recipientPublicKeyHex.startsWith('0x') ? recipientPublicKeyHex.slice(2) : recipientPublicKeyHex;
const recipientPubKeyBytes = hexToBytes(`0x${cleanPubKeyHex}`);
// 2. 计算共享密钥
const sharedSecret = secp256k1.getSharedSecret(ephemeralPrivKey, recipientPubKeyBytes);
// 3. 派生 AES 密钥
const aesKey = await deriveKey(sharedSecret);
// 4. 使用 AES-GCM 离线加密
const iv = cryptoProvider.getRandomValues(new Uint8Array(12)); // 12 字节初始向量
const encoder = new TextEncoder();
const plaintextBytes = encoder.encode(msgStr);
const ciphertextBuffer = await cryptoProvider.subtle.encrypt(
{ name: 'AES-GCM', iv },
aesKey,
plaintextBytes
);
const ciphertextBytes = new Uint8Array(ciphertextBuffer);
// 5. 打包输出:临时公钥 (65字节) + IV (12字节) + 密文 (含 Tag)
const result = new Uint8Array(ephemeralPubKey.length + iv.length + ciphertextBytes.length);
result.set(ephemeralPubKey, 0);
result.set(iv, ephemeralPubKey.length);
result.set(ciphertextBytes, ephemeralPubKey.length + iv.length);
return bytesToHex(result);
}
/**
* 本地私钥离线解密
*/
export async function decryptMessage(encryptedHex, privateKeyHex) {
const cleanDataHex = encryptedHex.startsWith('0x') ? encryptedHex.slice(2) : encryptedHex;
const encryptedBytes = hexToBytes(`0x${cleanDataHex}`);
const cleanPrivHex = privateKeyHex.startsWith('0x') ? privateKeyHex.slice(2) : privateKeyHex;
const privateKeyBytes = hexToBytes(`0x${cleanPrivHex}`);
// 1. 拆包数据
const ephemeralPubKeyBytes = encryptedBytes.slice(0, 65);
const iv = encryptedBytes.slice(65, 65 + 12);
const ciphertextBytes = encryptedBytes.slice(65 + 12);
// 2. 计算共享密钥
const sharedSecret = secp256k1.getSharedSecret(privateKeyBytes, ephemeralPubKeyBytes);
// 3. 派生相同的 AES 密钥
const aesKey = await deriveKey(sharedSecret);
// 4. 解密还原
const plaintextBuffer = await cryptoProvider.subtle.decrypt(
{ name: 'AES-GCM', iv },
aesKey,
ciphertextBytes
);
const decoder = new TextDecoder();
return decoder.decode(plaintextBuffer);
}
模块二:自动化跑通验证流水线 main.js
import { encryptMessage, decryptMessage } from './eciesCrypto.js';
import { secp256k1 } from '@noble/curves/secp256k1.js';
import { bytesToHex } from 'viem';
async function main() {
console.log("🔒 正在启动 ECIES 本地离线隐私加解密验证...");
// 1. 模拟生成接收方密钥对(✅ 完美适配 v2.x 的 randomSecretKey 规范)
const auditorPrivateKeyBytes = secp256k1.utils.randomSecretKey();
const auditorPrivateKey = bytesToHex(auditorPrivateKeyBytes);
// 获取非压缩公钥 (0x04...)
const auditorPublicKey = bytesToHex(secp256k1.getPublicKey(auditorPrivateKeyBytes, false));
console.log(`\n[🔑 密钥对初始化成功]`);
console.log(`- 接收方本地私钥: ${auditorPrivateKey}`);
console.log(`- 接收方公开公钥: ${auditorPublicKey}`);
// 2. 模拟高敏感商业隐私数据(明文)
const businessData = JSON.stringify({
sender: "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5",
recipient: "0x388C818CA8B9251b393131C08a736A67ccB19297",
shieldedAmount: "5000000000000000000000",
memo: "Q3 跨境大宗商品供应链核心原材料结算"
});
console.log(`\n[📝 待加密的原始明文]`);
console.log(JSON.parse(businessData));
// 3. 前端离线加密
console.log(`\n[🔒 正在执行前端本地离线加密...]`);
const encryptedBytesForChain = await encryptMessage(businessData, auditorPublicKey);
console.log(`\n[✨ 密文生成成功!这是可以安全传给智能合约 bytes 字段的数据]`);
console.log(`- 链上密文 Hex: ${encryptedBytesForChain}`);
// 4. 离线解密
console.log(`\n[🔓 正在执行本地私钥离线解密...]`);
const decryptedData = await decryptMessage(encryptedBytesForChain, auditorPrivateKey);
console.log(`\n[🎉 完美还原!解密后的明文如下]`);
console.log(JSON.parse(decryptedData));
}
main().catch((error) => {
console.error("❌ 脚本执行过程中发生严重错误:", error);
});
执行结果快照
🔒 正在启动 ECIES 本地离线隐私加解密验证...
[🔑 密钥对初始化成功]
- 接收方本地私钥: 0x75d9ffc0eb44ca1da85723b98ef3bff469f10f08f90c6bf00bf80f5347fe21be
- 接收方公开公钥: 0x0465c6d8767ae08f3fb259b48ddd9645909ca873bfdad05203cabbbc0f877dea5c9fd9cf2de59de05402c0e659b337cd2437429bd81edda3d5bb05f0020548b33f
[📝 待加密的原始明文]
{
sender: '0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5',
recipient: '0x388C818CA8B9251b393131C08a736A67ccB19297',
shieldedAmount: '5000000000000000000000',
memo: 'Q3 跨境大宗商品供应链核心原材料结算'
}
[🔒 正在执行前端本地离线加密...]
[✨ 密文生成成功!这是可以安全传给智能合约 bytes 字段的数据]
- 链上密文 Hex: 0x0419ad6072ac940503b5631365428e27c81792883b5edf05e1676cc9c7d82afe4342b8beb1659c79746169a33846fe5e23592b21df42bfe57a27185cf3a90ecd6a49c722ddc3a3dd36e8c3787e59b816ac5cfe866b8937ef86d97409d82585626144e7a9f7df6708c1ce266d5dd157aef648c4b8097eecadd8c25305124ddcd829725b18e8d6267a71c65c1c22ef3ffc34419c01d3bd497a6028a604956359b1c210666f615408f5d15fb1e1e554abf785f3a183f7f0497d21e5663cacec87b3386eacd73544ab561c43a52f0d18088801b4ef9a71ac49947eee12948be1f7736ddd59cf4c650064cedb040c3f5e3484adcd38d2942e9301f8d38fca0f494732b0daaf8f412c7bb50c94759821e4ede02f97064b91b6b01f9db13a75b34ebc5f89b1711568d7473ff900884b571cac9c6f93cd92
[🔓 正在执行本地私钥离线解密...]
[🎉 完美还原!解密后的明文如下]
{
sender: '0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5',
recipient: '0x388C818CA8B9251b393131C08a736A67ccB19297',
shieldedAmount: '5000000000000000000000',
memo: 'Q3 跨境大宗商品供应链核心原材料结算'
}
三、链上合约 + 链下前端,如何打出一套完美的组合拳?
通过这篇前端离线验证,我们和上一篇的智能合约完成了教科书级的跨平台联动:
- 白嫖全网节点的算力:加密在用户浏览器做,解密在审计员电脑上做。智能合约不需要运行任何非对称密码学逻辑,它只当一个 “没有感情的数据公告板” 。全网节点的 CPU 都不需要为此买单,这才是真正的合规隐私(Regulated Privacy)工程美学。
- 绝对的前向安全性(Forward Secrecy) :每次调用加密,前端都会在内存中揉碎并生成一个全新的临时私钥。就算未来有一笔交易的临时密钥泄露,也绝对无法逆推历史任何一笔交易!
- 零篡改可能(AEAD) :坚决摒弃过时的
AES-CBC模式。我们用的AES-GCM自带 16 字节的 Authentication Tag。在传输中或者链上存储时,只要有人动了 Hex 密文里的哪怕一个比特,解密端会瞬间抛出异常,彻底干碎重放攻击和注入攻击!
结语
到这里, 《EVM 复刻 Zcash 隐私合约系列》全面闭环! 链上合约我们写好了“保险箱”,链下前端我们造好了“密文兵工厂”和“轻节点审计工具”。
这套方案告诉我们:在 EVM 生态里玩转可编程隐私,根本不需要复杂繁琐的链上开销,控制权早已在我们的前端代码和密码学边界中被优雅锁死。