Skip to content
Open
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
200 changes: 139 additions & 61 deletions contracts/core/PasskeyManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,103 +2,181 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.12;

import "@account-abstraction/contracts/samples/SimpleAccount.sol";

import "../interfaces/IPasskeyManager.sol";
import "./PasskeyVerificationLibrary.sol";
import "../utils/Base64.sol";
import "@account-abstraction/contracts/interfaces/UserOperation.sol";


contract PasskeyManager is SimpleAccount, IPasskeyManager {
contract PasskeyManager is IPasskeyManager{

mapping(bytes32 => Passkey) private PasskeysAuthorised;
bytes32[] public KnownEncodedIdHashes;

// The constructor is used only for the "implementation" and only sets immutable values.
// Mutable value slots for proxy accounts are set by the 'initialize' function.
constructor(IEntryPoint anEntryPoint) SimpleAccount(anEntryPoint) {
}

/**
* The initializer for the PassKeysAcount instance.
* @param _encodedId the id of the key
* @param _pubKeyX public key X val from a passkey that will have a full ownership and control of this account.
* @param _pubKeyY public key X val from a passkey that will have a full ownership and control of this account.
*/
function initialize(string calldata _encodedId, uint256 _pubKeyX, uint256 _pubKeyY) public virtual initializer {
super._initialize(address(0));
bytes32 hashEncodedId = keccak256(abi.encodePacked(_encodedId));
_addPasskey(hashEncodedId, _encodedId, _pubKeyX, _pubKeyY);
}
mapping(bytes32 => Passkey) private passkeysAdded;

bytes32[] public AddedHashedEncodedIds;


function addPasskey(string calldata _encodedId, uint256 _publicKeyX, uint256 _publicKeyY) public override {
bytes32 hashEncodedId = keccak256(abi.encodePacked(_encodedId));
_addPasskey(hashEncodedId, _encodedId, _publicKeyX, _publicKeyY);
// require(msg.sender == address(this), "PM01 caller is not Wallet");
_addPasskey(_encodedId, _publicKeyX, _publicKeyY);
}

function _addPasskey(bytes32 hashEncodedId, string calldata _encodedId, uint256 _publicKeyX, uint256 _publicKeyY) internal {
function _addPasskey(string calldata _encodedId, uint256 _publicKeyX, uint256 _publicKeyY) internal {

require(PasskeysAuthorised[hashEncodedId].pubKeyX == 0 && PasskeysAuthorised[hashEncodedId].pubKeyY == 0, "PM01: Passkey already exists");
bytes32 hashEncodedId = keccak256(abi.encodePacked(_encodedId));
require(passkeysAdded[hashEncodedId].publicKeyX == 0 && passkeysAdded[hashEncodedId].publicKeyY == 0, "PM04");

Passkey memory passkey = Passkey({
pubKeyX: _publicKeyX,
pubKeyY: _publicKeyY
publicKeyX: _publicKeyX,
publicKeyY: _publicKeyY,
encodedId: _encodedId
});
KnownEncodedIdHashes.push(hashEncodedId);
PasskeysAuthorised[hashEncodedId] = passkey;
passkeysAdded[hashEncodedId] = passkey;
emit PasskeyAdded(_encodedId, _publicKeyX, _publicKeyY);
}

function removePasskey(string calldata _encodedId) external override {
//! Need to look into this
// require(msg.sender == address(this), "PM02: Only wallet can remove passkeys");
require(KnownEncodedIdHashes.length > 1, "PM03: cannot remove last key");
require(msg.sender == address(this), "PM01 caller is not Wallet");
require(AddedHashedEncodedIds.length > 1, "PM03 cannot remove last key");
bytes32 hashEncodedId = keccak256(abi.encodePacked(_encodedId));

Passkey memory passkey = PasskeysAuthorised[hashEncodedId];
Passkey memory passkey = passkeysAdded[hashEncodedId];

require(passkey.pubKeyX != 0 && passkey.pubKeyX != 0, "PM04: Passkey doesn't exist");
require(passkey.publicKeyX != 0 && passkey.publicKeyY != 0, "PM05");

delete PasskeysAuthorised[hashEncodedId];
for(uint i = 0; i < KnownEncodedIdHashes.length; ){
if(KnownEncodedIdHashes[i] == hashEncodedId){
KnownEncodedIdHashes[i] = KnownEncodedIdHashes[KnownEncodedIdHashes.length - 1];
KnownEncodedIdHashes.pop();
delete passkeysAdded[hashEncodedId];
for(uint i = 0; i < AddedHashedEncodedIds.length; ){
if(AddedHashedEncodedIds[i] == hashEncodedId){
AddedHashedEncodedIds[i] = AddedHashedEncodedIds[AddedHashedEncodedIds.length - 1];
AddedHashedEncodedIds.pop();
break;
}
unchecked {
i++;
}
}
emit PasskeyRemoved(_encodedId, passkey.pubKeyX, passkey.pubKeyY);
emit PasskeyRemoved(_encodedId, passkey.publicKeyX, passkey.publicKeyY);
}

/**
* @param userOp typical userOperation
* @param userOpHash the hash of the user operation.
* @return validationData
*/
function _validateSignature(UserOperation calldata userOp, bytes32 userOpHash)
internal override virtual returns (uint256)
{

(uint r, uint s, bytes memory authenticatorData, string memory clientDataJSONPre, string memory clientDataJSONPost, bytes32 encodedIdHash) = abi.decode(

function validateDataAndSignature(UserOperation calldata userOp, bytes32 userOpHash)
internal returns (bool success)
{
(uint r, uint s, bytes memory authenticatorData, string memory clientDataJSONPre, string memory clientDataJSONPost) = abi.decode(
userOp.signature,
(uint, uint, bytes, string, string, bytes32)
(uint, uint, bytes, string, string)
);

string memory opHashBase64 = Base64.encode(bytes.concat(userOpHash));
string memory userOpHashHex = lower(toHex(userOpHash));

bytes memory base64RequestId = bytes(Base64.encode(userOpHashHex));
string memory opHashBase64 = string(base64RequestId);
string memory clientDataJSON = string.concat(clientDataJSONPre, opHashBase64, clientDataJSONPost);

bytes32 clientHash = sha256(bytes(clientDataJSON));
bytes32 message = sha256(bytes.concat(authenticatorData, clientHash));

Passkey memory passKey = PasskeysAuthorised[encodedIdHash];
require(passKey.pubKeyX != 0 && passKey.pubKeyY != 0, "PM06: Passkey doesn't exist");
require(Secp256r1.Verify(
passKey,
r, s,
uint(message)
), "PM07: Invalid signature");
return 0;
bytes32 sigHash = sha256(bytes.concat(authenticatorData, clientHash));

// string memory userOpHashHex = lower(toHex(userOpHash));

// bytes memory base64RequestId = bytes(Base64.encode(userOpHashHex));

// if (keccak256(base64RequestId) != clientDataJsonHash) return false;

// Passkey memory passkey = passkeysAdded[hashedEncodedId];

// success = Secp256r1.Verify(
// uint(message),
// [r, s],
// [passkey.publicKeyX, passkey.publicKeyY]
// );
// Passkey memory passkey = passkeysAdded[hashedEncodedId];

bool success = PasskeyVerificationLib.Verify(
uint(sigHash),
[r, s],
[107490028906455791796471978989263119874761744151520737029565739289860294711432,
22721315569127605178492405433029903652409185507600908909410012870036792961855]
);
return true;
}

function toHex16(bytes16 data)
internal pure returns (bytes32 result)
{
result =
(bytes32(data) &
0xFFFFFFFFFFFFFFFF000000000000000000000000000000000000000000000000) |
((bytes32(data) &
0x0000000000000000FFFFFFFFFFFFFFFF00000000000000000000000000000000) >>
64);
result =
(result &
0xFFFFFFFF000000000000000000000000FFFFFFFF000000000000000000000000) |
((result &
0x00000000FFFFFFFF000000000000000000000000FFFFFFFF0000000000000000) >>
32);
result =
(result &
0xFFFF000000000000FFFF000000000000FFFF000000000000FFFF000000000000) |
((result &
0x0000FFFF000000000000FFFF000000000000FFFF000000000000FFFF00000000) >>
16);
result =
(result &
0xFF000000FF000000FF000000FF000000FF000000FF000000FF000000FF000000) |
((result &
0x00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000) >>
8);
result =
((result &
0xF000F000F000F000F000F000F000F000F000F000F000F000F000F000F000F000) >>
4) |
((result &
0x0F000F000F000F000F000F000F000F000F000F000F000F000F000F000F000F00) >>
8);
result = bytes32(
0x3030303030303030303030303030303030303030303030303030303030303030 +
uint256(result) +
(((uint256(result) +
0x0606060606060606060606060606060606060606060606060606060606060606) >>
4) &
0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F) *
7
);
}

function toHex(bytes32 data)
public pure returns (string memory)
{
return
string(
abi.encodePacked(
'0x',
toHex16(bytes16(data)),
toHex16(bytes16(data << 128))
)
);
}

function lower(string memory _base)
internal pure returns (string memory)
{
bytes memory _baseBytes = bytes(_base);
for (uint256 i = 0; i < _baseBytes.length; i++) {
_baseBytes[i] = _lower(_baseBytes[i]);
}
return string(_baseBytes);
}

function _lower(bytes1 _b1)
private pure returns (bytes1)
{
if (_b1 >= 0x41 && _b1 <= 0x5A) {
return bytes1(uint8(_b1) + 32);
}

return _b1;
}

}
37 changes: 19 additions & 18 deletions contracts/core/PasskeyManagerFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@ pragma solidity ^0.8.12;

import "@openzeppelin/contracts/utils/Create2.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@account-abstraction/contracts/interfaces/IEntryPoint.sol";
import "./PasskeyManager.sol";

/* solhint-disable no-inline-assembly */
import "./SimpleAcc.sol";

/**
* Based on SimpleAccountFactory.
* Cannot be a subclass since both constructor and createAccount depend on the
* constructor and initializer of the actual account contract.
* A sample factory contract for SimpleAccount
* A UserOperations "initCode" holds the address of the factory, and a method call (to createAccount, in this sample factory).
* The factory's createAccount returns the target account address even if it is already installed.
* This way, the entryPoint.getSenderAddress() can be called either before or after the account is created.
*/
contract PassKeyManagerFactory {
PasskeyManager public immutable accountImplementation;
contract SimpleAccountFactory {
PasskeyAccount public immutable accountImplementation;
event AccountCreated(address indexed account);

constructor(IEntryPoint entryPoint){
accountImplementation = new PasskeyManager(entryPoint);
constructor(IEntryPoint _entryPoint) {
accountImplementation = new PasskeyAccount(_entryPoint);
}

/**
Expand All @@ -26,28 +26,29 @@ contract PassKeyManagerFactory {
* Note that during UserOperation execution, this method is called only if the account is not deployed.
* This method returns an existing account address so that entryPoint.getSenderAddress() would work even after account creation
*/
function createAccount(uint256 salt, string calldata encodedId, uint256 pubKeyX, uint256 pubKeyY) public returns (PasskeyManager) {
address addr = getAddress(salt, encodedId, pubKeyX, pubKeyY);
function createAccount(string calldata _encodedId, uint256 _publicKeyX, uint256 _publicKeyY,uint256 salt) public returns (PasskeyAccount ret) {
address addr = gettAddress(_encodedId, _publicKeyX, _publicKeyY, salt);
uint codeSize = addr.code.length;
if (codeSize > 0) {
return PasskeyManager(payable(addr));
return PasskeyAccount(payable(addr));
}
return PasskeyManager(payable(new ERC1967Proxy{salt : bytes32(salt)}(
ret = PasskeyAccount(payable(new ERC1967Proxy{salt : bytes32(salt)}(
address(accountImplementation),
abi.encodeCall(PasskeyManager.initialize, (encodedId, pubKeyX, pubKeyY))
abi.encodeCall(PasskeyAccount.initialize, (_encodedId, _publicKeyX, _publicKeyY))
)));
emit AccountCreated(address(ret));
}

/**
* calculate the counterfactual address of this account as it would be returned by createAccount()
*/
function getAddress(uint256 salt, string calldata encodedId, uint256 pubKeyX, uint256 pubKeyY) public view returns (address) {
function gettAddress(string calldata _encodedId, uint256 _publicKeyX, uint256 _publicKeyY,uint256 salt) public view returns (address) {
return Create2.computeAddress(bytes32(salt), keccak256(abi.encodePacked(
type(ERC1967Proxy).creationCode,
abi.encode(
address(accountImplementation),
abi.encodeCall(PasskeyManager.initialize, (encodedId, pubKeyX, pubKeyY))
abi.encodeCall(PasskeyAccount.initialize, (_encodedId, _publicKeyX, _publicKeyY))
)
)));
}
}
}
Loading