Cryptography
This document is better viewed at https://docs.openzeppelin.com/contracts/api/utils/cryptography |
A collection of contracts and libraries that implement various signature validation schemes and cryptographic primitives. These utilities enable secure authentication, multisignature operations, and advanced cryptographic operations in smart contracts.
-
ECDSA
,MessageHashUtils
: Libraries for interacting with ECDSA signatures. -
P256
: Library for verifying and recovering public keys from secp256r1 signatures. -
RSA
: Library with RSA PKCS#1 v1.5 signature verification utilities. -
SignatureChecker
: A library helper to support regular ECDSA from EOAs as well as ERC-1271 signatures for smart contracts. -
Hashes
: Commonly used hash functions. -
MerkleProof
: Functions for verifying Merkle Tree proofs. -
EIP712
: Contract with functions to allow processing signed typed structure data according to EIP-712. -
ERC7739Utils
: Utilities library that implements a defensive rehashing mechanism to prevent replayability of smart contract signatures based on ERC-7739. -
AbstractSigner
: Abstract contract for internal signature validation in smart contracts. -
ERC7739
: An abstract contract to validate signatures following the rehashing scheme fromERC7739Utils
. -
SignerECDSA
,SignerP256
,SignerRSA
: Implementations of anAbstractSigner
with specific signature validation algorithms. -
SignerERC7702
: Implementation ofAbstractSigner
that validates signatures using the contract’s own address as the signer, useful for delegated accounts following EIP-7702. -
SignerERC7913
,MultiSignerERC7913
,MultiSignerERC7913Weighted
: Implementations ofAbstractSigner
that validate signatures based on ERC-7913. Including a simple and weighted multisignature scheme. -
ERC7913P256Verifier
,ERC7913RSAVerifier
: Ready to use ERC-7913 signature verifiers for P256 and RSA keys.
Utils
ECDSA
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
These functions can be used to verify that a message was signed by the holder of the private keys of a given address.
tryRecover(bytes32 hash, bytes signature) → address recovered, enum ECDSA.RecoverError err, bytes32 errArg
internal
Returns the address that signed a hashed message (hash
) with signature
or an error. This will not
return address(0) without also returning an error description. Errors are documented using an enum (error type)
and a bytes32 providing additional information about the error.
If no error is returned, then the address can be used for verification purposes.
The ecrecover
EVM precompile allows for malleable (non-unique) signatures:
this function rejects them by requiring the s
value to be in the lower
half order, and the v
value to be either 27 or 28.
hash must be the result of a hash operation for the
verification to be secure: it is possible to craft signatures that
recover to arbitrary addresses for non-hashed data. A safe way to ensure
this is by receiving a hash of the original message (which may otherwise
be too long), and then calling MessageHashUtils.toEthSignedMessageHash on it.
|
recover(bytes32 hash, bytes signature) → address
internal
Returns the address that signed a hashed message (hash
) with
signature
. This address can then be used for verification purposes.
The ecrecover
EVM precompile allows for malleable (non-unique) signatures:
this function rejects them by requiring the s
value to be in the lower
half order, and the v
value to be either 27 or 28.
hash must be the result of a hash operation for the
verification to be secure: it is possible to craft signatures that
recover to arbitrary addresses for non-hashed data. A safe way to ensure
this is by receiving a hash of the original message (which may otherwise
be too long), and then calling MessageHashUtils.toEthSignedMessageHash on it.
|
tryRecover(bytes32 hash, bytes32 r, bytes32 vs) → address recovered, enum ECDSA.RecoverError err, bytes32 errArg
internal
Overload of ECDSA.tryRecover
that receives the r
and vs
short-signature fields separately.
recover(bytes32 hash, bytes32 r, bytes32 vs) → address
internal
Overload of ECDSA.recover
that receives the r and `vs
short-signature fields separately.
tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) → address recovered, enum ECDSA.RecoverError err, bytes32 errArg
internal
Overload of ECDSA.tryRecover
that receives the v
,
r
and s
signature fields separately.
recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) → address
internal
Overload of ECDSA.recover
that receives the v
,
r
and s
signature fields separately.
MessageHashUtils
import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
Signature message hash utilities for producing digests to be consumed by ECDSA
recovery or signing.
The library provides methods for generating a hash of a message that conforms to the ERC-191 and EIP 712 specifications.
toEthSignedMessageHash(bytes32 messageHash) → bytes32 digest
internal
Returns the keccak256 digest of an ERC-191 signed data with version
0x45
(personal_sign
messages).
The digest is calculated by prefixing a bytes32 messageHash
with
"\x19Ethereum Signed Message:\n32"
and hashing the result. It corresponds with the
hash signed when using the eth_sign
JSON-RPC method.
The messageHash parameter is intended to be the result of hashing a raw message with
keccak256, although any bytes32 value can be safely used because the final digest will
be re-hashed.
|
See ECDSA.recover
.
toEthSignedMessageHash(bytes message) → bytes32
internal
Returns the keccak256 digest of an ERC-191 signed data with version
0x45
(personal_sign
messages).
The digest is calculated by prefixing an arbitrary message
with
"\x19Ethereum Signed Message:\n" + len(message)
and hashing the result. It corresponds with the
hash signed when using the eth_sign
JSON-RPC method.
See ECDSA.recover
.
toDataWithIntendedValidatorHash(address validator, bytes data) → bytes32
internal
Returns the keccak256 digest of an ERC-191 signed data with version
0x00
(data with intended validator).
The digest is calculated by prefixing an arbitrary data
with "\x19\x00"
and the intended
validator
address. Then hashing the result.
See ECDSA.recover
.
toDataWithIntendedValidatorHash(address validator, bytes32 messageHash) → bytes32 digest
internal
Variant of toDataWithIntendedValidatorHash
optimized for cases where data
is a bytes32.
toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) → bytes32 digest
internal
Returns the keccak256 digest of an EIP-712 typed data (ERC-191 version 0x01
).
The digest is calculated from a domainSeparator
and a structHash
, by prefixing them with
\x19\x01
and hashing the result. It corresponds to the hash signed by the
eth_signTypedData
JSON-RPC method as part of EIP-712.
See ECDSA.recover
.
P256
import "@openzeppelin/contracts/utils/cryptography/P256.sol";
Implementation of secp256r1 verification and recovery functions.
The secp256r1 curve (also known as P256) is a NIST standard curve with wide support in modern devices and cryptographic standards. Some notable examples include Apple’s Secure Enclave and Android’s Keystore as well as authentication protocols like FIDO2.
Based on the original implementation of itsobvioustech (GNU General Public License v3.0). Heavily inspired in maxrobot and tdrerup implementations.
Available since v5.1.
verify(bytes32 h, bytes32 r, bytes32 s, bytes32 qx, bytes32 qy) → bool
internal
Verifies a secp256r1 signature using the RIP-7212 precompile and falls back to the Solidity implementation if the precompile is not available. This version should work on all chains, but requires the deployment of more bytecode.
verifyNative(bytes32 h, bytes32 r, bytes32 s, bytes32 qx, bytes32 qy) → bool
internal
Same as verify
, but it will revert if the required precompile is not available.
Make sure any logic (code or precompile) deployed at that address is the expected one, otherwise the returned value may be misinterpreted as a positive boolean.
verifySolidity(bytes32 h, bytes32 r, bytes32 s, bytes32 qx, bytes32 qy) → bool
internal
Same as verify
, but only the Solidity implementation is used.
recovery(bytes32 h, uint8 v, bytes32 r, bytes32 s) → bytes32 x, bytes32 y
internal
Public key recovery
RSA
import "@openzeppelin/contracts/utils/cryptography/RSA.sol";
RSA PKCS#1 v1.5 signature verification implementation according to RFC8017.
This library supports PKCS#1 v1.5 padding to avoid malleability via chosen plaintext attacks in practical implementations. The padding follows the EMSA-PKCS1-v1_5-ENCODE encoding definition as per section 9.2 of the RFC. This padding makes RSA semantically secure for signing messages.
Inspired by Adrià Massanet’s work (GNU General Public License v3.0).
Available since v5.1.
pkcs1Sha256(bytes data, bytes s, bytes e, bytes n) → bool
internal
Same as pkcs1Sha256
but using SHA256 to calculate the digest of data
.
pkcs1Sha256(bytes32 digest, bytes s, bytes e, bytes n) → bool
internal
Verifies a PKCSv1.5 signature given a digest according to the verification method described in section 8.2.2 of RFC8017 with support for explicit or implicit NULL parameters in the DigestInfo (no other optional parameters are supported).
For security reason, this function requires the signature and modulus to have a length of at least 2048 bits. If you use a smaller key, consider replacing it with a larger, more secure, one. |
This verification algorithm doesn’t prevent replayability. If called multiple times with the same digest, public key and (valid signature), it will return true every time. Consider including an onchain nonce or unique identifier in the message to prevent replay attacks. |
This verification algorithm supports any exponent. NIST recommends using 65537 (or higher).
That is the default value many libraries use, such as OpenSSL. Developers may choose to reject public keys
using a low exponent out of security concerns.
|
SignatureChecker
import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
Signature verification helper that can be used instead of ECDSA.recover
to seamlessly support:
-
ECDSA signatures from externally owned accounts (EOAs)
-
ERC-1271 signatures from smart contract wallets like Argent and Safe Wallet (previously Gnosis Safe)
-
ERC-7913 signatures from keys that do not have an Ethereum address of their own
isValidSignatureNow(address signer, bytes32 hash, bytes signature) → bool
internal
Checks if a signature is valid for a given signer and data hash. If the signer has code, the
signature is validated against it using ERC-1271, otherwise it’s validated using ECDSA.recover
.
Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus change through time. It could return true at block N and false at block N+1 (or the opposite). |
For an extended version of this function that supports ERC-7913 signatures, see {isValidSignatureNow-bytes-bytes32-bytes-}. |
isValidERC1271SignatureNow(address signer, bytes32 hash, bytes signature) → bool
internal
Checks if a signature is valid for a given signer and data hash. The signature is validated against the signer smart contract using ERC-1271.
Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus change through time. It could return true at block N and false at block N+1 (or the opposite). |
isValidSignatureNow(bytes signer, bytes32 hash, bytes signature) → bool
internal
Verifies a signature for a given ERC-7913 signer and hash.
The signer is a bytes
object that is the concatenation of an address and optionally a key:
verifier || key
. A signer must be at least 20 bytes long.
Verification is done as follows:
-
If
signer.length < 20
: verification fails -
If
signer.length == 20
: verification is done usingisValidSignatureNow
-
Otherwise: verification is done using
IERC7913SignatureVerifier
Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus change through time. It could return true at block N and false at block N+1 (or the opposite). |
areValidSignaturesNow(bytes32 hash, bytes[] signers, bytes[] signatures) → bool
internal
Verifies multiple ERC-7913 signatures
for a given hash
using a set of signers
.
Returns false
if the number of signers and signatures is not the same.
The signers should be ordered by their keccak256
hash to ensure efficient duplication check. Unordered
signers are supported, but the uniqueness check will be more expensive.
Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus change through time. It could return true at block N and false at block N+1 (or the opposite). |
Hashes
import "@openzeppelin/contracts/utils/cryptography/Hashes.sol";
Library of standard hash functions.
Available since v5.1.
commutativeKeccak256(bytes32 a, bytes32 b) → bytes32
internal
Commutative Keccak256 hash of a sorted pair of bytes32. Frequently used when working with merkle proofs.
Equivalent to the standardNodeHash in our JavaScript library.
|
MerkleProof
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
These functions deal with verification of Merkle Tree proofs.
The tree and the proofs can be generated using our JavaScript library. You will find a quickstart guide in the readme.
You should avoid using leaf values that are 64 bytes long prior to hashing, or use a hash function other than keccak256 for hashing leaves. This is because the concatenation of a sorted pair of internal nodes in the Merkle tree could be reinterpreted as a leaf value. OpenZeppelin’s JavaScript library generates Merkle trees that are safe against this attack out of the box. |
Consider memory side-effects when using custom hashing functions that access memory in an unsafe way. |
This library supports proof verification for merkle trees built using
custom commutative hashing functions (i.e. H(a, b) == H(b, a) ). Proving
leaf inclusion in trees built using non-commutative hashing functions requires
additional logic that is not supported by this library.
|
verify(bytes32[] proof, bytes32 root, bytes32 leaf) → bool
internal
Returns true if a leaf
can be proved to be a part of a Merkle tree
defined by root
. For this, a proof
must be provided, containing
sibling hashes on the branch from the leaf to the root of the tree. Each
pair of leaves and each pair of pre-images are assumed to be sorted.
This version handles proofs in memory with the default hashing function.
processProof(bytes32[] proof, bytes32 leaf) → bytes32
internal
Returns the rebuilt hash obtained by traversing a Merkle tree up
from leaf
using proof
. A proof
is valid if and only if the rebuilt
hash matches the root of the tree. When processing the proof, the pairs
of leaves & pre-images are assumed to be sorted.
This version handles proofs in memory with the default hashing function.
verify(bytes32[] proof, bytes32 root, bytes32 leaf, function (bytes32,bytes32) view returns (bytes32) hasher) → bool
internal
Returns true if a leaf
can be proved to be a part of a Merkle tree
defined by root
. For this, a proof
must be provided, containing
sibling hashes on the branch from the leaf to the root of the tree. Each
pair of leaves and each pair of pre-images are assumed to be sorted.
This version handles proofs in memory with a custom hashing function.
processProof(bytes32[] proof, bytes32 leaf, function (bytes32,bytes32) view returns (bytes32) hasher) → bytes32
internal
Returns the rebuilt hash obtained by traversing a Merkle tree up
from leaf
using proof
. A proof
is valid if and only if the rebuilt
hash matches the root of the tree. When processing the proof, the pairs
of leaves & pre-images are assumed to be sorted.
This version handles proofs in memory with a custom hashing function.
verifyCalldata(bytes32[] proof, bytes32 root, bytes32 leaf) → bool
internal
Returns true if a leaf
can be proved to be a part of a Merkle tree
defined by root
. For this, a proof
must be provided, containing
sibling hashes on the branch from the leaf to the root of the tree. Each
pair of leaves and each pair of pre-images are assumed to be sorted.
This version handles proofs in calldata with the default hashing function.
processProofCalldata(bytes32[] proof, bytes32 leaf) → bytes32
internal
Returns the rebuilt hash obtained by traversing a Merkle tree up
from leaf
using proof
. A proof
is valid if and only if the rebuilt
hash matches the root of the tree. When processing the proof, the pairs
of leaves & pre-images are assumed to be sorted.
This version handles proofs in calldata with the default hashing function.
verifyCalldata(bytes32[] proof, bytes32 root, bytes32 leaf, function (bytes32,bytes32) view returns (bytes32) hasher) → bool
internal
Returns true if a leaf
can be proved to be a part of a Merkle tree
defined by root
. For this, a proof
must be provided, containing
sibling hashes on the branch from the leaf to the root of the tree. Each
pair of leaves and each pair of pre-images are assumed to be sorted.
This version handles proofs in calldata with a custom hashing function.
processProofCalldata(bytes32[] proof, bytes32 leaf, function (bytes32,bytes32) view returns (bytes32) hasher) → bytes32
internal
Returns the rebuilt hash obtained by traversing a Merkle tree up
from leaf
using proof
. A proof
is valid if and only if the rebuilt
hash matches the root of the tree. When processing the proof, the pairs
of leaves & pre-images are assumed to be sorted.
This version handles proofs in calldata with a custom hashing function.
multiProofVerify(bytes32[] proof, bool[] proofFlags, bytes32 root, bytes32[] leaves) → bool
internal
Returns true if the leaves
can be simultaneously proven to be a part of a Merkle tree defined by
root
, according to proof
and proofFlags
as described in processMultiProof
.
This version handles multiproofs in memory with the default hashing function.
Not all Merkle trees admit multiproofs. See processMultiProof for details.
|
Consider the case where root == proof[0] && leaves.length == 0 as it will return true .
The leaves must be validated independently. See processMultiProof .
|
processMultiProof(bytes32[] proof, bool[] proofFlags, bytes32[] leaves) → bytes32 merkleRoot
internal
Returns the root of a tree reconstructed from leaves
and sibling nodes in proof
. The reconstruction
proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
leaf/inner node or a proof sibling node, depending on whether each proofFlags
item is true or false
respectively.
This version handles multiproofs in memory with the default hashing function.
Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). |
The empty set (i.e. the case where proof.length == 1 && leaves.length == 0 ) is considered a no-op,
and therefore a valid multiproof (i.e. it returns proof[0] ). Consider disallowing this case if you’re not
validating the leaves elsewhere.
|
multiProofVerify(bytes32[] proof, bool[] proofFlags, bytes32 root, bytes32[] leaves, function (bytes32,bytes32) view returns (bytes32) hasher) → bool
internal
Returns true if the leaves
can be simultaneously proven to be a part of a Merkle tree defined by
root
, according to proof
and proofFlags
as described in processMultiProof
.
This version handles multiproofs in memory with a custom hashing function.
Not all Merkle trees admit multiproofs. See processMultiProof for details.
|
Consider the case where root == proof[0] && leaves.length == 0 as it will return true .
The leaves must be validated independently. See processMultiProof .
|
processMultiProof(bytes32[] proof, bool[] proofFlags, bytes32[] leaves, function (bytes32,bytes32) view returns (bytes32) hasher) → bytes32 merkleRoot
internal
Returns the root of a tree reconstructed from leaves
and sibling nodes in proof
. The reconstruction
proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
leaf/inner node or a proof sibling node, depending on whether each proofFlags
item is true or false
respectively.
This version handles multiproofs in memory with a custom hashing function.
Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). |
The empty set (i.e. the case where proof.length == 1 && leaves.length == 0 ) is considered a no-op,
and therefore a valid multiproof (i.e. it returns proof[0] ). Consider disallowing this case if you’re not
validating the leaves elsewhere.
|
multiProofVerifyCalldata(bytes32[] proof, bool[] proofFlags, bytes32 root, bytes32[] leaves) → bool
internal
Returns true if the leaves
can be simultaneously proven to be a part of a Merkle tree defined by
root
, according to proof
and proofFlags
as described in processMultiProof
.
This version handles multiproofs in calldata with the default hashing function.
Not all Merkle trees admit multiproofs. See processMultiProof for details.
|
Consider the case where root == proof[0] && leaves.length == 0 as it will return true .
The leaves must be validated independently. See processMultiProofCalldata .
|
processMultiProofCalldata(bytes32[] proof, bool[] proofFlags, bytes32[] leaves) → bytes32 merkleRoot
internal
Returns the root of a tree reconstructed from leaves
and sibling nodes in proof
. The reconstruction
proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
leaf/inner node or a proof sibling node, depending on whether each proofFlags
item is true or false
respectively.
This version handles multiproofs in calldata with the default hashing function.
Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). |
The empty set (i.e. the case where proof.length == 1 && leaves.length == 0 ) is considered a no-op,
and therefore a valid multiproof (i.e. it returns proof[0] ). Consider disallowing this case if you’re not
validating the leaves elsewhere.
|
multiProofVerifyCalldata(bytes32[] proof, bool[] proofFlags, bytes32 root, bytes32[] leaves, function (bytes32,bytes32) view returns (bytes32) hasher) → bool
internal
Returns true if the leaves
can be simultaneously proven to be a part of a Merkle tree defined by
root
, according to proof
and proofFlags
as described in processMultiProof
.
This version handles multiproofs in calldata with a custom hashing function.
Not all Merkle trees admit multiproofs. See processMultiProof for details.
|
Consider the case where root == proof[0] && leaves.length == 0 as it will return true .
The leaves must be validated independently. See processMultiProofCalldata .
|
processMultiProofCalldata(bytes32[] proof, bool[] proofFlags, bytes32[] leaves, function (bytes32,bytes32) view returns (bytes32) hasher) → bytes32 merkleRoot
internal
Returns the root of a tree reconstructed from leaves
and sibling nodes in proof
. The reconstruction
proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
leaf/inner node or a proof sibling node, depending on whether each proofFlags
item is true or false
respectively.
This version handles multiproofs in calldata with a custom hashing function.
Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). |
The empty set (i.e. the case where proof.length == 1 && leaves.length == 0 ) is considered a no-op,
and therefore a valid multiproof (i.e. it returns proof[0] ). Consider disallowing this case if you’re not
validating the leaves elsewhere.
|
EIP712
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
EIP-712 is a standard for hashing and signing of typed structured data.
The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose
encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract
does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to
produce the hash of their typed data using a combination of abi.encode
and keccak256
.
This contract implements the EIP-712 domain separator (_domainSeparatorV4
) that is used as part of the encoding
scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
(_hashTypedDataV4
).
The implementation of the domain separator was designed to be as efficient as possible while still properly updating the chain id to protect against replay attacks on an eventual fork of the chain.
This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
eth_signTypedDataV4 in MetaMask.
|
In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
separator of the implementation contract. This will cause the _domainSeparatorV4 function to always rebuild the
separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
|
constructor(string name, string version)
internal
Initializes the domain separator and parameter caches.
The meaning of name
and version
is specified in
EIP-712:
-
name
: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. -
version
: the current major version of the signing domain.
These parameters cannot be changed except through a smart contract upgrade. |
_hashTypedDataV4(bytes32 structHash) → bytes32
internal
Given an already hashed struct, this function returns the hash of the fully encoded EIP712 message for this domain.
This hash can be used together with ECDSA.recover
to obtain the signer of a message. For example:
bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
keccak256("Mail(address to,string contents)"),
mailTo,
keccak256(bytes(mailContents))
)));
address signer = ECDSA.recover(digest, signature);
eip712Domain() → bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions
public
returns the fields and values that describe the domain separator used by this contract for EIP-712 signature.
ERC7739Utils
import "@openzeppelin/contracts/utils/cryptography/draft-ERC7739Utils.sol";
Utilities to process ERC-7739 typed data signatures that are specific to an EIP-712 domain.
This library provides methods to wrap, unwrap and operate over typed data signatures with a defensive rehashing mechanism that includes the app’s EIP-712 and preserves readability of the signed content using an EIP-712 nested approach.
A smart contract domain can validate a signature for a typed data structure in two ways:
-
As an application validating a typed data signature. See
typedDataSignStructHash
. -
As a smart contract validating a raw message signature. See
personalSignStructHash
.
A provider for a smart contract wallet would need to return this signature as the
result of a call to personal_sign or eth_signTypedData , and this may be unsupported by
API clients that expect a return value of 129 bytes, or specifically the r,s,v parameters
of an ECDSA signature, as is for example specified for
EIP-712.
|
encodeTypedDataSig(bytes signature, bytes32 appSeparator, bytes32 contentsHash, string contentsDescr) → bytes
internal
Nest a signature for a given EIP-712 type into a nested signature for the domain of the app.
Counterpart of decodeTypedDataSig
to extract the original signature and the nested components.
decodeTypedDataSig(bytes encodedSignature) → bytes signature, bytes32 appSeparator, bytes32 contentsHash, string contentsDescr
internal
Parses a nested signature into its components.
Constructed as follows:
signature ‖ APP_DOMAIN_SEPARATOR ‖ contentsHash ‖ contentsDescr ‖ uint16(contentsDescr.length)
-
signature
is the signature for the (ERC-7739) nested struct hash. This signature indirectly signs over the original "contents" hash (from the app) and the account’s domain separator. -
APP_DOMAIN_SEPARATOR
is the EIP-712EIP712._domainSeparatorV4
of the application smart contract that is requesting the signature verification (though ERC-1271). -
contentsHash
is the hash of the underlying data structure or message. -
contentsDescr
is a descriptor of the "contents" part of the the EIP-712 type of the nested signature.
This function returns empty if the input format is invalid instead of reverting. data instead. |
personalSignStructHash(bytes32 contents) → bytes32
internal
Nests an ERC-191
digest into a PersonalSign
EIP-712 struct, and returns the corresponding struct hash.
This struct hash must be combined with a domain separator, using MessageHashUtils.toTypedDataHash
before
being verified/recovered.
This is used to simulates the personal_sign
RPC method in the context of smart contracts.
typedDataSignStructHash(string contentsName, string contentsType, bytes32 contentsHash, bytes domainBytes) → bytes32 result
internal
Nests an EIP-712
hash (contents
) into a TypedDataSign
EIP-712 struct, and returns the corresponding
struct hash. This struct hash must be combined with a domain separator, using MessageHashUtils.toTypedDataHash
before being verified/recovered.
typedDataSignStructHash(string contentsDescr, bytes32 contentsHash, bytes domainBytes) → bytes32 result
internal
Variant of typedDataSignStructHash
that takes a content descriptor
and decodes the contentsName
and contentsType
out of it.
typedDataSignTypehash(string contentsName, string contentsType) → bytes32
internal
Compute the EIP-712 typehash of the TypedDataSign
structure for a given type (and typename).
decodeContentsDescr(string contentsDescr) → string contentsName, string contentsType
internal
Parse the type name out of the ERC-7739 contents type description. Supports both the implicit and explicit modes.
Following ERC-7739 specifications, a contentsName
is considered invalid if it’s empty or it contains
any of the following bytes , )\x00
If the contentsType
is invalid, this returns an empty string. Otherwise, the return string has non-zero
length.
Abstract Signers
AbstractSigner
import "@openzeppelin/contracts/utils/cryptography/signers/AbstractSigner.sol";
Abstract contract for signature validation.
Developers must implement _rawSignatureValidation
and use it as the lowest-level signature validation mechanism.
@custom:stateless
ERC7739
import "@openzeppelin/contracts/utils/cryptography/signers/draft-ERC7739.sol";
Validates signatures wrapping the message hash in a nested EIP712 type. See ERC7739Utils
.
Linking the signature to the EIP-712 domain separator is a security measure to prevent signature replay across different EIP-712 domains (e.g. a single offchain owner of multiple contracts).
This contract requires implementing the _rawSignatureValidation
function, which passes the wrapped message hash,
which may be either an typed data or a personal sign nested type.
EIP-712 uses ShortStrings to optimize gas costs for short strings (up to 31 characters). Consider that strings longer than that will use storage, which may limit the ability of the signer to be used within the ERC-4337 validation phase (due to ERC-7562 storage access rules). |
isValidSignature(bytes32 hash, bytes signature) → bytes4 result
public
Attempts validating the signature in a nested EIP-712 type.
A nested EIP-712 type might be presented in 2 different ways:
-
As a nested EIP-712 typed data
-
As a personal signature (an EIP-712 mimic of the
eth_personalSign
for a smart contract)
SignerECDSA
import "@openzeppelin/contracts/utils/cryptography/signers/SignerECDSA.sol";
Implementation of AbstractSigner
using ECDSA signatures.
For Account
usage, a _setSigner
function is provided to set the signer
address.
Doing so is easier for a factory, who is likely to use initializable clones of this contract.
Example of usage:
contract MyAccountECDSA is Account, SignerECDSA, Initializable {
function initialize(address signerAddr) public initializer {
_setSigner(signerAddr);
}
}
Failing to call _setSigner either during construction (if used standalone)
or during initialization (if used as a clone) may leave the signer either front-runnable or unusable.
|
_setSigner(address signerAddr)
internal
Sets the signer with the address of the native signer. This function should be called during construction or through an initializer.
SignerP256
import "@openzeppelin/contracts/utils/cryptography/signers/SignerP256.sol";
Implementation of AbstractSigner
using P256 signatures.
For Account
usage, a _setSigner
function is provided to set the signer
public key.
Doing so is easier for a factory, who is likely to use initializable clones of this contract.
Example of usage:
contract MyAccountP256 is Account, SignerP256, Initializable {
function initialize(bytes32 qx, bytes32 qy) public initializer {
_setSigner(qx, qy);
}
}
Failing to call _setSigner either during construction (if used standalone)
or during initialization (if used as a clone) may leave the signer either front-runnable or unusable.
|
_setSigner(bytes32 qx, bytes32 qy)
internal
Sets the signer with a P256 public key. This function should be called during construction or through an initializer.
SignerRSA
import "@openzeppelin/contracts/utils/cryptography/signers/SignerRSA.sol";
Implementation of AbstractSigner
using RSA signatures.
For Account
usage, a _setSigner
function is provided to set the signer
public key.
Doing so is easier for a factory, who is likely to use initializable clones of this contract.
Example of usage:
contract MyAccountRSA is Account, SignerRSA, Initializable {
function initialize(bytes memory e, bytes memory n) public initializer {
_setSigner(e, n);
}
}
Failing to call _setSigner either during construction (if used standalone)
or during initialization (if used as a clone) may leave the signer either front-runnable or unusable.
|
_setSigner(bytes e, bytes n)
internal
Sets the signer with a RSA public key. This function should be called during construction or through an initializer.
_rawSignatureValidation(bytes32 hash, bytes signature) → bool
internal
See AbstractSigner._rawSignatureValidation
. Verifies a PKCSv1.5 signature by calling
RSA.pkcs1Sha256.
Following the RSASSA-PKCS1-V1_5-VERIFY procedure outlined in RFC8017 (section 8.2.2), the
provided hash is used as the M (message) and rehashed using SHA256 according to EMSA-PKCS1-v1_5
encoding as per section 9.2 (step 1) of the RFC.
|
SignerERC7702
import "@openzeppelin/contracts/utils/cryptography/signers/SignerERC7702.sol";
Implementation of AbstractSigner
for implementation for an EOA. Useful for ERC-7702 accounts.
@custom:stateless
SignerERC7913
import "@openzeppelin/contracts/utils/cryptography/signers/SignerERC7913.sol";
Implementation of AbstractSigner
using
ERC-7913 signature verification.
For Account
usage, a _setSigner
function is provided to set the ERC-7913 formatted signer
.
Doing so is easier for a factory, who is likely to use initializable clones of this contract.
The signer is a bytes
object that concatenates a verifier address and a key: verifier || key
.
Example of usage:
contract MyAccountERC7913 is Account, SignerERC7913, Initializable {
function initialize(bytes memory signer_) public initializer {
_setSigner(signer_);
}
function setSigner(bytes memory signer_) public onlyEntryPointOrSelf {
_setSigner(signer_);
}
}
Failing to call _setSigner either during construction (if used standalone)
or during initialization (if used as a clone) may leave the signer either front-runnable or unusable.
|
_setSigner(bytes signer_)
internal
Sets the signer (i.e. verifier || key
) with an ERC-7913 formatted signer.
_rawSignatureValidation(bytes32 hash, bytes signature) → bool
internal
Verifies a signature using {SignatureChecker-isValidSignatureNow-bytes-bytes32-bytes-}
with signer
, hash
and signature
.
MultiSignerERC7913
import "@openzeppelin/contracts/utils/cryptography/signers/MultiSignerERC7913.sol";
Implementation of AbstractSigner
using multiple ERC-7913 signers with a threshold-based
signature verification system.
This contract allows managing a set of authorized signers and requires a minimum number of signatures (threshold) to approve operations. It uses ERC-7913 formatted signers, which makes it natively compatible with ECDSA and ERC-1271 signers.
Example of usage:
contract MyMultiSignerAccount is Account, MultiSignerERC7913, Initializable {
function initialize(bytes[] memory signers, uint64 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(uint64 threshold) public onlyEntryPointOrSelf {
_setThreshold(threshold);
}
}
Failing to properly initialize the signers and threshold either during construction (if used standalone) or during initialization (if used as a clone) may leave the contract either front-runnable or unusable. |
getSigners(uint64 start, uint64 end) → bytes[]
public
Returns a slice of the set of authorized signers.
Using start = 0
and end = type(uint64).max
will return the entire set of signers.
Depending on the start and end , this operation can copy a large amount of data to memory, which
can be expensive. This is designed for view accessors queried without gas fees. Using it in state-changing
functions may become uncallable if the slice grows too large.
|
threshold() → uint64
public
Returns the minimum number of signers required to approve a multisignature operation.
_addSigners(bytes[] newSigners)
internal
Adds the newSigners
to those allowed to sign on behalf of this contract.
Internal version without access control.
Requirements:
-
Each of
newSigners
must be at least 20 bytes long. Reverts withMultiSignerERC7913InvalidSigner
if not. -
Each of
newSigners
must not be authorized. SeeisSigner
. Reverts withMultiSignerERC7913AlreadyExists
if so.
_removeSigners(bytes[] oldSigners)
internal
Removes the oldSigners
from the authorized signers. Internal version without access control.
Requirements:
-
Each of
oldSigners
must be authorized. SeeisSigner
. OtherwiseMultiSignerERC7913NonexistentSigner
is thrown. -
See
_validateReachableThreshold
for the threshold validation.
_setThreshold(uint64 newThreshold)
internal
Sets the signatures threshold
required to approve a multisignature operation.
Internal version without access control.
Requirements:
-
See
_validateReachableThreshold
for the threshold validation.
_validateReachableThreshold()
internal
Validates the current threshold is reachable.
Requirements:
-
The
getSignerCount
must be greater or equal than to thethreshold
. ThrowsMultiSignerERC7913UnreachableThreshold
if not.
_rawSignatureValidation(bytes32 hash, bytes signature) → bool
internal
Decodes, validates the signature and checks the signers are authorized.
See _validateSignatures
and _validateThreshold
for more details.
Example of signature encoding:
// Encode signers (verifier || key)
bytes memory signer1 = abi.encodePacked(verifier1, key1);
bytes memory signer2 = abi.encodePacked(verifier2, key2);
// Order signers by their id
if (keccak256(signer1) > keccak256(signer2)) {
(signer1, signer2) = (signer2, signer1);
(signature1, signature2) = (signature2, signature1);
}
// Assign ordered signers and signatures
bytes[] memory signers = new bytes[](2);
bytes[] memory signatures = new bytes[](2);
signers[0] = signer1;
signatures[0] = signature1;
signers[1] = signer2;
signatures[1] = signature2;
// Encode the multi signature
bytes memory signature = abi.encode(signers, signatures);
Requirements:
-
The
signature
must be encoded asabi.encode(signers, signatures)
.
_validateSignatures(bytes32 hash, bytes[] signers, bytes[] signatures) → bool valid
internal
Validates the signatures using the signers and their corresponding signatures. Returns whether the signers are authorized and the signatures are valid for the given hash.
Sorting the signers by their keccak256 hash will improve the gas efficiency of this function.
See {SignatureChecker-areValidSignaturesNow-bytes32-bytes[]-bytes[]} for more details.
|
Requirements:
-
The
signatures
andsigners
arrays must be equal in length. Returns false otherwise.
_validateThreshold(bytes[] validatingSigners) → bool
internal
Validates that the number of signers meets the threshold
requirement.
Assumes the signers were already validated. See _validateSignatures
for more details.
MultiSignerERC7913Weighted
import "@openzeppelin/contracts/utils/cryptography/signers/MultiSignerERC7913Weighted.sol";
Extension of MultiSignerERC7913
that supports weighted signatures.
This contract allows assigning different weights to each signer, enabling more flexible governance schemes. For example, some signers could have higher weight than others, allowing for weighted voting or prioritized authorization.
Example of usage:
contract MyWeightedMultiSignerAccount is Account, MultiSignerERC7913Weighted, Initializable {
function initialize(bytes[] memory signers, uint64[] memory weights, uint64 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(uint64 threshold) public onlyEntryPointOrSelf {
_setThreshold(threshold);
}
function setSignerWeights(bytes[] memory signers, uint64[] memory weights) public onlyEntryPointOrSelf {
_setSignerWeights(signers, weights);
}
}
When setting a threshold value, ensure it matches the scale used for signer weights.
For example, if signers have weights like 1, 2, or 3, then a threshold of 4 would require at
least two signers (e.g., one with weight 1 and one with weight 3). See signerWeight .
|
signerWeight(bytes signer) → uint64
public
Gets the weight of a signer. Returns 0 if the signer is not authorized.
_setSignerWeights(bytes[] signers, uint64[] weights)
internal
Sets weights for multiple signers at once. Internal version without access control.
Requirements:
-
signers
andweights
arrays must have the same length. Reverts withMultiSignerERC7913WeightedMismatchedLength
on mismatch. -
Each signer must exist in the set of authorized signers. Otherwise reverts with
MultiSignerERC7913NonexistentSigner
-
Each weight must be greater than 0. Otherwise reverts with
MultiSignerERC7913WeightedInvalidWeight
-
See
_validateReachableThreshold
for the threshold validation.
Emits ERC7913SignerWeightChanged
for each signer.
_addSigners(bytes[] newSigners)
internal
In cases where totalWeight
is almost type(uint64).max
(due to a large _totalExtraWeight
), adding new
signers could cause the totalWeight
computation to overflow. Adding a totalWeight
calls after the new
signers are added ensures no such overflow happens.
_removeSigners(bytes[] signers)
internal
Just like _addSigners
, this function does not emit ERC7913SignerWeightChanged
events. The
ERC7913SignerRemoved
event emitted by MultiSignerERC7913._removeSigners
is enough to track weights here.
_validateReachableThreshold()
internal
Sets the threshold for the multisignature operation. Internal version without access control.
Requirements:
-
The
totalWeight
must be>=
thethreshold
. Otherwise reverts withMultiSignerERC7913UnreachableThreshold
This function intentionally does not call super._validateReachableThreshold because the base implementation
assumes each signer has a weight of 1, which is a subset of this weighted implementation. Consider that multiple
implementations of this function may exist in the contract, so important side effects may be missed
depending on the linearization order.
|
_validateThreshold(bytes[] signers) → bool
internal
Validates that the total weight of signers meets the threshold requirement.
This function intentionally does not call super._validateThreshold because the base implementation
assumes each signer has a weight of 1, which is a subset of this weighted implementation. Consider that multiple
implementations of this function may exist in the contract, so important side effects may be missed
depending on the linearization order.
|
ERC7913SignerWeightChanged(bytes indexed signer, uint64 weight)
event
Emitted when a signer’s weight is changed.
Not emitted in _addSigners or _removeSigners . Indexers must rely on ERC7913SignerAdded
and ERC7913SignerRemoved to index a default weight of 1. See signerWeight .
|
MultiSignerERC7913WeightedInvalidWeight(bytes signer, uint64 weight)
error
Thrown when a signer’s weight is invalid.
MultiSignerERC7913WeightedMismatchedLength()
error
Thrown when the arrays lengths don’t match. See _setSignerWeights
.
Verifiers
ERC7913P256Verifier
import "@openzeppelin/contracts/utils/cryptography/verifiers/ERC7913P256Verifier.sol";
ERC-7913 signature verifier that support P256 (secp256r1) keys.
@custom:stateless
verify(bytes key, bytes32 hash, bytes signature) → bytes4
public
Verifies signature
as a valid signature of hash
by key
.
MUST return the bytes4 magic value IERC7913SignatureVerifier.verify.selector if the signature is valid. SHOULD return 0xffffffff or revert if the signature is not valid. SHOULD return 0xffffffff or revert if the key is empty
ERC7913RSAVerifier
import "@openzeppelin/contracts/utils/cryptography/verifiers/ERC7913RSAVerifier.sol";
ERC-7913 signature verifier that support RSA keys.
@custom:stateless
verify(bytes key, bytes32 hash, bytes signature) → bytes4
public
Verifies signature
as a valid signature of hash
by key
.
MUST return the bytes4 magic value IERC7913SignatureVerifier.verify.selector if the signature is valid. SHOULD return 0xffffffff or revert if the signature is not valid. SHOULD return 0xffffffff or revert if the key is empty