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
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@
"turbo": "latest",
"typescript": "5.5.4",
"typescript-eslint": "^8.0.0",
"viem": "2.31.4",
"webauthn-p256": "^0.0.10"
"viem": "2.31.4"
},
"packageManager": "yarn@4.10.1",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/smart-accounts-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@
"@metamask/delegation-core": "^0.4.0",
"@metamask/delegation-deployments": "^0.17.0",
"buffer": "^6.0.3",
"webauthn-p256": "^0.0.10"
"ox": "0.8.1"
},
"devDependencies": {
"@lavamoat/allow-scripts": "^3.4.0",
Expand Down
12 changes: 7 additions & 5 deletions packages/smart-accounts-kit/src/signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
TypedData,
TypedDataDefinition,
} from 'viem';
import type { SignReturnType as WebAuthnSignReturnType } from 'webauthn-p256';
import type { WebAuthnAccount } from 'viem/account-abstraction';

import { Implementation } from './constants';
import { aggregateSignature } from './signatures';
Expand Down Expand Up @@ -72,12 +72,14 @@ const resolveHybridSigner = (config: HybridSignerConfig): InternalSigner => {
throw new Error('Account is not a webAuthn account');
}

const encodeSignature = ({ signature, webauthn }: WebAuthnSignReturnType) =>
const encodeSignature = (
result: Awaited<ReturnType<WebAuthnAccount['signMessage']>>,
) =>
encodeDeleGatorSignature(
keyId,
signature,
webauthn.clientDataJSON,
webauthn.authenticatorData,
result.signature,
result.webauthn.clientDataJSON,
result.webauthn.authenticatorData,
);

