Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/EspressoNitroTEEVerifier.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {NitroValidator} from "@nitro-validator/NitroValidator.sol";
import {LibBytes} from "@nitro-validator/LibBytes.sol";
import {LibCborElement, CborElement, CborDecode} from "@nitro-validator/CborDecode.sol";
import {CertManager} from "@nitro-validator/CertManager.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import {IEspressoNitroTEEVerifier} from "./interface/IEspressoNitroTEEVerifier.sol";
/**
* @title Verifies quotes from the AWS Nitro Enclave (TEE) and attests on-chain
* @notice Contains the logic to verify an attestation and signature from the TEE and attest on-chain. It uses the NitroValidator contract
* from `base` to verify the quote. Along with some additional verification logic.
* (https://github.com/base/nitro-validator)
* The code of this contract is inspired from SystemConfigGlobal.sol
* (https://github.com/base/op-enclave/blob/main/contracts/src/SystemConfigGlobal.sol)
*/
contract EspressoNitroTEEVerifier is NitroValidator, IEspressoNitroTEEVerifier, Ownable2Step {
using CborDecode for bytes;
using LibBytes for bytes;
using LibCborElement for CborElement;
// PCR0 keccak hash
mapping(bytes32 => bool) public registeredEnclaveHash;
// Registered signers
mapping(address => bool) public registeredSigners;
// Certificate Manager
CertManager _certManager;
constructor(bytes32 enclaveHash, CertManager certManager)
NitroValidator(certManager)
Ownable()
{
_certManager = certManager;
registeredEnclaveHash[enclaveHash] = true;
_transferOwnership(msg.sender);
}
/**
* @notice This function registers a new signer by verifying an attestation from the AWS Nitro Enclave (TEE)
* @param attestation The attestation from the AWS Nitro Enclave (TEE)
* @param signature The cryptographic signature over the COSESign1 payload (extracted from the attestation)
*/
function registerSigner(bytes calldata attestation, bytes calldata signature) external {
Ptrs memory ptrs = validateAttestation(attestation, signature);
bytes32 pcr0Hash = attestation.keccak(ptrs.pcrs[0]);
if (!registeredEnclaveHash[pcr0Hash]) {
revert InvalidAWSEnclaveHash();
}
// The publicKey's first byte 0x04 byte followed which only determine if the public key is compressed or not.
// so we ignore the first byte.
bytes32 publicKeyHash =
attestation.keccak(ptrs.publicKey.start() + 1, ptrs.publicKey.length() - 1);
// Note: We take the keccak hash first to derive the address.
// This is the same which the go ethereum crypto library is doing for PubkeyToAddress()
address enclaveAddress = address(uint160(uint256(publicKeyHash)));
// Mark the signer as registered
if (!registeredSigners[enclaveAddress]) {
registeredSigners[enclaveAddress] = true;
emit AWSSignerRegistered(enclaveAddress, pcr0Hash);
}
}
/**
* @notice This function verifies a AWS Nitro Attestations CA Certificate on chain
* @param certificate The certificate from the attestation
* @param parentCertHash The keccak256 hash over the parent certificate
*/
function verifyCACert(bytes calldata certificate, bytes32 parentCertHash) external {
_certManager.verifyCACert(certificate, parentCertHash);
}
/**
* @notice This function verifies a AWS Nitro Attestations Client Certificate on chain
* @param certificate The certificate from the attestation
* @param parentCertHash The keccak256 hash over the parent certificate
*/
function verifyClientCert(bytes calldata certificate, bytes32 parentCertHash) external {
_certManager.verifyClientCert(certificate, parentCertHash);
}
/**
* @notice This function is a readonly function to check if a certificate is already verified on chain
* @param certHash The certificate keccak256 hash
*/
function certVerified(bytes32 certHash) external view returns (bool) {
bytes memory verifiedBytes = _certManager.verified(certHash);
return verifiedBytes.length > 0;
}
function setEnclaveHash(bytes32 enclaveHash, bool valid) external onlyOwner {
registeredEnclaveHash[enclaveHash] = valid;
emit AWSEnclaveHashSet(enclaveHash, valid);
}
function deleteRegisteredSigners(address[] memory signers) external onlyOwner {
for (uint256 i = 0; i < signers.length; i++) {
delete registeredSigners[signers[i]];
emit DeletedAWSRegisteredSigner(signers[i]);
}
}
}
"
},
"lib/nitro-validator/src/NitroValidator.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import {ICertManager} from "./ICertManager.sol";
import {Sha2Ext} from "./Sha2Ext.sol";
import {CborDecode, CborElement, LibCborElement} from "./CborDecode.sol";
import {ECDSA384} from "@solarity/libs/crypto/ECDSA384.sol";
import {ECDSA384Curve} from "./ECDSA384Curve.sol";
import {LibBytes} from "./LibBytes.sol";
// adapted from https://github.com/marlinprotocol/NitroProver/blob/f1d368d1f172ad3a55cd2aaaa98ad6a6e7dcde9d/src/NitroProver.sol
contract NitroValidator {
using LibBytes for bytes;
using CborDecode for bytes;
using LibCborElement for CborElement;
bytes32 public constant ATTESTATION_TBS_PREFIX = 0x63ce814bd924c1ef12c43686e4cbf48ed1639a78387b0570c23ca921e8ce071c; // keccak256(hex"846a5369676e61747572653144a101382240")
bytes32 public constant ATTESTATION_DIGEST = 0x501a3a7a4e0cf54b03f2488098bdd59bc1c2e8d741a300d6b25926d531733fef; // keccak256("SHA384")
bytes32 public constant CERTIFICATE_KEY = 0x925cec779426f44d8d555e01d2683a3a765ce2fa7562ca7352aeb09dfc57ea6a; // keccak256(bytes("certificate"))
bytes32 public constant PUBLIC_KEY_KEY = 0xc7b28019ccfdbd30ffc65951d94bb85c9e2b8434111a000b5afd533ce65f57a4; // keccak256(bytes("public_key"))
bytes32 public constant MODULE_ID_KEY = 0x8ce577cf664c36ba5130242bf5790c2675e9f4e6986a842b607821bee25372ee; // keccak256(bytes("module_id"))
bytes32 public constant TIMESTAMP_KEY = 0x4ebf727c48eac2c66272456b06a885c5cc03e54d140f63b63b6fd10c1227958e; // keccak256(bytes("timestamp"))
bytes32 public constant USER_DATA_KEY = 0x5e4ea5393e4327b3014bc32f2264336b0d1ee84a4cfd197c8ad7e1e16829a16a; // keccak256(bytes("user_data"))
bytes32 public constant CABUNDLE_KEY = 0x8a8cb7aa1da17ada103546ae6b4e13ccc2fafa17adf5f93925e0a0a4e5681a6a; // keccak256(bytes("cabundle"))
bytes32 public constant DIGEST_KEY = 0x682a7e258d80bd2421d3103cbe71e3e3b82138116756b97b8256f061dc2f11fb; // keccak256(bytes("digest"))
bytes32 public constant NONCE_KEY = 0x7ab1577440dd7bedf920cb6de2f9fc6bf7ba98c78c85a3fa1f8311aac95e1759; // keccak256(bytes("nonce"))
bytes32 public constant PCRS_KEY = 0x61585f8bc67a4b6d5891a4639a074964ac66fc2241dc0b36c157dc101325367a; // keccak256(bytes("pcrs"))
struct Ptrs {
CborElement moduleID;
uint64 timestamp;
CborElement digest;
CborElement[] pcrs;
CborElement cert;
CborElement[] cabundle;
CborElement publicKey;
CborElement userData;
CborElement nonce;
}
ICertManager public immutable certManager;
constructor(ICertManager _certManager) {
certManager = _certManager;
}
function decodeAttestationTbs(bytes memory attestation)
external
pure
returns (bytes memory attestationTbs, bytes memory signature)
{
uint256 offset = 1;
if (attestation[0] == 0xD2) {
offset = 2;
}
CborElement protectedPtr = attestation.byteStringAt(offset);
CborElement unprotectedPtr = attestation.nextMap(protectedPtr);
CborElement payloadPtr = attestation.nextByteString(unprotectedPtr);
CborElement signaturePtr = attestation.nextByteString(payloadPtr);
uint256 rawProtectedLength = protectedPtr.end() - offset;
uint256 rawPayloadLength = payloadPtr.end() - unprotectedPtr.end();
bytes memory rawProtectedBytes = attestation.slice(offset, rawProtectedLength);
bytes memory rawPayloadBytes = attestation.slice(unprotectedPtr.end(), rawPayloadLength);
attestationTbs =
_constructAttestationTbs(rawProtectedBytes, rawProtectedLength, rawPayloadBytes, rawPayloadLength);
signature = attestation.slice(signaturePtr.start(), signaturePtr.length());
}
function validateAttestation(bytes memory attestationTbs, bytes memory signature) public returns (Ptrs memory) {
Ptrs memory ptrs = _parseAttestation(attestationTbs);
require(ptrs.moduleID.length() > 0, "no module id");
require(ptrs.timestamp > 0, "no timestamp");
require(ptrs.cabundle.length > 0, "no cabundle");
require(attestationTbs.keccak(ptrs.digest) == ATTESTATION_DIGEST, "invalid digest");
require(1 <= ptrs.pcrs.length && ptrs.pcrs.length <= 32, "invalid pcrs");
require(
ptrs.publicKey.isNull() || (1 <= ptrs.publicKey.length() && ptrs.publicKey.length() <= 1024),
"invalid pub key"
);
require(ptrs.userData.isNull() || (ptrs.userData.length() <= 512), "invalid user data");
require(ptrs.nonce.isNull() || (ptrs.nonce.length() <= 512), "invalid nonce");
for (uint256 i = 0; i < ptrs.pcrs.length; i++) {
require(
ptrs.pcrs[i].length() == 32 || ptrs.pcrs[i].length() == 48 || ptrs.pcrs[i].length() == 64, "invalid pcr"
);
}
bytes memory cert = attestationTbs.slice(ptrs.cert);
bytes[] memory cabundle = new bytes[](ptrs.cabundle.length);
for (uint256 i = 0; i < ptrs.cabundle.length; i++) {
require(1 <= ptrs.cabundle[i].length() && ptrs.cabundle[i].length() <= 1024, "invalid cabundle cert");
cabundle[i] = attestationTbs.slice(ptrs.cabundle[i]);
}
ICertManager.VerifiedCert memory parent = verifyCertBundle(cert, cabundle);
bytes memory hash = Sha2Ext.sha384(attestationTbs, 0, attestationTbs.length);
_verifySignature(parent.pubKey, hash, signature);
return ptrs;
}
function verifyCertBundle(bytes memory certificate, bytes[] memory cabundle)
internal
returns (ICertManager.VerifiedCert memory)
{
bytes32 parentHash;
for (uint256 i = 0; i < cabundle.length; i++) {
parentHash = certManager.verifyCACert(cabundle[i], parentHash);
}
return certManager.verifyClientCert(certificate, parentHash);
}
function _constructAttestationTbs(
bytes memory rawProtectedBytes,
uint256 rawProtectedLength,
bytes memory rawPayloadBytes,
uint256 rawPayloadLength
) internal pure returns (bytes memory attestationTbs) {
attestationTbs = new bytes(13 + rawProtectedLength + rawPayloadLength);
attestationTbs[0] = bytes1(uint8(4 << 5 | 4)); // Outer: 4-length array
attestationTbs[1] = bytes1(uint8(3 << 5 | 10)); // Context: 10-length string
attestationTbs[12 + rawProtectedLength] = bytes1(uint8(2 << 5)); // ExternalAAD: 0-length bytes
string memory sig = "Signature1";
uint256 dest;
uint256 sigSrc;
uint256 protectedSrc;
uint256 payloadSrc;
assembly {
dest := add(attestationTbs, 32)
sigSrc := add(sig, 32)
protectedSrc := add(rawProtectedBytes, 32)
payloadSrc := add(rawPayloadBytes, 32)
}
LibBytes.memcpy(dest + 2, sigSrc, 10);
LibBytes.memcpy(dest + 12, protectedSrc, rawProtectedLength);
LibBytes.memcpy(dest + 13 + rawProtectedLength, payloadSrc, rawPayloadLength);
}
function _parseAttestation(bytes memory attestationTbs) internal pure returns (Ptrs memory) {
require(attestationTbs.keccak(0, 18) == ATTESTATION_TBS_PREFIX, "invalid attestation prefix");
CborElement payload = attestationTbs.byteStringAt(18);
CborElement current = attestationTbs.mapAt(payload.start());
Ptrs memory ptrs;
uint256 map_length = current.value();
for (uint256 pair = 0; pair < map_length; pair++) {
current = attestationTbs.nextTextString(current);
bytes32 keyHash = attestationTbs.keccak(current);
if (keyHash == MODULE_ID_KEY) {
current = attestationTbs.nextTextString(current);
ptrs.moduleID = current;
} else if (keyHash == DIGEST_KEY) {
current = attestationTbs.nextTextString(current);
ptrs.digest = current;
} else if (keyHash == CERTIFICATE_KEY) {
current = attestationTbs.nextByteString(current);
ptrs.cert = current;
} else if (keyHash == PUBLIC_KEY_KEY) {
current = attestationTbs.nextByteStringOrNull(current);
ptrs.publicKey = current;
} else if (keyHash == USER_DATA_KEY) {
current = attestationTbs.nextByteStringOrNull(current);
ptrs.userData = current;
} else if (keyHash == NONCE_KEY) {
current = attestationTbs.nextByteStringOrNull(current);
ptrs.nonce = current;
} else if (keyHash == TIMESTAMP_KEY) {
current = attestationTbs.nextPositiveInt(current);
ptrs.timestamp = uint64(current.value());
} else if (keyHash == CABUNDLE_KEY) {
current = attestationTbs.nextArray(current);
ptrs.cabundle = new CborElement[](current.value());
for (uint256 i = 0; i < ptrs.cabundle.length; i++) {
current = attestationTbs.nextByteString(current);
ptrs.cabundle[i] = current;
}
} else if (keyHash == PCRS_KEY) {
current = attestationTbs.nextMap(current);
ptrs.pcrs = new CborElement[](current.value());
for (uint256 i = 0; i < ptrs.pcrs.length; i++) {
current = attestationTbs.nextPositiveInt(current);
uint256 key = current.value();
require(key < ptrs.pcrs.length, "invalid pcr key value");
require(CborElement.unwrap(ptrs.pcrs[key]) == 0, "duplicate pcr key");
current = attestationTbs.nextByteString(current);
ptrs.pcrs[key] = current;
}
} else {
revert("invalid attestation key");
}
}
return ptrs;
}
function _verifySignature(bytes memory pubKey, bytes memory hash, bytes memory sig) internal view {
require(ECDSA384.verify(ECDSA384Curve.p384(), hash, sig, pubKey), "invalid sig");
}
}
"
},
"lib/nitro-validator/src/LibBytes.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
library LibBytes {
function keccak(bytes memory data, uint256 offset, uint256 length) internal pure returns (bytes32 result) {
require(offset + length <= data.length, "index out of bounds");
assembly {
result := keccak256(add(data, add(32, offset)), length)
}
}
function slice(bytes memory b, uint256 offset, uint256 length) internal pure returns (bytes memory result) {
require(offset + length <= b.length, "index out of bounds");
// Create a new bytes structure and copy contents
result = new bytes(length);
uint256 dest;
uint256 src;
assembly {
dest := add(result, 32)
src := add(b, add(32, offset))
}
memcpy(dest, src, length);
return result;
}
function readUint16(bytes memory b, uint256 index) internal pure returns (uint16) {
require(b.length >= index + 2, "index out of bounds");
bytes2 result;
assembly {
result := mload(add(b, add(index, 32)))
}
return uint16(result);
}
function readUint32(bytes memory b, uint256 index) internal pure returns (uint32) {
require(b.length >= index + 4, "index out of bounds");
bytes4 result;
assembly {
result := mload(add(b, add(index, 32)))
}
return uint32(result);
}
function readUint64(bytes memory b, uint256 index) internal pure returns (uint64) {
require(b.length >= index + 8, "index out of bounds");
bytes8 result;
assembly {
result := mload(add(b, add(index, 32)))
}
return uint64(result);
}
function memcpy(uint256 dest, uint256 src, uint256 len) internal pure {
// Copy word-length chunks while possible
for (; len >= 32; len -= 32) {
assembly {
mstore(dest, mload(src))
}
dest += 32;
src += 32;
}
if (len > 0) {
// Copy remaining bytes
uint256 mask = 256 ** (32 - len) - 1;
assembly {
let srcpart := and(mload(src), not(mask))
let destpart := and(mload(dest), mask)
mstore(dest, or(destpart, srcpart))
}
}
}
}
"
},
"lib/nitro-validator/src/CborDecode.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import {LibBytes} from "./LibBytes.sol";
type CborElement is uint256;
// Major types
uint8 constant TYPE_MAJOR_MASK = 0xe0; // first 3 bits
uint8 constant TYPE_UNSIGNED_INTEGER = 0x00;
uint8 constant TYPE_NEGATIVE_INTEGER = 0x20;
uint8 constant TYPE_BYTE_STRING = 0x40;
uint8 constant TYPE_TEXT_STRING = 0x60;
uint8 constant TYPE_ARRAY = 0x80;
uint8 constant TYPE_MAP = 0xa0;
uint8 constant TYPE_TAG = 0xc0;
uint8 constant TYPE_SIMPLE_OR_FLOAT = 0xe0;
// Signals parser that we don't expect a concrete type
uint8 constant TYPE_ANY = 0xff;
// Additional information
uint8 constant ADDITIONAL_INFO_MASK = 0x1f; // last 5 bits
uint8 constant ADDITIONAL_INFO_1BYTE = 0x18;
uint8 constant ADDITIONAL_INFO_2BYTES = 0x19;
uint8 constant ADDITIONAL_INFO_4BYTES = 0x1a;
uint8 constant ADDITIONAL_INFO_8BYTES = 0x1b;
uint8 constant ADDITIONAL_INFO_INDEFINITE = 0x1f;
library LibCborElement {
// Cbor element type
function cborType(CborElement self) internal pure returns (uint8) {
return uint8(CborElement.unwrap(self));
}
// First byte index of the content
function start(CborElement self) internal pure returns (uint256) {
return uint80(CborElement.unwrap(self) >> 80);
}
// First byte index of the next element (exclusive end of content)
function end(CborElement self) internal pure returns (uint256) {
return start(self) + length(self);
}
// Content length (0 for non-string types)
function length(CborElement self) internal pure returns (uint256) {
uint8 _type = cborType(self);
if (_type == TYPE_BYTE_STRING || _type == TYPE_TEXT_STRING) {
// length is non-zero only for byte strings and text strings
return value(self);
}
return 0;
}
// Value of the element (length for string/map/array types, value for others)
function value(CborElement self) internal pure returns (uint64) {
return uint64(CborElement.unwrap(self) >> 160);
}
// Returns true if the element is null
function isNull(CborElement self) internal pure returns (bool) {
uint8 _type = cborType(self);
return _type == 0xf6 || _type == 0xf7; // null or undefined
}
// Pack 3 uint80s into a uint256
function toCborElement(uint256 _type, uint256 _start, uint256 _length) internal pure returns (CborElement) {
return CborElement.wrap(_type | _start << 80 | _length << 160);
}
}
library CborDecode {
using LibBytes for bytes;
using LibCborElement for CborElement;
// Calculate the keccak256 hash of the given cbor element
function keccak(bytes memory cbor, CborElement ptr) internal pure returns (bytes32) {
return cbor.keccak(ptr.start(), ptr.length());
}
// Take a slice of the given cbor element
function slice(bytes memory cbor, CborElement ptr) internal pure returns (bytes memory) {
return cbor.slice(ptr.start(), ptr.length());
}
function byteStringAt(bytes memory cbor, uint256 ix) internal pure returns (CborElement) {
return elementAt(cbor, ix, TYPE_BYTE_STRING, true);
}
function nextByteString(bytes memory cbor, CborElement ptr) internal pure returns (CborElement) {
return elementAt(cbor, ptr.end(), TYPE_BYTE_STRING, true);
}
function nextByteStringOrNull(bytes memory cbor, CborElement ptr) internal pure returns (CborElement) {
return elementAt(cbor, ptr.end(), TYPE_BYTE_STRING, false);
}
function nextTextString(bytes memory cbor, CborElement ptr) internal pure returns (CborElement) {
return elementAt(cbor, ptr.end(), TYPE_TEXT_STRING, true);
}
function nextPositiveInt(bytes memory cbor, CborElement ptr) internal pure returns (CborElement) {
return elementAt(cbor, ptr.end(), TYPE_UNSIGNED_INTEGER, true);
}
function mapAt(bytes memory cbor, uint256 ix) internal pure returns (CborElement) {
return elementAt(cbor, ix, TYPE_MAP, true);
}
function nextMap(bytes memory cbor, CborElement ptr) internal pure returns (CborElement) {
return mapAt(cbor, ptr.end());
}
function nextArray(bytes memory cbor, CborElement ptr) internal pure returns (CborElement) {
return elementAt(cbor, ptr.end(), TYPE_ARRAY, true);
}
function elementAt(bytes memory cbor, uint256 ix, uint8 expectedType, bool required)
internal
pure
returns (CborElement)
{
uint8 _type = uint8(cbor[ix] & 0xe0);
uint8 ai = uint8(cbor[ix] & 0x1f);
if (_type == TYPE_SIMPLE_OR_FLOAT) {
// The primitive type can encode a float, bool, null, undefined, etc.
// We only need support for null (and we treat undefined as null).
require(ai == 22 || ai == 23, "only null primitive values are supported");
require(!required, "null value for required element");
// retain the additional information:
return LibCborElement.toCborElement(_type | ai, ix + 1, 0);
}
if (expectedType != TYPE_ANY) {
require((_type == expectedType), "unexpected type");
}
require((ai <= ADDITIONAL_INFO_8BYTES || ai == ADDITIONAL_INFO_INDEFINITE), "unsupported type");
if (ai == ADDITIONAL_INFO_1BYTE) {
return LibCborElement.toCborElement(_type, ix + 2, uint8(cbor[ix + 1]));
} else if (ai == ADDITIONAL_INFO_2BYTES) {
return LibCborElement.toCborElement(_type, ix + 3, cbor.readUint16(ix + 1));
} else if (ai == ADDITIONAL_INFO_4BYTES) {
return LibCborElement.toCborElement(_type, ix + 5, cbor.readUint32(ix + 1));
} else if (ai == ADDITIONAL_INFO_8BYTES) {
return LibCborElement.toCborElement(_type, ix + 9, cbor.readUint64(ix + 1));
} else if (ai == ADDITIONAL_INFO_INDEFINITE) {
uint256 cursor = ix + 1;
uint256 length = 0;
uint256 nested_length = 0;
while (cursor < cbor.length) {
if (cbor[cursor] == 0xFF) {
if (_type == TYPE_MAP) {
return LibCborElement.toCborElement(_type, ix + 1, (length - nested_length) / 2);
} else {
return LibCborElement.toCborElement(_type, ix + 1, (length - nested_length));
}
}
CborElement el = elementAt(cbor, cursor, TYPE_ANY, false);
length += 1;
if (el.cborType() == TYPE_MAP) {
nested_length += el.value() * 2;
} else if (el.cborType() == TYPE_ARRAY) {
nested_length += el.value();
}
cursor = el.end();
}
revert("couldn't find the end of indefinite length item");
}
return LibCborElement.toCborElement(_type, ix + 1, ai);
}
}
"
},
"lib/nitro-validator/src/CertManager.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
import {Sha2Ext} from "./Sha2Ext.sol";
import {Asn1Decode, Asn1Ptr, LibAsn1Ptr} from "./Asn1Decode.sol";
import {ECDSA384} from "@solarity/libs/crypto/ECDSA384.sol";
import {ECDSA384Curve} from "./ECDSA384Curve.sol";
import {LibBytes} from "./LibBytes.sol";
import {ICertManager} from "./ICertManager.sol";
// adapted from https://github.com/marlinprotocol/NitroProver/blob/f1d368d1f172ad3a55cd2aaaa98ad6a6e7dcde9d/src/CertManager.sol
// Manages a mapping of verified certificates and their metadata.
// The root of trust is the AWS Nitro root cert.
// Certificate revocation is not currently supported.
contract CertManager is ICertManager {
using Asn1Decode for bytes;
using LibAsn1Ptr for Asn1Ptr;
using LibBytes for bytes;
event CertVerified(bytes32 indexed certHash);
// root CA certificate constants (don't store it to reduce contract size)
bytes32 public constant ROOT_CA_CERT_HASH = 0x311d96fcd5c5e0ccf72ef548e2ea7d4c0cd53ad7c4cc49e67471aed41d61f185;
uint64 public constant ROOT_CA_CERT_NOT_AFTER = 2519044085;
int64 public constant ROOT_CA_CERT_MAX_PATH_LEN = -1;
bytes32 public constant ROOT_CA_CERT_SUBJECT_HASH =
0x3c3e2e5f1dd14dee5db88341ba71521e939afdb7881aa24c9f1e1c007a2fa8b6;
bytes public constant ROOT_CA_CERT_PUB_KEY =
hex"fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4";
// OID 1.2.840.10045.4.3.3 represents {iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA2(3) ecdsa-with-SHA384(3)}
// which essentially means the signature algorithm is Elliptic curve Digital Signature Algorithm (DSA) coupled with the Secure Hash Algorithm 384 (SHA384) algorithm
// @dev Sig algo is hardcoded here because the root certificate's sig algorithm is known beforehand
// @dev reference article for encoding https://learn.microsoft.com/en-in/windows/win32/seccertenroll/about-object-identifier
bytes32 public constant CERT_ALGO_OID = 0x53ce037f0dfaa43ef13b095f04e68a6b5e3f1519a01a3203a1e6440ba915b87e; // keccak256(hex"06082a8648ce3d040303")
// https://oid-rep.orange-labs.fr/get/1.2.840.10045.2.1
// 1.2.840.10045.2.1 {iso(1) member-body(2) us(840) ansi-x962(10045) keyType(2) ecPublicKey(1)} represents Elliptic curve public key cryptography
bytes32 public constant EC_PUB_KEY_OID = 0xb60fee1fd85f867dd7c8d16884a49a20287ebe4c0fb49294e9825988aa8e42b4; // keccak256(hex"2a8648ce3d0201")
// https://oid-rep.orange-labs.fr/get/1.3.132.0.34
// 1.3.132.0.34 {iso(1) identified-organization(3) certicom(132) curve(0) ansip384r1(34)} represents NIST 384-bit elliptic curve
bytes32 public constant SECP_384_R1_OID = 0xbd74344bb507daeb9ed315bc535f24a236ccab72c5cd6945fb0efe5c037e2097; // keccak256(hex"2b81040022")
// extension OID certificate constants
bytes32 public constant BASIC_CONSTRAINTS_OID = 0x6351d72a43cb42fb9a2531a28608c278c89629f8f025b5f5dc705f3fe45e950a; // keccak256(hex"551d13")
bytes32 public constant KEY_USAGE_OID = 0x45529d8772b07ebd6d507a1680da791f4a2192882bf89d518801579f7a5167d2; // keccak256(hex"551d0f")
// certHash -> VerifiedCert
mapping(bytes32 => bytes) public verified;
constructor() {
_saveVerified(
ROOT_CA_CERT_HASH,
VerifiedCert({
ca: true,
notAfter: ROOT_CA_CERT_NOT_AFTER,
maxPathLen: ROOT_CA_CERT_MAX_PATH_LEN,
subjectHash: ROOT_CA_CERT_SUBJECT_HASH,
pubKey: ROOT_CA_CERT_PUB_KEY
})
);
}
function verifyCACert(bytes memory cert, bytes32 parentCertHash) external returns (bytes32) {
bytes32 certHash = keccak256(cert);
_verifyCert(cert, certHash, true, _loadVerified(parentCertHash));
return certHash;
}
function verifyClientCert(bytes memory cert, bytes32 parentCertHash) external returns (VerifiedCert memory) {
return _verifyCert(cert, keccak256(cert), false, _loadVerified(parentCertHash));
}
function _verifyCert(bytes memory certificate, bytes32 certHash, bool ca, VerifiedCert memory parent)
internal
returns (VerifiedCert memory)
{
if (certHash != ROOT_CA_CERT_HASH) {
require(parent.pubKey.length > 0, "parent cert unverified");
require(parent.notAfter >= block.timestamp, "parent cert expired");
require(parent.ca, "parent cert is not a CA");
require(!ca || parent.maxPathLen != 0, "maxPathLen exceeded");
}
// skip verification if already verified
VerifiedCert memory cert = _loadVerified(certHash);
if (cert.pubKey.length != 0) {
require(cert.notAfter >= block.timestamp, "cert expired");
require(cert.ca == ca, "cert is not a CA");
return cert;
}
Asn1Ptr root = certificate.root();
Asn1Ptr tbsCertPtr = certificate.firstChildOf(root);
(uint64 notAfter, int64 maxPathLen, bytes32 issuerHash, bytes32 subjectHash, bytes memory pubKey) =
_parseTbs(certificate, tbsCertPtr, ca);
require(parent.subjectHash == issuerHash, "issuer / subject mismatch");
// constrain maxPathLen to parent's maxPathLen-1
if (parent.maxPathLen > 0 && (maxPathLen < 0 || maxPathLen >= parent.maxPathLen)) {
maxPathLen = parent.maxPathLen - 1;
}
_verifyCertSignature(certificate, tbsCertPtr, parent.pubKey);
cert =
VerifiedCert({ca: ca, notAfter: notAfter, maxPathLen: maxPathLen, subjectHash: subjectHash, pubKey: pubKey});
_saveVerified(certHash, cert);
emit CertVerified(certHash);
return cert;
}
function _parseTbs(bytes memory certificate, Asn1Ptr ptr, bool ca)
internal
view
returns (uint64 notAfter, int64 maxPathLen, bytes32 issuerHash, bytes32 subjectHash, bytes memory pubKey)
{
Asn1Ptr versionPtr = certificate.firstChildOf(ptr);
Asn1Ptr vPtr = certificate.firstChildOf(versionPtr);
Asn1Ptr serialPtr = certificate.nextSiblingOf(versionPtr);
Asn1Ptr sigAlgoPtr = certificate.nextSiblingOf(serialPtr);
require(certificate.keccak(sigAlgoPtr.content(), sigAlgoPtr.length()) == CERT_ALGO_OID, "invalid cert sig algo");
uint256 version = certificate.uintAt(vPtr);
// as extensions are used in cert, version should be 3 (value 2) as per https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.1
require(version == 2, "version should be 3");
(notAfter, maxPathLen, issuerHash, subjectHash, pubKey) = _parseTbsInner(certificate, sigAlgoPtr, ca);
}
function _parseTbsInner(bytes memory certificate, Asn1Ptr sigAlgoPtr, bool ca)
internal
view
returns (uint64 notAfter, int64 maxPathLen, bytes32 issuerHash, bytes32 subjectHash, bytes memory pubKey)
{
Asn1Ptr issuerPtr = certificate.nextSiblingOf(sigAlgoPtr);
issuerHash = certificate.keccak(issuerPtr.content(), issuerPtr.length());
Asn1Ptr validityPtr = certificate.nextSiblingOf(issuerPtr);
Asn1Ptr subjectPtr = certificate.nextSiblingOf(validityPtr);
subjectHash = certificate.keccak(subjectPtr.content(), subjectPtr.length());
Asn1Ptr subjectPublicKeyInfoPtr = certificate.nextSiblingOf(subjectPtr);
Asn1Ptr extensionsPtr = certificate.nextSiblingOf(subjectPublicKeyInfoPtr);
if (certificate[extensionsPtr.header()] == 0x81) {
// skip optional issuerUniqueID
extensionsPtr = certificate.nextSiblingOf(extensionsPtr);
}
if (certificate[extensionsPtr.header()] == 0x82) {
// skip optional subjectUniqueID
extensionsPtr = certificate.nextSiblingOf(extensionsPtr);
}
notAfter = _verifyValidity(certificate, validityPtr);
maxPathLen = _verifyExtensions(certificate, extensionsPtr, ca);
pubKey = _parsePubKey(certificate, subjectPublicKeyInfoPtr);
}
function _parsePubKey(bytes memory certificate, Asn1Ptr subjectPublicKeyInfoPtr)
internal
pure
returns (bytes memory subjectPubKey)
{
Asn1Ptr pubKeyAlgoPtr = certificate.firstChildOf(subjectPublicKeyInfoPtr);
Asn1Ptr pubKeyAlgoIdPtr = certificate.firstChildOf(pubKeyAlgoPtr);
Asn1Ptr algoParamsPtr = certificate.nextSiblingOf(pubKeyAlgoIdPtr);
Asn1Ptr subjectPublicKeyPtr = certificate.nextSiblingOf(pubKeyAlgoPtr);
Asn1Ptr subjectPubKeyPtr = certificate.bitstring(subjectPublicKeyPtr);
require(
certificate.keccak(pubKeyAlgoIdPtr.content(), pubKeyAlgoIdPtr.length()) == EC_PUB_KEY_OID,
"invalid cert algo id"
);
require(
certificate.keccak(algoParamsPtr.content(), algoParamsPtr.length()) == SECP_384_R1_OID,
"invalid cert algo param"
);
uint256 end = subjectPubKeyPtr.content() + subjectPubKeyPtr.length();
subjectPubKey = certificate.slice(end - 96, 96);
}
function _verifyValidity(bytes memory certificate, Asn1Ptr validityPtr) internal view returns (uint64 notAfter) {
Asn1Ptr notBeforePtr = certificate.firstChildOf(validityPtr);
Asn1Ptr notAfterPtr = certificate.nextSiblingOf(notBeforePtr);
uint256 notBefore = certificate.timestampAt(notBeforePtr);
notAfter = uint64(certificate.timestampAt(notAfterPtr));
require(notBefore <= block.timestamp, "certificate not valid yet");
require(notAfter >= block.timestamp, "certificate not valid anymore");
}
function _verifyExtensions(bytes memory certificate, Asn1Ptr extensionsPtr, bool ca)
internal
pure
returns (int64 maxPathLen)
{
require(certificate[extensionsPtr.header()] == 0xa3, "invalid extensions");
extensionsPtr = certificate.firstChildOf(extensionsPtr);
Asn1Ptr extensionPtr = certificate.firstChildOf(extensionsPtr);
uint256 end = extensionsPtr.content() + extensionsPtr.length();
bool basicConstraintsFound = false;
bool keyUsageFound = false;
maxPathLen = -1;
while (true) {
Asn1Ptr oidPtr = certificate.firstChildOf(extensionPtr);
bytes32 oid = certificate.keccak(oidPtr.content(), oidPtr.length());
if (oid == BASIC_CONSTRAINTS_OID || oid == KEY_USAGE_OID) {
Asn1Ptr valuePtr = certificate.nextSiblingOf(oidPtr);
if (certificate[valuePtr.header()] == 0x01) {
// skip optional critical bool
require(valuePtr.length() == 1, "invalid critical bool value");
valuePtr = certificate.nextSiblingOf(valuePtr);
}
valuePtr = certificate.octetString(valuePtr);
if (oid == BASIC_CONSTRAINTS_OID) {
basicConstraintsFound = true;
maxPathLen = _verifyBasicConstraintsExtension(certificate, valuePtr, ca);
} else {
keyUsageFound = true;
_verifyKeyUsageExtension(certificate, valuePtr, ca);
}
}
if (extensionPtr.content() + extensionPtr.length() == end) {
break;
}
extensionPtr = certificate.nextSiblingOf(extensionPtr);
}
require(basicConstraintsFound, "basicConstraints not found");
require(keyUsageFound, "keyUsage not found");
require(ca || maxPathLen == -1, "maxPathLen must be undefined for client cert");
}
function _verifyBasicConstraintsExtension(bytes memory certificate, Asn1Ptr valuePtr, bool ca)
internal
pure
returns (int64 maxPathLen)
{
maxPathLen = -1;
Asn1Ptr basicConstraintsPtr = certificate.firstChildOf(valuePtr);
bool isCA;
if (certificate[basicConstraintsPtr.header()] == 0x01) {
require(basicConstraintsPtr.length() == 1, "invalid isCA bool value");
isCA = certificate[basicConstraintsPtr.content()] == 0xff;
basicConstraintsPtr = certificate.nextSiblingOf(basicConstraintsPtr);
}
require(ca == isCA, "isCA must be true for CA certs");
if (certificate[basicConstraintsPtr.header()] == 0x02) {
maxPathLen = int64(uint64(certificate.uintAt(basicConstraintsPtr)));
}
}
function _verifyKeyUsageExtension(bytes memory certificate, Asn1Ptr valuePtr, bool ca) internal pure {
uint256 value = certificate.bitstringUintAt(valuePtr);
// bits are reversed (DigitalSignature 0x01 => 0x80, CertSign 0x32 => 0x04)
if (ca) {
require(value & 0x04 == 0x04, "CertSign must be present");
} else {
require(value & 0x80 == 0x80, "DigitalSignature must be present");
}
}
function _verifyCertSignature(bytes memory certificate, Asn1Ptr ptr, bytes memory pubKey) internal view {
Asn1Ptr sigAlgoPtr = certificate.nextSiblingOf(ptr);
require(certificate.keccak(sigAlgoPtr.content(), sigAlgoPtr.length()) == CERT_ALGO_OID, "invalid cert sig algo");
bytes memory hash = Sha2Ext.sha384(certificate, ptr.header(), ptr.totalLength());
Asn1Ptr sigPtr = certificate.nextSiblingOf(sigAlgoPtr);
Asn1Ptr sigBPtr = certificate.bitstring(sigPtr);
Asn1Ptr sigRoot = certificate.rootOf(sigBPtr);
Asn1Ptr sigRPtr = certificate.firstChildOf(sigRoot);
Asn1Ptr sigSPtr = certificate.nextSiblingOf(sigRPtr);
(uint128 rhi, uint256 rlo) = certificate.uint384At(sigRPtr);
(uint128 shi, uint256 slo) = certificate.uint384At(sigSPtr);
bytes memory sigPacked = abi.encodePacked(rhi, rlo, shi, slo);
_verifySignature(pubKey, hash, sigPacked);
}
function _verifySignature(bytes memory pubKey, bytes memory hash, bytes memory sig) internal view {
require(ECDSA384.verify(ECDSA384Curve.p384(), hash, sig, pubKey), "invalid sig");
}
function _saveVerified(bytes32 certHash, VerifiedCert memory cert) internal {
verified[certHash] = abi.encodePacked(cert.ca, cert.notAfter, cert.maxPathLen, cert.subjectHash, cert.pubKey);
}
function _loadVerified(bytes32 certHash) internal view returns (VerifiedCert memory) {
bytes memory packed = verified[certHash];
if (packed.length == 0) {
return VerifiedCert({ca: false, notAfter: 0, maxPathLen: 0, subjectHash: 0, pubKey: ""});
}
uint8 ca;
uint64 notAfter;
int64 maxPathLen;
bytes32 subjectHash;
assembly {
ca := mload(add(packed, 0x1))
notAfter := mload(add(packed, 0x9))
maxPathLen := mload(add(packed, 0x11))
subjectHash := mload(add(packed, 0x31))
}
bytes memory pubKey = packed.slice(0x31, packed.length - 0x31);
return VerifiedCert({
ca: ca != 0,
notAfter: notAfter,
maxPathLen: maxPathLen,
subjectHash: subjectHash,
pubKey: pubKey
});
}
}
"
},
"lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (access/Ownable2Step.sol)
pragma solidity ^0.8.0;
import "./Ownable.sol";
/**
* @dev Contract module which provides access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership} and {acceptOwnership}.
*
* This module is used through inheritance. It will make available all functions
* from parent (Ownable).
*/
abstract contract Ownable2Step is Ownable {
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
/**
* @dev Returns the address of the pending owner.
*/
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}
/**
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}
/**
* @dev The new owner accepts the ownership transfer.
*/
function acceptOwnership() external {
address sender = _msgSender();
require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner");
_transferOwnership(sender);
}
}
"
},
"src/interface/IEspressoNitroTEEVerifier.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IEspressoNitroTEEVerifier {
// This error is thrown when the PCR0 values don't match
error InvalidAWSEnclaveHash();
event AWSEnclaveHashSet(bytes32 enclaveHash, bool valid);
event AWSSignerRegistered(address signer, bytes32 enclaveHash);
event DeletedAWSRegisteredSigner(address signer);
function registeredSigners(address signer) external view returns (bool);
function registeredEnclaveHash(bytes32 enclaveHash) external view returns (bool);
function registerSigner(bytes calldata attestation, bytes calldata data) external;
function verifyCACert(bytes calldata certificate, bytes32 parentCertHash) external;
function verifyClientCert(bytes calldata certificate, bytes32 parentCertHash) external;
function certVerified(bytes32 certHash) external view returns (bool);
function setEnclaveHash(bytes32 enclaveHash, bool valid) external;
function deleteRegisteredSigners(address[] memory signers) external;
}
"
},
"lib/nitro-validator/src/ICertManager.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
interface ICertManager {
struct VerifiedCert {
bool ca;
uint64 notAfter;
int64 maxPathLen;
bytes32 subjectHash;
bytes pubKey;
}
function verifyCACert(bytes memory cert, bytes32 parentCertHash) external returns (bytes32);
function verifyClientCert(bytes memory cert, bytes32 parentCertHash) external returns (VerifiedCert memory);
}
"
},
"lib/nitro-validator/src/Sha2Ext.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
// adapted from https://github.com/yangfh2004/SolSha2Ext/blob/main/contracts/lib/Sha2Ext.sol
import {LibBytes} from "./LibBytes.sol";
library Sha2Ext {
using LibBytes for bytes;
function sha2(bytes memory message, uint256 offset, uint256 length, uint64[8] memory h) internal pure {
uint64[80] memory k = [
0x428a2f98d728ae22,
0x7137449123ef65cd,
0xb5c0fbcfec4d3b2f,
0xe9b5dba58189dbbc,
0x3956c25bf348b538,
0x59f111f1b605d019,
0x923f82a4af194f9b,
0xab1c5ed5da6d8118,
0xd807aa98a3030242,
0x12835b0145706fbe,
0x243185be4ee4b28c,
0x550c7dc3d5ffb4e2,
0x72be5d74f27b896f,
0x80deb1fe3b1696b1,
0x9bdc06a725c71235,
0xc19bf174cf692694,
0xe49b69c19ef14ad2,
0xefbe4786384f25e3,
0x0fc19dc68b8cd5b5,
0x240ca1cc77ac9c65,
0x2de92c6f592b0275,
0x4a7484aa6ea6e483,
0x5cb0a9dcbd41fbd4,
0x76f988da831153b5,
0x983e5152ee66dfab,
0xa831c66d2db43210,
0xb00327c898fb213f,
0xbf597fc7beef0ee4,
0xc6e00bf33da88fc2,
0xd5a79147930aa725,
0x06ca6351e003826f,
0x142929670a0e6e70,
0x27b70a8546d22ffc,
0x2e1b21385c26c926,
0x4d2c6dfc5ac42aed,
0x53380d139d95b3df,
0x650a73548baf63de,
0x766a0abb3c77b2a8,
0x81c2c92e47edaee6,
0x92722c851482353b,
0xa2bfe8a14cf10364,
0xa81a664bbc423001,
0xc24b8b70d0f89791,
0xc76c51a30654be30,
0xd192e819d6ef5218,
0xd69906245565a910,
0xf40e35855771202a,
0x106aa07032bbd1b8,
0x19a4c116b8d2d0c8,
0x1e376c085141ab53,
0x2748774cdf8eeb99,
0x34b0bcb5e19b48a8,
0x391c0cb3c5c95a63,
0x4ed8aa4ae3418acb,
0x5b9cca4f7763e373,
0x682e6ff3d6b2b8a3,
0x748f82ee5defb2fc,
0x78a5636f43172f60,
0x84c87814a1f0ab72,
0x8cc702081a6439ec,
0x90befffa23631e28,
0xa4506cebde82bde9,
0xbef9a3f7b2c67915,
0xc67178f2e372532b,
0xca273eceea26619c,
0xd186b8c721c0c207,
0xeada7dd6cde0eb1e,
0xf57d4f7fee6ed178,
0x06f067aa72176fba,
0x0a637dc5a2c898a6,
0x113f9804bef90dae,
0x1b710b35131c471b,
0x28db77f523047d84,
0x32caab7b40c72493,
0x3c9ebe0a15c9bebc,
0x431d67c49c100d4c,
0x4cc5d4becb3e42b6,
0x597f299cfc657e2a,
0x5fcb6fab3ad6faec,
0x6c44198c4a475817
];
require(offset + length <= message.length, "OUT_OF_BOUNDS");
bytes memory padding = padMessage(message, offset, length);
require(padding.length % 128 == 0, "PADDING_ERROR");
uint64[80] memory w;
uint64[8] memory temp;
uint64[16] memory blocks;
uint256 messageLength = (length / 128) * 128;
unchecked {
for (uint256 i = 0; i < (messageLength + padding.length); i += 128) {
if (i < messageLength) {
getBlock(message, blocks, offset + i);
} else {
getBlock(padding, blocks, i - messageLength);
}
for (uint256 j = 0; j < 16; ++j) {
w[j] = blocks[j];
}
for (uint256 j = 16; j < 80; ++j) {
w[j] = gamma1(w[j - 2]) + w[j - 7] + gamma0(w[j - 15]) + w[j - 16];
}
for (uint256 j = 0; j < 8; ++j) {
temp[j] = h[j];
}
for (uint256 j = 0; j < 80; ++j) {
uint64 t1 = temp[7] + sigma1(temp[4]) + ch(temp[4], temp[5], temp[6]) + k[j] + w[j];
uint64 t2 = sigma0(temp[0]) + maj(temp[0], temp[1], temp[2]);
temp[7] = temp[6];
temp[6] = temp[5];
temp[5] = temp[4];
temp[4] = temp[3] + t1;
temp[3] = temp[2];
temp[2] = temp[1];
temp[1] = temp[0];
temp[0] = t1 + t2;
}
for (uint256 j = 0; j < 8; ++j) {
h[j] += temp[j];
}
}
}
}
function sha384(bytes memory message, uint256 offset, uint256 length) internal pure returns (bytes memory) {
uint64[8] memory h = [
0xcbbb9d5dc1059ed8,
0x629a292a367cd507,
0x9159015a3070dd17,
0x152fecd8f70e5939,
0x67332667ffc00b31,
0x8eb44a8768581511,
0xdb0c2e0d64f98fa7,
0x47b5481dbefa4fa4
];
sha2(message, offset, length, h);
return abi.encodePacked(bytes8(h[0]), bytes8(h[1]), bytes8(h[2]), bytes8(h[3]), bytes8(h[4]), bytes8(h[5]));
}
function sha512(bytes memory message, uint256 offset, uint256 length) internal pure returns (bytes memory) {
uint64[8] memory h = [
0x6a09e667f3bcc908,
0xbb67ae8584caa73b,
0x3c6ef372fe94f82b,
0xa54ff53a5f1d36f1,
0x510e527fade682d1,
0x9b05688c2b3e6c1f,
0x1f83d9abfb41bd6b,
0x5be0cd19137e2179
];
sha2(message, offset, length, h);
return abi.encodePacked(
bytes8(h[0]),
bytes8(h[1]),
bytes8(h[2]),
bytes8(h[3]),
bytes8(h[4]),
bytes8(h[5]),
bytes8(h[6]),
bytes8(h[7])
);
}
function padMessage(bytes memory message, uint256 offset, uint256 length) internal pure returns (bytes memory) {
bytes8 bitLength = bytes8(uint64(length * 8));
uint256 mdi = length % 128;
uint256 paddingLength;
if (mdi < 112) {
paddingLength = 119 - mdi;
} else {
paddingLength = 247 - mdi;
}
bytes memory padding = new bytes(paddingLength);
bytes memory tail = message.slice(offset + length - mdi, mdi);
return abi.encodePacked(tail, bytes1(0x80), padding, bitLength);
}
function getBlock(bytes memory message, uint64[16] memory blocks, uint256 index) internal pure {
for (uint256 i = 0; i < 16; ++i) {
blocks[i] = message.readUint64(index + i * 8);
}
}
function ch(uint64 x, uint64 y, uint64 z) internal pure returns (uint64) {
return (x & y) ^ (~x & z);
}
function maj(uint64 x, uint64 y, uint64 z) internal pure returns (uint64) {
return (x & y) ^ (x & z) ^ (y & z);
}
function sigma0(uint64 x) internal pure returns (uint64) {
return (rotateRight(x, 28) ^ rotateRight(x, 34) ^ rotateRight(x, 39));
}
function sigma1(uint64 x) internal pure returns (uint64) {
return (rotateRight(x, 14) ^ rotateRight(x, 18) ^ rotateRight(x, 41));
}
function gamma0(uint64 x) internal pure returns (uint64) {
return (rotateRight(x, 1) ^ rotateRight(x, 8) ^ (x >> 7));
}
function gamma1(uint64 x) internal pure returns (uint64) {
return (rotateRight(x, 19) ^ rotateRight(x, 61) ^ (x >> 6));
}
function rotateRight(uint64 x, uint64 n) internal pure returns (uint64) {
return (x << (64 - n)) | (x >> n);
}
}
"
},
"lib/nitro-validator/lib/solidity-lib/contracts/libs/crypto/ECDSA384.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {MemoryUtils} from "../utils/MemoryUtils.sol";
/**
* @notice Cryptography module
*
* This library provides functionality for ECDSA verification over any 384-bit curve. Currently,
* this is the most efficient implementation out there, consuming ~8.025 million gas per call.
*
* The approach is Strauss-Shamir double scalar multiplication with 6 bits of precompute + affine coordinates.
* For reference, naive implementation uses ~400 billion gas, which is 50000 times more expensive.
*
* We also tried using projective coordinates, however, the gas consumption rose to ~9 million gas.
*/
library ECDSA384 {
using MemoryUtils for *;
using U384 for *;
/**
* @notice 384-bit curve parameters.
*/
struct Parameters {
bytes a;
bytes b;
bytes gx;
bytes gy;
bytes p;
bytes n;
bytes lowSmax;
}
struct _Parameters {
uint256 a;
uint256 b;
uint256 gx;
uint256 gy;
uint256 p;
uint256 n;
uint256 lowSmax;
}
struct _Inputs {
uint256 r;
uint256 s;
uint256 x;
uint256 y;
}
/**
* @notice The function to verify the ECDSA signature
* @param curveParams_ the 384-bit curve parameters. `lowSmax` is `n / 2`.
* @param hashedMessage_ the already hashed message to be verified.
* @param signature_ the ECDSA signature. Equals to `bytes(r) + bytes(s)`.
* @param pubKey_ the full public key of a signer. Equals to `bytes(x) + bytes(y)`.
*
* Note that signatures only from the lower part of the curve are accepted.
* If your `s > n / 2`, change it to `s = n - s`.
*/
function verify(
Parameters memory curveParams_,
bytes memory hashedMessage_,
bytes memory signature_,
bytes memory pubKey_
) internal view returns (bool) {
unchecked {
_Inputs memory inputs_;
(inputs_.r, inputs_.s) = U384.init2(signature_);
(inputs_.x, inputs_.y) = U384.init2(pubKey_);
_Parameters memory params_ = _Parameters({
a: curveParams_.a.init(),
b: curveParams_.b.init(),
gx: curveParams_.gx.init(),
gy: curveParams_.gy.init(),
p: curveParams_.p.init(),
n: curveParams_.n.init(),
lowSmax: curveParams_.lowSmax.init()
});
uint256 call = U384.initCall(params_.p);
/// accept s only from the lower part of the curve
if (
U384.eqInteger(inputs_.r, 0) ||
U384.cmp(inputs_.r, params_.n) >= 0 ||
U384.eqInteger(inputs_.s, 0) ||
U384.cmp(inputs_.s, params_.lowSmax) > 0
) {
return false;
}
if (!_isOnCurve(call, params_.p, params_.a, params_.b, inputs_.x, inputs_.y)) {
return false;
}
/// allow compatibility with non-384-bit hash functions.
{
uint256 hashedMessageLength_ = hashedMessage_.length;
if (hashedMessageLength_ < 48) {
bytes memory tmp_ = new bytes(48);
MemoryUtils.unsafeCopy(
hashedMessage_.getDataPointer(),
tmp_.getDataPointer() + 48 - hashedMessageLength_,
hashedMessageLength_
);
hashedMessage_ = tmp_;
}
}
uint256 scalar1 = U384.moddiv(call, hashedMessage_.init(), inputs_.s, params_.n);
uint256 scalar2 = U384.moddiv(call, inputs_.r, inputs_.s, params_.n);
{
uint256 three = U384.init(3);
/// We use 6-bit masks where the first 3 bits refer to `scalar1` and the last 3 bits refer to `scalar2`.
uint256[2][64] memory points_ = _precomputePointsTable(
call,
params_.p,
three,
params_.a,
params_.gx,
params_.gy,
inputs_.x,
inputs_.y
);
(scalar1, ) = _doubleScalarMultiplication(
call,
params_.p,
three,
params_.a,
points_,
scalar1,
scalar2
);
}
U384.modAssign(call, scalar1, params_.n);
return U384.eq(scalar1, inputs_.r);
}
}
/**
* @dev Check if a point in affine coordinates is on the curve.
*/
function _isOnCurve(
uint256 call,
uint256 p,
uint256 a,
uint256 b,
uint256 x,
uint256 y
) private view returns (bool) {
unchecked {
if (U384.eqInteger(x, 0) || U384.eq(x, p) || U384.eqInteger(y, 0) || U384.eq(y, p)) {
return false;
}
uint256 LHS = U384.modexp(call, y, 2);
uint256 RHS = U384.modexp(call, x, 3);
if (!U384.eqInteger(a, 0)) {
RHS = U384.modadd(RHS, U384.modmul(call, x, a), p); // x^3 + a*x
}
if (!U384.eqInteger(b, 0)) {
RHS = U384.modadd(RHS, b, p); // x^3 + a*x + b
}
return U384.eq(LHS, RHS);
}
}
/**
* @dev Compute the Strauss-Shamir double scalar multiplication scalar1*G + scalar2*H.
*/
function _doubleScalarMultiplication(
uint256 call,
uint256 p,
uint256 three,
uint256 a,
uint256[2][64] memory points,
uint256 scalar1,
uint256 scalar2
) private view returns (uint256 x, uint256 y) {
unchecked {
uint256 mask_;
uint256 scalar1Bits_;
uint256 scalar2Bits_;
assembly {
scalar1Bits_ := mload(scalar1)
scalar2Bits_ := mload(scalar2)
}
(x, y) = _twiceAffine(call, p, three, a, x, y);
mask_ = ((scalar1Bits_ >> 183) << 3) | (scalar2Bits_ >> 183);
if (mask_ != 0) {
(x, y) = _addAffine(call, p, three, a, points[mask_][0], points[mask_][1], x, y);
}
for (uint256 word = 4; word <= 184; word += 3) {
(x, y) = _twice3Affine(call, p, three, a, x, y);
mask_ =
(((scalar1Bits_ >> (184 - word)) & 0x07) << 3) |
((scalar2Bits_ >> (184 - word)) & 0x07);
if (mask_ != 0) {
(x, y) = _addAffine(
call,
p,
three,
a,
points[mask_][0],
points[mask_][1],
x,
y
);
}
}
assembly {
scalar1Bits_ := mload(add(scalar1, 0x20))
scalar2Bits_ := mload(add(scalar2, 0x20))
}
(x, y) = _twiceAffine(call, p, three, a, x, y);
mask_ = ((scalar1Bits_ >> 255) << 3) | (scalar2Bits_ >> 255);
if (mask_ != 0) {
(x, y) = _addAffine(call, p, three, a, points[mask_][0], points[mask_][1], x, y);
}
for (uint256 word = 4; word <= 256; word += 3) {
(x, y) = _twice3Affine(call, p, three, a, x, y);
mask_ =
(((scalar1Bits_ >> (256 - word)) & 0x07) << 3) |
((scalar2Bits_ >> (256 - word)) & 0x07);
if (mask_ != 0) {
(x, y) = _addAffine(
call,
p,
three,
a,
points[mask_][0],
points[mask_][1],
x,
y
);
}
}
}
}
/**
* @dev Double an elliptic curve point in affine coordinates.
*/
function _twiceAffine(
uint256 call,
uint256 p,
uint256 three,
uint256 a,
uint256 x1,
uint256 y1
) private view returns (uint256 x2, uint256 y2) {
unchecked {
if (x1 == 0) {
return (0, 0);
}
if (U384.eqInteger(y1, 0)) {
return (0, 0);
}
uint256 m1 = U384.modexp(call, x1, 2);
U384.modmulAssign(call, m1, three);
U384.modaddAssign(m1, a, p);
uint256 m2 = U384.modshl1(y1, p);
U384.moddivAssign(call, m1, m2);
x2 = U384.modexp(call, m1, 2);
U384.modsubAssign(x2, x1, p);
U384.modsubAssign(x2, x1, p);
y2 = U384.modsub(x1, x2, p);
U384.modmulAssign(call, y2, m1);
U384.modsubAssign(y2, y1, p);
}
}
/**
* @dev Doubles an elliptic curve point 3 times in affine coordinates.
*/
function _twice3Affine(
uint256 call,
uint256 p,
uint256 three,
uint256 a,
uint256 x1,
uint256 y1
) private view returns (uint256 x2, uint256 y2) {
unchecked {
if (x1 == 0) {
return (0, 0);
}
if (U384.eqInteger(y1, 0)) {
return (0, 0);
}
uint256 m1 = U384.modexp(call, x1, 2);
U384.modmulAssign(call, m1, three);
U384.modaddAssign(m1, a, p);
uint256 m2 = U384.modshl1(y1, p);
U384.moddivAssign(call, m1, m2);
x2 = U384.modexp(call, m1, 2);
U384.modsubAssign(x2, x1, p);
U384.modsubAssign(x2, x1, p);
y2 = U384.modsub(x1, x2, p);
U384.modmulAssign(call, y2, m1);
U384.modsubAssign(y2, y1, p);
if (U384.eqInteger(y2, 0)) {
return (0, 0);
}
U384.modexpAssignTo(call, m1, x2, 2);
U384.modmulAssign(call, m1, three);
U384.modaddAssign(m1, a, p);
U384.modshl1AssignTo(m2, y2, p);
U384.moddivAssign(call, m1, m2);
U384.modexpAssignTo(call, x1, m1, 2);
U384.modsubAssign(x1, x2, p);
U384.modsubAssign(x1, x2, p);
U384.modsubAssignTo(y1, x2, x1, p);
U384.modmulAssign(call, y1, m1);
U384.modsubAssign(y1, y2, p);
if (U384.eqInteger(y1, 0)) {
return (0, 0);
}
U384.modexpAssignTo(call, m1, x1, 2);
U384.modmulAssign(call, m1, three);
U384.modaddAssign(m1, a, p);
U384.modshl1AssignTo(m2, y1, p);
U384.moddivAssign(call, m1, m2);
U384.modexpAssignTo(call, x2, m1, 2);
U384.modsubAssign(x2, x1, p);
U384.modsubAssign(x2, x1, p);
U384.modsubAssignTo(y2, x1, x2, p);
U384.modmulAssign(call, y2, m1);
U384.modsubAssign(y2, y1, p);
}
}
/**
* @dev Add two elliptic curve points in affine coordinates.
*/
function _addAffine(
uint256 call,
uint256 p,
uint256 three,
uint256 a,
uint256 x1,
uint256 y1,
uint256 x2,
uint256 y2
) private view returns (uint256 x3, uint256 y3) {
unchecked {
if (x1 == 0 || x2 == 0) {
if (x1 == 0 && x2 == 0) {
return (0, 0);
}
return x1 == 0 ? (x2.copy(), y2.copy()) : (x1.copy(), y1.copy());
}
if (U384.eq(x1, x2)) {
if (U384.eq(y1, y2)) {
return _twiceAffine(call, p, three, a, x1, y1);
}
return (0, 0);
}
uint256 m1 = U384.modsub(y1, y
Submitted on: 2025-09-30 16:49:19
Comments
Log in to comment.
No comments yet.