Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement P256 verification via RIP-7212 precompile with Solidity fallback #4881

Merged
merged 82 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from 76 commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
5e82076
Add P256 implementation and testing
Amxx Feb 7, 2024
da0f27e
enable optimizations by default
Amxx Feb 7, 2024
aa59c67
test recovering address
Amxx Feb 7, 2024
9512947
improved testing
Amxx Feb 7, 2024
a60bf48
spelling
Amxx Feb 7, 2024
9185026
fix lint
Amxx Feb 7, 2024
025e360
expose imports tick
Amxx Feb 7, 2024
803e735
fix lint
Amxx Feb 7, 2024
57fcecd
fix lint
Amxx Feb 7, 2024
4dae298
add changeset
Amxx Feb 7, 2024
6cf039d
improve doc
Amxx Feb 7, 2024
c094fa1
add envvar to force allowUnlimitedContractSize
Amxx Feb 7, 2024
20a03df
fix lint
Amxx Feb 7, 2024
15f1a6b
fix stack too deep error in coverage
Amxx Feb 7, 2024
e2040e4
reoder arguments to match ecrecover and EIP-7212
Amxx Feb 13, 2024
695b732
reduce diff
Amxx Mar 13, 2024
41aaf71
Merge branch 'master' into feature/P256
Amxx Mar 13, 2024
3bf4557
Update contracts/utils/cryptography/P256.sol
Amxx Apr 24, 2024
3cbf426
Merge branch 'master' into feature/P256
Amxx Apr 25, 2024
bba7fa3
update pseudocode reference
Amxx Apr 25, 2024
2812ed8
Update contracts/utils/cryptography/P256.sol
Amxx Apr 25, 2024
e0ef63b
refactor neutral element in jAdd
Amxx Apr 26, 2024
61a244d
add EIP-7212 support
Amxx May 17, 2024
910bc71
Merge branch 'master' into feature/P256
Amxx Jun 12, 2024
2e9d04d
Apply PR suggestions
ernestognw Jun 14, 2024
9062633
move invModPrime to Math.sol
Amxx Jun 17, 2024
a44bb71
update
Amxx Jun 17, 2024
3a6e1f5
update
Amxx Jun 17, 2024
3e71fad
codespell
Amxx Jun 17, 2024
887272b
test signature maleability
Amxx Jun 17, 2024
433548f
Iterate
ernestognw Jun 20, 2024
4f80ca0
Add more comments
ernestognw Jun 21, 2024
be69f5c
remove P256 public key to address derivation
Amxx Jun 21, 2024
fcde23f
Move publicKey from privateKey derivation function to tests
ernestognw Jun 21, 2024
5828566
Remove unnecessary test
ernestognw Jun 21, 2024
9362936
add wycheproof test
cairoeth Jun 21, 2024
921745b
Readd malleability check and rename
ernestognw Jun 22, 2024
2c113f4
Change arguments to bytes32
ernestognw Jun 22, 2024
fb7dc6f
remove unused malleable version
Amxx Jun 24, 2024
f264dae
Update contracts/utils/cryptography/P256.sol
Amxx Jun 24, 2024
2c9a137
Update contracts/utils/cryptography/P256.sol
Amxx Jun 24, 2024
f4cbf51
up
Amxx Jun 24, 2024
0227656
recovery malleability
Amxx Jun 24, 2024
e3a8338
fix bug (inverse return values)
Amxx Jun 24, 2024
cbd2ff5
better private key gen
cairoeth Jun 24, 2024
194f19a
Update contracts/utils/cryptography/P256.sol
ernestognw Jun 24, 2024
704a12e
Fix hardhat tests and add documentation
ernestognw Jun 24, 2024
61d52a5
Update test/utils/cryptography/P256.test.js
Amxx Jun 24, 2024
242c796
Ensure lower s in Foundry tests
ernestognw Jun 24, 2024
787834d
Lint
ernestognw Jun 24, 2024
d8f4f7e
fix bug for valid signatures with large `r` values
cairoeth Jun 24, 2024
fc54017
run original wycheproof in hardhat
Amxx Jun 24, 2024
5a7887b
Merge remote-tracking branch 'amxx/feature/P256' into feature/P256
Amxx Jun 24, 2024
cc82c17
Update test/utils/cryptography/P256.test.js
Amxx Jun 24, 2024
a67e5a2
Almost fix tests
ernestognw Jun 24, 2024
4c93009
Bound r to N so for lower s values
ernestognw Jun 24, 2024
046463c
Remove unnecessary comment
ernestognw Jun 24, 2024
e4df1d1
Remove foundry wycheproof
ernestognw Jun 24, 2024
1bddcf5
Tests nit
ernestognw Jun 24, 2024
e5ba358
Update .changeset/odd-lobsters-wash.md
ernestognw Jun 24, 2024
2eecacf
Update test/utils/cryptography/P256.t.sol
ernestognw Jun 24, 2024
ced4fb8
Update P256.t.sol
Amxx Jun 24, 2024
b82af11
Merge branch 'master' into feature/P256
ernestognw Jun 24, 2024
c6a86d9
Add more docs and nit
ernestognw Jun 24, 2024
9b24014
Manage to compile without via-ir
ernestognw Jun 25, 2024
3616771
Improve comments
ernestognw Jun 25, 2024
be078b1
Remove unnecessary CI flag
ernestognw Jun 25, 2024
ecd3aa2
cleanup _jAdd with memory
Amxx Jun 25, 2024
d83e707
up
Amxx Jun 25, 2024
fbc11f5
Update contracts/utils/cryptography/P256.sol
Amxx Jun 25, 2024
9c88101
Apply suggestions from code review
Amxx Jun 25, 2024
b5e6bd7
Update hardhat.config.js
Amxx Jun 25, 2024
db76353
Update hardhat.config.js
Amxx Jun 25, 2024
0722d93
Update hardhat.config.js
Amxx Jun 25, 2024
306a5f6
Revert all changes to hardhat.config.js
Amxx Jun 26, 2024
e67a456
uniform style
Amxx Jun 26, 2024
1a8cb63
add bound checks to isOnCurve
Amxx Jun 26, 2024
3c3fa27
rename isOnCurve -> isValidPublicKey + add _isProperSignature helper
Amxx Jun 26, 2024
2fe4a16
Update contracts/utils/cryptography/P256.sol
ernestognw Jun 27, 2024
2420d13
Update contracts/utils/cryptography/P256.sol
ernestognw Jul 1, 2024
49f3ad9
Merge branch 'master' into feature/P256
ernestognw Jul 2, 2024
5314727
Enable --ir-minimum in forge coverage
ernestognw Jul 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/odd-lobsters-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`P256`: Library for verification and public key recovery of P256 (aka secp256r1) signatures.
1 change: 1 addition & 0 deletions contracts/mocks/Stateless.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {ERC721Holder} from "../token/ERC721/utils/ERC721Holder.sol";
import {Math} from "../utils/math/Math.sol";
import {MerkleProof} from "../utils/cryptography/MerkleProof.sol";
import {MessageHashUtils} from "../utils/cryptography/MessageHashUtils.sol";
import {P256} from "../utils/cryptography/P256.sol";
import {Panic} from "../utils/Panic.sol";
import {Packing} from "../utils/Packing.sol";
import {RSA} from "../utils/cryptography/RSA.sol";
Expand Down
5 changes: 5 additions & 0 deletions contracts/utils/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ library Errors {
* @dev The deployment failed.
*/
error FailedDeployment();

/**
* @dev A necessary precompile is missing.
*/
error MissingPrecompile(address);
}
2 changes: 2 additions & 0 deletions contracts/utils/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Miscellaneous contracts and libraries containing utility functions you can use t
* {Math}, {SignedMath}: Implementation of various arithmetic functions.
* {SafeCast}: Checked downcasting functions to avoid silent truncation.
* {ECDSA}, {MessageHashUtils}: Libraries for interacting with ECDSA signatures.
* {P256}: Library for verifying and recovering public keys from secp256r1 signatures.
* {RSA}: Library with RSA PKCS#1 v1.5 signature verification utilities.
* {SignatureChecker}: A library helper to support regular ECDSA from EOAs as well as ERC-1271 signatures for smart contracts.
* {Hashes}: Commonly used hash functions.
* {MerkleProof}: Functions for verifying https://en.wikipedia.org/wiki/Merkle_tree[Merkle Tree] proofs.
Expand Down
304 changes: 304 additions & 0 deletions contracts/utils/cryptography/P256.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Math} from "../math/Math.sol";
import {Errors} from "../Errors.sol";

