全部文章

基于模糊提取器的去中心化身份生物特征密钥派生

·
2025年10月21日
·
阅读约 11 分钟
·
••• 次浏览
·
••• 次点赞

本文译自英文原文。 阅读英文原文

基于模糊提取器的 DID 生物特征绑定密钥派生

如今的身份系统要求我们对公司给予太多的信任,相信它们会安全地存储你的数据。多家 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 复现,它们能做一件非常令人兴奋的事情:

Gen(w)→(R,P)Gen(w) \rightarrow (R, P) - 接收你带噪声的生物特征输入 w,输出一个均匀随机的字符串 R 也就是真正的密码学密钥,以及一个公开的辅助字符串 P。还有一个有趣的事实:P 可以公开发布,而不会损害安全性。它不会泄露任何关于你的生物特征的有意义的信息。

Rep(w′,P)→RRep(w', P) \rightarrow R - 接收另一次生物特征读数 w' 以及同一个公开的辅助字符串 P,只要 w' 与原始的 w 足够接近,就能复现出完全相同的 R。我知道这听起来有点吓人,但在这里,足够接近 是通过某个度量空间中的距离来衡量的 二进制串用汉明距离,序列用编辑距离,特征向量用集合差。

它的安全保证是信息论意义上的,即使给定了 P,只要你的生物特征的最小熵满足 ≥m\geq m,提取出的密钥 R 就与均匀分布 ε\varepsilon-接近。这不需要任何计算困难性假设。即使是拥有无限算力的攻击者,也无法以大于 ε\varepsilon 的优势把 R 与真正的随机密钥区分开来。

这背后的数学非常优美。对于一个参数为 (m,ℓ,t,ε)(m, \ell, t, \varepsilon) 的模糊提取器:

它实际上是如何工作的

这个构造使用了两个协同工作的原语:

安全草图(Secure Sketch)(SS,Rec)(SS, Rec)

安全草图类似于一种模糊承诺。SS(w)→sSS(w) \rightarrow s 输出一个草图 s,它泄露的信息刚好足以纠正错误,但远远不足以重建 w 本身。然后,Rec(w′,s)→wRec(w', s) \rightarrow 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

这里发生了什么:

从草图到提取器

完成纠错之后,对恢复出的生物特征做哈希,就能提取出一个均匀分布的密钥,例如:

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() 函数:

fuzzy_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
    };
}

整个过程如下:

具体到 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;
}

发生了什么:

凭证的 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 };
}

证明了什么:

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);
    }
}

这个合约会:

所以从这个流程中,你可以看到你的生物特征变成了:

而且这并不是多个相互独立的系统 - 它是一个统一的流程。钱包生成只在注册时发生一次。只要需要身份验证,就可以使用零知识证明流程。可以这样理解:在普通交易中,你可以像使用其他任何加密钱包一样使用你的生物特征钱包。但当某个 dApp 需要 KYC 或任何基于 PII 的验证时 比如 Polygon ID 的使用场景,零知识证明层就会介入,在不暴露你数据的情况下证明你的凭证。

一些可能的使用场景

  1. 智能体护照:

每个 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:

SingularityNET 正在构建的正是这样的东西,一个 AI Agent 信任注册中心,agent 可以在其中使用可验证凭证来证明自己的真实性、安全性和审计状态。biometric-wallet-ZKP 流程让 agent 能够自主运行,同时保持可问责性。

  1. dApp 身份层:

某个 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);
    }
}

这个协议永远看不到你的护照、姓名或地址。既满足了合规要求,又保护了隐私。

同样的模式还适用于:

  1. Web2 无密码登录
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);
}

每个网站都会得到一个唯一的派生密钥。不需要发送密码,也没有可以被钓鱼的凭证。

  1. 生物特征支付:

由你的生物特征来授权交易:

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 链和代币上开通了钱包,这一切都只需要你的脸!

  1. 防诈骗数据库:
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 在允许访问之前会先进行查询。已知的诈骗者会在全网范围内被拦截,一些有过异常活动历史的地址也会被标记出来,同时集成方会收到关于过往可疑活动的通知。

开发者集成

基于生物特征身份进行构建需要基础设施。整个生态提供了三个核心组件:

签发节点(Issuer Node)

各个组织可以运行自托管的签发节点来签发凭证:

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

功能:

API 示例:

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 }
});

工具生态

一些技术挑战与解决方案

1:生物特征的可变性

你的指纹在不同扫描之间会有 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 摄像头。效果非常好 - 误识率 <0.0001%< 0.0001\%。

4:辅助字符串的隐私性

前面讲到的 P 会泄露生物特征信息吗?不会。根据定理 Dodis 等人,攻击者最多只能获知 n−kn-k 比特的信息 即草图长度,密钥 R 在统计意义上依然是随机的。以我们的虹膜为例:攻击者获知 408 比特,仍然剩下 392 比特的熵。

它已经可以用于生产环境了吗?

所需的组件已经全部存在:

还缺什么?集成……

生产环境实现示例

下面是在生产环境中实际使用这套系统的样子

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 的实现中,那就是你的脸!

重要链接

•••
分享本文:

获取最新动态

将最新文章直接发送到你的收件箱。