Skip to content

Architecture

This is the high-level architecture of the Stellar Passkey Kit — the SCF-43 RFP submission for adding Soroban smart-account passkey support to the Stellar wallet ecosystem.

Diagram

Plain-English walkthrough

What each box does

  • WebAuthn API — Built into every modern browser. Talks to the platform authenticator (Touch ID, Windows Hello, security key) over CTAP2. The kit never sees the user's biometric data; only the public half of a P-256 keypair and the per-ceremony assertion bytes ever leave the secure enclave.
  • @stellar-passkey/core — Browser-side TypeScript SDK. Drives the five canonical ceremonies — createPasskey, connectPasskey, signTransaction, signAuthEntry, recoverPasskey. Does the heavy lifting on the WebAuthn ↔ Soroban auth boundary: CBOR-decodes authenticator data, extracts the SEC-1 uncompressed P-256 key, normalises Apple's high-S signatures to low-S client-side (CAP-0051 on-chain secp256r1_verify deliberately rejects high-S, see Stellar protocol #1435), and packs the WebAuthn assertion into the contract's Signature ScVal.
  • @stellar-passkey/ui — Three framework-agnostic Lit Web Components (<passkey-create-button>, <passkey-sign-tx>, <passkey-recover>) consuming the core SDK. Themable via CSS custom properties, no React/Vue dependency.
  • @creit.tech/stellar-wallets-kit (fork) — The community SEP-43 multi-wallet aggregator. Our fork adds a PasskeyModule that sits alongside Freighter / Albedo / WalletConnect etc.; consumers wire it with two callbacks (signTransaction, signAuthEntry) pointing at the reference SDK above. The upstream Draft PR lands at the end of Day 7 (YK-288).
  • soroban-rpc — Stellar testnet's Soroban RPC endpoint. Used for getLatestLedger (to compute signatureExpirationLedger), simulateTransaction (to populate resource footprints and SorobanAuthorizationEntrys before signing) and sendTransaction.
  • Smart-Account Contract — The Rust soroban-sdk 23 contract (packages/contract) deployed once per WASM and instantiated per user. Its __check_auth entry point reconstructs the WebAuthn preimage hash from authenticatorData ‖ sha256(clientDataJSON) and feeds it through CAP-0051's host-provided secp256r1_verify. Per user, exactly one instance lives on-chain.

Cryptographic flow

  1. Register. Browser calls navigator.credentials.create({ rpId, pubKeyCredParams: [{ type: "public-key", alg: -7 }] })-7 is ES256 (P-256 + SHA-256); RS256 is hard-rejected, the on-chain verifier only supports secp256r1. The authenticator returns attestationObject whose CBOR-encoded authData contains the public key in COSE form. The SDK extracts the SEC-1 uncompressed bytes 0x04 ‖ X ‖ Y (65 bytes) and hands them to the wallet contract's bootstrap-admin call.
  2. Deploy. A DeployerCallbacks (wired by the consumer — backend, Launchtube relay, browser-wallet, etc.) issues CreateContract(walletWasmHash, salt) and then add(credentialId, pk, admin=true) on the freshly-deployed wallet. The SDK never holds a Stellar signing key.
  3. Sign. For each Soroban auth entry whose credentials.address matches the active wallet, the SDK:
    • sets signatureExpirationLedger = currentLedger + 60 (rpc.prepareTransaction leaves this zero — required-to-set, see CHANGELOG entry on YK-253);
    • hashes the HashIdPreimage::SorobanAuthorization envelope (this is the signature_payload: Hash<32> the contract expects);
    • calls navigator.credentials.get({ allowCredentials: [credentialId] }) with that hash as challenge;
    • verifies the assertion's clientDataJSON.challenge matches (replay defence);
    • converts DER → raw r ‖ s, applies low-S normalisation, packs into the contract's Signature ScVal in canonical map order;
    • mutates the auth entry in-place.
    • re-simulates the transaction (SorobanRpc.assembleTransaction) so the resource footprint reflects the real signature size.
  4. Recover. When localStorage is gone (new device, cleared cache), the SDK drives a discoverable WebAuthn ceremony (no allowCredentials), receives the user-chosen credentialId, and queries getEvents for ("sw_v1","add", credentialId) topics. Each matching event's emitting contract is a wallet bound to that passkey — the user picks one.

Where user data lives

DataLocationNotes
Biometric templateUser device secure enclaveNever leaves the device.
Private key (P-256)Authenticator secure enclave (TPM / Secure Element)Non-extractable.
Public key (P-256, 65 B)On-chain in the smart-account contract's storageIndexed by credentialId.
credentialIdlocalStorage (key @stellar-passkey/lastSession) and on-chain in contract eventsStorage record carries only (credentialId, rpId, createdAt); NOT the contract address — that is re-derived from chain state on every connectPasskey to prevent storage-tampering attacks (see storage.ts).
Contract C… addressRe-derived from ("sw_v1","add", credentialId) events on-chainNever persisted client-side by the kit.
Stellar signing keyNever held by the kit.Wired by the caller via DeployerCallbacks (backend, Launchtube, browser-wallet, test-harness).

Network surface

The SDK talks to one off-chain endpoint: https://soroban-testnet.stellar.org (or its mainnet equivalent). No analytics, no telemetry, no central coordination server. Transaction submission can additionally hit Horizon or Launchtube if the consumer wires those into DeployerCallbacks; both are optional.

Authoritative references

MIT — SCF-43 RFP submission (2026). Status: pre-1.0.