Skip to content

Commit 81b46c3

Browse files
committed
♻️ use the library solidity's native type
Refacto the contract to use the solidity's native type. This will allow more compatibility with other contracts of the ecosystem.
1 parent 346f5fa commit 81b46c3

File tree

6 files changed

+454
-441
lines changed

6 files changed

+454
-441
lines changed

script/DeployWebAuthn256r1.s.sol

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,29 @@
22
pragma solidity >=0.8.19 <0.9.0;
33

44
import { BaseScript } from "./BaseScript.s.sol";
5-
import { WebAuthn256r1 } from "../src/WebAuthn256r1.sol";
5+
import { WebAuthnWrapper } from "test/WebAuthnWrapper.sol";
66

77
/// @notice This script deploys the ECDSA256r1 library
8-
contract MyScript is BaseScript {
8+
contract DeployScript is BaseScript {
99
function run() external broadcast returns (address addr) {
1010
// deploy the library contract and return the address
11-
addr = address(new WebAuthn256r1());
11+
addr = address(new WebAuthnWrapper());
1212
}
1313
}
1414

1515
/*
1616
1717
ℹ️ HOW TO USE THIS SCRIPT USING A LEDGER:
18-
forge script script/DeployWebAuthn.s.sol:MyScript --rpc-url <RPC_URL> --ledger --sender <ACCOUNT_ADDRESS> \
18+
forge script script/DeployWebAuthn256r1.s.sol:DeployScript --rpc-url <RPC_URL> --ledger --sender <ACCOUNT_ADDRESS> \
1919
[--broadcast]
2020
2121
2222
ℹ️ HOW TO USE THIS SCRIPT WITH AN ARBITRARY PRIVATE KEY (NOT RECOMMENDED):
23-
PRIVATE_KEY=<PRIVATE_KEY> forge script script/DeployWebAuthn.s.sol:MyScript --rpc-url <RPC_URL> [--broadcast]
23+
PRIVATE_KEY=<PRIVATE_KEY> forge script script/DeployWebAuthn256r1.s.sol:DeployScript --rpc-url <RPC_URL> [--broadcast]
2424
2525
2626
ℹ️ HOW TO USE THIS SCRIPT ON ANVIL IN DEFAULT MODE:
27-
forge script script/DeployWebAuthn.s.sol:MyScript --rpc-url http://127.0.0.1:8545 --broadcast --sender \
27+
forge script script/DeployWebAuthn256r1.s.sol:DeployScript --rpc-url http://127.0.0.1:8545 --broadcast --sender \
2828
0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --mnemonics "test test test test test test test test test test test junk"
2929
3030

src/WebAuthn256r1.sol

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,114 @@
22
pragma solidity >=0.8.19 <0.9.0;
33

44
import { ECDSA256r1 } from "../lib/secp256r1-verify/src/ECDSA256r1.sol";
5-
import { WebAuthnBase } from "./WebAuthnBase.sol";
5+
import { Base64 } from "../lib/solady/src/utils/Base64.sol";
66

77
/// @title WebAuthn256r1
88
/// @notice A library to verify ECDSA signature though WebAuthn on the secp256r1 curve
99
/// @custom:experimental This is an experimental library.
10-
contract WebAuthn256r1 is WebAuthnBase {
10+
library WebAuthn256r1 {
11+
error InvalidAuthenticatorData();
12+
error InvalidClientData();
13+
error InvalidChallenge();
14+
15+
/// @notice Validate the webauthn data and generate the signature message needed to recover
16+
/// @param authenticatorDataFlagMask This is a bit mask that will be used to validate the flag in the
17+
/// authenticator data. The flag is located at byte 32 of the authenticator
18+
/// data and is used to indicate, among other things, wheter the user's
19+
/// presence/verification ceremonies have been performed.
20+
/// This argument is not expected to be exposed to the end user, it is the
21+
/// responsibility of the caller to enforce the value of the flag for their flows.
22+
///
23+
/// Here are some flags you may want to use depending on your needs.
24+
/// - 0x01: User presence (UP) is required. If the UP flag is not set, revert
25+
/// - 0x04: User verification (UV) is required. If the UV flag is not set, revert
26+
/// - 0x05: UV and UP are both accepted. If none of them is set, revert
27+
///
28+
// Read more about UP here: https://www.w3.org/TR/webauthn-2/#test-of-user-presence
29+
// Read more about UV here: https://www.w3.org/TR/webauthn-2/#user-verification
30+
/// @param authenticatorData The authenticator data structure encodes contextual bindings made by the authenticator.
31+
/// Described here: https://www.w3.org/TR/webauthn-2/#authenticator-data
32+
/// @param clientData This is the client data that was signed. The client data represents the
33+
/// contextual bindings of both the WebAuthn Relying Party and the client.
34+
/// Described here: https://www.w3.org/TR/webauthn-2/#client-data
35+
/// @param clientChallenge This is the challenge that was sent to the client to sign. It is
36+
/// part of the client data. In a classic non-EVM flow, this challenge
37+
/// is generated by the server and sent to the client to avoid replay
38+
/// attack. In our context, as we already have the nonce for this purpose
39+
/// we use this field to pass the arbitrary execution order.
40+
/// This value is expected to not be encoded in Base64, the encoding is done
41+
/// during the verification.
42+
/// @param clientChallengeOffset The offset of the client challenge in the client data
43+
/// @return message The signature message needed to recover
44+
/// @dev 1. The signature counter is not checked in this implementation because
45+
/// we already have the nonce on-chain to prevent the anti-replay attack.
46+
/// The counter is 4-bytes long and it is located at bytes 33 of the authenticator data.
47+
/// 2. The RP.ID is not checked in this implementation as it is impossible to generate
48+
/// the same keys for different RP.IDs with a well formed authenticator. The hash of the id
49+
/// is 32-bytes long and it is located at bytes 0 of the authenticator data.
50+
/// 3. The length of the authenticator data is not fixed. It is at least 37 bytes
51+
/// (rpIdHash (32) + flags (1) + counter (4)) but it can be longer if there is an
52+
/// attested credential data and/or some extensions data. As we do not consider
53+
/// the counter in this implementation, we only require the authenticator data to be
54+
/// at least 32 bytes long in order to save some calldata gas.
55+
/// 4. You may probably ask why we encode the challenge in base64 on-chain instead of
56+
/// of sending it already encoded to save some gas. This library is opinionated and
57+
/// it assumes that it is used in the context of Account Abstraction. In this context,
58+
/// valuable informations required to proceed the transaction will be stored in the
59+
/// challenge meaning we need the challenge in clear to use it later in the flow.
60+
/// That's why we decided to add an extra encoding step during the validation.
61+
/// 5. It is assumed this is not the responsibility of this contract to check the value
62+
/// of the `alg` parameter. It is expected this contract will be extended by another
63+
/// contract that will redirect the message produced by this contract to the right
64+
/// recovery function.
65+
/// 6. Both extension data and attested credential data are out of scope of this implementation.
66+
/// 7. It is not the responsibility of this contract to validate the attestation statement formats
67+
///
68+
/// This contract is based on the level 2 of the WebAuthn specification.
69+
/// and until proven otherwise compliant with the level 3 of the specification.
70+
function generateMessage(
71+
bytes1 authenticatorDataFlagMask,
72+
bytes calldata authenticatorData,
73+
bytes calldata clientData,
74+
bytes calldata clientChallenge,
75+
uint256 clientChallengeOffset
76+
)
77+
internal
78+
pure
79+
returns (bytes32 message)
80+
{
81+
unchecked {
82+
// Let the caller check the value of the flag in the authenticator data
83+
// @dev: we don't need to manually check the length of the authenticator data
84+
// here as the EVM will automatically revert if the length is lower than 32
85+
if ((authenticatorData[32] & authenticatorDataFlagMask) == 0) {
86+
revert InvalidAuthenticatorData();
87+
}
88+
89+
// Ensure the client challenge is not null
90+
if (clientChallenge.length == 0) revert InvalidChallenge();
91+
92+
// Encode the client challenge in base64 and explicitly convert it to bytes
93+
bytes memory challengeEncoded = bytes(Base64.encode(clientChallenge, true, true));
94+
95+
// Extract the challenge from the client data and hash it
96+
// @dev: we don't need to check the overflow here as the EVM will automatically revert if
97+
// `clientChallengeOffset + challengeEncoded.length` overflow. This is because we will
98+
// try to access a chunk of memory by passing an end index lower than the start index
99+
bytes32 challengeHashed =
100+
keccak256(clientData[clientChallengeOffset:(clientChallengeOffset + challengeEncoded.length)]);
101+
102+
// Hash the encoded challenge and check both challenges are equal
103+
if (keccak256(challengeEncoded) != challengeHashed) {
104+
revert InvalidClientData();
105+
}
106+
107+
// Craft the signature message by hashing the client data, then concatenating
108+
// it to the authenticator data without padding, before hashing the result
109+
message = sha256(abi.encodePacked(authenticatorData, sha256(clientData)));
110+
}
111+
}
112+
11113
/// @notice Verify ECDSA signature though WebAuthn on the secp256r1 curve
12114
function verify(
13115
bytes1 authenticatorDataFlagMask,
@@ -20,7 +122,7 @@ contract WebAuthn256r1 is WebAuthnBase {
20122
uint256 qx,
21123
uint256 qy
22124
)
23-
external
125+
internal
24126
returns (bool)
25127
{
26128
unchecked {

src/WebAuthnBase.sol

Lines changed: 0 additions & 110 deletions
This file was deleted.

0 commit comments

Comments
 (0)