refactor: move brother_node development artifact to dev/test-nodes subdirectory

Development Artifact Cleanup:
 BROTHER_NODE REORGANIZATION: Moved development test node to appropriate location
- dev/test-nodes/brother_node/: Moved from root directory for better organization
- Contains development configuration, test logs, and test chain data
- No impact on production systems - purely development/testing artifact

 DEVELOPMENT ARTIFACTS IDENTIFIED:
- Chain ID: aitbc-brother-chain (test/development chain)
- Ports: 8010 (P2P) and 8011 (RPC) - different from production
- Environment: .env file with test configuration
- Logs: rpc.log and node.log from development testing session (March 15, 2026)

 ROOT DIRECTORY CLEANUP: Removed development clutter from production directory
- brother_node/ moved to dev/test-nodes/brother_node/
- Root directory now contains only production-ready components
- Development artifacts properly organized in dev/ subdirectory

DIRECTORY STRUCTURE IMPROVEMENT:
📁 dev/test-nodes/: Development and testing node configurations
🏗️ Root Directory: Clean production structure with only essential components
🧪 Development Isolation: Test environments separated from production

BENEFITS:
 Clean Production Directory: No development artifacts in root
 Better Organization: Development nodes grouped in dev/ subdirectory
 Clear Separation: Production vs development environments clearly distinguished
 Maintainability: Easier to identify and manage development components

RESULT: Successfully moved brother_node development artifact to dev/test-nodes/ subdirectory, cleaning up the root directory while preserving development testing environment for future use.
This commit is contained in:
2026-03-30 17:09:06 +02:00
parent bf730dcb4a
commit 816e258d4c
11734 changed files with 2001707 additions and 0 deletions

View File

@@ -0,0 +1,180 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.20;
/**
* @dev 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.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS
}
/**
* @dev The signature derives the `address(0)`.
*/
error ECDSAInvalidSignature();
/**
* @dev The signature has an invalid length.
*/
error ECDSAInvalidSignatureLength(uint256 length);
/**
* @dev The signature has an S value that is in the upper half order.
*/
error ECDSAInvalidSignatureS(bytes32 s);
/**
* @dev 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.
*
* IMPORTANT: `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.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*/
function tryRecover(
bytes32 hash,
bytes memory signature
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
assembly ("memory-safe") {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
}
}
/**
* @dev 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.
*
* IMPORTANT: `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.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[ERC-2098 short signatures]
*/
function tryRecover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
unchecked {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
// We do not check for an overflow here since the shift operation results in 0 or 1.
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*/
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS, s);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature, bytes32(0));
}
return (signer, RecoverError.NoError, bytes32(0));
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
*/
function _throwError(RecoverError error, bytes32 errorArg) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert ECDSAInvalidSignature();
} else if (error == RecoverError.InvalidSignatureLength) {
revert ECDSAInvalidSignatureLength(uint256(errorArg));
} else if (error == RecoverError.InvalidSignatureS) {
revert ECDSAInvalidSignatureS(errorArg);
}
}
}

View File

@@ -0,0 +1,160 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/EIP712.sol)
pragma solidity ^0.8.20;
import {MessageHashUtils} from "./MessageHashUtils.sol";
import {ShortStrings, ShortString} from "../ShortStrings.sol";
import {IERC5267} from "../../interfaces/IERC5267.sol";
/**
* @dev https://eips.ethereum.org/EIPS/eip-712[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.
*
* NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
* https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
*
* NOTE: 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.
*
* @custom:oz-upgrades-unsafe-allow state-variable-immutable
*/
abstract contract EIP712 is IERC5267 {
using ShortStrings for *;
bytes32 private constant TYPE_HASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
// Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
// invalidate the cached domain separator if the chain id changes.
bytes32 private immutable _cachedDomainSeparator;
uint256 private immutable _cachedChainId;
address private immutable _cachedThis;
bytes32 private immutable _hashedName;
bytes32 private immutable _hashedVersion;
ShortString private immutable _name;
ShortString private immutable _version;
// slither-disable-next-line constable-states
string private _nameFallback;
// slither-disable-next-line constable-states
string private _versionFallback;
/**
* @dev Initializes the domain separator and parameter caches.
*
* The meaning of `name` and `version` is specified in
* https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[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.
*
* NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
* contract upgrade].
*/
constructor(string memory name, string memory version) {
_name = name.toShortStringWithFallback(_nameFallback);
_version = version.toShortStringWithFallback(_versionFallback);
_hashedName = keccak256(bytes(name));
_hashedVersion = keccak256(bytes(version));
_cachedChainId = block.chainid;
_cachedDomainSeparator = _buildDomainSeparator();
_cachedThis = address(this);
}
/**
* @dev Returns the domain separator for the current chain.
*/
function _domainSeparatorV4() internal view returns (bytes32) {
if (address(this) == _cachedThis && block.chainid == _cachedChainId) {
return _cachedDomainSeparator;
} else {
return _buildDomainSeparator();
}
}
function _buildDomainSeparator() private view returns (bytes32) {
return keccak256(abi.encode(TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));
}
/**
* @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[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:
*
* ```solidity
* bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
* keccak256("Mail(address to,string contents)"),
* mailTo,
* keccak256(bytes(mailContents))
* )));
* address signer = ECDSA.recover(digest, signature);
* ```
*/
function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);
}
/// @inheritdoc IERC5267
function eip712Domain()
public
view
virtual
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
)
{
return (
hex"0f", // 01111
_EIP712Name(),
_EIP712Version(),
block.chainid,
address(this),
bytes32(0),
new uint256[](0)
);
}
/**
* @dev The name parameter for the EIP712 domain.
*
* NOTE: By default this function reads _name which is an immutable value.
* It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
*/
// solhint-disable-next-line func-name-mixedcase
function _EIP712Name() internal view returns (string memory) {
return _name.toStringWithFallback(_nameFallback);
}
/**
* @dev The version parameter for the EIP712 domain.
*
* NOTE: By default this function reads _version which is an immutable value.
* It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
*/
// solhint-disable-next-line func-name-mixedcase
function _EIP712Version() internal view returns (string memory) {
return _version.toStringWithFallback(_versionFallback);
}
}

View File

