Skip to content
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
7 changes: 7 additions & 0 deletions .changeset/young-readers-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@openzeppelin/wizard': patch
'@openzeppelin/wizard-common': patch
'@openzeppelin/contracts-mcp': patch
---

Solidity account signer: Add `WebAuthn` to the list of signers available.
5 changes: 3 additions & 2 deletions packages/common/src/ai/descriptions/solidity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,11 @@ export const solidityAccountDescriptions = {
signer: `Defines the signature verification algorithm used by the account to verify user operations. Options:
- ECDSA: Standard Ethereum signature validation using secp256k1, validates signatures against a specified owner address
- EIP7702: Special ECDSA validation using account's own address as signer, enables EOAs to delegate execution rights
- Multisig: ERC-7913 multisignature requiring minimum number of signatures from authorized signers
- MultisigWeighted: ERC-7913 weighted multisignature where signers have different voting weights
- P256: NIST P-256 curve (secp256r1) validation for integration with Passkeys and HSMs
- RSA: RSA PKCS#1 v1.5 signature validation (RFC8017) for PKI systems and HSMs
- Multisig: ERC-7913 multisignature requiring minimum number of signatures from authorized signers
- MultisigWeighted: ERC-7913 weighted multisignature where signers have different voting weights`,
- WebAuthn: Web Authentication (WebAuthn) assertion validation for integration with Passkeys and HSMs on top of P256`,
batchedExecution:
'Whether to implement a minimal batching interface for the account to allow multiple operations to be executed in a single transaction following the ERC-7821 standard.',
ERC7579Modules:
Expand Down
3 changes: 2 additions & 1 deletion packages/core/solidity/src/account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { account } from '.';
import type { AccountOptions } from './account';
import { buildAccount } from './account';
import { printContract } from './print';
import { SignerOptions } from './signer';

/**
* Tests external API for equivalence with internal API
Expand Down Expand Up @@ -61,7 +62,7 @@ function format(upgradeable: false | 'uups' | 'transparent') {
}
}

for (const signer of [false, 'ECDSA', 'EIP7702', 'P256', 'RSA', 'Multisig', 'MultisigWeighted'] as const) {
for (const signer of SignerOptions) {
for (const upgradeable of [false, 'uups', 'transparent'] as const) {
if (signer === 'EIP7702' && !!upgradeable) continue;

Expand Down
5,796 changes: 3,751 additions & 2,045 deletions packages/core/solidity/src/account.test.ts.md

Large diffs are not rendered by default.

Binary file modified packages/core/solidity/src/account.test.ts.snap
Binary file not shown.
16 changes: 10 additions & 6 deletions packages/core/solidity/src/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,12 +247,16 @@ function overrideRawSignatureValidation(c: ContractBuilder, opts: AccountOptions
const signerBaseName = signers[opts.signer].name;
const signerName = opts.upgradeable ? upgradeableName(signerBaseName) : signerBaseName;

c.addImportOnly({
name: 'AbstractSigner',
path: '@openzeppelin/contracts/utils/cryptography/signers/AbstractSigner.sol',
transpiled: false,
});
c.addOverride({ name: 'AbstractSigner', transpiled: false }, signerFunctions._rawSignatureValidation);
// WebAuthnSigner depends inherits from P256Signer, so the AbstractSigner override is handled by `addSigner`
if (opts.signer !== 'WebAuthn') {
c.addImportOnly({
name: 'AbstractSigner',
path: '@openzeppelin/contracts/utils/cryptography/signers/AbstractSigner.sol',
transpiled: false,
});
c.addOverride({ name: 'AbstractSigner', transpiled: false }, signerFunctions._rawSignatureValidation);
}

c.addOverride({ name: 'AccountERC7579' }, signerFunctions._rawSignatureValidation);
c.setFunctionComments(
[
Expand Down
2 changes: 1 addition & 1 deletion packages/core/solidity/src/generate/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const account = {
signatureValidation: [false, 'ERC1271', 'ERC7739'] as const,
ERC721Holder: [false, true] as const,
ERC1155Holder: [false, true] as const,
signer: ['ECDSA', 'EIP7702', 'P256', 'RSA', 'Multisig', 'MultisigWeighted'] as const,
signer: ['ECDSA', 'EIP7702', 'Multisig', 'MultisigWeighted', 'P256', 'RSA', 'WebAuthn'] as const,
batchedExecution: [false, true] as const,
ERC7579Modules: [false, 'AccountERC7579', 'AccountERC7579Hooked'] as const,
access: [false] as const,
Expand Down
58 changes: 46 additions & 12 deletions packages/core/solidity/src/signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@ import { OptionsError } from './error';
import type { Upgradeable } from './set-upgradeable';
import { defineFunctions } from './utils/define-functions';

export const SignerOptions = [false, 'ECDSA', 'EIP7702', 'P256', 'RSA', 'Multisig', 'MultisigWeighted'] as const;
export const SignerOptions = [
false,
'ECDSA',
'EIP7702',
'Multisig',
'MultisigWeighted',
'P256',
'RSA',
'WebAuthn',
] as const;
export type SignerOptions = (typeof SignerOptions)[number];

export function addSigner(c: ContractBuilder, signer: SignerOptions, upgradeable: Upgradeable): void {
Expand Down Expand Up @@ -34,6 +43,26 @@ export function addSigner(c: ContractBuilder, signer: SignerOptions, upgradeable
);
break;
}
case 'WebAuthn': {
signerArgs.P256.forEach(arg => c.addConstructorArgument(arg));
c.addParent(
signers.P256,
signerArgs.P256.map(arg => ({ lit: arg.name })),
);
c.addParent(signers[signer]);
c.addImportOnly({
name: 'AbstractSigner',
path: '@openzeppelin/contracts/utils/cryptography/signers/AbstractSigner.sol',
transpiled: false,
});
c.addOverride({ name: 'AbstractSigner', transpiled: false }, signerFunctions._rawSignatureValidation);
c.addOverride({ name: 'SignerP256' }, signerFunctions._rawSignatureValidation);
break;
}
default: {
const _: never = signer;
throw new Error('Unknown signer');
}
}
}

Expand All @@ -46,6 +75,14 @@ export const signers = {
name: 'SignerEIP7702',
path: '@openzeppelin/contracts/utils/cryptography/signers/SignerEIP7702.sol',
},
Multisig: {
name: 'MultiSignerERC7913',
path: '@openzeppelin/contracts/utils/cryptography/signers/MultiSignerERC7913.sol',
},
MultisigWeighted: {
name: 'MultiSignerERC7913Weighted',
path: '@openzeppelin/contracts/utils/cryptography/signers/MultiSignerERC7913Weighted.sol',
},
P256: {
name: 'SignerP256',
path: '@openzeppelin/contracts/utils/cryptography/signers/SignerP256.sol',
Expand All @@ -54,13 +91,9 @@ export const signers = {
name: 'SignerRSA',
path: '@openzeppelin/contracts/utils/cryptography/signers/SignerRSA.sol',
},
Multisig: {
name: 'MultiSignerERC7913',
path: '@openzeppelin/contracts/utils/cryptography/signers/MultiSignerERC7913.sol',
},
MultisigWeighted: {
name: 'MultiSignerERC7913Weighted',
path: '@openzeppelin/contracts/utils/cryptography/signers/MultiSignerERC7913Weighted.sol',
WebAuthn: {
name: 'SignerWebAuthn',
path: '@openzeppelin/contracts/utils/cryptography/signers/SignerWebAuthn.sol',
},
};

Expand All @@ -70,10 +103,6 @@ export const signerArgs: Record<Exclude<SignerOptions, false | 'EIP7702'>, { nam
{ name: 'qx', type: 'bytes32' },
{ name: 'qy', type: 'bytes32' },
],
RSA: [
{ name: 'e', type: 'bytes memory' },
{ name: 'n', type: 'bytes memory' },
],
Multisig: [
{ name: 'signers', type: 'bytes[] memory' },
{ name: 'threshold', type: 'uint64' },
Expand All @@ -83,6 +112,11 @@ export const signerArgs: Record<Exclude<SignerOptions, false | 'EIP7702'>, { nam
{ name: 'weights', type: 'uint64[] memory' },
{ name: 'threshold', type: 'uint64' },
],
RSA: [
{ name: 'e', type: 'bytes memory' },
{ name: 'n', type: 'bytes memory' },
],
WebAuthn: [],
};

export const signerFunctions = defineFunctions({
Expand Down
5 changes: 3 additions & 2 deletions packages/mcp/src/solidity/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,11 @@ export const accountSchema = {
.literal(false)
.or(z.literal('ECDSA'))
.or(z.literal('EIP7702'))
.or(z.literal('P256'))
.or(z.literal('RSA'))
.or(z.literal('Multisig'))
.or(z.literal('MultisigWeighted'))
.or(z.literal('P256'))
.or(z.literal('RSA'))
.or(z.literal('WebAuthn'))
.optional()
.describe(solidityAccountDescriptions.signer),
batchedExecution: z.boolean().optional().describe(solidityAccountDescriptions.batchedExecution),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ export const solidityAccountAIFunctionDefinition = {
signer: {
anyOf: [
{ type: 'boolean', enum: [false] },
{ type: 'string', enum: ['ECDSA', 'EIP7702', 'P256', 'RSA', 'Multisig', 'MultisigWeighted'] },
{ type: 'string', enum: ['ECDSA', 'EIP7702', 'P256', 'Multisig', 'MultisigWeighted', 'RSA', 'WebAuthn'] },
],
description: solidityAccountDescriptions.signer,
},
Expand Down
7 changes: 7 additions & 0 deletions packages/ui/src/solidity/AccountControls.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,13 @@
hardware security modules that use RSA keys.
</HelpTooltip>
</label>
<label class:checked={opts.signer === 'WebAuthn'}>
<input type="radio" bind:group={opts.signer} value="WebAuthn" />
WebAuthn
<HelpTooltip link="https://docs.openzeppelin.com/contracts/5.x/api/utils/cryptography#WebAuthn">
Web Authentication (WebAuthn) assertion validation for integration with Passkeys and HSMs on top of P256.
</HelpTooltip>
</label>
</div>
</ExpandableToggleRadio>

Expand Down
Loading