From d7bb26f8bd6ecb0629f4529c4597e0611882b0d7 Mon Sep 17 00:00:00 2001 From: Artem Chystiakov <47551140+Arvolear@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:38:40 +0200 Subject: [PATCH] Crypto libs (#115) * added ecdsa384 lib * added rsassapss * try fix ci * fix callvalue * Added tests covering the `verify` and `_isOnCurve` functions * fix coverage * split getModifiedSigOrPubKey into modifyLeft and modifyRight * Added tests covering the RSASSAPSS lib * switched to assert two unreachable conditions * fixed typos in test * removed unused functions in U384 lib * added test for a U384.cmp function * trying to fix test for cmp * add brainpoolP384r1 test --------- Co-authored-by: aritkulova --- .github/workflows/coverage.yml | 6 +- .github/workflows/tests.yml | 2 + README.md | 11 +- contracts/libs/crypto/ECDSA384.sol | 1102 ++++++++++++++++++ contracts/libs/crypto/RSASSAPSS.sol | 223 ++++ contracts/mock/libs/crypto/ECDSA384Mock.sol | 91 ++ contracts/mock/libs/crypto/RSASSAPSSMock.sol | 17 + package-lock.json | 4 +- package.json | 2 +- test/libs/crypto/ECDSA384.test.ts | 178 +++ test/libs/crypto/RSASSAPSS.test.ts | 63 + 11 files changed, 1690 insertions(+), 9 deletions(-) create mode 100644 contracts/libs/crypto/ECDSA384.sol create mode 100644 contracts/libs/crypto/RSASSAPSS.sol create mode 100644 contracts/mock/libs/crypto/ECDSA384Mock.sol create mode 100644 contracts/mock/libs/crypto/RSASSAPSSMock.sol create mode 100644 test/libs/crypto/ECDSA384.test.ts create mode 100644 test/libs/crypto/RSASSAPSS.test.ts diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 96641f23..11823836 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -18,6 +18,10 @@ jobs: - name: Setup uses: ./.github/actions/setup - name: Run coverage + env: + NODE_OPTIONS: "--max_old_space_size=8192" run: npm run coverage - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c7ed224e..9012324f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,4 +18,6 @@ jobs: - name: Setup uses: ./.github/actions/setup - name: Run tests + env: + NODE_OPTIONS: "--max_old_space_size=8192" run: npm run test diff --git a/README.md b/README.md index 647eb421..fe784c10 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,17 @@ Solidity modules and utilities that **go far beyond mediocre solidity**. - Implementation of [**Contracts Registry**](https://eips.ethereum.org/EIPS/eip-6224) pattern -- Versatile access control smart contracts (**Merkle whitelists**, **RBAC**) -- Enhanced and simplified [**Diamond**](https://eips.ethereum.org/EIPS/eip-2535) pattern +- State-of-the-art cryptography primitives (**ECDSA over 384 bit curves**, **RSASSA-PSS**) - Advanced data structures (**Vector**, **DynamicSet**, **PriorityQueue**, **AVLTree**) - ZK-friendly [**Sparse Merkle Tree**](https://docs.iden3.io/publications/pdfs/Merkle-Tree.pdf) and [**Incremental Merkle Tree**](https://github.com/runtimeverification/deposit-contract-verification/blob/master/deposit-contract-verification.pdf) implementations -- Flexible finance primitives (**Staking**, **Vesting**) +- Versatile access control smart contracts (**Merkle whitelists**, **RBAC**) +- Enhanced and simplified [**Diamond**](https://eips.ethereum.org/EIPS/eip-2535) pattern +- Flexible finance instruments (**Staking**, **Vesting**) - Robust UniswapV2 and UniswapV3 oracles - Lightweight SBT implementation -- Utilities to ease work with memory, ERC20 decimals, arrays, sets, and ZK proofs +- Utilities to ease work with memory, types, ERC20 decimals, arrays, sets, and ZK proofs -Built with the help of [Openzeppelin Contracts](https://github.com/OpenZeppelin/openzeppelin-contracts) (4.9.6). +Built leveraging [OpenZeppelin Contracts](https://github.com/OpenZeppelin/openzeppelin-contracts) (4.9.6). ## Overview diff --git a/contracts/libs/crypto/ECDSA384.sol b/contracts/libs/crypto/ECDSA384.sol new file mode 100644 index 00000000..59704e20 --- /dev/null +++ b/contracts/libs/crypto/ECDSA384.sol @@ -0,0 +1,1102 @@ +// 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 ~9 million gas per call. + * + * The approach is Strauss-Shamir double scalar multiplication with 4 bits of precompute + projective points. + */ +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 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 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 view { + 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/contracts/libs/crypto/RSASSAPSS.sol b/contracts/libs/crypto/RSASSAPSS.sol new file mode 100644 index 00000000..f26546db --- /dev/null +++ b/contracts/libs/crypto/RSASSAPSS.sol @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/** + * @notice Cryptography module + * + * This library provides functionality to verify RSASSA-PSS signatures with MGF1 mask generation function. + * + * Users may provide custom hash functions via `Parameters` struct. However, the usage of `sha256` is recommended. + * The RSASSA-PSS signature verification costs ~340k gas. + * + * Learn more about the algorithm [here](https://datatracker.ietf.org/doc/html/rfc3447#section-8.1). + */ +library RSASSAPSS { + /** + * @notice The RSASSA-PSS parameters. + * @param hashLength the hash function output length in bytes. + * @param saltLength the pss encoding salt length in bytes. + * @param hasher the function-pointer to a custom hash function. + */ + struct Parameters { + uint256 hashLength; + uint256 saltLength; + function(bytes memory) internal pure returns (bytes memory) hasher; + } + + /** + * @notice Same as `verify` but with `sha256` hash function preconfiguration. + */ + function verifySha256( + bytes memory message_, + bytes memory s_, + bytes memory e_, + bytes memory n_ + ) internal view returns (bool) { + unchecked { + Parameters memory params_ = Parameters({ + hashLength: 32, + saltLength: 32, + hasher: _sha256 + }); + + return verify(params_, message_, s_, e_, n_); + } + } + + /** + * @notice Verifies RSAPSS-SSA signature with custom parameters. + * @param params_ The parameters to specify the hash length, salt length, and hash function of choice. + * @param message_ The arbitrary message to be verified. + * @param s_ The "encrypted" signature + * @param e_ The public key exponent. `65537` is a recommended value. + * @param n_ The modulus of a public key. + */ + function verify( + Parameters memory params_, + bytes memory message_, + bytes memory s_, + bytes memory e_, + bytes memory n_ + ) internal view returns (bool) { + unchecked { + if (s_.length == 0 || e_.length == 0 || n_.length == 0) { + return false; + } + + bytes memory decipher_ = _rsa(s_, e_, n_); + + return _pss(message_, decipher_, params_); + } + } + + /** + * @notice Calculates RSA via modexp (0x05) precompile. + */ + function _rsa( + bytes memory s_, + bytes memory e_, + bytes memory n_ + ) private view returns (bytes memory decipher_) { + unchecked { + bytes memory input_ = abi.encodePacked(s_.length, e_.length, n_.length, s_, e_, n_); + + decipher_ = new bytes(n_.length); + + assembly { + pop( + staticcall( + sub(gas(), 2000), // gas buffer + 5, + add(input_, 0x20), + mload(input_), + add(decipher_, 0x20), + mload(n_) + ) + ) + } + } + } + + /** + * @notice Checks the PSS encoding. + */ + function _pss( + bytes memory message_, + bytes memory signature_, + Parameters memory params_ + ) private pure returns (bool) { + unchecked { + uint256 hashLength_ = params_.hashLength; + uint256 saltLength_ = params_.saltLength; + uint256 sigBytes_ = signature_.length; + uint256 sigBits_ = (sigBytes_ * 8 - 1) & 7; + + assert(message_.length < 2 ** 61); + + bytes memory messageHash_ = params_.hasher(message_); + + if (sigBytes_ < hashLength_ + saltLength_ + 2) { + return false; + } + + if (signature_[sigBytes_ - 1] != hex"BC") { + return false; + } + + bytes memory db_ = new bytes(sigBytes_ - hashLength_ - 1); + bytes memory h_ = new bytes(hashLength_); + + for (uint256 i = 0; i < db_.length; ++i) { + db_[i] = signature_[i]; + } + + for (uint256 i = 0; i < hashLength_; ++i) { + h_[i] = signature_[i + db_.length]; + } + + if (uint8(db_[0] & bytes1(uint8(((0xFF << (sigBits_)))))) == 1) { + return false; + } + + bytes memory dbMask_ = _mgf(params_, h_, db_.length); + + for (uint256 i = 0; i < db_.length; ++i) { + db_[i] ^= dbMask_[i]; + } + + if (sigBits_ > 0) { + db_[0] &= bytes1(uint8(0xFF >> (8 - sigBits_))); + } + + uint256 zeroBytes_; + + for ( + zeroBytes_ = 0; + db_[zeroBytes_] == 0 && zeroBytes_ < (db_.length - 1); + ++zeroBytes_ + ) {} + + if (db_[zeroBytes_] != hex"01") { + return false; + } + + bytes memory salt_ = new bytes(saltLength_); + + for (uint256 i = 0; i < salt_.length; ++i) { + salt_[i] = db_[db_.length - salt_.length + i]; + } + + bytes memory hh_ = params_.hasher( + abi.encodePacked(hex"0000000000000000", messageHash_, salt_) + ); + + /// check bytes equality + if (keccak256(h_) != keccak256(hh_)) { + return false; + } + + return true; + } + } + + /** + * @notice MGF1 mask generation function + */ + function _mgf( + Parameters memory params_, + bytes memory message_, + uint256 maskLen_ + ) private pure returns (bytes memory res_) { + unchecked { + uint256 hashLength_ = params_.hashLength; + + bytes memory cnt_ = new bytes(4); + + assert(maskLen_ <= (2 ** 32) * hashLength_); + + for (uint256 i = 0; i < (maskLen_ + hashLength_ - 1) / hashLength_; ++i) { + cnt_[0] = bytes1(uint8((i >> 24) & 255)); + cnt_[1] = bytes1(uint8((i >> 16) & 255)); + cnt_[2] = bytes1(uint8((i >> 8) & 255)); + cnt_[3] = bytes1(uint8(i & 255)); + + bytes memory hashedResInter_ = params_.hasher(abi.encodePacked(message_, cnt_)); + + res_ = abi.encodePacked(res_, hashedResInter_); + } + + assembly { + mstore(res_, maskLen_) + } + } + } + + /** + * @notice Utility `sha256` wrapper. + */ + function _sha256(bytes memory data) private pure returns (bytes memory) { + unchecked { + return abi.encodePacked(sha256(data)); + } + } +} diff --git a/contracts/mock/libs/crypto/ECDSA384Mock.sol b/contracts/mock/libs/crypto/ECDSA384Mock.sol new file mode 100644 index 00000000..17aea8eb --- /dev/null +++ b/contracts/mock/libs/crypto/ECDSA384Mock.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import {ECDSA384, U384} from "../../../libs/crypto/ECDSA384.sol"; + +contract ECDSA384Mock { + using ECDSA384 for *; + + ECDSA384.Parameters private _secp384r1CurveParams = + ECDSA384.Parameters({ + a: hex"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc", + b: hex"b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef", + gx: hex"aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7", + gy: hex"3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f", + p: hex"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff", + n: hex"ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973", + lowSmax: hex"7fffffffffffffffffffffffffffffffffffffffffffffffe3b1a6c0fa1b96efac0d06d9245853bd76760cb5666294b9" + }); + + ECDSA384.Parameters private _brainpoolP384r1CurveParams = + ECDSA384.Parameters({ + a: hex"7bc382c63d8c150c3c72080ace05afa0c2bea28e4fb22787139165efba91f90f8aa5814a503ad4eb04a8c7dd22ce2826", + b: hex"04a8c7dd22ce28268b39b55416f0447c2fb77de107dcd2a62e880ea53eeb62d57cb4390295dbc9943ab78696fa504c11", + gx: hex"1d1c64f068cf45ffa2a63a81b7c13f6b8847a3e77ef14fe3db7fcafe0cbd10e8e826e03436d646aaef87b2e247d4af1e", + gy: hex"8abe1d7520f9c2a45cb1eb8e95cfd55262b70b29feec5864e19c054ff99129280e4646217791811142820341263c5315", + p: hex"8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b412b1da197fb71123acd3a729901d1a71874700133107ec53", + n: hex"8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b31f166e6cac0425a7cf3ab6af6b7fc3103b883202e9046565", + lowSmax: hex"465c8f41519c369407aeb7bf287320ef8a97b884f6aa2b598f8b3736560212d3e79d5b57b5bfe1881dc41901748232b2" + }); + + function verifySECP384r1( + bytes calldata message_, + bytes calldata signature_, + bytes calldata pubKey_ + ) external view returns (bool) { + return + _secp384r1CurveParams.verify(abi.encodePacked(sha256(message_)), signature_, pubKey_); + } + + function verifyBrainpoolP384r1( + bytes calldata message_, + bytes calldata signature_, + bytes calldata pubKey_ + ) external view returns (bool) { + return + _brainpoolP384r1CurveParams.verify( + abi.encodePacked(sha256(message_)), + signature_, + pubKey_ + ); + } + + function verifySECP384r1CustomCurveParameters( + bytes calldata message_, + bytes calldata signature_, + bytes calldata pubKey_, + bytes calldata customA_, + bytes calldata customB_ + ) external view returns (bool) { + ECDSA384.Parameters memory curveParams_ = _secp384r1CurveParams; + curveParams_.a = customA_; + curveParams_.b = customB_; + + return curveParams_.verify(abi.encodePacked(sha256(message_)), signature_, pubKey_); + } + + function verifySECP384r1WithoutHashing( + bytes calldata hashedMessage_, + bytes calldata signature_, + bytes calldata pubKey_ + ) external view returns (bool) { + return _secp384r1CurveParams.verify(abi.encodePacked(hashedMessage_), signature_, pubKey_); + } + + function cmpMock() external pure returns (int256 cmp_) { + uint256 a_; + uint256 b_; + + assembly { + a_ := mload(0x40) + b_ := add(a_, 0x40) + + mstore(add(a_, 0x20), 0x1234) + mstore(add(b_, 0x20), 0x5678) + + mstore(0x40, add(b_, 0x40)) + } + + return U384.cmp(a_, b_); + } +} diff --git a/contracts/mock/libs/crypto/RSASSAPSSMock.sol b/contracts/mock/libs/crypto/RSASSAPSSMock.sol new file mode 100644 index 00000000..7b59116e --- /dev/null +++ b/contracts/mock/libs/crypto/RSASSAPSSMock.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import {RSASSAPSS} from "../../../libs/crypto/RSASSAPSS.sol"; + +contract RSASSAPSSMock { + using RSASSAPSS for *; + + function verifySha256( + bytes calldata message_, + bytes calldata s_, + bytes calldata e_, + bytes calldata n_ + ) external view returns (bool) { + return message_.verifySha256(s_, e_, n_); + } +} diff --git a/package-lock.json b/package-lock.json index 80b28e76..96721ca1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@solarity/solidity-lib", - "version": "2.7.11", + "version": "2.7.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@solarity/solidity-lib", - "version": "2.7.11", + "version": "2.7.12", "license": "MIT", "dependencies": { "@openzeppelin/contracts": "4.9.6", diff --git a/package.json b/package.json index 6c619a44..e871edf5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@solarity/solidity-lib", - "version": "2.7.11", + "version": "2.7.12", "license": "MIT", "author": "Distributed Lab", "readme": "README.md", diff --git a/test/libs/crypto/ECDSA384.test.ts b/test/libs/crypto/ECDSA384.test.ts new file mode 100644 index 00000000..591848bb --- /dev/null +++ b/test/libs/crypto/ECDSA384.test.ts @@ -0,0 +1,178 @@ +import { ethers } from "hardhat"; +import { expect } from "chai"; +import { Reverter } from "@/test/helpers/reverter"; + +import { ECDSA384Mock } from "@ethers-v6"; + +function modifyLeft(value: string, modifier: string): string { + let newSignature = "0x"; + + if (modifier != "0") { + newSignature += modifier; + } + + newSignature = newSignature.padEnd(98, "0"); + + newSignature += value.substring(98, 194); + + return newSignature; +} + +function modifyRight(value: string, modifier: string): string { + let newSignature = value.substring(0, 98); + + if (modifier != "0") { + newSignature += modifier; + } + + newSignature = newSignature.padEnd(194, "0"); + + return newSignature; +} + +describe("ECDSA384", () => { + const reverter = new Reverter(); + + let ecdsa384: ECDSA384Mock; + + before(async () => { + const ECDSA384Mock = await ethers.getContractFactory("ECDSA384Mock"); + + ecdsa384 = await ECDSA384Mock.deploy(); + + await reverter.snapshot(); + }); + + afterEach(reverter.revert); + + describe("SECP384r1", () => { + const signature = + "0x3044b552135e5eb46368e739b3138f9f1f2eb37a0518f564d2767d02ac67a9f41fb71bad06a99f54ee2e43ead2916f630e07a31eb5214798e5ecb032e49585f5d3d52b6f74d8bd71fbfd606a4466ae7a33723520475d1367c1a35e30a0e80a96"; + const pubKey = + "0x56931fd7d42942eec92298d7291371cdbac29c60230c9f635d010939ab7f8f5d977ccfe90bd7528cafa53afad6225bf61e2af4d20831aed1e6b578ccb00e1534182f6d1ee6bf524fbd62bd056d0d538c24eb7f2a436e336e139f00a072b0ba1a"; + const message = + "0x308203cfa0030201020204492f01a0300a06082a8648ce3d0403023041310b3009060355040613024742310e300c060355040a1305554b4b50413122302006035504031319436f756e747279205369676e696e6720417574686f72697479301e170d3232303830313030303030305a170d3333313230313030303030305a305c310b3009060355040613024742311b3019060355040a1312484d2050617373706f7274204f6666696365310f300d060355040b13064c6f6e646f6e311f301d06035504031316446f63756d656e74205369676e696e67204b657920363082014b3082010306072a8648ce3d02013081f7020101302c06072a8648ce3d0101022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff305b0420ffffffff00000001000000000000000000000000fffffffffffffffffffffffc04205ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b031500c49d360886e704936a6678e1139d26b7819f7e900441046b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63255102010103420004369b6087115805a184e0a04e522acc1c58959aa0c9b19d80c8dd293fdd504ec0675381123b71874d105693f18105022fe4eb9ac7c2dfbcdcc58cbd7351d263d4a38201a4308201a030420603551d11043b30398125646f63756d656e742e746563686e6f6c6f677940686f6d656f66666963652e676f762e756ba410300e310c300a06035504071303474252302b0603551d1004243022800f32303232303830313030303030305a810f32303232313130343030303030305a300e0603551d0f0101ff04040302078030630603551d12045c305aa410300e310c300a06035504071303474252811f646f63756d656e742e746563686e6f6c6f677940686d706f2e676f762e756b8125646f63756d656e742e746563686e6f6c6f677940686f6d656f66666963652e676f762e756b3019060767810801010602040e300c020100310713015013025054305d0603551d1f045630543052a050a04e862068747470733a2f2f686d706f2e676f762e756b2f637363612f4742522e63726c862a68747470733a2f2f706b64646f776e6c6f6164312e6963616f2e696e742f43524c732f4742522e63726c301f0603551d23041830168014499e4730278520c57cfc118024e14c1562a249d6301d0603551d0e0416041439b5abb7415fb8629b55c137d12a01c35fb49486"; + + describe("verify", () => { + it("should verify the signature", async () => { + expect(await ecdsa384.verifySECP384r1(message, signature, pubKey)).to.be.true; + }); + + it("should not verify invalid signature", async () => { + const message = "0x0123456789"; + + expect(await ecdsa384.verifySECP384r1(message, signature, pubKey)).to.be.false; + }); + + it("should not verify if U384.eqInteger(inputs_.r, 0) is true", async () => { + const sigWithZeroR = modifyLeft(signature, "0"); + + expect(await ecdsa384.verifySECP384r1(message, sigWithZeroR, pubKey)).to.be.false; + }); + + it("should not verify if U384.eqInteger(inputs_.s, 0) is true", async () => { + const sigWithZeroS = modifyRight(signature, "0"); + + expect(await ecdsa384.verifySECP384r1(message, sigWithZeroS, pubKey)).to.be.false; + }); + + it("should not verify if U384.cmp(inputs_.r, params_.n) >= 0", async () => { + const n = "ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973"; + + const modifiedSig = modifyLeft(signature, n); + + expect(await ecdsa384.verifySECP384r1(message, modifiedSig, pubKey)).to.be.false; + }); + + it("should not verify if U384.cmp(inputs_.s, params_.lowSmax) > 0", async () => { + const lowSmaxPlusOne = + "7fffffffffffffffffffffffffffffffffffffffffffffffe3b1a6c0fa1b96efac0d06d9245853bd76760cb5666294ba"; + + const modifiedSig = modifyRight(signature, lowSmaxPlusOne); + + expect(await ecdsa384.verifySECP384r1(message, modifiedSig, pubKey)).to.be.false; + }); + + it("should revert if curve parameters have an invalid length", async () => { + await expect( + ecdsa384.verifySECP384r1CustomCurveParameters(message, signature, pubKey, "0x", "0x"), + ).to.be.revertedWith("U384: not 384"); + }); + + it("should revert if signature or public key has an invalid length", async () => { + const wrongSig = + "0x3066023100a2fcd465ab5b507fc55941c1c6cd8286de04b83c94c6be25b5bdf58e27d86c3759d5f94ffcbd009618b6371bc51994f0023100d708d5045caa4a61cad42622c14bfb3343a5a9dc8fdbd19ce46b9e24c2aff84ba5114bb543fc4b0099f369079302b721"; + + await expect(ecdsa384.verifySECP384r1(message, wrongSig, pubKey)).to.be.revertedWith("U384: not 768"); + + const wrongPubKey = + "0x3076301006072a8648ce3d020106052b81040022036200041d77728fada41a8a7a23fe922e4e2dc8881a94b72a0612077ad80eeef13ff3bbea92aeef36a0f65885417aea104b86b76aedc226e260f7d0eeea8405b9269f354d929e5a98cab64fe192db94ed9335b7395e38e99b8bfaf32effa163a92889f9"; + + await expect(ecdsa384.verifySECP384r1(message, signature, wrongPubKey)).to.be.revertedWith("U384: not 768"); + }); + + it("should not revert when message is hashed using SHA-384", async () => { + const hashed384Message = + "0x576c1527e84521aac7ba48de1f22ac732e117c3cac1a51b5f44c0b2af3f281c2cccc51707d7a1b12b9085c24f00fd251"; + + expect(await ecdsa384.verifySECP384r1WithoutHashing(hashed384Message, signature, pubKey)).to.be.false; + }); + }); + + describe("_isOnCurve", () => { + const p = "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff"; + + it("should not verify if U384.eqInteger(x, 0) is true", async () => { + const modifiedPubKey = modifyLeft(pubKey, "0"); + + expect(await ecdsa384.verifySECP384r1(message, signature, modifiedPubKey)).to.be.false; + }); + + it("should not verify if U384.eqInteger(y, 0) is true", async () => { + const modifiedPubKey = modifyRight(pubKey, "0"); + + expect(await ecdsa384.verifySECP384r1(message, signature, modifiedPubKey)).to.be.false; + }); + + it("should not verify if U384.eq(x, p) is true", async () => { + const modifiedPubKey = modifyLeft(pubKey, p); + + expect(await ecdsa384.verifySECP384r1(message, signature, modifiedPubKey)).to.be.false; + }); + + it("should not verify if U384.eq(y, p) is true", async () => { + const modifiedPubKey = modifyRight(pubKey, p); + + expect(await ecdsa384.verifySECP384r1(message, signature, modifiedPubKey)).to.be.false; + }); + + it("should not revert if the a or b curve parameters are zero", async () => { + const zeroParameter = + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + + expect( + await ecdsa384.verifySECP384r1CustomCurveParameters(message, signature, pubKey, zeroParameter, zeroParameter), + ).to.be.false; + }); + }); + }); + + describe("brainpoolP384r1", () => { + const signature = + "0x42d803dcea3f9809cda4ce5a541d969dbeacd6ab7bef7788db1e4a00dac3ae87c1c241c24bb39e041725e607718fc322306b08967b56e4e49d7c9afc48833f580ac9b49cdcec0962d564f89a8f0b57a9742573ebcbe709869253e8b466cb33be"; + const pubKey = + "0x65f48a6a05d2a1a7f038a0dc909d572023e42e119638e48097436185f6f46b4007ca81ce970d679e41574ad47223e7820d560be7bbda5a7081f729c0f530ec4053f849add568bbf30047515327d264abaf867b35c87d24a1f8ed3dc039138c0a"; + const message = + "0x30820388a00302010202013d300a06082a8648ce3d0403023057310b3009060355040613024c5631243022060355040a0c1b4e6174696f6e616c20536563757269747920417574686f726974793114301206035504030c0b43534341204c6174766961310c300a06035504051303303038301e170d3230313030323039353130385a170d3331303130323130353130385a3059310b3009060355040613024c5631343032060355040a0c2b4f6666696365206f6620436974697a656e7368697020616e64204d6967726174696f6e20416666616972733114301206035504030c0b4453204c61747669612031308201333081ec06072a8648ce3d02013081e0020101302c06072a8648ce3d0101022100a9fb57dba1eea9bc3e660a909d838d726e3bf623d52620282013481d1f6e5377304404207d5a0975fc2c3057eef67530417affe7fb8055c126dc5c6ce94a4b44f330b5d9042026dc5c6ce94a4b44f330b5d9bbd77cbf958416295cf7e1ce6bccdc18ff8c07b60441048bd2aeb9cb7e57cb2c4b482ffc81b7afb9de27e1e3bd23c23a4453bd9ace3262547ef835c3dac4fd97f8461a14611dc9c27745132ded8e545c1d54c72f046997022100a9fb57dba1eea9bc3e660a909d838d718c397aa3b561a6f7901e0e82974856a702010103420004039499e285521446ae25b2a4bbd0b24bfbf0b2887cdc813b4b3e2e0537f91877741deacf543de2afa050d552ab8dc5c140043514b6bf4da3a3598c56282a6213a382016530820161301d0603551d0e04160414120ef5c34d19b3b3e25f61462a61ec1e8f1f6add302d0603551d1104263024a410300e310c300a06035504070c034c564181106e706b6440706d6c702e676f762e6c76300e0603551d0f0101ff040403020780301f0603551d23041830168014b72748d1e35062f7f6bd5b2df43eb8ccb8601ea03015060767810801010602040a30080201003103130150306d0603551d1f046630643030a02ea02c862a68747470733a2f2f706b64646f776e6c6f6164312e6963616f2e696e742f43524c732f4c56412e63726c3030a02ea02c862a68747470733a2f2f706b64646f776e6c6f6164322e6963616f2e696e742f43524c732f4c56412e63726c302d0603551d1204263024a410300e310c300a06035504070c034c564181106e706b6440706d6c702e676f762e6c76302b0603551d1004243022800f32303230313030323039353130385a810f32303231303130323130353130385a"; + + it("should verify the signature", async () => { + expect(await ecdsa384.verifyBrainpoolP384r1(message, signature, pubKey)).to.be.true; + }); + }); + + describe("U384", () => { + it("should compare correctly if aWord < bWord", async () => { + expect(await ecdsa384.cmpMock()).to.be.equal(-1); + }); + }); +}); diff --git a/test/libs/crypto/RSASSAPSS.test.ts b/test/libs/crypto/RSASSAPSS.test.ts new file mode 100644 index 00000000..60e842fc --- /dev/null +++ b/test/libs/crypto/RSASSAPSS.test.ts @@ -0,0 +1,63 @@ +import { ethers } from "hardhat"; +import { expect } from "chai"; +import { Reverter } from "@/test/helpers/reverter"; + +import { RSASSAPSSMock } from "@ethers-v6"; +import { randomBytes } from "ethers"; + +describe("RSASSAPSS", () => { + const reverter = new Reverter(); + + let rsassapss: RSASSAPSSMock; + + before(async () => { + const RSASSAPSSMock = await ethers.getContractFactory("RSASSAPSSMock"); + + rsassapss = await RSASSAPSSMock.deploy(); + + await reverter.snapshot(); + }); + + afterEach(reverter.revert); + + describe("SHA256", () => { + const message = + "0x308203c3a003020102020874442b6b708ef7a2304106092a864886f70d01010a3034a00f300d06096086480165030402010500a11c301a06092a864886f70d010108300d06096086480165030402010500a203020120302f310b3009060355040613025048310c300a060355040a0c034446413112301006035504030c09435343413031303037301e170d3232313231323136303030305a170d3333303430313135353935395a302d310b3009060355040613025048310c300a060355040a13034446413110300e060355040313074453303131313330820122300d06092a864886f70d01010105000382010f003082010a0282010100ab630b320a41ecf8886a904ab50fabcfad658f5af8a9f8aaecefe0dc5e2ea99eba3deccba3f58885f8574fe0ad5c889763afc2b68e66b5928403d508724ad1e7fd05c573c053e04660fd31128cff2e2f574ec92430202f5dafa6df66b46fb16ece1372424d3aa3b975428c59f18fe1f32e6c328b64f58f95e05684dfff2d21a85cb73bcb32ac172c8f782fa2ea942118379833bec37cab64de493ddae79014ed0e6fcaa2ca4cdc3bdb0442ba550cde8355194c3c3934b2d8bfa513fcf5788c0569e0527cd20daa5e8e114204661a3d1f21650d01703e7a112602cf8fbefbc329afc18d3d49a68b60e5c89c5152ad6e7f0480b0e4157b26640c569ae477e04f190203010001a38201c7308201c3300e0603551d0f0101ff040403020780301d0603551d0e04160414363257ff5b20debaa6e26c257d3ebb4fa1e7bcf5305e0603551d23045730558014a1436db84f1c134e49b387da56cee801102d4f73a133a431302f310b3009060355040613025048310c300a060355040a0c034446413112301006035504030c0943534341303130303782086b8a5f2f46fa934b302b0603551d1004243022800f32303232313231323136303030305a810f32303233303430313135353935395a30390603551d1104323030811c70617373706f72742e6469726563746f72406466612e676f762e7068a410300e310c300a06035504070c0350484c30390603551d1204323030811c70617373706f72742e6469726563746f72406466612e676f762e7068a410300e310c300a06035504070c0350484c306d0603551d1f046630643030a02ea02c862a68747470733a2f2f706b64646f776e6c6f6164312e6963616f2e696e742f43524c732f50484c2e63726c3030a02ea02c862a68747470733a2f2f706b64646f776e6c6f6164322e6963616f2e696e742f43524c732f50484c2e63726c3020060767810801010602041530131301501302504f130250441302505213025053"; + const s = + "0x875ef62f6832599f41b50ca51a478c92ff47b61f2090157f64b425b1e1ad5612e6abb7d5808d9be5f0eaaa16d2b516ef161534c78d542ffd659107535c2bab643163fb9af27a50389792508d1cdbda347a103404c5e08d2d97c7935994631d42fe7e0caa892dc3ec39d3ac94dbccb3cd0870b21b9c836feed5bc32e9ec6830392bdade1fc9b5280fbaa2ceaa78d9524af3d015cbaf07eebc84a9caec81a4407452573a101b79772056193d207a8398690ed0dd0cc5a6410fd844d313c50934d6e1d556f8e7b39b12525f3cd766c9342fbd892e40408b0c232d888da11fc64d0f09db70971d395d7a1d2aacfe9da78e3c46ce43b3ce5b9fc1e6a90c065cdafa2e8a117d63c00cf9f54e3a3313789f03dd7efc76641c2cf5068ca4512c82fa6c62f6bd36b12523dc46f444b8312d2f6e6ec22cd10eddb19220d9b8ba4cc442dd836335482c6309d56e87492d2fdaefdb7b5ede566ed43eb87955451225846ce2535b803a9ca79034cc3aa41307cc57f0962cb8b2c3b99a5c87150387f7d8de6a18f6a838404c4aa5bb279378fb285c096d4c2664c700ac4e3c0cb44f920928e764dd4b10f22d3cb5bdfac78066b1b0a5ae75528e447b262510d41150a94ab0f645cc61ae99a3719bd29cf3901dde6de7cc162051f34c642a0f7854ef00d4143d755ad72bc71371c3a8dedb94118272f37f853bd171743b0c7a9a8cb96095476c9c"; + const e = ethers.toBeHex(65537n); + const n = + "0xd24081be6cc14fe3fb4b35ab6df1a7f28f373017ef15a26b67ff2dd04773a3ef8942b7ba5f2f91aea469fe757e2e3362a907610b441f3610f528b1f39739a132c4bebc26c37d25b6d12481336fecddfc6bdcd011be4f2912ff0663cb70d9938280813dd3f32f2e6fef184881f784bbd2fd2d165b169d8594c45d832dbaebfdcb532d6542b57413825df5164b577dcdc248dfc4a8b071eb0bef021128e9172b77a18a5a6b00ebc0e07af0a9df6592684805a4ba0db00dddaabf793641ec0da51aecc6160acd56a0194d1161271b8feaaf0ae851ae65f1464c79607bbb237de3dbd0a299e9cda846c362976108d10555b57866a56c87c6a5d92d1888d6260fa90459afb14688b8a53921d2d477d1677518956412e01eb2592b27ad62a3d3a50777c4bee3f348b4788bb7bbd38d6bd902968a7b3640d75f98b78824ee9462e1b2d405b8c1ce7d7dbef479c2979553790b7d7fa8a6f05dad4cf95b92a218b410eb5b9df712d099b6952a07f122d21e95b934e9a765e758397b191fd01c0c4ae669039a0d7003308ab78d03809752bb7b676c3f3bbd9ac4b8cf0162efb50e01c52e3a97fed31d1e73f12b3da7b4df87fd7e93f70f92dc154d0bcef5a39c9631b2b50c7c7b91f4a63d4e3d487c37fc63bbdf87b71cad5581409661d15f77ff738a4d53d23424d999490607ebfd8293b2fb5c269cd8a19477ca88f6f7cb1abe00fb16c9"; + + it("should verify signature", async () => { + expect(await rsassapss.verifySha256(message, s, e, n)).to.be.true; + }); + + it("should not verify invalid signature", async () => { + const message = "0x0123456789"; + + expect(await rsassapss.verifySha256(message, s, e, n)).to.be.false; + }); + + it("should not verify if s, e or n parameters length is equal to zero", async () => { + const zeroParameter = "0x"; + + expect(await rsassapss.verifySha256(message, zeroParameter, e, n)).to.be.false; + expect(await rsassapss.verifySha256(message, s, zeroParameter, n)).to.be.false; + expect(await rsassapss.verifySha256(message, s, e, zeroParameter)).to.be.false; + }); + + it("should not verify if signature_[sigBytes_ - 1] != hex`BC`", async () => { + const zeroParameter = + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + + expect(await rsassapss.verifySha256(message, zeroParameter, e, n)).to.be.false; + }); + + it("should not verify if sigBytes_ < hashLength_ + saltLength_ + 2", async () => { + const n = "0x" + Buffer.from(randomBytes(64)).toString("hex"); + + expect(await rsassapss.verifySha256(message, s, e, n)).to.be.false; + }); + }); +});