@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/Hashes.sol)
pragma solidity ^0.8.20;
/**
* @dev Library of standard hash functions.
*
* _Available since v5.1._
*/
library Hashes {
/**
* @dev Commutative Keccak256 hash of a sorted pair of bytes32. Frequently used when working with merkle proofs.
*
* NOTE: Equivalent to the `standardNodeHash` in our https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
*/
function commutativeKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32) {
return a < b ? efficientKeccak256(a, b) : efficientKeccak256(b, a);
}
/**
* @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
*/
function efficientKeccak256(bytes32 a, bytes32 b) internal pure returns (bytes32 value) {
assembly ("memory-safe") {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}

View File

@@ -0,0 +1,514 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/MerkleProof.sol)
// This file was procedurally generated from scripts/generate/templates/MerkleProof.js.
pragma solidity ^0.8.20;
import {Hashes} from "./Hashes.sol";
/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
* The tree and the proofs can be generated using our
* https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
* You will find a quickstart guide in the readme.
*
* WARNING: 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.
*
* IMPORTANT: Consider memory side-effects when using custom hashing functions
* that access memory in an unsafe way.
*
* NOTE: 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.
*/
library MerkleProof {
/**
*@dev The multiproof provided is not valid.
*/
error MerkleProofInvalidMultiproof();
/**
* @dev 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.
*/
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev 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.
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev 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.
*/
function verify(
bytes32[] memory proof,
bytes32 root,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processProof(proof, leaf, hasher) == root;
}
/**
* @dev 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.
*/
function processProof(
bytes32[] memory proof,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = hasher(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev 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.
*/
function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProofCalldata(proof, leaf) == root;
}
/**
* @dev 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.
*/
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = Hashes.commutativeKeccak256(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev 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.
*/
function verifyCalldata(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processProofCalldata(proof, leaf, hasher) == root;
}
/**
* @dev 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.
*/
function processProofCalldata(
bytes32[] calldata proof,
bytes32 leaf,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = hasher(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev 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.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProof}.
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProof(proof, proofFlags, leaves) == root;
}
/**
* @dev 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.
*
* CAUTION: 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).
*
* NOTE: 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.
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = Hashes.commutativeKeccak256(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev 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.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProof}.
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processMultiProof(proof, proofFlags, leaves, hasher) == root;
}
/**
* @dev 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.
*
* CAUTION: 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).
*
* NOTE: 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.
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = hasher(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev 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.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProofCalldata}.
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves) == root;
}
/**
* @dev 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.
*
* CAUTION: 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).
*
* NOTE: 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.
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = Hashes.commutativeKeccak256(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev 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.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*
* NOTE: Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`.
* The `leaves` must be validated independently. See {processMultiProofCalldata}.
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves, hasher) == root;
}
/**
* @dev 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.
*
* CAUTION: 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).
*
* NOTE: 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.
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves,
function(bytes32, bytes32) view returns (bytes32) hasher
) internal view returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofFlagsLen = proofFlags.length;
// Check proof validity.
if (leavesLen + proof.length != proofFlagsLen + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](proofFlagsLen);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < proofFlagsLen; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = hasher(a, b);
}
if (proofFlagsLen > 0) {
if (proofPos != proof.length) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[proofFlagsLen - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
}

View File

@@ -0,0 +1,99 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/MessageHashUtils.sol)
pragma solidity ^0.8.20;
import {Strings} from "../Strings.sol";
/**
* @dev 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
* https://eips.ethereum.org/EIPS/eip-191[ERC-191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]
* specifications.
*/
library MessageHashUtils {
/**
* @dev 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 https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method.
*
* NOTE: 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}.
*/
function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {
assembly ("memory-safe") {
mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash
mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix
digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)
}
}
/**
* @dev 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 https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method.
*
* See {ECDSA-recover}.
*/
function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {
return
keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message));
}
/**
* @dev 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}.
*/
function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(hex"19_00", validator, data));
}
/**
* @dev Variant of {toDataWithIntendedValidatorHash-address-bytes} optimized for cases where `data` is a bytes32.
*/
function toDataWithIntendedValidatorHash(
address validator,
bytes32 messageHash
) internal pure returns (bytes32 digest) {
assembly ("memory-safe") {
mstore(0x00, hex"19_00")
mstore(0x02, shl(96, validator))
mstore(0x16, messageHash)
digest := keccak256(0x00, 0x36)
}
}
/**
* @dev 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
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.
*
* See {ECDSA-recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(ptr, hex"19_01")
mstore(add(ptr, 0x02), domainSeparator)
mstore(add(ptr, 0x22), structHash)
digest := keccak256(ptr, 0x42)
}
}
}

View File

@@ -0,0 +1,408 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/P256.sol)
pragma solidity ^0.8.20;
import {Math} from "../math/Math.sol";
import {Errors} from "../Errors.sol";
/**
* @dev 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 https://github.com/itsobvioustech/aa-passkeys-wallet/blob/d3d423f28a4d8dfcb203c7fa0c47f42592a7378e/src/Secp256r1.sol[implementation of itsobvioustech] (GNU General Public License v3.0).
* Heavily inspired in https://github.com/maxrobot/elliptic-solidity/blob/c4bb1b6e8ae89534d8db3a6b3a6b52219100520f/contracts/Secp256r1.sol[maxrobot] and
* https://github.com/tdrerup/elliptic-curve-solidity/blob/59a9c25957d4d190eff53b6610731d81a077a15e/contracts/curves/EllipticCurve.sol[tdrerup] implementations.
*
* _Available since v5.1._
*/
library P256 {
struct JPoint {
uint256 x;
uint256 y;
uint256 z;
}
/// @dev Generator (x component)
uint256 internal constant GX = 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296;
/// @dev Generator (y component)
uint256 internal constant GY = 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5;
/// @dev P (size of the field)
uint256 internal constant P = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF;
/// @dev N (order of G)
uint256 internal constant N = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551;
/// @dev A parameter of the weierstrass equation
uint256 internal constant A = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC;
/// @dev B parameter of the weierstrass equation
uint256 internal constant B = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B;
/// @dev (P + 1) / 4. Useful to compute sqrt
uint256 private constant P1DIV4 = 0x3fffffffc0000000400000000000000000000000400000000000000000000000;
/// @dev N/2 for excluding higher order `s` values
uint256 private constant HALF_N = 0x7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a8;
/**
* @dev 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.
*
* @param h - hashed message
* @param r - signature half R
* @param s - signature half S
* @param qx - public key coordinate X
* @param qy - public key coordinate Y
*
* IMPORTANT: This function disallows signatures where the `s` value is above `N/2` to prevent malleability.
* To flip the `s` value, compute `s = N - s`.
*/
function verify(bytes32 h, bytes32 r, bytes32 s, bytes32 qx, bytes32 qy) internal view returns (bool) {
(bool valid, bool supported) = _tryVerifyNative(h, r, s, qx, qy);
return supported ? valid : verifySolidity(h, r, s, qx, qy);
}
/**
* @dev 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.
*/
function verifyNative(bytes32 h, bytes32 r, bytes32 s, bytes32 qx, bytes32 qy) internal view returns (bool) {
(bool valid, bool supported) = _tryVerifyNative(h, r, s, qx, qy);
if (supported) {
return valid;
} else {
revert Errors.MissingPrecompile(address(0x100));
}
}
/**
* @dev Same as {verify}, but it will return false if the required precompile is not available.
*/
function _tryVerifyNative(
bytes32 h,
bytes32 r,
bytes32 s,
bytes32 qx,
bytes32 qy
) private view returns (bool valid, bool supported) {
if (!_isProperSignature(r, s) || !isValidPublicKey(qx, qy)) {
return (false, true); // signature is invalid, and its not because the precompile is missing
} else if (_rip7212(h, r, s, qx, qy)) {
return (true, true); // precompile is present, signature is valid
} else if (
// Given precompiles have no bytecode (i.e. `address(0x100).code.length == 0`), we use
// a valid signature with small `r` and `s` values to check if the precompile is present. Taken from
// https://github.com/C2SP/wycheproof/blob/4672ff74d68766e7785c2cac4c597effccef2c5c/testvectors/ecdsa_secp256r1_sha256_p1363_test.json#L1173-L1204
_rip7212(
0xbb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca605023, // sha256("123400")
0x0000000000000000000000000000000000000000000000000000000000000005,
0x0000000000000000000000000000000000000000000000000000000000000001,
0xa71af64de5126a4a4e02b7922d66ce9415ce88a4c9d25514d91082c8725ac957,
0x5d47723c8fbe580bb369fec9c2665d8e30a435b9932645482e7c9f11e872296b
)
) {
return (false, true); // precompile is present, signature is invalid
} else {
return (false, false); // precompile is absent
}
}
/**
* @dev Low level helper for {_tryVerifyNative}. Calls the precompile and checks if there is a return value.
*/
function _rip7212(bytes32 h, bytes32 r, bytes32 s, bytes32 qx, bytes32 qy) private view returns (bool isValid) {
assembly ("memory-safe") {
// Use the free memory pointer without updating it at the end of the function
let ptr := mload(0x40)
mstore(ptr, h)
mstore(add(ptr, 0x20), r)
mstore(add(ptr, 0x40), s)
mstore(add(ptr, 0x60), qx)
mstore(add(ptr, 0x80), qy)
// RIP-7212 precompiles return empty bytes when an invalid signature is passed, making it impossible
// to distinguish the presence of the precompile. Custom precompile implementations may decide to
// return `bytes32(0)` (i.e. false) without developers noticing, so we decide to evaluate the return value
// without expanding memory using scratch space.
mstore(0x00, 0) // zero out scratch space in case the precompile doesn't return anything
if iszero(staticcall(gas(), 0x100, ptr, 0xa0, 0x00, 0x20)) {
invalid()
}
isValid := mload(0x00)
}
}
/**
* @dev Same as {verify}, but only the Solidity implementation is used.
*/
function verifySolidity(bytes32 h, bytes32 r, bytes32 s, bytes32 qx, bytes32 qy) internal view returns (bool) {
if (!_isProperSignature(r, s) || !isValidPublicKey(qx, qy)) {
return false;
}
JPoint[16] memory points = _preComputeJacobianPoints(uint256(qx), uint256(qy));
uint256 w = Math.invModPrime(uint256(s), N);
uint256 u1 = mulmod(uint256(h), w, N);
uint256 u2 = mulmod(uint256(r), w, N);
(uint256 x, ) = _jMultShamir(points, u1, u2);
return ((x % N) == uint256(r));
}
/**
* @dev Public key recovery
*
* @param h - hashed message
* @param v - signature recovery param
* @param r - signature half R
* @param s - signature half S
*
* IMPORTANT: This function disallows signatures where the `s` value is above `N/2` to prevent malleability.
* To flip the `s` value, compute `s = N - s` and `v = 1 - v` if (`v = 0 | 1`).
*/
function recovery(bytes32 h, uint8 v, bytes32 r, bytes32 s) internal view returns (bytes32 x, bytes32 y) {
if (!_isProperSignature(r, s) || v > 1) {
return (0, 0);
}
uint256 p = P; // cache P on the stack
uint256 rx = uint256(r);
uint256 ry2 = addmod(mulmod(addmod(mulmod(rx, rx, p), A, p), rx, p), B, p); // weierstrass equation y² = x³ + a.x + b
uint256 ry = Math.modExp(ry2, P1DIV4, p); // This formula for sqrt work because P ≡ 3 (mod 4)
if (mulmod(ry, ry, p) != ry2) return (0, 0); // Sanity check
if (ry % 2 != v) ry = p - ry;
JPoint[16] memory points = _preComputeJacobianPoints(rx, ry);
uint256 w = Math.invModPrime(uint256(r), N);
uint256 u1 = mulmod(N - (uint256(h) % N), w, N);
uint256 u2 = mulmod(uint256(s), w, N);
(uint256 xU, uint256 yU) = _jMultShamir(points, u1, u2);
return (bytes32(xU), bytes32(yU));
}
/**
* @dev Checks if (x, y) are valid coordinates of a point on the curve.
* In particular this function checks that x < P and y < P.
*/
function isValidPublicKey(bytes32 x, bytes32 y) internal pure returns (bool result) {
assembly ("memory-safe") {
let p := P
let lhs := mulmod(y, y, p) // y^2
let rhs := addmod(mulmod(addmod(mulmod(x, x, p), A, p), x, p), B, p) // ((x^2 + a) * x) + b = x^3 + ax + b
result := and(and(lt(x, p), lt(y, p)), eq(lhs, rhs)) // Should conform with the Weierstrass equation
}
}
/**
* @dev Checks if (r, s) is a proper signature.
* In particular, this checks that `s` is in the "lower-range", making the signature non-malleable.
*/
function _isProperSignature(bytes32 r, bytes32 s) private pure returns (bool) {
return uint256(r) > 0 && uint256(r) < N && uint256(s) > 0 && uint256(s) <= HALF_N;
}
/**
* @dev Reduce from jacobian to affine coordinates
* @param jx - jacobian coordinate x
* @param jy - jacobian coordinate y
* @param jz - jacobian coordinate z
* @return ax - affine coordinate x
* @return ay - affine coordinate y
*/
function _affineFromJacobian(uint256 jx, uint256 jy, uint256 jz) private view returns (uint256 ax, uint256 ay) {
if (jz == 0) return (0, 0);
uint256 p = P; // cache P on the stack
uint256 zinv = Math.invModPrime(jz, p);
assembly ("memory-safe") {
let zzinv := mulmod(zinv, zinv, p)
ax := mulmod(jx, zzinv, p)
ay := mulmod(jy, mulmod(zzinv, zinv, p), p)
}
}
/**
* @dev Point addition on the jacobian coordinates
* Reference: https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-add-1998-cmo-2
*
* Note that:
*
* - `addition-add-1998-cmo-2` doesn't support identical input points. This version is modified to use
* the `h` and `r` values computed by `addition-add-1998-cmo-2` to detect identical inputs, and fallback to
* `doubling-dbl-1998-cmo-2` if needed.
* - if one of the points is at infinity (i.e. `z=0`), the result is undefined.
*/
function _jAdd(
JPoint memory p1,
uint256 x2,
uint256 y2,
uint256 z2
) private pure returns (uint256 rx, uint256 ry, uint256 rz) {
assembly ("memory-safe") {
let p := P
let z1 := mload(add(p1, 0x40))
let zz1 := mulmod(z1, z1, p) // zz1 = z1²
let s1 := mulmod(mload(add(p1, 0x20)), mulmod(mulmod(z2, z2, p), z2, p), p) // s1 = y1*z2³
let r := addmod(mulmod(y2, mulmod(zz1, z1, p), p), sub(p, s1), p) // r = s2-s1 = y2*z1³-s1 = y2*z1³-y1*z2³
let u1 := mulmod(mload(p1), mulmod(z2, z2, p), p) // u1 = x1*z2²
let h := addmod(mulmod(x2, zz1, p), sub(p, u1), p) // h = u2-u1 = x2*z1²-u1 = x2*z1²-x1*z2²
// detect edge cases where inputs are identical
switch and(iszero(r), iszero(h))
// case 0: points are different
case 0 {
let hh := mulmod(h, h, p) // h²
// x' = r²-h³-2*u1*h²
rx := addmod(
addmod(mulmod(r, r, p), sub(p, mulmod(h, hh, p)), p),
sub(p, mulmod(2, mulmod(u1, hh, p), p)),
p
)
// y' = r*(u1*h²-x')-s1*h³
ry := addmod(
mulmod(r, addmod(mulmod(u1, hh, p), sub(p, rx), p), p),
sub(p, mulmod(s1, mulmod(h, hh, p), p)),
p
)
// z' = h*z1*z2
rz := mulmod(h, mulmod(z1, z2, p), p)
}
// case 1: points are equal
case 1 {
let x := x2
let y := y2
let z := z2
let yy := mulmod(y, y, p)
let zz := mulmod(z, z, p)
let m := addmod(mulmod(3, mulmod(x, x, p), p), mulmod(A, mulmod(zz, zz, p), p), p) // m = 3*x²+a*z⁴
let s := mulmod(4, mulmod(x, yy, p), p) // s = 4*x*y²
// x' = t = m²-2*s
rx := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p)
// y' = m*(s-t)-8*y⁴ = m*(s-x')-8*y⁴
// cut the computation to avoid stack too deep
let rytmp1 := sub(p, mulmod(8, mulmod(yy, yy, p), p)) // -8*y⁴
let rytmp2 := addmod(s, sub(p, rx), p) // s-x'
ry := addmod(mulmod(m, rytmp2, p), rytmp1, p) // m*(s-x')-8*y⁴
// z' = 2*y*z
rz := mulmod(2, mulmod(y, z, p), p)
}
}
}
/**
* @dev Point doubling on the jacobian coordinates
* Reference: https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-1998-cmo-2
*/
function _jDouble(uint256 x, uint256 y, uint256 z) private pure returns (uint256 rx, uint256 ry, uint256 rz) {
assembly ("memory-safe") {
let p := P
let yy := mulmod(y, y, p)
let zz := mulmod(z, z, p)
let m := addmod(mulmod(3, mulmod(x, x, p), p), mulmod(A, mulmod(zz, zz, p), p), p) // m = 3*x²+a*z⁴
let s := mulmod(4, mulmod(x, yy, p), p) // s = 4*x*y²
// x' = t = m²-2*s
rx := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p)
// y' = m*(s-t)-8*y⁴ = m*(s-x')-8*y⁴
ry := addmod(mulmod(m, addmod(s, sub(p, rx), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p)
// z' = 2*y*z
rz := mulmod(2, mulmod(y, z, p), p)
}
}
/**
* @dev Compute G·u1 + P·u2 using the precomputed points for G and P (see {_preComputeJacobianPoints}).
*
* Uses Strauss Shamir trick for EC multiplication
* https://stackoverflow.com/questions/50993471/ec-scalar-multiplication-with-strauss-shamir-method
*
* We optimize this for 2 bits at a time rather than a single bit. The individual points for a single pass are
* precomputed. Overall this reduces the number of additions while keeping the same number of
* doublings
*/
function _jMultShamir(
JPoint[16] memory points,
uint256 u1,
uint256 u2
) private view returns (uint256 rx, uint256 ry) {
uint256 x = 0;
uint256 y = 0;
uint256 z = 0;
unchecked {
for (uint256 i = 0; i < 128; ++i) {
if (z > 0) {
(x, y, z) = _jDouble(x, y, z);
(x, y, z) = _jDouble(x, y, z);
}
// Read 2 bits of u1, and 2 bits of u2. Combining the two gives the lookup index in the table.
uint256 pos = ((u1 >> 252) & 0xc) | ((u2 >> 254) & 0x3);
// Points that have z = 0 are points at infinity. They are the additive 0 of the group
// - if the lookup point is a 0, we can skip it
// - otherwise:
// - if the current point (x, y, z) is 0, we use the lookup point as our new value (0+P=P)
// - if the current point (x, y, z) is not 0, both points are valid and we can use `_jAdd`
if (points[pos].z != 0) {
if (z == 0) {
(x, y, z) = (points[pos].x, points[pos].y, points[pos].z);
} else {
(x, y, z) = _jAdd(points[pos], x, y, z);
}
}
u1 <<= 2;
u2 <<= 2;
}
}
return _affineFromJacobian(x, y, z);
}
/**
* @dev Precompute a matrice of useful jacobian points associated with a given P. This can be seen as a 4x4 matrix
* that contains combination of P and G (generator) up to 3 times each. See the table below:
*
* ┌────┬─────────────────────┐
* │ i │ 0 1 2 3 │
* ├────┼─────────────────────┤
* │ 0 │ 0 p 2p 3p │
* │ 4 │ g g+p g+2p g+3p │
* │ 8 │ 2g 2g+p 2g+2p 2g+3p │
* │ 12 │ 3g 3g+p 3g+2p 3g+3p │
* └────┴─────────────────────┘
*
* Note that `_jAdd` (and thus `_jAddPoint`) does not handle the case where one of the inputs is a point at
* infinity (z = 0). However, we know that since `N ≡ 1 mod 2` and `N ≡ 1 mod 3`, there is no point P such that
* 2P = 0 or 3P = 0. This guarantees that g, 2g, 3g, p, 2p, 3p are all non-zero, and that all `_jAddPoint` calls
* have valid inputs.
*/
function _preComputeJacobianPoints(uint256 px, uint256 py) private pure returns (JPoint[16] memory points) {
points[0x00] = JPoint(0, 0, 0); // 0,0
points[0x01] = JPoint(px, py, 1); // 1,0 (p)
points[0x04] = JPoint(GX, GY, 1); // 0,1 (g)
points[0x02] = _jDoublePoint(points[0x01]); // 2,0 (2p)
points[0x08] = _jDoublePoint(points[0x04]); // 0,2 (2g)
points[0x03] = _jAddPoint(points[0x01], points[0x02]); // 3,0 (p+2p = 3p)
points[0x05] = _jAddPoint(points[0x01], points[0x04]); // 1,1 (p+g)
points[0x06] = _jAddPoint(points[0x02], points[0x04]); // 2,1 (2p+g)
points[0x07] = _jAddPoint(points[0x03], points[0x04]); // 3,1 (3p+g)
points[0x09] = _jAddPoint(points[0x01], points[0x08]); // 1,2 (p+2g)
points[0x0a] = _jAddPoint(points[0x02], points[0x08]); // 2,2 (2p+2g)
points[0x0b] = _jAddPoint(points[0x03], points[0x08]); // 3,2 (3p+2g)
points[0x0c] = _jAddPoint(points[0x04], points[0x08]); // 0,3 (g+2g = 3g)
points[0x0d] = _jAddPoint(points[0x01], points[0x0c]); // 1,3 (p+3g)
points[0x0e] = _jAddPoint(points[0x02], points[0x0c]); // 2,3 (2p+3g)
points[0x0f] = _jAddPoint(points[0x03], points[0x0c]); // 3,3 (3p+3g)
}
function _jAddPoint(JPoint memory p1, JPoint memory p2) private pure returns (JPoint memory) {
(uint256 x, uint256 y, uint256 z) = _jAdd(p1, p2.x, p2.y, p2.z);
return JPoint(x, y, z);
}
function _jDoublePoint(JPoint memory p) private pure returns (JPoint memory) {
(uint256 x, uint256 y, uint256 z) = _jDouble(p.x, p.y, p.z);
return JPoint(x, y, z);
}
}