/**
* @dev Implementation of secp256r1 verification and recovery functions.
*
* The secp256r1 curve (also known as P256) is a NIST standard curve with wide support in modern devices
* and cryptographic standards. Some notable examples include Apple's Secure Enclave and Android's Keystore
* as well as authentication protocols like FIDO2.
*
* Based on the original https://github.com/itsobvioustech/aa-passkeys-wallet/blob/main/src/Secp256r1.sol[implementation of itsobvioustech].
* Heavily inspired in https://github.com/maxrobot/elliptic-solidity/blob/master/contracts/Secp256r1.sol[maxrobot] and
* https://github.com/tdrerup/elliptic-curve-solidity/blob/master/contracts/curves/EllipticCurve.sol[tdrerup] implementations.
*/
library P256 {
struct JPoint {
uint256 x;
uint256 y;
uint256 z;
}

/// @dev Generator (x component)
uint256 internal constant GX = 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296;
/// @dev Generator (y component)
uint256 internal constant GY = 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5;
/// @dev P (size of the field)
uint256 internal constant P = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF;
/// @dev N (order of G)
uint256 internal constant N = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551;
/// @dev A parameter of the weierstrass equation
uint256 internal constant A = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC;
/// @dev B parameter of the weierstrass equation
uint256 internal constant B = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B;
ernestognw marked this conversation as resolved.
Show resolved Hide resolved

/// @dev (P + 1) / 4. Useful to compute sqrt
uint256 private constant P1DIV4 = 0x3fffffffc0000000400000000000000000000000400000000000000000000000;

/// @dev N/2 for excluding higher order `s` values
uint256 private constant HALF_N = 0x7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a8;

/**
* @dev Verifies a secp256r1 signature using the RIP-7212 precompile and falls back to the Solidity implementation
* if the precompile is not available. This version should work on all chains, but requires the deployment of more
* bytecode.
*
* @param h - hashed message
* @param r - signature half R
* @param s - signature half S
* @param qx - public key coordinate X
* @param qy - public key coordinate Y
*
* IMPORTANT: This function disallows signatures where the `s` value is above `N/2` to prevent malleability.
* To flip the `s` value, compute `s = N - s`.
*/
function verify(bytes32 h, bytes32 r, bytes32 s, bytes32 qx, bytes32 qy) internal view returns (bool) {
(bool valid, bool supported) = _tryVerifyNative(h, r, s, qx, qy);
return supported ? valid : verifySolidity(h, r, s, qx, qy);
}

/**
* @dev Same as {verify}, but it will revert if the required precompile is not available.
ernestognw marked this conversation as resolved.
Show resolved Hide resolved
*/
function verifyNative(bytes32 h, bytes32 r, bytes32 s, bytes32 qx, bytes32 qy) internal view returns (bool) {
(bool valid, bool supported) = _tryVerifyNative(h, r, s, qx, qy);
if (supported) {
return valid;
} else {
revert Errors.MissingPrecompile(address(0x100));
}
}

/**
* @dev Same as {verify}, but it will return false if the required precompile is not available.
*/
function _tryVerifyNative(
bytes32 h,
bytes32 r,
bytes32 s,
bytes32 qx,
bytes32 qy
) private view returns (bool valid, bool supported) {
if (r == 0 || uint256(r) >= N || s == 0 || uint256(s) > HALF_N || !isOnCurve(qx, qy)) {
return (false, true); // signature is invalid, and its not because the precompile is missing
}

(bool success, bytes memory returndata) = address(0x100).staticcall(abi.encode(h, r, s, qx, qy));
ernestognw marked this conversation as resolved.
Show resolved Hide resolved
ernestognw marked this conversation as resolved.
Show resolved Hide resolved
return (success && returndata.length == 0x20) ? (abi.decode(returndata, (bool)), true) : (false, false);
ernestognw marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @dev Same as {verify}, but only the Solidity implementation is used.
*/
function verifySolidity(bytes32 h, bytes32 r, bytes32 s, bytes32 qx, bytes32 qy) internal view returns (bool) {
if (r == 0 || uint256(r) >= N || s == 0 || uint256(s) > HALF_N || !isOnCurve(qx, qy)) {
return false;
}
cairoeth marked this conversation as resolved.
Show resolved Hide resolved

JPoint[16] memory points = _preComputeJacobianPoints(uint256(qx), uint256(qy));
uint256 w = Math.invModPrime(uint256(s), N);
uint256 u1 = mulmod(uint256(h), w, N);
uint256 u2 = mulmod(uint256(r), w, N);
(uint256 x, ) = _jMultShamir(points, u1, u2);
return ((x % N) == uint256(r));
}

/**
* @dev Public key recovery
*
* @param h - hashed message
* @param v - signature recovery param
* @param r - signature half R
* @param s - signature half S
*
* IMPORTANT: This function disallows signatures where the `s` value is above `N/2` to prevent malleability.
* To flip the `s` value, compute `s = N - s` and `v = 1 - v` if (`v = 0 | 1`).
*/
function recovery(bytes32 h, uint8 v, bytes32 r, bytes32 s) internal view returns (bytes32, bytes32) {
if (r == 0 || uint256(r) >= N || s == 0 || uint256(s) > HALF_N || v > 1) {
return (0, 0);
}

uint256 rx = uint256(r);
uint256 ry2 = addmod(mulmod(addmod(mulmod(rx, rx, P), A, P), rx, P), B, P); // weierstrass equation y² = x³ + a.x + b
uint256 ry = Math.modExp(ry2, P1DIV4, P); // This formula for sqrt work because P ≡ 3 (mod 4)
if (mulmod(ry, ry, P) != ry2) return (0, 0); // Sanity check
if (ry % 2 != v % 2) ry = P - ry;

JPoint[16] memory points = _preComputeJacobianPoints(rx, ry);
uint256 w = Math.invModPrime(uint256(r), N);
uint256 u1 = mulmod(N - (uint256(h) % N), w, N);
uint256 u2 = mulmod(uint256(s), w, N);
(uint256 x, uint256 y) = _jMultShamir(points, u1, u2);
return (bytes32(x), bytes32(y));
}

/**
* @dev Checks if a point is on the curve.
*/
function isOnCurve(bytes32 x, bytes32 y) internal pure returns (bool result) {
assembly ("memory-safe") {
let p := P
let lhs := mulmod(y, y, p) // y^2
let rhs := addmod(mulmod(addmod(mulmod(x, x, p), A, p), x, p), B, p) // ((x^2 + a) * x) + b = x^3 + ax + b
result := eq(lhs, rhs) // Should conform with the Weierstrass equation
}
}

/**
* @dev Reduce from jacobian to affine coordinates
* @param jx - jacobian coordinate x
* @param jy - jacobian coordinate y
* @param jz - jacobian coordinate z
* @return ax - affine coordinate x
* @return ay - affine coordinate y
*/
function _affineFromJacobian(uint256 jx, uint256 jy, uint256 jz) private view returns (uint256 ax, uint256 ay) {
if (jz == 0) return (0, 0);
uint256 zinv = Math.invModPrime(jz, P);
uint256 zzinv = mulmod(zinv, zinv, P);
uint256 zzzinv = mulmod(zzinv, zinv, P);
ax = mulmod(jx, zzinv, P);
ay = mulmod(jy, zzzinv, P);
}

/**
* @dev Point addition on the jacobian coordinates
* Reference: https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-add-1998-cmo-2
*/
function _jAdd(
JPoint memory p1,
uint256 x2,
uint256 y2,
uint256 z2
) private pure returns (uint256 rx, uint256 ry, uint256 rz) {
assembly ("memory-safe") {
let p := P
let z1 := mload(add(p1, 0x40))
let s1 := mulmod(mload(add(p1, 0x20)), mulmod(mulmod(z2, z2, p), z2, p), p) // s1 = y1*z2³
let s2 := mulmod(y2, mulmod(mulmod(z1, z1, p), z1, p), p) // s2 = y2*z1³
let r := addmod(s2, sub(p, s1), p) // r = s2-s1
let u1 := mulmod(mload(p1), mulmod(z2, z2, p), p) // u1 = x1*z2²
let u2 := mulmod(x2, mulmod(z1, z1, p), p) // u2 = x2*z1²
let h := addmod(u2, sub(p, u1), p) // h = u2-u1
ernestognw marked this conversation as resolved.
Show resolved Hide resolved
let hh := mulmod(h, h, p) // h²

// x' = r²-h³-2*u1*h²
rx := addmod(
addmod(mulmod(r, r, p), sub(p, mulmod(h, hh, p)), p),
sub(p, mulmod(2, mulmod(u1, hh, p), p)),
p
)
// y' = r*(u1*h²-x')-s1*h³
ry := addmod(
mulmod(r, addmod(mulmod(u1, hh, p), sub(p, rx), p), p),
sub(p, mulmod(s1, mulmod(h, hh, p), p)),
p
)
// z' = h*z1*z2
rz := mulmod(h, mulmod(z1, z2, p), p)
}
}

/**
* @dev Point doubling on the jacobian coordinates
* Reference: https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-1998-cmo-2
*/
function _jDouble(uint256 x, uint256 y, uint256 z) private pure returns (uint256 rx, uint256 ry, uint256 rz) {
assembly ("memory-safe") {
let p := P
let yy := mulmod(y, y, p)
ernestognw marked this conversation as resolved.
Show resolved Hide resolved
let zz := mulmod(z, z, p)
let s := mulmod(4, mulmod(x, yy, p), p) // s = 4*x*y²
let m := addmod(mulmod(3, mulmod(x, x, p), p), mulmod(A, mulmod(zz, zz, p), p), p) // m = 3*x²+a*z⁴
let t := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p) // t = m²-2*s

// x' = t
rx := t
// y' = m*(s-t)-8*y⁴
ry := addmod(mulmod(m, addmod(s, sub(p, t), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p)
// z' = 2*y*z
rz := mulmod(2, mulmod(y, z, p), p)
}
}

/**
* @dev Compute P·u1 + Q·u2 using the precomputed points for P and Q (see {_preComputeJacobianPoints}).
*
* Uses Strauss Shamir trick for EC multiplication
* https://stackoverflow.com/questions/50993471/ec-scalar-multiplication-with-strauss-shamir-method
* we optimise on this a bit to do with 2 bits at a time rather than a single bit
* the individual points for a single pass are precomputed
* overall this reduces the number of additions while keeping the same number of doublings
*/
function _jMultShamir(JPoint[16] memory points, uint256 u1, uint256 u2) private view returns (uint256, uint256) {
uint256 x = 0;
uint256 y = 0;
uint256 z = 0;
unchecked {
for (uint256 i = 0; i < 128; ++i) {
if (z > 0) {
(x, y, z) = _jDouble(x, y, z);
(x, y, z) = _jDouble(x, y, z);
}
// Read 2 bits of u1, and 2 bits of u2. Combining the two give a lookup index in the table.
uint256 pos = ((u1 >> 252) & 0xc) | ((u2 >> 254) & 0x3);
if (pos > 0) {
if (z == 0) {
(x, y, z) = (points[pos].x, points[pos].y, points[pos].z);
} else {
(x, y, z) = _jAdd(points[pos], x, y, z);
}
}
u1 <<= 2;
u2 <<= 2;
}
}
return _affineFromJacobian(x, y, z);
}

/**
* @dev Precompute a matrice of useful jacobian points associated with a given P. This can be seen as a 4x4 matrix
* that contains combination of P and G (generator) up to 3 times each. See the table below:
*
* ┌────┬─────────────────────┐
* │ i │ 0 1 2 3 │
* ├────┼─────────────────────┤
* │ 0 │ 0 p 2p 3p │
* │ 4 │ g g+p g+2p g+3p │
* │ 8 │ 2g 2g+p 2g+2p 2g+3p │
* │ 12 │ 3g 3g+p 3g+2p 3g+3p │
* └────┴─────────────────────┘
*/
function _preComputeJacobianPoints(uint256 px, uint256 py) private pure returns (JPoint[16] memory points) {
points[0x00] = JPoint(0, 0, 0); // 0,0
points[0x01] = JPoint(px, py, 1); // 1,0 (p)
points[0x04] = JPoint(GX, GY, 1); // 0,1 (g)
points[0x02] = _jDoublePoint(points[0x01]); // 2,0 (2p)
points[0x08] = _jDoublePoint(points[0x04]); // 0,2 (2g)
points[0x03] = _jAddPoint(points[0x01], points[0x02]); // 3,0 (3p)
points[0x05] = _jAddPoint(points[0x01], points[0x04]); // 1,1 (p+g)
points[0x06] = _jAddPoint(points[0x02], points[0x04]); // 2,1 (2p+g)
points[0x07] = _jAddPoint(points[0x03], points[0x04]); // 3,1 (3p+g)
points[0x09] = _jAddPoint(points[0x01], points[0x08]); // 1,2 (p+2g)
points[0x0a] = _jAddPoint(points[0x02], points[0x08]); // 2,2 (2p+2g)
points[0x0b] = _jAddPoint(points[0x03], points[0x08]); // 3,2 (3p+2g)
points[0x0c] = _jAddPoint(points[0x04], points[0x08]); // 0,3 (g+2g)
points[0x0d] = _jAddPoint(points[0x01], points[0x0c]); // 1,3 (p+3g)
points[0x0e] = _jAddPoint(points[0x02], points[0x0c]); // 2,3 (2p+3g)
points[0x0f] = _jAddPoint(points[0x03], points[0x0C]); // 3,3 (3p+3g)
}

function _jAddPoint(JPoint memory p1, JPoint memory p2) private pure returns (JPoint memory) {
(uint256 x, uint256 y, uint256 z) = _jAdd(p1, p2.x, p2.y, p2.z);
return JPoint(x, y, z);
}

function _jDoublePoint(JPoint memory p) private pure returns (JPoint memory) {
(uint256 x, uint256 y, uint256 z) = _jDouble(p.x, p.y, p.z);
return JPoint(x, y, z);
}
}
19 changes: 17 additions & 2 deletions contracts/utils/math/Math.sol
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,8 @@ library Math {
*
* If the input value is not inversible, 0 is returned.
*
* NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Ferma's little theorem and get the
* inverse using `Math.modExp(a, n - 2, n)`.
* NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the
* inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.
*/
function invMod(uint256 a, uint256 n) internal pure returns (uint256) {
unchecked {
Expand Down Expand Up @@ -288,6 +288,21 @@ library Math {
}
}

/**
* @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.
*
* From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is
* prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that
* `a**(p-2)` is the modular multiplicative inverse of a in Fp.
*
* NOTE: this function does NOT check that `p` is a prime greater than `2`.
*/
function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {
unchecked {
return Math.modExp(a, p - 2, p);
}
}

/**
* @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)
*
Expand Down
Loading