@www.hyperlinks.space/program-kit 1.2.181818 → 81.81.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +19 -0
- package/api/{base.ts → _base.ts} +1 -1
- package/api/wallet/_auth.ts +143 -0
- package/api/wallet/register.ts +151 -0
- package/api/wallet/status.ts +89 -0
- package/app/index.tsx +319 -5
- package/assets/images/PreviewImage.png +0 -0
- package/database/start.ts +0 -1
- package/database/wallets.ts +266 -0
- package/docs/auth-and-centralized-encrypted-keys-plan.md +440 -0
- package/docs/keys-retrieval-console-scripts.js +131 -0
- package/docs/security_plan_raw.md +1 -1
- package/docs/security_raw.md +22 -13
- package/docs/storage-availability-console-script.js +152 -0
- package/docs/storage-lifetime.md +33 -0
- package/docs/telegram-raw-keys-cloud-storage-risks.md +31 -0
- package/docs/wallets_hosting_architecture.md +147 -1
- package/fullREADME.md +71 -26
- package/index.js +3 -0
- package/package.json +7 -2
- package/scripts/test-api-base.ts +2 -2
- package/telegram/post.ts +44 -8
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
# Auth + Centralized Encrypted Keys Plan (Supabase)
|
|
2
|
+
|
|
3
|
+
This document defines a practical implementation plan for:
|
|
4
|
+
|
|
5
|
+
- Multi-provider login: **Google**, **Telegram**, **GitHub**, and **email + protection code (OTP)**
|
|
6
|
+
- Centralized storage in **Supabase**
|
|
7
|
+
- Wallet secrets stored as **encrypted blobs only** (no plaintext mnemonic/private keys server-side)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1) Product Goal
|
|
12
|
+
|
|
13
|
+
Enable users to sign in from multiple platforms and recover wallet access from Supabase by using account credentials plus a user-held decryption secret model.
|
|
14
|
+
|
|
15
|
+
## 2) Security Goal
|
|
16
|
+
|
|
17
|
+
- Supabase/backend stores only ciphertext envelopes and metadata.
|
|
18
|
+
- Decryption happens client-side.
|
|
19
|
+
- Server does not receive plaintext mnemonic/private keys.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 3) Trust Model Decision
|
|
24
|
+
|
|
25
|
+
To avoid custodial key handling by backend, choose one of these decryption models:
|
|
26
|
+
|
|
27
|
+
1. **Password-derived key model (recommended for centralized sync)**
|
|
28
|
+
- User sets a wallet passphrase.
|
|
29
|
+
- Client derives key with Argon2id.
|
|
30
|
+
- Ciphertext stored in Supabase.
|
|
31
|
+
2. **Device-key model only**
|
|
32
|
+
- Better local UX, weaker cross-device recovery unless mnemonic re-entry is required.
|
|
33
|
+
|
|
34
|
+
For this plan, use model (1) as canonical centralized recovery path.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 3.1) What "wrapped decrypt key" means (plain language)
|
|
39
|
+
|
|
40
|
+
This phrase means we do not store the real decryption key directly in the database.
|
|
41
|
+
|
|
42
|
+
- **DEK (Data Encryption Key):** key that encrypts wallet secret/mnemonic.
|
|
43
|
+
- **KEK (Key Encryption Key):** key that encrypts ("wraps") the DEK.
|
|
44
|
+
|
|
45
|
+
So database stores:
|
|
46
|
+
- wallet ciphertext (encrypted by DEK)
|
|
47
|
+
- wrapped DEK (encrypted by KEK)
|
|
48
|
+
|
|
49
|
+
Database does **not** store:
|
|
50
|
+
- plaintext mnemonic
|
|
51
|
+
- plaintext DEK
|
|
52
|
+
- plaintext KEK
|
|
53
|
+
|
|
54
|
+
### Why KMS/HSM is mentioned
|
|
55
|
+
|
|
56
|
+
`KEK` should live in a managed key system (AWS KMS, GCP KMS, Azure Key Vault HSM, etc.), not in app code or DB columns.
|
|
57
|
+
|
|
58
|
+
When app needs decrypt flow:
|
|
59
|
+
1. User authenticates (Google/Telegram/GitHub/email OTP).
|
|
60
|
+
2. Backend loads `wrapped_dek` + wallet `ciphertext` from DB.
|
|
61
|
+
3. Backend asks KMS/HSM to unwrap DEK (or returns a short-lived tokenized decrypt result, depending on policy).
|
|
62
|
+
4. Decrypt happens in the chosen boundary (client-side or controlled backend flow).
|
|
63
|
+
|
|
64
|
+
### Tiny analogy
|
|
65
|
+
|
|
66
|
+
- Wallet data = document in locked box (ciphertext).
|
|
67
|
+
- DEK = key to that box.
|
|
68
|
+
- KEK = key to a safe that contains the DEK.
|
|
69
|
+
- KMS/HSM = guarded safe room.
|
|
70
|
+
|
|
71
|
+
This way, stealing only DB rows is not enough to decrypt user wallets.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 3.2) Newbie note: "But ciphertext and wrapped key are in the same DB"
|
|
76
|
+
|
|
77
|
+
This is a common concern and the short answer is:
|
|
78
|
+
|
|
79
|
+
- Yes, they can be stored in the same row.
|
|
80
|
+
- No, that does not automatically break security.
|
|
81
|
+
|
|
82
|
+
### Why this can still be safe
|
|
83
|
+
|
|
84
|
+
Think of three pieces:
|
|
85
|
+
|
|
86
|
+
1. `ciphertext` (locked wallet data)
|
|
87
|
+
2. `wrapped_dek` (the key to the lock, but itself locked)
|
|
88
|
+
3. `kek` in KMS/HSM (the key that unlocks `wrapped_dek`)
|
|
89
|
+
|
|
90
|
+
If attacker steals DB only, they get (1) and (2), but not (3).
|
|
91
|
+
Without (3), they cannot recover plaintext keys.
|
|
92
|
+
|
|
93
|
+
### What actually protects the system
|
|
94
|
+
|
|
95
|
+
The real protection is **access control to KMS/HSM**, not hiding DB relations.
|
|
96
|
+
|
|
97
|
+
- Keep KEK outside DB.
|
|
98
|
+
- Restrict KMS permissions to minimum required service path.
|
|
99
|
+
- Audit and rate-limit unwrap operations.
|
|
100
|
+
|
|
101
|
+
### Optional extra hardening
|
|
102
|
+
|
|
103
|
+
You can split storage across two databases (ciphertext in one, wrapped key in another), but this is additional defense-in-depth. It does not replace KMS/HSM boundary.
|
|
104
|
+
|
|
105
|
+
Rule of thumb for beginners:
|
|
106
|
+
**Do not rely on "they cannot match rows"; rely on "they cannot access KEK".**
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 3.3) How user session works with KEK (Google/Telegram/GitHub/email OTP)
|
|
111
|
+
|
|
112
|
+
Important: user login session is an **authorization signal**, not the KEK itself.
|
|
113
|
+
|
|
114
|
+
- Google/Telegram/GitHub/email OTP proves "this user is authenticated".
|
|
115
|
+
- Backend then decides whether to allow key unwrap path.
|
|
116
|
+
- KEK remains in KMS/HSM and is never replaced by OAuth/session token.
|
|
117
|
+
|
|
118
|
+
Typical flow:
|
|
119
|
+
|
|
120
|
+
1. User logs in and gets a valid app session.
|
|
121
|
+
2. App requests wallet unlock/decrypt operation.
|
|
122
|
+
3. Backend verifies session + policy checks (device/risk/rate limits).
|
|
123
|
+
4. Backend reads `wrapped_dek + ciphertext` from DB.
|
|
124
|
+
5. Backend calls KMS/HSM to unwrap DEK.
|
|
125
|
+
6. Decrypt/sign path continues under selected trust boundary.
|
|
126
|
+
|
|
127
|
+
This is why people say "session gates KEK usage", not "session is KEK".
|
|
128
|
+
|
|
129
|
+
### Two deployment variants
|
|
130
|
+
|
|
131
|
+
- **Variant A (more custodial):**
|
|
132
|
+
- Backend unwraps DEK and performs decrypt/sign server-side.
|
|
133
|
+
- User gets signed result/tx hash.
|
|
134
|
+
- **Variant B (hybrid):**
|
|
135
|
+
- Backend authorizes and returns short-lived decrypt material/session token.
|
|
136
|
+
- Client resolves/decrypts locally for signing.
|
|
137
|
+
|
|
138
|
+
Choose Variant A only if you explicitly accept custodial responsibility.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 3.4) Why keep both `wrapped_dek` and `ciphertext` in DB (not one encrypted entity)
|
|
143
|
+
|
|
144
|
+
You can think "why not one giant blob encrypted by KEK directly?".
|
|
145
|
+
Short answer: envelope encryption with separate DEK is safer and more operationally practical.
|
|
146
|
+
|
|
147
|
+
Reasons:
|
|
148
|
+
|
|
149
|
+
1. **KMS/HSM usage limits and performance**
|
|
150
|
+
- KMS is best for wrapping small keys, not encrypting large/high-volume payloads repeatedly.
|
|
151
|
+
- DEK handles data encryption efficiently.
|
|
152
|
+
|
|
153
|
+
2. **Key rotation without re-encrypting all wallet data**
|
|
154
|
+
- Rotate KEK by re-wrapping DEKs.
|
|
155
|
+
- No need to decrypt/re-encrypt every wallet ciphertext each time KEK rotates.
|
|
156
|
+
|
|
157
|
+
3. **Cryptographic separation of duties**
|
|
158
|
+
- DEK protects wallet payload.
|
|
159
|
+
- KEK protects DEK.
|
|
160
|
+
- Cleaner blast-radius control and auditing.
|
|
161
|
+
|
|
162
|
+
4. **Metadata and versioning flexibility**
|
|
163
|
+
- You can evolve ciphertext formats (AEAD params/version) independently from KEK lifecycle.
|
|
164
|
+
|
|
165
|
+
5. **Standard industry pattern**
|
|
166
|
+
- This is standard "envelope encryption" used by major cloud security systems.
|
|
167
|
+
|
|
168
|
+
So storing both `ciphertext` and `wrapped_dek` is expected architecture, not duplication.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 3.5) KMS/HSM section: what it is and how to operate it
|
|
173
|
+
|
|
174
|
+
## What is KMS?
|
|
175
|
+
|
|
176
|
+
**KMS (Key Management Service)** is a managed service that stores and uses cryptographic master keys with strict access controls, logging, and rotation features.
|
|
177
|
+
|
|
178
|
+
Examples:
|
|
179
|
+
- AWS KMS
|
|
180
|
+
- Google Cloud KMS
|
|
181
|
+
- Azure Key Vault (with HSM-backed options)
|
|
182
|
+
|
|
183
|
+
## What is HSM?
|
|
184
|
+
|
|
185
|
+
**HSM (Hardware Security Module)** is specialized hardware designed to keep key material protected and perform crypto operations with strong tamper resistance.
|
|
186
|
+
|
|
187
|
+
In practice:
|
|
188
|
+
- Many cloud KMS offerings can use HSM-backed keys.
|
|
189
|
+
- Teams usually start with managed KMS and move to stricter HSM policies if required by risk/compliance.
|
|
190
|
+
|
|
191
|
+
## Why use KMS/HSM here
|
|
192
|
+
|
|
193
|
+
- Keep `KEK` out of application DB and source code.
|
|
194
|
+
- Centralize key policy and access control.
|
|
195
|
+
- Get immutable audit logs for unwrap/encrypt/decrypt operations.
|
|
196
|
+
- Support controlled key rotation and key disable/emergency revoke.
|
|
197
|
+
|
|
198
|
+
## How to deal with it (practical checklist)
|
|
199
|
+
|
|
200
|
+
1. **Create KEK in KMS/HSM**
|
|
201
|
+
- Mark as non-exportable when available.
|
|
202
|
+
- Separate key per environment (`dev/stage/prod`).
|
|
203
|
+
|
|
204
|
+
2. **Restrict IAM permissions**
|
|
205
|
+
- App service can only call required operations (typically `Decrypt/Unwrap`, maybe `Encrypt/Wrap`).
|
|
206
|
+
- No broad admin access from runtime services.
|
|
207
|
+
|
|
208
|
+
3. **Use envelope encryption pattern**
|
|
209
|
+
- Generate DEK for wallet payload encryption.
|
|
210
|
+
- Store `ciphertext + wrapped_dek` in DB.
|
|
211
|
+
- Never store plaintext KEK/DEK at rest.
|
|
212
|
+
|
|
213
|
+
4. **Add policy checks before unwrap**
|
|
214
|
+
- Require valid session and account state.
|
|
215
|
+
- Apply risk checks (IP/device anomalies, rate limits, cooldowns).
|
|
216
|
+
- Log every sensitive operation.
|
|
217
|
+
|
|
218
|
+
5. **Implement rotation**
|
|
219
|
+
- Rotate KEK on schedule.
|
|
220
|
+
- Re-wrap DEKs in background jobs.
|
|
221
|
+
- Keep key version metadata in DB.
|
|
222
|
+
|
|
223
|
+
6. **Prepare incident controls**
|
|
224
|
+
- Ability to disable key version quickly.
|
|
225
|
+
- Emergency freeze for high-risk accounts.
|
|
226
|
+
- Recovery runbook for key compromise scenarios.
|
|
227
|
+
|
|
228
|
+
## Common mistakes to avoid
|
|
229
|
+
|
|
230
|
+
- Putting KEK plaintext in `.env` and calling it "KMS-ready".
|
|
231
|
+
- Giving app runtime full KMS admin permissions.
|
|
232
|
+
- Missing unwrap rate limits and anomaly detection.
|
|
233
|
+
- No audit review pipeline for key operations.
|
|
234
|
+
- Designing without key-rotation path from day one.
|
|
235
|
+
|
|
236
|
+
## Newbie rule
|
|
237
|
+
|
|
238
|
+
If DB is stolen, attacker should still need **separate KMS/HSM access** to decrypt anything.
|
|
239
|
+
If DB theft alone can decrypt wallets, architecture is wrong.
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## 4) Authentication Architecture
|
|
244
|
+
|
|
245
|
+
Use **Supabase Auth** as identity layer:
|
|
246
|
+
|
|
247
|
+
- Google OAuth
|
|
248
|
+
- GitHub OAuth
|
|
249
|
+
- Email + protection code (OTP / magic code)
|
|
250
|
+
- Telegram login bridge (custom verifier service; link to Supabase user)
|
|
251
|
+
|
|
252
|
+
### Identity Linking
|
|
253
|
+
|
|
254
|
+
A single user can link multiple auth methods to one account.
|
|
255
|
+
|
|
256
|
+
- Primary identity key: `user_id` (Supabase Auth UUID)
|
|
257
|
+
- Linked providers table stores provider identifiers (`google_sub`, `github_id`, `telegram_id`, etc.)
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## 5) Wallet Encryption Envelope
|
|
262
|
+
|
|
263
|
+
Store one or more encrypted wallet envelopes per user.
|
|
264
|
+
|
|
265
|
+
### Envelope format (server-stored)
|
|
266
|
+
|
|
267
|
+
- `ciphertext` (base64)
|
|
268
|
+
- `nonce/iv`
|
|
269
|
+
- `kdf` params:
|
|
270
|
+
- `algorithm = argon2id`
|
|
271
|
+
- `salt`
|
|
272
|
+
- `memory_kib`
|
|
273
|
+
- `iterations`
|
|
274
|
+
- `parallelism`
|
|
275
|
+
- `dk_len`
|
|
276
|
+
- `aead`:
|
|
277
|
+
- `algorithm = aes-256-gcm` (or xchacha20-poly1305)
|
|
278
|
+
- optional `aad`
|
|
279
|
+
- `version`
|
|
280
|
+
- `created_at`, `updated_at`
|
|
281
|
+
|
|
282
|
+
### Crypto rules
|
|
283
|
+
|
|
284
|
+
- KDF and encryption run **client-side only**.
|
|
285
|
+
- Never send passphrase to server.
|
|
286
|
+
- Never log secrets/ciphertext in verbose logs.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## 6) Supabase Data Model
|
|
291
|
+
|
|
292
|
+
## `profiles`
|
|
293
|
+
- `id` (uuid, fk -> auth.users.id)
|
|
294
|
+
- `username` (nullable unique)
|
|
295
|
+
- `display_name`
|
|
296
|
+
- `created_at`
|
|
297
|
+
|
|
298
|
+
## `auth_identities`
|
|
299
|
+
- `id` (uuid)
|
|
300
|
+
- `user_id` (uuid)
|
|
301
|
+
- `provider` (`google|github|telegram|email_otp`)
|
|
302
|
+
- `provider_subject` (string)
|
|
303
|
+
- unique (`provider`, `provider_subject`)
|
|
304
|
+
|
|
305
|
+
## `wallet_envelopes`
|
|
306
|
+
- `id` (uuid)
|
|
307
|
+
- `user_id` (uuid)
|
|
308
|
+
- `wallet_label`
|
|
309
|
+
- `ciphertext`
|
|
310
|
+
- `kdf_json`
|
|
311
|
+
- `aead_json`
|
|
312
|
+
- `envelope_version`
|
|
313
|
+
- `is_active`
|
|
314
|
+
- timestamps
|
|
315
|
+
|
|
316
|
+
## `security_events`
|
|
317
|
+
- `id`, `user_id`
|
|
318
|
+
- `event_type`
|
|
319
|
+
- `ip_hash`, `ua_hash`
|
|
320
|
+
- timestamps
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## 7) Authorization and RLS
|
|
325
|
+
|
|
326
|
+
Enable Row Level Security:
|
|
327
|
+
|
|
328
|
+
- user can read/write only rows where `user_id = auth.uid()`.
|
|
329
|
+
- admin/service role separated for backend-only tasks.
|
|
330
|
+
- strict policies for envelope update/delete.
|
|
331
|
+
|
|
332
|
+
Add rate limits and abuse controls at API edge:
|
|
333
|
+
|
|
334
|
+
- login attempt throttling
|
|
335
|
+
- envelope fetch/update throttling
|
|
336
|
+
- device/IP anomaly checks
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## 8) Provider-Specific Notes
|
|
341
|
+
|
|
342
|
+
## Google / GitHub
|
|
343
|
+
- Use Supabase OAuth providers.
|
|
344
|
+
- Standard callback + session issuance.
|
|
345
|
+
|
|
346
|
+
## Email + Protection Code (OTP)
|
|
347
|
+
- Use Supabase OTP flow (`signInWithOtp`) with short code expiry.
|
|
348
|
+
- Rate-limit OTP requests and verify attempts.
|
|
349
|
+
- Add anti-abuse checks (per-IP/per-email cooldown).
|
|
350
|
+
- Optional: require email verification before provider linking actions.
|
|
351
|
+
|
|
352
|
+
## Telegram
|
|
353
|
+
- Verify Telegram login payload (`id_token`/signed data) in backend function.
|
|
354
|
+
- On success, link Telegram identity to existing `user_id` or create new profile.
|
|
355
|
+
- Telegram auth is identity only, not decryption secret.
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## 9) Client Flows
|
|
360
|
+
|
|
361
|
+
## Registration
|
|
362
|
+
1. User signs up via any provider.
|
|
363
|
+
2. App prompts wallet passphrase setup (if no envelope exists).
|
|
364
|
+
3. Client generates/imports mnemonic.
|
|
365
|
+
4. Client encrypts mnemonic -> envelope.
|
|
366
|
+
5. Upload envelope to Supabase.
|
|
367
|
+
|
|
368
|
+
## Login on new device
|
|
369
|
+
1. User authenticates via provider.
|
|
370
|
+
2. App fetches envelope from Supabase.
|
|
371
|
+
3. User enters wallet passphrase.
|
|
372
|
+
4. Client decrypts locally and unlocks wallet.
|
|
373
|
+
|
|
374
|
+
## Passphrase change
|
|
375
|
+
1. Unlock with current passphrase.
|
|
376
|
+
2. Re-encrypt with new Argon2id salt/params.
|
|
377
|
+
3. Replace envelope atomically.
|
|
378
|
+
|
|
379
|
+
## Email protection-code recovery
|
|
380
|
+
1. User requests login code by email.
|
|
381
|
+
2. Enters code and gets authenticated session.
|
|
382
|
+
3. If wallet envelope exists, user enters wallet passphrase to decrypt.
|
|
383
|
+
4. If passphrase is forgotten, user must recover with mnemonic and set a new passphrase.
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## 10) Operational Security Requirements
|
|
388
|
+
|
|
389
|
+
- CSP hardening and dependency pinning for web/TMA.
|
|
390
|
+
- Secrets scanning and signed CI artifacts.
|
|
391
|
+
- Audit trail for sensitive actions.
|
|
392
|
+
- Incident playbook for account takeover and suspicious activity.
|
|
393
|
+
- User alerts (new login, passphrase changed, provider linked/unlinked).
|
|
394
|
+
- User alerts (new OTP login, passphrase changed, provider linked/unlinked).
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## 11) Migration Plan (from current model)
|
|
399
|
+
|
|
400
|
+
1. Keep existing TMA SecureStorage/DeviceStorage path active.
|
|
401
|
+
2. Add optional centralized envelope creation for users.
|
|
402
|
+
3. Backfill on next successful unlock/sign event.
|
|
403
|
+
4. After adoption, use centralized envelope as cross-device recovery path.
|
|
404
|
+
5. Maintain mnemonic-first emergency recovery.
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## 12) Scope, Non-goals, and Warnings
|
|
409
|
+
|
|
410
|
+
Non-goals:
|
|
411
|
+
- Backend plaintext key custody
|
|
412
|
+
- Signing in backend with user mnemonic/private key
|
|
413
|
+
|
|
414
|
+
Warnings:
|
|
415
|
+
- If user forgets both mnemonic and passphrase, recovery is impossible by design.
|
|
416
|
+
- Centralized ciphertext still increases account-takeover pressure; defense must focus on OTP hardening, rate limits, and alerts.
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## 13) Phase Breakdown
|
|
421
|
+
|
|
422
|
+
## Phase A: Identity foundation
|
|
423
|
+
- Configure Supabase Auth providers.
|
|
424
|
+
- Add account linking UX.
|
|
425
|
+
- Add RLS and identity tables.
|
|
426
|
+
|
|
427
|
+
## Phase B: Envelope crypto
|
|
428
|
+
- Implement client Argon2id + AEAD module.
|
|
429
|
+
- Add `wallet_envelopes` APIs and tests.
|
|
430
|
+
|
|
431
|
+
## Phase C: Recovery UX
|
|
432
|
+
- New device unlock flow.
|
|
433
|
+
- Passphrase reset/re-encrypt flow.
|
|
434
|
+
|
|
435
|
+
## Phase D: Hardening
|
|
436
|
+
- Abuse controls, telemetry, alerts, and audits.
|
|
437
|
+
|
|
438
|
+
## Phase E: Rollout
|
|
439
|
+
- Feature flags, gradual rollout, and migration metrics.
|
|
440
|
+
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Mini App key retrieval checker (paste into DevTools console).
|
|
3
|
+
*
|
|
4
|
+
* Purpose:
|
|
5
|
+
* - Inspect where wallet keys are currently stored after app flow runs.
|
|
6
|
+
* - Check these keys across available storages:
|
|
7
|
+
* 1) SecureStorage: wallet_master_key
|
|
8
|
+
* 2) DeviceStorage: wallet_master_key
|
|
9
|
+
* 3) CloudStorage: wallet_seed_cipher
|
|
10
|
+
*
|
|
11
|
+
* What it prints:
|
|
12
|
+
* - Per-storage availability (API object + method presence).
|
|
13
|
+
* - Read result for each key (err/value present/length).
|
|
14
|
+
* - Final quick verdict about expected Desktop fallback model:
|
|
15
|
+
* - secureOnly: master key in SecureStorage
|
|
16
|
+
* - deviceFallback: master key in DeviceStorage
|
|
17
|
+
* - none: master key not found in either local store
|
|
18
|
+
*
|
|
19
|
+
* Notes:
|
|
20
|
+
* - This script does not print full secret values; it shows only existence/length.
|
|
21
|
+
* - In some Telegram Desktop builds, SecureStorage methods exist but return UNSUPPORTED.
|
|
22
|
+
*/
|
|
23
|
+
(async () => {
|
|
24
|
+
const wa = window.Telegram?.WebApp;
|
|
25
|
+
if (!wa) {
|
|
26
|
+
const out = { ok: false, error: "Telegram.WebApp not found" };
|
|
27
|
+
console.log("[keys-retrieval]", out);
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const readSecure = (key) =>
|
|
32
|
+
new Promise((resolve) => {
|
|
33
|
+
const storage = wa.SecureStorage;
|
|
34
|
+
if (!storage || typeof storage.getItem !== "function") {
|
|
35
|
+
resolve({ present: false, err: "NO_API", value: null, canRestore: null });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
storage.getItem(key, (err, value, canRestore) => {
|
|
39
|
+
resolve({
|
|
40
|
+
present: true,
|
|
41
|
+
err: err ?? null,
|
|
42
|
+
value: value ?? null,
|
|
43
|
+
canRestore: canRestore ?? null,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const readDevice = (key) =>
|
|
49
|
+
new Promise((resolve) => {
|
|
50
|
+
const storage = wa.DeviceStorage;
|
|
51
|
+
if (!storage || typeof storage.getItem !== "function") {
|
|
52
|
+
resolve({ present: false, err: "NO_API", value: null });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
storage.getItem(key, (err, value) => {
|
|
56
|
+
resolve({
|
|
57
|
+
present: true,
|
|
58
|
+
err: err ?? null,
|
|
59
|
+
value: value ?? null,
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const readCloud = (key) =>
|
|
65
|
+
new Promise((resolve) => {
|
|
66
|
+
const storage = wa.CloudStorage;
|
|
67
|
+
if (!storage || typeof storage.getItem !== "function") {
|
|
68
|
+
resolve({ present: false, err: "NO_API", value: null });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
storage.getItem(key, (err, value) => {
|
|
72
|
+
resolve({
|
|
73
|
+
present: true,
|
|
74
|
+
err: err ?? null,
|
|
75
|
+
value: value ?? null,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const [secureMaster, deviceMaster, cloudSeedCipher] = await Promise.all([
|
|
81
|
+
readSecure("wallet_master_key"),
|
|
82
|
+
readDevice("wallet_master_key"),
|
|
83
|
+
readCloud("wallet_seed_cipher"),
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
const hasSecureMaster = secureMaster.value != null && secureMaster.err == null;
|
|
87
|
+
const hasDeviceMaster = deviceMaster.value != null && deviceMaster.err == null;
|
|
88
|
+
const hasCloudSeedCipher = cloudSeedCipher.value != null && cloudSeedCipher.err == null;
|
|
89
|
+
|
|
90
|
+
const localTier = hasSecureMaster ? "secureOnly" : hasDeviceMaster ? "deviceFallback" : "none";
|
|
91
|
+
|
|
92
|
+
const result = {
|
|
93
|
+
ok: true,
|
|
94
|
+
webApp: {
|
|
95
|
+
platform: wa.platform ?? null,
|
|
96
|
+
version: wa.version ?? null,
|
|
97
|
+
},
|
|
98
|
+
keys: {
|
|
99
|
+
wallet_master_key: {
|
|
100
|
+
secureStorage: {
|
|
101
|
+
available: secureMaster.present,
|
|
102
|
+
err: secureMaster.err,
|
|
103
|
+
exists: hasSecureMaster,
|
|
104
|
+
valueLength: typeof secureMaster.value === "string" ? secureMaster.value.length : 0,
|
|
105
|
+
canRestore: secureMaster.canRestore ?? null,
|
|
106
|
+
},
|
|
107
|
+
deviceStorage: {
|
|
108
|
+
available: deviceMaster.present,
|
|
109
|
+
err: deviceMaster.err,
|
|
110
|
+
exists: hasDeviceMaster,
|
|
111
|
+
valueLength: typeof deviceMaster.value === "string" ? deviceMaster.value.length : 0,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
wallet_seed_cipher: {
|
|
115
|
+
cloudStorage: {
|
|
116
|
+
available: cloudSeedCipher.present,
|
|
117
|
+
err: cloudSeedCipher.err,
|
|
118
|
+
exists: hasCloudSeedCipher,
|
|
119
|
+
valueLength: typeof cloudSeedCipher.value === "string" ? cloudSeedCipher.value.length : 0,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
verdict: {
|
|
124
|
+
localTier,
|
|
125
|
+
modelOkForDesktopFallback: localTier === "deviceFallback" && hasCloudSeedCipher,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
console.log("[keys-retrieval]", result);
|
|
130
|
+
return result;
|
|
131
|
+
})();
|
|
@@ -78,7 +78,7 @@ This file describes a practical implementation plan for the security model defin
|
|
|
78
78
|
- Step 3: Show mnemonic & confirmation quiz.
|
|
79
79
|
- Step 4:
|
|
80
80
|
- Derive wallet master key from mnemonic.
|
|
81
|
-
- Store it via `
|
|
81
|
+
- Store it via `SecureStorage.setItem('wallet_master_key', …)` when supported; if that fails (e.g. Desktop `UNSUPPORTED`), fall back to `DeviceStorage` and warn the user (see `docs/security_raw.md`).
|
|
82
82
|
- Derive/encode wallet seed or root key, encrypt with master key → `seed_cipher`.
|
|
83
83
|
- Store `seed_cipher` via `Telegram.WebApp.CloudStorage.setItem('wallet_seed_cipher', <cipher>)`.
|
|
84
84
|
- Derive `wallet_address` from mnemonic/root key.
|
package/docs/security_raw.md
CHANGED
|
@@ -21,9 +21,20 @@ This document describes the current high-level design; it is not a formal audit.
|
|
|
21
21
|
- **Wallet seed cipher**: Encrypted blob that contains the wallet seed (or a seed-derived secret), usable only together with the wallet master key.
|
|
22
22
|
- **Telegram CloudStorage**: Per-user, per-bot key–value store synced via Telegram servers. Suitable for non-secret data or ciphertext.
|
|
23
23
|
- **Telegram SecureStorage**: Per-device secure storage, backed by iOS Keychain / Android Keystore, intended for small sensitive values like tokens and keys (see [Mini Apps docs](https://core.telegram.org/bots/webapps#securestorage)).
|
|
24
|
+
- **Telegram DeviceStorage**: Persistent **local** key–value storage inside the Telegram client for the bot (up to 5 MB per user). Telegram documents it as conceptually similar to browser `localStorage`, **not** as Keychain/Keystore-backed secure storage (see [DeviceStorage](https://core.telegram.org/bots/webapps#devicestorage)).
|
|
24
25
|
|
|
25
26
|
We **never store raw mnemonics** on our backend or in Telegram CloudStorage.
|
|
26
27
|
|
|
28
|
+
### SecureStorage vs DeviceStorage (product stance)
|
|
29
|
+
|
|
30
|
+
- **SecureStorage** is the preferred place for the wallet master key: encrypted at rest and isolated using OS secure hardware where Telegram implements it.
|
|
31
|
+
- Some clients (notably **Telegram Desktop** in common builds) report `secure_storage_failed` with errors such as **`UNSUPPORTED`**, meaning there is no SecureStorage bridge for that Mini App session.
|
|
32
|
+
- **DeviceStorage fallback** (used only when SecureStorage fails) keeps the same split as the design (master key local, seed as ciphertext in CloudStorage), but the master key is **not** under the same hardware-backed guarantees. It is **stricter than random website `localStorage`** (scoped to the Telegram client and bot), yet **strictly weaker than SecureStorage** against threats that can read Mini App storage (e.g. XSS, compromised WebView, malware with access to Telegram’s local storage).
|
|
33
|
+
|
|
34
|
+
**Alternative architectures** (not the default in this repo) include: **require** SecureStorage and **block** signing until the user opens the Mini App on a supported client, or a **Tonkeeper-style** model (password-encrypted secrets with persistence via CloudStorage only, with security bounded by password strength and KDF choice).
|
|
35
|
+
|
|
36
|
+
The Mini App implementation tries **SecureStorage first**, then **DeviceStorage** for `wallet_master_key`, and surfaces a short in-app notice when the weaker path is used.
|
|
37
|
+
|
|
27
38
|
---
|
|
28
39
|
|
|
29
40
|
## I. Wallet Creation in Telegram
|
|
@@ -36,10 +47,8 @@ Flow when a user creates a wallet for the first time in the Telegram Mini App on
|
|
|
36
47
|
|
|
37
48
|
2. **Derive wallet master key (device-local)**
|
|
38
49
|
- From the mnemonic, the app derives a **wallet master key** (e.g. via a BIP-style KDF / HKDF).
|
|
39
|
-
- This master key is stored
|
|
40
|
-
-
|
|
41
|
-
- Encrypted at rest.
|
|
42
|
-
- Bound to this device + Telegram app.
|
|
50
|
+
- This master key is stored in Telegram **`SecureStorage` when available** (Keychain / Keystore on mobile).
|
|
51
|
+
- If SecureStorage is unavailable (`UNSUPPORTED` or failure), the app **falls back to `DeviceStorage`** and explains the weaker guarantee in the UI (see “SecureStorage vs DeviceStorage” above).
|
|
43
52
|
|
|
44
53
|
3. **Create and store wallet seed cipher (cloud)**
|
|
45
54
|
- The app creates a **wallet seed cipher**:
|
|
@@ -57,12 +66,12 @@ Flow when a user creates a wallet for the first time in the Telegram Mini App on
|
|
|
57
66
|
**Properties**
|
|
58
67
|
|
|
59
68
|
- The **first Telegram device** has everything needed to use the wallet:
|
|
60
|
-
- Master key in `SecureStorage`.
|
|
69
|
+
- Master key in `SecureStorage` or, on some clients, `DeviceStorage`.
|
|
61
70
|
- Seed cipher in `CloudStorage`.
|
|
62
71
|
- If the app is reinstalled on the **same device**, we can:
|
|
63
|
-
- Recover the master key from
|
|
72
|
+
- Recover the master key from the same Telegram local store (if Telegram restores it).
|
|
64
73
|
- Decrypt the seed cipher for a smooth UX without re-entering the mnemonic.
|
|
65
|
-
- If
|
|
74
|
+
- If local storage is wiped, the user must re-enter the mnemonic.
|
|
66
75
|
|
|
67
76
|
---
|
|
68
77
|
|
|
@@ -275,7 +284,7 @@ When the user taps the button in the bot message and opens the Mini App:
|
|
|
275
284
|
On **Confirm** from the Mini App:
|
|
276
285
|
|
|
277
286
|
1. Mini App reconstructs/derives the transaction to be signed using:
|
|
278
|
-
- The locally available wallet (seed/master key in `SecureStorage`).
|
|
287
|
+
- The locally available wallet (seed/master key in Telegram `SecureStorage` or `DeviceStorage` fallback).
|
|
279
288
|
- The payload from `pending_transactions`.
|
|
280
289
|
2. Mini App signs the transaction **locally** using the wallet key.
|
|
281
290
|
3. Mini App sends a request to a serverless endpoint, e.g. `POST /api/tx/<id>/complete` with:
|
|
@@ -311,7 +320,7 @@ For users who click a **web URL** from the bot instead of the Mini App:
|
|
|
311
320
|
- No private keys or mnemonics are ever stored or derived in serverless functions.
|
|
312
321
|
- Bot pushes and confirmation flows are coordinated exclusively via:
|
|
313
322
|
- Telegram Bot API (for notifications),
|
|
314
|
-
- Telegram Mini App (for
|
|
323
|
+
- Telegram Mini App (for signing with local master key in SecureStorage or DeviceStorage fallback),
|
|
315
324
|
- Telegram Login (for identity on web / mobile).
|
|
316
325
|
|
|
317
326
|
This model keeps transaction approvals **user‑driven and key‑local** (inside Telegram or another explicit wallet) while still fitting neatly into a serverless architecture.
|
|
@@ -325,12 +334,12 @@ This model keeps transaction approvals **user‑driven and key‑local** (inside
|
|
|
325
334
|
- Neither our backend nor Telegram can unilaterally move funds without the mnemonic / keys.
|
|
326
335
|
|
|
327
336
|
- **Device-local keys:**
|
|
328
|
-
- Each device has its own **wallet master key** stored in
|
|
337
|
+
- Each device has its own **wallet master key** stored in Telegram **SecureStorage** when supported, else **DeviceStorage** (weaker).
|
|
329
338
|
- Compromise of one device does **not** automatically compromise others.
|
|
330
339
|
|
|
331
340
|
- **Cloud data is ciphertext only:**
|
|
332
|
-
- `Wallet Seed Cipher` in CloudStorage is encrypted with
|
|
333
|
-
- An attacker with only CloudStorage access cannot derive the mnemonic.
|
|
341
|
+
- `Wallet Seed Cipher` in CloudStorage is encrypted with the wallet master key, which should exist only on the user’s Telegram client for that device.
|
|
342
|
+
- An attacker with only CloudStorage access cannot derive the mnemonic without the master key.
|
|
334
343
|
|
|
335
344
|
- **Cross-platform restore requires mnemonic:**
|
|
336
345
|
- Any **new environment** (new Telegram device with empty SecureStorage or any non-Telegram platform) requires the mnemonic once.
|
|
@@ -341,5 +350,5 @@ This model keeps transaction approvals **user‑driven and key‑local** (inside
|
|
|
341
350
|
|
|
342
351
|
- If the **mnemonic is lost**, there is no recovery (by design) – it is the self-custodial root.
|
|
343
352
|
- If a device with a master key is compromised, an attacker can act as the owner from that device until the user moves funds to a new wallet.
|
|
344
|
-
- Telegram `SecureStorage` is documented for **iOS and Android**;
|
|
353
|
+
- Telegram `SecureStorage` is documented for **iOS and Android**; **Desktop and some builds may not support it**, triggering fallback to `DeviceStorage` or leaving the key unsaved until the user uses a supported client or restores with the mnemonic.
|
|
345
354
|
- Telegram Login for Websites is an **authentication mechanism only** – it does not give access to keys or the mnemonic itself, and cannot replace the mnemonic for wallet authorization.
|