
如今的身份系统要求我们对公司给予太多的信任,相信它们会安全地存储你的数据。多家 KYC 服务商无限期地持有你的数据。每个应用都会拿到你的全部个人信息,哪怕它们只需要知道你是否已满 18 岁,这些信息分享得太多了,而且最终会零散地分布在各个地方。
自主权身份 SSI 解决了这个问题,它把凭证的控制权交还给你,你可以只分享必要的信息,在不泄露数据的情况下证明某项声明。简单来说,可验证凭证 VC 能让你在不出示确切出生日期的情况下,证明自己已满 18 岁。但 SSI 有一个问题 - 控制你身份的私钥要存放在哪里?
针对这个问题,有很多根本性的解决方案,其中之一就是 biometric crypto identities,它并不是最流行的方案之一。问题是,如果你的 biometric,比如 face,就是你的 private-key 呢?没有会丢失的 seed phrases,没有会忘记的 passwords,也没有需要保管的 keys。你的 biometric 生成你的 wallet,钱包里存放着你的可验证凭证,而你可以用零知识证明(ZKP)来证明这些凭证。
借助 fuzzy extractors + zero-knowledge proofs,这一切成为可能。下面来看看它是如何工作的。
传统的密码学系统是为可复现的比特而设计的。但生物特征打破了这些系统所依赖的两条关键规则:
密钥必须能够完美复现 - 这些系统要求密钥能够逐比特地精确复现。你用来签名交易的私钥每一次都必须完全相同。但生物特征是有噪声的,需要考虑的变量非常多。由于传感器差异、手指放置的位置、湿度、温度等因素,你今天的指纹扫描结果不会和昨天的逐比特一致 - 这样的因素可以一直列下去。人脸扫描、虹膜纹理或者任何生物测量都是如此。
密钥必须是均匀随机的 - 密码学安全性假设你的密钥是从整个密钥空间中均匀随机抽取的。但生物特征数据是有结构和模式的。你的虹膜并不是均匀随机的,它受到生物学上的约束,在人群层面存在相似性,而且熵集中在某些特定的特征上。
这就造成了常见的先有鸡还是先有蛋的问题:你不能直接把生物特征当作密钥 它们的噪声很大,你没法安全地存储它们 它们属于个人身份信息(PII),而且无法更改,你也不能简单地对它们做哈希 正如你所知,噪声会让哈希函数失效。那该怎么办呢?
现在,精彩的部分来了!
你可以把模糊提取器看作是针对噪声数据的纠错密码学。它由一对算法组成,Gen 生成 和 Rep 复现,它们能做一件非常令人兴奋的事情:
- 接收你带噪声的生物特征输入 w,输出一个均匀随机的字符串 R 也就是真正的密码学密钥,以及一个公开的辅助字符串 P。还有一个有趣的事实:P 可以公开发布,而不会损害安全性。它不会泄露任何关于你的生物特征的有意义的信息。
- 接收另一次生物特征读数 w' 以及同一个公开的辅助字符串 P,只要 w' 与原始的 w 足够接近,就能复现出完全相同的 R。我知道这听起来有点吓人,但在这里,足够接近 是通过某个度量空间中的距离来衡量的 二进制串用汉明距离,序列用编辑距离,特征向量用集合差。
它的安全保证是信息论意义上的,即使给定了 P,只要你的生物特征的最小熵满足 ,提取出的密钥 R 就与均匀分布 -接近。这不需要任何计算困难性假设。即使是拥有无限算力的攻击者,也无法以大于 的优势把 R 与真正的随机密钥区分开来。
这背后的数学非常优美。对于一个参数为 的模糊提取器:
R 的长度w' 与 w 可以相差多少这个构造使用了两个协同工作的原语:
安全草图类似于一种模糊承诺。 输出一个草图 s,它泄露的信息刚好足以纠正错误,但远远不足以重建 w 本身。然后, 利用这个草图,从带噪声的 w' 中恢复出原始的 w。
对于使用汉明距离的二进制串来说,校验子构造(Syndrome Construction)是最优的:
class SyndromeSketch:
def __init__(self, n, k, t):
self.n, self.k, self.t = n, k, t
self.syn_matrix = generate_bch_syndrome_matrix(n, k)
def sketch(self, w):
return np.dot(self.syn_matrix, w) % 2
def recover(self, w_prime, s):
s_prime = np.dot(self.syn_matrix, w_prime) % 2
error_syn = (s_prime - s) % 2
e = bch_decode(error_syn, self.t)
return (w_prime - e) % 2这里发生了什么:
sketch() 计算生物特征 w 的校验子,输出 s,长度为 $n-k$ 比特recover() 利用草图 s 纠正带噪声的 w_prime 中的错误$n-k$ 比特BCH 码 Bose–Chaudhuri–Hocquenghem 码:损失 64 比特,最多可以纠正 个错误完成纠错之后,对恢复出的生物特征做哈希,就能提取出一个均匀分布的密钥,例如:
pub fn fuzzy_gen(biometric: &[u8]) -> (Vec<u8>, Vec<u8>) {
let sketch = secure_sketch(biometric);
let seed: Vec<u8> = (0..32).map(|_| rng.gen()).collect();
let key = extract_key(biometric, &seed);
let helper = [sketch, seed].concat();
(key, helper)
}
pub fn fuzzy_rep(noisy_biometric: &[u8], helper: &[u8]) -> Vec<u8> {
let (sketch, seed) = split_helper(helper);
let recovered = sketch_recover(noisy_biometric, sketch);
extract_key(&recovered, &seed)
}fuzzy_gen() 函数:
hash(biometric || seed) 提取密钥key, helper,其中 helper = sketch, seedfuzzy_rep() 函数:
对于典型的生物特征:
注意:原始的生物特征表示中存在冗余和相关性。虹膜编码的原始比特数(2048)与信息论意义上的熵(约 249 比特)之比约为 8:1。密钥提取必须以实际熵为准,而不是以表示的大小为准。