View File

@@ -0,0 +1,154 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/RSA.sol)
pragma solidity ^0.8.20;
import {Math} from "../math/Math.sol";
/**
* @dev RSA PKCS#1 v1.5 signature verification implementation according to https://datatracker.ietf.org/doc/html/rfc8017[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 https://github.com/adria0/SolRsaVerify/blob/79c6182cabb9102ea69d4a2e996816091d5f1cd1[Adrià Massanet's work] (GNU General Public License v3.0).
*
* _Available since v5.1._
*/
library RSA {
/**
* @dev Same as {pkcs1Sha256} but using SHA256 to calculate the digest of `data`.
*/
function pkcs1Sha256(
bytes memory data,
bytes memory s,
bytes memory e,
bytes memory n
) internal view returns (bool) {
return pkcs1Sha256(sha256(data), s, e, n);
}
/**
* @dev Verifies a PKCSv1.5 signature given a digest according to the verification
* method described in https://datatracker.ietf.org/doc/html/rfc8017#section-8.2.2[section 8.2.2 of RFC8017] with
* support for explicit or implicit NULL parameters in the DigestInfo (no other optional parameters are supported).
*
* IMPORTANT: 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.
*
* WARNING: 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.
*
* WARNING: 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.
*
* @param digest the digest to verify
* @param s is a buffer containing the signature
* @param e is the exponent of the public key
* @param n is the modulus of the public key
*/
function pkcs1Sha256(bytes32 digest, bytes memory s, bytes memory e, bytes memory n) internal view returns (bool) {
unchecked {
// cache and check length
uint256 length = n.length;
if (
length < 0x100 || // Enforce 2048 bits minimum
length != s.length // signature must have the same length as the finite field
) {
return false;
}
// Verify that s < n to ensure there's only one valid signature for a given message
for (uint256 i = 0; i < length; i += 0x20) {
uint256 p = Math.min(i, length - 0x20);
bytes32 sp = _unsafeReadBytes32(s, p);
bytes32 np = _unsafeReadBytes32(n, p);
if (sp < np) {
// s < n in the upper bits (everything before is equal) → s < n globally: ok
break;
} else if (sp > np || p == length - 0x20) {
// s > n in the upper bits (everything before is equal) → s > n globally: fail
// or
// s = n and we are looking at the lower bits → s = n globally: fail
return false;
}
}
// RSAVP1 https://datatracker.ietf.org/doc/html/rfc8017#section-5.2.2
// The previous check guarantees that n > 0. Therefore modExp cannot revert.
bytes memory buffer = Math.modExp(s, e, n);
// Check that buffer is well encoded:
// buffer ::= 0x00 | 0x01 | PS | 0x00 | DigestInfo
//
// With
// - PS is padding filled with 0xFF
// - DigestInfo ::= SEQUENCE {
// digestAlgorithm AlgorithmIdentifier,
// [optional algorithm parameters] -- not currently supported
// digest OCTET STRING
// }
// Get AlgorithmIdentifier from the DigestInfo, and set the config accordingly
// - params: includes 00 + first part of DigestInfo
// - mask: filter to check the params
// - offset: length of the suffix (including digest)
bytes32 params; // 0x00 | DigestInfo
bytes32 mask;
uint256 offset;
// Digest is expected at the end of the buffer. Therefore if NULL param is present,
// it should be at 32 (digest) + 2 bytes from the end. To those 34 bytes, we add the
// OID (9 bytes) and its length (2 bytes) to get the position of the DigestInfo sequence,
// which is expected to have a length of 0x31 when the NULL param is present or 0x2f if not.
if (bytes1(_unsafeReadBytes32(buffer, length - 0x32)) == 0x31) {
offset = 0x34;
// 00 (1 byte) | SEQUENCE length (0x31) = 3031 (2 bytes) | SEQUENCE length (0x0d) = 300d (2 bytes) | OBJECT_IDENTIFIER length (0x09) = 0609 (2 bytes)
// SHA256 OID = 608648016503040201 (9 bytes) | NULL = 0500 (2 bytes) (explicit) | OCTET_STRING length (0x20) = 0420 (2 bytes)
params = 0x003031300d060960864801650304020105000420000000000000000000000000;
mask = 0xffffffffffffffffffffffffffffffffffffffff000000000000000000000000; // (20 bytes)
} else if (bytes1(_unsafeReadBytes32(buffer, length - 0x30)) == 0x2F) {
offset = 0x32;
// 00 (1 byte) | SEQUENCE length (0x2f) = 302f (2 bytes) | SEQUENCE length (0x0b) = 300b (2 bytes) | OBJECT_IDENTIFIER length (0x09) = 0609 (2 bytes)
// SHA256 OID = 608648016503040201 (9 bytes) | NULL = <implicit> | OCTET_STRING length (0x20) = 0420 (2 bytes)
params = 0x00302f300b060960864801650304020104200000000000000000000000000000;
mask = 0xffffffffffffffffffffffffffffffffffff0000000000000000000000000000; // (18 bytes)
} else {
// unknown
return false;
}
// Length is at least 0x100 and offset is at most 0x34, so this is safe. There is always some padding.
uint256 paddingEnd = length - offset;
// The padding has variable (arbitrary) length, so we check it byte per byte in a loop.
// This is required to ensure non-malleability. Not checking would allow an attacker to
// use the padding to manipulate the message in order to create a valid signature out of
// multiple valid signatures.
for (uint256 i = 2; i < paddingEnd; ++i) {
if (bytes1(_unsafeReadBytes32(buffer, i)) != 0xFF) {
return false;
}
}
// All the other parameters are small enough to fit in a bytes32, so we can check them directly.
return
bytes2(0x0001) == bytes2(_unsafeReadBytes32(buffer, 0x00)) && // 00 | 01
// PS was checked in the loop
params == _unsafeReadBytes32(buffer, paddingEnd) & mask && // DigestInfo
// Optional parameters are not checked
digest == _unsafeReadBytes32(buffer, length - 0x20); // Digest
}
}
/// @dev Reads a bytes32 from a bytes array without bounds checking.
function _unsafeReadBytes32(bytes memory array, uint256 offset) private pure returns (bytes32 result) {
// Memory safeness is guaranteed as long as the provided `array` is a Solidity-allocated bytes array
// and `offset` is within bounds. This is the case for all calls to this private function from {pkcs1Sha256}.
assembly ("memory-safe") {
result := mload(add(add(array, 0x20), offset))
}
}
}

