daku 0.0.1 → 0.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 +193 -118
- package/index.js +56 -41
- package/package.json +1 -1
- package/username.js +8057 -0
package/README.md
CHANGED
|
@@ -1,8 +1,45 @@
|
|
|
1
|
-
#
|
|
1
|
+
# DAKU
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> **Anonymous authentication. Zero personal data.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**DAKU** (pronounced _DAA KU_) means "bandits" in Punjabi. Historically, bandits operated anonymously, often using masks to hide their identity. This library adopts that privacy-first ethos—anonymous cryptographic authentication without personal data.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Why DAKU?
|
|
10
|
+
|
|
11
|
+
**Stop storing passwords. Stop managing email verifications. Stop worrying about data breaches.**
|
|
12
|
+
|
|
13
|
+
DAKU is a simpler approach to user authentication that keeps both you and your users anonymous. No databases of usernames, no password hashes to secure, no personal information to leak.
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
// Traditional auth: Store emails, hash passwords, manage resets...
|
|
17
|
+
// DAKU: Just verify cryptographic signatures ✨
|
|
18
|
+
const publicKey = await verifyAuth(token);
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### The Problem with Traditional Auth
|
|
22
|
+
|
|
23
|
+
Every traditional authentication system carries risk:
|
|
24
|
+
|
|
25
|
+
- **Passwords**: Users reuse them, forget them, get them stolen
|
|
26
|
+
- **Email/Phone**: Requires collecting personal data (GDPR, privacy laws)
|
|
27
|
+
- **Databases**: Honeypots for hackers; one breach exposes everything
|
|
28
|
+
- **Identity**: Your users leave traces everywhere they sign up
|
|
29
|
+
|
|
30
|
+
### The DAKU Way
|
|
31
|
+
|
|
32
|
+
Users authenticate with cryptographic keypairs—like Bitcoin wallets, but for your app:
|
|
33
|
+
|
|
34
|
+
- **No signup forms**: Generate a keypair, start using your app
|
|
35
|
+
- **No passwords**: Users never create or remember passwords
|
|
36
|
+
- **No PII collection**: No emails, phones, or personal data
|
|
37
|
+
- **Built-in spam protection**: Proof-of-work prevents abuse
|
|
38
|
+
- **You stay clean**: Nothing sensitive to store, nothing to breach
|
|
39
|
+
|
|
40
|
+
> DAKU uses **secp256k1** signatures (same as Bitcoin/Ethereum) with **proof-of-work** spam protection. Auth tokens expire in 1 minute. Users control their private keys, you just verify signatures.
|
|
41
|
+
|
|
42
|
+
---
|
|
6
43
|
|
|
7
44
|
## Installation
|
|
8
45
|
|
|
@@ -12,188 +49,226 @@ npm install daku
|
|
|
12
49
|
|
|
13
50
|
## Quick Start
|
|
14
51
|
|
|
15
|
-
### 1. Generate a Keypair
|
|
16
|
-
|
|
17
52
|
```javascript
|
|
18
|
-
import { generateKeyPair } from
|
|
53
|
+
import { generateKeyPair, createAuth, verifyAuth } from "daku";
|
|
19
54
|
|
|
20
|
-
//
|
|
55
|
+
// 1. User generates keypair (client-side)
|
|
21
56
|
const { privateKey, publicKey } = generateKeyPair();
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
### 2. Client-Side: Create Authentication Request
|
|
25
57
|
|
|
26
|
-
|
|
27
|
-
import { createAuth } from 'daku';
|
|
28
|
-
|
|
29
|
-
// Create authentication token (includes timestamp, nonce, signature, and POW)
|
|
58
|
+
// 2. Create auth token (client-side)
|
|
30
59
|
const token = await createAuth(privateKey);
|
|
31
60
|
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
headers: {
|
|
36
|
-
'Content-Type': 'application/json',
|
|
37
|
-
'daku': token
|
|
38
|
-
}
|
|
39
|
-
});
|
|
61
|
+
// 3. Verify auth (server-side)
|
|
62
|
+
const publicKey = await verifyAuth(token);
|
|
63
|
+
// ✅ Authenticated! publicKey is the unique user ID
|
|
40
64
|
```
|
|
41
65
|
|
|
42
|
-
|
|
66
|
+
---
|
|
43
67
|
|
|
44
|
-
|
|
45
|
-
import { verifyAuth } from 'daku';
|
|
68
|
+
## Core Functions
|
|
46
69
|
|
|
47
|
-
|
|
70
|
+
### `generateKeyPair()`
|
|
48
71
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
// ❌ Invalid or expired authentication
|
|
54
|
-
res.status(401).json({ error: 'Unauthorized' });
|
|
55
|
-
}
|
|
72
|
+
**Create a new identity**
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
const { privateKey, publicKey } = generateKeyPair();
|
|
56
76
|
```
|
|
57
77
|
|
|
58
|
-
|
|
78
|
+
Generates a secp256k1 keypair. The **privateKey** stays with the user (never share it), the **publicKey** identifies them to your service.
|
|
59
79
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
import { verifyAuth } from 'daku';
|
|
80
|
+
- Returns: `{ privateKey: string, publicKey: string }`
|
|
81
|
+
- Use case: First-time users, account creation
|
|
63
82
|
|
|
64
|
-
|
|
83
|
+
---
|
|
65
84
|
|
|
66
|
-
|
|
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
|
-
}
|
|
85
|
+
### `getPublicKey(privateKey)`
|
|
74
86
|
|
|
75
|
-
|
|
87
|
+
**Derive the public identity**
|
|
76
88
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
```javascript
|
|
90
|
+
const publicKey = getPublicKey(privateKey);
|
|
91
|
+
```
|
|
80
92
|
|
|
81
|
-
|
|
82
|
-
req.userId = publicKey;
|
|
83
|
-
next();
|
|
84
|
-
};
|
|
85
|
-
};
|
|
93
|
+
Extract the public key from a private key. Useful when users return with their saved privateKey.
|
|
86
94
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
res.json({
|
|
90
|
-
message: 'Access granted',
|
|
91
|
-
userId: req.userId
|
|
92
|
-
});
|
|
93
|
-
});
|
|
95
|
+
- Returns: `publicKey` string
|
|
96
|
+
- Deterministic: Same privateKey always produces same publicKey
|
|
94
97
|
|
|
95
|
-
|
|
96
|
-
app.post('/api/high-security', daku(4), (req, res) => {
|
|
97
|
-
res.json({ message: 'High security endpoint' });
|
|
98
|
-
});
|
|
98
|
+
---
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
```
|
|
100
|
+
### `getUsername(publicKey)`
|
|
102
101
|
|
|
103
|
-
|
|
102
|
+
**Make public keys human-readable**
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
const username = await getUsername(publicKey);
|
|
106
|
+
// → "happy-ocean-flows-1234"
|
|
107
|
+
```
|
|
104
108
|
|
|
105
|
-
|
|
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
|
|
109
|
+
Public keys are long hex strings. `getUsername()` converts them into memorable usernames for your UI.
|
|
111
110
|
|
|
112
|
-
|
|
111
|
+
- Returns: Human-readable username string
|
|
112
|
+
- Deterministic: Same publicKey always → same username
|
|
113
|
+
- Format: `adjective-noun-verb-number`
|
|
113
114
|
|
|
114
|
-
|
|
115
|
+
> [!IMPORTANT]
|
|
116
|
+
> **Never ask users to create usernames.** DAKU keeps users anonymous. Display the generated username in your UI, but always identify users by their **publicKey** in your database.
|
|
115
117
|
|
|
116
|
-
|
|
118
|
+
---
|
|
117
119
|
|
|
118
|
-
|
|
120
|
+
### `createAuth(privateKey, pow?)`
|
|
119
121
|
|
|
120
|
-
**
|
|
122
|
+
**Generate authentication token**
|
|
121
123
|
|
|
122
|
-
**Example:**
|
|
123
124
|
```javascript
|
|
124
|
-
const token = await createAuth(privateKey);
|
|
125
|
-
// "eyJwdWJsaWNrZXkiOiIwMmE..."
|
|
125
|
+
const token = await createAuth(privateKey, 2); // pow = difficulty
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
Creates a signed auth token with timestamp, nonce, signature, and proof-of-work. Send this to your server for verification.
|
|
129
|
+
|
|
130
|
+
- Returns: Base64-encoded auth token
|
|
131
|
+
- Default POW: 2 (adjust for spam protection)
|
|
132
|
+
- Includes: timestamp, nonce, signature, proof-of-work
|
|
129
133
|
|
|
130
|
-
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
### `verifyAuth(token, pow?)`
|
|
131
137
|
|
|
132
|
-
**
|
|
138
|
+
**Verify authentication token**
|
|
133
139
|
|
|
134
|
-
**Example:**
|
|
135
140
|
```javascript
|
|
136
|
-
const publicKey = await verifyAuth(token);
|
|
141
|
+
const publicKey = await verifyAuth(token, 2);
|
|
142
|
+
if (publicKey) {
|
|
143
|
+
// ✅ Valid! User authenticated
|
|
144
|
+
console.log(`User ${publicKey} logged in`);
|
|
145
|
+
} else {
|
|
146
|
+
// ❌ Invalid or expired
|
|
147
|
+
}
|
|
137
148
|
```
|
|
138
149
|
|
|
150
|
+
Verifies the signature, proof-of-work, and timestamp (must be < 1 minute old). Returns the user's publicKey on success.
|
|
151
|
+
|
|
152
|
+
- Returns: `publicKey` string or `null`
|
|
153
|
+
- Checks: Signature validity, POW correctness, timestamp freshness
|
|
154
|
+
- Token lifetime: 1 minute
|
|
155
|
+
|
|
139
156
|
---
|
|
140
157
|
|
|
141
|
-
###
|
|
158
|
+
### `sign(message, privateKey, pow?)`
|
|
159
|
+
|
|
160
|
+
**Sign any message**
|
|
142
161
|
|
|
143
|
-
|
|
162
|
+
```javascript
|
|
163
|
+
const { signature, pow } = await sign("hello world", privateKey, 2);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Create a cryptographic signature for any message with proof-of-work. Lower-level function used by `createAuth()`.
|
|
144
167
|
|
|
145
|
-
|
|
168
|
+
- Returns: `{ signature: string, pow: number }`
|
|
169
|
+
- Use case: Custom message signing beyond authentication
|
|
146
170
|
|
|
147
|
-
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
### `verify(message, signatureData, publicKey, pow?)`
|
|
148
174
|
|
|
149
|
-
**
|
|
175
|
+
**Verify any signature**
|
|
150
176
|
|
|
151
|
-
**Example:**
|
|
152
177
|
```javascript
|
|
153
|
-
const
|
|
154
|
-
// { signature: 'a1b2c3...', pow: 42 }
|
|
178
|
+
const isValid = await verify("hello world", { signature, pow }, publicKey, 2);
|
|
155
179
|
```
|
|
156
180
|
|
|
157
|
-
|
|
181
|
+
Verify a message signature and proof-of-work. Lower-level function used by `verifyAuth()`.
|
|
158
182
|
|
|
159
|
-
|
|
183
|
+
- Returns: `boolean`
|
|
184
|
+
- Checks: Signature + POW validity
|
|
160
185
|
|
|
161
|
-
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Express.js Middleware
|
|
162
189
|
|
|
163
|
-
**Example:**
|
|
164
190
|
```javascript
|
|
165
|
-
|
|
191
|
+
import express from "express";
|
|
192
|
+
import { verifyAuth, getUsername } from "daku";
|
|
193
|
+
|
|
194
|
+
const app = express();
|
|
195
|
+
|
|
196
|
+
// Middleware: Verify DAKU auth
|
|
197
|
+
const daku =
|
|
198
|
+
(powDifficulty = 2) =>
|
|
199
|
+
async (req, res, next) => {
|
|
200
|
+
const token = req.headers["daku"];
|
|
201
|
+
const publicKey = await verifyAuth(token, powDifficulty);
|
|
202
|
+
|
|
203
|
+
if (!publicKey) {
|
|
204
|
+
return res.status(401).json({ error: "Unauthorized" });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
req.userId = publicKey; // Attach user ID
|
|
208
|
+
next();
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Protected route
|
|
212
|
+
app.post("/api/profile", daku(), async (req, res) => {
|
|
213
|
+
const username = await getUsername(req.userId);
|
|
214
|
+
res.json({
|
|
215
|
+
message: `Welcome, ${username}!`,
|
|
216
|
+
userId: req.userId,
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
app.listen(3000);
|
|
166
221
|
```
|
|
167
222
|
|
|
168
223
|
---
|
|
169
224
|
|
|
170
|
-
|
|
225
|
+
## Key Benefits
|
|
171
226
|
|
|
172
|
-
|
|
227
|
+
| Traditional Auth | DAKU |
|
|
228
|
+
| ----------------------------------- | -------------------------------------- |
|
|
229
|
+
| Manage passwords, hashes, resets | No passwords—just verify signatures |
|
|
230
|
+
| Store emails/phones (PII) | Zero personal data collected |
|
|
231
|
+
| User databases = security liability | Only store public keys (not sensitive) |
|
|
232
|
+
| Slow authentication flows | Instant cryptographic verification |
|
|
233
|
+
| GDPR compliance overhead | No PII = simpler compliance |
|
|
234
|
+
| Spam = manual moderation/CAPTCHAs | Built-in proof-of-work protection |
|
|
173
235
|
|
|
174
|
-
|
|
236
|
+
---
|
|
175
237
|
|
|
176
|
-
|
|
238
|
+
## Features
|
|
177
239
|
|
|
178
|
-
|
|
240
|
+
- **🕵️ Anonymous**: No email, no phone, no personal data
|
|
241
|
+
- **🔐 Secure**: secp256k1 signatures (Bitcoin/Ethereum-grade)
|
|
242
|
+
- **🛡️ Spam-proof**: Configurable proof-of-work difficulty
|
|
243
|
+
- **⚡ Lightweight**: Minimal dependencies, works everywhere
|
|
244
|
+
- **🌐 Universal**: Node.js + Browser compatible
|
|
245
|
+
- **⏱️ Short-lived tokens**: 1-minute expiration (anti-replay)
|
|
246
|
+
- **🌍 Cross-project identity**: Reuse one keypair across apps
|
|
179
247
|
|
|
180
|
-
|
|
248
|
+
---
|
|
181
249
|
|
|
182
|
-
|
|
250
|
+
## Global Identity
|
|
183
251
|
|
|
184
|
-
|
|
252
|
+
Users can reuse **one private key** across multiple services. Same privateKey → same publicKey → same identity everywhere.
|
|
185
253
|
|
|
186
|
-
|
|
254
|
+
```javascript
|
|
255
|
+
// User's keypair works on yourapp.com AND anotherapp.com
|
|
256
|
+
const publicKey = getPublicKey(samePrivateKey);
|
|
257
|
+
// → Same publicKey = consistent cross-platform identity
|
|
258
|
+
```
|
|
187
259
|
|
|
188
|
-
|
|
260
|
+
> [!WARNING]
|
|
261
|
+
> Reusing keypairs links user identity across services. This enables seamless cross-app experiences but reduces anonymity between services. For per-app isolation, derive or generate separate keys.
|
|
189
262
|
|
|
190
|
-
|
|
263
|
+
---
|
|
191
264
|
|
|
192
|
-
|
|
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
|
|
265
|
+
## Security Notes
|
|
196
266
|
|
|
197
|
-
|
|
267
|
+
- **Private keys**: Users must store them securely (localStorage, hardware wallets). Lost keys = lost access.
|
|
268
|
+
- **Token lifetime**: Hardcoded to 1 minute; prevent replay attacks.
|
|
269
|
+
- **POW difficulty**: Default = 2 leading zeros. Increase for high-traffic endpoints (trade-off: slower auth).
|
|
270
|
+
- **No server secrets**: DAKU has no shared secrets—everything is public-key cryptography.
|
|
271
|
+
|
|
272
|
+
---
|
|
198
273
|
|
|
199
|
-
|
|
274
|
+
**DAKU: The authentication system that doesn't know anything about your users. By design.**
|
package/index.js
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
import * as secp from "@noble/secp256k1";
|
|
3
3
|
import { hmac } from "@noble/hashes/hmac";
|
|
4
4
|
import { sha256 as nobleSha256 } from "@noble/hashes/sha2";
|
|
5
|
+
import { generateAccountIdentifier } from "./username.js";
|
|
5
6
|
|
|
6
7
|
// Set up HMAC for secp256k1
|
|
7
|
-
secp.etc.hmacSha256Sync = (key, ...msgs) =>
|
|
8
|
+
secp.etc.hmacSha256Sync = (key, ...msgs) =>
|
|
9
|
+
hmac(nobleSha256, key, secp.etc.concatBytes(...msgs));
|
|
8
10
|
|
|
9
11
|
// Browser compatibility helper for TextEncoder
|
|
10
12
|
async function getTextEncoder() {
|
|
@@ -19,12 +21,14 @@ async function getTextEncoder() {
|
|
|
19
21
|
|
|
20
22
|
// Helper function to convert bytes to hex
|
|
21
23
|
function bytesToHex(bytes) {
|
|
22
|
-
return Array.from(bytes, byte => byte.toString(16).padStart(2,
|
|
24
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join(
|
|
25
|
+
""
|
|
26
|
+
);
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
// Helper function to convert hex to bytes
|
|
26
30
|
function hexToBytes(hex) {
|
|
27
|
-
return new Uint8Array(hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
|
|
31
|
+
return new Uint8Array(hex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
// Helper function to generate random bytes
|
|
@@ -47,7 +51,7 @@ function base64Encode(obj) {
|
|
|
47
51
|
if (typeof window !== "undefined") {
|
|
48
52
|
return btoa(json);
|
|
49
53
|
} else {
|
|
50
|
-
return Buffer.from(json).toString(
|
|
54
|
+
return Buffer.from(json).toString("base64");
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
57
|
|
|
@@ -56,14 +60,15 @@ function base64Decode(str) {
|
|
|
56
60
|
if (typeof window !== "undefined") {
|
|
57
61
|
return JSON.parse(atob(str));
|
|
58
62
|
} else {
|
|
59
|
-
return JSON.parse(Buffer.from(str,
|
|
63
|
+
return JSON.parse(Buffer.from(str, "base64").toString());
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
// --- Key Generation ---
|
|
64
68
|
export function generateKeyPair() {
|
|
65
69
|
const privateKey = secp.utils.randomPrivateKey();
|
|
66
|
-
const publicKey = secp.getPublicKey(privateKey, true);
|
|
70
|
+
const publicKey = secp.getPublicKey(privateKey, true);
|
|
71
|
+
|
|
67
72
|
return {
|
|
68
73
|
privateKey: bytesToHex(privateKey),
|
|
69
74
|
publicKey: bytesToHex(publicKey),
|
|
@@ -77,11 +82,16 @@ export function getPublicKey(privateKeyHex) {
|
|
|
77
82
|
return bytesToHex(publicKeyBytes);
|
|
78
83
|
}
|
|
79
84
|
|
|
85
|
+
// --- Account Username from Public Key ---
|
|
86
|
+
export async function getUsername(publicKey) {
|
|
87
|
+
return await generateAccountIdentifier(publicKey);
|
|
88
|
+
}
|
|
89
|
+
|
|
80
90
|
// --- Hashing (SHA-256) ---
|
|
81
91
|
export async function sha256(msg) {
|
|
82
92
|
const encoder = await getTextEncoder();
|
|
83
93
|
const encoded = encoder.encode(msg);
|
|
84
|
-
|
|
94
|
+
|
|
85
95
|
if (typeof window === "undefined") {
|
|
86
96
|
// Node.js - dynamic import
|
|
87
97
|
const crypto = await import("node:crypto");
|
|
@@ -94,88 +104,88 @@ export async function sha256(msg) {
|
|
|
94
104
|
}
|
|
95
105
|
|
|
96
106
|
// --- Proof of Work Helper ---
|
|
97
|
-
async function solveProofOfWork(message, difficulty =
|
|
107
|
+
async function solveProofOfWork(message, difficulty = 1) {
|
|
98
108
|
if (difficulty < 1) {
|
|
99
109
|
difficulty = 1; // Minimum POW is 1
|
|
100
110
|
}
|
|
101
|
-
|
|
102
|
-
const target =
|
|
111
|
+
|
|
112
|
+
const target = "0".repeat(difficulty);
|
|
103
113
|
let nonce = 0;
|
|
104
|
-
|
|
114
|
+
|
|
105
115
|
while (true) {
|
|
106
116
|
const combined = message + nonce;
|
|
107
117
|
const hash = await sha256(combined);
|
|
108
118
|
const hexHash = bytesToHex(hash);
|
|
109
|
-
|
|
119
|
+
|
|
110
120
|
if (hexHash.startsWith(target)) {
|
|
111
121
|
return nonce;
|
|
112
122
|
}
|
|
113
123
|
nonce++;
|
|
114
|
-
|
|
124
|
+
|
|
115
125
|
// Yield to event loop every 1000 attempts to avoid blocking
|
|
116
126
|
if (nonce % 1000 === 0) {
|
|
117
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
127
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
118
128
|
}
|
|
119
129
|
}
|
|
120
130
|
}
|
|
121
131
|
|
|
122
132
|
// --- Verify Proof of Work ---
|
|
123
|
-
async function verifyProofOfWork(message, powNonce, difficulty =
|
|
133
|
+
async function verifyProofOfWork(message, powNonce, difficulty = 1) {
|
|
124
134
|
if (difficulty < 1) {
|
|
125
135
|
difficulty = 1; // Minimum POW is 1
|
|
126
136
|
}
|
|
127
|
-
|
|
137
|
+
|
|
128
138
|
if (powNonce === null || powNonce === undefined) {
|
|
129
139
|
return false;
|
|
130
140
|
}
|
|
131
|
-
|
|
132
|
-
const target =
|
|
141
|
+
|
|
142
|
+
const target = "0".repeat(difficulty);
|
|
133
143
|
const combined = message + powNonce;
|
|
134
144
|
const hash = await sha256(combined);
|
|
135
145
|
const hexHash = bytesToHex(hash);
|
|
136
|
-
|
|
146
|
+
|
|
137
147
|
return hexHash.startsWith(target);
|
|
138
148
|
}
|
|
139
149
|
|
|
140
150
|
// --- Sign Message ---
|
|
141
|
-
export async function sign(message, privateKeyHex, pow =
|
|
151
|
+
export async function sign(message, privateKeyHex, pow = 1) {
|
|
142
152
|
// Enforce minimum POW of 1
|
|
143
153
|
if (pow < 1) {
|
|
144
154
|
pow = 1;
|
|
145
155
|
}
|
|
146
|
-
|
|
156
|
+
|
|
147
157
|
const hash = await sha256(message);
|
|
148
158
|
const privateKeyBytes = hexToBytes(privateKeyHex);
|
|
149
159
|
const sig = secp.sign(hash, privateKeyBytes);
|
|
150
160
|
const signature = bytesToHex(sig.toCompactRawBytes());
|
|
151
|
-
|
|
161
|
+
|
|
152
162
|
// Always generate POW and return object format
|
|
153
163
|
const powNonce = await solveProofOfWork(message, pow);
|
|
154
164
|
return { signature, pow: powNonce };
|
|
155
165
|
}
|
|
156
166
|
|
|
157
167
|
// --- Verify Signature ---
|
|
158
|
-
export async function verify(message, signatureData, publicKeyHex, pow =
|
|
168
|
+
export async function verify(message, signatureData, publicKeyHex, pow = 1) {
|
|
159
169
|
try {
|
|
160
170
|
// Enforce minimum POW of 1
|
|
161
171
|
if (pow < 1) {
|
|
162
172
|
pow = 1;
|
|
163
173
|
}
|
|
164
|
-
|
|
174
|
+
|
|
165
175
|
// Always expect object format { signature, pow }
|
|
166
176
|
const signatureHex = signatureData.signature;
|
|
167
177
|
const powNonce = signatureData.pow;
|
|
168
|
-
|
|
178
|
+
|
|
169
179
|
if (!signatureHex || powNonce === undefined || powNonce === null) {
|
|
170
180
|
return false;
|
|
171
181
|
}
|
|
172
|
-
|
|
182
|
+
|
|
173
183
|
// Verify POW
|
|
174
184
|
const powValid = await verifyProofOfWork(message, powNonce, pow);
|
|
175
185
|
if (!powValid) {
|
|
176
186
|
return false;
|
|
177
187
|
}
|
|
178
|
-
|
|
188
|
+
|
|
179
189
|
// Verify signature
|
|
180
190
|
const hash = await sha256(message);
|
|
181
191
|
const signatureBytes = hexToBytes(signatureHex);
|
|
@@ -192,25 +202,25 @@ export async function createAuth(privateKeyHex, pow = 2) {
|
|
|
192
202
|
if (pow < 1) {
|
|
193
203
|
pow = 1;
|
|
194
204
|
}
|
|
195
|
-
|
|
205
|
+
|
|
196
206
|
const publicKeyHex = getPublicKey(privateKeyHex);
|
|
197
207
|
|
|
198
208
|
const timestamp = Date.now();
|
|
199
209
|
const nonceBytes = await randomBytes(16);
|
|
200
210
|
const nonce = bytesToHex(nonceBytes);
|
|
201
211
|
const message = `${timestamp}:${nonce}`;
|
|
202
|
-
|
|
212
|
+
|
|
203
213
|
const signatureData = await sign(message, privateKeyHex, pow);
|
|
204
|
-
|
|
214
|
+
|
|
205
215
|
const authPayload = {
|
|
206
216
|
publickey: publicKeyHex,
|
|
207
217
|
signature: signatureData.signature,
|
|
208
218
|
pow: signatureData.pow,
|
|
209
219
|
message,
|
|
210
220
|
timestamp,
|
|
211
|
-
nonce
|
|
221
|
+
nonce,
|
|
212
222
|
};
|
|
213
|
-
|
|
223
|
+
|
|
214
224
|
return base64Encode(authPayload);
|
|
215
225
|
}
|
|
216
226
|
|
|
@@ -221,35 +231,40 @@ export async function verifyAuth(token, pow = 2) {
|
|
|
221
231
|
if (pow < 1) {
|
|
222
232
|
pow = 1;
|
|
223
233
|
}
|
|
224
|
-
|
|
234
|
+
|
|
225
235
|
// Decode token
|
|
226
236
|
const authData = base64Decode(token);
|
|
227
237
|
const publicKeyHex = authData.publickey;
|
|
228
238
|
const { signature, message, pow: powNonce } = authData;
|
|
229
|
-
|
|
230
|
-
if (
|
|
239
|
+
|
|
240
|
+
if (
|
|
241
|
+
!publicKeyHex ||
|
|
242
|
+
!signature ||
|
|
243
|
+
!message ||
|
|
244
|
+
powNonce === undefined ||
|
|
245
|
+
powNonce === null
|
|
246
|
+
) {
|
|
231
247
|
return null;
|
|
232
248
|
}
|
|
233
|
-
|
|
249
|
+
|
|
234
250
|
// Extract timestamp from message
|
|
235
|
-
const timestamp = Number(message.split(
|
|
236
|
-
|
|
251
|
+
const timestamp = Number(message.split(":")[0]);
|
|
252
|
+
|
|
237
253
|
// Check timestamp is within 1 minute
|
|
238
254
|
const maxAgeMs = 1 * 60 * 1000; // Hardcoded to 1 minute
|
|
239
255
|
const now = Date.now();
|
|
240
256
|
if (isNaN(timestamp) || Math.abs(now - timestamp) > maxAgeMs) {
|
|
241
257
|
return null;
|
|
242
258
|
}
|
|
243
|
-
|
|
259
|
+
|
|
244
260
|
// Verify signature and POW
|
|
245
261
|
const signatureData = { signature, pow: powNonce };
|
|
246
262
|
const isValid = await verify(message, signatureData, publicKeyHex, pow);
|
|
247
263
|
if (!isValid) {
|
|
248
264
|
return null;
|
|
249
265
|
}
|
|
250
|
-
|
|
266
|
+
|
|
251
267
|
return publicKeyHex;
|
|
252
|
-
|
|
253
268
|
} catch {
|
|
254
269
|
return null;
|
|
255
270
|
}
|