const signMessage = async (args: { message: SignableMessage }) =>
Expand Down
16 changes: 7 additions & 9 deletions packages/smart-accounts-kit/src/webAuthn.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Signature } from 'ox';
import {
parseAbiParameters,
encodeAbiParameters,
Expand All @@ -7,7 +8,6 @@ import {
concat,
hexToBytes,
} from 'viem';
import { parseSignature } from 'webauthn-p256';

export const FIELD_MODULUS =
115792089210356248762697446949407573529996955224135760342422259061068512044369n;
Expand Down Expand Up @@ -96,16 +96,14 @@ export function encodeDeleGatorSignature(
): Hex {
const keyIdHash = keccak256(encodePacked(['string'], [keyId]));

const parsedSignature = parseSignature(signature);
const { r: rValue, s: sValue } = Signature.fromHex(signature);

let { s } = parsedSignature;
let normalizedS = sValue;

while (s > MALLEABILITY_THRESHOLD) {
s = FIELD_MODULUS - s;
while (normalizedS > MALLEABILITY_THRESHOLD) {
normalizedS = FIELD_MODULUS - normalizedS;
}

const { r } = parsedSignature;

const [clientDataComponent1, clientDataComponent2] =
splitOnChallenge(clientDataJSON);

Expand All @@ -115,8 +113,8 @@ export function encodeDeleGatorSignature(

const encodedSignature = encodeAbiParameters(SIGNATURE_ABI_PARAMS, [
keyIdHash,
r,
s,
rValue,
normalizedS,
authenticatorData,
userVerified,
clientDataComponent1,
Expand Down
18 changes: 16 additions & 2 deletions packages/smart-accounts-kit/test/signer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,26 @@ describe('resolveSigner', () => {
userVerificationRequired: true,
} as const;

// Mock `WebAuthnAccount.signature`: the same shape Viem produces via `Signature.toHex` after
// `WebAuthnP256.sign` — 32-byte r and 32-byte s concatenated (130-character 0x-prefixed hex).
// These scalars match the fixture used in `webAuthn.test.ts` (P-256 values from a real passkey-style
// assertion, not DER). If you change `webauthn` or the hybrid `keyId` below, re-derive by calling
// `encodeDeleGatorSignature` or run the test once and paste the actual output.
const rawSignature =
'0x304502204e2fe56da5cc6f6ca66813cf57c1b6650c7f1b974d8d2a8c742655eb505e0052022100ce082e098a86ea36c6ab96cad392445961627f6b65bc684bc65c4f06117a907d' as const;
'0x4e2fe56da5cc6f6ca66813cf57c1b6650c7f1b974d8d2a8c742655eb505e005231f7d1f5757915ca395469352c6dbba65b847b42415b36392d5d7bbceae894d4' as const;

// Correct output of `resolveHybridSigner` → `signMessage` / `signTypedData`: internally
// `encodeDeleGatorSignature(keyId, rawSignature, webauthn.clientDataJSON, webauthn.authenticatorData)`
// where `keyId` is the hybrid signer config key (`0x1234…` below).
// ABI payload: keccak(keyId) ‖ r ‖ normalized s ‖ authenticatorData ‖ flags ‖ clientData prefix/suffix ‖
// responseTypeLocation.
const expectedSignature =
'0x55608cdbde2ff4183a81e62da096fa863d8f910d29d17826124fccc9bcc11f62304502204e2fe56da5cc6f6ca66813cf57c1b6650c7f1b974d8d2a8c742655eb505e0052022100ce082e098a86ea36c6ab96cad392445961627f6b65bc684bc600000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002549960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038222c226f726967696e223a2268747470733a2f2f706173736b65792d6f726967696e222c2263726f73734f726967696e223a66616c73657d0000000000000000';
'0x55608cdbde2ff4183a81e62da096fa863d8f910d29d17826124fccc9bcc11f624e2fe56da5cc6f6ca66813cf57c1b6650c7f1b974d8d2a8c742655eb505e005231f7d1f5757915ca395469352c6dbba65b847b42415b36392d5d7bbceae894d400000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002549960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038222c226f726967696e223a2268747470733a2f2f706173736b65792d6f726967696e222c2263726f73734f726967696e223a66616c73657d0000000000000000';

// Correct output of `getStubSignature`: `createDummyWebAuthnSignature` with the same hybrid `keyId`.
// Same ABI layout as `expectedSignature`, but fixed placeholder r/s, synthetic authenticator data, and
// client JSON slices from `createDummyWebAuthnSignature` (`passkey-domain`, etc.). Recorded hex matches
// that helper’s deterministic output for this `keyId`.
const expectedStubSignature =
'0x55608cdbde2ff4183a81e62da096fa863d8f910d29d17826124fccc9bcc11f627fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a87fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a800000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000253c55a232fc5832375a85c15dd44e43a8ed84e8a1eff53e743b7138bda4cc189e050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030222c226f726967696e223a22706173736b65792d646f6d61696e222c2263726f73734f726967696e223a66616c73657d00000000000000000000000000000000';
const webAuthnAccount: WebAuthnAccount = {
Expand Down
6 changes: 3 additions & 3 deletions packages/smart-accounts-kit/test/webAuthn.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Buffer as BufferPolyfill } from 'buffer/';
import { Signature } from 'ox';
import {
decodeAbiParameters,
isHex,
Expand All @@ -8,7 +9,6 @@ import {
serializeSignature,
} from 'viem';
import { describe, it, expect } from 'vitest';
import { parseSignature } from 'webauthn-p256';

import {
MALLEABILITY_THRESHOLD,
Expand Down Expand Up @@ -47,7 +47,7 @@ export const validWebAuthnP256SignatureData = {
const validWebAuthnData = {
keyId: '0x8d39ed41281a2b1d3b61dcc5cd4a0697c6831052b9c91840c62b58caba12ce06',
signature:
'0x304502204e2fe56da5cc6f6ca66813cf57c1b6650c7f1b974d8d2a8c742655eb505e0052022100ce082e098a86ea36c6ab96cad392445961627f6b65bc684bc65c4f06117a907d',
'0x4e2fe56da5cc6f6ca66813cf57c1b6650c7f1b974d8d2a8c742655eb505e005231f7d1f5757915ca395469352c6dbba65b847b42415b36392d5d7bbceae894d4',
encodedSignature:
'0xe66a1dfc4ba036bb7cc16ab3a58bdfad55a61c83f6712d948956be9b2f9990f24e2fe56da5cc6f6ca66813cf57c1b6650c7f1b974d8d2a8c742655eb505e005231f7d1f5757915ca395469352c6dbba65b847b42415b36392d5d7bbceae894d400000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002549960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038222c226f726967696e223a2268747470733a2f2f706173736b65792d6f726967696e222c2263726f73734f726967696e223a66616c73657d0000000000000000',
authenticatorData:
Expand Down Expand Up @@ -122,7 +122,7 @@ describe('webAuthn', () => {
clientDataJSONSuffix,
} = validWebAuthnData;

const rawSignature = parseSignature(signature);
const rawSignature = Signature.fromHex(signature);

const expectedKeyIdHash = keccak256(encodePacked(['string'], [keyId]));

Expand Down
17 changes: 3 additions & 14 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,7 @@ __metadata:
eslint-plugin-n: "npm:^17.10.3"
eslint-plugin-prettier: "npm:^5.5.4"
eslint-plugin-promise: "npm:^7.1.0"
ox: "npm:0.8.1"
prettier: "npm:^3.5.3"
sinon: "npm:^18.0.0"
ts-node: "npm:^10.9.2"
Expand All @@ -807,7 +808,6 @@ __metadata:
typescript-eslint: "npm:^8.0.0"
viem: "npm:2.31.4"
vitest: "npm:^3.2.4"
webauthn-p256: "npm:^0.0.10"
peerDependencies:
viem: ^2.31.4
languageName: unknown
Expand Down Expand Up @@ -892,7 +892,7 @@ __metadata:
languageName: node
linkType: hard

"@noble/curves@npm:^1.4.0, @noble/curves@npm:^1.9.1, @noble/curves@npm:~1.9.0":
"@noble/curves@npm:^1.9.1, @noble/curves@npm:~1.9.0":
version: 1.9.7
resolution: "@noble/curves@npm:1.9.7"
dependencies:
Expand All @@ -908,7 +908,7 @@ __metadata:
languageName: node
linkType: hard

"@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:^1.8.0, @noble/hashes@npm:~1.8.0":
"@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:^1.8.0, @noble/hashes@npm:~1.8.0":
version: 1.8.0
resolution: "@noble/hashes@npm:1.8.0"
checksum: 10/474b7f56bc6fb2d5b3a42132561e221b0ea4f91e590f4655312ca13667840896b34195e2b53b7f097ec080a1fdd3b58d902c2a8d0fbdf51d2e238b53808a177e
Expand Down Expand Up @@ -2627,7 +2627,6 @@ __metadata:
typescript: "npm:5.5.4"
typescript-eslint: "npm:^8.0.0"
viem: "npm:2.31.4"
webauthn-p256: "npm:^0.0.10"
languageName: unknown
linkType: soft

Expand Down Expand Up @@ -6583,16 +6582,6 @@ __metadata:
languageName: node
linkType: hard

"webauthn-p256@npm:^0.0.10":
version: 0.0.10
resolution: "webauthn-p256@npm:0.0.10"
dependencies:
"@noble/curves": "npm:^1.4.0"
"@noble/hashes": "npm:^1.4.0"
checksum: 10/dde2b6313b6a0f20996f7ee90181258fc7685bfff401df7d904578da75b374f25d5b9c1189cd2fcec30625b1f276b393188d156d49783f0611623cd713bb5b09
languageName: node
linkType: hard

"webidl-conversions@npm:^4.0.2":
version: 4.0.2
resolution: "webidl-conversions@npm:4.0.2"
Expand Down
Loading