Monorepo for Aesthetic.Computer aesthetic.computer
at main 350 lines 14 kB view raw view rendered
1# Keeps Wallet — Beacon Protocol Implementation Plan 2 3**Goal:** Make Keeps Wallet a drop-in replacement for Temple Wallet by implementing the Beacon SDK postMessage protocol. Any dApp using Beacon (keep.kidlisp.com, objkt.com, etc.) will see Keeps Wallet as a connectable wallet — no custom integration needed. 4 5--- 6 7## 1. How Beacon Extension Communication Works 8 9The Beacon SDK (used by dApps) discovers and communicates with browser extension wallets via `window.postMessage`. The protocol has three phases: 10 11### Discovery (Ping/Pong) 12``` 13dApp → window.postMessage({ target: "toExtension", payload: "ping" }) 14extension → window.postMessage({ target: "toPage", payload: "pong", sender: { id, name, iconURL } }) 15``` 16The dApp's `DappPostMessageTransport` sends a `ping` and collects `pong` responses to build a list of available wallets. 17 18### Pairing (Key Exchange) 19``` 20dApp → PostMessagePairingRequest { id, name, publicKey, version, icon?, appUrl? } 21extension → PostMessagePairingResponse { id, name, publicKey, version, type: "postmessage-pairing-response", extensionId } 22``` 23Both sides exchange Ed25519 public keys. All subsequent messages are encrypted using `crypto_box` (libsodium shared secret derived from both keypairs). 24 25### Encrypted Message Exchange 26``` 27dApp → { target: "toExtension", encryptedPayload: hex(nonce + ciphertext), targetId: extensionId } 28extension → { target: "toPage", encryptedPayload: hex(nonce + ciphertext) } 29``` 30Payloads are Beacon protocol messages (JSON) encrypted with the shared secret. 31 32### Message Types (ExtensionMessageTarget) 33```typescript 34enum ExtensionMessageTarget { 35 BACKGROUND = 'toBackground', 36 PAGE = 'toPage', 37 EXTENSION = 'toExtension' 38} 39 40interface ExtensionMessage<T> { 41 target: ExtensionMessageTarget 42 targetId?: string // extension ID for routing 43 sender?: { id, name, iconURL } 44 payload: T // 'ping' | 'pong' | encrypted payload string 45} 46``` 47 48### Beacon Message Types (inside encrypted payloads) 49``` 50PermissionRequest → PermissionResponse 51OperationRequest → OperationResponse 52SignPayloadRequest → SignPayloadResponse 53BroadcastRequest → (optional, can decline) 54Disconnect 55Acknowledge (sent immediately on receipt of any request) 56Error (sent when request fails) 57``` 58 59--- 60 61## 2. Implementation Steps 62 63### Step 1: Add libsodium for Beacon Crypto 64 65**File:** `wallet/extension/lib/beacon-crypto.mjs` 66 67Beacon uses libsodium `crypto_box` for encrypted communication between dApp and wallet. We need: 68- Ed25519 keypair generation (for wallet's Beacon identity — separate from Tezos keys) 69- `crypto_box` / `crypto_box_open` for encrypting/decrypting messages 70- Key exchange: convert Ed25519 keys to Curve25519 for `crypto_box` 71- Nonce generation (24 bytes, prepended to ciphertext) 72 73We already have libsodium in `lib/crypto.mjs` — extend or import from there. 74 75**Persistent Beacon keypair:** Store a separate Ed25519 keypair seed in `chrome.storage.local` under `beacon_keypair_seed`. This is the wallet's Beacon identity (not the Tezos signing key). Generate on first use. 76 77### Step 2: Beacon Protocol Handler in Background 78 79**File:** `wallet/extension/beacon.mjs` (new) 80 81This module handles the Beacon protocol logic: 82 83```javascript 84// Core functions needed: 85getOrCreateBeaconKeypair() // Persistent Beacon identity 86encryptMessage(msg, peerPK) // crypto_box with shared secret 87decryptMessage(payload, peerPK) // crypto_box_open 88handleBeaconRequest(msg) // Route to permission/operation/sign handlers 89buildBeaconResponse(request, data) // Construct proper response with id, version, senderId 90``` 91 92**Request handlers:** 93 94| Beacon Request | What Keeps Wallet Does | 95|---|---| 96| `PermissionRequest` | Return account address + public key. Scope: `sign`, `operation_request`. Opens confirmation popup. | 97| `OperationRequest` | Forge operations via Tezos RPC, sign with wallet key, inject. Return `opHash`. Opens confirmation popup. | 98| `SignPayloadRequest` | Sign raw bytes with ed25519 key. Return `edsig...` signature. Opens confirmation popup. | 99| `BroadcastRequest` | Inject pre-signed operation to RPC node. Return `opHash`. | 100| `Disconnect` | Clear active dApp session. | 101 102**For every request:** immediately send an `Acknowledge` response (unencrypted `{ type: "acknowledge", id: request.id }`), then process and send the real response. 103 104### Step 3: Rewrite Content Script for Beacon 105 106**File:** `wallet/extension/content.js` 107 108The content script becomes the message relay between the page and background: 109 110```javascript 111// 1. Listen for messages FROM the page (dApp → extension) 112window.addEventListener('message', (event) => { 113 if (event.source !== window) return; 114 const msg = event.data; 115 116 // Beacon ping 117 if (msg?.target === 'toExtension' && msg?.payload === 'ping') { 118 // Respond with pong + wallet info 119 window.postMessage({ 120 target: 'toPage', 121 payload: 'pong', 122 sender: { id: EXTENSION_ID, name: 'Keeps Wallet', iconURL: ICON_URL } 123 }, '*'); 124 return; 125 } 126 127 // Beacon encrypted message or pairing request 128 if (msg?.target === 'toExtension') { 129 chrome.runtime.sendMessage({ type: 'BEACON_MESSAGE', data: msg }) 130 .then(response => { 131 if (response) { 132 window.postMessage({ target: 'toPage', ...response }, '*'); 133 } 134 }); 135 return; 136 } 137 138 // Keep existing KEEPS_* custom protocol as fallback 139 if (msg?.type?.startsWith('KEEPS_')) { /* existing relay logic */ } 140}); 141 142// 2. Listen for messages FROM background (extension → page) 143chrome.runtime.onMessage.addListener((message) => { 144 if (message.type === 'BEACON_RESPONSE') { 145 window.postMessage({ target: 'toPage', ...message.data }, '*'); 146 } 147 if (message.type === 'KEEPS_LOCKED') { 148 window.postMessage({ type: 'KEEPS_LOCKED' }, '*'); 149 } 150}); 151``` 152 153### Step 4: Update Inpage Script 154 155**File:** `wallet/extension/inpage.js` 156 157Keep the existing `window.keeps` API but also announce via Beacon's expected mechanism. The inpage script doesn't need to do much for Beacon — the content script handles the postMessage relay directly. But we should: 158 1591. Dispatch a custom event announcing wallet availability: 160```javascript 161window.dispatchEvent(new CustomEvent('keeps:ready')); 162``` 163 1642. Optionally set `window.tezos` or a similar global that some dApps check (Temple sets this too). 165 166### Step 5: UI — Only Keeps, Alive 167 168**Design philosophy:** The wallet is not a finance app. It's a living collection. You open it and you see your keeps running. Everything else is invisible until needed. 169 170**Style guide:** Match `keep.kidlisp.com` — same CSS variables, fonts, and feel. 171 172```css 173/* Fonts */ 174--font-mono: 'Noto Sans Mono', 'SF Mono', 'Monaco', 'Menlo', 'Consolas', monospace; 175--font-display: 'YWFTProcessing-Regular', monospace; /* headings / logo */ 176--font-fun: 'Comic Relief', 'Comic Sans MS', cursive; 177 178/* Light */ 179--bg-primary: #f7f7f7; 180--bg-secondary: #e8e8e8; 181--bg-tertiary: white; 182--text-primary: #111; 183--text-secondary: #333; 184--text-tertiary: #666; 185--border-color: #ddd; 186--ac-purple: rgb(205, 92, 155); 187 188/* Dark */ 189--bg-primary: #1e1e1e; 190--bg-secondary: #252526; 191--bg-tertiary: #2d2d30; 192--text-primary: #d4d4d4; 193--text-tertiary: #858585; 194--border-color: #3e3e42; 195 196/* Links in ac-purple, cursor from aesthetic.computer */ 197``` 198 199Auto theme (follows system `prefers-color-scheme`), with manual override via `data-theme`. Font files loaded from `https://aesthetic.computer/type/webfonts/`. 200 201**One view, three states:** 202 203**No wallet yet:** 204A single demo keep runs full-bleed as a live `<canvas>`. Two words overlaid at the bottom: "Create" / "Import". Password fields appear inline when tapped — no separate view. 205 206Import accepts seed phrases from **Temple** or **Kukai** (same BIP39 → ed25519 derivation). Your `aesthetic.tez` identity comes with you — same tz1 address, same keeps collection. The wallet shows your keeps immediately after import. 207 208**Locked:** 209Your most recent keep runs blurred behind a single password field centered on screen. Type, hit enter. No title, no logo, no chrome. 210 211**Unlocked (the only real view):** 212Your keeps in a grid. Each thumbnail is a live `<canvas>` running the KidLisp source code (~30KB evaluator). That's the whole UI. No transaction history, no settings pages. 213 214- Balance hidden behind a small `$` icon in the corner — tap to reveal, tap again to hide 215- Tap a keep → it expands to fill the panel, interactive (touch/click works) 216- Tap edge or swipe → back to grid 217- Connected dApps are invisible dots along the bottom edge (tap to disconnect) 218- Network badge (ghostnet/mainnet) is a tiny pill in the corner, tap to toggle 219 220**Confirmation overlay:** 221When a Beacon request arrives, a translucent sheet slides up over whatever keep is currently displayed: 222- dApp name 223- What it wants (connect / sign / send) 224- Approve / Reject 225- No navigation away from the collection — it's an overlay 226 227**File:** `wallet/extension/popup/popup.html` (rewrite) 228**File:** `wallet/extension/popup/popup.js` (rewrite) 229 230The popup loads the KidLisp evaluator (`kidlisp.mjs`) and creates a `<canvas>` per keep. Keeps metadata (including source `$code`) is fetched from TzKT. The evaluator runs each piece at thumbnail resolution (~100x100) for the grid, full resolution when expanded. 231 232### Step 6: Update Manifest 233 234**File:** `wallet/extension/manifest.json` 235 236```json 237{ 238 "content_scripts": [{ 239 "matches": [ 240 "https://aesthetic.computer/*", 241 "https://*.aesthetic.computer/*", 242 "https://keep.kidlisp.com/*", 243 "https://*.kidlisp.com/*", 244 "https://objkt.com/*", 245 "http://localhost:8888/*", 246 "https://localhost:8888/*", 247 "<all_urls>" 248 ] 249 }] 250} 251``` 252 253For a true Temple replacement, we need `<all_urls>` so the wallet works on any Tezos dApp. Add `"permissions": ["storage", "activeTab"]` (already present). 254 255### Step 7: dApp Session Management 256 257**File:** `wallet/extension/beacon.mjs` (extends Step 2) 258 259Track connected dApps: 260```javascript 261// Store in chrome.storage.local: 262{ 263 beacon_peers: { 264 [dAppId]: { 265 name: "keep.kidlisp.com", 266 publicKey: "...", 267 connectedAt: timestamp, 268 permissions: ["sign", "operation_request"] 269 } 270 } 271} 272``` 273 274On `Disconnect` message: remove peer. On lock: notify all connected dApps. 275Connected dApps appear as small dots at the bottom edge of the keeps grid — tap to see name + disconnect. No separate view. 276 277--- 278 279## 3. File Changes Summary 280 281| File | Action | Description | 282|---|---|---| 283| `wallet/extension/lib/beacon-crypto.mjs` | **New** | Beacon-specific crypto (crypto_box, key exchange) | 284| `wallet/extension/beacon.mjs` | **New** | Beacon protocol handler (request routing, responses) | 285| `wallet/extension/content.js` | **Modify** | Add Beacon toExtension/toPage relay alongside existing KEEPS_ relay | 286| `wallet/extension/inpage.js` | **Modify** | Keep window.keeps, add wallet announcement | 287| `wallet/extension/background.js` | **Modify** | Add BEACON_MESSAGE handler, pending request queue, confirmation flow | 288| `wallet/extension/popup/popup.html` | **Rewrite** | Living keeps grid — no finance UI, just running canvases | 289| `wallet/extension/popup/popup.js` | **Rewrite** | KidLisp evaluator integration, keeps grid, confirmation overlay | 290| `wallet/extension/manifest.json` | **Modify** | Add `<all_urls>` to content scripts, add kidlisp.com | 291| `wallet/extension/package.json` | **Modify** | Add libsodium-wrappers if not already present | 292 293--- 294 295## 4. Dependencies 296 297Already have in `wallet/extension/package.json`: 298- `tweetnacl` (ed25519 signing) 299- `bip39` (mnemonic) 300- `bs58check` (base58) 301 302Need to add or verify: 303- `libsodium-wrappers` — for `crypto_box` (Beacon uses this, not tweetnacl's box). Or use tweetnacl's `nacl.box` which is compatible (same algorithm: x25519-xsalsa20-poly1305). 304 305**Decision:** Use `tweetnacl.box` since it's already a dependency. Same underlying crypto as libsodium's `crypto_box`. This avoids adding another dependency. 306 307--- 308 309## 5. Testing Plan 310 3111. **Unit test Beacon crypto** — encrypt/decrypt roundtrip with known test vectors 3122. **Ping/pong discovery** — Load extension, open a page with Beacon SDK, verify wallet appears in wallet list 3133. **Permission request** — Connect from keep.kidlisp.com, verify address returned matches wallet 3144. **Operation request** — Mint a keep on ghostnet, verify confirmation popup appears, transaction succeeds 3155. **Sign payload** — Sign arbitrary bytes, verify signature is valid ed25519 3166. **Disconnect** — Disconnect from dApp, verify session cleared 3177. **Auto-lock** — Verify Beacon requests fail gracefully when wallet is locked 3188. **Multi-dApp** — Connect to two dApps simultaneously, verify independent sessions 319 320--- 321 322## 6. Implementation Order 323 3241. **Beacon crypto layer** (Step 1) — foundation 3252. **Content script Beacon relay** (Step 3) — ping/pong first 3263. **Background Beacon handler** (Step 2) — permission request flow 3274. **Confirmation popup** (Step 5) — security gate 3285. **Manifest update** (Step 6) — broader site support 3296. **Operation & sign handlers** (Step 2 continued) — full transaction support 3307. **Session management** (Step 7) — track connected dApps 3318. **Popup UI updates** (Step 8) — connected dApps view 3329. **Testing on keep.kidlisp.com** — end-to-end validation 333 334--- 335 336## 7. Key References 337 338- **Beacon SDK source:** `github.com/airgap-it/beacon-sdk` (branch: master) 339 - `packages/beacon-transport-postmessage/src/` — PostMessage transport 340 - `packages/beacon-types/src/types/ExtensionMessage.ts` — Message format 341 - `packages/beacon-types/src/types/ExtensionMessageTarget.ts``toPage`/`toExtension` enum 342 - `packages/beacon-types/src/types/beacon/BeaconMessageType.ts` — All 19 message types 343 - `packages/beacon-types/src/types/PostMessagePairingRequest.ts` — Pairing request format 344 - `packages/beacon-types/src/types/PostMessagePairingResponse.ts` — Pairing response format 345- **Temple Wallet source:** `github.com/madfish-solutions/templewallet-extension` (branch: development) 346 - `src/content-scripts/main.ts` — Content script with Beacon relay 347 - `src/lib/temple/beacon.ts` — Beacon protocol handler (encrypt/decrypt, message routing) 348- **Spire (reference wallet):** `github.com/airgap-it/spire` — AirGap's own Beacon extension wallet 349- **TZIP-10 standard:** Wallet interaction standard that Beacon implements 350- **Beacon docs:** `docs.walletbeacon.io`