View File

@@ -0,0 +1,135 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/SignatureChecker.sol)
pragma solidity ^0.8.24;
import {ECDSA} from "./ECDSA.sol";
import {IERC1271} from "../../interfaces/IERC1271.sol";
import {IERC7913SignatureVerifier} from "../../interfaces/IERC7913.sol";
import {Bytes} from "../../utils/Bytes.sol";
/**
* @dev 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
*
* See https://eips.ethereum.org/EIPS/eip-1271[ERC-1271] and https://eips.ethereum.org/EIPS/eip-7913[ERC-7913].
*/
library SignatureChecker {
using Bytes for bytes;
/**
* @dev 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`.
*
* NOTE: 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).
*
* NOTE: For an extended version of this function that supports ERC-7913 signatures, see {isValidSignatureNow-bytes-bytes32-bytes-}.
*/
function isValidSignatureNow(address signer, bytes32 hash, bytes memory signature) internal view returns (bool) {
if (signer.code.length == 0) {
(address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecover(hash, signature);
return err == ECDSA.RecoverError.NoError && recovered == signer;
} else {
return isValidERC1271SignatureNow(signer, hash, signature);
}
}
/**
* @dev 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.
*
* NOTE: 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).
*/
function isValidERC1271SignatureNow(
address signer,
bytes32 hash,
bytes memory signature
) internal view returns (bool) {
(bool success, bytes memory result) = signer.staticcall(
abi.encodeCall(IERC1271.isValidSignature, (hash, signature))
);
return (success &&
result.length >= 32 &&
abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector));
}
/**
* @dev 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 using {isValidSignatureNow}
* * Otherwise: verification is done using {IERC7913SignatureVerifier}
*
* NOTE: 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).
*/
function isValidSignatureNow(
bytes memory signer,
bytes32 hash,
bytes memory signature
) internal view returns (bool) {
if (signer.length < 20) {
return false;
} else if (signer.length == 20) {
return isValidSignatureNow(address(bytes20(signer)), hash, signature);
} else {
(bool success, bytes memory result) = address(bytes20(signer)).staticcall(
abi.encodeCall(IERC7913SignatureVerifier.verify, (signer.slice(20), hash, signature))
);
return (success &&
result.length >= 32 &&
abi.decode(result, (bytes32)) == bytes32(IERC7913SignatureVerifier.verify.selector));
}
}
/**
* @dev 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.
*
* NOTE: 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).
*/
function areValidSignaturesNow(
bytes32 hash,
bytes[] memory signers,
bytes[] memory signatures
) internal view returns (bool) {
if (signers.length != signatures.length) return false;
bytes32 lastId = bytes32(0);
for (uint256 i = 0; i < signers.length; ++i) {
bytes memory signer = signers[i];
// If one of the signatures is invalid, reject the batch
if (!isValidSignatureNow(signer, hash, signatures[i])) return false;
bytes32 id = keccak256(signer);
// If the current signer ID is greater than all previous IDs, then this is a new signer.
if (lastId < id) {
lastId = id;
} else {
// If this signer id is not greater than all the previous ones, verify that it is not a duplicate of a previous one
// This loop is never executed if the signers are ordered by id.
for (uint256 j = 0; j < i; ++j) {
if (id == keccak256(signers[j])) return false;
}
}
}
return true;
}
}

