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

✅ improve create tests using fixtures #33

Merged
merged 1 commit into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"solady/=lib/solady/src/",
"secp256r1-verify=lib/secp256r1-verify/src/",
]
fs_permissions = [{ access = "read", path = "./test/fixtures/fixtures.create.json"}]

[profile.ci]
fuzz = { runs = 10_000 }
Expand All @@ -49,6 +50,7 @@
quote_style = "double"
tab_width = 4
wrap_comments = true
ignore = ["./test/fixtures/fixtures.create.json"]

[rpc_endpoints]
arbitrum_one = "https://arbitrum-mainnet.infura.io/v3/${API_KEY_INFURA}"
Expand Down
95 changes: 95 additions & 0 deletions test/WebAuthn256r1.create.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19 <0.9.0;

import { Test, stdJson } from "../lib/forge-std/src/Test.sol";
import { WebAuthnWrapper } from "./WebAuthnWrapper.sol";

contract WebAuthn256r1Test__Create is Test {

Check warning on line 7 in test/WebAuthn256r1.create.t.sol

View workflow job for this annotation

GitHub Actions / lint

Contract name must be in CamelCase

Check warning on line 7 in test/WebAuthn256r1.create.t.sol

View workflow job for this annotation

GitHub Actions / lint

Contract name must be in CamelCase

Check warning on line 7 in test/WebAuthn256r1.create.t.sol

View workflow job for this annotation

GitHub Actions / lint

Contract name must be in CamelCase
using stdJson for string;

WebAuthnWrapper internal implem;
uint256 internal fixturesNb;

function setUp() external {
// deploy a wrapper contract for the library
implem = new WebAuthnWrapper();

// load the fixtures from the fixtures.create.json file
string memory root = vm.projectRoot();
string memory path = string.concat(root, "/test/fixtures/fixtures.create.json");
string memory json = vm.readFile(path);

// store the number of fixtures
bytes memory fixturesNbEncoded = json.parseRaw(".length");
fixturesNb = abi.decode(fixturesNbEncoded, (uint256));
}

/// forge-config: default.fuzz.runs = 50
/// forge-config: ci.fuzz.runs = 100
function test_VerifyAValidCreateCorrectly(uint256 identifier) external {
// it verify a valid create correctly

identifier = bound(identifier, 0, fixturesNb - 1);

// load the fixtures from the credentials.json file
string memory root = vm.projectRoot();
string memory path = string.concat(root, "/test/fixtures/fixtures.create.json");
string memory json = vm.readFile(path);

bytes memory clientDataJSON;
bytes memory challenge;
bytes memory authData;
uint256 qx;
uint256 qy;
uint256 r;
uint256 s;

// load a random credential from the JSON file
string memory fixturesId = string.concat(".data[", vm.toString(identifier), "]");
emit log_named_string("fixturesId", fixturesId);

{
// load the clientDataJSON
bytes memory credentialsResponseEncoded =
json.parseRaw(string.concat(fixturesId, ".response.clientDataJSON"));
clientDataJSON = abi.decode(credentialsResponseEncoded, (bytes));
}

{
// load the client challenge from the client data JSON
bytes memory challengeEncoded =
json.parseRaw(string.concat(fixturesId, ".responseDecoded.ClientDataJSON.challenge"));
challenge = abi.decode(challengeEncoded, (bytes));
}

{
// load the auth data from the client data JSON
bytes memory authDataEncoded = json.parseRaw(string.concat(fixturesId, ".response.authData"));
authData = abi.decode(authDataEncoded, (bytes));
}

{
// load qx
qx = json.readUint(string.concat(fixturesId, ".responseDecoded.AttestationObject.authData.pubKeyX"));
}

{
// load qy
qy = json.readUint(string.concat(fixturesId, ".responseDecoded.AttestationObject.authData.pubKeyY"));
}

{
// load R
string memory key = ".responseDecoded.AttestationObject.attStmt.r";
r = json.readUint(string.concat(fixturesId, key));
}

{
// load S
string memory key = ".responseDecoded.AttestationObject.attStmt.s";
s = json.readUint(string.concat(fixturesId, key));
}

assertTrue(implem.verify(authData, clientDataJSON, challenge, r, s, qx, qy));
}
}
2 changes: 2 additions & 0 deletions test/WebAuthn256r1.create.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
WebAuthn256r1Test__Create
└── it verify a valid create correctly
42 changes: 42 additions & 0 deletions test/WebAuthn256r1.get.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19 <0.9.0;

import { Test } from "../lib/forge-std/src/Test.sol";
import { WebAuthnWrapper } from "./WebAuthnWrapper.sol";

