Skip to content

Commit

Permalink
⚡️ Optimize WebAuthn for struct case (#1157)
Browse files Browse the repository at this point in the history
  • Loading branch information
Vectorized authored Nov 7, 2024
1 parent fef3cd7 commit 65823dd
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 72 deletions.
64 changes: 28 additions & 36 deletions src/utils/WebAuthn.sol
Original file line number Diff line number Diff line change
Expand Up @@ -86,27 +86,23 @@ library WebAuthn {
function verify(
bytes memory challenge,
bool requireUserVerification,
bytes memory authenticatorData,
string memory clientDataJSON,
uint256 challengeIndex,
uint256 typeIndex,
bytes32 r,
bytes32 s,
WebAuthnAuth memory auth,
bytes32 x,
bytes32 y
) internal view returns (bool result) {
bytes32 h; // The message hash.
string memory b = Base64.encode(challenge, true, true);
bytes32 messageHash;
string memory encoded = Base64.encode(challenge, true, true);
/// @solidity memory-safe-assembly
assembly {
let clientDataJSON := mload(add(auth, 0x20))
let n := mload(clientDataJSON) // `clientDataJSON`'s length.
let o := add(clientDataJSON, 0x20) // Start of `clientData`'s bytes.
{
let n := mload(clientDataJSON) // `clientDataJSON`'s length.
let o := add(clientDataJSON, 0x20) // Start of `clientData`'s bytes.
let c := challengeIndex
let t := typeIndex
let l := mload(b) // Cache `b`'s length.
let q := add(l, 0x0d) // Length of `b` prefixed with '"challenge":"'.
mstore(b, shr(152, '"challenge":"')) // Temp prefix with '"challenge":"'.
let c := mload(add(auth, 0x40)) // Challenge index in `clientDataJSON`.
let t := mload(add(auth, 0x60)) // Type index in `clientDataJSON`.
let l := mload(encoded) // Cache `encoded`'s length.
let q := add(l, 0x0d) // Length of `encoded` prefixed with '"challenge":"'.
mstore(encoded, shr(152, '"challenge":"')) // Temp prefix with '"challenge":"'.
result :=
and(
// 11. Verify JSON's type. Also checks for possible addition overflows.
Expand All @@ -116,58 +112,54 @@ library WebAuthn {
),
// 12. Verify JSON's challenge. Includes a check for the closing '"'.
and(
eq(keccak256(add(o, c), q), keccak256(add(b, 0x13), q)),
eq(keccak256(add(o, c), q), keccak256(add(encoded, 0x13), q)),
and(eq(byte(0, mload(add(add(o, c), q))), 34), lt(add(q, c), n))
)
)
mstore(b, l) // Restore `b`'s length, in case of string interning.
h := n // Reuse `h` for `n`, to prevent stack-too-deep.
b := o // Reuse `b` for `o`, to prevent stack-too-deep.
mstore(encoded, l) // Restore `encoded`'s length, in case of string interning.
}
// Skip 13., 14., 15.
let l := mload(authenticatorData) // Length of `authenticatorData`
let l := mload(mload(auth)) // Length of `authenticatorData`.
// 16. Verify that the "User Present" flag is set (bit 0).
// 17. Verify that the "User Verified" flag is set (bit 2), if required.
// See: https://www.w3.org/TR/webauthn-2/#flags.
let u := or(1, shl(2, iszero(iszero(requireUserVerification))))
// forgefmt: disable-next-item
result := and(and(result, gt(l, 0x20)),
eq(and(byte(0, mload(add(authenticatorData, 0x40))), u), u))
result := and(and(result, gt(l, 0x20)), eq(and(mload(add(mload(auth), 0x21)), u), u))
if result {
let p := add(authenticatorData, 0x20) // Start of `authenticatorData`'s bytes.
let p := add(mload(auth), 0x20) // Start of `authenticatorData`'s bytes.
let e := add(p, l) // Location of the word after `authenticatorData`.
let w := mload(e) // Cache the word after `authenticatorData`.
// 19. Compute `sha256(clientDataJSON)`.
pop(staticcall(gas(), 2, b, h, e, 0x20))
pop(staticcall(gas(), 2, o, n, e, 0x20))
// 20. Compute `sha256(authenticatorData ‖ sha256(clientDataJSON))`.
pop(staticcall(gas(), 2, p, add(l, 0x20), 0x00, returndatasize()))
mstore(e, w) // Restore the word after `authenticatorData`, in case of reuse.
messageHash := mload(0x00)
// `returndatasize()` is `0x20` on `sha256` success, and `0x00` otherwise.
if iszero(returndatasize()) { invalid() }
mstore(e, w) // Restore the word after `authenticatorData`, in case of reuse.
h := mload(0x00)
}
}
// `P256.verifySignature` returns false if `s > N/2` due to the malleability check.
if (result) result = P256.verifySignature(h, r, s, x, y);
if (result) result = P256.verifySignature(messageHash, auth.r, auth.s, x, y);
}

/// @dev Returns if the `auth` is valid.
/// @dev Plain variant of verify.
function verify(
bytes memory challenge,
bool requireUserVerification,
WebAuthnAuth memory auth,
bytes memory authenticatorData,
string memory clientDataJSON,
uint256 challengeIndex,
uint256 typeIndex,
bytes32 r,
bytes32 s,
bytes32 x,
bytes32 y
) internal view returns (bool) {
return verify(
challenge,
requireUserVerification,
auth.authenticatorData,
auth.clientDataJSON,
auth.challengeIndex,
auth.typeIndex,
auth.r,
auth.s,
WebAuthnAuth(authenticatorData, clientDataJSON, challengeIndex, typeIndex, r, s),
x,
y
);
Expand Down
64 changes: 28 additions & 36 deletions src/utils/g/WebAuthn.sol
Original file line number Diff line number Diff line change
Expand Up @@ -90,27 +90,23 @@ library WebAuthn {
function verify(
bytes memory challenge,
bool requireUserVerification,
bytes memory authenticatorData,
string memory clientDataJSON,
uint256 challengeIndex,
uint256 typeIndex,
bytes32 r,
bytes32 s,
WebAuthnAuth memory auth,
bytes32 x,
bytes32 y
) internal view returns (bool result) {
bytes32 h; // The message hash.
string memory b = Base64.encode(challenge, true, true);
bytes32 messageHash;
string memory encoded = Base64.encode(challenge, true, true);
/// @solidity memory-safe-assembly
assembly {
let clientDataJSON := mload(add(auth, 0x20))
let n := mload(clientDataJSON) // `clientDataJSON`'s length.
let o := add(clientDataJSON, 0x20) // Start of `clientData`'s bytes.
{
let n := mload(clientDataJSON) // `clientDataJSON`'s length.
let o := add(clientDataJSON, 0x20) // Start of `clientData`'s bytes.
let c := challengeIndex
let t := typeIndex
let l := mload(b) // Cache `b`'s length.
let q := add(l, 0x0d) // Length of `b` prefixed with '"challenge":"'.
mstore(b, shr(152, '"challenge":"')) // Temp prefix with '"challenge":"'.
let c := mload(add(auth, 0x40)) // Challenge index in `clientDataJSON`.
let t := mload(add(auth, 0x60)) // Type index in `clientDataJSON`.
let l := mload(encoded) // Cache `encoded`'s length.
let q := add(l, 0x0d) // Length of `encoded` prefixed with '"challenge":"'.
mstore(encoded, shr(152, '"challenge":"')) // Temp prefix with '"challenge":"'.
result :=
and(
// 11. Verify JSON's type. Also checks for possible addition overflows.
Expand All @@ -120,58 +116,54 @@ library WebAuthn {
),
// 12. Verify JSON's challenge. Includes a check for the closing '"'.
and(
eq(keccak256(add(o, c), q), keccak256(add(b, 0x13), q)),
eq(keccak256(add(o, c), q), keccak256(add(encoded, 0x13), q)),
and(eq(byte(0, mload(add(add(o, c), q))), 34), lt(add(q, c), n))
)
)
mstore(b, l) // Restore `b`'s length, in case of string interning.
h := n // Reuse `h` for `n`, to prevent stack-too-deep.
b := o // Reuse `b` for `o`, to prevent stack-too-deep.
mstore(encoded, l) // Restore `encoded`'s length, in case of string interning.
}
// Skip 13., 14., 15.
let l := mload(authenticatorData) // Length of `authenticatorData`
let l := mload(mload(auth)) // Length of `authenticatorData`.
// 16. Verify that the "User Present" flag is set (bit 0).
// 17. Verify that the "User Verified" flag is set (bit 2), if required.
// See: https://www.w3.org/TR/webauthn-2/#flags.
let u := or(1, shl(2, iszero(iszero(requireUserVerification))))
// forgefmt: disable-next-item
result := and(and(result, gt(l, 0x20)),
eq(and(byte(0, mload(add(authenticatorData, 0x40))), u), u))
result := and(and(result, gt(l, 0x20)), eq(and(mload(add(mload(auth), 0x21)), u), u))
if result {
let p := add(authenticatorData, 0x20) // Start of `authenticatorData`'s bytes.
let p := add(mload(auth), 0x20) // Start of `authenticatorData`'s bytes.
let e := add(p, l) // Location of the word after `authenticatorData`.
let w := mload(e) // Cache the word after `authenticatorData`.
// 19. Compute `sha256(clientDataJSON)`.
pop(staticcall(gas(), 2, b, h, e, 0x20))
pop(staticcall(gas(), 2, o, n, e, 0x20))
// 20. Compute `sha256(authenticatorData ‖ sha256(clientDataJSON))`.
pop(staticcall(gas(), 2, p, add(l, 0x20), 0x00, returndatasize()))
mstore(e, w) // Restore the word after `authenticatorData`, in case of reuse.
messageHash := mload(0x00)
// `returndatasize()` is `0x20` on `sha256` success, and `0x00` otherwise.
if iszero(returndatasize()) { invalid() }
mstore(e, w) // Restore the word after `authenticatorData`, in case of reuse.
h := mload(0x00)
}
}
// `P256.verifySignature` returns false if `s > N/2` due to the malleability check.
if (result) result = P256.verifySignature(h, r, s, x, y);
if (result) result = P256.verifySignature(messageHash, auth.r, auth.s, x, y);
}

/// @dev Returns if the `auth` is valid.
/// @dev Plain variant of verify.
function verify(
bytes memory challenge,
bool requireUserVerification,
WebAuthnAuth memory auth,
bytes memory authenticatorData,
string memory clientDataJSON,
uint256 challengeIndex,
uint256 typeIndex,
bytes32 r,
bytes32 s,
bytes32 x,
bytes32 y
) internal view returns (bool) {
return verify(
challenge,
requireUserVerification,
auth.authenticatorData,
auth.clientDataJSON,
auth.challengeIndex,
auth.typeIndex,
auth.r,
auth.s,
WebAuthnAuth(authenticatorData, clientDataJSON, challengeIndex, typeIndex, r, s),
x,
y
);
Expand Down

0 comments on commit 65823dd

Please sign in to comment.