Skip to content

Commit

Permalink
WebAuthn main module implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Agusx1211 committed Jul 9, 2024
1 parent 1b3d7f1 commit a9ca157
Show file tree
Hide file tree
Showing 8 changed files with 743 additions and 21 deletions.
11 changes: 11 additions & 0 deletions contracts/EternalFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.19;

import "./utils/LibClone.sol";


contract EternalFactory {
function deployEternal(address _mainModule, bytes32 _salt) public payable returns (address _contract) {
return LibClone.cloneDeterministic(_mainModule, _salt);
}
}
63 changes: 51 additions & 12 deletions contracts/libs/p256-verifier/P256.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,61 @@ pragma solidity 0.8.19;

/**
* Helper library for external contracts to verify P256 signatures.
* Provided by: https://github.com/daimo-eth/p256-verifier
* Provided by: https://github.com/Vectorized/solady/blob/main/src/utils/P256.sol
**/
library P256 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev Unable to verify the P256 signature, due to missing
/// RIP-7212 P256 verifier precompile and missing Daimo P256 verifier.
error P256VerificationFailed();

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev Address of the Daimo P256 verifier.
address internal constant VERIFIER = 0xc2b78104907F722DABAc4C69f826a522B2754De4;

/// @dev Address of the RIP-7212 P256 verifier precompile.
/// Currently, we don't support EIP-7212's precompile at 0x0b as it has not been finalized.
/// See: https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md
address internal constant RIP_PRECOMPILE = 0x0000000000000000000000000000000000000100;

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* P256 VERIFICATION OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @dev Returns if the signature (`r`, `s`) is valid for `hash` and public key (`x`, `y`).
/// Does NOT include the malleability check.
function verifySignature(
bytes32 message_hash,
uint256 r,
uint256 s,
uint256 x,
uint256 y
) internal view returns (bool) {
bytes memory args = abi.encode(message_hash, r, s, x, y);
(bool success, bytes memory ret) = VERIFIER.staticcall(args);
assert(success); // never reverts, always returns 0 or 1

return abi.decode(ret, (uint256)) == 1;
bytes32 hash,
uint256 r,
uint256 s,
uint256 x,
uint256 y
) internal view returns (bool isValid) {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, hash)
mstore(add(m, 0x20), r)
mstore(add(m, 0x40), s)
mstore(add(m, 0x60), x)
mstore(add(m, 0x80), y)
let success := staticcall(gas(), RIP_PRECOMPILE, m, 0xa0, 0x00, 0x20)
// `returndatasize` is `0x20` if verifier exists and sufficient gas, else `0x00`.
if iszero(returndatasize()) {
// The verifier may actually revert, as it has `abi.decode` and `assert`.
success := staticcall(gas(), VERIFIER, m, 0xa0, returndatasize(), 0x20)
if iszero(returndatasize()) {
mstore(returndatasize(), 0xd0d5039b) // `P256VerificationFailed()`.
revert(0x1c, 0x04)
}
}
isValid := and(eq(1, mload(0x00)), success)
}
}
}
21 changes: 12 additions & 9 deletions contracts/libs/p256-verifier/WebAuthn.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ library WebAuthn {
/// https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion.
function checkAuthFlags(
bytes1 flags,
bool requireUserVerification
bool requireUserVerification,
bool requireBackupSanityCheck
) internal pure returns (bool) {
// 17. Verify that the UP bit of the flags in authData is set.
if (flags & AUTH_DATA_FLAGS_UP != AUTH_DATA_FLAGS_UP) {
Expand All @@ -37,11 +38,13 @@ library WebAuthn {
return false;
}

// 19. If the BE bit of the flags in authData is not set, verify that
// the BS bit is not set.
if (flags & AUTH_DATA_FLAGS_BE != AUTH_DATA_FLAGS_BE) {
if (flags & AUTH_DATA_FLAGS_BS == AUTH_DATA_FLAGS_BS) {
return false;
// // 19. If the BE bit of the flags in authData is not set, verify that
// // the BS bit is not set.
if (requireBackupSanityCheck) {
if (flags & AUTH_DATA_FLAGS_BE != AUTH_DATA_FLAGS_BE) {
if (flags & AUTH_DATA_FLAGS_BS == AUTH_DATA_FLAGS_BS) {
return false;
}
}
}

Expand Down Expand Up @@ -102,6 +105,7 @@ library WebAuthn {
bytes memory challenge,
bytes memory authenticatorData,
bool requireUserVerification,
bool requireBackupSanityCheck,
string memory clientDataJSON,
uint256 challengeLocation,
uint256 responseTypeLocation,
Expand All @@ -113,7 +117,7 @@ library WebAuthn {
// Check that authenticatorData has good flags
if (
authenticatorData.length < 37 ||
!checkAuthFlags(authenticatorData[32], requireUserVerification)
!checkAuthFlags(authenticatorData[32], requireUserVerification, requireBackupSanityCheck)
) {
return false;
}
Expand All @@ -137,9 +141,8 @@ library WebAuthn {
}

// Check that the public key signed sha256(authenticatorData || sha256(clientDataJSON))
bytes32 clientDataJSONHash = sha256(bytes(clientDataJSON));
bytes32 messageHash = sha256(
abi.encodePacked(authenticatorData, clientDataJSONHash)
abi.encodePacked(authenticatorData, sha256(bytes(clientDataJSON)))
);

return P256.verifySignature(messageHash, r, s, x, y);
Expand Down
66 changes: 66 additions & 0 deletions contracts/modules/MainModuleWebAuthnOnly.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.19;

import "./commons/ModuleWebAuthnOnly.sol";
import "./commons/ModuleHooks.sol";
import "./commons/ModuleCalls.sol";
import "./commons/ModuleCreator.sol";


/**
* Implement a Sequence MainModule that uses WebAuthn for authentication
* it only allows a 1/1 WebAuthn signature, without the possibility of rotating the key
*/
contract MainModuleWebAuthnOnly is
ModuleWebAuthnOnly,
ModuleCalls,
ModuleHooks,
ModuleCreator
{
constructor(
address _factory
) ModuleWebAuthnOnly(
_factory
) { }

function _isValidImage(
bytes32 _imageHash
) internal override(
IModuleAuth,
ModuleWebAuthnOnly
) view returns (bool) {
return super._isValidImage(_imageHash);
}

function signatureRecovery(
bytes32 _digest,
bytes calldata _signature
) public override(
ModuleWebAuthnOnly,
IModuleAuth
) virtual view returns (
uint256 threshold,
uint256 weight,
bytes32 imageHash,
bytes32 subdigest,
uint256 checkpoint
) {
return super.signatureRecovery(_digest, _signature);
}

/**
* @notice Query if a contract implements an interface
* @param _interfaceID The interface identifier, as specified in ERC-165
* @return `true` if the contract implements `_interfaceID`
*/
function supportsInterface(
bytes4 _interfaceID
) public override(
ModuleWebAuthnOnly,
ModuleCalls,
ModuleHooks,
ModuleCreator
) pure returns (bool) {
return super.supportsInterface(_interfaceID);
}
}
150 changes: 150 additions & 0 deletions contracts/modules/commons/ModuleAuthWebAuthnOnly.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.19;

import "./ModuleAuth.sol";
import "./ModuleUpdate.sol";
import "./ModuleSelfAuth.sol";

import "../../utils/LibClone.sol";
import "../../libs/p256-verifier/WebAuthn.sol";
import "../../Wallet.sol";

/**
* Implements ModuleAuth by validating the signature image against
* the salt used to deploy the contract
*
* This module allows wallets to be deployed with a default configuration
* without using any aditional contract storage
*/
abstract contract ModuleAuthWebAuthnOnly is ModuleSelfAuth, ModuleAuth {
bytes32 public immutable INIT_CODE_HASH;
address public immutable FACTORY;

bytes32 public constant WEBAUTHN_IMAGEHASH = keccak256(
"Webauthn(uint256 x, uint256 y, bool requireUserValidation, bool requireBackupSanityCheck)"
);

error InvalidP256Signature(bytes32 _r, bytes32 _s, bytes32 _x, bytes32 _y);

constructor(address _factory) {
// Build init code hash of the deployed wallets using that module
bytes32 initCodeHash = LibClone.initCodeHash(address(this));

INIT_CODE_HASH = initCodeHash;
FACTORY = _factory;
}

function _hashWebauthnConfiguration(
uint256 _x,
uint256 _y,
bool _requireUserValidation,
bool _requireBackupSanityCheck
) internal pure returns (bytes32) {
return keccak256(
abi.encode(
WEBAUTHN_IMAGEHASH,
_x,
_y,
_requireUserValidation,
_requireBackupSanityCheck
)
);
}

function signatureRecovery(
bytes32 _digest,
bytes calldata _signature
) public override(ModuleAuth) virtual view returns (
uint256 threshold,
uint256 weight,
bytes32 imageHash,
bytes32 subdigest,
uint256 checkpoint
) {
(
bytes memory authenticatorData,
string memory clientDataJSON,
uint256 r,
uint256 s,
uint256 x,
uint256 y,
uint256 packedFlagsAndPointers
) = abi.decode(_signature, (bytes, string, uint256, uint256, uint256, uint256, uint256));

// Decode the packed flag and pointers
// [
// 1 byte requireUserValidation
// 1 byte noChainId,
// 1 byte requireBackupSanityCheck,
// 4 bytes challengeLocation,
// 4 bytes responseTypeLocation
// ]

// Extract the flags
bool requireUserValidation = uint8(packedFlagsAndPointers >> 248) == 1;
bool noChainId = uint8(packedFlagsAndPointers >> 240) == 1;
bool requireBackupSanityCheck = uint8(packedFlagsAndPointers >> 232) == 1;
uint32 challengeLocation = uint32(packedFlagsAndPointers >> 32);
uint32 responseTypeLocation = uint32(packedFlagsAndPointers);

// The challenge is the subdigest
if (noChainId) {
subdigest = SequenceNoChainIdSig.subdigest(_digest);
} else {
subdigest = SequenceBaseSig.subdigest(_digest);
}

bytes memory challenge = abi.encodePacked(subdigest);

// Validate the signature
if (!WebAuthn.verifySignature(
challenge,
authenticatorData,
requireUserValidation,
requireBackupSanityCheck,
clientDataJSON,
challengeLocation,
responseTypeLocation,
r,
s,
x,
y
)) {
revert InvalidP256Signature(bytes32(r), bytes32(s), bytes32(x), bytes32(y));
}

// The threshold and weight are always 1 and 1
threshold = 1;
weight = 1;

// Checkpoint always zero
checkpoint = 0;

// The imageHash is a special case of hashing:
// - Magic constant
// - X and Y coordinates of the public key
// - Require user validation flag
// - Require backup sanity check flag
imageHash = _hashWebauthnConfiguration(
x, y, requireUserValidation, requireBackupSanityCheck
);
}

/**
* @notice Validates the signature image with the salt used to deploy the contract
* @param _imageHash Hash image of signature
* @return true if the signature image is valid
*/
function _isValidImage(bytes32 _imageHash) internal override virtual view returns (bool) {
return LibClone.predictDeterministicAddress(INIT_CODE_HASH, _imageHash, FACTORY) == address(this);
}

/**
* @notice Query if a contract implements an interface
* @param _interfaceID The interface identifier, as specified in ERC-165
* @return `true` if the contract implements `_interfaceID`
*/
function supportsInterface(bytes4 _interfaceID) public override(ModuleAuth) virtual pure returns (bool) {
return super.supportsInterface(_interfaceID);
}
}
Loading

0 comments on commit a9ca157

Please sign in to comment.