View File

@@ -0,0 +1,207 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/draft-ERC7739Utils.sol)
pragma solidity ^0.8.20;
import {Calldata} from "../Calldata.sol";
/**
* @dev Utilities to process https://ercs.ethereum.org/ERCS/erc-7739[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 xref:api:utils/cryptography#EIP712-_domainSeparatorV4[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}.
*
* NOTE: 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 xref:api:utils/cryptography#ECDSA[ECDSA] signature, as is for example specified for
* xref:api:utils/cryptography#EIP712[EIP-712].
*/
library ERC7739Utils {
/**
* @dev An EIP-712 type to represent "personal" signatures
* (i.e. mimic of `personal_sign` for smart contracts).
*/
bytes32 private constant PERSONAL_SIGN_TYPEHASH = keccak256("PersonalSign(bytes prefixed)");
/**
* @dev 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.
*/
function encodeTypedDataSig(
bytes memory signature,
bytes32 appSeparator,
bytes32 contentsHash,
string memory contentsDescr
) internal pure returns (bytes memory) {
return
abi.encodePacked(signature, appSeparator, contentsHash, contentsDescr, uint16(bytes(contentsDescr).length));
}
/**
* @dev 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-712 {EIP712-_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.
*
* NOTE: This function returns empty if the input format is invalid instead of reverting.
* data instead.
*/
function decodeTypedDataSig(
bytes calldata encodedSignature
)
internal
pure
returns (bytes calldata signature, bytes32 appSeparator, bytes32 contentsHash, string calldata contentsDescr)
{
unchecked {
uint256 sigLength = encodedSignature.length;
// 66 bytes = contentsDescrLength (2 bytes) + contentsHash (32 bytes) + APP_DOMAIN_SEPARATOR (32 bytes).
if (sigLength < 66) return (Calldata.emptyBytes(), 0, 0, Calldata.emptyString());
uint256 contentsDescrEnd = sigLength - 2; // Last 2 bytes
uint256 contentsDescrLength = uint16(bytes2(encodedSignature[contentsDescrEnd:]));
// Check for space for `contentsDescr` in addition to the 66 bytes documented above
if (sigLength < 66 + contentsDescrLength) return (Calldata.emptyBytes(), 0, 0, Calldata.emptyString());
uint256 contentsHashEnd = contentsDescrEnd - contentsDescrLength;
uint256 separatorEnd = contentsHashEnd - 32;
uint256 signatureEnd = separatorEnd - 32;
signature = encodedSignature[:signatureEnd];
appSeparator = bytes32(encodedSignature[signatureEnd:separatorEnd]);
contentsHash = bytes32(encodedSignature[separatorEnd:contentsHashEnd]);
contentsDescr = string(encodedSignature[contentsHashEnd:contentsDescrEnd]);
}
}
/**
* @dev 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.
*/
function personalSignStructHash(bytes32 contents) internal pure returns (bytes32) {
return keccak256(abi.encode(PERSONAL_SIGN_TYPEHASH, contents));
}
/**
* @dev 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.
*/
function typedDataSignStructHash(
string calldata contentsName,
string calldata contentsType,
bytes32 contentsHash,
bytes memory domainBytes
) internal pure returns (bytes32 result) {
return
bytes(contentsName).length == 0
? bytes32(0)
: keccak256(
abi.encodePacked(typedDataSignTypehash(contentsName, contentsType), contentsHash, domainBytes)
);
}
/**
* @dev Variant of {typedDataSignStructHash-string-string-bytes32-bytes} that takes a content descriptor
* and decodes the `contentsName` and `contentsType` out of it.
*/
function typedDataSignStructHash(
string calldata contentsDescr,
bytes32 contentsHash,
bytes memory domainBytes
) internal pure returns (bytes32 result) {
(string calldata contentsName, string calldata contentsType) = decodeContentsDescr(contentsDescr);
return typedDataSignStructHash(contentsName, contentsType, contentsHash, domainBytes);
}
/**
* @dev Compute the EIP-712 typehash of the `TypedDataSign` structure for a given type (and typename).
*/
function typedDataSignTypehash(
string calldata contentsName,
string calldata contentsType
) internal pure returns (bytes32) {
return
keccak256(
abi.encodePacked(
"TypedDataSign(",
contentsName,
" contents,string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)",
contentsType
)
);
}
/**
* @dev 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.
*/
function decodeContentsDescr(
string calldata contentsDescr
) internal pure returns (string calldata contentsName, string calldata contentsType) {
bytes calldata buffer = bytes(contentsDescr);
if (buffer.length == 0) {
// pass through (fail)
} else if (buffer[buffer.length - 1] == bytes1(")")) {
// Implicit mode: read contentsName from the beginning, and keep the complete descr
for (uint256 i = 0; i < buffer.length; ++i) {
bytes1 current = buffer[i];
if (current == bytes1("(")) {
// if name is empty - passthrough (fail)
if (i == 0) break;
// we found the end of the contentsName
return (string(buffer[:i]), contentsDescr);
} else if (_isForbiddenChar(current)) {
// we found an invalid character (forbidden) - passthrough (fail)
break;
}
}
} else {
// Explicit mode: read contentsName from the end, and remove it from the descr
for (uint256 i = buffer.length; i > 0; --i) {
bytes1 current = buffer[i - 1];
if (current == bytes1(")")) {
// we found the end of the contentsName
return (string(buffer[i:]), string(buffer[:i]));
} else if (_isForbiddenChar(current)) {
// we found an invalid character (forbidden) - passthrough (fail)
break;
}
}
}
return (Calldata.emptyString(), Calldata.emptyString());
}
function _isForbiddenChar(bytes1 char) private pure returns (bool) {
return char == 0x00 || char == bytes1(" ") || char == bytes1(",") || char == bytes1("(") || char == bytes1(")");
}
}

