# Smirk Wallet Integration How Wownerogue integrates with the [Smirk](https://smirk.cash) browser extension for wallet-based authentication. --- ## Overview Smirk injects a `window.smirk` API (similar to MetaMask's `window.ethereum`) that websites can use to: 1. Request wallet connection (get public keys) 2. Request message signatures (for authentication) 3. Get wallet addresses (for payouts) Wownerogue uses the WOW (Wownero) key for authentication via a challenge-response signature flow. --- ## Authentication Flow ``` Client Server Extension | | | |-- POST /challenge --------->| | |<-------- { challenge } -----| | | | | |-- smirk.connect() ---------------------------------->| |<-------------- { publicKeys } -----------------------| | | | |-- smirk.signMessage(challenge) --------------------->| |<-------------- { signatures[] } ---------------------| | | | |-- POST /verify ------------>| | | { challenge, publicKey, | | | signature, socketId } | | |<------ { linked: true } ----| | | | | |-- smirk.getAddresses() ------------------------------>| |<-------------- { wow: "Wo..." } ---------------------| | | | |-- socket: address:update -->| | ``` --- ## API Endpoints ### POST `/api/auth/smirk/challenge` Generate a challenge for signature verification. **Request:** ```json { "socketId": "" } ``` **Response:** ```json { "challenge": "<64-char hex string>", "expiresIn": 300 } ``` The challenge is a random 32-byte hex string stored in the database with a 5-minute TTL. ### POST `/api/auth/smirk/verify` Verify a wallet signature and link it to the user session. **Request:** ```json { "socketId": "", "challenge": "", "publicKey": "", "signature": "" } ``` **Response (success):** ```json { "success": true, "linked": true, "message": "Smirk wallet linked successfully" } ``` **Errors:** - `400` - Missing fields, invalid/expired challenge, invalid signature, or wallet already linked to another account - `404` - No active session for the given socketId ### GET `/api/auth/smirk/status` Check if a session has a linked wallet. **Query:** `?socketId=` **Response:** ```json { "linked": true, "hasPayoutAddress": true } ``` --- ## Signature Verification The Smirk extension signs messages using Ed25519 with a **SHA256 prehash**: 1. Extension receives the challenge string (e.g. `"a1b2c3..."`) 2. Extension computes `msgHash = SHA256(UTF8_encode(challenge))` 3. Extension signs `msgHash` with the Ed25519 private spend key 4. Extension returns the 64-byte signature as hex The server must verify against the same prehash: ```javascript const crypto = require('crypto'); const nacl = require('tweetnacl'); const challengeHash = crypto.createHash('sha256').update(challenge).digest(); const signatureBytes = Buffer.from(signature, 'hex'); const publicKeyBytes = Buffer.from(publicKey, 'hex'); const valid = nacl.sign.detached.verify(challengeHash, signatureBytes, publicKeyBytes); ``` **Important:** Do NOT verify against `Buffer.from(challenge, 'hex')` (the raw hex-decoded bytes). The extension always SHA256-hashes the message string before signing. --- ## `window.smirk` API Reference The extension injects a frozen `window.smirk` object on all pages. ### Detection ```javascript if (typeof window.smirk !== 'undefined') { // Extension is installed } ``` ### `smirk.connect()` -> `Promise` Requests user approval to share public keys. Opens an approval popup on first use; subsequent calls from an approved origin return keys immediately. ```javascript const keys = await window.smirk.connect(); // keys.wow - Ed25519 public spend key (hex) // keys.btc - secp256k1 compressed public key (hex) // keys.ltc - secp256k1 compressed public key (hex) // keys.xmr - Ed25519 public spend key (hex) // keys.grin - Ed25519 public key (hex) ``` ### `smirk.signMessage(message)` -> `Promise` Signs a message with all wallet keys. Always prompts for user approval. ```javascript const result = await window.smirk.signMessage(challenge); const wowSig = result.signatures.find(s => s.asset === 'wow'); // wowSig.signature - 64-byte Ed25519 signature (hex) // wowSig.publicKey - public key that signed (hex) ``` Message limit: 10,000 characters. ### `smirk.getAddresses()` -> `Promise` Returns wallet addresses for all assets. Requires prior `connect()`. ```javascript const addrs = await window.smirk.getAddresses(); // addrs.wow - "Wo..." (97-char CryptoNote standard address) // addrs.btc - "bc1q..." (bech32 P2WPKH) // addrs.ltc - "ltc1q..." (bech32 P2WPKH) // addrs.xmr - "4..." (95-char CryptoNote standard address) // addrs.grin - "grin1..." (bech32 slatepack address) ``` ### `smirk.isConnected()` -> `Promise` Check if the current origin is connected (approved). ### `smirk.disconnect()` -> `Promise` Revoke site access. ### `smirk.getPublicKeys()` -> `Promise` Get public keys without prompting (only works if already connected). Returns `null` if not connected. --- ## Client-Side Implementation See `html/js/network/smirkAuth.js` for the full implementation. Key points: - The `SmirkAuth` module is initialized after DOM ready and socket connection - It checks `SocketHandlers._smirkEnabled` (set by server's `game_mode_info` event) to gate the feature - On successful auth, it calls `smirk.getAddresses()` to auto-set the payout address via `socket.emit('address:update')` - The button shows install link if extension is not detected, connect button if it is --- ## Database The `smirk_challenges` table stores pending challenges: | Column | Type | Description | |--------|------|-------------| | id | serial | Primary key | | challenge | text | 64-char hex challenge string | | socket_id | text | Socket.io session ID | | used | boolean | Whether challenge has been consumed | | expires_at | timestamp | Auto-set to NOW() + 5 minutes | | created_at | timestamp | Row creation time | The `users` table has a `smirk_public_key` column that stores the linked Ed25519 public key (hex). --- ## Signature Schemes by Asset | Asset | Curve | Hash | Format | |-------|-------|------|--------| | BTC | secp256k1 (ECDSA) | Double SHA256 with Bitcoin message prefix | Compact signature (64 bytes) | | LTC | secp256k1 (ECDSA) | Double SHA256 with Bitcoin message prefix | Compact signature (64 bytes) | | XMR | Ed25519 | SHA256 prehash | R \|\| s (64 bytes) | | WOW | Ed25519 | SHA256 prehash | R \|\| s (64 bytes) | | GRIN | Ed25519 | SHA256 prehash | R \|\| s (64 bytes) | Wownerogue only uses the WOW signature for authentication.