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

Passkey support #808

Merged
merged 49 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
061321e
Add passkey support
DaniSomoza May 8, 2024
55e24a6
update createAddOwnerTx to receive passkey param
DaniSomoza May 9, 2024
6702b3d
fix init issue in relay-kit
DaniSomoza May 9, 2024
a74b809
fix issue in BaseContract
DaniSomoza May 9, 2024
33fd454
PasskeyArgType type as Uppercase
DaniSomoza Jun 4, 2024
d20dc53
remove Safe Proxy Factory mentions in the SafeWebAuthnSignerFactoryCo…
DaniSomoza Jun 4, 2024
dac782f
fix getSafeWebAuthnSignerFactoryContract params
DaniSomoza Jun 4, 2024
f1ef57e
remove left-hand side condition
DaniSomoza Jun 4, 2024
a61e70c
Add support for passkeys to 4337
DaniSomoza May 16, 2024
a547b98
verificationGasLimit adjustment
DaniSomoza May 30, 2024
b2ac8df
Added createSafeProvider util function
DaniSomoza Jun 4, 2024
eff8ab5
added createSafeProvider in relay-kit
DaniSomoza Jun 4, 2024
3d524ef
use createSafeProvider in SafeFactory
DaniSomoza Jun 4, 2024
3aa0fdd
feat(protocol-kit): Tests for passkey (#838)
tmjssz Jun 5, 2024
06cdedf
fix isTypedDataSigner to return false if it is passkey signer
DaniSomoza Jun 5, 2024
f3cf8ab
Merge branch 'passkey-support' into passkey-4337-support
DaniSomoza Jun 5, 2024
694488b
refactor createSafeProvider into a static async init method in SafePr…
DaniSomoza Jun 5, 2024
94cb8a0
flatten signingMethod ifs
DaniSomoza Jun 5, 2024
dd0dddc
removed getSafeWebAuthnSignerFactoryContract from Contract Manager
DaniSomoza Jun 5, 2024
625b668
Merge branch 'development' into passkey-support
DaniSomoza Jun 5, 2024
801f8f2
fix types in SafeFactory signer param
DaniSomoza Jun 5, 2024
7ba2e47
updated docs
DaniSomoza Jun 5, 2024
378c03e
Add FIXME comment to use the production deployment packages instead o…
DaniSomoza Jun 5, 2024
70e09af
Add Passkeys as an experimental feature only available on the Sepolia…
DaniSomoza Jun 6, 2024
4977cf6
Merge pull request #841 from safe-global/passkey-4337-support
dasanra Jun 6, 2024
2e284eb
Set alpha.0 version
dasanra Jun 6, 2024
7d58f60
add version compatibility check for passkeys
DaniSomoza Jun 10, 2024
200c6b2
Merge branch 'development' into passkey-support
dasanra Jun 10, 2024
a4a45de
Merge branch 'development' into passkey-support
dasanra Jun 12, 2024
6cbd3f6
feat(protocol-kit): Restrict passkeys tests according to safe version…
leonardotc Jun 12, 2024
d5f32ec
feat(relay-kit): add dummy signature as a passkey signature (#857)
DaniSomoza Jun 12, 2024
61a1280
feat(relay-kit): Tests for using passkey with 4337 (#846)
tmjssz Jun 17, 2024
102c3b6
Update passkey type (#859)
DaniSomoza Jun 18, 2024
9ff82fc
Set alpha.1 version
dasanra Jun 19, 2024
a51e837
feat(protocol-kit): Tests for swap and remove passkey owners (#861)
leonardotc Jun 20, 2024
3dd6595
[Passkeys] Detect Shared Signer owner (#875)
DaniSomoza Jul 5, 2024
1b91381
Merge branch 'development' into passkey-support
dasanra Jul 10, 2024
64cd846
Merge branch 'development' into passkey-support
dasanra Jul 10, 2024
8fbbded
Fix `safe-kit` reconnections
dasanra Jul 10, 2024
1ba2989
Prepare passkeys release (#937)
DaniSomoza Aug 12, 2024
b875321
Merge branch 'development' into passkey-support
dasanra Aug 12, 2024
eb1ce1a
fix(protocol-kit): build not finishing because of breaking change in …
dasanra Aug 12, 2024
36ef47f
Set alpha.2 version
dasanra Aug 12, 2024
13c8ef5
add comment to use external util
dasanra Aug 12, 2024
c31b351
Merge branch 'development' into passkey-support
dasanra Aug 14, 2024
d40e489
use contract addresses from safe-modules-deployments (#950)
DaniSomoza Aug 21, 2024
2081134
Merge branch 'development' into passkey-support
DaniSomoza Aug 22, 2024
d3da1e0
chore: remove bytecode from webauthn ABIs
dasanra Aug 22, 2024
1c22d9c
update passkeys contract names from v1_4_1 to v0.2.1 (#953)
DaniSomoza Aug 23, 2024
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
1 change: 1 addition & 0 deletions .github/workflows/sdk-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
- name: Test
env:
PRIVATE_KEY: ${{ secrets.TESTING_PRIVATE_KEY }}
PASSKEY_PRIVATE_KEY: ${{ secrets.TESTING_PASSKEY_PRIVATE_KEY }}
run: yarn test

- name: Upload coverage report
Expand Down
2 changes: 2 additions & 0 deletions guides/integrating-the-safe-core-sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const contractNetworks: ContractNetworksConfig = {
signMessageLibAddress: '<SIGN_MESSAGE_LIB_ADDRESS>',
createCallAddress: '<CREATE_CALL_ADDRESS>',
simulateTxAccessorAddress: '<SIMULATE_TX_ACCESSOR_ADDRESS>',
safeWebAuthnSignerFactoryAddress:'<SAFE_WEB_AUTHN_SIGNER_FACTORY_ADDRESS>',
safeSingletonAbi: '<SINGLETON_ABI>', // Optional. Only needed with web3.js
safeProxyFactoryAbi: '<PROXY_FACTORY_ABI>', // Optional. Only needed with web3.js
multiSendAbi: '<MULTI_SEND_ABI>', // Optional. Only needed with web3.js
Expand All @@ -97,6 +98,7 @@ const contractNetworks: ContractNetworksConfig = {
signMessageLibAbi: '<SIGN_MESSAGE_LIB_ABI>', // Optional. Only needed with web3.js
createCallAbi: '<CREATE_CALL_ABI>', // Optional. Only needed with web3.js
simulateTxAccessorAbi: '<SIMULATE_TX_ACCESSOR_ABI>' // Optional. Only needed with web3.js
safeWebAuthnSignerFactoryAbi: '<SAFE_WEB_AUTHN_SIGNER_FACTORY_ABI>' // Optional. Only needed with web3.js
}
}

Expand Down
6 changes: 3 additions & 3 deletions packages/api-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"@types/yargs": "^17.0.32",
"chai": "^4.3.10",
"chai-as-promised": "^7.1.1",
"hardhat": "^2.19.3",
"hardhat": "2.20.1",
"mocha": "^10.2.0",
"semver": "^7.6.1",
"sinon": "^14.0.2",
Expand All @@ -59,8 +59,8 @@
"yargs": "^17.7.2"
},
"dependencies": {
"@safe-global/protocol-kit": "^4.0.2",
"@safe-global/safe-core-sdk-types": "^5.0.2",
"@safe-global/protocol-kit": "^4.1.0-alpha.1",
"@safe-global/safe-core-sdk-types": "^5.1.0-alpha.1",
"ethers": "^6.13.1",
"node-fetch": "^2.7.0"
}
Expand Down
2 changes: 1 addition & 1 deletion packages/auth-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
},
"dependencies": {
"@safe-global/api-kit": "^2.4.2",
"@safe-global/protocol-kit": "^4.0.2",
"@safe-global/protocol-kit": "^4.1.0-alpha.1",
"@web3auth/safeauth-embed": "^0.0.0",
"ethers": "^6.13.1"
}
Expand Down
4 changes: 2 additions & 2 deletions packages/onramp-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
"dependencies": {
"@monerium/sdk": "^2.12.0",
"@safe-global/api-kit": "^2.4.2",
"@safe-global/protocol-kit": "^4.0.2",
"@safe-global/safe-core-sdk-types": "^5.0.2",
"@safe-global/protocol-kit": "^4.1.0-alpha.1",
"@safe-global/safe-core-sdk-types": "^5.1.0-alpha.1",
"@stripe/crypto": "^0.0.4",
"@stripe/stripe-js": "^1.54.2",
"ethers": "^6.13.1"
Expand Down
2 changes: 1 addition & 1 deletion packages/protocol-kit/contracts/Deps_V1_3_0.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.7.0 <0.9.0;
pragma solidity >=0.7.0 <=0.8.0;

import { GnosisSafeProxyFactory } from "@gnosis.pm/safe-contracts-v1.3.0/contracts/proxies/GnosisSafeProxyFactory.sol";
import { GnosisSafe } from "@gnosis.pm/safe-contracts-v1.3.0/contracts/GnosisSafe.sol";
Expand Down
219 changes: 219 additions & 0 deletions packages/protocol-kit/contracts/Deps_passkeys.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.24;

import {SafeWebAuthnSignerFactory} from '@safe-global/safe-passkey/contracts/SafeWebAuthnSignerFactory.sol';
import {FCLP256Verifier} from '@safe-global/safe-passkey/contracts/verifiers/FCLP256Verifier.sol';
import {SignatureValidator} from '@safe-global/safe-passkey/contracts/base/SignatureValidator.sol';
import {P256, WebAuthn} from '@safe-global/safe-passkey/contracts/libraries/WebAuthn.sol';

/**
* @title Safe Smart Account
* @dev Minimal interface of a Safe smart account. This only includes functions that are used by
* this project.
* @custom:security-contact [email protected]
*/
interface ISafeTest {
/**
* @notice Sets an initial storage of the Safe contract.
* @dev This method can only be called once. If a proxy was created without setting up, anyone
* can call setup and claim the proxy.
* @param owners List of Safe owners.
* @param threshold Number of required confirmations for a Safe transaction.
* @param to Contract address for optional delegate call.
* @param data Data payload for optional delegate call.
* @param fallbackHandler Handler for fallback calls to this contract
* @param paymentToken Token that should be used for the payment (0 is ETH)
* @param payment Value that should be paid
* @param paymentReceiver Address that should receive the payment (or 0 if tx.origin)
*/
function setup(
address[] calldata owners,
uint256 threshold,
address to,
bytes calldata data,
address fallbackHandler,
address paymentToken,
uint256 payment,
address payable paymentReceiver
) external;

/**
* @notice Reads `length` bytes of storage in the currents contract
* @param offset - the offset in the current contract's storage in words to start reading from
* @param length - the number of words (32 bytes) of data to read
* @return the bytes that were read.
*/
function getStorageAt(uint256 offset, uint256 length) external view returns (bytes memory);
}

contract SafeWebAuthnSignerFactory_SV1_4_1 is SafeWebAuthnSignerFactory {}

contract WebAuthnContract is FCLP256Verifier {}

// TODO: ADD SHARED SIGNER CONTRACT FROM @safe-global/safe-passkey

/**
* @title Safe WebAuthn Shared Signer
* @dev A contract for verifying WebAuthn signatures shared by all Safe accounts. This contract uses
* storage from the Safe account itself for full ERC-4337 compatibility.
*/
contract SafeWebAuthnSharedSigner is SignatureValidator {
/**
* @notice Data associated with a WebAuthn signer. It represents the X and Y coordinates of the
* signer's public key as well as the P256 verifiers to use. This is stored in account storage
* starting at the storage slot {SIGNER_SLOT}.
*/
struct Signer {
uint256 x;
uint256 y;
P256.Verifiers verifiers;
}

/**
* @notice The storage slot of the mapping from shared WebAuthn signer address to signer data.
* @custom:computed-as keccak256("SafeWebAuthnSharedSigner.signer") - 1
* @dev This value is intentionally computed to be a hash -1 as a precaution to avoid any
* potential issues from unintended hash collisions, and have enough space for all the signer
* fields. Also, this is the slot of a `mapping(address self => Signer)` to ensure that multiple
* {SafeWebAuthnSharedSigner} instances can coexist with the same account.
*/
uint256 private constant _SIGNER_MAPPING_SLOT =
0x2e0aed53485dc2290ceb5ce14725558ad3e3a09d38c69042410ad15c2b4ea4e8;

/**
* @notice An error indicating a `CALL` to a function that should only be `DELEGATECALL`-ed.
*/
error NotDelegateCalled();

/**
* @notice Address of the shared signer contract itself.
* @dev This is used for determining whether or not the contract is being `DELEGATECALL`-ed when
* setting signer data.
*/
address private immutable _SELF;

/**
* @notice The starting storage slot on the account containing the signer data.
*/
uint256 public immutable SIGNER_SLOT;

/**
* @notice Create a new shared WebAuthn signer instance.
*/
constructor() {
_SELF = address(this);
SIGNER_SLOT = uint256(keccak256(abi.encode(address(this), _SIGNER_MAPPING_SLOT)));
}

/**
* @notice Validates the call is done via `DELEGATECALL`.
*/
modifier onlyDelegateCall() {
if (address(this) == _SELF) {
revert NotDelegateCalled();
}
_;
}

/**
* @notice Return the signer configuration for the specified account.
* @dev The calling account must be a Safe, as the signer data is stored in the Safe's storage
* and must be read with the {StorageAccessible} support from the Safe.
* @param account The account to request signer data for.
*/
function getConfiguration(address account) public view returns (Signer memory signer) {
bytes memory getStorageAtData = abi.encodeCall(
ISafeTest(account).getStorageAt,
(SIGNER_SLOT, 3)
);

// Call the {StorageAccessible.getStorageAt} with assembly. This allows us to return a
// zeroed out signer configuration instead of reverting for `account`s that are not Safes.
// We also, expect the implementation to behave **exactly** like the Safe's - that is it
// should encode the return data using a standard ABI encoding:
// - The first 32 bytes is the offset of the values bytes array, always `0x20`
// - The second 32 bytes is the length of the values bytes array, always `0x60`
// - the following 3 words (96 bytes) are the values of the signer configuration.

// solhint-disable-next-line no-inline-assembly
assembly ('memory-safe') {
// Note that Yul expressions are evaluated in reverse order, so the `staticcall` is the
// first thing to be evaluated in the nested `and` expression.
if and(
and(
// The offset of the ABI encoded bytes is 0x20, this should always be the case
// for standard ABI encoding of `(bytes)` tuple that `getStorageAt` returns.
eq(mload(0x00), 0x20),
// The length of the encoded bytes is exactly 0x60 bytes (i.e. 3 words, which is
// exactly how much we read from the Safe's storage in the `getStorageAt` call).
eq(mload(0x20), 0x60)
),
and(
// The length of the return data should be exactly 0xa0 bytes, which should
// always be the case for the Safe's `getStorageAt` implementation.
eq(returndatasize(), 0xa0),
// The call succeeded. We write the first two words of the return data into the
// scratch space, as we need to inspect them before copying the signer
// signer configuration to our `signer` memory pointer.
staticcall(
gas(),
account,
add(getStorageAtData, 0x20),
mload(getStorageAtData),
0x00,
0x40
)
)
) {
// Copy only the storage values from the return data to our `signer` memory address.
// This only happens on success, so the `signer` value will be zeroed out if any of
// the above conditions fail, indicating that no signer is configured.
returndatacopy(signer, 0x40, 0x60)
}
}
}

/**
* @notice Sets the signer configuration for the calling account.
* @dev The Safe must call this function with a `DELEGATECALL`, as the signer data is stored in
* the Safe account's storage.
* @param signer The new signer data to set for the calling account.
*/
function configure(Signer memory signer) external onlyDelegateCall {
uint256 signerSlot = SIGNER_SLOT;
Signer storage signerStorage;

// solhint-disable-next-line no-inline-assembly
assembly ('memory-safe') {
signerStorage.slot := signerSlot
}

signerStorage.x = signer.x;
signerStorage.y = signer.y;
signerStorage.verifiers = signer.verifiers;
}

/**
* @inheritdoc SignatureValidator
*/
function _verifySignature(
bytes32 message,
bytes calldata signature
) internal view virtual override returns (bool isValid) {
Signer memory signer = getConfiguration(msg.sender);

// Make sure that the signer is configured in the first place.
if (P256.Verifiers.unwrap(signer.verifiers) == 0) {
return false;
}

isValid = WebAuthn.verifySignature(
message,
signature,
WebAuthn.USER_VERIFICATION,
signer.x,
signer.y,
signer.verifiers
);
}
}
22 changes: 21 additions & 1 deletion packages/protocol-kit/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,27 @@ if (PK) {
const config: HardhatUserConfig = {
defaultNetwork: 'hardhat',
solidity: {
compilers: [{ version: '0.5.17' }, { version: '0.5.3' }, { version: '0.8.0' }]
compilers: [
{ version: '0.5.17' },
{ version: '0.5.3' },
{ version: '0.8.0' },
{
version: '0.8.24',
settings: {
optimizer: {
enabled: true,
runs: 10_000_000
},
viaIR: false,
evmVersion: 'paris'
}
}
],
overrides: {
'@gnosis.pm/safe-contracts-v1.3.0/contracts/handler/CompatibilityFallbackHandler.sol': {
version: '0.8.0'
}
}
},
paths: {
artifacts: 'artifacts',
Expand Down
41 changes: 41 additions & 0 deletions packages/protocol-kit/hardhat/deploy/deploy-contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,22 @@ const simulateTxAccessorContracts: SafeVersions = {
'1.0.0': { name: 'SimulateTxAccessor_SV1_3_0' }
}

const safeWebAuthnSignerFactoryContracts: SafeVersions = {
'1.4.1': { name: 'SafeWebAuthnSignerFactory_SV1_4_1' },
'1.3.0': { name: 'SafeWebAuthnSignerFactory_SV1_4_1' },
'1.2.0': { name: 'SafeWebAuthnSignerFactory_SV1_4_1' },
'1.1.1': { name: 'SafeWebAuthnSignerFactory_SV1_4_1' },
'1.0.0': { name: 'SafeWebAuthnSignerFactory_SV1_4_1' }
}

const safeWebAuthnSharedSignerContracts: SafeVersions = {
'1.4.1': { name: 'SafeWebAuthnSharedSigner' },
'1.3.0': { name: 'SafeWebAuthnSharedSigner' },
'1.2.0': { name: 'SafeWebAuthnSharedSigner' },
'1.1.1': { name: 'SafeWebAuthnSharedSigner' },
'1.0.0': { name: 'SafeWebAuthnSharedSigner' }
}

export const safeDeployed = safeContracts[safeVersionDeployed]
export const proxyFactoryDeployed = proxyFactoryContracts[safeVersionDeployed]
export const multiSendDeployed = multiSendContracts[safeVersionDeployed]
Expand All @@ -79,6 +95,10 @@ export const compatibilityFallbackHandlerDeployed =
export const signMessageLibDeployed = signMessageLibContracts[safeVersionDeployed]
export const createCallDeployed = createCallContracts[safeVersionDeployed]
export const simulateTxAccessorDeployed = simulateTxAccessorContracts[safeVersionDeployed]
export const safeWebAuthnSignerFactoryDeployed =
safeWebAuthnSignerFactoryContracts[safeVersionDeployed]
export const safeWebAuthnSharedSignerDeployed =
safeWebAuthnSharedSignerContracts[safeVersionDeployed]

const deploy: DeployFunction = async (hre: HardhatRuntimeEnvironment): Promise<void> => {
const { deployments, getNamedAccounts } = hre
Expand Down Expand Up @@ -141,6 +161,27 @@ const deploy: DeployFunction = async (hre: HardhatRuntimeEnvironment): Promise<v
deterministicDeployment: true
})

await deploy(safeWebAuthnSignerFactoryDeployed.name, {
from: deployer,
args: [],
log: true,
deterministicDeployment: true
})

await deploy(safeWebAuthnSharedSignerDeployed.name, {
from: deployer,
args: [],
log: true,
deterministicDeployment: true
})

await deploy('WebAuthnContract', {
from: deployer,
args: [],
log: true,
deterministicDeployment: true
})

await deploy('DailyLimitModule', {
from: deployer,
args: [],
Expand Down
Loading
Loading