phrasekit 1.0.0 β†’ 1.0.2

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 +106 -24
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -4,17 +4,50 @@
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.
20
+ - **No identity, no liability:** If there's nothing personal to store, there's nothing to leak, hand over, or protect.
21
+
22
+ ## πŸ” Privacy by Law, Not Just by Design
23
+
24
+ Most auth systems collect personal data by default β€” emails, names, phone numbers.
25
+ This creates legal obligations under **GDPR** and **152-FZ**: secure storage,
26
+ breach reporting, and government handover on request.
27
+
28
+ **phrasekit** eliminates this at the root. There's nothing to hand over and expose,
29
+ because there's nothing to store.
30
+
31
+ > ⚠️ **This library uses SHA-256 for hashing. This is intentional.
32
+ > phrasekit is not designed for services that store personal data.
33
+ > If your users have no identity β€” there's nothing sensitive to protect.
34
+ > For traditional auth with personal data, use bcrypt/Argon2.**
35
+
36
+ **Natural fit for:**
37
+
38
+ - VPN and proxy services
39
+ - Anonymous social platforms
40
+ - Any service where privacy is a legal requirement
41
+
42
+ ## πŸ“¦ Installation
43
+
44
+ ```bash
45
+ npm install phrasekit
46
+ # or
47
+ pnpm add phrasekit
48
+ # or
49
+ bun add phrasekit
50
+ ```
18
51
 
19
52
  ## πŸš€ Quick Start
20
53
 
@@ -26,16 +59,19 @@ const phrase = phrasekit.generate(6);
26
59
  console.log(phrase.toString()); // "glider confirm armhole swoop lacing lemon"
27
60
 
28
61
  // 2. Get a unique ID for your database (salted)
29
- const accountId = await phrase.hash("your-app-salt");
62
+ const phraseHash = await phrase.hash("your-app-salt");
30
63
 
31
64
  // 3. Authenticate user input
65
+ // Use .suggest() for your UI autocomplete
66
+ const search = "app";
67
+ const suggestions = phrasekit.suggest(search); // ['apple', 'apply', 'appoint', ...]
32
68
  try {
33
69
  const userPhrase = phrasekit.from(
34
70
  "glider confirm armhole swoop lacing lemon",
35
71
  );
36
- const loginId = await userPhrase.hash("your-app-salt");
72
+ const userPhraseHash = await userPhrase.hash("your-app-salt");
37
73
 
38
- if (loginId === accountId) {
74
+ if (phraseHash === userPhraseHash) {
39
75
  // Access granted!
40
76
  }
41
77
  } catch (e) {
@@ -45,36 +81,82 @@ try {
45
81
 
46
82
  ## 🌈 API Reference
47
83
 
48
- ### `phrasekit` (The Toolkit)
84
+ ```javascript
85
+ // Toolkit itself
86
+ // The library exports a pre-instantiated `phrasekit` instance,
87
+ // but you can also import the class to use a custom wordlist.
88
+ class PhraseKit {
89
+ private readonly words;
90
+ private readonly wordSet;
91
+
92
+ constructor(customList?: string[]); // Can be created with custom wordList if needed
93
+
94
+ generate(count?: number): Phrase; // Generates a cryptographically secure Phrase object. Defaults to 6 words.
95
+
96
+ from(input: string | string[], separator?: string): Phrase; // Creates a Phrase object from user input. Normalizes casing and spaces. Throws if words are invalid.
97
+
98
+ suggest(prefix: string, limit?: number): string[]; // Returns words from the EFF dictionary starting with the prefix. Ready and perfect for UI autocomplete.
99
+
100
+ validate(phrase: string | string[], separator?: string): boolean; // Quickly checks if the input is a valid phrase without throwing errors.
101
+ }
102
+
103
+ // Result returned by toolkit
104
+ class Phrase {
105
+ readonly words: string[];
106
+ private readonly dictionarySize;
107
+
108
+ constructor(words: string[], dictionarySize: number);
49
109
 
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.
110
+ get entropy(): number; // Calculation of bits of randomness (e.g., ~77.5 for 6 words).
58
111
 
59
- ### `Phrase` (The Result)
112
+ toString(): string; // Returns the phrase joined by spaces.
113
+ toJSON(): string[]; // Returns same output as this.words give
114
+ join(separator: string): string; // Returns the phrase with a custom separator (e.g., -).
60
115
 
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.
116
+ hash(salt?: string): Promise<string>; // Returns a SHA-256 hex-encoded hash.
117
+ }
118
+ ```
66
119
 
67
120
  ## πŸ€” Wait, why not BIP39?
68
121
 
69
- BIP39 is the standard for crypto wallets, but it's often too rigid for simple account authentication:
122
+ 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
123
 
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).
124
+ 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
125
  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
126
  3. **Human-Centric:** EFF words were specifically designed to be easy to read and type, reducing errors when your users are logging in.
74
127
  4. **Zero Dependencies:** Most BIP39 libraries pull in heavy crypto-dependencies. **phrasekit** is tiny and uses native Web Crypto API.
75
128
 
76
129
  > 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
130
 
131
+ ## πŸ“Š Let’s talk math (the fun kind)
132
+
133
+ You might wonder: _"Wait, isn't 6 words too few? Crypto wallets use 12!"_
134
+ Here is how **phrasekit** stacks up against other methods when we talk about entropy (the "guessability" of your secret):
135
+
136
+ | Method | Combination pool | Entropy | Best for |
137
+ | :------------------------- | :--------------- | :------------- | :----------------- |
138
+ | **Mullvad ID** (16 digits) | $10^{16}$ | ~53 bits | Online Auth |
139
+ | **phrasekit** (6 words) | $7,776^6$ | **~77.5 bits** | **The Sweet Spot** |
140
+ | **BIP39** (12 words) | $2,048^{12}$ | ~128 bits | Cold Storage |
141
+
142
+ #### The "Online" Reality Check
143
+
144
+ 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.
145
+
146
+ 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.
147
+
148
+ **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".
149
+
150
+ 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.
151
+
152
+ ## πŸ’– Inspirations and Thanks to
153
+
154
+ This project wouldn't exist without the amazing work of others:
155
+
156
+ - **[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.
157
+ - **[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.
158
+ - **[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.
159
+
78
160
  ## πŸ“„ License
79
161
 
80
- MIT Β© 2026 by Aria Lume <thearialume@gmail.com>
162
+ 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.2",
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",