-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathllms-full.txt
More file actions
587 lines (436 loc) · 17.8 KB
/
llms-full.txt
File metadata and controls
587 lines (436 loc) · 17.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
# @forgesworn/range-proof — Full API Reference
> Pedersen commitment range proofs on secp256k1 — prove a secret value lies within `[min, max]` without revealing it. Built on CDS OR-composition (Cramer-Damgård-Schoenmakers) bit proofs with Fiat-Shamir domain separation. Pure TypeScript, ESM-only, browser-compatible.
GitHub: https://github.com/forgesworn/range-proof
npm: https://www.npmjs.com/package/@forgesworn/range-proof
Version: 2.0.0
## Install
```bash
npm install @forgesworn/range-proof
```
Runtime dependencies: `@noble/curves`, `@noble/hashes`. ESM-only. Requires Node.js 18+. Browser-compatible.
---
## Quick Start
```typescript
import { createRangeProof, verifyRangeProof } from '@forgesworn/range-proof';
// Prove age is in [18, 150] without revealing the exact age
const proof = createRangeProof(25, 18, 150);
const valid = verifyRangeProof(proof, 18, 150); // true
// Bind proof to a specific subject (prevents transplant attacks)
const bound = createRangeProof(25, 18, 150, 'subject-pubkey-hex');
const validBound = verifyRangeProof(bound, 18, 150, 'subject-pubkey-hex'); // true
// Age convenience helpers
import { createAgeRangeProof, verifyAgeRangeProof } from '@forgesworn/range-proof';
const ageProof = createAgeRangeProof(25, '18+');
const ageValid = verifyAgeRangeProof(ageProof, '18+'); // true
const bandProof = createAgeRangeProof(10, '8-12', 'subject-pubkey');
const bandValid = verifyAgeRangeProof(bandProof, '8-12', 'subject-pubkey'); // true
```
---
## Types
### `PedersenCommitment`
```typescript
interface PedersenCommitment {
commitment: string // The commitment point C = v*G + r*H (compressed hex, 66 chars)
blinding: string // The blinding factor r (64-char hex) — kept secret by the committer
value: number // The committed value — kept secret by the committer
}
```
Never transmit `blinding` or `value` to a verifier. Share only `commitment`.
---
### `RangeProof`
```typescript
interface RangeProof {
commitment: string // Overall commitment C = v*G + r*H (compressed hex)
min: number // Minimum of the range
max: number // Maximum of the range
bits: number // Number of bits used (ceil(log2(max - min + 1)))
lowerProof: BitProof[] // CDS bit proofs for (value - min)
upperProof: BitProof[] // CDS bit proofs for (max - value)
lowerCommitment: string // Commitment to (value - min)
upperCommitment: string // Commitment to (max - value)
sumBindingE: string // Sum-binding Schnorr proof challenge (hex scalar)
sumBindingS: string // Sum-binding Schnorr proof response (hex scalar)
commitBindingE: string // Commitment-binding Schnorr proof challenge (hex scalar)
commitBindingS: string // Commitment-binding Schnorr proof response (hex scalar)
context?: string // Optional binding context string
}
```
`RangeProof` is JSON-serialisable. All hex strings are lowercase. Pass the whole object to `serializeRangeProof()` for transport.
---
## Core Functions
### `createRangeProof(value, min, max, bindingContext?): RangeProof`
```typescript
function createRangeProof(
value: number,
min: number,
max: number,
bindingContext?: string
): RangeProof
```
Proves that `value` is in `[min, max]` without revealing `value`.
- `value`, `min`, `max` must be safe integers
- `min` must be non-negative
- `max` must be `>= min`
- `value` must be in `[min, max]`
- Maximum range: `2^32` (throws `CryptoError` if exceeded)
- `bindingContext` — optional string included in all Fiat-Shamir challenges; a proof created with one context will not verify under a different context
**Throws** `ValidationError` for invalid inputs, `CryptoError` if the range exceeds `2^32`.
```typescript
const proof = createRangeProof(42, 0, 100);
// proof.commitment — share this with the verifier
// proof itself — share this with the verifier
// value (42) — never share
```
---
### `verifyRangeProof(proof, expectedMin, expectedMax, expectedBindingContext?): boolean`
```typescript
function verifyRangeProof(
proof: RangeProof,
expectedMin: number,
expectedMax: number,
expectedBindingContext?: string
): boolean
```
Verifies a range proof. Returns `true` only if all of the following hold:
1. All CDS OR-composition bit proofs for `(value - min)` verify
2. All CDS OR-composition bit proofs for `(max - value)` verify
3. Bit commitments sum to the aggregate lower/upper commitments
4. Sum-binding Schnorr proof verifies (proves `lowerVal + upperVal = max - min`)
5. Commitment-binding Schnorr proof verifies (proves `commitment` is bound to `lowerCommitment`)
6. `proof.min === expectedMin`, `proof.max === expectedMax`
7. Binding context matches `expectedBindingContext`
Returns `false` (not throws) on tampered proofs, invalid hex, or mismatched parameters. The commitment embedded in the proof is verified algebraically (the commitment-binding Schnorr proof proves `C` is bound to the lower-range commitment) -- no separate manual commitment check is needed.
```typescript
const ok = verifyRangeProof(proof, 0, 100); // true
const fail = verifyRangeProof(proof, 0, 50); // false — range mismatch
```
---
### `createAgeRangeProof(age, ageRange, subjectPubkey?): RangeProof`
```typescript
function createAgeRangeProof(
age: number,
ageRange: string,
subjectPubkey?: string
): RangeProof
```
Convenience wrapper for age proofs. `ageRange` formats:
- `'18+'` — proves `age >= 18` (uses `[18, 150]` as the range)
- `'8-12'` — proves `age in [8, 12]`
`subjectPubkey` is used as the binding context.
**Throws** `ValidationError` for invalid age range format (must match `"min-max"` or `"min+"`).
```typescript
const proof = createAgeRangeProof(25, '18+', 'npub1...');
```
---
### `verifyAgeRangeProof(proof, expectedAgeRange, expectedSubjectPubkey?): boolean`
```typescript
function verifyAgeRangeProof(
proof: RangeProof,
expectedAgeRange: string,
expectedSubjectPubkey?: string
): boolean
```
Verify an age range proof. Returns `false` (not throws) on bad input or invalid proof.
```typescript
const ok = verifyAgeRangeProof(proof, '18+', 'npub1...');
```
---
## Pedersen Commitment Functions
### `commit(value, blinding?): PedersenCommitment`
```typescript
function commit(value: number, blinding?: bigint): PedersenCommitment
```
Create a Pedersen commitment `C = v*G + r*H`.
- `value` must be a non-negative safe integer
- `blinding` — optional blinding factor as `bigint`; random if not provided
- Returns `{ commitment, blinding, value }` — keep `blinding` and `value` secret
```typescript
const c = commit(42);
// c.commitment — share with verifier
// c.blinding, c.value — keep secret
```
**Throws** `ValidationError` for negative or non-integer values, or a zero blinding factor.
---
### `verifyCommitment(commitment, value, blinding): boolean`
```typescript
function verifyCommitment(
commitment: string,
value: number,
blinding: string
): boolean
```
Open and verify a Pedersen commitment. Returns `true` if `commitment = value*G + blinding*H`.
```typescript
const ok = verifyCommitment(c.commitment, 42, c.blinding); // true
```
Returns `false` (not throws) on invalid inputs.
---
## Serialisation
### `serializeRangeProof(proof: RangeProof): string`
```typescript
function serializeRangeProof(proof: RangeProof): string
```
Serialise a range proof to a JSON string. The output is suitable for storage or transport.
---
### `deserializeRangeProof(json: string): RangeProof`
```typescript
function deserializeRangeProof(json: string): RangeProof
```
Deserialise a range proof from a JSON string. Performs full input validation:
- Validates hex format for all commitment and scalar fields
- Validates compressed point format (`02`/`03` prefix + 64 hex chars)
- Validates `bits` is consistent with `max - min`
- Validates proof array lengths match `bits`
- Validates `context` length if present
- Reconstructs from an explicit whitelist of fields (prevents prototype pollution)
**Throws** `ValidationError` for any malformed input.
```typescript
const json = serializeRangeProof(proof);
const recovered = deserializeRangeProof(json);
```
---
## Error Classes
### `RangeProofError`
Base class for all library errors.
```typescript
class RangeProofError extends Error {
name: 'RangeProofError'
}
```
### `ValidationError extends RangeProofError`
Thrown for malformed inputs, out-of-range values, or bad JSON.
```typescript
class ValidationError extends RangeProofError {
name: 'ValidationError'
}
```
**Triggers:** negative values, non-integer inputs, `max < min`, `value` outside `[min, max]`, invalid hex, bad age range format, context exceeding 1024 bytes.
### `CryptoError extends RangeProofError`
Thrown for cryptographic failures.
```typescript
class CryptoError extends RangeProofError {
name: 'CryptoError'
}
```
**Triggers:** range exceeds `2^32`.
---
## Error Handling Patterns
### Catching specific error types
```typescript
import {
createRangeProof, verifyRangeProof,
ValidationError, CryptoError, RangeProofError
} from '@forgesworn/range-proof';
try {
const proof = createRangeProof(value, min, max, bindingContext);
const valid = verifyRangeProof(proof, min, max, bindingContext);
} catch (err) {
if (err instanceof ValidationError) {
// Malformed inputs: negative values, non-integers, value outside [min, max],
// bad age range format, context > 1024 bytes
console.error('Invalid input:', err.message);
} else if (err instanceof CryptoError) {
// Cryptographic constraint violated: range exceeds 2^32
console.error('Crypto error:', err.message);
} else if (err instanceof RangeProofError) {
// Catch-all for any library error
console.error('Range proof error:', err.message);
}
}
```
### Handling serialisation errors
```typescript
import {
serializeRangeProof, deserializeRangeProof, ValidationError
} from '@forgesworn/range-proof';
// Serialisation does not throw (proof is already valid)
const json = serializeRangeProof(proof);
// Deserialisation validates all fields — catch ValidationError
try {
const recovered = deserializeRangeProof(json);
} catch (err) {
if (err instanceof ValidationError) {
// Invalid hex, bad point format, wrong array length, oversized context, etc.
console.error('Malformed proof JSON:', err.message);
}
}
```
### Extending error classes
The error hierarchy is designed for subclassing. Custom validators can throw library-compatible errors:
```typescript
import { ValidationError } from '@forgesworn/range-proof';
class PolicyError extends ValidationError {
constructor(policy: string) {
super(`Policy violation: ${policy}`);
this.name = 'PolicyError';
}
}
// Callers catching ValidationError will also catch PolicyError
try {
if (age < 0) throw new PolicyError('age must be non-negative');
} catch (err) {
if (err instanceof ValidationError) {
// Catches both ValidationError and PolicyError
}
}
```
---
## Cryptographic Design
### Generator H
The second generator `H` is a nothing-up-my-sleeve point: derived by hashing the string `'secp256k1-pedersen-H-v1'` to a curve point via try-and-increment. No one knows `log_G(H)`, which is essential for the binding property of Pedersen commitments.
**Algorithm (runnable with @noble/curves v2):**
```typescript
import { secp256k1 } from '@noble/curves/secp256k1';
import { sha256 } from '@noble/hashes/sha2';
import { bytesToHex, utf8ToBytes } from '@noble/hashes/utils';
function deriveGeneratorH(): typeof secp256k1.Point.BASE {
const seed = utf8ToBytes('secp256k1-pedersen-H-v1');
for (let i = 0; i < 256; i++) {
const buf = new Uint8Array(seed.length + 1);
buf.set(seed);
buf[seed.length] = i;
const h = sha256(buf);
const compressedHex = '02' + bytesToHex(h); // Try even-y prefix
try {
const point = secp256k1.Point.fromHex(compressedHex);
point.assertValidity(); // Confirms point is on the curve and in the prime-order subgroup
return point;
} catch {
continue; // SHA-256 output was not a valid x-coordinate; try next counter
}
}
throw new Error('Failed to derive H');
}
const H = deriveGeneratorH();
// H is deterministic — every implementation must produce the same point
```
The try-and-increment approach hashes `seed || counter` for `counter = 0, 1, 2, ...`, interprets the hash as a compressed x-coordinate with `02` prefix, and accepts the first valid curve point. This is deterministic and reproducible across implementations.
### CDS OR-Composition
Range proofs decompose the value into bits. Each bit commitment is proved to be 0 or 1 using Cramer-Damgård-Schoenmakers OR-composition. The prover holds the real branch; the other branch is simulated via the Schnorr convention `s = k - e*r`.
### Sum-Binding Proof
Proves `lowerC + upperC - range*G = r_total * H`, establishing that `(value - min) + (max - value) = max - min` without revealing either sub-value.
### Commitment-Binding Proof
Proves `C - lowerC - min*G = (r - r_lower)*H`, algebraically binding the overall commitment `C` to the verified lower-range commitment.
### Fiat-Shamir Domain Separators
Three non-interactive challenge domains prevent cross-proof attacks:
- `pedersen-bit-proof-v1` — bit proof challenges
- `pedersen-sum-binding-v1` — sum-binding challenges
- `pedersen-commitment-binding-v1` — commitment-binding challenges
Do not modify these strings — changing them breaks all existing proofs.
### Binding Context
An optional UTF-8 string (max 1024 bytes) is included in all Fiat-Shamir hash inputs. Use the subject's public key as context to prevent a valid proof being transplanted to a different credential.
---
## Use Cases
- **Age verification** — prove a user is 18+ without revealing their birth year
- **Income or credit checks** — prove a score exceeds a threshold without disclosing the number
- **Salary negotiation** — prove a salary falls within a band without naming the figure
- **Privacy-preserving credentials** — combine with `nostr-attestations` (kind 31000) for zero-knowledge attestations
- **Compliance checks** — prove a value satisfies a regulatory threshold without logging it
---
## Worked Examples
### Credit score proof with error handling
```typescript
import {
createRangeProof, verifyRangeProof, serializeRangeProof,
deserializeRangeProof, ValidationError, CryptoError
} from '@forgesworn/range-proof';
// Prover: prove credit score is in [300, 850] without revealing exact score
function proveScore(score: number, subjectPubkey: string): string {
try {
const proof = createRangeProof(score, 300, 850, subjectPubkey);
return serializeRangeProof(proof);
} catch (err) {
if (err instanceof ValidationError) {
throw new Error(`Invalid score input: ${err.message}`);
}
throw err;
}
}
// Verifier: check the proof (receives JSON + expected context)
function verifyScore(proofJson: string, subjectPubkey: string): boolean {
try {
const proof = deserializeRangeProof(proofJson);
return verifyRangeProof(proof, 300, 850, subjectPubkey);
} catch (err) {
if (err instanceof ValidationError) {
console.error('Malformed proof:', err.message);
return false;
}
throw err;
}
}
const json = proveScore(720, 'abc123pubkey');
const ok = verifyScore(json, 'abc123pubkey'); // true
const bad = verifyScore(json, 'wrong-pubkey'); // false — context mismatch
```
### Multi-proof verification session (same identity)
Verify multiple range proofs bound to the same subject in a single session:
```typescript
import {
createRangeProof, verifyRangeProof, ValidationError
} from '@forgesworn/range-proof';
interface ProofCheck {
label: string;
proof: ReturnType<typeof createRangeProof>;
min: number;
max: number;
}
// Generate a real secp256k1 public key for binding context
import { secp256k1 } from '@noble/curves/secp256k1';
import { bytesToHex } from '@noble/hashes/utils';
const privKey = secp256k1.utils.randomPrivateKey();
const pubKey = bytesToHex(secp256k1.getPublicKey(privKey, true)); // compressed hex
// Create multiple proofs bound to the same identity
const checks: ProofCheck[] = [
{
label: 'age',
proof: createRangeProof(25, 18, 150, pubKey),
min: 18,
max: 150,
},
{
label: 'credit-score',
proof: createRangeProof(720, 300, 850, pubKey),
min: 300,
max: 850,
},
{
label: 'income-bracket',
proof: createRangeProof(55000, 30000, 100000, pubKey),
min: 30000,
max: 100000,
},
];
// Verify all proofs in a single session
const results = checks.map(({ label, proof, min, max }) => ({
label,
valid: verifyRangeProof(proof, min, max, pubKey),
}));
const allValid = results.every(r => r.valid);
console.log('All proofs valid:', allValid); // true
// If any fail, identify which ones
const failures = results.filter(r => !r.valid);
if (failures.length > 0) {
console.error('Failed checks:', failures.map(f => f.label));
}
```
### Generating a real public key for binding context
```typescript
import { secp256k1 } from '@noble/curves/secp256k1';
import { bytesToHex } from '@noble/hashes/utils';
import { createRangeProof, verifyRangeProof } from '@forgesworn/range-proof';
// Generate a secp256k1 key pair
const privateKey = secp256k1.utils.randomPrivateKey();
const publicKey = bytesToHex(secp256k1.getPublicKey(privateKey, true));
// publicKey is a 66-char compressed hex string (02/03 prefix + 32 bytes)
// Use the public key as binding context
const proof = createRangeProof(25, 18, 150, publicKey);
// Verifier must supply the same public key
const valid = verifyRangeProof(proof, 18, 150, publicKey); // true
const transplant = verifyRangeProof(proof, 18, 150, 'other-pubkey'); // false
```
---
## Related Packages
- `nostr-attestations` — NIP-VA kind 31000 attestation events (attach proofs as content or tags)
- `ring-sig` — SAG/LSAG ring signatures for anonymous group membership proofs
- `@forgesworn/shamir-words` — Shamir secret sharing with BIP-39 word encoding