SSI 信任模型中有三个参与方:
完整的流程: 生物特征 → 钱包 → 凭证 → 零知识证明 → 已验证的身份

我们一步一步来看:
async function biometricToWallet(rawBiometric) {
const binary = await preprocessBiometric(rawBiometric);
const { key, helper } = await fuzzyExtractor.gen(binary);
const expandedKey = await hkdf(
key,
128,
'bip39-seed',
await crypto.getRandomValues(new Uint8Array(32))
);
const mnemonic = bip39.entropyToMnemonic(expandedKey);
const seed = await bip39.mnemonicToSeed(mnemonic);
const master = bip32.fromSeed(seed);
return {
btc: deriveBitcoin(master),
eth: deriveEthereum(master),
sol: deriveSolana(seed),
helper,
salt
};
}整个过程如下:
fuzzy_gen() → 得到 40-128 比特的密钥 + 公开的辅助字符串BIP39 助记词 → 派生出 HD 钱包种子P2WPKH、ETH EVM、SOL Ed25519 的地址具体到 Solana,它用的是一条不同的曲线:
import { derivePath } from 'ed25519-hd-key';
const solSeed = derivePath("m/44'/501'/0'/0'", seed.toString('hex')).key;
const solKeypair = Keypair.fromSeed(solSeed);
const solAddress = solKeypair.publicKey.toBase58();注意:Solana 使用的是
Ed25519,而不是secp256k1。所以在你的实现中,要使用ed25519-hd-key来满足SLIP-0010规范 与Phantom、Solflare保持一致。
现在你的钱包有了一个 DID,但要证明任何事情,你还需要一份可验证凭证 VC。信任三角就是从这里开始的。
签发方会在链下核验你的证件,然后签发一份经过密码学签名的证明:
async function requestCredential(wallet, issuerEndpoint, claimData) {
const connectionRequest = await issuerEndpoint.createConnection(wallet.did);
const credentialOffer = await issuerEndpoint.offerCredential({
type: 'KYCCredential',
schema: 'https://schema.org/kyc-v1',
claims: {
country: claimData.country,
birthYear: claimData.birthYear,
verified: true
}
});
const signedVC = await wallet.acceptCredential(credentialOffer);
await wallet.storeCredential(signedVC);
return signedVC;
}发生了什么:
cryptographically 签名的 VCVC 中包含签发方的 DID 签名,用来证明其真实性VCs,比如学历、工作经历、KYC凭证的 schema 定义了 VC 中包含哪些内容:
{
"@context": "https://www.w3.org/2018/credentials/v1",
"type": ["VerifiableCredential", "KYCCredential"],
"issuer": "did:polygonid:polygon:amoy:2qH7...",
"issuanceDate": "2025-01-15T00:00:00Z",
"credentialSubject": {
"id": "did:polygonid:polygon:amoy:2qPz...",
"birthYear": 1995,
"country": "US",
"kycVerified": true
},
"proof": {
"type": "BJJSignature2021",
"issuerData": {...},
"signature": "0x..."
}
}现在,当验证方要求你 证明你已满 18 岁 时,你就可以基于这份凭证生成一个零知识证明。
在这一层,你可以证明自己的个人信息,比如 年龄、国籍、学位、地址、出生日期等,而不需要泄露这些敏感数据。
async function proveAge(wallet, minAge) {
const credential = await wallet.getCredential('kyc');
const inputs = {
birthYear: credential.birthYear,
currentYear: 2025,
minAge: minAge,
signature: credential.signature
};
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
inputs,
"ageCheck.wasm",
"ageCheck.zkey"
);
return { proof, publicSignals };
}证明了什么:
minAge 岁Circom 电路:
template AgeCheck() {
signal input birthYear;
signal input currentYear;
signal input minAge;
signal age <== currentYear - birthYear;
component check = GreaterEqThan(16);
check.in[0] <== age;
check.in[1] <== minAge;
signal output isValid;
isValid <== check.out;
}链上验证 - Solidity:
contract IdentityGate {
mapping(address => mapping(uint256 => bool)) public verified;
function submitProof(
uint256 requestId,
uint[2] calldata pA,
uint[2][2] calldata pB,
uint[2] calldata pC,
uint[3] calldata pubSignals
) external {
require(zkpVerifier.verifyProof(pA, pB, pC, pubSignals), "Invalid proof");
require(pubSignals[2] >= minAge, "Age requirement not met");
verified[msg.sender][requestId] = true;
_grantAccess(msg.sender);
}
}这个合约会:
所以从这个流程中,你可以看到你的生物特征变成了:
DIDVCZKP 不会泄露任何关于你的信息而且这并不是多个相互独立的系统 - 它是一个统一的流程。钱包生成只在注册时发生一次。只要需要身份验证,就可以使用零知识证明流程。可以这样理解:在普通交易中,你可以像使用其他任何加密钱包一样使用你的生物特征钱包。但当某个 dApp 需要 KYC 或任何基于 PII 的验证时 比如 Polygon ID 的使用场景,零知识证明层就会介入,在不暴露你数据的情况下证明你的凭证。
每个 AI agent 都可以拥有一个由生物特征派生的 DID。想象一个拥有自己的钱包和已验证凭证的 AI 交易 agent:
class TradingAgent {
constructor(agentFingerprint) {
this.wallet = await biometricToWallet(agentFingerprint);
this.did = await createDID(this.wallet);
}
async trade(dex, params) {
const proof = await this.proveReputation();
return await dex.trade(params, proof);
}
}这个 agent:
biometric 神经网络哈希 派生出钱包ZKP 证明自己的信誉 1000 笔以上的成功交易SingularityNET 正在构建的正是这样的东西,一个 AI Agent 信任注册中心,agent 可以在其中使用可验证凭证来证明自己的真实性、安全性和审计状态。biometric-wallet-ZKP 流程让 agent 能够自主运行,同时保持可问责性。
某个 DeFi 协议需要做 KYC,但又不想存储用户数据 既有监管责任,又会成为黑客眼中的蜜罐:
contract DeFiProtocol {
modifier onlyKYCVerified() {
require(identityGate.verified(msg.sender, KYC_ID), "KYC required");
_;
}
function borrowUndercollateralized(uint256 amount) external onlyKYCVerified {
_issueLoan(msg.sender, amount);
}
}这个协议永远看不到你的护照、姓名或地址。既满足了合规要求,又保护了隐私。
同样的模式还适用于:
async function login(website) {
const { key } = await fuzzyExtractor.rep(biometric, helper);
const siteKey = deriveKey(key, website.domain);
const signature = sign(challenge, siteKey);
await website.verify(signature);
}每个网站都会得到一个唯一的派生密钥。不需要发送密码,也没有可以被钓鱼的凭证。
由你的生物特征来授权交易:
async function pay(merchant, amount) {
const wallet = await biometricToWallet(scan);
if (amount > 1000) {
await merchant.verifyProof(generateKYCProof(wallet));
}
await wallet.sendTransaction({ to: merchant.address, value: amount });
}BMONI + BKey 目前就实现了这一点,你的所有钱包都完全由你的生物特征“你的脸”派生而来,并在特定的 EVM 链和代币上开通了钱包,这一切都只需要你的脸!
contract AML {
bytes32 public scamMerkleRoot;
function checkClean(address wallet, bytes32[] calldata proof) public view returns (bool) {
bytes32 hash = keccak256(abi.encodePacked(wallet));
return !MerkleProof.verify(proof, scamMerkleRoot, hash);
}
}dApp 在允许访问之前会先进行查询。已知的诈骗者会在全网范围内被拦截,一些有过异常活动历史的地址也会被标记出来,同时集成方会收到关于过往可疑活动的通知。
基于生物特征身份进行构建需要基础设施。整个生态提供了三个核心组件:
各个组织可以运行自托管的签发节点来签发凭证:
docker-compose up -d
make import-private-key-to-kms private_key=<eth-key>
# access the ui at localhost:8088
# access the api at localhost:3001功能:
DIDschemasVCsAPI 示例:
const issuerAPI = new IssuerNode({
url: 'https://issuer.example.com',
auth: { user: 'admin', password: 'secure' }
});
await issuerAPI.createIdentity({
blockchain: 'polygon',
network: 'amoy',
method: 'polygonid'
});
await issuerAPI.issueCredential({
did: 'did:polygonid:...',
schema: 'KYCCredential',
claims: { ageOver: 18 }
});工具生态
tools.privado.id/builder 创建自定义的 VC schematools.privado.id/query-builder 设计零知识证明查询schemasissuers1:生物特征的可变性
你的指纹在不同扫描之间会有 10-15% 的差异。解决方案:BCH 码是比特级纠错的最优选择。对于一个错误率为 10%(约 205 个比特错误)的 2048 比特虹膜编码 其中实际熵约为 249 比特:
关键区别:这 2048 比特是原始的虹膜编码表示 Daugman 算法的输出。由于生物学上的相关性和冗余,实际的信息论熵只有约 249 比特 比例约为 8:1。
2:模板老化
问题:你的脸会随着时间而变化 衰老、体重、胡须等
解决方案:定期重新注册,并迁移密钥:
async function migrate(oldBio, newBio) {
const oldWallet = await recover(oldBio);
const { newWallet, newHelper } = await enroll(newBio);
await oldWallet.transferAll(newWallet.address);
await storage.update(newHelper);
}人脸每 2-3 年重新注册一次,虹膜每 5 年一次,声纹则每年一次,以此类推。
3:活体检测
问题:拿着你照片的攻击者不应该能够访问你的钱包。
解决方案:在采集生物特征时进行活体检测:
const checks = await Promise.all([
depthSensor.measure(),
motionDetector.verify(['blink', 'nod']),
thermalCamera.capture(),
videoAnalyzer.checkTemporal()
]);WorldCoin 的 Orb 在虹膜扫描中就是这么做的:25 项以上的活体检测、3D 成像、红外 + RGB 摄像头。效果非常好 - 误识率 。
4:辅助字符串的隐私性
前面讲到的 P 会泄露生物特征信息吗?不会。根据定理 Dodis 等人,攻击者最多只能获知 比特的信息 即草图长度,密钥 R 在统计意义上依然是随机的。以我们的虹膜为例:攻击者获知 408 比特,仍然剩下 392 比特的熵。
所需的组件已经全部存在:
CompFE 库(MIT 许可证),可以在移动端运行还缺什么?集成……
下面是在生产环境中实际使用这套系统的样子
async function enrollUser() {
const bio = await captureLive();
const { key, helper } = await fuzzyExtractor.gen(bio);
const salt = await crypto.getRandomValues(new Uint8Array(32));
const expandedKey = await hkdf('sha256', key, salt, 'bip39-seed', 128/8);
const wallet = await deriveWallet(expandedKey);
const did = await createDID(wallet.eth);
const kycCred = await requestKYC(did, { passport, selfie });
await storage.save({ helper, salt, did, credentials: [kycCred] });
return { wallet, did };
}
async function accessDApp(dappURL) {
const bio = await captureLive();
const { helper, salt } = await storage.getHelperData();
const key = await fuzzyExtractor.rep(bio, helper);
const expandedKey = await hkdf('sha256', key, salt, 'bip39-seed', 128/8);
const wallet = await deriveWallet(expandedKey);
const proof = await generateProof(wallet, dappURL.request);
await submitProof(wallet, proof, dappURL.verifier);
await dappURL.grantAccess(wallet.address);
}
async function sendPayment(recipient, amount) {
const bio = await captureLive();
const { helper, salt } = await storage.getHelperData();
const key = await fuzzyExtractor.rep(bio, helper);
const expandedKey = await hkdf('sha256', key, salt, 'bip39-seed', 128/8);
const wallet = await deriveWallet(expandedKey);
await wallet.send({ to: recipient, value: amount });
}普通的钱包流程和身份流程可以共存。只有在需要验证时,零知识证明才会登场,无论是用于 KYC,还是用于任何基于 PII 的验证,比如年龄、国籍、学位、地址、出生日期等。
你的生物特征就是你的密钥,你的钱包就是你在所有支持的链上的身份,而且你永远不用担心弄丢助记词或私钥,一切都与你的身体特征绑定在一起,在 BMoni 的实现中,那就是你的脸!
将最新文章直接发送到你的收件箱。