Skip to content

Commit

Permalink
N-03: fix documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
dcposch committed Jul 30, 2024
1 parent 7178382 commit e35ffc1
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 55 deletions.
58 changes: 36 additions & 22 deletions src/P256.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,41 @@ pragma solidity 0.8.21;
* @custom:security-contact [email protected]
**/
library P256 {
/// Address of the RIP-7212 precompile
address public constant PRECOMPILE = address(0x100);

/// Address of the fallback P256Verifier contract
address public constant VERIFIER =
0xc2b78104907F722DABAc4C69f826a522B2754De4;

/// P256 curve order n/2 for malleability check
uint256 constant P256_N_DIV_2 =
57896044605178124381348723474703786764998477612067880171211129530534256022184;

/**
* @dev Verifies a P256 signature. Costs ~3k gas for a valid signature on a
* on a chain with RIP-7212, ~300k otherwise. Treats malleable (s > n/2)
* signatures as invalid.
*/
function verifySignature(
bytes32 message_hash,
uint256 r,
uint256 s,
uint256 x,
uint256 y
) internal view returns (bool) {
// check for signature malleability
if (s > P256_N_DIV_2) {
return false;
}

return verifySignatureAllowMalleability(message_hash, r, s, x, y);
}

/**
* @dev Verifies a P256 signature. Treats malleable (s > n/2) signatures as
* valid, matching the behavior specified by NIST and RIP-7212 exactly.
*/
function verifySignatureAllowMalleability(
bytes32 message_hash,
uint256 r,
Expand All @@ -23,36 +54,19 @@ library P256 {

(bool success, bytes memory ret) = PRECOMPILE.staticcall(args);
if (success && ret.length > 0) {
// RIP-7212 precompile returns 1 if signature is valid
// and nothing if signature is invalid, so those fall back to
// more expensive Solidity implementation.
// RIP-7212 precompile returns 1 if signature is valid.
return abi.decode(ret, (uint256)) == 1;
}

// RIP-7212 is flawed in that it returns no data for an invalid
// signature. This means that "invalid signature" and "missing
// precompile" are not distguishable: both fall back to the more
// expensive Solidity implementation.
(bool fallbackSuccess, bytes memory fallbackRet) = VERIFIER.staticcall(
args
);
assert(fallbackSuccess); // never reverts, always returns 0 or 1

return abi.decode(fallbackRet, (uint256)) == 1;
}

/// P256 curve order n/2 for malleability check
uint256 constant P256_N_DIV_2 =
57896044605178124381348723474703786764998477612067880171211129530534256022184;

function verifySignature(
bytes32 message_hash,
uint256 r,
uint256 s,
uint256 x,
uint256 y
) internal view returns (bool) {
// check for signature malleability
if (s > P256_N_DIV_2) {
return false;
}

return verifySignatureAllowMalleability(message_hash, r, s, x, y);
}
}
40 changes: 17 additions & 23 deletions src/P256Verifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,28 +40,29 @@ contract P256Verifier {
return abi.encodePacked(ret);
}

// Parameters for the sec256r1 (P256) elliptic curve
// Curve prime field modulus
// Parameters for the secp256r1 (P256) elliptic curve
/// P256 curve prime field modulus
uint256 private constant p =
0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF;
// Short weierstrass first coefficient
/// Short weierstrass first coefficient
uint256 private constant a = // The assumption a == -3 (mod p) is used throughout the codebase
0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC;
// Short weierstrass second coefficient
/// Short weierstrass second coefficient
uint256 private constant b =
0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B;
// Generating point affine coordinates
/// Generating point affine x-coordinate
uint256 private constant GX =
0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296;
/// Generating point affine y-coordinate
uint256 private constant GY =
0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5;
// Curve order (number of points)
/// Curve order (number of points)
uint256 private constant n =
0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551;
// -2 mod p constant, used to speed up inversion and doubling (avoid negation)
/// -2 mod p constant, used to speed up inversion and doubling (avoid negation)
uint256 private constant minus_2modp =
0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFD;
// -2 mod n constant, used to speed up inversion
/// -2 mod n constant, used to speed up inversion
uint256 private constant minus_2modn =
0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63254F;

Expand Down Expand Up @@ -98,8 +99,8 @@ contract P256Verifier {
}

/**
* @dev Check if a point in affine coordinates is on the curve
* Reject 0 point at infinity.
* @dev Check if a point in affine coordinates is on the curve.
* Reject the 0 point at infinity.
*/
function ecAff_isValidPubkey(
uint256 x,
Expand All @@ -109,13 +110,6 @@ contract P256Verifier {
return false;
}

return ecAff_satisfiesCurveEqn(x, y);
}

function ecAff_satisfiesCurveEqn(
uint256 x,
uint256 y
) internal pure returns (bool) {
uint256 LHS = mulmod(y, y, p); // y^2
uint256 RHS = addmod(mulmod(mulmod(x, x, p), x, p), mulmod(a, x, p), p); // x^3 + a x
RHS = addmod(RHS, b, p); // x^3 + a*x + b
Expand All @@ -124,9 +118,10 @@ contract P256Verifier {
}

/**
* @dev Computation of uG + vQ using Strauss-Shamir's trick, G basepoint, Q public key
* returns tuple of (x coordinate of uG + vQ, boolean that is false if internal precompile staticcall fail)
* Strauss-Shamir is described well in https://stackoverflow.com/a/50994362
* @dev Computation of uG + vQ using Strauss-Shamir's trick, G basepoint,
* Q public key. Strauss-Shamir is described well in the following post:
* https://stackoverflow.com/a/50994362
* @return X The x-coordinate of uG + vQ
*/
function ecZZ_mulmuladd(
uint256 QX,
Expand Down Expand Up @@ -243,16 +238,15 @@ contract P256Verifier {
}

/**
* @dev Check if a point is the infinity point in ZZ rep.
* Assumes point is on the EC or is the point at infinity.
* @dev Checks if a point is the infinity point in ZZ rep, using only the zz
* and zzz coordinates. Assumes the point is on the curve or at infinity.
*/
function ecZZ_IsInf(
uint256 zz,
uint256 zzz
) internal pure returns (bool flag) {
// invariant((zz == 0 && zzz == 0) || ecAff_isOnCurve(x, y) for affine
// form of the point)

return (zz == 0 && zzz == 0);
}

Expand Down
28 changes: 18 additions & 10 deletions src/WebAuthn.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import "./P256.sol";
* @custom:security-contact [email protected]
*/
library WebAuthn {
/// Checks whether prefix occurs in the beginning of str.
/**
* @dev Checks whether prefix occurs in the beginning of str.
*/
function startsWith(
string memory prefix,
string memory str
Expand All @@ -33,14 +35,20 @@ library WebAuthn {
return true;
}

bytes1 private constant AUTH_DATA_FLAGS_UP = 0x01; // Bit 0
bytes1 private constant AUTH_DATA_FLAGS_UV = 0x04; // Bit 2
bytes1 private constant AUTH_DATA_FLAGS_BE = 0x08; // Bit 3
bytes1 private constant AUTH_DATA_FLAGS_BS = 0x10; // Bit 4
/// Bit mask: authData bit 0, indicating user presence
bytes1 private constant AUTH_DATA_FLAGS_UP = 0x01;
/// Bit mask: authData bit 2, indicating user verification
bytes1 private constant AUTH_DATA_FLAGS_UV = 0x04;
/// Bit mask: authData bit 3, indicating backup eligibility
bytes1 private constant AUTH_DATA_FLAGS_BE = 0x08;
/// Bit mask: authData bit 4, indicating backup state
bytes1 private constant AUTH_DATA_FLAGS_BS = 0x10;

/// Verifies the authFlags in authenticatorData. Numbers in inline comment
/// correspond to the same numbered bullets in
/// https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion.
/**
* @dev Verifies the authFlags in authenticatorData. Numbers in inline
* comment correspond to the same numbered bullets in the WebAuthn spec:
* https://www.w3.org/TR/webauthn-3/#sctn-authenticator-data
*/
function checkAuthFlags(
bytes1 flags,
bool requireUserVerification
Expand Down Expand Up @@ -72,8 +80,8 @@ library WebAuthn {
}

/**
* Verifies a Webauthn P256 signature (Authentication Assertion) as described
* in https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion. We do not
* @dev Verifies a Webauthn P256 signature (Authentication Assertion). See
* https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion. We do not
* verify all the steps as described in the specification, only ones relevant
* to our context. Please carefully read through this list before usage.
* Specifically, we do verify the following:
Expand Down

0 comments on commit e35ffc1

Please sign in to comment.