Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -246,12 +246,16 @@ function overrideRawSignatureValidation(c: ContractBuilder, opts: AccountOptions
const accountName = opts.upgradeable ? upgradeableName('AccountERC7579') : 'AccountERC7579';
const signerName = opts.upgradeable ? upgradeableName(`Signer${opts.signer}`) : `Signer${opts.signer}`;

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