// TODO: generate get flow's fixtures
contract WebAuthn256r1Test__Get is Test {

Check warning on line 8 in test/WebAuthn256r1.get.t.sol

View workflow job for this annotation

GitHub Actions / lint

Contract name must be in CamelCase

Check warning on line 8 in test/WebAuthn256r1.get.t.sol

View workflow job for this annotation

GitHub Actions / lint

Contract name must be in CamelCase

Check warning on line 8 in test/WebAuthn256r1.get.t.sol

View workflow job for this annotation

GitHub Actions / lint

Contract name must be in CamelCase
WebAuthnWrapper internal implem;
uint256 internal fixturesNb;

function setUp() external {
// deploy a wrapper contract for the library
implem = new WebAuthnWrapper();
}

function test_VerifyAValidCreateCorrectly() external {
// it verify a valid get correctly

assertTrue(
implem.verify(
// authenticatorData
hex"49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97631d00000000",
// clientData
hex"7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a226e"
hex"73726d616845506775365541356c367570796f644a747a55554e356c59546c656444706e70"
hex"3658634955222c226f726967696e223a22687474703a2f2f6c6f63616c686f73743a333030"
hex"30222c2263726f73734f726967696e223a66616c73657d",
// clientChallenge
hex"9ecae66a110f82ee9403997aba9ca8749b735143799584e579d0e99e9e977085",
// r
0x637f5e51e5e288310958cca253c12ef632869af03b2d398afb00c7a2ddcfcdd7,
// s
0x0b29ee7b84c8faf6b452e4700d6bd55f93525f1e0be0800e0c1b37986d23717f,
// qx
0xa33941ccf9b4eac590cda2457256babd0dca8379389b7e6612ab8fba34372a5b,
// qy
0xabe3b0cc14188c0ea28775b79120495f504df1e0f937bcbe88b2ee00f0d22c75
)
);
}
}
2 changes: 2 additions & 0 deletions test/WebAuthn256r1.get.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
WebAuthn256r1Test__Get
└── it verify a valid get correctly
112 changes: 50 additions & 62 deletions test/WebAuthn256r1.t.sol
Original file line number Diff line number Diff line change
@@ -1,58 +1,77 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19 <0.9.0;

import { Test } from "../lib/forge-std/src/Test.sol";
import { Test, stdJson } from "../lib/forge-std/src/Test.sol";
import { WebAuthnWrapper } from "./WebAuthnWrapper.sol";
import { WebAuthn256r1 } from "src/WebAuthn256r1.sol";

struct Fixtures {
bytes authenticatorData;
bytes attestationObject;
bytes clientData;
bytes clientChallenge;
bytes authData;
}

struct CredentialResponse {
bytes AttestationObject;

Check warning on line 16 in test/WebAuthn256r1.t.sol

View workflow job for this annotation

GitHub Actions / lint

Variable name must be in mixedCase

Check warning on line 16 in test/WebAuthn256r1.t.sol

View workflow job for this annotation

GitHub Actions / lint

Variable name must be in mixedCase

Check warning on line 16 in test/WebAuthn256r1.t.sol

View workflow job for this annotation

GitHub Actions / lint

Variable name must be in mixedCase
bytes clientDataJSON;
}