View File

@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/signers/AbstractSigner.sol)
pragma solidity ^0.8.20;
/**
* @dev Abstract contract for signature validation.
*
* Developers must implement {_rawSignatureValidation} and use it as the lowest-level signature validation mechanism.
*
* @custom:stateless
*/
abstract contract AbstractSigner {
/**
* @dev Signature validation algorithm.
*
* WARNING: Implementing a signature validation algorithm is a security-sensitive operation as it involves
* cryptographic verification. It is important to review and test thoroughly before deployment. Consider
* using one of the signature verification libraries (xref:api:utils/cryptography#ECDSA[ECDSA],
* xref:api:utils/cryptography#P256[P256] or xref:api:utils/cryptography#RSA[RSA]).
*/
function _rawSignatureValidation(bytes32 hash, bytes calldata signature) internal view virtual returns (bool);
}

View File

@@ -0,0 +1,252 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/signers/MultiSignerERC7913.sol)
pragma solidity ^0.8.26;
import {AbstractSigner} from "./AbstractSigner.sol";
import {SignatureChecker} from "../SignatureChecker.sol";
import {EnumerableSet} from "../../structs/EnumerableSet.sol";
/**
* @dev 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:
*
* ```solidity
* 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);
* }
* }
* ```
*
* IMPORTANT: 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.
*/
abstract contract MultiSignerERC7913 is AbstractSigner {
using EnumerableSet for EnumerableSet.BytesSet;
using SignatureChecker for *;
EnumerableSet.BytesSet private _signers;
uint64 private _threshold;
/// @dev Emitted when a signer is added.
event ERC7913SignerAdded(bytes indexed signers);
/// @dev Emitted when a signers is removed.
event ERC7913SignerRemoved(bytes indexed signers);
/// @dev Emitted when the threshold is updated.
event ERC7913ThresholdSet(uint64 threshold);
/// @dev The `signer` already exists.
error MultiSignerERC7913AlreadyExists(bytes signer);
/// @dev The `signer` does not exist.
error MultiSignerERC7913NonexistentSigner(bytes signer);
/// @dev The `signer` is less than 20 bytes long.
error MultiSignerERC7913InvalidSigner(bytes signer);
/// @dev The `threshold` is zero.
error MultiSignerERC7913ZeroThreshold();
/// @dev The `threshold` is unreachable given the number of `signers`.
error MultiSignerERC7913UnreachableThreshold(uint64 signers, uint64 threshold);
constructor(bytes[] memory signers_, uint64 threshold_) {
_addSigners(signers_);
_setThreshold(threshold_);
}
/**
* @dev Returns a slice of the set of authorized signers.
*
* Using `start = 0` and `end = type(uint64).max` will return the entire set of signers.
*
* WARNING: 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.
*/
function getSigners(uint64 start, uint64 end) public view virtual returns (bytes[] memory) {
return _signers.values(start, end);
}
/// @dev Returns the number of authorized signers
function getSignerCount() public view virtual returns (uint256) {
return _signers.length();
}
/// @dev Returns whether the `signer` is an authorized signer.
function isSigner(bytes memory signer) public view virtual returns (bool) {
return _signers.contains(signer);
}
/// @dev Returns the minimum number of signers required to approve a multisignature operation.
function threshold() public view virtual returns (uint64) {
return _threshold;
}
/**
* @dev 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 with {MultiSignerERC7913InvalidSigner} if not.
* * Each of `newSigners` must not be authorized. See {isSigner}. Reverts with {MultiSignerERC7913AlreadyExists} if so.
*/
function _addSigners(bytes[] memory newSigners) internal virtual {
for (uint256 i = 0; i < newSigners.length; ++i) {
bytes memory signer = newSigners[i];
require(signer.length >= 20, MultiSignerERC7913InvalidSigner(signer));
require(_signers.add(signer), MultiSignerERC7913AlreadyExists(signer));
emit ERC7913SignerAdded(signer);
}
}
/**
* @dev Removes the `oldSigners` from the authorized signers. Internal version without access control.
*
* Requirements:
*
* * Each of `oldSigners` must be authorized. See {isSigner}. Otherwise {MultiSignerERC7913NonexistentSigner} is thrown.
* * See {_validateReachableThreshold} for the threshold validation.
*/
function _removeSigners(bytes[] memory oldSigners) internal virtual {
for (uint256 i = 0; i < oldSigners.length; ++i) {
bytes memory signer = oldSigners[i];
require(_signers.remove(signer), MultiSignerERC7913NonexistentSigner(signer));
emit ERC7913SignerRemoved(signer);
}
_validateReachableThreshold();
}
/**
* @dev Sets the signatures `threshold` required to approve a multisignature operation.
* Internal version without access control.
*
* Requirements:
*
* * See {_validateReachableThreshold} for the threshold validation.
*/
function _setThreshold(uint64 newThreshold) internal virtual {
require(newThreshold > 0, MultiSignerERC7913ZeroThreshold());
_threshold = newThreshold;
_validateReachableThreshold();
emit ERC7913ThresholdSet(newThreshold);
}
/**
* @dev Validates the current threshold is reachable.
*
* Requirements:
*
* * The {getSignerCount} must be greater or equal than to the {threshold}. Throws
* {MultiSignerERC7913UnreachableThreshold} if not.
*/
function _validateReachableThreshold() internal view virtual {
uint256 signersLength = _signers.length();
uint64 currentThreshold = threshold();
require(
signersLength >= currentThreshold,
MultiSignerERC7913UnreachableThreshold(
uint64(signersLength), // Safe cast. Economically impossible to overflow.
currentThreshold
)
);
}
/**
* @dev Decodes, validates the signature and checks the signers are authorized.
* See {_validateSignatures} and {_validateThreshold} for more details.
*
* Example of signature encoding:
*
* ```solidity
* // 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 as `abi.encode(signers, signatures)`.
*/
function _rawSignatureValidation(
bytes32 hash,
bytes calldata signature
) internal view virtual override returns (bool) {
if (signature.length == 0) return false; // For ERC-7739 compatibility
(bytes[] memory signers, bytes[] memory signatures) = abi.decode(signature, (bytes[], bytes[]));
return _validateThreshold(signers) && _validateSignatures(hash, signers, signatures);
}
/**
* @dev 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.
*
* IMPORTANT: 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` and `signers` arrays must be equal in length. Returns false otherwise.
*/
function _validateSignatures(
bytes32 hash,
bytes[] memory signers,
bytes[] memory signatures
) internal view virtual returns (bool valid) {
for (uint256 i = 0; i < signers.length; ++i) {
if (!isSigner(signers[i])) {
return false;
}
}
return hash.areValidSignaturesNow(signers, signatures);
}
/**
* @dev Validates that the number of signers meets the {threshold} requirement.
* Assumes the signers were already validated. See {_validateSignatures} for more details.
*/
function _validateThreshold(bytes[] memory validatingSigners) internal view virtual returns (bool) {
return validatingSigners.length >= threshold();
}
}

View File

