daku 0.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 (3) hide show
  1. package/README.md +199 -0
  2. package/index.js +256 -0
  3. package/package.json +50 -0
package/README.md ADDED
@@ -0,0 +1,199 @@
1
+ # daku
2
+
3
+ > Leave no trace. Just authenticate.
4
+
5
+ Cryptographic authentication library with built-in proof-of-work spam protection. No emails, no passwords, no personal data.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install daku
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ### 1. Generate a Keypair
16
+
17
+ ```javascript
18
+ import { generateKeyPair } from 'daku';
19
+
20
+ // Generate once and store securely (localStorage, secure storage, etc.)
21
+ const { privateKey, publicKey } = generateKeyPair();
22
+ ```
23
+
24
+ ### 2. Client-Side: Create Authentication Request
25
+
26
+ ```javascript
27
+ import { createAuth } from 'daku';
28
+
29
+ // Create authentication token (includes timestamp, nonce, signature, and POW)
30
+ const token = await createAuth(privateKey);
31
+
32
+ // Send to your server
33
+ fetch('/api/login', {
34
+ method: 'POST',
35
+ headers: {
36
+ 'Content-Type': 'application/json',
37
+ 'daku': token
38
+ }
39
+ });
40
+ ```
41
+
42
+ ### 3. Server-Side: Verify Authentication
43
+
44
+ ```javascript
45
+ import { verifyAuth } from 'daku';
46
+
47
+ const publicKey = await verifyAuth(req.headers['daku']);
48
+
49
+ if (publicKey) {
50
+ // ✅ Authenticated! Use publicKey as unique user ID
51
+ console.log(`User ${publicKey} authenticated`);
52
+ } else {
53
+ // ❌ Invalid or expired authentication
54
+ res.status(401).json({ error: 'Unauthorized' });
55
+ }
56
+ ```
57
+
58
+ ## ExpressJS Middleware
59
+
60
+ ```javascript
61
+ import express from 'express';
62
+ import { verifyAuth } from 'daku';
63
+
64
+ const app = express();
65
+
66
+ // Reusable authentication middleware
67
+ const daku = (powDifficulty = 2) => {
68
+ return async (req, res, next) => {
69
+ const token = req.headers['daku'];
70
+
71
+ if (!token) {
72
+ return res.status(401).json({ error: 'Unauthorized' });
73
+ }
74
+
75
+ const publicKey = await verifyAuth(token, powDifficulty);
76
+
77
+ if (!publicKey) {
78
+ return res.status(401).json({ error: 'Unauthorized' });
79
+ }
80
+
81
+ // Attach user's public key to request
82
+ req.userId = publicKey;
83
+ next();
84
+ };
85
+ };
86
+
87
+ // Use on protected routes
88
+ app.post('/api/protected', daku(), (req, res) => {
89
+ res.json({
90
+ message: 'Access granted',
91
+ userId: req.userId
92
+ });
93
+ });
94
+
95
+ // Use with custom POW difficulty
96
+ app.post('/api/high-security', daku(4), (req, res) => {
97
+ res.json({ message: 'High security endpoint' });
98
+ });
99
+
100
+ app.listen(3000);
101
+ ```
102
+
103
+ ## Key Features
104
+
105
+ - **🕵️ Anonymous**: No email, phone, or personal data required
106
+ - **🛡️ Spam Protection**: Built-in proof-of-work (default: 2 leading zeros)
107
+ - **🔐 Secure**: secp256k1 cryptographic signatures (same as Bitcoin/Ethereum)
108
+ - **⚡ Lightweight**: Minimal dependencies (@noble/secp256k1, @noble/hashes)
109
+ - **🌐 Cross-Platform**: Works in Node.js and browsers
110
+ - **⏱️ Time-Limited**: Auth requests expire after 1 minute
111
+
112
+ ## API Reference
113
+
114
+ ### Authentication Functions
115
+
116
+ #### `createAuth(privateKey, pow = 2)`
117
+
118
+ Creates a complete authentication token with timestamp, nonce, signature, and proof-of-work.
119
+
120
+ **Returns:** String (base64-encoded token)
121
+
122
+ **Example:**
123
+ ```javascript
124
+ const token = await createAuth(privateKey);
125
+ // "eyJwdWJsaWNrZXkiOiIwMmE..."
126
+ ```
127
+
128
+ #### `verifyAuth(token, pow = 2)`
129
+
130
+ Verifies authentication token. Checks signature validity, proof-of-work, and timestamp (must be within 1 minute).
131
+
132
+ **Returns:** `publicKey` string on success, `null` on failure
133
+
134
+ **Example:**
135
+ ```javascript
136
+ const publicKey = await verifyAuth(token);
137
+ ```
138
+
139
+ ---
140
+
141
+ ### General Signing Functions
142
+
143
+ Use these for signing arbitrary messages (not authentication).
144
+
145
+ #### `sign(message, privateKey, pow = 2)`
146
+
147
+ Signs any message with proof-of-work.
148
+
149
+ **Returns:** Object with `{ signature, pow }`
150
+
151
+ **Example:**
152
+ ```javascript
153
+ const result = await sign('Hello World', privateKey);
154
+ // { signature: 'a1b2c3...', pow: 42 }
155
+ ```
156
+
157
+ #### `verify(message, signatureData, publicKey, pow = 2)`
158
+
159
+ Verifies a signed message.
160
+
161
+ **Returns:** `true` if valid, `false` otherwise
162
+
163
+ **Example:**
164
+ ```javascript
165
+ const isValid = await verify('Hello World', { signature: 'a1b2...', pow: 42 }, publicKey);
166
+ ```
167
+
168
+ ---
169
+
170
+ ### Utility Functions
171
+
172
+ #### `generateKeyPair()`
173
+
174
+ Generates a new secp256k1 keypair.
175
+
176
+ **Returns:** `{ privateKey: string, publicKey: string }`
177
+
178
+ #### `getPublicKey(privateKey)`
179
+
180
+ Derives the public key from a private key.
181
+
182
+ **Returns:** `string` (compressed public key in hex)
183
+
184
+ #### `sha256(message)`
185
+
186
+ SHA-256 hash helper.
187
+
188
+ **Returns:** `Uint8Array`
189
+
190
+ ## Use Cases
191
+
192
+ - **Authentication**: Use `createAuth()` + `verifyAuth()` for login flows
193
+ - **Message Signing**: Use `sign()` + `verify()` for arbitrary data signatures
194
+ - **Spam Prevention**: POW difficulty prevents automated abuse
195
+ - **Privacy-First Apps**: No PII required, just cryptographic proofs
196
+
197
+ ## License
198
+
199
+ ISC
package/index.js ADDED
@@ -0,0 +1,256 @@
1
+ // Signature-Login: Cross-platform cryptographic login/verify module
2
+ import * as secp from "@noble/secp256k1";
3
+ import { hmac } from "@noble/hashes/hmac";
4
+ import { sha256 as nobleSha256 } from "@noble/hashes/sha2";
5
+
6
+ // Set up HMAC for secp256k1
7
+ secp.etc.hmacSha256Sync = (key, ...msgs) => hmac(nobleSha256, key, secp.etc.concatBytes(...msgs));
8
+
9
+ // Browser compatibility helper for TextEncoder
10
+ async function getTextEncoder() {
11
+ if (typeof window !== "undefined") {
12
+ return new window.TextEncoder();
13
+ } else {
14
+ // Dynamic import for Node.js only
15
+ const util = await import("node:util");
16
+ return new util.TextEncoder();
17
+ }
18
+ }
19
+
20
+ // Helper function to convert bytes to hex
21
+ function bytesToHex(bytes) {
22
+ return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
23
+ }
24
+
25
+ // Helper function to convert hex to bytes
26
+ function hexToBytes(hex) {
27
+ return new Uint8Array(hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
28
+ }
29
+
30
+ // Helper function to generate random bytes
31
+ async function randomBytes(length) {
32
+ if (typeof window !== "undefined" && window.crypto) {
33
+ const bytes = new Uint8Array(length);
34
+ window.crypto.getRandomValues(bytes);
35
+ return bytes;
36
+ } else {
37
+ // Dynamic import for Node.js only
38
+ const crypto = await import("node:crypto");
39
+ return new Uint8Array(crypto.randomBytes(length));
40
+ }
41
+ }
42
+
43
+ // --- Base64 Encoding/Decoding Helpers ---
44
+ // Browser-safe base64 encode
45
+ function base64Encode(obj) {
46
+ const json = JSON.stringify(obj);
47
+ if (typeof window !== "undefined") {
48
+ return btoa(json);
49
+ } else {
50
+ return Buffer.from(json).toString('base64');
51
+ }
52
+ }
53
+
54
+ // Browser-safe base64 decode
55
+ function base64Decode(str) {
56
+ if (typeof window !== "undefined") {
57
+ return JSON.parse(atob(str));
58
+ } else {
59
+ return JSON.parse(Buffer.from(str, 'base64').toString());
60
+ }
61
+ }
62
+
63
+ // --- Key Generation ---
64
+ export function generateKeyPair() {
65
+ const privateKey = secp.utils.randomPrivateKey();
66
+ const publicKey = secp.getPublicKey(privateKey, true); // compressed
67
+ return {
68
+ privateKey: bytesToHex(privateKey),
69
+ publicKey: bytesToHex(publicKey),
70
+ };
71
+ }
72
+
73
+ // --- Get Public Key from Private Key ---
74
+ export function getPublicKey(privateKeyHex) {
75
+ const privateKeyBytes = hexToBytes(privateKeyHex);
76
+ const publicKeyBytes = secp.getPublicKey(privateKeyBytes, true); // compressed
77
+ return bytesToHex(publicKeyBytes);
78
+ }
79
+
80
+ // --- Hashing (SHA-256) ---
81
+ export async function sha256(msg) {
82
+ const encoder = await getTextEncoder();
83
+ const encoded = encoder.encode(msg);
84
+
85
+ if (typeof window === "undefined") {
86
+ // Node.js - dynamic import
87
+ const crypto = await import("node:crypto");
88
+ return new Uint8Array(crypto.createHash("sha256").update(encoded).digest());
89
+ } else {
90
+ // Browser
91
+ const hash = await window.crypto.subtle.digest("SHA-256", encoded);
92
+ return new Uint8Array(hash);
93
+ }
94
+ }
95
+
96
+ // --- Proof of Work Helper ---
97
+ async function solveProofOfWork(message, difficulty = 2) {
98
+ if (difficulty < 1) {
99
+ difficulty = 1; // Minimum POW is 1
100
+ }
101
+
102
+ const target = '0'.repeat(difficulty);
103
+ let nonce = 0;
104
+
105
+ while (true) {
106
+ const combined = message + nonce;
107
+ const hash = await sha256(combined);
108
+ const hexHash = bytesToHex(hash);
109
+
110
+ if (hexHash.startsWith(target)) {
111
+ return nonce;
112
+ }
113
+ nonce++;
114
+
115
+ // Yield to event loop every 1000 attempts to avoid blocking
116
+ if (nonce % 1000 === 0) {
117
+ await new Promise(resolve => setTimeout(resolve, 0));
118
+ }
119
+ }
120
+ }
121
+
122
+ // --- Verify Proof of Work ---
123
+ async function verifyProofOfWork(message, powNonce, difficulty = 2) {
124
+ if (difficulty < 1) {
125
+ difficulty = 1; // Minimum POW is 1
126
+ }
127
+
128
+ if (powNonce === null || powNonce === undefined) {
129
+ return false;
130
+ }
131
+
132
+ const target = '0'.repeat(difficulty);
133
+ const combined = message + powNonce;
134
+ const hash = await sha256(combined);
135
+ const hexHash = bytesToHex(hash);
136
+
137
+ return hexHash.startsWith(target);
138
+ }
139
+
140
+ // --- Sign Message ---
141
+ export async function sign(message, privateKeyHex, pow = 2) {
142
+ // Enforce minimum POW of 1
143
+ if (pow < 1) {
144
+ pow = 1;
145
+ }
146
+
147
+ const hash = await sha256(message);
148
+ const privateKeyBytes = hexToBytes(privateKeyHex);
149
+ const sig = secp.sign(hash, privateKeyBytes);
150
+ const signature = bytesToHex(sig.toCompactRawBytes());
151
+
152
+ // Always generate POW and return object format
153
+ const powNonce = await solveProofOfWork(message, pow);
154
+ return { signature, pow: powNonce };
155
+ }
156
+
157
+ // --- Verify Signature ---
158
+ export async function verify(message, signatureData, publicKeyHex, pow = 2) {
159
+ try {
160
+ // Enforce minimum POW of 1
161
+ if (pow < 1) {
162
+ pow = 1;
163
+ }
164
+
165
+ // Always expect object format { signature, pow }
166
+ const signatureHex = signatureData.signature;
167
+ const powNonce = signatureData.pow;
168
+
169
+ if (!signatureHex || powNonce === undefined || powNonce === null) {
170
+ return false;
171
+ }
172
+
173
+ // Verify POW
174
+ const powValid = await verifyProofOfWork(message, powNonce, pow);
175
+ if (!powValid) {
176
+ return false;
177
+ }
178
+
179
+ // Verify signature
180
+ const hash = await sha256(message);
181
+ const signatureBytes = hexToBytes(signatureHex);
182
+ const publicKeyBytes = hexToBytes(publicKeyHex);
183
+ return secp.verify(signatureBytes, hash, publicKeyBytes);
184
+ } catch {
185
+ return false;
186
+ }
187
+ }
188
+
189
+ // --- Auth Header Helper ---
190
+ export async function createAuth(privateKeyHex, pow = 2) {
191
+ // Enforce minimum POW of 1
192
+ if (pow < 1) {
193
+ pow = 1;
194
+ }
195
+
196
+ const publicKeyHex = getPublicKey(privateKeyHex);
197
+
198
+ const timestamp = Date.now();
199
+ const nonceBytes = await randomBytes(16);
200
+ const nonce = bytesToHex(nonceBytes);
201
+ const message = `${timestamp}:${nonce}`;
202
+
203
+ const signatureData = await sign(message, privateKeyHex, pow);
204
+
205
+ const authPayload = {
206
+ publickey: publicKeyHex,
207
+ signature: signatureData.signature,
208
+ pow: signatureData.pow,
209
+ message,
210
+ timestamp,
211
+ nonce
212
+ };
213
+
214
+ return base64Encode(authPayload);
215
+ }
216
+
217
+ // --- Verify Auth Token ---
218
+ export async function verifyAuth(token, pow = 2) {
219
+ try {
220
+ // Enforce minimum POW of 1
221
+ if (pow < 1) {
222
+ pow = 1;
223
+ }
224
+
225
+ // Decode token
226
+ const authData = base64Decode(token);
227
+ const publicKeyHex = authData.publickey;
228
+ const { signature, message, pow: powNonce } = authData;
229
+
230
+ if (!publicKeyHex || !signature || !message || powNonce === undefined || powNonce === null) {
231
+ return null;
232
+ }
233
+
234
+ // Extract timestamp from message
235
+ const timestamp = Number(message.split(':')[0]);
236
+
237
+ // Check timestamp is within 1 minute
238
+ const maxAgeMs = 1 * 60 * 1000; // Hardcoded to 1 minute
239
+ const now = Date.now();
240
+ if (isNaN(timestamp) || Math.abs(now - timestamp) > maxAgeMs) {
241
+ return null;
242
+ }
243
+
244
+ // Verify signature and POW
245
+ const signatureData = { signature, pow: powNonce };
246
+ const isValid = await verify(message, signatureData, publicKeyHex, pow);
247
+ if (!isValid) {
248
+ return null;
249
+ }
250
+
251
+ return publicKeyHex;
252
+
253
+ } catch {
254
+ return null;
255
+ }
256
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "daku",
3
+ "version": "0.0.1",
4
+ "description": "Leave no trace. Just authenticate.",
5
+ "homepage": "https://github.com/besoeasy/daku#readme",
6
+ "keywords": [
7
+ "authentication",
8
+ "anonymous",
9
+ "privacy",
10
+ "passwordless",
11
+ "no-email",
12
+ "cryptographic",
13
+ "signature",
14
+ "secp256k1",
15
+ "proof-of-work",
16
+ "pow",
17
+ "spam-protection",
18
+ "login",
19
+ "auth",
20
+ "crypto",
21
+ "keypair",
22
+ "bitcoin",
23
+ "ethereum",
24
+ "web3",
25
+ "zero-knowledge",
26
+ "gdpr",
27
+ "no-pii",
28
+ "anonymous-auth",
29
+ "private-auth",
30
+ "ghost"
31
+ ],
32
+ "bugs": {
33
+ "url": "https://github.com/besoeasy/daku/issues"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/besoeasy/daku.git"
38
+ },
39
+ "dependencies": {
40
+ "@noble/hashes": "^1.8.0",
41
+ "@noble/secp256k1": "^2.3.0"
42
+ },
43
+ "license": "ISC",
44
+ "author": "besoeasy",
45
+ "type": "module",
46
+ "main": "index.js",
47
+ "scripts": {
48
+ "test": "node test.js"
49
+ }
50
+ }