diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..762a296 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,45 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Show Forge version + run: | + forge --version + + - name: Run Forge fmt + run: | + forge fmt --check + id: fmt + + - name: Run Forge build + run: | + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7771e5f --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env +/.idea/ +/.DS_Store diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..888d42d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0802c67 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright 2020-2024 + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 0000000..25b918f --- /dev/null +++ b/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 0000000..1eea5ba --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..feaba2d --- /dev/null +++ b/remappings.txt @@ -0,0 +1 @@ +forge-std/=lib/forge-std/src/ diff --git a/src/Asn1Decode.sol b/src/Asn1Decode.sol new file mode 100644 index 0000000..0771d5e --- /dev/null +++ b/src/Asn1Decode.sol @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: MIT + +// Copyright (c) 2019 Jonah Groendal + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +pragma solidity ^0.8.15; + +// adapted from https://github.com/JonahGroendal/asn1-decode/tree/master + +import {NodePtr, LibNodePtr} from "./NodePtr.sol"; +import {LibBytes} from "./LibBytes.sol"; + +library Asn1Decode { + using LibNodePtr for NodePtr; + using LibBytes for bytes; + + /* + * @dev Get the root node. First step in traversing an ASN1 structure + * @param der The DER-encoded ASN1 structure + * @return A pointer to the outermost node + */ + function root(bytes memory der) internal pure returns (NodePtr) { + return readNodeLength(der, 0); + } + + /* + * @dev Get a child root of the current node + * @param der The DER-encoded ASN1 structure + * @param ptr Pointer to the current node + * @return A pointer to the child root node + */ + function rootOf(bytes memory der, NodePtr ptr) internal pure returns (NodePtr) { + return readNodeLength(der, ptr.content()); + } + + /* + * @dev Get the next sibling node + * @param der The DER-encoded ASN1 structure + * @param ptr Points to the indices of the current node + * @return A pointer to the next sibling node + */ + function nextSiblingOf(bytes memory der, NodePtr ptr) internal pure returns (NodePtr) { + return readNodeLength(der, ptr.content() + ptr.length()); + } + + /* + * @dev Get the first child node of the current node + * @param der The DER-encoded ASN1 structure + * @param ptr Points to the indices of the current node + * @return A pointer to the first child node + */ + function firstChildOf(bytes memory der, NodePtr ptr) internal pure returns (NodePtr) { + require(der[ptr.header()] & 0x20 == 0x20, "Not a constructed type"); + return readNodeLength(der, ptr.content()); + } + + /* + * @dev Extract value of bitstring node from DER-encoded structure + * @param der The DER-encoded ASN1 structure + * @param ptr Points to the indices of the current node + * @return Value of bitstring converted to bytes + */ + function bitstringAt(bytes memory der, NodePtr ptr) internal pure returns (NodePtr) { + require(der[ptr.header()] == 0x03, "Not type BIT STRING"); + // Only 00 padded bitstr can be converted to bytestr! + require(der[ptr.content()] == 0x00); + return LibNodePtr.toNodePtr(ptr.header(), ptr.content() + 1, ptr.length() - 1); + } + + /* + * @dev Extract value of node from DER-encoded structure + * @param der The der-encoded ASN1 structure + * @param ptr Points to the indices of the current node + * @return Uint value of node + */ + function uintAt(bytes memory der, NodePtr ptr) internal pure returns (uint256) { + require(der[ptr.header()] == 0x02, "Not type INTEGER"); + require(der[ptr.content()] & 0x80 == 0, "Not positive"); + uint256 len = ptr.length(); + return uint256(readBytesN(der, ptr.content(), len) >> (32 - len) * 8); + } + + function uint384At(bytes memory der, NodePtr ptr) internal pure returns (uint128, uint256) { + require(der[ptr.header()] == 0x02, "Not type INTEGER"); + require(der[ptr.content()] & 0x80 == 0, "Not positive"); + uint256 valueLength = ptr.length(); + uint256 start = ptr.content(); + if (der[start] == 0) { + start++; + valueLength--; + } + return ( + uint128(uint256(readBytesN(der, start, 16) >> 128)), + uint256(readBytesN(der, start + 16, valueLength - 16) >> (48 - valueLength) * 8) + ); + } + + function timestampAt(bytes memory der, NodePtr ptr) internal pure returns (uint256) { + uint16 _years; + uint256 offset = ptr.content(); + uint256 length = ptr.length(); + + if (length == 13) { + _years = (uint8(der[offset]) - 48 < 5) ? 2000 : 1900; + } else { + _years = (uint8(der[offset]) - 48) * 1000 + (uint8(der[offset + 1]) - 48) * 100; + offset += 2; + } + _years += (uint8(der[offset]) - 48) * 10 + uint8(der[offset + 1]) - 48; + uint8 _months = (uint8(der[offset + 2]) - 48) * 10 + uint8(der[offset + 3]) - 48; + uint8 _days = (uint8(der[offset + 4]) - 48) * 10 + uint8(der[offset + 5]) - 48; + uint8 _hours = (uint8(der[offset + 6]) - 48) * 10 + uint8(der[offset + 7]) - 48; + uint8 _mins = (uint8(der[offset + 8]) - 48) * 10 + uint8(der[offset + 9]) - 48; + uint8 _secs = (uint8(der[offset + 10]) - 48) * 10 + uint8(der[offset + 11]) - 48; + + return timestampFromDateTime(_years, _months, _days, _hours, _mins, _secs); + } + + function byteAtOffset(bytes memory der, NodePtr ptr, uint256 offset) internal pure returns (bytes1) { + return der[ptr.content() + offset]; + } + + function readNodeLength(bytes memory der, uint256 ix) private pure returns (NodePtr) { + uint256 length; + uint80 ixFirstContentByte; + if ((der[ix + 1] & 0x80) == 0) { + length = uint8(der[ix + 1]); + ixFirstContentByte = uint80(ix + 2); + } else { + uint8 lengthbytesLength = uint8(der[ix + 1] & 0x7F); + if (lengthbytesLength == 1) { + length = uint8(der[ix + 2]); + } else if (lengthbytesLength == 2) { + length = der.readUint16(ix + 2); + } else { + length = uint256(readBytesN(der, ix + 2, lengthbytesLength) >> (32 - lengthbytesLength) * 8); + } + ixFirstContentByte = uint80(ix + 2 + lengthbytesLength); + } + return LibNodePtr.toNodePtr(ix, ixFirstContentByte, uint80(length)); + } + + function readBytesN(bytes memory self, uint256 idx, uint256 len) private pure returns (bytes32 ret) { + require(len <= 32); + require(idx + len <= self.length); + assembly { + let mask := not(sub(exp(256, sub(32, len)), 1)) + ret := and(mload(add(add(self, 32), idx)), mask) + } + } + + // Calculate the number of seconds from 1970/01/01 to + // year/month/day/hour/minute/second using the date conversion + // algorithm from https://aa.usno.navy.mil/faq/JD_formula.html + // and subtracting the offset 2440588 so that 1970/01/01 is day 0 + function timestampFromDateTime( + uint256 year, + uint256 month, + uint256 day, + uint256 hour, + uint256 minute, + uint256 second + ) private pure returns (uint256) { + require(year >= 1970); + int256 _year = int256(year); + int256 _month = int256(month); + int256 _day = int256(day); + + int256 _days = _day - 32075 + 1461 * (_year + 4800 + (_month - 14) / 12) / 4 + + 367 * (_month - 2 - (_month - 14) / 12 * 12) / 12 - 3 * ((_year + 4900 + (_month - 14) / 12) / 100) / 4 + - 2440588; + + return ((uint256(_days) * 24 + hour) * 60 + minute) * 60 + second; + } +} diff --git a/src/ECDSA384.sol b/src/ECDSA384.sol new file mode 100644 index 0000000..03ae937 --- /dev/null +++ b/src/ECDSA384.sol @@ -0,0 +1,1029 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +// adapted from https://github.com/dl-solarity/solidity-lib/blob/ceb1e347c56c335a7e680e7c224cf569cc2b37d4/contracts/libs/crypto/ECDSA384.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 ~9 million gas per call. + * + * The approach is Strauss-Shamir double scalar multiplication with 4 bits of precompute + projective points. + */ +library ECDSA384 { + 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 from either the lower or upper part of the curve are accepted. + */ + 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); + + 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); + + unsafeCopy( + getDataPointer(hashedMessage_), + getDataPointer(tmp_) + 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 4-bit masks where the first 2 bits refer to `scalar1` and the last 2 bits refer to `scalar2`. + uint256[3][16] memory points_ = _precomputePointsTable( + call, params_.p, three, params_.a, params_.gx, params_.gy, inputs_.x, inputs_.y + ); + + (scalar1,, scalar2) = + _doubleScalarMultiplication(call, params_.p, three, params_.a, points_, scalar1, scalar2); + } + + return U384.eq(U384.moddiv(call, scalar1, scalar2, params_.p), 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[3][16] memory points, + uint256 scalar1, + uint256 scalar2 + ) private view returns (uint256 x, uint256 y, uint256 z) { + unchecked { + uint256 mask_; + uint256 scalar1Bits_; + uint256 scalar2Bits_; + + assembly { + scalar1Bits_ := mload(scalar1) + scalar2Bits_ := mload(scalar2) + } + + x = U384.init(0); + y = U384.init(0); + z = U384.init(1); + + for (uint256 word = 2; word <= 184; word += 2) { + (x, y, z) = _twiceProj(call, p, three, a, x, y, z); + (x, y, z) = _twiceProj(call, p, three, a, x, y, z); + + mask_ = (((scalar1Bits_ >> (184 - word)) & 0x03) << 2) | ((scalar2Bits_ >> (184 - word)) & 0x03); + + if (mask_ != 0) { + uint256[3] memory maskedPoints_ = points[mask_]; + + (x, y, z) = + _addProj(call, p, three, a, maskedPoints_[0], maskedPoints_[1], maskedPoints_[2], x, y, z); + } + } + + assembly { + scalar1Bits_ := mload(add(scalar1, 0x20)) + scalar2Bits_ := mload(add(scalar2, 0x20)) + } + + for (uint256 word = 2; word <= 256; word += 2) { + (x, y, z) = _twiceProj(call, p, three, a, x, y, z); + (x, y, z) = _twiceProj(call, p, three, a, x, y, z); + + mask_ = (((scalar1Bits_ >> (256 - word)) & 0x03) << 2) | ((scalar2Bits_ >> (256 - word)) & 0x03); + + if (mask_ != 0) { + uint256[3] memory maskedPoints_ = points[mask_]; + + (x, y, z) = + _addProj(call, p, three, a, maskedPoints_[0], maskedPoints_[1], maskedPoints_[2], x, y, z); + } + } + + return (x, y, z); + } + } + + /** + * @dev Double an elliptic curve point in projective coordinates. See + * https://www.nayuki.io/page/elliptic-curve-point-addition-in-projective-coordinates + */ + function _twiceProj(uint256 call, uint256 p, uint256 three, uint256 a, uint256 x0, uint256 y0, uint256 z0) + private + view + returns (uint256 x1, uint256 y1, uint256 z1) + { + unchecked { + if (U384.eqInteger(x0, 0) && U384.eqInteger(y0, 0)) { + return (U384.init(0), U384.init(0), U384.init(1)); // zero proj + } + + uint256 u = U384.modmul(call, y0, z0); + U384.modshl1Assign(u, p); + + x1 = U384.modmul(call, u, x0); + U384.modmulAssign(call, x1, y0); + U384.modshl1Assign(x1, p); + + x0 = U384.modexp(call, x0, 2); + + y1 = U384.modmul(call, x0, three); + + z0 = U384.modexp(call, z0, 2); + U384.modmulAssign(call, z0, a); + U384.modaddAssign(y1, z0, p); + + z1 = U384.modexp(call, y1, 2); + U384.modshl1AssignTo(x0, x1, p); + + uint256 diff = U384.sub(p, x0); + U384.modaddAssign(z1, diff, p); + + U384.subAssignTo(diff, p, z1); + U384.modaddAssignTo(x0, x1, diff, p); + U384.modmulAssign(call, x0, y1); + + y0 = U384.modmul(call, y0, u); + U384.modexpAssign(call, y0, 2); + U384.modshl1Assign(y0, p); + + U384.subAssignTo(diff, p, y0); + U384.modaddAssignTo(y1, x0, diff, p); + + U384.modmulAssignTo(call, x1, u, z1); + + U384.modexpAssignTo(call, z1, u, 2); + U384.modmulAssign(call, z1, u); + } + } + + /** + * @dev Add two elliptic curve points in projective coordinates. See + * https://www.nayuki.io/page/elliptic-curve-point-addition-in-projective-coordinates + */ + function _addProj( + uint256 call, + uint256 p, + uint256 three, + uint256 a, + uint256 x0, + uint256 y0, + uint256 z0, + uint256 x1, + uint256 y1, + uint256 z1 + ) private view returns (uint256 x2, uint256 y2, uint256 z2) { + unchecked { + if (U384.eqInteger(x0, 0) && U384.eqInteger(y0, 0)) { + return (x1.copy(), y1.copy(), z1.copy()); + } else if (U384.eqInteger(x1, 0) && U384.eqInteger(y1, 0)) { + return (x0.copy(), y0.copy(), z0.copy()); + } + + x2 = U384.modmul(call, y0, z1); + y2 = U384.modmul(call, y1, z0); + z2 = U384.modmul(call, x0, z1); + y1 = U384.modmul(call, x1, z0); + + if (U384.eq(z2, y1)) { + if (U384.eq(x2, y2)) { + return _twiceProj(call, p, three, a, x0, y0, z0); + } else { + return (U384.init(0), U384.init(0), U384.init(1)); // zero proj + } + } + + a = U384.modmul(call, z0, z1); + + return _addProj2(call, a, z2, p, y1, y2, x2); + } + } + + /** + * @dev Helper function that splits addProj to avoid too many local variables. + */ + function _addProj2(uint256 call, uint256 v, uint256 u0, uint256 p, uint256 u1, uint256 t1, uint256 t0) + private + view + returns (uint256 x2, uint256 y2, uint256 z2) + { + unchecked { + uint256 diff = U384.sub(p, t1); + y2 = U384.modadd(t0, diff, p); + + U384.subAssignTo(diff, p, u1); + x2 = U384.modadd(u0, diff, p); + uint256 u2 = U384.modexp(call, x2, 2); + + z2 = U384.modexp(call, y2, 2); + + U384.modmulAssign(call, z2, v); + u1 = U384.modadd(u1, u0, p); + U384.modmulAssign(call, u1, u2); + U384.subAssignTo(diff, p, u1); + U384.modaddAssign(z2, diff, p); + + uint256 u3 = U384.modmul(call, u2, x2); + + U384.modmulAssign(call, x2, z2); + + u0 = U384.modmul(call, u0, u2); + + U384.subAssignTo(diff, p, z2); + U384.modaddAssign(u0, diff, p); + U384.modmulAssign(call, y2, u0); + t0 = U384.modmul(call, t0, u3); + + U384.subAssignTo(diff, p, t0); + U384.modaddAssign(y2, diff, p); + + U384.modmulAssignTo(call, z2, u3, v); + } + } + + function _precomputePointsTable( + uint256 call, + uint256 p, + uint256 three, + uint256 a, + uint256 gx, + uint256 gy, + uint256 hx, + uint256 hy + ) private view returns (uint256[3][16] memory points_) { + /// 0b0100: 1G + 0H + (points_[0x04][0], points_[0x04][1], points_[0x04][2]) = (gx.copy(), gy.copy(), U384.init(1)); + /// 0b1000: 2G + 0H + (points_[0x08][0], points_[0x08][1], points_[0x08][2]) = + _twiceProj(call, p, three, a, points_[0x04][0], points_[0x04][1], points_[0x04][2]); + /// 0b1100: 3G + 0H + (points_[0x0C][0], points_[0x0C][1], points_[0x0C][2]) = _addProj( + call, + p, + three, + a, + points_[0x04][0], + points_[0x04][1], + points_[0x04][2], + points_[0x08][0], + points_[0x08][1], + points_[0x08][2] + ); + /// 0b0001: 0G + 1H + (points_[0x01][0], points_[0x01][1], points_[0x01][2]) = (hx.copy(), hy.copy(), U384.init(1)); + /// 0b0010: 0G + 2H + (points_[0x02][0], points_[0x02][1], points_[0x02][2]) = + _twiceProj(call, p, three, a, points_[0x01][0], points_[0x01][1], points_[0x01][2]); + /// 0b0011: 0G + 3H + (points_[0x03][0], points_[0x03][1], points_[0x03][2]) = _addProj( + call, + p, + three, + a, + points_[0x01][0], + points_[0x01][1], + points_[0x01][2], + points_[0x02][0], + points_[0x02][1], + points_[0x02][2] + ); + /// 0b0101: 1G + 1H + (points_[0x05][0], points_[0x05][1], points_[0x05][2]) = _addProj( + call, + p, + three, + a, + points_[0x04][0], + points_[0x04][1], + points_[0x04][2], + points_[0x01][0], + points_[0x01][1], + points_[0x01][2] + ); + /// 0b0110: 1G + 2H + (points_[0x06][0], points_[0x06][1], points_[0x06][2]) = _addProj( + call, + p, + three, + a, + points_[0x04][0], + points_[0x04][1], + points_[0x04][2], + points_[0x02][0], + points_[0x02][1], + points_[0x02][2] + ); + /// 0b0111: 1G + 3H + (points_[0x07][0], points_[0x07][1], points_[0x07][2]) = _addProj( + call, + p, + three, + a, + points_[0x04][0], + points_[0x04][1], + points_[0x04][2], + points_[0x03][0], + points_[0x03][1], + points_[0x03][2] + ); + /// 0b1001: 2G + 1H + (points_[0x09][0], points_[0x09][1], points_[0x09][2]) = _addProj( + call, + p, + three, + a, + points_[0x08][0], + points_[0x08][1], + points_[0x08][2], + points_[0x01][0], + points_[0x01][1], + points_[0x01][2] + ); + /// 0b1010: 2G + 2H + (points_[0x0A][0], points_[0x0A][1], points_[0x0A][2]) = _addProj( + call, + p, + three, + a, + points_[0x08][0], + points_[0x08][1], + points_[0x08][2], + points_[0x02][0], + points_[0x02][1], + points_[0x02][2] + ); + /// 0b1011: 2G + 3H + (points_[0x0B][0], points_[0x0B][1], points_[0x0B][2]) = _addProj( + call, + p, + three, + a, + points_[0x08][0], + points_[0x08][1], + points_[0x08][2], + points_[0x03][0], + points_[0x03][1], + points_[0x03][2] + ); + /// 0b1101: 3G + 1H + (points_[0x0D][0], points_[0x0D][1], points_[0x0D][2]) = _addProj( + call, + p, + three, + a, + points_[0x0C][0], + points_[0x0C][1], + points_[0x0C][2], + points_[0x01][0], + points_[0x01][1], + points_[0x01][2] + ); + /// 0b1110: 3G + 2H + (points_[0x0E][0], points_[0x0E][1], points_[0x0E][2]) = _addProj( + call, + p, + three, + a, + points_[0x0C][0], + points_[0x0C][1], + points_[0x0C][2], + points_[0x02][0], + points_[0x02][1], + points_[0x02][2] + ); + /// 0b1111: 3G + 3H + (points_[0x0F][0], points_[0x0F][1], points_[0x0F][2]) = _addProj( + call, + p, + three, + a, + points_[0x0C][0], + points_[0x0C][1], + points_[0x0C][2], + points_[0x03][0], + points_[0x03][1], + points_[0x03][2] + ); + } + + /** + * @notice Copies memory from one location to another efficiently via identity precompile. + * @param sourcePointer_ The offset in the memory from which to copy. + * @param destinationPointer_ The offset in the memory where the result will be copied. + * @param size_ The size of the memory to copy. + * + * @dev This function does not account for free memory pointer and should be used with caution. + * + * This signature of calling identity precompile is: + * staticcall(gas(), address(0x04), argsOffset, argsSize, retOffset, retSize) + */ + function unsafeCopy(uint256 sourcePointer_, uint256 destinationPointer_, uint256 size_) private view { + assembly { + pop(staticcall(gas(), 4, sourcePointer_, size_, destinationPointer_, size_)) + } + } + + /** + * @notice Returns the memory pointer to the given bytes data starting position skipping the length. + */ + function getDataPointer(bytes memory data_) private pure returns (uint256 pointer_) { + assembly { + pointer_ := add(data_, 32) + } + } +} + +/** + * @notice Low-level utility library that implements unsigned 384-bit arithmetics. + * + * Should not be used outside of this file. + */ +library U384 { + uint256 private constant SHORT_ALLOCATION = 64; + uint256 private constant LONG_ALLOCATION = 96; + + uint256 private constant CALL_ALLOCATION = 3 * 288; + + uint256 private constant MUL_OFFSET = 288; + uint256 private constant EXP_OFFSET = 2 * 288; + + function init(uint256 from_) internal pure returns (uint256 handler_) { + unchecked { + handler_ = _allocate(SHORT_ALLOCATION); + + assembly { + mstore(handler_, 0x00) + mstore(add(0x20, handler_), from_) + } + + return handler_; + } + } + + function init(bytes memory from_) internal pure returns (uint256 handler_) { + unchecked { + require(from_.length == 48, "U384: not 384"); + + handler_ = _allocate(SHORT_ALLOCATION); + + assembly { + mstore(handler_, 0x00) + mstore(add(handler_, 0x10), mload(add(from_, 0x20))) + mstore(add(handler_, 0x20), mload(add(from_, 0x30))) + } + + return handler_; + } + } + + function init2(bytes memory from2_) internal pure returns (uint256 handler1_, uint256 handler2_) { + unchecked { + require(from2_.length == 96, "U384: not 768"); + + handler1_ = _allocate(SHORT_ALLOCATION); + handler2_ = _allocate(SHORT_ALLOCATION); + + assembly { + mstore(handler1_, 0x00) + mstore(add(handler1_, 0x10), mload(add(from2_, 0x20))) + mstore(add(handler1_, 0x20), mload(add(from2_, 0x30))) + + mstore(handler2_, 0x00) + mstore(add(handler2_, 0x10), mload(add(from2_, 0x50))) + mstore(add(handler2_, 0x20), mload(add(from2_, 0x60))) + } + + return (handler1_, handler2_); + } + } + + function initCall(uint256 m_) internal pure returns (uint256 handler_) { + unchecked { + handler_ = _allocate(CALL_ALLOCATION); + + assembly { + let call_ := add(handler_, MUL_OFFSET) + + mstore(call_, 0x60) + mstore(add(0x20, call_), 0x20) + mstore(add(0x40, call_), 0x40) + mstore(add(0xC0, call_), 0x01) + mstore(add(0xE0, call_), mload(m_)) + mstore(add(0x0100, call_), mload(add(m_, 0x20))) + + call_ := add(handler_, EXP_OFFSET) + + mstore(call_, 0x40) + mstore(add(0x20, call_), 0x20) + mstore(add(0x40, call_), 0x40) + mstore(add(0xC0, call_), mload(m_)) + mstore(add(0xE0, call_), mload(add(m_, 0x20))) + } + } + } + + function copy(uint256 handler_) internal pure returns (uint256 handlerCopy_) { + unchecked { + handlerCopy_ = _allocate(SHORT_ALLOCATION); + + assembly { + mstore(handlerCopy_, mload(handler_)) + mstore(add(handlerCopy_, 0x20), mload(add(handler_, 0x20))) + } + + return handlerCopy_; + } + } + + function eq(uint256 a_, uint256 b_) internal pure returns (bool eq_) { + assembly { + eq_ := and(eq(mload(a_), mload(b_)), eq(mload(add(a_, 0x20)), mload(add(b_, 0x20)))) + } + } + + function eqInteger(uint256 a_, uint256 bInteger_) internal pure returns (bool eq_) { + assembly { + eq_ := and(eq(mload(a_), 0), eq(mload(add(a_, 0x20)), bInteger_)) + } + } + + function cmp(uint256 a_, uint256 b_) internal pure returns (int256 cmp_) { + unchecked { + uint256 aWord_; + uint256 bWord_; + + assembly { + aWord_ := mload(a_) + bWord_ := mload(b_) + } + + if (aWord_ > bWord_) { + return 1; + } + + if (aWord_ < bWord_) { + return -1; + } + + assembly { + aWord_ := mload(add(a_, 0x20)) + bWord_ := mload(add(b_, 0x20)) + } + + if (aWord_ > bWord_) { + return 1; + } + + if (aWord_ < bWord_) { + return -1; + } + } + } + + function modexp(uint256 call_, uint256 b_, uint256 eInteger_) internal view returns (uint256 r_) { + unchecked { + r_ = _allocate(SHORT_ALLOCATION); + + assembly { + call_ := add(call_, EXP_OFFSET) + + mstore(add(0x60, call_), mload(b_)) + mstore(add(0x80, call_), mload(add(b_, 0x20))) + mstore(add(0xA0, call_), eInteger_) + + pop(staticcall(gas(), 0x5, call_, 0x0100, r_, 0x40)) + } + + return r_; + } + } + + function modexpAssign(uint256 call_, uint256 b_, uint256 eInteger_) internal view { + assembly { + call_ := add(call_, EXP_OFFSET) + + mstore(add(0x60, call_), mload(b_)) + mstore(add(0x80, call_), mload(add(b_, 0x20))) + mstore(add(0xA0, call_), eInteger_) + + pop(staticcall(gas(), 0x5, call_, 0x0100, b_, 0x40)) + } + } + + function modexpAssignTo(uint256 call_, uint256 to_, uint256 b_, uint256 eInteger_) internal view { + assembly { + call_ := add(call_, EXP_OFFSET) + + mstore(add(0x60, call_), mload(b_)) + mstore(add(0x80, call_), mload(add(b_, 0x20))) + mstore(add(0xA0, call_), eInteger_) + + pop(staticcall(gas(), 0x5, call_, 0x0100, to_, 0x40)) + } + } + + function modadd(uint256 a_, uint256 b_, uint256 m_) internal pure returns (uint256 r_) { + unchecked { + r_ = _allocate(SHORT_ALLOCATION); + + _add(a_, b_, r_); + + if (cmp(r_, m_) >= 0) { + _subFrom(r_, m_); + } + + return r_; + } + } + + function modaddAssign(uint256 a_, uint256 b_, uint256 m_) internal pure { + unchecked { + _addTo(a_, b_); + + if (cmp(a_, m_) >= 0) { + return _subFrom(a_, m_); + } + } + } + + function modaddAssignTo(uint256 to_, uint256 a_, uint256 b_, uint256 m_) internal pure { + unchecked { + _add(a_, b_, to_); + + if (cmp(to_, m_) >= 0) { + return _subFrom(to_, m_); + } + } + } + + function modmul(uint256 call_, uint256 a_, uint256 b_) internal view returns (uint256 r_) { + unchecked { + r_ = _allocate(SHORT_ALLOCATION); + + _mul(a_, b_, call_ + MUL_OFFSET + 0x60); + + assembly { + call_ := add(call_, MUL_OFFSET) + + pop(staticcall(gas(), 0x5, call_, 0x0120, r_, 0x40)) + } + + return r_; + } + } + + function modmulAssign(uint256 call_, uint256 a_, uint256 b_) internal view { + unchecked { + _mul(a_, b_, call_ + MUL_OFFSET + 0x60); + + assembly { + call_ := add(call_, MUL_OFFSET) + + pop(staticcall(gas(), 0x5, call_, 0x0120, a_, 0x40)) + } + } + } + + function modmulAssignTo(uint256 call_, uint256 to_, uint256 a_, uint256 b_) internal view { + unchecked { + _mul(a_, b_, call_ + MUL_OFFSET + 0x60); + + assembly { + call_ := add(call_, MUL_OFFSET) + + pop(staticcall(gas(), 0x5, call_, 0x0120, to_, 0x40)) + } + } + } + + function sub(uint256 a_, uint256 b_) internal pure returns (uint256 r_) { + unchecked { + r_ = _allocate(SHORT_ALLOCATION); + + _sub(a_, b_, r_); + + return r_; + } + } + + function subAssignTo(uint256 to_, uint256 a_, uint256 b_) internal pure { + unchecked { + _sub(a_, b_, to_); + } + } + + function modshl1Assign(uint256 a_, uint256 m_) internal pure { + unchecked { + _shl1To(a_); + + if (cmp(a_, m_) >= 0) { + _subFrom(a_, m_); + } + } + } + + function modshl1AssignTo(uint256 to_, uint256 a_, uint256 m_) internal pure { + unchecked { + _shl1(a_, to_); + + if (cmp(to_, m_) >= 0) { + _subFrom(to_, m_); + } + } + } + + function moddiv(uint256 call_, uint256 a_, uint256 b_, uint256 m_) internal view returns (uint256 r_) { + unchecked { + r_ = modinv(call_, b_, m_); + + _mul(a_, r_, call_ + 0x60); + + assembly { + mstore(call_, 0x60) + mstore(add(0x20, call_), 0x20) + mstore(add(0x40, call_), 0x40) + mstore(add(0xC0, call_), 0x01) + mstore(add(0xE0, call_), mload(m_)) + mstore(add(0x0100, call_), mload(add(m_, 0x20))) + + pop(staticcall(gas(), 0x5, call_, 0x0120, r_, 0x40)) + } + } + } + + function modinv(uint256 call_, uint256 b_, uint256 m_) internal view returns (uint256 r_) { + unchecked { + r_ = _allocate(SHORT_ALLOCATION); + + _sub(m_, init(2), call_ + 0xA0); + + assembly { + mstore(call_, 0x40) + mstore(add(0x20, call_), 0x40) + mstore(add(0x40, call_), 0x40) + mstore(add(0x60, call_), mload(b_)) + mstore(add(0x80, call_), mload(add(b_, 0x20))) + mstore(add(0xE0, call_), mload(m_)) + mstore(add(0x0100, call_), mload(add(m_, 0x20))) + + pop(staticcall(gas(), 0x5, call_, 0x0120, r_, 0x40)) + } + } + } + + function _shl1(uint256 a_, uint256 r_) internal pure { + assembly { + let a1_ := mload(add(a_, 0x20)) + + mstore(r_, or(shl(1, mload(a_)), shr(255, a1_))) + mstore(add(r_, 0x20), shl(1, a1_)) + } + } + + function _shl1To(uint256 a_) internal pure { + assembly { + let a1_ := mload(add(a_, 0x20)) + + mstore(a_, or(shl(1, mload(a_)), shr(255, a1_))) + mstore(add(a_, 0x20), shl(1, a1_)) + } + } + + function _add(uint256 a_, uint256 b_, uint256 r_) private pure { + assembly { + let aWord_ := mload(add(a_, 0x20)) + let sum_ := add(aWord_, mload(add(b_, 0x20))) + + mstore(add(r_, 0x20), sum_) + + sum_ := gt(aWord_, sum_) + sum_ := add(sum_, add(mload(a_), mload(b_))) + + mstore(r_, sum_) + } + } + + function _sub(uint256 a_, uint256 b_, uint256 r_) private pure { + assembly { + let aWord_ := mload(add(a_, 0x20)) + let diff_ := sub(aWord_, mload(add(b_, 0x20))) + + mstore(add(r_, 0x20), diff_) + + diff_ := gt(diff_, aWord_) + diff_ := sub(sub(mload(a_), mload(b_)), diff_) + + mstore(r_, diff_) + } + } + + function _subFrom(uint256 a_, uint256 b_) private pure { + assembly { + let aWord_ := mload(add(a_, 0x20)) + let diff_ := sub(aWord_, mload(add(b_, 0x20))) + + mstore(add(a_, 0x20), diff_) + + diff_ := gt(diff_, aWord_) + diff_ := sub(sub(mload(a_), mload(b_)), diff_) + + mstore(a_, diff_) + } + } + + function _addTo(uint256 a_, uint256 b_) private pure { + assembly { + let aWord_ := mload(add(a_, 0x20)) + let sum_ := add(aWord_, mload(add(b_, 0x20))) + + mstore(add(a_, 0x20), sum_) + + sum_ := gt(aWord_, sum_) + sum_ := add(sum_, add(mload(a_), mload(b_))) + + mstore(a_, sum_) + } + } + + function _mul(uint256 a_, uint256 b_, uint256 r_) private pure { + assembly { + let a0_ := mload(a_) + let a1_ := shr(128, mload(add(a_, 0x20))) + let a2_ := and(mload(add(a_, 0x20)), 0xffffffffffffffffffffffffffffffff) + + let b0_ := mload(b_) + let b1_ := shr(128, mload(add(b_, 0x20))) + let b2_ := and(mload(add(b_, 0x20)), 0xffffffffffffffffffffffffffffffff) + + // r5 + let current_ := mul(a2_, b2_) + let r0_ := and(current_, 0xffffffffffffffffffffffffffffffff) + + // r4 + current_ := shr(128, current_) + + let temp_ := mul(a1_, b2_) + current_ := add(current_, temp_) + let curry_ := lt(current_, temp_) + + temp_ := mul(a2_, b1_) + current_ := add(current_, temp_) + curry_ := add(curry_, lt(current_, temp_)) + + mstore(add(r_, 0x40), add(shl(128, current_), r0_)) + + // r3 + current_ := add(shl(128, curry_), shr(128, current_)) + curry_ := 0 + + temp_ := mul(a0_, b2_) + current_ := add(current_, temp_) + curry_ := lt(current_, temp_) + + temp_ := mul(a1_, b1_) + current_ := add(current_, temp_) + curry_ := add(curry_, lt(current_, temp_)) + + temp_ := mul(a2_, b0_) + current_ := add(current_, temp_) + curry_ := add(curry_, lt(current_, temp_)) + + r0_ := and(current_, 0xffffffffffffffffffffffffffffffff) + + // r2 + current_ := add(shl(128, curry_), shr(128, current_)) + curry_ := 0 + + temp_ := mul(a0_, b1_) + current_ := add(current_, temp_) + curry_ := lt(current_, temp_) + + temp_ := mul(a1_, b0_) + current_ := add(current_, temp_) + curry_ := add(curry_, lt(current_, temp_)) + + mstore(add(r_, 0x20), add(shl(128, current_), r0_)) + + // r1 + current_ := add(shl(128, curry_), shr(128, current_)) + current_ := add(current_, mul(a0_, b0_)) + + mstore(r_, current_) + } + } + + function _allocate(uint256 bytes_) private pure returns (uint256 handler_) { + unchecked { + assembly { + handler_ := mload(0x40) + mstore(0x40, add(handler_, bytes_)) + } + + return handler_; + } + } +} diff --git a/src/LibBytes.sol b/src/LibBytes.sol new file mode 100644 index 0000000..5c68091 --- /dev/null +++ b/src/LibBytes.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +library LibBytes { + function keccak(bytes memory data, uint256 offset, uint256 length) public pure returns (bytes32 result) { + assembly { + result := keccak256(add(data, add(32, offset)), length) + } + } + + function slice(bytes memory b, uint256 from, uint256 to) internal pure returns (bytes memory result) { + require(from <= to, "from greater than to"); + require(to <= b.length, "index out of bounds"); + + // Create a new bytes structure and copy contents + result = new bytes(to - from); + uint256 dest; + uint256 src; + assembly { + dest := add(result, 32) + src := add(b, add(32, from)) + } + memcpy(dest, src, result.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) private 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)) + } + } + } +} diff --git a/src/NitroAttestation.sol b/src/NitroAttestation.sol new file mode 100644 index 0000000..8b8ba5e --- /dev/null +++ b/src/NitroAttestation.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {NodePtr, LibNodePtr} from "./NodePtr.sol"; +import {LibBytes} from "./LibBytes.sol"; + +library NitroAttestation { + using LibNodePtr for NodePtr; + using LibBytes for bytes; + + bytes32 public constant ATTESTATION_TBS_PREFIX = keccak256(hex"846a5369676e61747572653144a101382240"); + bytes32 public constant CERTIFICATE_KEY = keccak256(bytes("certificate")); + bytes32 public constant PUBLIC_KEY_KEY = keccak256(bytes("public_key")); + bytes32 public constant MODULE_ID_KEY = keccak256(bytes("module_id")); + bytes32 public constant TIMESTAMP_KEY = keccak256(bytes("timestamp")); + bytes32 public constant USER_DATA_KEY = keccak256(bytes("user_data")); + bytes32 public constant CABUNDLE_KEY = keccak256(bytes("cabundle")); + bytes32 public constant DIGEST_KEY = keccak256(bytes("digest")); + bytes32 public constant NONCE_KEY = keccak256(bytes("nonce")); + bytes32 public constant PCRS_KEY = keccak256(bytes("pcrs")); + + struct Ptrs { + NodePtr moduleID; + uint64 timestamp; + NodePtr digest; + NodePtr[] pcrs; + NodePtr cert; + NodePtr[] cabundle; + NodePtr publicKey; + NodePtr userData; + NodePtr nonce; + } + + function parseAttestation(bytes memory attestationTbs) internal view returns (Ptrs memory) { + require(attestationTbs.keccak(0, 18) == ATTESTATION_TBS_PREFIX, "invalid attestation prefix"); + + NodePtr payload = readNextElement(attestationTbs, 18); + require(payload.header() == 0x40, "invalid attestation payload type"); + NodePtr payloadMap = readNextElement(attestationTbs, payload.content()); + require(payloadMap.header() == 0xa0, "invalid attestation payload map type"); + + Ptrs memory ptrs; + uint256 offset = payloadMap.content(); + uint256 end = payload.content() + payload.length(); + while (offset < end) { + NodePtr key = readNextElement(attestationTbs, offset); + require(key.header() == 0x60, "invalid attestation key type"); + bytes32 keyHash = attestationTbs.keccak(key.content(), key.length()); + NodePtr value = readNextElement(attestationTbs, key.content() + key.length()); + if (keyHash == MODULE_ID_KEY) { + require(value.header() == 0x60, "invalid module_id type"); + ptrs.moduleID = value; + offset = value.content() + value.length(); + } else if (keyHash == DIGEST_KEY) { + require(value.header() == 0x60, "invalid digest type"); + ptrs.digest = value; + offset = value.content() + value.length(); + } else if (keyHash == CERTIFICATE_KEY) { + require(value.header() == 0x40, "invalid cert type"); + ptrs.cert = value; + offset = value.content() + value.length(); + } else if (keyHash == PUBLIC_KEY_KEY) { + ptrs.publicKey = value; + offset = value.content() + value.length(); + } else if (keyHash == USER_DATA_KEY) { + ptrs.userData = value; + offset = value.content() + value.length(); + } else if (keyHash == NONCE_KEY) { + ptrs.nonce = value; + offset = value.content() + value.length(); + } else if (keyHash == TIMESTAMP_KEY) { + require(value.header() == 0x00, "invalid timestamp type"); + ptrs.timestamp = uint64(value.length()); + offset = value.content(); + } else if (keyHash == CABUNDLE_KEY) { + require(value.header() == 0x80, "invalid cabundle type"); + offset = value.content(); + ptrs.cabundle = new NodePtr[](value.length()); + for (uint256 i = 0; i < value.length(); i++) { + NodePtr cert = readNextElement(attestationTbs, offset); + require(cert.header() == 0x40, "invalid cert type"); + ptrs.cabundle[i] = cert; + offset = cert.content() + cert.length(); + } + } else if (keyHash == PCRS_KEY) { + require(value.header() == 0xa0, "invalid pcrs type"); + offset = value.content(); + ptrs.pcrs = new NodePtr[](value.length()); + for (uint256 i = 0; i < value.length(); i++) { + key = readNextElement(attestationTbs, offset); + require(key.header() == 0x00, "invalid pcr key type"); + require(key.length() == i, "invalid pcr key value"); + NodePtr pcr = readNextElement(attestationTbs, key.content()); + require(pcr.header() == 0x40, "invalid pcr type"); + ptrs.pcrs[i] = pcr; + offset = pcr.content() + pcr.length(); + } + } else { + revert("invalid attestation key"); + } + } + + return ptrs; + } + + function readNextElement(bytes memory cbor, uint256 ix) private pure returns (NodePtr) { + uint256 _type = uint256(uint8(cbor[ix] & 0xe0)); + uint256 length = uint256(uint8(cbor[ix] & 0x1f)); + uint256 header = 1; + if (length == 24) { + length = uint8(cbor[ix + 1]); + header = 2; + } else if (length == 25) { + length = cbor.readUint16(ix + 1); + header = 3; + } else if (length == 26) { + length = cbor.readUint32(ix + 1); + header = 5; + } else if (length == 27) { + length = cbor.readUint64(ix + 1); + header = 9; + } + return LibNodePtr.toNodePtr(_type, ix + header, length); + } +} diff --git a/src/NitroValidator.sol b/src/NitroValidator.sol new file mode 100644 index 0000000..849423c --- /dev/null +++ b/src/NitroValidator.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {console} from "forge-std/console.sol"; +import {Sha2Ext} from "./Sha2Ext.sol"; +import {Asn1Decode} from "./Asn1Decode.sol"; +import {NitroAttestation} from "./NitroAttestation.sol"; +import {ECDSA384} from "./ECDSA384.sol"; +import {LibBytes} from "./LibBytes.sol"; +import {NodePtr, LibNodePtr} from "./NodePtr.sol"; + +contract NitroValidator { + using Asn1Decode for bytes; + using NitroAttestation for bytes; + using LibBytes for bytes; + using LibNodePtr for NodePtr; + + // @dev download the root CA cert for AWS nitro enclaves from https://aws-nitro-enclaves.amazonaws.com/AWS_NitroEnclaves_Root-G1.zip + // @dev convert the base64 encoded pub key into hex to get the cert below + bytes public constant ROOT_CA_CERT = + hex"3082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff6"; + bytes32 public constant ROOT_CA_CERT_HASH = keccak256(ROOT_CA_CERT); + // 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 cerificate'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 = 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 = 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 = keccak256(hex"2b81040022"); + + // ECDSA384 curve parameters (NIST P-384) + bytes public constant CURVE_A = + hex"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc"; + bytes public constant CURVE_B = + hex"b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef"; + bytes public constant CURVE_GX = + hex"aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7"; + bytes public constant CURVE_GY = + hex"3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f"; + bytes public constant CURVE_P = + hex"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff"; + bytes public constant CURVE_N = + hex"ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973"; + bytes public constant CURVE_LOW_S_MAX = + hex"7fffffffffffffffffffffffffffffffffffffffffffffffe3b1a6c0fa1b96efac0d06d9245853bd76760cb5666294b9"; + + event CertVerified(bytes32 indexed certHash, bytes32 indexed parentCertHash); + + // certHash -> expiry + mapping(bytes32 => uint256) public certExpires; + // certHash -> pubKey + mapping(bytes32 => bytes) public certPubKey; + + constructor() { + bytes memory emptyPubKey; + _verifyCert(ROOT_CA_CERT, LibNodePtr.toNodePtr(0, 0, ROOT_CA_CERT.length), ROOT_CA_CERT_HASH, emptyPubKey); + } + + function verifyCert(bytes memory cert, bytes32 parentCertHash) external { + bytes memory parentPubKey = certPubKey[parentCertHash]; + require(parentPubKey.length != 0, "parent cert unverified"); + require(certExpires[parentCertHash] >= block.timestamp, "parent cert expired"); + bytes32 certHash = keccak256(cert); + require(certPubKey[certHash].length == 0, "cert already verified"); + _verifyCert(cert, LibNodePtr.toNodePtr(0, 0, cert.length), certHash, parentPubKey); + emit CertVerified(certHash, parentCertHash); + } + + function verifyCertBundle(bytes memory certificate, bytes[] calldata cabundle) external returns (bytes memory) { + bytes memory pubKey; + for (uint256 i = 0; i < cabundle.length; i++) { + bytes32 certHash = keccak256(cabundle[i]); + require(i > 0 || certHash == ROOT_CA_CERT_HASH, "Root CA cert not matching"); + pubKey = _verifyCert(cabundle[i], LibNodePtr.toNodePtr(0, 0, cabundle.length), certHash, pubKey); + require(pubKey.length != 0, "invalid pub key"); + } + return _verifyCert(certificate, LibNodePtr.toNodePtr(0, 0, certificate.length), keccak256(certificate), pubKey); + } + + function validateAttestation(bytes memory attestationTbs, bytes memory signature) external { + NitroAttestation.Ptrs memory ptrs = attestationTbs.parseAttestation(); + + bytes memory pubKey; + bytes32 certHash; + for (uint256 i = 0; i < ptrs.cabundle.length; i++) { + certHash = attestationTbs.keccak(ptrs.cabundle[i].content(), ptrs.cabundle[i].length()); + require(i > 0 || certHash == ROOT_CA_CERT_HASH, "Root CA cert not matching"); + pubKey = _verifyCert(attestationTbs, ptrs.cabundle[i], certHash, pubKey); + require(pubKey.length != 0, "invalid pub key"); + } + certHash = attestationTbs.keccak(ptrs.cert.content(), ptrs.cert.length()); + pubKey = _verifyCert(attestationTbs, ptrs.cert, certHash, pubKey); + + bytes memory hash = Sha2Ext.sha384(attestationTbs, 0, attestationTbs.length); + verifySignature(pubKey, hash, signature); + } + + function _verifyCert(bytes memory certificate, NodePtr ptr, bytes32 certHash, bytes memory parentPubKey) + internal + returns (bytes memory) + { + // skip verification if already verified + bytes memory pubKey = certPubKey[certHash]; + if (pubKey.length != 0) { + require(certExpires[certHash] >= block.timestamp, "cert expired"); + return pubKey; + } + + NodePtr root = certificate.rootOf(ptr); + NodePtr tbsCertPtr = certificate.firstChildOf(root); + uint256 notAfter; + + (notAfter, pubKey) = _parseTbs(certificate, tbsCertPtr); + + if (parentPubKey.length != 0 || certHash != ROOT_CA_CERT_HASH) { + NodePtr sigAlgoPtr = certificate.nextSiblingOf(tbsCertPtr); + require( + certificate.keccak(sigAlgoPtr.content(), sigAlgoPtr.length()) == CERT_ALGO_OID, "invalid cert sig algo" + ); + + bytes memory hash = Sha2Ext.sha384(certificate, tbsCertPtr.header(), tbsCertPtr.totalLength()); + bytes memory sigPacked = packSig(certificate, sigAlgoPtr); + verifySignature(parentPubKey, hash, sigPacked); + } + + certPubKey[certHash] = pubKey; + certExpires[certHash] = notAfter; + + return pubKey; + } + + function packSig(bytes memory certificate, NodePtr sigAlgoPtr) internal pure returns (bytes memory) { + NodePtr sigPtr = certificate.nextSiblingOf(sigAlgoPtr); + NodePtr sigBPtr = certificate.bitstringAt(sigPtr); + NodePtr sigRoot = certificate.rootOf(sigBPtr); + NodePtr sigRPtr = certificate.firstChildOf(sigRoot); + (uint128 rhi, uint256 rlo) = certificate.uint384At(sigRPtr); + NodePtr sigSPtr = certificate.nextSiblingOf(sigRPtr); + (uint128 shi, uint256 slo) = certificate.uint384At(sigSPtr); + return abi.encodePacked(rhi, rlo, shi, slo); + } + + function pad(bytes memory b, uint256 l) internal pure returns (bytes memory) { + require(b.length <= l, ""); + if (b.length == l) return b; + bytes memory padding = new bytes(l - b.length); + return abi.encodePacked(padding, b); + } + + function _parseTbs(bytes memory certificate, NodePtr ptr) internal view returns (uint256, bytes memory) { + NodePtr versionPtr = certificate.firstChildOf(ptr); + NodePtr vPtr = certificate.firstChildOf(versionPtr); + 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"); + + NodePtr serialPtr = certificate.nextSiblingOf(versionPtr); + // TODO: are there any checks on serialPtr other than being +ve? + + NodePtr sigAlgoPtr = certificate.nextSiblingOf(serialPtr); + require( + certificate.keccak(sigAlgoPtr.content(), sigAlgoPtr.length()) == CERT_ALGO_OID, "invalid cert sig algo" + ); + + return _parseTbs2(certificate, sigAlgoPtr); + } + + function _parseTbs2(bytes memory certificate, NodePtr sigAlgoPtr) internal view returns (uint256, bytes memory) { + NodePtr issuerPtr = certificate.nextSiblingOf(sigAlgoPtr); + // TODO: add checks on issuer + + NodePtr validityPtr = certificate.nextSiblingOf(issuerPtr); + NodePtr notBeforePtr = certificate.firstChildOf(validityPtr); + uint256 notBefore = certificate.timestampAt(notBeforePtr); + require(notBefore <= block.timestamp, "certificate not valid yet"); + NodePtr notAfterPtr = certificate.nextSiblingOf(notBeforePtr); + uint256 notAfter = certificate.timestampAt(notAfterPtr); + require(notAfter >= block.timestamp, "certificate not valid anymore"); + + NodePtr subjectPtr = certificate.nextSiblingOf(validityPtr); + // TODO: are there any checks on subject + // TODO: need to check if issuer of this cert is the parent cert + + return (notAfter, _verifyTbs2(certificate, certificate.nextSiblingOf(subjectPtr))); + } + + function _verifyTbs2(bytes memory certificate, NodePtr subjectPublicKeyInfoPtr) + internal + pure + returns (bytes memory) + { + NodePtr pubKeyAlgoPtr = certificate.firstChildOf(subjectPublicKeyInfoPtr); + NodePtr pubKeyAlgoIdPtr = certificate.firstChildOf(pubKeyAlgoPtr); + require( + certificate.keccak(pubKeyAlgoIdPtr.content(), pubKeyAlgoIdPtr.length()) == EC_PUB_KEY_OID, + "invalid cert algo id" + ); + + NodePtr algoParamsPtr = certificate.nextSiblingOf(pubKeyAlgoIdPtr); + require( + certificate.keccak(algoParamsPtr.content(), algoParamsPtr.length()) == SECP_384_R1_OID, + "invalid cert algo param" + ); + + NodePtr subjectPublicKeyPtr = certificate.nextSiblingOf(pubKeyAlgoPtr); + NodePtr subjectPubKeyPtr = certificate.bitstringAt(subjectPublicKeyPtr); + uint256 end = subjectPubKeyPtr.content() + subjectPubKeyPtr.length(); + bytes memory subjectPubKey = certificate.slice(end - 96, end); + + // NodePtr extensionsPtr = certificate.nextSiblingOf(subjectPublicKeyInfoPtr); + // TODO: verify extensions based on 3.2.3.2 section in https://github.com/aws/aws-nitro-enclaves-nsm-api/blob/main/docs/attestation_process.md#32-syntactical-validation + + return subjectPubKey; + } + + function verifySignature(bytes memory pubKey, bytes memory hash, bytes memory sig) internal view { + ECDSA384.Parameters memory CURVE_PARAMETERS = ECDSA384.Parameters({ + a: CURVE_A, + b: CURVE_B, + gx: CURVE_GX, + gy: CURVE_GY, + p: CURVE_P, + n: CURVE_N, + lowSmax: CURVE_LOW_S_MAX + }); + require(ECDSA384.verify(CURVE_PARAMETERS, hash, sig, pubKey), "invalid sig"); + } +} diff --git a/src/NodePtr.sol b/src/NodePtr.sol new file mode 100644 index 0000000..c9659e3 --- /dev/null +++ b/src/NodePtr.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +type NodePtr is uint256; + +library LibNodePtr { + using LibNodePtr for NodePtr; + + // First byte index of the header + function header(NodePtr self) internal pure returns (uint256) { + return uint80(NodePtr.unwrap(self)); + } + + // First byte index of the content + function content(NodePtr self) internal pure returns (uint256) { + return uint80(NodePtr.unwrap(self) >> 80); + } + + // Content length + function length(NodePtr self) internal pure returns (uint256) { + return uint80(NodePtr.unwrap(self) >> 160); + } + + // Total length (header length + content length) + function totalLength(NodePtr self) internal pure returns (uint256) { + return self.length() + self.content() - self.header(); + } + + // Pack 3 uint80s into a uint256 + function toNodePtr(uint256 _header, uint256 _content, uint256 _length) internal pure returns (NodePtr) { + return NodePtr.wrap(_header | _content << 80 | _length << 160); + } +} diff --git a/src/Sha2Ext.sol b/src/Sha2Ext.sol new file mode 100644 index 0000000..9833881 --- /dev/null +++ b/src/Sha2Ext.sol @@ -0,0 +1,222 @@ +// 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 + ]; + + 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, offset + length); + 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); + } +} diff --git a/test/NitroValidator.t.sol b/test/NitroValidator.t.sol new file mode 100644 index 0000000..067346e --- /dev/null +++ b/test/NitroValidator.t.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {NitroValidator} from "../src/NitroValidator.sol"; + +contract CounterTest is Test { + NitroValidator public validator; + + function setUp() public { + vm.warp(1732580000); + validator = new NitroValidator(); + } + + function test_ValidateAttestation() public { + bytes memory attestationTbs = + hex"846a5369676e61747572653144a101382240591142a9696d6f64756c655f69647827692d30666661626464383636323664616631662d656e633031393333323835326161633834333066646967657374665348413338346974696d657374616d701b00000193658c698f6470637273b0005830ec74bfbe7f7445a6c7610e152935e028276f638042b74797b119648e13f7a3675796b721034c320f140ea001b41aeae2015830fa2593b59f3e4fc7daba5cbdddfd3449d67cd02d43bb1128885e8f38b914d081dccdb68fff6d5b7a76bcb866a18a74a302583056ba201a72e36cd051e95e5c4724c899039b711770f4d9d4fe7a1de007119a10b364badcd35e90f728a5bdc9109057230358303c9cadd84f0d027d6a5370c3de4af9179824fd6f3f02ebab723ee4439c75d8f5183e1c55f523415d44e9e6580b06655204583069b1ee83e684fd1c2dbb31f668cbc336fbb0649616a965c896d76cc51689e94c6ab563f9d0257aa33f46db7bc71809db0558300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000658300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000758300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000858300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000958300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f58300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b63657274696669636174655902803082027c30820201a0030201020210019332852aac84300000000067450121300a06082a8648ce3d04030330818e310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533139303706035504030c30692d30666661626464383636323664616631662e75732d656173742d312e6177732e6e6974726f2d656e636c61766573301e170d3234313132353232353833385a170d3234313132363031353834315a308193310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753313e303c06035504030c35692d30666661626464383636323664616631662d656e63303139333332383532616163383433302e75732d656173742d312e6177733076301006072a8648ce3d020106052b8104002203620004dcae821ff99b2d890039bb0ac16e729439d842ad713ffe2609f8bc3f7dc8909cfed78e39cb5583e350b2719d52f7109ee56c988f4081a5789940a3e591b43c3697bb4b79409fc9dda34dacfaff2594e55eeb15086e268d73cc392dc187499768a31d301b300c0603551d130101ff04023000300b0603551d0f0404030206c0300a06082a8648ce3d0403030369003066023100896c399489c267213e069bd73e1ec4ef201a0bb4032472acfda46b96b506862d19384667c6ede4a3fb8dbfe5f26595d9023100a71c8937ee835d489a99b3b24817982fa8f1034728ceed3deae88fb193d98588bf411d009904fbd7ac6b31b5b23eb2b668636162756e646c65845902153082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff65902c3308202bf30820244a00302010202100b93e39c65609c59e8144a2ad34ba3a0300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132333036333235355a170d3234313231333037333235355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004ee78108039725a03e0b63a5d7d1244f6294eb7631f305e360997c8e5c06c779f23cfaeb64cb9aeac8a031bfac9f4dafc3621b4367f003c08c0ce410c2118396cc5d56ec4e92e1b17f9709b2bffcef462f7bcb97d6ca11325c4a30156c9720de7a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e041604142b3d75d274a3cdd61b2c13f539e08c960ce757dd300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030369003066023100fce7a6c2b38e0a8ebf0d28348d74463458b84bfe8b2b95315dd4da665e8e83d4ab911852a4e92a8263ecf571d2df3b89023100ab92be511136be76aa313018f9f4825eaad602d0342d268e6da632767f68f55f761fa9fd2a7ee716c481c67f26e3f8f4590319308203153082029aa003020102021020c20971680e956fc3c8ce925d784bc7300a06082a8648ce3d0403033064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c61766573301e170d3234313132353032323230345a170d3234313230313030323230345a308189313c303a06035504030c33626635396531633335623630386133382e7a6f6e616c2e75732d656173742d312e6177732e6e6974726f2d656e636c61766573310c300a060355040b0c03415753310f300d060355040a0c06416d617a6f6e310b3009060355040613025553310b300906035504080c0257413110300e06035504070c0753656174746c653076301006072a8648ce3d020106052b8104002203620004df741cd0537abbbc37bb32b06c835f497df86933b6ac8b4ee15d1251cfde596a7953756bb2759896a4d50c7cfb7d50cfc62fd4010a8c0d4a58a6f38988de6707d5aeaef3e3ca523ffac31260cc7c33546dc667d52ba524c39bd0ed6b82c0652da381ea3081e730120603551d130101ff040830060101ff020101301f0603551d230418301680142b3d75d274a3cdd61b2c13f539e08c960ce757dd301d0603551d0e04160414e8b15a6bc0b83e3d9e50ab9b289fb5fa0c61eabf300e0603551d0f0101ff0404030201863081800603551d1f047930773075a073a071866f687474703a2f2f63726c2d75732d656173742d312d6177732d6e6974726f2d656e636c617665732e73332e75732d656173742d312e616d617a6f6e6177732e636f6d2f63726c2f39636665653133332d613562622d343431392d613462372d3730386661643563363866662e63726c300a06082a8648ce3d04030303690030660231009595351f7c4411011eb4cf1a18181c2ed6901e84c2971c781e2cdc2725d5135066fc8d96ac70c98fc27106cdb345a563023100f96927f5bc58f1c29f8ea06d9bb5eeae3a6e2e572aff9911a8c90ed6e00c1cc7c534b9fde367781807c35ba9427d05fe5902c1308202bd30820244a00302010202142690c27f442c86646256455d3442f8998be152dc300a06082a8648ce3d040303308189313c303a06035504030c33626635396531633335623630386133382e7a6f6e616c2e75732d656173742d312e6177732e6e6974726f2d656e636c61766573310c300a060355040b0c03415753310f300d060355040a0c06416d617a6f6e310b3009060355040613025553310b300906035504080c0257413110300e06035504070c0753656174746c65301e170d3234313132353133353135375a170d3234313132363133353135375a30818e310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533139303706035504030c30692d30666661626464383636323664616631662e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004c041c328653a7028db8060f3f19f589197b8c1a17dc755a5629c0f47c3cd3414412fd38b7f87fc70a6f22a0c2698fe1f748eff998f783add861a6b2373fba37a31f9bf0ab75fd2c4e17cc0df8124ddb0a4513483e40721ebb15a80619696747aa366306430120603551d130101ff040830060101ff020100300e0603551d0f0101ff040403020204301d0603551d0e041604145555580de23b6d83eb6a5be1b4dbdb376f69e444301f0603551d23041830168014e8b15a6bc0b83e3d9e50ab9b289fb5fa0c61eabf300a06082a8648ce3d0403030367003064023072c53164609cba5d7a16914d5d2102a9e70009288aae1215cc5e8d70f2d2d4b49bffb0119ec523e620275729f09e566e02302e0f2b7998eb25fa493dc1300329f7f142337b38e76df0a32b8660f41599c5febae120e4ed2c60efbbaa842ba6db8d916a7075626c69635f6b657958410433a4701fa871b188983d570e2c2d8cf98fd66eb19ba8ca7617bc8e20e152a5d7f0205eae76e608ce855077e4565be69db4471ef72857253742f9602c11ff04e569757365725f64617461f6656e6f6e6365f6"; + bytes memory signature = hex"a3dfa0e2c5fcbebc057b29544b1f43133e0153f8b7a711da749b3207acf9931660d4195a0cd912a7b720859321f8f0f472d9807d217935580857066fde8d2db62a946766cb5c0a383a1d55c7970ccb15c5700746a8ebe8c2a2b21a77a204765b"; + validator.validateAttestation(attestationTbs, signature); + } + + function test_VerifyCertBundle() public { + bytes memory cert = + hex"3082027c30820201a0030201020210019332852aac84300000000067450121300a06082a8648ce3d04030330818e310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533139303706035504030c30692d30666661626464383636323664616631662e75732d656173742d312e6177732e6e6974726f2d656e636c61766573301e170d3234313132353232353833385a170d3234313132363031353834315a308193310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753313e303c06035504030c35692d30666661626464383636323664616631662d656e63303139333332383532616163383433302e75732d656173742d312e6177733076301006072a8648ce3d020106052b8104002203620004dcae821ff99b2d890039bb0ac16e729439d842ad713ffe2609f8bc3f7dc8909cfed78e39cb5583e350b2719d52f7109ee56c988f4081a5789940a3e591b43c3697bb4b79409fc9dda34dacfaff2594e55eeb15086e268d73cc392dc187499768a31d301b300c0603551d130101ff04023000300b0603551d0f0404030206c0300a06082a8648ce3d0403030369003066023100896c399489c267213e069bd73e1ec4ef201a0bb4032472acfda46b96b506862d19384667c6ede4a3fb8dbfe5f26595d9023100a71c8937ee835d489a99b3b24817982fa8f1034728ceed3deae88fb193d98588bf411d009904fbd7ac6b31b5b23eb2b6"; + bytes[] memory cabundle = new bytes[](4); + cabundle[0] = + hex"3082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff6"; + cabundle[1] = + hex"308202bf30820244a00302010202100b93e39c65609c59e8144a2ad34ba3a0300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132333036333235355a170d3234313231333037333235355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004ee78108039725a03e0b63a5d7d1244f6294eb7631f305e360997c8e5c06c779f23cfaeb64cb9aeac8a031bfac9f4dafc3621b4367f003c08c0ce410c2118396cc5d56ec4e92e1b17f9709b2bffcef462f7bcb97d6ca11325c4a30156c9720de7a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e041604142b3d75d274a3cdd61b2c13f539e08c960ce757dd300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030369003066023100fce7a6c2b38e0a8ebf0d28348d74463458b84bfe8b2b95315dd4da665e8e83d4ab911852a4e92a8263ecf571d2df3b89023100ab92be511136be76aa313018f9f4825eaad602d0342d268e6da632767f68f55f761fa9fd2a7ee716c481c67f26e3f8f4"; + cabundle[2] = + hex"308203153082029aa003020102021020c20971680e956fc3c8ce925d784bc7300a06082a8648ce3d0403033064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c61766573301e170d3234313132353032323230345a170d3234313230313030323230345a308189313c303a06035504030c33626635396531633335623630386133382e7a6f6e616c2e75732d656173742d312e6177732e6e6974726f2d656e636c61766573310c300a060355040b0c03415753310f300d060355040a0c06416d617a6f6e310b3009060355040613025553310b300906035504080c0257413110300e06035504070c0753656174746c653076301006072a8648ce3d020106052b8104002203620004df741cd0537abbbc37bb32b06c835f497df86933b6ac8b4ee15d1251cfde596a7953756bb2759896a4d50c7cfb7d50cfc62fd4010a8c0d4a58a6f38988de6707d5aeaef3e3ca523ffac31260cc7c33546dc667d52ba524c39bd0ed6b82c0652da381ea3081e730120603551d130101ff040830060101ff020101301f0603551d230418301680142b3d75d274a3cdd61b2c13f539e08c960ce757dd301d0603551d0e04160414e8b15a6bc0b83e3d9e50ab9b289fb5fa0c61eabf300e0603551d0f0101ff0404030201863081800603551d1f047930773075a073a071866f687474703a2f2f63726c2d75732d656173742d312d6177732d6e6974726f2d656e636c617665732e73332e75732d656173742d312e616d617a6f6e6177732e636f6d2f63726c2f39636665653133332d613562622d343431392d613462372d3730386661643563363866662e63726c300a06082a8648ce3d04030303690030660231009595351f7c4411011eb4cf1a18181c2ed6901e84c2971c781e2cdc2725d5135066fc8d96ac70c98fc27106cdb345a563023100f96927f5bc58f1c29f8ea06d9bb5eeae3a6e2e572aff9911a8c90ed6e00c1cc7c534b9fde367781807c35ba9427d05fe"; + cabundle[3] = + hex"308202bd30820244a00302010202142690c27f442c86646256455d3442f8998be152dc300a06082a8648ce3d040303308189313c303a06035504030c33626635396531633335623630386133382e7a6f6e616c2e75732d656173742d312e6177732e6e6974726f2d656e636c61766573310c300a060355040b0c03415753310f300d060355040a0c06416d617a6f6e310b3009060355040613025553310b300906035504080c0257413110300e06035504070c0753656174746c65301e170d3234313132353133353135375a170d3234313132363133353135375a30818e310b30090603550406130255533113301106035504080c0a57617368696e67746f6e3110300e06035504070c0753656174746c65310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533139303706035504030c30692d30666661626464383636323664616631662e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004c041c328653a7028db8060f3f19f589197b8c1a17dc755a5629c0f47c3cd3414412fd38b7f87fc70a6f22a0c2698fe1f748eff998f783add861a6b2373fba37a31f9bf0ab75fd2c4e17cc0df8124ddb0a4513483e40721ebb15a80619696747aa366306430120603551d130101ff040830060101ff020100300e0603551d0f0101ff040403020204301d0603551d0e041604145555580de23b6d83eb6a5be1b4dbdb376f69e444301f0603551d23041830168014e8b15a6bc0b83e3d9e50ab9b289fb5fa0c61eabf300a06082a8648ce3d0403030367003064023072c53164609cba5d7a16914d5d2102a9e70009288aae1215cc5e8d70f2d2d4b49bffb0119ec523e620275729f09e566e02302e0f2b7998eb25fa493dc1300329f7f142337b38e76df0a32b8660f41599c5febae120e4ed2c60efbbaa842ba6db8d91"; + validator.verifyCertBundle(cert, cabundle); + } + + function test_VerifyCert() public { + bytes memory parent = + hex"3082021130820196a003020102021100f93175681b90afe11d46ccb4e4e7f856300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3139313032383133323830355a170d3439313032383134323830355a3049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004fc0254eba608c1f36870e29ada90be46383292736e894bfff672d989444b5051e534a4b1f6dbe3c0bc581a32b7b176070ede12d69a3fea211b66e752cf7dd1dd095f6f1370f4170843d9dc100121e4cf63012809664487c9796284304dc53ff4a3423040300f0603551d130101ff040530030101ff301d0603551d0e041604149025b50dd90547e796c396fa729dcf99a9df4b96300e0603551d0f0101ff040403020186300a06082a8648ce3d0403030369003066023100a37f2f91a1c9bd5ee7b8627c1698d255038e1f0343f95b63a9628c3d39809545a11ebcbf2e3b55d8aeee71b4c3d6adf3023100a2f39b1605b27028a5dd4ba069b5016e65b4fbde8fe0061d6a53197f9cdaf5d943bc61fc2beb03cb6fee8d2302f3dff6"; + bytes memory cert = + hex"308202bf30820244a00302010202100b93e39c65609c59e8144a2ad34ba3a0300a06082a8648ce3d0403033049310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c03415753311b301906035504030c126177732e6e6974726f2d656e636c61766573301e170d3234313132333036333235355a170d3234313231333037333235355a3064310b3009060355040613025553310f300d060355040a0c06416d617a6f6e310c300a060355040b0c034157533136303406035504030c2d353133623665666332313639303264372e75732d656173742d312e6177732e6e6974726f2d656e636c617665733076301006072a8648ce3d020106052b8104002203620004ee78108039725a03e0b63a5d7d1244f6294eb7631f305e360997c8e5c06c779f23cfaeb64cb9aeac8a031bfac9f4dafc3621b4367f003c08c0ce410c2118396cc5d56ec4e92e1b17f9709b2bffcef462f7bcb97d6ca11325c4a30156c9720de7a381d53081d230120603551d130101ff040830060101ff020102301f0603551d230418301680149025b50dd90547e796c396fa729dcf99a9df4b96301d0603551d0e041604142b3d75d274a3cdd61b2c13f539e08c960ce757dd300e0603551d0f0101ff040403020186306c0603551d1f046530633061a05fa05d865b687474703a2f2f6177732d6e6974726f2d656e636c617665732d63726c2e73332e616d617a6f6e6177732e636f6d2f63726c2f61623439363063632d376436332d343262642d396539662d3539333338636236376638342e63726c300a06082a8648ce3d0403030369003066023100fce7a6c2b38e0a8ebf0d28348d74463458b84bfe8b2b95315dd4da665e8e83d4ab911852a4e92a8263ecf571d2df3b89023100ab92be511136be76aa313018f9f4825eaad602d0342d268e6da632767f68f55f761fa9fd2a7ee716c481c67f26e3f8f4"; + validator.verifyCert(cert, keccak256(parent)); + } +}