@@ -0,0 +1,208 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/signers/MultiSignerERC7913Weighted.sol)
pragma solidity ^0.8.26;
import {SafeCast} from "../../math/SafeCast.sol";
import {MultiSignerERC7913} from "./MultiSignerERC7913.sol";
/**
* @dev 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:
*
* ```solidity
* 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);
* }
* }
* ```
*
* IMPORTANT: 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}.
*/
abstract contract MultiSignerERC7913Weighted is MultiSignerERC7913 {
using SafeCast for *;
// Sum of all the extra weights of all signers. Storage packed with `MultiSignerERC7913._threshold`
uint64 private _totalExtraWeight;
// Mapping from signer to extraWeight (in addition to all authorized signers having weight 1)
mapping(bytes signer => uint64) private _extraWeights;
/**
* @dev Emitted when a signer's weight is changed.
*
* NOTE: Not emitted in {_addSigners} or {_removeSigners}. Indexers must rely on {ERC7913SignerAdded}
* and {ERC7913SignerRemoved} to index a default weight of 1. See {signerWeight}.
*/
event ERC7913SignerWeightChanged(bytes indexed signer, uint64 weight);
/// @dev Thrown when a signer's weight is invalid.
error MultiSignerERC7913WeightedInvalidWeight(bytes signer, uint64 weight);
/// @dev Thrown when the arrays lengths don't match. See {_setSignerWeights}.
error MultiSignerERC7913WeightedMismatchedLength();
constructor(bytes[] memory signers_, uint64[] memory weights_, uint64 threshold_) MultiSignerERC7913(signers_, 1) {
_setSignerWeights(signers_, weights_);
_setThreshold(threshold_);
}
/// @dev Gets the weight of a signer. Returns 0 if the signer is not authorized.
function signerWeight(bytes memory signer) public view virtual returns (uint64) {
unchecked {
// Safe cast, _setSignerWeights guarantees 1+_extraWeights is a uint64
return uint64(isSigner(signer).toUint() * (1 + _extraWeights[signer]));
}
}
/// @dev Gets the total weight of all signers.
function totalWeight() public view virtual returns (uint64) {
return (getSignerCount() + _totalExtraWeight).toUint64();
}
/**
* @dev Sets weights for multiple signers at once. Internal version without access control.
*
* Requirements:
*
* * `signers` and `weights` arrays must have the same length. Reverts with {MultiSignerERC7913WeightedMismatchedLength} 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.
*/
function _setSignerWeights(bytes[] memory signers, uint64[] memory weights) internal virtual {
require(signers.length == weights.length, MultiSignerERC7913WeightedMismatchedLength());
uint256 extraWeightAdded = 0;
uint256 extraWeightRemoved = 0;
for (uint256 i = 0; i < signers.length; ++i) {
bytes memory signer = signers[i];
require(isSigner(signer), MultiSignerERC7913NonexistentSigner(signer));
uint64 weight = weights[i];
require(weight > 0, MultiSignerERC7913WeightedInvalidWeight(signer, weight));
unchecked {
uint64 oldExtraWeight = _extraWeights[signer];
uint64 newExtraWeight = weight - 1;
if (oldExtraWeight != newExtraWeight) {
// Overflow impossible: weight values are bounded by uint64 and economic constraints
extraWeightRemoved += oldExtraWeight;
extraWeightAdded += _extraWeights[signer] = newExtraWeight;
emit ERC7913SignerWeightChanged(signer, weight);
}
}
}
unchecked {
// Safe from underflow: `extraWeightRemoved` is bounded by `_totalExtraWeight` by construction
// and weight values are bounded by uint64 and economic constraints
_totalExtraWeight = (uint256(_totalExtraWeight) + extraWeightAdded - extraWeightRemoved).toUint64();
}
_validateReachableThreshold();
}
/**
* @dev See {MultiSignerERC7913-_addSigners}.
*
* 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.
*/
function _addSigners(bytes[] memory newSigners) internal virtual override {
super._addSigners(newSigners);
// This will revert if the new signers cause an overflow
_validateReachableThreshold();
}
/**
* @dev See {MultiSignerERC7913-_removeSigners}.
*
* Just like {_addSigners}, this function does not emit {ERC7913SignerWeightChanged} events. The
* {ERC7913SignerRemoved} event emitted by {MultiSignerERC7913-_removeSigners} is enough to track weights here.
*/
function _removeSigners(bytes[] memory signers) internal virtual override {
// Clean up weights for removed signers
//
// The `extraWeightRemoved` is bounded by `_totalExtraWeight`. The `super._removeSigners` function will revert
// if the signers array contains any duplicates, ensuring each signer's weight is only counted once. Since
// `_totalExtraWeight` is stored as a `uint64`, the final subtraction operation is also safe.
unchecked {
uint64 extraWeightRemoved = 0;
for (uint256 i = 0; i < signers.length; ++i) {
bytes memory signer = signers[i];
extraWeightRemoved += _extraWeights[signer];
delete _extraWeights[signer];
}
_totalExtraWeight -= extraWeightRemoved;
}
super._removeSigners(signers);
}
/**
* @dev Sets the threshold for the multisignature operation. Internal version without access control.
*
* Requirements:
*
* * The {totalWeight} must be `>=` the {threshold}. Otherwise reverts with {MultiSignerERC7913UnreachableThreshold}
*
* NOTE: 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.
*/
function _validateReachableThreshold() internal view virtual override {
uint64 weight = totalWeight();
uint64 currentThreshold = threshold();
require(weight >= currentThreshold, MultiSignerERC7913UnreachableThreshold(weight, currentThreshold));
}
/**
* @dev Validates that the total weight of signers meets the threshold requirement.
*
* NOTE: 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.
*/
function _validateThreshold(bytes[] memory signers) internal view virtual override returns (bool) {
unchecked {
uint64 weight = 0;
for (uint256 i = 0; i < signers.length; ++i) {
// Overflow impossible: weight values are bounded by uint64 and economic constraints
weight += signerWeight(signers[i]);
}
return weight >= threshold();
}
}
}

View File

@@ -0,0 +1,56 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/signers/SignerECDSA.sol)
pragma solidity ^0.8.20;
import {AbstractSigner} from "./AbstractSigner.sol";
import {ECDSA} from "../ECDSA.sol";
/**
* @dev Implementation of {AbstractSigner} using xref:api:utils/cryptography#ECDSA[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:
*
* ```solidity
* contract MyAccountECDSA is Account, SignerECDSA, Initializable {
* function initialize(address signerAddr) public initializer {
* _setSigner(signerAddr);
* }
* }
* ```
*
* IMPORTANT: 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.
*/
abstract contract SignerECDSA is AbstractSigner {
address private _signer;
constructor(address signerAddr) {
_setSigner(signerAddr);
}
/**
* @dev Sets the signer with the address of the native signer. This function should be called during construction
* or through an initializer.
*/
function _setSigner(address signerAddr) internal {
_signer = signerAddr;
}
/// @dev Return the signer's address.
function signer() public view virtual returns (address) {
return _signer;
}
/// @inheritdoc AbstractSigner
function _rawSignatureValidation(
bytes32 hash,
bytes calldata signature
) internal view virtual override returns (bool) {
(address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecover(hash, signature);
return signer() == recovered && err == ECDSA.RecoverError.NoError;
}
}

View File

@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/signers/SignerERC7702.sol)
pragma solidity ^0.8.20;
import {AbstractSigner} from "./AbstractSigner.sol";
import {ECDSA} from "../ECDSA.sol";
/**
* @dev Implementation of {AbstractSigner} for implementation for an EOA. Useful for ERC-7702 accounts.
*
* @custom:stateless
*/
abstract contract SignerERC7702 is AbstractSigner {
/**
* @dev Validates the signature using the EOA's address (i.e. `address(this)`).
*/
function _rawSignatureValidation(
bytes32 hash,
bytes calldata signature
) internal view virtual override returns (bool) {
(address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecover(hash, signature);
return address(this) == recovered && err == ECDSA.RecoverError.NoError;
}
}

View File

@@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/signers/SignerERC7913.sol)
pragma solidity ^0.8.24;
import {AbstractSigner} from "./AbstractSigner.sol";
import {SignatureChecker} from "../SignatureChecker.sol";
/**
* @dev Implementation of {AbstractSigner} using
* https://eips.ethereum.org/EIPS/eip-7913[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:
*
* ```solidity
* contract MyAccountERC7913 is Account, SignerERC7913, Initializable {
* function initialize(bytes memory signer_) public initializer {
* _setSigner(signer_);
* }
*
* function setSigner(bytes memory signer_) public onlyEntryPointOrSelf {
* _setSigner(signer_);
* }
* }
* ```
*
* IMPORTANT: 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.
*/
abstract contract SignerERC7913 is AbstractSigner {
bytes private _signer;
constructor(bytes memory signer_) {
_setSigner(signer_);
}
/// @dev Return the ERC-7913 signer (i.e. `verifier || key`).
function signer() public view virtual returns (bytes memory) {
return _signer;
}
/// @dev Sets the signer (i.e. `verifier || key`) with an ERC-7913 formatted signer.
function _setSigner(bytes memory signer_) internal {
_signer = signer_;
}
/**
* @dev Verifies a signature using {SignatureChecker-isValidSignatureNow-bytes-bytes32-bytes-}
* with {signer}, `hash` and `signature`.
*/
function _rawSignatureValidation(
bytes32 hash,
bytes calldata signature
) internal view virtual override returns (bool) {
return SignatureChecker.isValidSignatureNow(signer(), hash, signature);
}
}