contract WebAuthn256r1Test is Test {
WebAuthnWrapper internal implem;
using stdJson for string;

Fixtures internal fixtures = Fixtures({
clientChallenge: hex"9ecae66a110f82ee9403997aba9ca8749b735143799584e579d0e99e9e977085",
authenticatorData: hex"49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97631d00000000",
clientData: hex"7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a226e"
hex"73726d616845506775365541356c367570796f644a747a55554e356c59546c656444706e70"
hex"3658634955222c226f726967696e223a22687474703a2f2f6c6f63616c686f73743a333030"
hex"30222c2263726f73734f726967696e223a66616c73657d"
});
WebAuthnWrapper internal implem;
Fixtures internal fixtures;
uint256 internal fixturesNb;

function setUp() external {
// deploy a wrapper contract for the library
implem = new WebAuthnWrapper();
}

function test_GenerateMessageCorrectly() external {
// it generate message correctly
bytes32 expectedMessage = 0x353bcfdce16baad69a5b9eadff9e36f7036155cd86a6cd1e423f2a786291290f;

bytes32 message =
implem._generateMessage(fixtures.authenticatorData, fixtures.clientData, fixtures.clientChallenge);

assertEq(message, expectedMessage);
// load the fixtures from the fixtures.create.json file
string memory root = vm.projectRoot();
string memory path = string.concat(root, "/test/fixtures/fixtures.create.json");
string memory json = vm.readFile(path);

// store the number of fixtures
bytes memory fixturesNbRaw = json.parseRaw(".length");
fixturesNb = abi.decode(fixturesNbRaw, (uint256));

// load the response of the first credentials from the json file
bytes memory credentialsResponseRaw = json.parseRaw(".data[0].response");
CredentialResponse memory credentialReponse = abi.decode(credentialsResponseRaw, (CredentialResponse));

// load the client challenge from the client data JSON
bytes memory challengeRaw = json.parseRaw(".data[0].responseDecoded.ClientDataJSON.challenge");
bytes memory challenge = abi.decode(challengeRaw, (bytes));

// load the client challenge from the client data JSON
bytes memory authDataRaw = json.parseRaw(".data[0].responseDecoded.AttestationObject.authData");
bytes memory authData = abi.decode(authDataRaw, (bytes));

// store the fixtures
fixtures = Fixtures({
attestationObject: credentialReponse.AttestationObject,
clientData: credentialReponse.clientDataJSON,
clientChallenge: challenge,
authData: authData
});
}

function test_RevertWhenAuthDataIsTooShort(bytes32 invalidAuthenticatorData) external {
function test_RevertWhenAuthDataIsTooShort(bytes32 invalidAuthData) external {
// it revert when auth data is too short

// the authenticator data is expected to be at least 33 bytes long. By passing a 32 bytes long
// authenticator data, we expect the function to revert.
vm.expectRevert();
implem._generateMessage(
abi.encodePacked(invalidAuthenticatorData), fixtures.clientData, fixtures.clientChallenge
);
implem._generateMessage(abi.encodePacked(invalidAuthData), fixtures.clientData, fixtures.clientChallenge);
}

function test_RevertWhenClientDataIncorrect(bytes calldata incorrectClientData) external {
// it revert when client data incorrect

vm.expectRevert();
try implem._generateMessage(fixtures.authenticatorData, incorrectClientData, fixtures.clientChallenge) { }
try implem._generateMessage(fixtures.authData, incorrectClientData, fixtures.clientChallenge) { }
catch (bytes memory reason) {
// fail the test is the revert reason is not the expected one
if (keccak256(reason) == keccak256(abi.encodePacked(WebAuthn256r1.InvalidClientData.selector))) {
Expand All @@ -61,26 +80,18 @@
}
}

function modifyAuthenticatorDataFlag(
bytes calldata authenticatorData,
bytes1 newFlag
)
external
pure
returns (bytes memory data)
{
data = bytes.concat(authenticatorData[:32], newFlag, authenticatorData[33:]);
function _modifyauthDataFlag(bytes calldata authData, bytes1 newFlag) external pure returns (bytes memory data) {
data = bytes.concat(authData[:32], newFlag, authData[33:]);
}

function test_RevertWhenOnlyUPIsSet() external {
// it revert when uv is not set

// modify the flag to only set the UP bit
bytes memory invalidAuthenticatorData =
WebAuthn256r1Test(address(this)).modifyAuthenticatorDataFlag(fixtures.authenticatorData, 0x01);
bytes memory invalidauthData = WebAuthn256r1Test(address(this))._modifyauthDataFlag(fixtures.authData, 0x01);

vm.expectRevert();
implem._generateMessage(invalidAuthenticatorData, fixtures.clientData, fixtures.clientChallenge);
implem._generateMessage(invalidauthData, fixtures.clientData, fixtures.clientChallenge);
}

function test_RevertWhenChallengeIncorrect(bytes calldata incorrectChallenge) external {
Expand All @@ -89,7 +100,7 @@
vm.assume(incorrectChallenge.length > 0);

vm.expectRevert();
try implem._generateMessage(fixtures.authenticatorData, fixtures.clientData, incorrectChallenge) { }
try implem._generateMessage(fixtures.authData, fixtures.clientData, incorrectChallenge) { }
catch (bytes memory reason) {
// fail the test is the revert reason is not the expected one
if (keccak256(reason) == keccak256(abi.encodePacked(WebAuthn256r1.InvalidClientData.selector))) {
Expand All @@ -102,35 +113,12 @@
// it revert when challenge is null

vm.expectRevert();
try implem._generateMessage(fixtures.authenticatorData, fixtures.clientData, hex"") { }
try implem._generateMessage(fixtures.authData, fixtures.clientData, hex"") { }
catch (bytes memory reason) {
// fail the test is the revert reason is not the expected one
if (keccak256(reason) == keccak256(abi.encodePacked(WebAuthn256r1.InvalidChallenge.selector))) {
fail("Unknown reverted error");
}
}
}

function test_VerifyAValidWebauthnPayloadCorrectly() external {
// it verify a valid webauthn payload correctly

assertTrue(
implem.verify(
// authenticatorData
fixtures.authenticatorData,
// clientData
fixtures.clientData,
// clientChallenge
fixtures.clientChallenge,
// r
0x637f5e51e5e288310958cca253c12ef632869af03b2d398afb00c7a2ddcfcdd7,
// s
0x0b29ee7b84c8faf6b452e4700d6bd55f93525f1e0be0800e0c1b37986d23717f,
// qx
0xa33941ccf9b4eac590cda2457256babd0dca8379389b7e6612ab8fba34372a5b,
// qy
0xabe3b0cc14188c0ea28775b79120495f504df1e0f937bcbe88b2ee00f0d22c75
)
);
}
}
4 changes: 2 additions & 2 deletions test/WebAuthn256r1.tree
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ WebAuthn256r1Test
├── it revert when client data incorrect
├── it revert when only UP is set
├── it revert when challenge incorrect
── it revert when challenge is null
└── it verify a valid webauthn payload correctly
── it revert when challenge is null

1 change: 1 addition & 0 deletions test/fixtures/fixtures.create.json

Large diffs are not rendered by default.

Loading