本文档介绍了OpenZeppelin社区合约中的密码学工具,包括多种签名验证方案和加密原语的实现。这些工具支持智能合约中的安全认证、多重签名操作和高级加密操作,例如ERC7739Utils、ERC7913Utils、ZKEmailUtils和WebAuthn等。
在 https://docs.openzeppelin.com/community-contracts/utils/cryptography 上查看此文档效果更佳 |
实现了各种签名验证方案和密码学原语的合约和库的集合。这些实用程序支持智能合约中的安全身份验证、多重签名操作和高级密码学操作。
ERC7739Utils
: 实用程序库,实现了一种防御性重新哈希机制,以防止基于 ERC-7739 的智能合约签名被重放。
ERC7913Utils
: 实用程序库,实现了 ERC-1271 和 ECDSA 签名验证,并回退到 ERC-7913。
ZKEmailUtils
: 用于 ZKEmail 签名验证实用程序的库,通过零知识证明实现基于电子邮件的身份验证。
WebAuthn
: 用于验证 WebAuthn 身份验证断言的库。
AbstractSigner
: 用于智能合约中内部签名验证的抽象合约。
ERC7739
: 一个抽象合约,用于验证遵循 ERC7739Utils
中重新哈希方案的签名。
SignerECDSA
, SignerP256
, SignerRSA
: 具有特定签名验证算法的 AbstractSigner
的实现。
SignerERC7702
: AbstractSigner
的实现,该实现使用合约自身的地址作为签名者来验证签名,这对于遵循 EIP-7702 的委托帐户很有用。
SignerERC7913
, MultiSignerERC7913
, MultiSignerERC7913Weighted
: AbstractSigner
的实现,该实现基于 ERC-7913 验证签名。包括一个简单且加权的多重签名方案。
SignerZKEmail
: AbstractSigner
的实现,该实现通过零知识证明启用基于电子邮件的身份验证。
SignerWebAuthn
: 支持 WebAuthn 身份验证断言的 SignerP256
的实现。
ERC7913P256Verifier
, ERC7913RSAVerifier
, ERC7913ZKEmailVerifier
: 用于 P256、RSA 密钥和 ZKEmail 的即用型 ERC-7913 签名验证器。
ERC7739Utils
import "@openzeppelin/community-contracts/utils/cryptography/ERC7739Utils.sol";
用于处理 ERC-7739 类型化数据签名的实用程序,这些签名特定于 EIP-712 域。
该库提供了包装、解包和操作类型化数据签名的方法,具有防御性重新哈希机制,该机制包括应用程序的 EIP-712,并使用 EIP-712 嵌套方法保留签名内容的可读性。
智能合约域可以通过两种方式验证类型化数据结构的签名:
作为验证类型化数据签名的应用程序。请参阅 typedDataSignStructHash
。
作为验证原始消息签名的智能合约。请参阅 personalSignStructHash
。
智能合约钱包的提供商需要将此签名作为调用 personal_sign 或 eth_signTypedData 的结果返回,但这可能不受 API 客户端的支持,这些客户端期望返回值为 129 字节,或者特别是 ECDSA 签名的 r,s,v 参数,例如 EIP-712 中指定的。 |
函数
encodeTypedDataSig(signature, appSeparator, contentsHash, contentsDescr)
decodeTypedDataSig(encodedSignature)
personalSignStructHash(contents)
typedDataSignStructHash(contentsName, contentsType, contentsHash, domainBytes)
typedDataSignStructHash(contentsDescr, contentsHash, domainBytes)
typedDataSignTypehash(contentsName, contentsType)
decodeContentsDescr(contentsDescr)
encodeTypedDataSig(bytes signature, bytes32 appSeparator, bytes32 contentsHash, string contentsDescr) → bytes
internal将给定 EIP-712 类型的签名嵌套到应用程序域的嵌套签名中。
decodeTypedDataSig
的对应项,用于提取原始签名和嵌套组件。
decodeTypedDataSig(bytes encodedSignature) → bytes signature, bytes32 appSeparator, bytes32 contentsHash, string contentsDescr
internal将嵌套签名解析为其组件。
构造如下:
signature ‖ APP_DOMAIN_SEPARATOR ‖ contentsHash ‖ contentsDescr ‖ uint16(contentsDescr.length)
signature
是 (ERC-7739) 嵌套结构哈希的签名。此签名间接签署原始“contents”哈希(来自应用程序)和帐户的域分隔符。
APP_DOMAIN_SEPARATOR
是请求签名验证(通过 ERC-1271)的应用程序智能合约的 EIP-712 {EIP712-_domainSeparatorV4}。
contentsHash
是底层数据结构或消息的哈希。
contentsDescr
是嵌套签名的 EIP-712 类型的“contents”部分的描述符。
如果输入格式无效,此函数返回空值,而不是恢复。<br>数据。 |
personalSignStructHash(bytes32 contents) → bytes32
internal将 ERC-191
摘要嵌套到 PersonalSign
EIP-712 结构中,并返回相应的结构哈希。此结构哈希必须与域分隔符组合,在使用 {MessageHashUtils-toTypedDataHash} 进行验证/恢复之前。
这用于在智能合约的上下文中模拟 personal_sign
RPC 方法。
typedDataSignStructHash(string contentsName, string contentsType, bytes32 contentsHash, bytes domainBytes) → bytes32 result
internal将 EIP-712
哈希(contents
)嵌套到 TypedDataSign
EIP-712 结构中,并返回相应的结构哈希。此结构哈希必须与域分隔符组合,在使用 {MessageHashUtils-toTypedDataHash} 进行验证/恢复之前。
typedDataSignStructHash(string contentsDescr, bytes32 contentsHash, bytes domainBytes) → bytes32 result
internal{typedDataSignStructHash-string-string-bytes32-bytes} 的变体,它接受内容描述符并从中解码 contentsName
和 contentsType
。
typedDataSignTypehash(string contentsName, string contentsType) → bytes32
internal计算给定类型(和类型名称)的 TypedDataSign
结构的 EIP-712 类型哈希。
decodeContentsDescr(string contentsDescr) → string contentsName, string contentsType
internal从 ERC-7739 内容类型描述中解析类型名称。支持隐式和显式模式。
按照 ERC-7739 规范,如果 contentsName
为空或包含以下任何字节,则认为其无效 , )\x00
如果 contentsType
无效,则返回空字符串。否则,返回字符串具有非零长度。
ERC7913Utils
import "@openzeppelin/community-contracts/utils/cryptography/ERC7913Utils.sol";
提供通用 ERC-7913 实用程序函数的库。
该库扩展了 SignatureChecker 的功能,以支持没有自己的以太坊地址的密钥的签名验证,如 ERC-1271 中所示。
参见 ERC-7913。
函数
isValidSignatureNow(signer, hash, signature)
areValidSignaturesNow(hash, signers, signatures)
isValidSignatureNow(bytes signer, bytes32 hash, bytes signature) → bool
internal验证给定签名者和哈希的签名。
签名者是一个 bytes
对象,它是地址和可选密钥的串联:
verifier || key
。签名者必须至少 20 字节长。
验证按如下方式完成:
signer.length < 20
:验证失败signer.length == 20
:使用 {SignatureChecker} 完成验证areValidSignaturesNow(bytes32 hash, bytes[] signers, bytes[] signatures) → bool
internal使用一组 signers
验证给定哈希的多个 signatures
。
签名者必须按其 keccak256
哈希排序,以确保没有重复并优化验证过程。如果签名者未正确排序,该函数将返回 false
。
要求:
signatures
数组必须至少是 signers
数组的长度。ZKEmailUtils
import "@openzeppelin/community-contracts/utils/cryptography/ZKEmailUtils.sol";
ZKEmail 签名验证实用程序的库。
ZKEmail 是一种协议,它使用零知识证明为智能合约启用基于电子邮件的身份验证和授权。它允许用户在不泄露电子邮件内容或私钥的情况下证明电子邮件地址的所有权。
验证过程涉及几个关键组件:
一个 DKIMRegistry (DomainKeys Identified Mail) 验证机制,以确保电子邮件是从有效域发送的。由 IDKIMRegistry
接口定义。
一个 命令模板 验证机制,以确保电子邮件命令与预期的格式和参数匹配。
一个 零知识证明 验证机制,以确保电子邮件实际上已发送和接收,而不会泄露其内容。由 IVerifier
接口定义。
函数
isValidZKEmail(emailAuthMsg, dkimregistry, verifier)
isValidZKEmail(emailAuthMsg, dkimregistry, verifier, template)
isValidZKEmail(emailAuthMsg, dkimregistry, verifier, template, stringCase)
isValidZKEmail(struct EmailAuthMsg emailAuthMsg, contract IDKIMRegistry dkimregistry, contract IVerifier verifier) → enum ZKEmailUtils.EmailProofError
internalisValidZKEmail
的变体,它验证 ["signHash", "{uint}"]
命令模板。
isValidZKEmail(struct EmailAuthMsg emailAuthMsg, contract IDKIMRegistry dkimregistry, contract IVerifier verifier, string[] template) → enum ZKEmailUtils.EmailProofError
internal验证 ZKEmail 身份验证消息。
此函数采用电子邮件身份验证消息、DKIM 注册表合约和验证器合约作为输入。它执行多个验证检查并返回一个元组,其中包含一个布尔成功标志和一个 EmailProofError
,如果验证失败。如果所有验证都通过,则返回 {EmailProofError.NoError},否则返回 false 和一个特定的 EmailProofError
,指示哪个验证检查失败。
尝试验证所有可能的字符串 Case 值的命令。 |
isValidZKEmail(struct EmailAuthMsg emailAuthMsg, contract IDKIMRegistry dkimregistry, contract IVerifier verifier, string[] template, enum ZKEmailUtils.Case stringCase) → enum ZKEmailUtils.EmailProofError
internalisValidZKEmail
的变体,它验证具有特定字符串 Case
的模板。
对于具有以太坊地址匹配器(即 {ethAddr}
)的模板很有用,这些模板区分大小写(例如 ["someCommand", "{address}"]
)。
WebAuthn
import "@openzeppelin/community-contracts/utils/cryptography/WebAuthn.sol";
用于验证 WebAuthn 身份验证断言的库。
WebAuthn 使用 P256 为智能合约启用强大的身份验证,作为传统 secp256k1 ECDSA 签名的替代方案。此库验证在 WebAuthn 身份验证仪式期间生成的签名,如 WebAuthn Level 2 standard 中所述。
对于区块链用例,有意省略以下 WebAuthn 验证:
原始验证: clientDataJSON
中的原始验证被省略,因为区块链
上下文依赖于身份验证器和 dapp 前端强制执行。标准身份验证器
实施适当的原始验证。
RP ID 哈希验证: 省略了在 authenticatorData
中验证 rpIdHash
与预期 RP ID 哈希的验证。这通常由平台级别的安全措施处理。
建议在签名数据中包含过期时间戳以提高安全性。
签名计数器: 省略了验证签名计数器增量的验证。虽然 对于检测凭据克隆很有用,但链上操作通常包括 nonce 保护,从而使此检查变得多余。
扩展输出: 省略了扩展输出值验证,因为这些对于区块链应用程序中的核心身份验证安全性不是必需的。
证明: 省略了证明对象验证,因为此实现
侧重于身份验证(webauthn.get
)而不是注册仪式。
灵感来自:
函数
verifyMinimal(challenge, auth, qx, qy)
verify(challenge, auth, qx, qy)
verifyStrict(challenge, auth, qx, qy)
validateUserPresentBitSet(flags)
validateUserVerifiedBitSet(flags)
validateBackupEligibilityAndState(flags)
validateExpectedTypeHash(clientDataJSON, typeIndex)
validateChallenge(clientDataJSON, challengeIndex, expectedChallenge)
verifyMinimal(bytes challenge, struct WebAuthn.WebAuthnAuth auth, bytes32 qx, bytes32 qy) → bool
internal对 WebAuthn 身份验证断言执行绝对最小的验证。 此函数仅包含基本 WebAuthn 安全性所需的必要检查:
类型为“webauthn.get”(参见 validateExpectedTypeHash
)
挑战与预期值匹配(参见 validateChallenge
)
加密签名对于给定的公钥有效
对于大多数应用程序,请改用 verify
或 verifyStrict
。
此函数有意省略用户存在 (UP)、用户验证 (UV)、<br>和备份状态/资格检查。仅当需要更广泛的与<br>身份验证器的兼容性或在受限环境中使用时才使用此函数。 |
verify(bytes challenge, struct WebAuthn.WebAuthnAuth auth, bytes32 qx, bytes32 qy) → bool
internal执行 WebAuthn 身份验证断言的标准验证。
与 verifyMinimal
相同,但也验证:
validateUserPresentBitSet
- 确认身份验证期间的物理用户存在此合规性级别满足核心 WebAuthn 验证要求,同时
保持与身份验证器的广泛兼容性。对于更高的安全要求,
请考虑使用 verifyStrict
。
verifyStrict(bytes challenge, struct WebAuthn.WebAuthnAuth auth, bytes32 qx, bytes32 qy) → bool
internal执行 WebAuthn 身份验证断言的严格验证。
与 verify
相同,但也验证:
validateUserVerifiedBitSet
- 确认更强的用户身份验证(生物识别/PIN)
validateBackupEligibilityAndState
- 备份资格 ( BE
) 和备份状态 (BS) 位
关系有效
建议对以下情况使用此严格验证:
高价值交易
特权操作
帐户恢复或关键设置更改
安全优先于身份验证器广泛兼容性的应用程序
validateUserPresentBitSet(bytes1 flags) → bool
internal验证是否设置了 User Present (UP) 位。 验证断言 中的步骤 16。
WebAuthn 规范要求,但对于受控环境中的平台身份验证器<br>(Touch ID, Windows Hello) 可以跳过。强制执行面向公众的应用程序。 |
validateUserVerifiedBitSet(bytes1 flags) → bool
internal验证是否设置了 User Verified (UV) 位。 验证断言 中的步骤 17。
UV 位指示是否使用更强的识别方法(生物识别、PIN、密码)验证了用户。虽然是可选的,但建议要求 UV=1
:
高价值交易和敏感操作
帐户恢复和关键设置更改
特权操作
对于日常操作或使用没有验证功能的硬件身份验证器,<br>UV=0 可能是可以接受的。是否需要 UV 的选择代表了安全性和可用性<br>之间的权衡 - 对于处理有价值资产的区块链应用程序,通常需要 UV 更安全。 |
validateBackupEligibilityAndState(bytes1 flags) → bool
internal根据 WebAuthn 规范验证备份资格 ( BE
) 和备份状态 ( BS
) 位之间的关系。
该函数强制执行:如果凭据已备份 ( BS=1
),则它也必须有资格进行备份 ( BE=1
)。这可以防止未经授权的凭据备份,并确保符合 WebAuthn 规范。
在以下有效状态下返回 true:
BE=1
,BS=0
:凭据有资格但未备份
BE=1
,BS=1
:凭据有资格并且已备份
BE=0
,BS=0
:凭据没有资格且未备份
仅当 BE=0
和 BS=1
时返回 false,这是一个无效状态,指示
已备份但没有资格进行备份的凭据。
虽然 WebAuthn 规范定义了 BE 和 BS 位之间的这种关系,<br>但验证它并不是核心验证过程的明确要求。<br>某些实现可能会选择跳过此检查以获得更广泛的身份验证器<br>兼容性,或者当应用程序的威胁模型不认为凭据<br>同步是主要风险时。 |
validateExpectedTypeHash(bytes clientDataJSON, uint256 typeIndex) → bool
internal验证客户端数据 JSON 中的 Type 字段 是否设置为“webauthn.get”。
validateChallenge(bytes clientDataJSON, uint256 challengeIndex, bytes expectedChallenge) → bool
internal验证客户端数据 JSON 中的挑战是否与 expectedChallenge
匹配。
AbstractSigner
import "@openzeppelin/community-contracts/utils/cryptography/signers/AbstractSigner.sol";
用于签名验证的抽象合约。
开发人员必须实现 _rawSignatureValidation
并将其用作最低级别的签名验证机制。
函数
_rawSignatureValidation(hash, signature)
_rawSignatureValidation(bytes32 hash, bytes signature) → bool
internal签名验证算法。
实现签名验证算法是一项对安全性敏感的操作,因为它涉及<br>密码学验证。在部署之前彻底审查和测试非常重要。考虑<br>使用签名验证库(ECDSA、<br>P256 或 RSA)。 |
ERC7739
import "@openzeppelin/community-contracts/utils/cryptography/signers/ERC7739.sol";
验证签名,将消息哈希包装在嵌套的 EIP712 类型中。参见 ERC7739Utils
。
将签名链接到 EIP-712 域分隔符是一种安全措施,可以防止跨不同 EIP-712 域重放签名(例如,单个链下所有者拥有多个合约)。
此合约需要实现 _rawSignatureValidation
函数,该函数传递包装的消息哈希,该哈希可以是类型化数据或个人签名嵌套类型。
EIP-712 使用<br>ShortStrings 来优化短字符串(最多 31 个字符)的 gas 成本。考虑一下,超过该长度的字符串将使用存储,这可能会限制签名者在 ERC-4337 验证阶段使用的能力(由于<br>ERC-7562 存储访问规则)。 |
函数
isValidSignature(hash, signature)
EIP712
_domainSeparatorV4()
_hashTypedDataV4(structHash)
eip712Domain()
_EIP712Name()
_EIP712Version()
AbstractSigner
_rawSignatureValidation(hash, signature)
事件
IERC5267
EIP712DomainChanged()
isValidSignature(bytes32 hash, bytes signature) → bytes4 result
public尝试验证嵌套 EIP-712 类型中的签名。
嵌套 EIP-712 类型可能以 2 种不同的方式呈现:
作为嵌套的 EIP-712 类型化数据
作为 personal 签名(智能合约的 eth_personalSign
的 EIP-712 模拟)
SignerECDSA
import "@openzeppelin/community-contracts/utils/cryptography/signers/SignerECDSA.sol";
使用
ECDSA 签名的 AbstractSigner
的实现。
对于 Account
用法,提供了一个 _setSigner
函数来设置 signer
地址。
这样做对于工厂来说更容易,工厂很可能会使用此合约的可初始化克隆。
用法示例:
contract MyAccountECDSA is Account, SignerECDSA, Initializable {
function initialize(address signerAddr) public initializer {
_setSigner(signerAddr);
}
}
未能在构造期间(如果单独使用)<br>或初始化期间(如果用作克隆)调用 _setSigner 可能会使签名者成为抢跑者或不可用。 |
函数
_setSigner(signerAddr)
signer()
_rawSignatureValidation(hash, signature)
_setSigner(address signerAddr)
internal使用本机签名者的地址设置签名者。应在构造期间或通过初始化程序调用此函数。
signer() → address
public返回签名者的地址。
_rawSignatureValidation(bytes32 hash, bytes signature) → bool
internal签名验证算法。
实现签名验证算法是一项对安全性敏感的操作,因为它涉及<br>密码学验证。在部署之前彻底审查和测试非常重要。考虑<br>使用签名验证库(ECDSA、<br>P256 或 RSA)。 |
SignerP256
import "@openzeppelin/community-contracts/utils/cryptography/signers/SignerP256.sol";
使用
P256 签名的 AbstractSigner
的实现。
对于 Account
用法,提供了一个 _setSigner
函数来设置 signer
公钥。
这样做对于工厂来说更容易,工厂很可能会使用此合约的可初始化克隆。
用法示例:
contract MyAccountP256 is Account, SignerP256, Initializable {
function initialize(bytes32 qx, bytes32 qy) public initializer {
_setSigner(qx, qy);
}
}
未能在构造期间(如果单独使用)<br>或初始化期间(如果用作克隆)调用 _setSigner 可能会使签名者成为抢跑者或不可用。 |
函数
_setSigner(qx, qy)
signer()
_rawSignatureValidation(hash, signature)
错误
SignerP256InvalidPublicKey(qx,#####
_rawSignatureValidation(bytes32 hash, bytes signature) → bool` internal签名验证算法。
实现签名验证算法是一项对安全性非常敏感的操作,因为它涉及密码学验证。在部署之前,务必彻底审查和测试。考虑使用签名验证库(ECDSA,P256 或 RSA)。 |
SignerP256InvalidPublicKey(bytes32 qx, bytes32 qy)
errorSignerRSA
import "@openzeppelin/community-contracts/utils/cryptography/signers/SignerRSA.sol";
使用 RSA 签名实现的 AbstractSigner
。
对于 Account
的使用,提供了一个 _setSigner
函数来设置 signer
公钥。
这样做对于工厂来说更容易,因为工厂很可能使用此合约的可初始化克隆。
用法示例:
contract MyAccountRSA is Account, SignerRSA, Initializable {
function initialize(bytes memory e, bytes memory n) public initializer {
_setSigner(e, n);
}
}
如果在构造期间(如果单独使用)或初始化期间(如果用作克隆)未能调用 _setSigner 可能会使签名者容易受到抢跑攻击或无法使用。 |
函数
_setSigner(e, n)
signer()
_rawSignatureValidation(hash, signature)
_setSigner(bytes e, bytes n)
internal使用 RSA 公钥设置签名者。此函数应在构造期间或通过初始化程序调用。
signer() → bytes e, bytes n
public返回签名者的 RSA 公钥。
_rawSignatureValidation(bytes32 hash, bytes signature) → bool
internal参见 AbstractSigner._rawSignatureValidation
。通过调用 RSA.pkcs1Sha256 验证 PKCSv1.5 签名。
按照 RFC8017(第 8.2.2 节)中概述的 RSASSA-PKCS1-V1_5-VERIFY 过程,提供的 hash 被用作 M (消息),并按照 RFC 第 9.2 节(步骤 1)的 EMSA-PKCS1-v1_5 编码使用 SHA256 重新哈希。 |
SignerERC7702
import "@openzeppelin/community-contracts/utils/cryptography/signers/SignerERC7702.sol";
EOA 实现的 AbstractSigner
的实现。对于 ERC-7702 账户非常有用。
函数
_rawSignatureValidation(hash, signature)
_rawSignatureValidation(bytes32 hash, bytes signature) → bool
internal使用 EOA 的地址(即 address(this)
)验证签名。
SignerERC7913
import "@openzeppelin/community-contracts/utils/cryptography/signers/SignerERC7913.sol";
使用 ERC-7913 签名验证实现的 AbstractSigner
。
对于 Account
的使用,提供了一个 _setSigner
函数来设置 ERC-7913 格式的 signer
。
这样做对于工厂来说更容易,因为工厂很可能使用此合约的可初始化克隆。
签名者是一个 bytes
对象,它连接一个验证者地址和一个密钥:verifier || key
。
用法示例:
contract MyAccountERC7913 is Account, SignerERC7913, Initializable {
function initialize(bytes memory signer_) public initializer {
_setSigner(signer_);
}
}
如果在构造期间(如果单独使用)或初始化期间(如果用作克隆)未能调用 _setSigner 可能会使签名者容易受到抢跑攻击或无法使用。 |
函数
signer()
_setSigner(signer_)
_rawSignatureValidation(hash, signature)
signer() → bytes
public返回 ERC-7913 签名者(即 verifier || key
)。
_setSigner(bytes signer_)
internal使用 ERC-7913 格式的签名者设置签名者(即 verifier || key
)。
_rawSignatureValidation(bytes32 hash, bytes signature) → bool
internal使用 ERC7913Utils.isValidSignatureNow
验证签名,参数为 signer
、hash
和 signature
。
MultiSignerERC7913
import "@openzeppelin/community-contracts/utils/cryptography/signers/MultiSignerERC7913.sol";
使用具有基于阈值的签名验证系统的多个 ERC-7913 签名者实现的 AbstractSigner
。
此合约允许管理一组授权签名者,并需要最少数量的签名(阈值)来批准操作。它使用 ERC-7913 格式的签名者,该签名者连接一个验证者地址和一个密钥:verifier || key
。
用法示例:
contract MyMultiSignerAccount is Account, MultiSignerERC7913, Initializable {
constructor() EIP712("MyMultiSignerAccount", "1") {}
function initialize(bytes[] memory signers, uint256 threshold) public initializer {
_addSigners(signers);
_setThreshold(threshold);
}
function addSigners(bytes[] memory signers) public onlyEntryPointOrSelf {
_addSigners(signers);
}
function removeSigners(bytes[] memory signers) public onlyEntryPointOrSelf {
_removeSigners(signers);
}
function setThreshold(uint256 threshold) public onlyEntryPointOrSelf {
_setThreshold(threshold);
}
}
如果在构造期间(如果单独使用)或初始化期间(如果用作克隆)未能正确初始化签名者和阈值可能会使合约容易受到抢跑攻击或无法使用。 |
函数
signers()
isSigner(signer)
threshold()
_addSigners(newSigners)
_removeSigners(oldSigners)
_setThreshold(newThreshold)
_validateReachableThreshold()
_rawSignatureValidation(hash, signature)
_validateSignatures(hash, signingSigners, signatures)
_validateThreshold(validatingSigners)
事件
ERC7913SignerAdded(signers)
ERC7913SignerRemoved(signers)
ERC7913ThresholdSet(threshold)
错误
MultiSignerERC7913AlreadyExists(signer)
MultiSignerERC7913NonexistentSigner(signer)
MultiSignerERC7913InvalidSigner(signer)
MultiSignerERC7913UnreachableThreshold(signers, threshold)
signers() → bytes[]
public返回授权签名者的集合。内部使用时首选{_signers}。
此操作将整个签名者集合复制到内存中,这可能很昂贵。这是为在没有 gas 费的情况下查询的视图访问器设计的。如果在状态更改函数中使用它,如果签名者集合变得太大,可能会变得不可调用。 |
isSigner(bytes signer) → bool
public返回 signer
是否为授权签名者。
threshold() → uint256
public返回批准多重签名操作所需的最少签名者数量。
_addSigners(bytes[] newSigners)
internal将 newSigners
添加到那些被允许代表此合约签名的签名者。没有访问控制的内部版本。
要求:
每个 newSigners
的长度必须至少为 20 字节。如果没有,则会抛出 MultiSignerERC7913InvalidSigner
。
每个 newSigners
都不能被授权。参见 isSigner
。如果是,则会抛出 MultiSignerERC7913AlreadyExists
。
_removeSigners(bytes[] oldSigners)
internal从授权签名者中删除 oldSigners
。没有访问控制的内部版本。
要求:
每个 oldSigners
必须被授权。参见 isSigner
。否则,将抛出 MultiSignerERC7913NonexistentSigner
。
有关阈值验证,请参见 _validateReachableThreshold
。
_setThreshold(uint256 newThreshold)
internal设置批准多重签名操作所需的签名 threshold
。没有访问控制的内部版本。
要求:
_validateReachableThreshold
。_validateReachableThreshold()
internal验证当前阈值是否可达。
要求:
signers
的长度必须 >=
threshold
。如果没有,则抛出 MultiSignerERC7913UnreachableThreshold
。_rawSignatureValidation(bytes32 hash, bytes signature) → bool
internal解码、验证签名并检查签名者是否已授权。
有关更多详细信息,请参见 _validateSignatures
和 _validateThreshold
。
签名编码示例:
// 编码签名者 (verifier || key)
bytes memory signer1 = abi.encodePacked(verifier1, key1);
bytes memory signer2 = abi.encodePacked(verifier2, key2);
// 按 id 排序签名者
if (keccak256(signer1) > keccak256(signer2)) {
(signer1, signer2) = (signer2, signer1);
(signature1, signature2) = (signature2, signature1);
}
// 分配排序后的签名者和签名
bytes[] memory signers = new bytes[](2);
bytes[] memory signatures = new bytes[](2);
signers[0] = signer1;
signatures[0] = signature1;
signers[1] = signer2;
signatures[1] = signature2;
// 编码多重签名
bytes memory signature = abi.encode(signers, signatures);
要求:
signature
必须编码为 abi.encode(signers, signatures)
。_validateSignatures(bytes32 hash, bytes[] signingSigners, bytes[] signatures) → bool valid
internal使用签名者及其相应的签名验证签名。 返回签名者是否已授权以及签名对于给定哈希是否有效。
为了简单起见,此合约假定签名者按其keccak256 哈希排序,以避免在迭代签名者时重复(即 keccak256(signer1) < keccak256(signer2) )。如果签名者未排序,该函数将返回 false。 |
要求:
signatures
数组的长度必须至少与signingSigners
数组一样大。否则会panic。_validateThreshold(bytes[] validatingSigners) → bool
internal验证签名者的数量是否满足threshold
的要求。
假定签名者已经过验证。有关更多详细信息,请参见_validateSignatures
。
ERC7913SignerAdded(bytes indexed signers)
event添加签名者时发出。
ERC7913SignerRemoved(bytes indexed signers)
event删除签名者时发出。
ERC7913ThresholdSet(uint256 threshold)
event更新阈值时发出。
MultiSignerERC7913AlreadyExists(bytes signer)
errorsigner
已存在。
MultiSignerERC7913NonexistentSigner(bytes signer)
errorsigner
不存在。
MultiSignerERC7913InvalidSigner(bytes signer)
errorsigner
的长度小于20个字节。
MultiSignerERC7913UnreachableThreshold(uint256 signers, uint256 threshold)
error给定signers
的数量,threshold
无法达到。
MultiSignerERC7913Weighted
import "@openzeppelin/community-contracts/utils/cryptography/signers/MultiSignerERC7913Weighted.sol";
MultiSignerERC7913
的扩展,支持加权签名。
此合约允许为每个签名者分配不同的权重,从而实现更灵活的治理方案。例如,某些签名者的权重可能高于其他签名者,从而允许加权投票或优先授权。
用法示例:
contract MyWeightedMultiSignerAccount is Account, MultiSignerERC7913Weighted, Initializable {
constructor() EIP712("MyWeightedMultiSignerAccount", "1") {}
function initialize(bytes[] memory signers, uint256[] memory weights, uint256 threshold) public initializer {
_addSigners(signers);
_setSignerWeights(signers, weights);
_setThreshold(threshold);
}
function addSigners(bytes[] memory signers) public onlyEntryPointOrSelf {
_addSigners(signers);
}
function removeSigners(bytes[] memory signers) public onlyEntryPointOrSelf {
_removeSigners(signers);
}
function setThreshold(uint256 threshold) public onlyEntryPointOrSelf {
_setThreshold(threshold);
}
function setSignerWeights(bytes[] memory signers, uint256[] memory weights) public onlyEntryPointOrSelf {
_setSignerWeights(signers, weights);
}
}
设置阈值时,请确保它与用于签名者权重的比例匹配。例如,如果签名者具有诸如1、2或3之类的权重,则阈值4将需要至少两个签名者(例如,一个权重为1,一个权重为3)。请参见signerWeight 。 |
函数
signerWeight(signer)
totalWeight()
_signerWeight(signer)
_setSignerWeights(signers, newWeights)
_addSigners(newSigners)
_removeSigners(oldSigners)
_validateReachableThreshold()
_validateThreshold(signers)
_weightSigners(signers)
MultiSignerERC7913
signers()
isSigner(signer)
threshold()
_setThreshold(newThreshold)
_rawSignatureValidation(hash, signature)
_validateSignatures(hash, signingSigners, signatures)
事件
ERC7913SignerWeightChanged(signer, weight)
MultiSignerERC7913
ERC7913SignerAdded(signers)
ERC7913SignerRemoved(signers)
ERC7913ThresholdSet(threshold)
错误
MultiSignerERC7913WeightedInvalidWeight(signer, weight)
MultiSignerERC7913WeightedMismatchedLength()
MultiSignerERC7913
MultiSignerERC7913AlreadyExists(signer)
MultiSignerERC7913NonexistentSigner(signer)
MultiSignerERC7913InvalidSigner(signer)
MultiSignerERC7913UnreachableThreshold(signers, threshold)
signerWeight(bytes signer) → uint256
public获取签名者的权重。如果未授权,则返回0。
totalWeight() → uint256
public获取所有签名者的总权重。
_signerWeight(bytes signer) → uint256
internal获取当前签名者的权重。如果未明确设置,则返回1。
此内部函数不检查签名者是否已授权。 |
_setSignerWeights(bytes[] signers, uint256[] newWeights)
internal一次为多个签名者设置权重。没有访问控制的内部版本。
要求:
signers
和weights
数组的长度必须相同。如果数量不匹配,则会抛出MultiSignerERC7913WeightedMismatchedLength
。
每个签名者都必须存在于授权签名者的集合中。如果不存在,则会抛出MultiSignerERC7913NonexistentSigner
。
每个权重必须大于0。如果不是,则抛出MultiSignerERC7913WeightedInvalidWeight
。
有关阈值验证,请参见_validateReachableThreshold
。
为每个签名者发出ERC7913SignerWeightChanged
。
_addSigners(bytes[] newSigners)
internal将newSigners
添加到那些被允许代表此合约签名的签名者。没有访问控制的内部版本。
要求:
每个newSigners
的长度必须至少为20个字节。如果不是,则会抛出MultiSignerERC7913InvalidSigner
。
每个newSigners
都不能被授权。请参阅isSigner
。如果是,则会抛出MultiSignerERC7913AlreadyExists
。
_removeSigners(bytes[] oldSigners)
internal参见MultiSignerERC7913._removeSigners
。
为每个删除的签名者发出ERC7913SignerWeightChanged
。
_validateReachableThreshold()
internal为多重签名操作设置阈值。没有访问控制的内部版本。
要求:
此函数故意不调用super._validateReachableThreshold ,因为基本实现假定每个签名者的权重为1,这是此加权实现的子集。请注意,合约中可能存在多个此函数,因此根据线性化顺序,可能会遗漏重要的副作用。 |
_validateThreshold(bytes[] signers) → bool
internal验证签名者的总权重是否满足阈值要求。
此函数故意不调用super. _validateThreshold ,因为基本实现假定每个签名者的权重为1,这是此加权实现的子集。请注意,合约中可能存在多个此函数,因此根据线性化顺序,可能会遗漏重要的副作用。 |
_weightSigners(bytes[] signers) → uint256
internal计算一组签名者的总权重。对于所有签名者权重,请使用totalWeight
。
ERC7913SignerWeightChanged(bytes indexed signer, uint256 weight)
event签名者的权重更改时发出。
MultiSignerERC7913WeightedInvalidWeight(bytes signer, uint256 weight)
error签名者的权重无效时抛出。
MultiSignerERC7913WeightedMismatchedLength()
error当阈值无法达到时抛出。
SignerZKEmail
import "@openzeppelin/community-contracts/utils/cryptography/signers/SignerZKEmail.sol";
使用 ZKEmail 签名实现的 AbstractSigner
。
ZKEmail 通过电子邮件消息实现安全身份验证和授权,利用来自 DKIMRegistry
的 DKIM 签名和由 verifier
合约启用的零知识证明,该合约确保电子邮件的真实性而不泄露敏感信息。DKIM 注册表被信任可以正确更新 DKIM 密钥,但是用户可以覆盖此行为并设置自己的密钥。此合约实现了在智能合约验证基于电子邮件的签名的核心功能。
开发人员必须在合约初始化期间设置以下组件:
accountSalt
- 从用户的电子邮件地址和帐户代码派生的唯一标识符。
DKIMRegistry
- 用于域名验证的 DKIM 注册表合约的实例。
verifier
- 用于零知识证明验证的 Verifier 合约的实例。
templateId
- 签名哈希命令的模板ID,定义了期望的格式。
用法示例:
contract MyAccountZKEmail is Account, SignerZKEmail, Initializable {
function initialize(
bytes32 accountSalt,
IDKIMRegistry registry,
IVerifier verifier,
uint256 templateId
) public initializer {
// 如果签名者已经初始化,将回滚
_setAccountSalt(accountSalt);
_setDKIMRegistry(registry);
_setVerifier(verifier);
_setTemplateId(templateId);
}
}
避免调用 _setAccountSalt ,_setDKIMRegistry ,_setVerifier 和 _setTemplateId <br>无论是构造期间(如果单独使用)还是初始化期间(如果用作克隆) 可能会使签名者容易受到抢跑攻击或无法使用。 |
函数
accountSalt()
DKIMRegistry()
verifier()
templateId()
_setAccountSalt(accountSalt_)
_setDKIMRegistry(registry_)
_setVerifier(verifier_)
_setTemplateId(templateId_)
_rawSignatureValidation(hash, signature)
错误
InvalidEmailProof(err)
accountSalt() → bytes32
public此合约所有者的唯一标识符,定义为电子邮件地址和帐户代码的哈希。
帐户代码是 BN254 曲线的有限标量域中的随机整数。 它是用于从电子邮件地址派生用户的 Ethereum 地址的 CREATE2 盐的私有随机性,即 userEtherAddr := CREATE2(hash(userEmailAddr, accountCode))。
帐户盐用于:
隐私:只要随机生成的帐户代码不透露给对手,就可以在链上启用电子邮件地址隐私。
安全性:提供无法轻易猜测或强力破解的唯一标识符,因为它由电子邮件地址和随机帐户代码派生。
确定性地址生成:可以创建基于电子邮件地址的确定性地址,允许用户仅使用其电子邮件恢复其帐户。
DKIMRegistry() → contract IDKIMRegistry
publicDKIM 注册表合约的实例。 请参阅 DKIM 验证。
verifier() → contract IVerifier
publicVerifier 合约的实例。 请参阅 ZK 证明。
templateId() → uint256
public签名哈希命令的命令模板。
_setAccountSalt(bytes32 accountSalt_)
internal设置accountSalt
。
_setDKIMRegistry(contract IDKIMRegistry registry_)
internal设置DKIMRegistry
合约地址。
_setVerifier(contract IVerifier verifier_)
internal设置verifier
合约地址。
_setTemplateId(uint256 templateId_)
internal设置命令的templateId
。
_rawSignatureValidation(bytes32 hash, bytes signature) → bool
internal请参阅AbstractSigner._rawSignatureValidation
。通过以下方式验证原始签名:
从签名中解码电子邮件身份验证消息
验证哈希是否与命令参数匹配
检查模板ID是否匹配
验证帐户盐
验证电子邮件证明
InvalidEmailProof(enum ZKEmailUtils.EmailProofError err)
error证明验证错误。
SignerWebAuthn
import "@openzeppelin/community-contracts/utils/cryptography/signers/SignerWebAuthn.sol";
支持 WebAuthn签名预期是 abi 编码的 WebAuthn.WebAuthnAuth
结构体。
使用示例:
contract MyAccountWebAuthn is Account, SignerWebAuthn, Initializable {
function initialize(bytes32 qx, bytes32 qy) public initializer {
_setSigner(qx, qy);
}
}
未能调用 _setSigner ,无论是在构造期间(如果单独使用)<br>还是在初始化期间(如果用作克隆),都可能导致签名者容易被抢跑或无法使用。 |
函数
_rawSignatureValidation(hash, signature)
SignerP256
_setSigner(qx, qy)
signer()
错误
SignerP256
SignerP256InvalidPublicKey(qx, qy)
_rawSignatureValidation(bytes32 hash, bytes signature) → bool
internal使用 WebAuthn 身份验证断言验证原始签名。
如果签名无法验证,则回退到
SignerP256._rawSignatureValidation
方法,通过传递原始签名中的 r
和 s
值来进行原始 P256 签名验证。
ERC7913P256Verifier
import "@openzeppelin/community-contracts/utils/cryptography/verifiers/ERC7913P256Verifier.sol";
ERC-7913 签名验证器,支持 P256 (secp256r1) 密钥。
函数
verify(key, hash, signature)
verify(bytes key, bytes32 hash, bytes signature) → bytes4
public验证 signature
是否为 key
对 hash
的有效签名。
如果签名有效,则必须返回 bytes4 魔法值 IERC7913SignatureVerifier.verify.selector。 如果签名无效,则应该返回 0xffffffff 或 revert。 如果密钥为空,则应该返回 0xffffffff 或 revert
ERC7913RSAVerifier
import "@openzeppelin/community-contracts/utils/cryptography/verifiers/ERC7913RSAVerifier.sol";
ERC-7913 签名验证器,支持 RSA 密钥。
函数
verify(key, hash, signature)
verify(bytes key, bytes32 hash, bytes signature) → bytes4
public验证 signature
是否为 key
对 hash
的有效签名。
如果签名有效,则必须返回 bytes4 魔法值 IERC7913SignatureVerifier.verify.selector。 如果签名无效,则应该返回 0xffffffff 或 revert。 如果密钥为空,则应该返回 0xffffffff 或 revert
ERC7913ZKEmailVerifier
import "@openzeppelin/community-contracts/utils/cryptography/verifiers/ERC7913ZKEmailVerifier.sol";
ERC-7913 签名验证器,支持 ZKEmail 帐户。
此合约验证通过 ZKEmail 的零知识 证明生成的签名,允许用户使用其电子邮件地址进行身份验证。
密钥解码逻辑是可自定义的:用户可以覆盖 _decodeKey
函数
以强制执行对解码值的限制或验证(例如,要求特定的验证器、templateId 或注册表)。为了保持符合 ERC-7913 的无状态性,
建议仅使用 immutable 变量来强制执行此类限制。
覆盖 _decodeKey 以强制执行特定验证器、注册表(或 templateId)的示例:
function _decodeKey(bytes calldata key) internal view override returns (
IDKIMRegistry registry,
bytes32 accountSalt,
IVerifier verifier,
uint256 templateId
) {
(registry, accountSalt, verifier, templateId) = super._decodeKey(key);
require(verifier == _verifier, "Invalid verifier");
require(registry == _registry, "Invalid registry");
return (registry, accountSalt, verifier, templateId);
}
函数
verify(key, hash, signature)
_decodeKey(key)
verify(bytes key, bytes32 hash, bytes signature) → bytes4
public验证由 DKIMRegistry
合约验证的电子邮件签名的零知识证明。
密钥格式是 ABI 编码的 (IDKIMRegistry, bytes32, IVerifier, uint256),其中:
IDKIMRegistry:验证 DKIM 公钥哈希的注册表合约
bytes32:唯一标识用户电子邮件地址的帐户 salt
IVerifier:ZK 证明验证的验证器合约实例。
uint256:命令的模板 ID
有关密钥编码格式,请参见 _decodeKey
。
签名是包含命令参数、模板 ID 和证明详细信息的 ABI 编码的 {ZKEmailUtils-EmailAuthMsg} 结构体。
签名编码:
bytes memory signature = abi.encode(EmailAuthMsg({
templateId: 1,
commandParams: [hash],
proof: {
domainName: "example.com", // 电子邮件发件人的域名
publicKeyHash: bytes32(0x...), // 用于签署电子邮件的 DKIM 公钥的哈希
timestamp: block.timestamp, // 发送电子邮件的时间
maskedCommand: "Sign hash", // 正在执行的命令,包含敏感数据被遮盖
emailNullifier: bytes32(0x...), // 电子邮件的唯一标识符,用于防止重放攻击
accountSalt: bytes32(0x...), // 从电子邮件和帐户代码派生的唯一标识符
isCodeExist: true, // 证明中是否存在帐户代码
proof: bytes(0x...) // 验证电子邮件真实性的零知识证明
}
}));
_decodeKey(bytes key) → contract IDKIMRegistry registry, bytes32 accountSalt, contract IVerifier verifier, uint256 templateId
internal将密钥解码为其组成部分。
bytes memory key = abi.encode(registry, accountSalt, verifier, templateId);
- 原文链接: docs.openzeppelin.com/co...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!