voidlogue-crypto 2.0.2 → 2.0.3
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/README.md +4 -1
- package/SECURITY.md +3 -2
- package/package.json +6 -6
- package/src/voidshield.ts +18 -5
package/README.md
CHANGED
|
@@ -66,6 +66,8 @@ const codename = generateCodename(4); // e.g. "correct-horse-battery-staple"
|
|
|
66
66
|
| Method | Description |
|
|
67
67
|
|---|---|
|
|
68
68
|
| `hex(input)` | SHA-256 of input string → 64-char hex |
|
|
69
|
+
| `relationshipHash(emailA, emailB)` | Pure relationship identifier. Commutative. |
|
|
70
|
+
| `isInitiator(myEmail, theirEmail)` | Deterministic tie-breaker for UI deadlocks |
|
|
69
71
|
| `roomId(emailA, emailB, codename)` | Derives opaque room hash. Commutative. |
|
|
70
72
|
| `validateCodename(codename)` | Returns `{ valid, reason? }` |
|
|
71
73
|
| `deriveKey(codename, roomHash)` | PBKDF2 → AES-256-GCM key (non-extractable) |
|
|
@@ -138,7 +140,8 @@ console.log(EFF_WORDLIST.length); // 7776
|
|
|
138
140
|
```
|
|
139
141
|
Room hash:
|
|
140
142
|
hA, hB = SHA-256(email.toLowerCase().trim())
|
|
141
|
-
|
|
143
|
+
relHash = SHA-256(sort([hA,hB]).join(":") + ":" + APP_SALT + ":relationship")
|
|
144
|
+
roomHash = SHA-256(relHash + ":" + codename + ":" + APP_SALT)
|
|
142
145
|
|
|
143
146
|
Conversation key:
|
|
144
147
|
key = PBKDF2(codename, salt=roomHash, iters=600_000, hash=SHA-256) → AES-256-GCM
|
package/SECURITY.md
CHANGED
|
@@ -10,7 +10,7 @@ against the actual code running in the browser.
|
|
|
10
10
|
|
|
11
11
|
### Claim 1: "We cannot read your Conversation messages"
|
|
12
12
|
|
|
13
|
-
**Code that proves it:** `src/voidshield.ts` — `roomId()`, `deriveKey()`, `encrypt()`
|
|
13
|
+
**Code that proves it:** `src/voidshield.ts` — `relationshipHash()`, `roomId()`, `deriveKey()`, `encrypt()`
|
|
14
14
|
|
|
15
15
|
Room hashes are derived entirely client-side from the pair of email addresses
|
|
16
16
|
and the shared codename. The algorithm:
|
|
@@ -18,7 +18,8 @@ and the shared codename. The algorithm:
|
|
|
18
18
|
```
|
|
19
19
|
hA = SHA-256(emailA.toLowerCase().trim())
|
|
20
20
|
hB = SHA-256(emailB.toLowerCase().trim())
|
|
21
|
-
|
|
21
|
+
relHash = SHA-256(sort([hA, hB]).join(":") + ":" + APP_SALT + ":relationship")
|
|
22
|
+
roomHash = SHA-256(relHash + ":" + codename + ":" + APP_SALT)
|
|
22
23
|
```
|
|
23
24
|
|
|
24
25
|
The server receives only `roomHash` — an opaque 64-character hex string. It
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "voidlogue-crypto",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "Open-source client-side cryptographic primitives for Voidlogue — published for independent audit and verification of privacy claims.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -45,14 +45,14 @@
|
|
|
45
45
|
"node": ">=18.0.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@eslint/js": "^
|
|
49
|
-
"@types/node": "^
|
|
50
|
-
"eslint": "^
|
|
48
|
+
"@eslint/js": "^10.0.1",
|
|
49
|
+
"@types/node": "^25.5.2",
|
|
50
|
+
"eslint": "^10.2.0",
|
|
51
51
|
"expect": "^30.3.0",
|
|
52
|
-
"fast-check": "^
|
|
52
|
+
"fast-check": "^4.6.0",
|
|
53
53
|
"prettier": "^3.0.0",
|
|
54
54
|
"tsx": "^4.21.0",
|
|
55
|
-
"typescript": "^
|
|
55
|
+
"typescript": "^6.0.2",
|
|
56
56
|
"typescript-eslint": "^8.58.0"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
package/src/voidshield.ts
CHANGED
|
@@ -65,6 +65,8 @@ export interface KyberKeyPair {
|
|
|
65
65
|
|
|
66
66
|
export interface VoidShieldAPI {
|
|
67
67
|
hex(input: string): Promise<string>;
|
|
68
|
+
relationshipHash(emailA: string, emailB: string): Promise<string>;
|
|
69
|
+
isInitiator(myEmail: string, theirEmail: string): Promise<boolean>;
|
|
68
70
|
roomId(emailA: string, emailB: string, codename: string): Promise<string>;
|
|
69
71
|
validateCodename(codename: string): CodenameValidation;
|
|
70
72
|
deriveKey(codename: string, roomHash: string): Promise<CryptoKey>;
|
|
@@ -210,16 +212,27 @@ export const VoidShield: VoidShieldAPI = {
|
|
|
210
212
|
|
|
211
213
|
// ── Conversation ─────────────────────────────────────────────────────────
|
|
212
214
|
|
|
215
|
+
async relationshipHash(emailA: string, emailB: string): Promise<string> {
|
|
216
|
+
const [hA, hB] = await Promise.all([
|
|
217
|
+
this.hex(emailA.toLowerCase().trim()),
|
|
218
|
+
this.hex(emailB.toLowerCase().trim()),
|
|
219
|
+
]);
|
|
220
|
+
return this.hex(`${[hA, hB].sort().join(':')}:${APP_SALT}:relationship`);
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
async isInitiator(myEmail: string, theirEmail: string): Promise<boolean> {
|
|
224
|
+
const hashMe = await this.hex(myEmail.toLowerCase().trim());
|
|
225
|
+
const hashThem = await this.hex(theirEmail.toLowerCase().trim());
|
|
226
|
+
return hashMe < hashThem;
|
|
227
|
+
},
|
|
228
|
+
|
|
213
229
|
async roomId(
|
|
214
230
|
emailA: string,
|
|
215
231
|
emailB: string,
|
|
216
232
|
codename: string
|
|
217
233
|
): Promise<string> {
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
this.hex(emailB.toLowerCase().trim()),
|
|
221
|
-
]);
|
|
222
|
-
return this.hex(`${[hA, hB].sort().join(':')}:${codename}:${APP_SALT}`);
|
|
234
|
+
const relHash = await this.relationshipHash(emailA, emailB);
|
|
235
|
+
return this.hex(`${relHash}:${codename}:${APP_SALT}`);
|
|
223
236
|
},
|
|
224
237
|
|
|
225
238
|
validateCodename(codename: string): CodenameValidation {
|