@biconomy/passkey
Version:
Passkey validator plug in for Biconomy SDK
205 lines • 7.88 kB
JavaScript
import { toModule } from "@biconomy/sdk";
import { startAuthentication } from "@simplewebauthn/browser";
import { encodeAbiParameters } from "viem";
import { PASSKEY_VALIDATOR_ADDRESS } from "./constants.js";
import { b64ToBytes, base64FromUint8Array, findQuoteIndices, hexStringToUint8Array, isRIP7212SupportedNetwork, parseAndNormalizeSig, uint8ArrayToHexString } from "./utils.js";
/**
* Signs a message using WebAuthn.
*
* @param message - The message to be signed.
* @param chainId - The chain ID for the network.
* @param allowCredentials - Optional credentials for authentication.
* @returns A promise that resolves to the encoded signature.
*/
const signMessageUsingWebAuthn = async (message, chainId, allowCredentials) => {
let messageContent;
if (typeof message === "string") {
// message is a string
messageContent = message;
}
else if ("raw" in message && typeof message.raw === "string") {
// message.raw is a Hex string
messageContent = message.raw;
}
else if ("raw" in message && message.raw instanceof Uint8Array) {
// message.raw is a ByteArray
messageContent = message.raw.toString();
}
else {
throw new Error("Unsupported message format");
}
// remove 0x prefix if present
const formattedMessage = messageContent.startsWith("0x")
? messageContent.slice(2)
: messageContent;
const challenge = base64FromUint8Array(hexStringToUint8Array(formattedMessage), true);
// prepare assertion options
const assertionOptions = {
challenge,
allowCredentials,
userVerification: "required"
};
// start authentication (signing)
const cred = await startAuthentication(assertionOptions);
// get authenticator data
const { authenticatorData } = cred.response;
const authenticatorDataHex = uint8ArrayToHexString(b64ToBytes(authenticatorData));
// get client data JSON
const clientDataJSON = atob(cred.response.clientDataJSON);
// get challenge and response type location
const { beforeType } = findQuoteIndices(clientDataJSON);
// get signature r,s
const { signature } = cred.response;
const signatureHex = uint8ArrayToHexString(b64ToBytes(signature));
const { r, s } = parseAndNormalizeSig(signatureHex);
// encode signature
const encodedSignature = encodeAbiParameters([
{ name: "authenticatorData", type: "bytes" },
{ name: "clientDataJSON", type: "string" },
{ name: "responseTypeLocation", type: "uint256" },
{ name: "r", type: "uint256" },
{ name: "s", type: "uint256" },
{ name: "usePrecompiled", type: "bool" }
], [
authenticatorDataHex,
clientDataJSON,
beforeType,
BigInt(r),
BigInt(s),
isRIP7212SupportedNetwork(chainId)
]);
return encodedSignature;
};
/**
* Creates a passkey validator module
*
* @param params - The parameters for creating the passkey validator
* @param params.webAuthnKey - The WebAuthn key data containing public key coordinates and authenticator info
* @param params.chainId - The chain ID of the network
* @param params.account - The Nexus account instance
*
* @returns A Promise that resolves to a Module instance configured for passkey validation through the @biconomy/sdk
*
* @example
* ```typescript
* import { createNexusClient, moduleActivator } from "@biconomy/sdk";
* import { http } from "viem";
* import { baseSepolia } from "viem/chains";
* import { WebAuthnMode, toWebAuthnKey } from "@biconomy/passkey";
*
* // Initialize Nexus client
* const nexusClient = await createNexusClient({
* signer: account,
* chain: baseSepolia,
* transport: http(),
* bundlerTransport: http("https://bundler.biconomy.io/api/v3/...")
* });
*
* // Create WebAuthn credentials
* const webAuthnKey = await toWebAuthnKey({
* passkeyName: "nexus",
* mode: WebAuthnMode.Register // Use Register for new passkey, Login for existing
* });
*
* // Create and initialize the passkey validator
* const passkeyValidator = await toPasskeyValidator({
* webAuthnKey,
* chainId: baseSepolia.id,
* account: nexusClient.account
* });
*
* // Install the validator module
* const opHash = await nexusClient.installModule({ module: passkeyValidator });
* await nexusClient.waitForUserOperationReceipt({ hash: opHash });
*
* // Activate the passkey validator
* nexusClient.extend(moduleActivator(passkeyValidator));
*
* // Use the passkey validator to sign and send transactions
* const txHash = await nexusClient.sendTransaction({
* calls: [{
* to: "0x...",
* value: 0n
* }]
* });
*
* // Wait for transaction confirmation
* const receipt = await nexusClient.waitForTransactionReceipt({ hash: txHash });
* ```
*/
export async function toPasskeyValidator({ webAuthnKey, account }) {
return toModule({
signer: account.signer,
address: PASSKEY_VALIDATOR_ADDRESS,
accountAddress: account.address,
signUserOpHash: async (userOpHash) => {
if (!account.client?.chain) {
throw new Error("Chain ID is required but not found in account client");
}
return signMessageUsingWebAuthn(userOpHash, account.client.chain.id, [{ id: webAuthnKey.authenticatorId, type: "public-key" }]);
},
initData: encodeAbiParameters([
{
components: [
{ name: "x", type: "uint256" },
{ name: "y", type: "uint256" }
],
name: "webAuthnData",
type: "tuple"
},
{
name: "authenticatorIdHash",
type: "bytes32"
}
], [
{
x: webAuthnKey.pubX,
y: webAuthnKey.pubY
},
webAuthnKey.authenticatorIdHash
]),
moduleInitData: {
initData: encodeAbiParameters([
{
components: [
{ name: "x", type: "uint256" },
{ name: "y", type: "uint256" }
],
name: "webAuthnData",
type: "tuple"
},
{
name: "authenticatorIdHash",
type: "bytes32"
}
], [
{
x: webAuthnKey.pubX,
y: webAuthnKey.pubY
},
webAuthnKey.authenticatorIdHash
]),
address: PASSKEY_VALIDATOR_ADDRESS,
type: "validator"
},
deInitData: "0x", // Add this line, adjust if needed
async getStubSignature() {
return encodeAbiParameters([
{ name: "authenticatorData", type: "bytes" },
{ name: "clientDataJSON", type: "string" },
{ name: "responseTypeLocation", type: "uint256" },
{ name: "r", type: "uint256" },
{ name: "s", type: "uint256" },
{ name: "usePrecompiled", type: "bool" }
], [
"0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97631d00000000",
'{"type":"webauthn.get","challenge":"tbxXNFS9X_4Byr1cMwqKrIGB-_30a0QhZ6y7ucM0BOE","origin":"http://localhost:3000","crossOrigin":false, "other_keys_can_be_added_here":"do not compare clientDataJSON against a template. See https://goo.gl/yabPex"}',
1n,
44941127272049826721201904734628716258498742255959991581049806490182030242267n,
9910254599581058084911561569808925251374718953855182016200087235935345969636n,
false
]);
}
});
}
//# sourceMappingURL=toPasskeyValidator.js.map