View File

@@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/signers/SignerP256.sol)
pragma solidity ^0.8.20;
import {AbstractSigner} from "./AbstractSigner.sol";
import {P256} from "../P256.sol";
/**
* @dev Implementation of {AbstractSigner} using xref:api:utils/cryptography#P256[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:
*
* ```solidity
* contract MyAccountP256 is Account, SignerP256, Initializable {
* function initialize(bytes32 qx, bytes32 qy) public initializer {
* _setSigner(qx, qy);
* }
* }
* ```
*
* IMPORTANT: 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.
*/
abstract contract SignerP256 is AbstractSigner {
bytes32 private _qx;
bytes32 private _qy;
error SignerP256InvalidPublicKey(bytes32 qx, bytes32 qy);
constructor(bytes32 qx, bytes32 qy) {
_setSigner(qx, qy);
}
/**
* @dev Sets the signer with a P256 public key. This function should be called during construction
* or through an initializer.
*/
function _setSigner(bytes32 qx, bytes32 qy) internal {
if (!P256.isValidPublicKey(qx, qy)) revert SignerP256InvalidPublicKey(qx, qy);
_qx = qx;
_qy = qy;
}
/// @dev Return the signer's P256 public key.
function signer() public view virtual returns (bytes32 qx, bytes32 qy) {
return (_qx, _qy);
}
/// @inheritdoc AbstractSigner
function _rawSignatureValidation(
bytes32 hash,
bytes calldata signature
) internal view virtual override returns (bool) {
if (signature.length < 0x40) return false;
bytes32 r = bytes32(signature[0x00:0x20]);
bytes32 s = bytes32(signature[0x20:0x40]);
(bytes32 qx, bytes32 qy) = signer();
return P256.verify(hash, r, s, qx, qy);
}
}

View File

@@ -0,0 +1,65 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/signers/SignerRSA.sol)
pragma solidity ^0.8.20;
import {AbstractSigner} from "./AbstractSigner.sol";
import {RSA} from "../RSA.sol";
/**
* @dev Implementation of {AbstractSigner} using xref:api:utils/cryptography#RSA[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:
*
* ```solidity
* contract MyAccountRSA is Account, SignerRSA, Initializable {
* function initialize(bytes memory e, bytes memory n) public initializer {
* _setSigner(e, n);
* }
* }
* ```
*
* IMPORTANT: 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.
*/
abstract contract SignerRSA is AbstractSigner {
bytes private _e;
bytes private _n;
constructor(bytes memory e, bytes memory n) {
_setSigner(e, n);
}
/**
* @dev Sets the signer with a RSA public key. This function should be called during construction
* or through an initializer.
*/
function _setSigner(bytes memory e, bytes memory n) internal {
_e = e;
_n = n;
}
/// @dev Return the signer's RSA public key.
function signer() public view virtual returns (bytes memory e, bytes memory n) {
return (_e, _n);
}
/**
* @dev See {AbstractSigner-_rawSignatureValidation}. Verifies a PKCSv1.5 signature by calling
* xref:api:utils/cryptography.adoc#RSA-pkcs1Sha256-bytes-bytes-bytes-bytes-[RSA.pkcs1Sha256].
*
* IMPORTANT: 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.
*/
function _rawSignatureValidation(
bytes32 hash,
bytes calldata signature
) internal view virtual override returns (bool) {
(bytes memory e, bytes memory n) = signer();
return RSA.pkcs1Sha256(abi.encodePacked(hash), signature, e, n);
}
}

View File

@@ -0,0 +1,99 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/signers/draft-ERC7739.sol)
pragma solidity ^0.8.20;
import {AbstractSigner} from "./AbstractSigner.sol";
import {EIP712} from "../EIP712.sol";
import {ERC7739Utils} from "../draft-ERC7739Utils.sol";
import {IERC1271} from "../../../interfaces/IERC1271.sol";
import {MessageHashUtils} from "../MessageHashUtils.sol";
import {ShortStrings} from "../../ShortStrings.sol";
/**
* @dev 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.
*
* NOTE: xref:api:utils/cryptography#EIP712[EIP-712] uses xref:api:utils/cryptography#ShortStrings[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
* https://eips.ethereum.org/EIPS/eip-7562#storage-rules[ERC-7562 storage access rules]).
*/
abstract contract ERC7739 is AbstractSigner, EIP712, IERC1271 {
using ERC7739Utils for *;
using MessageHashUtils for bytes32;
/**
* @dev 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)
*/
function isValidSignature(bytes32 hash, bytes calldata signature) public view virtual returns (bytes4 result) {
// For the hash `0x7739773977397739773977397739773977397739773977397739773977397739` and an empty signature,
// we return the magic value `0x77390001` as it's assumed impossible to find a preimage for it that can be used
// maliciously. Useful for simulation purposes and to validate whether the contract supports ERC-7739.
return
(_isValidNestedTypedDataSignature(hash, signature) || _isValidNestedPersonalSignSignature(hash, signature))
? IERC1271.isValidSignature.selector
: (hash == 0x7739773977397739773977397739773977397739773977397739773977397739 && signature.length == 0)
? bytes4(0x77390001)
: bytes4(0xffffffff);
}
/**
* @dev Nested personal signature verification.
*/
function _isValidNestedPersonalSignSignature(bytes32 hash, bytes calldata signature) private view returns (bool) {
return _rawSignatureValidation(_domainSeparatorV4().toTypedDataHash(hash.personalSignStructHash()), signature);
}
/**
* @dev Nested EIP-712 typed data verification.
*/
function _isValidNestedTypedDataSignature(
bytes32 hash,
bytes calldata encodedSignature
) private view returns (bool) {
// decode signature
(
bytes calldata signature,
bytes32 appSeparator,
bytes32 contentsHash,
string calldata contentsDescr
) = encodedSignature.decodeTypedDataSig();
(
,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
) = eip712Domain();
// Check that contentHash and separator are correct
// Rebuild nested hash
return
hash == appSeparator.toTypedDataHash(contentsHash) &&
bytes(contentsDescr).length != 0 &&
_rawSignatureValidation(
appSeparator.toTypedDataHash(
ERC7739Utils.typedDataSignStructHash(
contentsDescr,
contentsHash,
abi.encode(keccak256(bytes(name)), keccak256(bytes(version)), chainId, verifyingContract, salt)
)
),
signature
);
}
}

View File

@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/verifiers/ERC7913P256Verifier.sol)
pragma solidity ^0.8.20;
import {P256} from "../../../utils/cryptography/P256.sol";
import {IERC7913SignatureVerifier} from "../../../interfaces/IERC7913.sol";
/**
* @dev ERC-7913 signature verifier that support P256 (secp256r1) keys.
*
* @custom:stateless
*/
contract ERC7913P256Verifier is IERC7913SignatureVerifier {
/// @inheritdoc IERC7913SignatureVerifier
function verify(bytes calldata key, bytes32 hash, bytes calldata signature) public view virtual returns (bytes4) {
// Signature length may be 0x40 or 0x41.
if (key.length == 0x40 && signature.length >= 0x40) {
bytes32 qx = bytes32(key[0x00:0x20]);
bytes32 qy = bytes32(key[0x20:0x40]);
bytes32 r = bytes32(signature[0x00:0x20]);
bytes32 s = bytes32(signature[0x20:0x40]);
if (P256.verify(hash, r, s, qx, qy)) {
return IERC7913SignatureVerifier.verify.selector;
}
}
return 0xFFFFFFFF;
}
}

View File

@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/verifiers/ERC7913RSAVerifier.sol)
pragma solidity ^0.8.20;
import {RSA} from "../../../utils/cryptography/RSA.sol";
import {IERC7913SignatureVerifier} from "../../../interfaces/IERC7913.sol";
/**
* @dev ERC-7913 signature verifier that support RSA keys.
*
* @custom:stateless
*/
contract ERC7913RSAVerifier is IERC7913SignatureVerifier {
/// @inheritdoc IERC7913SignatureVerifier
function verify(bytes calldata key, bytes32 hash, bytes calldata signature) public view virtual returns (bytes4) {
(bytes memory e, bytes memory n) = abi.decode(key, (bytes, bytes));
return
RSA.pkcs1Sha256(abi.encodePacked(hash), signature, e, n)
? IERC7913SignatureVerifier.verify.selector
: bytes4(0xFFFFFFFF);
}
}