Skip to content

Add a Passkey module for Soroban smart wallets#94

Open
Signor1 wants to merge 2 commits into
Creit-Tech:mainfrom
jes-labs:feat/passkey-module
Open

Add a Passkey module for Soroban smart wallets#94
Signor1 wants to merge 2 commits into
Creit-Tech:mainfrom
jes-labs:feat/passkey-module

Conversation

@Signor1

@Signor1 Signor1 commented Jun 12, 2026

Copy link
Copy Markdown

What kind of change does this PR introduce?

Feature: A new wallet module adding passkey support . Soroban smart wallets whose signer is a WebAuthn credential, verified on-chain through native secp256r1 (Protocol 21). Users create and use a wallet with Face ID / Touch ID / a fingerprint. No extension, no seed phrase.

We opened this #93, and built to follow the guidance shared here #90 : a regular module like the other wallets, with auth-entries signing as the realistic path and honest behavior where SEP-43's source-account assumption and contract accounts diverge. Related interest: #91.

What is the current behavior?

The kit has no passkey option. Every Stellar app that wants passkey smart wallets builds the WebAuthn plumbing, the Soroban payload construction, and the contract signature encoding from scratch, outside the kit.

What is the new behavior (if this is a feature change)?

A self-contained PasskeyModule under src/sdk/modules/passkey/, registered like any other module:

 import { PasskeyModule } from "@creit.tech/stellar-wallets-kit/modules/passkey";

 StellarWalletsKit.init({
   network,
   modules: [
     new PasskeyModule({
       networkPassphrase: Networks.TESTNET,
       rpName: "My App",
       deployer: FACTORY_DEPLOYER, // or getWalletAddress: (credentialId) => ...
     }),
     ...defaultModules(),
   ],
 });

Behavior, mapped to the source-account discussion:

  • getAddress connects by proving control of a passkey: the browser offers existing discoverable credentials, or creates a new one, and the wallet's C-address is resolved by deterministic derivation (the passkey-kit factory convention, salt = sha256(credentialId)) or a caller-provided resolver (e.g. an indexer). Deploying the wallet contract stays the application's job.
  • signAuthEntry is the native operation: it computes the Soroban authorization preimage hash, runs the WebAuthn ceremony with it as the challenge, and attaches the encoded signature to the entry.
  • signTransaction does what a contract account actually can: it signs every Soroban authorization entry in the transaction that belongs to this wallet and leaves
    source/fee/sequence untouched for whoever submits (e.g. Launchtube). If the transaction carries no auth entries for the wallet, it rejects with an error explaining exactly that.
  • signMessage is rejected with a clear error — arbitrary message signing has no standard on-chain meaning for a contract account.
  • The contract signature layout is pluggable via a signatureEncoder param; the default targets the kalepail/passkey-kit (https://github.com/kalepail/passkey-kit) smart-wallet contracts, the de-facto standard for passkeys on Stellar.

Zero new dependencies: @stellar/stellar-base and @std/encoding (already in the kit) plus the WebAuthn browser API. The two small pieces of signature math (DER → compact with low-S normalization, P-256 curve membership) are plain BigInt arithmetic over public data.

How it was verified:

  • deno check, deno lint, deno fmt, and the full deno task build-npm (dnt) all pass.
  • The signature math is cross-checked against @noble/curves over 1,000+ cases, including forced high-S malleable forms and malformed-input rejection.
  • The ScVal encoding is structurally verified against a real signed smart-wallet transaction from passkey-kit's fixtures.
  • The built npm package was exercised end to end in Chromium with a virtual authenticator, through the kit's own facade: authModal showing the module in the picker, fetchAddress running a real registration ceremony, signAuthEntry over a realistic auth entry — with the resulting signature, challenge binding, and ScVal layout verified by an independent
    implementation (test suite in the repo linked below).
  • Tested by hand in a real browser with Touch ID: the kit modal → Passkey → biometric prompt → C-address connected.

Other information:

This is part of the SCF "Passkey UI" RFP, which requires the passkey SDK to be adopted into Stellar Wallets Kit rather than shipped as a parallel package. The compatibility knowledge behind the module's behavior (what breaks across devices/browsers, with fallbacks, verified on real hardware) lives here:

Live demo of this module: https://wallet-passkey-demo.vercel.app — and a recording is in the comments below.

One design question:
Is HOT_WALLET the right ModuleType here, or do smart accounts warrant a dedicated type for filtering? Happy to adjust.

Two transparency notes: end-to-end verification against a deployed smart wallet on testnet is in progress on our side (coordinating with Tyler on the contract lineage) — I'll post the transaction link on this PR as soon as it lands. And the icon is an inlined SVG so the module ships without external assets; if you prefer it hosted on stellar.creit.tech like the others, point me and I'll switch.

@Signor1

Signor1 commented Jun 12, 2026

Copy link
Copy Markdown
Author

Demo: the module connected from the kit's modal and signing with Touch ID

wallet-passkey-kit.mp4

@Signor1

Signor1 commented Jun 12, 2026

Copy link
Copy Markdown
Author

Following up on the transparency note: on-chain verification is done. We deployed a smart wallet on testnet from the unmodified passkey-kit contracts, and moved 25 XLM out of it authorized by a secp256r1 WebAuthn signature, verified by the contract's __check_auth:

Doing this surfaced one fix, now pushed to this branch: the contract's Signatures type is a one-field tuple struct, so it encodes as a Vec wrapping the map. The module's encoding is now byte-identical to the contract's Rust SDK (we generate both from the same fixture and compare). The address derivation also checks out: the C-address the module derives offline matched the on-chain deployment exactly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant