@gryt/crypto 0.3.0 → 0.4.0
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/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/seed-words.d.ts +37 -0
- package/dist/seed-words.js +64 -0
- package/package.json +3 -2
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The identity seed as 24 words, and back.
|
|
3
|
+
*
|
|
4
|
+
* Both apps had this, and a phrase written down from one has to restore on the
|
|
5
|
+
* other or it is worthless. They agreed — same wordlist, same BIP-39 calls, the
|
|
6
|
+
* same 24 — but they agreed by two people keeping two files in step, which is
|
|
7
|
+
* the arrangement this package exists to end.
|
|
8
|
+
*
|
|
9
|
+
* The standard 2048-word list is 11 bits a word: 256 bits of seed plus 8 bits of
|
|
10
|
+
* checksum makes 264, which is 24 words exactly. The checksum lives inside the
|
|
11
|
+
* words rather than beside them, so a mistyped or reordered phrase is rejected
|
|
12
|
+
* instead of quietly producing a different identity — and since no two words in
|
|
13
|
+
* the list share their first four letters, a misread word usually is not a word
|
|
14
|
+
* at all and fails before the checksum is reached.
|
|
15
|
+
*
|
|
16
|
+
* Only the encoding is borrowed from wallets. The key-stretching step they do on
|
|
17
|
+
* top is not wanted here: these 256 bits are the seed already, not a passphrase
|
|
18
|
+
* to grind into one.
|
|
19
|
+
*/
|
|
20
|
+
/** Length of the seed every local identity is calculated from. */
|
|
21
|
+
export declare const SEED_BYTES = 32;
|
|
22
|
+
/** How many words a backup is. Stated once so the message and the check agree. */
|
|
23
|
+
export declare const BACKUP_WORDS = 24;
|
|
24
|
+
/**
|
|
25
|
+
* Refuse a seed that cannot be one.
|
|
26
|
+
*
|
|
27
|
+
* The length check is the real one. The repeated-byte check is for a generator
|
|
28
|
+
* that has failed open and is handing back zeros, which is worth catching loudly
|
|
29
|
+
* rather than deriving a whole identity from.
|
|
30
|
+
*/
|
|
31
|
+
export declare function assertUsableSeed(seed: Uint8Array): void;
|
|
32
|
+
export declare function seedToWords(seed: Uint8Array): string;
|
|
33
|
+
/**
|
|
34
|
+
* The messages here are read by somebody typing 24 words back in, so they say
|
|
35
|
+
* what is wrong rather than that something is.
|
|
36
|
+
*/
|
|
37
|
+
export declare function wordsToSeed(phrase: string): Uint8Array;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { entropyToMnemonic, mnemonicToEntropy, validateMnemonic, } from "@scure/bip39";
|
|
2
|
+
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
3
|
+
/**
|
|
4
|
+
* The identity seed as 24 words, and back.
|
|
5
|
+
*
|
|
6
|
+
* Both apps had this, and a phrase written down from one has to restore on the
|
|
7
|
+
* other or it is worthless. They agreed — same wordlist, same BIP-39 calls, the
|
|
8
|
+
* same 24 — but they agreed by two people keeping two files in step, which is
|
|
9
|
+
* the arrangement this package exists to end.
|
|
10
|
+
*
|
|
11
|
+
* The standard 2048-word list is 11 bits a word: 256 bits of seed plus 8 bits of
|
|
12
|
+
* checksum makes 264, which is 24 words exactly. The checksum lives inside the
|
|
13
|
+
* words rather than beside them, so a mistyped or reordered phrase is rejected
|
|
14
|
+
* instead of quietly producing a different identity — and since no two words in
|
|
15
|
+
* the list share their first four letters, a misread word usually is not a word
|
|
16
|
+
* at all and fails before the checksum is reached.
|
|
17
|
+
*
|
|
18
|
+
* Only the encoding is borrowed from wallets. The key-stretching step they do on
|
|
19
|
+
* top is not wanted here: these 256 bits are the seed already, not a passphrase
|
|
20
|
+
* to grind into one.
|
|
21
|
+
*/
|
|
22
|
+
/** Length of the seed every local identity is calculated from. */
|
|
23
|
+
export const SEED_BYTES = 32;
|
|
24
|
+
/** How many words a backup is. Stated once so the message and the check agree. */
|
|
25
|
+
export const BACKUP_WORDS = 24;
|
|
26
|
+
/**
|
|
27
|
+
* Refuse a seed that cannot be one.
|
|
28
|
+
*
|
|
29
|
+
* The length check is the real one. The repeated-byte check is for a generator
|
|
30
|
+
* that has failed open and is handing back zeros, which is worth catching loudly
|
|
31
|
+
* rather than deriving a whole identity from.
|
|
32
|
+
*/
|
|
33
|
+
export function assertUsableSeed(seed) {
|
|
34
|
+
if (seed.length !== SEED_BYTES) {
|
|
35
|
+
throw new Error(`An identity seed is ${SEED_BYTES} bytes, not ${seed.length}.`);
|
|
36
|
+
}
|
|
37
|
+
const first = seed[0];
|
|
38
|
+
if (seed.every((b) => b === first)) {
|
|
39
|
+
throw new Error("Identity seed is a single repeated byte — the generator is broken.");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function seedToWords(seed) {
|
|
43
|
+
assertUsableSeed(seed);
|
|
44
|
+
return entropyToMnemonic(seed, wordlist);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The messages here are read by somebody typing 24 words back in, so they say
|
|
48
|
+
* what is wrong rather than that something is.
|
|
49
|
+
*/
|
|
50
|
+
export function wordsToSeed(phrase) {
|
|
51
|
+
const normalised = phrase.trim().toLowerCase().split(/\s+/).join(" ");
|
|
52
|
+
if (!normalised)
|
|
53
|
+
throw new Error("Enter your identity words.");
|
|
54
|
+
const count = normalised.split(" ").length;
|
|
55
|
+
if (count !== BACKUP_WORDS) {
|
|
56
|
+
throw new Error(`That is ${count} words — an identity backup is ${BACKUP_WORDS}.`);
|
|
57
|
+
}
|
|
58
|
+
if (!validateMnemonic(normalised, wordlist)) {
|
|
59
|
+
throw new Error("Those words aren't a valid identity backup. Check for a mistyped or swapped word.");
|
|
60
|
+
}
|
|
61
|
+
const seed = mnemonicToEntropy(normalised, wordlist);
|
|
62
|
+
assertUsableSeed(seed);
|
|
63
|
+
return seed;
|
|
64
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gryt/crypto",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Message encryption for Gryt: key derivation, key bindings, sealed envelopes, pinning and comparison codes. Shared by the desktop client and the mobile app.",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@noble/ciphers": "^2",
|
|
35
35
|
"@noble/curves": "^2.3.0",
|
|
36
|
-
"@noble/hashes": "^2.3.0"
|
|
36
|
+
"@noble/hashes": "^2.3.0",
|
|
37
|
+
"@scure/bip39": "^2.3.0"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
40
|
"@types/node": "^20.19.33",
|