phrasekit 1.0.0 β†’ 1.0.1

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.
Files changed (2) hide show
  1. package/README.md +88 -24
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -4,18 +4,30 @@
4
4
 
5
5
  A lightweight, zero-dependency TypeScript library for generating and managing secure word-based keys. Inspired by Mullvad VPN, but made more human.
6
6
 
7
- [![License: MIT](https://img.shields.io/badge/License-MIT-purple.svg)](https://opensource.org/licenses/MIT)
7
+ [![npm version](https://img.shields.io/npm/v/phrasekit)](https://www.npmjs.com/package/phrasekit)
8
+ [![npm downloads](https://img.shields.io/npm/dt/phrasekit)](https://www.npmjs.com/package/phrasekit)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
8
10
  ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue)
9
- ![Size](https://img.shields.io/bundlephobia/minzip/phrasekit)
11
+ ![Bundle Size](https://img.shields.io/bundlephobia/minzip/phrasekit)
10
12
 
11
13
  ## πŸ’‘ The Philosophy
12
14
 
13
- I loved the privacy of **Mullvad VPN**, but I always thought typing 16-digit numbers is quite uncomfortable. **phrasekit** is built on a few simple ideas:
15
+ I loved the privacy of **Mullvad VPN** and found their anonymous account system absolutely great! But for some reason, the idea of using a static 16-digit number didn't sound quite right and convenient in my head. So I decided to build **phrasekit** around few simple ideas:
14
16
 
15
17
  - **Words are better than numbers:** They are easier to read, remember, and type, especially with autocomplete.
16
18
  - **Passphrase is a secret, not an identity:** Unlike Mullvad's static account IDs, here a passphrase is more like a "password without a username". It generates a unique Hash ID for your database, but you can allow users to rotate or reset their phrases if needed.
17
19
  - **Privacy by design:** No emails, phone numbers or names. Just words.
18
20
 
21
+ ## πŸ“¦ Installation
22
+
23
+ ```bash
24
+ npm install phrasekit
25
+ # or
26
+ pnpm add phrasekit
27
+ # or
28
+ bun add phrasekit
29
+ ```
30
+
19
31
  ## πŸš€ Quick Start
20
32
 
21
33
  ```typescript
@@ -26,16 +38,22 @@ const phrase = phrasekit.generate(6);
26
38
  console.log(phrase.toString()); // "glider confirm armhole swoop lacing lemon"
27
39
 
28
40
  // 2. Get a unique ID for your database (salted)
29
- const accountId = await phrase.hash("your-app-salt");
41
+ const phraseHash = await phrase.hash("your-app-salt");
30
42
 
31
43
  // 3. Authenticate user input
44
+ // Use .suggest() for your UI autocomplete
45
+ const search = "app";
46
+ const suggestions = phrasekit.suggest(search);
47
+ // ['apple', 'apply', 'appoint', ...]
48
+
49
+ // 4. Authenticate user input
32
50
  try {
33
51
  const userPhrase = phrasekit.from(
34
52
  "glider confirm armhole swoop lacing lemon",
35
53
  );
36
- const loginId = await userPhrase.hash("your-app-salt");
54
+ const userPhraseHash = await userPhrase.hash("your-app-salt");
37
55
 
38
- if (loginId === accountId) {
56
+ if (phraseHash === userPhraseHash) {
39
57
  // Access granted!
40
58
  }
41
59
  } catch (e) {
@@ -45,36 +63,82 @@ try {
45
63
 
46
64
  ## 🌈 API Reference
47
65
 
48
- ### `phrasekit` (The Toolkit)
66
+ ```javascript
67
+ // Toolkit itself
68
+ // The library exports a pre-instantiated `phrasekit` instance,
69
+ // but you can also import the class to use a custom wordlist.
70
+ class PhraseKit {
71
+ private readonly words;
72
+ private readonly wordSet;
49
73
 
50
- - **`generate(count?: number): Phrase`**
51
- Generates a cryptographically secure `Phrase` object. Defaults to 6 words.
52
- - **`from(input: string | string[]): Phrase`**
53
- Creates a `Phrase` object from user input. Normalizes casing and spaces. Throws if words are invalid.
54
- - **`suggest(prefix: string, limit?: number): string[]`**
55
- Returns words from the EFF dictionary starting with the prefix. Perfect for UI autocomplete.
56
- - **`validate(input: string | string[]): boolean`**
57
- Quickly checks if the input is a valid phrase without throwing errors.
74
+ constructor(customList?: string[]); // Can be created with custom wordList if needed
58
75
 
59
- ### `Phrase` (The Result)
76
+ generate(count?: number): Phrase; // Generates a cryptographically secure Phrase object. Defaults to 6 words.
60
77
 
61
- - **`words: string[]`** β€” The raw array of words.
62
- - **`entropy: number`** β€” Calculation of bits of randomness (e.g., ~77.5 for 6 words).
63
- - **`toString()`** β€” Returns the phrase joined by spaces.
64
- - **`join(separator: string)`** β€” Returns the phrase with a custom separator (e.g., `-`).
65
- - **`hash(salt?: string): Promise<string>`** β€” Returns a SHA-256 hex-encoded hash.
78
+ from(input: string | string[], separator?: string): Phrase; // Creates a Phrase object from user input. Normalizes casing and spaces. Throws if words are invalid.
79
+
80
+ suggest(prefix: string, limit?: number): string[]; // Returns words from the EFF dictionary starting with the prefix. Ready and perfect for UI autocomplete.
81
+
82
+ validate(phrase: string | string[], separator?: string): boolean; // Quickly checks if the input is a valid phrase without throwing errors.
83
+ }
84
+
85
+ // Result returned by toolkit
86
+ class Phrase {
87
+ readonly words: string[];
88
+ private readonly dictionarySize;
89
+
90
+ constructor(words: string[], dictionarySize: number);
91
+
92
+ get entropy(): number; // Calculation of bits of randomness (e.g., ~77.5 for 6 words).
93
+
94
+ toString(): string; // Returns the phrase joined by spaces.
95
+ toJSON(): string[]; // Returns same output as this.words give
96
+ join(separator: string): string; // Returns the phrase with a custom separator (e.g., -).
97
+
98
+ hash(salt?: string): Promise<string>; // Returns a SHA-256 hex-encoded hash.
99
+ }
100
+ ```
66
101
 
67
102
  ## πŸ€” Wait, why not BIP39?
68
103
 
69
- BIP39 is the standard for crypto wallets, but it's often too rigid for simple account authentication:
104
+ BIP39 is the standard for crypto wallets and I won't lie, it's what I wanted to use from the start! But I found it too rigid for simple account authentication:
70
105
 
71
- 1. **Better Wordlist:** BIP39 uses 2,048 words. **phrasekit** uses the **EFF Large Wordlist** with 7,776 words. This means 6 words in phrasekit (~77 bits) provide significantly more entropy than 6 words in BIP39 (~66 bits).
106
+ 1. **Better Wordlist:** BIP39 uses 2,048 words. **phrasekit** uses the [**EFF Large Wordlist**](https://www.eff.org/files/2016/07/18/eff_large_wordlist.txt) with 7,776 words. This means 6 words in phrasekit (~77 bits) provide significantly more entropy than 6 words in BIP39 (~66 bits).
72
107
  2. **No Checksum Baggage:** BIP39 requires a specific checksum, which makes it impossible to just "pick" or "rotate" words freely. **phrasekit** is built for flexibility.
73
108
  3. **Human-Centric:** EFF words were specifically designed to be easy to read and type, reducing errors when your users are logging in.
74
109
  4. **Zero Dependencies:** Most BIP39 libraries pull in heavy crypto-dependencies. **phrasekit** is tiny and uses native Web Crypto API.
75
110
 
76
111
  > A passphrase here isn't a seed phrase β€” it's a secret key. Lose it, generate a new one, and rotate your Hash ID. Simple.
77
112
 
113
+ ## πŸ“Š Let’s talk math (the fun kind)
114
+
115
+ You might wonder: _"Wait, isn't 6 words too few? Crypto wallets use 12!"_
116
+ Here is how **phrasekit** stacks up against other methods when we talk about entropy (the "guessability" of your secret):
117
+
118
+ | Method | Combination pool | Entropy | Best for |
119
+ | :------------------------- | :--------------- | :------------- | :----------------- |
120
+ | **Mullvad ID** (16 digits) | $10^{16}$ | ~53 bits | Online Auth |
121
+ | **phrasekit** (6 words) | $7,776^6$ | **~77.5 bits** | **The Sweet Spot** |
122
+ | **BIP39** (12 words) | $2,048^{12}$ | ~128 bits | Cold Storage |
123
+
124
+ #### The "Online" Reality Check
125
+
126
+ The reason 12 words (BIP39) exist is to protect against **offline attacks**, where a hacker has your file and tries billions of keys per second on a massive GPU rig.
127
+
128
+ But **phrasekit** is for **online authentication**. Your server has rate limiting (hopefully!). Even if a hacker could try 100 phrases per second (which is a lot!), it would take them **more than a couple of hundred trillion years** to brute-force a 77-bit secret.
129
+
130
+ **The bottom line:** I chose 6 words because they are roughly **22,000,000 times more secure** than a standard 16-digit ID, while remaining short enough to type on a mobile keyboard without losing your mind. It’s the perfect balance between "impossible to guess" and "human-friendly".
131
+
132
+ And if you are _really_ paranoid, you can just call `phrasekit.generate(12)` and get 155 bits of entropy. That's more than some of hardware wallets.
133
+
134
+ ## πŸ’– Inspirations and Thanks to
135
+
136
+ This project wouldn't exist without the amazing work of others:
137
+
138
+ - **[Mullvad VPN](https://mullvad.net/):** For proving that anonymous, ID-based authentication is not just possible, but actually great for privacy-focused apps. They were the spark that started this idea.
139
+ - **[Electronic Frontier Foundation (EFF)](https://www.eff.org/):** For their incredible research and the [Large Wordlist](https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases). They did the heavy lifting of making secrets human-readable.
140
+ - **[Emil Bayes](https://github.com/emilbayes):** Thanks to his `eff-diceware-passphrase` library. It was the first place where I discovered the EFF wordlist and realized how cool word-based keys could be.
141
+
78
142
  ## πŸ“„ License
79
143
 
80
- MIT Β© 2026 by Aria Lume <thearialume@gmail.com>
144
+ MIT Β© 2026 by [Aria Lume](https://github.com/thearialume) <thearialume@gmail.com>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phrasekit",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "A lightweight, zero-dependency TypeScript library for generating and managing secure word-based keys. Inspired by Mullvad VPN, but made more human.",
5
5
  "author": "Aria Lume <thearialume@gmail.com> (https://github.com/thearialume)",
6
6
  "license": "MIT",