On-chain OAuth wallets on Starknet. Verify Google/Apple JWTs directly in a Cairo smart contract — no backend, no custodian.
cavos-account implements an account contract (SRC-6) that authenticates users via OAuth JWTs instead of traditional private keys. The contract:
- Verifies RSA-2048 / SHA-256 JWT signatures on-chain using Garaga
- Ties ephemeral session keys to JWT nonces (Poseidon-based binding)
- Validates issuer (
iss) and expiry (exp) claims from the signed JWT payload - Enforces per-session spending policies (allowed contracts, call limits, time windows)
- Supports Google, Apple, and Firebase as identity providers
cavos_account.cairo — Main SRC-6 account contract
jwks_registry.cairo — Admin-controlled public key store
deployer.cairo — Deterministic account deployment (address = f(iss, sub, salt))
jwt/
base64.cairo — Base64url decoder
jwt_parser.cairo — JWT claim parsing utilities
utils/
address_seed.cairo — Poseidon(iss, sub, salt) → deterministic address seed
nonce.cairo — Poseidon(eph_pubkey, max_block, randomness) → nonce
base64url.cairo — Re-exports
- Scarb 2.14.0
- starknet-foundry 0.53.0+
# Install Scarb
curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh
# Install snforge
curl -L https://raw.githubusercontent.com/foundry-rs/starknet-foundry/master/scripts/install.sh | sh
# Build
scarb build
# Test
snforge testJWT signatures are verified on-chain using Garaga's RSA-2048 implementation — an audited, ~11.8M gas verifier based on multi-precision arithmetic over RNS channels.
use garaga::signatures::rsa::{
RSA2048PublicKey, RSA2048SignatureWithHint,
is_valid_rsa2048_sha256_signature,
};
// Construct public key from 24 × u96 limbs stored in JWKSKey
let public_key = RSA2048PublicKey { modulus: key.to_rsa2048_chunks() };
// Verify: internally computes SHA-256 + PKCS#1 v1.5 + s^65537 mod n
assert!(
is_valid_rsa2048_sha256_signature(@sig_with_hint, @public_key, @jwt_signed_bytes),
'Invalid JWT signature',
);from garaga.starknet.tests_and_calldata_generators.signatures import RSA2048Signature
sig = RSA2048Signature.from_sha256_message(jwt_signed_bytes, seed=0)
calldata = sig.serialize_sha256_with_hints(
message=jwt_signed_bytes, prepend_public_key=False
)
# → 864 felt252 values: sig(24) + encoded_msg(24) + reductions(17×48)import { rsa2048CalldataBuilder } from 'garaga';
const calldata = rsa2048CalldataBuilder(
sigBigInt, // RSA signature as bigint
msgBytes, // JWT header.payload as Uint8Array
modulusBigInt, // RSA modulus as bigint
false // don't prepend public key (already in registry)
);
// calldata.length === 864Garaga encodes the SHA-256 digest as:
0x00 || 0x01 || 0xFF×202 || 0x00 || DigestInfo(19 bytes) || SHA-256(32 bytes)
This is verified inside is_valid_rsa2048_sha256_signature — no manual encoding needed in the contract.
Public keys are stored as 24 × felt252 limbs (96-bit words, little-endian), matching Garaga's RSA2048Chunks layout:
pub struct JWKSKey {
pub n0: felt252, pub n1: felt252, // ...
pub n23: felt252,
pub provider: felt252, // same felt value as the JWT `iss` this key is allowed to verify
pub valid_until: u64,
pub is_active: bool,
}Extract limbs from a PEM/JWK modulus (Python):
mask = (1 << 96) - 1
limbs = [(n >> (96 * i)) & mask for i in range(24)]- User authenticates with Google/Apple → receives JWT
- SDK generates ephemeral key pair
- Nonce =
Poseidon(eph_pubkey_lo, eph_pubkey_hi, max_block, randomness)embedded in OAuth request - Contract verifies JWT signature against registered JWKS public key
- Session key is bound to the account for
max_blockblocks
[0] Magic (OAUTH_JWT_V1)
[1] ECDSA r
[2] ECDSA s
[3] Session public key
[4] valid_until
[5] randomness
[6] jwt_sub
[7] jwt_nonce
[8] jwt_exp
[9] jwt_kid (Poseidon hash of kid string)
[10] jwt_iss (Poseidon hash of issuer string)
[11] salt
[12] wallet_name
[13-18] Claim offsets (sub, nonce, kid positions in raw JWT)
[19] Garaga RSA blob length (864)
[20-883] RSA2048SignatureWithHint (sig×24 + msg×24 + 17×48 reductions)
[884] JWT bytes length
[885+] Packed JWT bytes (31-byte chunks)
[after] Spending policy
See CONTRIBUTING.md.
MIT — see LICENSE.