signet-protocol 0.1.0
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/LICENSE +21 -0
- package/README.md +112 -0
- package/dist/anomaly.d.ts +42 -0
- package/dist/anomaly.d.ts.map +1 -0
- package/dist/anomaly.js +209 -0
- package/dist/anomaly.js.map +1 -0
- package/dist/badge.d.ts +56 -0
- package/dist/badge.d.ts.map +1 -0
- package/dist/badge.js +171 -0
- package/dist/badge.js.map +1 -0
- package/dist/bonds.d.ts +39 -0
- package/dist/bonds.d.ts.map +1 -0
- package/dist/bonds.js +149 -0
- package/dist/bonds.js.map +1 -0
- package/dist/challenges.d.ts +18 -0
- package/dist/challenges.d.ts.map +1 -0
- package/dist/challenges.js +145 -0
- package/dist/challenges.js.map +1 -0
- package/dist/cold-call.d.ts +74 -0
- package/dist/cold-call.d.ts.map +1 -0
- package/dist/cold-call.js +176 -0
- package/dist/cold-call.js.map +1 -0
- package/dist/compliance.d.ts +82 -0
- package/dist/compliance.d.ts.map +1 -0
- package/dist/compliance.js +478 -0
- package/dist/compliance.js.map +1 -0
- package/dist/connections.d.ts +63 -0
- package/dist/connections.d.ts.map +1 -0
- package/dist/connections.js +170 -0
- package/dist/connections.js.map +1 -0
- package/dist/constants.d.ts +86 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +124 -0
- package/dist/constants.js.map +1 -0
- package/dist/credentials.d.ts +190 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +686 -0
- package/dist/credentials.js.map +1 -0
- package/dist/crypto.d.ts +27 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +75 -0
- package/dist/crypto.js.map +1 -0
- package/dist/errors.d.ts +17 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +29 -0
- package/dist/errors.js.map +1 -0
- package/dist/i18n.d.ts +98 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +1118 -0
- package/dist/i18n.js.map +1 -0
- package/dist/identity-bridge.d.ts +52 -0
- package/dist/identity-bridge.d.ts.map +1 -0
- package/dist/identity-bridge.js +228 -0
- package/dist/identity-bridge.js.map +1 -0
- package/dist/identity-tree.d.ts +47 -0
- package/dist/identity-tree.d.ts.map +1 -0
- package/dist/identity-tree.js +69 -0
- package/dist/identity-tree.js.map +1 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +86 -0
- package/dist/index.js.map +1 -0
- package/dist/key-derivation.d.ts +43 -0
- package/dist/key-derivation.d.ts.map +1 -0
- package/dist/key-derivation.js +212 -0
- package/dist/key-derivation.js.map +1 -0
- package/dist/lsag.d.ts +23 -0
- package/dist/lsag.d.ts.map +1 -0
- package/dist/lsag.js +35 -0
- package/dist/lsag.js.map +1 -0
- package/dist/merkle.d.ts +19 -0
- package/dist/merkle.d.ts.map +1 -0
- package/dist/merkle.js +155 -0
- package/dist/merkle.js.map +1 -0
- package/dist/policies.d.ts +22 -0
- package/dist/policies.d.ts.map +1 -0
- package/dist/policies.js +123 -0
- package/dist/policies.js.map +1 -0
- package/dist/range-proof.d.ts +6 -0
- package/dist/range-proof.d.ts.map +1 -0
- package/dist/range-proof.js +45 -0
- package/dist/range-proof.js.map +1 -0
- package/dist/relay.d.ts +106 -0
- package/dist/relay.d.ts.map +1 -0
- package/dist/relay.js +336 -0
- package/dist/relay.js.map +1 -0
- package/dist/ring-signature.d.ts +35 -0
- package/dist/ring-signature.d.ts.map +1 -0
- package/dist/ring-signature.js +56 -0
- package/dist/ring-signature.js.map +1 -0
- package/dist/shamir.d.ts +55 -0
- package/dist/shamir.d.ts.map +1 -0
- package/dist/shamir.js +253 -0
- package/dist/shamir.js.map +1 -0
- package/dist/signet-words.d.ts +42 -0
- package/dist/signet-words.d.ts.map +1 -0
- package/dist/signet-words.js +82 -0
- package/dist/signet-words.js.map +1 -0
- package/dist/store.d.ts +65 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +290 -0
- package/dist/store.js.map +1 -0
- package/dist/trust-score.d.ts +9 -0
- package/dist/trust-score.d.ts.map +1 -0
- package/dist/trust-score.js +186 -0
- package/dist/trust-score.js.map +1 -0
- package/dist/types.d.ts +358 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +11 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +21 -0
- package/dist/utils.js.map +1 -0
- package/dist/validation.d.ts +33 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +312 -0
- package/dist/validation.js.map +1 -0
- package/dist/verifiers.d.ts +18 -0
- package/dist/verifiers.d.ts.map +1 -0
- package/dist/verifiers.js +118 -0
- package/dist/verifiers.js.map +1 -0
- package/dist/vouches.d.ts +14 -0
- package/dist/vouches.d.ts.map +1 -0
- package/dist/vouches.js +103 -0
- package/dist/vouches.js.map +1 -0
- package/package.json +76 -0
- package/src/anomaly.ts +307 -0
- package/src/badge.ts +208 -0
- package/src/bonds.ts +203 -0
- package/src/challenges.ts +187 -0
- package/src/cold-call.ts +238 -0
- package/src/compliance.ts +612 -0
- package/src/connections.ts +216 -0
- package/src/constants.ts +146 -0
- package/src/credentials.ts +908 -0
- package/src/crypto.ts +85 -0
- package/src/errors.ts +31 -0
- package/src/i18n.ts +1347 -0
- package/src/identity-bridge.ts +262 -0
- package/src/identity-tree.ts +90 -0
- package/src/index.ts +452 -0
- package/src/lsag.ts +53 -0
- package/src/merkle.ts +176 -0
- package/src/policies.ts +154 -0
- package/src/range-proof.ts +66 -0
- package/src/relay.ts +433 -0
- package/src/ring-signature.ts +76 -0
- package/src/signet-words.ts +122 -0
- package/src/store.ts +336 -0
- package/src/trust-score.ts +208 -0
- package/src/types.ts +482 -0
- package/src/utils.ts +20 -0
- package/src/validation.ts +391 -0
- package/src/verifiers.ts +156 -0
- package/src/vouches.ts +141 -0
package/dist/shamir.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// Shamir's Secret Sharing over GF(256)
|
|
2
|
+
// Split secrets into threshold-of-n shares using polynomial interpolation
|
|
3
|
+
import { randomBytes } from '@noble/hashes/utils';
|
|
4
|
+
import { wordlist as BIP39_WORDLIST } from '@scure/bip39/wordlists/english.js';
|
|
5
|
+
import { zeroBytes } from './utils.js';
|
|
6
|
+
import { SignetValidationError, SignetCryptoError } from './errors.js';
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// GF(256) Arithmetic — irreducible polynomial 0x11b (same as AES)
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
const IRREDUCIBLE = 0x11b;
|
|
11
|
+
const GENERATOR = 0x03;
|
|
12
|
+
/** Log table: log_g(i) for i in [0..255]. LOG[0] is unused. */
|
|
13
|
+
const LOG = new Uint8Array(256);
|
|
14
|
+
/** Exp table: g^i for i in [0..255]. EXP[255] wraps to EXP[0]. */
|
|
15
|
+
const EXP = new Uint8Array(256);
|
|
16
|
+
/** Carryless multiplication used only during table construction */
|
|
17
|
+
function gf256MulSlow(a, b) {
|
|
18
|
+
let result = 0;
|
|
19
|
+
let aa = a;
|
|
20
|
+
let bb = b;
|
|
21
|
+
while (bb > 0) {
|
|
22
|
+
if (bb & 1)
|
|
23
|
+
result ^= aa;
|
|
24
|
+
aa <<= 1;
|
|
25
|
+
if (aa & 0x100)
|
|
26
|
+
aa ^= IRREDUCIBLE;
|
|
27
|
+
bb >>= 1;
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
// Build log/exp tables at module load time using generator 0x03
|
|
32
|
+
{
|
|
33
|
+
let val = 1;
|
|
34
|
+
for (let i = 0; i < 255; i++) {
|
|
35
|
+
EXP[i] = val;
|
|
36
|
+
LOG[val] = i;
|
|
37
|
+
val = gf256MulSlow(val, GENERATOR);
|
|
38
|
+
}
|
|
39
|
+
// Wrap: makes modular indexing simpler
|
|
40
|
+
EXP[255] = EXP[0];
|
|
41
|
+
}
|
|
42
|
+
/** Addition in GF(256) is XOR */
|
|
43
|
+
export function gf256Add(a, b) {
|
|
44
|
+
return a ^ b;
|
|
45
|
+
}
|
|
46
|
+
/** Multiplication in GF(256) using log/exp tables */
|
|
47
|
+
export function gf256Mul(a, b) {
|
|
48
|
+
if (a === 0 || b === 0)
|
|
49
|
+
return 0;
|
|
50
|
+
return EXP[(LOG[a] + LOG[b]) % 255];
|
|
51
|
+
}
|
|
52
|
+
/** Multiplicative inverse in GF(256) */
|
|
53
|
+
export function gf256Inv(a) {
|
|
54
|
+
if (a === 0)
|
|
55
|
+
throw new SignetCryptoError('No inverse for zero in GF(256)');
|
|
56
|
+
return EXP[(255 - LOG[a]) % 255];
|
|
57
|
+
}
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Shamir's Secret Sharing
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
/**
|
|
62
|
+
* Evaluate a polynomial at x in GF(256) using Horner's method.
|
|
63
|
+
* coeffs[0] is the constant term (the secret byte).
|
|
64
|
+
*/
|
|
65
|
+
function evalPoly(coeffs, x) {
|
|
66
|
+
let result = 0;
|
|
67
|
+
for (let i = coeffs.length - 1; i >= 0; i--) {
|
|
68
|
+
result = gf256Add(gf256Mul(result, x), coeffs[i]);
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Split a secret into shares using Shamir's Secret Sharing over GF(256).
|
|
74
|
+
*
|
|
75
|
+
* @param secret The secret bytes to split
|
|
76
|
+
* @param threshold Minimum shares needed to reconstruct (>= 2)
|
|
77
|
+
* @param shares Total number of shares to create (>= threshold, <= 255)
|
|
78
|
+
* @returns Array of ShamirShare objects
|
|
79
|
+
*/
|
|
80
|
+
export function splitSecret(secret, threshold, shares) {
|
|
81
|
+
if (threshold < 2) {
|
|
82
|
+
throw new SignetValidationError('Threshold must be at least 2');
|
|
83
|
+
}
|
|
84
|
+
if (shares < threshold) {
|
|
85
|
+
throw new SignetValidationError('Number of shares must be >= threshold');
|
|
86
|
+
}
|
|
87
|
+
if (shares > 255) {
|
|
88
|
+
throw new SignetValidationError('Number of shares must be <= 255');
|
|
89
|
+
}
|
|
90
|
+
const secretLen = secret.length;
|
|
91
|
+
const result = [];
|
|
92
|
+
// Initialize share data arrays
|
|
93
|
+
for (let i = 0; i < shares; i++) {
|
|
94
|
+
result.push({ id: i + 1, data: new Uint8Array(secretLen) });
|
|
95
|
+
}
|
|
96
|
+
// For each byte of the secret, build a random polynomial and evaluate
|
|
97
|
+
for (let byteIdx = 0; byteIdx < secretLen; byteIdx++) {
|
|
98
|
+
// coeffs[0] = secret byte, coeffs[1..threshold-1] = random
|
|
99
|
+
const coeffs = new Uint8Array(threshold);
|
|
100
|
+
coeffs[0] = secret[byteIdx];
|
|
101
|
+
const rand = randomBytes(threshold - 1);
|
|
102
|
+
for (let j = 1; j < threshold; j++) {
|
|
103
|
+
coeffs[j] = rand[j - 1];
|
|
104
|
+
}
|
|
105
|
+
// Evaluate at x = 1, 2, ..., shares
|
|
106
|
+
for (let i = 0; i < shares; i++) {
|
|
107
|
+
result[i].data[byteIdx] = evalPoly(coeffs, i + 1);
|
|
108
|
+
}
|
|
109
|
+
zeroBytes(coeffs);
|
|
110
|
+
zeroBytes(rand);
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Reconstruct a secret from shares using Lagrange interpolation over GF(256).
|
|
116
|
+
*
|
|
117
|
+
* @param shares Array of shares (at least `threshold` shares)
|
|
118
|
+
* @param threshold The threshold used during splitting
|
|
119
|
+
* @returns The reconstructed secret bytes
|
|
120
|
+
*/
|
|
121
|
+
export function reconstructSecret(shares, threshold) {
|
|
122
|
+
if (shares.length < threshold) {
|
|
123
|
+
throw new SignetValidationError(`Need at least ${threshold} shares, got ${shares.length}`);
|
|
124
|
+
}
|
|
125
|
+
// Use only the first `threshold` shares
|
|
126
|
+
const used = shares.slice(0, threshold);
|
|
127
|
+
// Validate no duplicate share IDs
|
|
128
|
+
const ids = new Set(used.map(s => s.id));
|
|
129
|
+
if (ids.size !== used.length) {
|
|
130
|
+
throw new SignetValidationError('Duplicate share IDs detected — each share must have a unique ID');
|
|
131
|
+
}
|
|
132
|
+
// Reject invalid share IDs: x must be in [1, 255] for GF(256)
|
|
133
|
+
for (const share of used) {
|
|
134
|
+
if (share.id === 0) {
|
|
135
|
+
throw new SignetValidationError('Invalid share ID: 0 is not a valid x-coordinate');
|
|
136
|
+
}
|
|
137
|
+
if (share.id > 255) {
|
|
138
|
+
throw new SignetValidationError('Invalid share ID: must be in [1, 255] for GF(256)');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const secretLen = used[0].data.length;
|
|
142
|
+
for (const share of used) {
|
|
143
|
+
if (share.data.length !== secretLen) {
|
|
144
|
+
throw new SignetValidationError('Inconsistent share lengths — shares may be from different secrets');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const result = new Uint8Array(secretLen);
|
|
148
|
+
// Lagrange interpolation at x = 0 for each byte position
|
|
149
|
+
for (let byteIdx = 0; byteIdx < secretLen; byteIdx++) {
|
|
150
|
+
let value = 0;
|
|
151
|
+
for (let i = 0; i < threshold; i++) {
|
|
152
|
+
const xi = used[i].id;
|
|
153
|
+
const yi = used[i].data[byteIdx];
|
|
154
|
+
// Lagrange basis l_i(0) = product of x_j / (x_i ^ x_j) for j != i
|
|
155
|
+
// In GF(256): subtraction = addition = XOR
|
|
156
|
+
let basis = 1;
|
|
157
|
+
for (let j = 0; j < threshold; j++) {
|
|
158
|
+
if (i === j)
|
|
159
|
+
continue;
|
|
160
|
+
const xj = used[j].id;
|
|
161
|
+
basis = gf256Mul(basis, gf256Mul(xj, gf256Inv(gf256Add(xi, xj))));
|
|
162
|
+
}
|
|
163
|
+
value = gf256Add(value, gf256Mul(yi, basis));
|
|
164
|
+
}
|
|
165
|
+
result[byteIdx] = value;
|
|
166
|
+
}
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// BIP-39 Word Encoding
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
/**
|
|
173
|
+
* Encode a share as BIP-39 words.
|
|
174
|
+
* Prepends the share ID byte to the data, then converts to 11-bit groups.
|
|
175
|
+
*/
|
|
176
|
+
export function shareToWords(share) {
|
|
177
|
+
// Prepend ID byte to data
|
|
178
|
+
const bytes = new Uint8Array(1 + share.data.length);
|
|
179
|
+
bytes[0] = share.id;
|
|
180
|
+
bytes.set(share.data, 1);
|
|
181
|
+
// Stream bytes into 11-bit word indices using Number (safe up to 53 bits)
|
|
182
|
+
// We extract words as soon as we have 11 bits, keeping accumulator small
|
|
183
|
+
const words = [];
|
|
184
|
+
let bits = 0;
|
|
185
|
+
let accumulator = 0;
|
|
186
|
+
for (const byte of bytes) {
|
|
187
|
+
// accumulator has at most 10 bits here (< 11), so (10 + 8 = 18) fits safely in 32-bit
|
|
188
|
+
accumulator = ((accumulator << 8) | byte) >>> 0; // >>> 0 ensures unsigned 32-bit
|
|
189
|
+
bits += 8;
|
|
190
|
+
while (bits >= 11) {
|
|
191
|
+
bits -= 11;
|
|
192
|
+
const index = (accumulator >>> bits) & 0x7ff;
|
|
193
|
+
words.push(BIP39_WORDLIST[index]);
|
|
194
|
+
accumulator &= (1 << bits) - 1; // clear extracted bits to keep accumulator small
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Pad remaining bits on the right to form a final 11-bit group
|
|
198
|
+
if (bits > 0) {
|
|
199
|
+
const index = ((accumulator << (11 - bits)) >>> 0) & 0x7ff;
|
|
200
|
+
words.push(BIP39_WORDLIST[index]);
|
|
201
|
+
}
|
|
202
|
+
return words;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Decode BIP-39 words back to a share.
|
|
206
|
+
* Reverses the encoding from shareToWords.
|
|
207
|
+
*/
|
|
208
|
+
export function wordsToShare(words) {
|
|
209
|
+
if (words.length === 0)
|
|
210
|
+
throw new SignetValidationError('Cannot decode empty word list');
|
|
211
|
+
if (words.length > 256) {
|
|
212
|
+
throw new SignetValidationError('Word count exceeds maximum (256)');
|
|
213
|
+
}
|
|
214
|
+
// Convert words to 11-bit indices
|
|
215
|
+
const indices = [];
|
|
216
|
+
for (const word of words) {
|
|
217
|
+
const idx = BIP39_WORDLIST.indexOf(word.toLowerCase());
|
|
218
|
+
if (idx === -1)
|
|
219
|
+
throw new SignetValidationError(`Unknown BIP-39 word: "${word}"`);
|
|
220
|
+
indices.push(idx);
|
|
221
|
+
}
|
|
222
|
+
// Total bits encoded, and how many full bytes that represents
|
|
223
|
+
const totalBits = indices.length * 11;
|
|
224
|
+
const totalBytes = Math.floor(totalBits / 8);
|
|
225
|
+
// Stream 11-bit groups into bytes using safe unsigned arithmetic
|
|
226
|
+
let bits = 0;
|
|
227
|
+
let accumulator = 0;
|
|
228
|
+
const byteList = [];
|
|
229
|
+
for (const index of indices) {
|
|
230
|
+
// accumulator has at most 7 bits here (< 8), so (7 + 11 = 18) fits safely in 32-bit
|
|
231
|
+
accumulator = ((accumulator << 11) | index) >>> 0; // >>> 0 ensures unsigned 32-bit
|
|
232
|
+
bits += 11;
|
|
233
|
+
while (bits >= 8) {
|
|
234
|
+
bits -= 8;
|
|
235
|
+
byteList.push((accumulator >>> bits) & 0xff);
|
|
236
|
+
accumulator &= (1 << bits) - 1; // clear extracted bits
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Only take the expected number of full bytes (discard padding bits)
|
|
240
|
+
const usable = byteList.slice(0, totalBytes);
|
|
241
|
+
// Need at least 2 bytes: 1 ID + 1 data byte
|
|
242
|
+
if (usable.length < 2) {
|
|
243
|
+
throw new SignetValidationError('Word list too short — need at least 1 ID byte + 1 data byte');
|
|
244
|
+
}
|
|
245
|
+
// First byte is the share ID, rest is data
|
|
246
|
+
const id = usable[0];
|
|
247
|
+
if (id === 0) {
|
|
248
|
+
throw new SignetValidationError('Invalid share ID: 0 is not a valid x-coordinate for GF(256)');
|
|
249
|
+
}
|
|
250
|
+
const data = new Uint8Array(usable.slice(1));
|
|
251
|
+
return { id, data };
|
|
252
|
+
}
|
|
253
|
+
//# sourceMappingURL=shamir.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shamir.js","sourceRoot":"","sources":["../src/shamir.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** The Canary spoken-clarity wordlist used for Signet word verification. */
|
|
2
|
+
export { WORDLIST as SIGNET_WORDLIST } from 'spoken-token/wordlist';
|
|
3
|
+
/** Default: words rotate every 30 seconds */
|
|
4
|
+
export declare const SIGNET_EPOCH_SECONDS = 30;
|
|
5
|
+
/** Default: 3 words per signet */
|
|
6
|
+
export declare const SIGNET_WORD_COUNT = 3;
|
|
7
|
+
/** Default: accept +/-1 epoch window for clock skew tolerance */
|
|
8
|
+
export declare const SIGNET_TOLERANCE = 1;
|
|
9
|
+
/** Maximum supported word count (limited by HMAC output bytes: 32 bytes / 2 bytes per word = 16) */
|
|
10
|
+
export declare const MAX_WORD_COUNT = 16;
|
|
11
|
+
/** Configuration options for signet words */
|
|
12
|
+
export interface SignetWordsConfig {
|
|
13
|
+
/** Number of words to derive (1-16, default: 3) */
|
|
14
|
+
wordCount?: number;
|
|
15
|
+
/** Epoch interval in seconds (default: 30) */
|
|
16
|
+
epochSeconds?: number;
|
|
17
|
+
/** Tolerance in epochs, +/- (default: 1) */
|
|
18
|
+
tolerance?: number;
|
|
19
|
+
}
|
|
20
|
+
/** Return the current epoch index for a given interval. */
|
|
21
|
+
export declare function getEpoch(timestampMs?: number, epochSeconds?: number): number;
|
|
22
|
+
/** Derive N words from a shared secret and epoch number.
|
|
23
|
+
* Uses SPOKEN-DERIVE (HMAC-SHA256) and encodes as spoken-clarity words.
|
|
24
|
+
* @param sharedSecret - Hex string or Uint8Array shared secret (minimum 16 bytes).
|
|
25
|
+
* @param epoch - Time-based epoch counter (uint32).
|
|
26
|
+
* @param wordCount - Number of words to derive (1-16, default 3).
|
|
27
|
+
* @param context - Domain-separation context string (default: 'signet:verify'). */
|
|
28
|
+
export declare function deriveWords(sharedSecret: Uint8Array | string, epoch: number, wordCount?: number, context?: string): string[];
|
|
29
|
+
/** Get the current word sequence for a shared secret. */
|
|
30
|
+
export declare function getSignetWords(sharedSecret: string, timestampMs?: number, config?: SignetWordsConfig): string[];
|
|
31
|
+
/** Verify that the given words match the current epoch (+/- tolerance).
|
|
32
|
+
* Returns true if the words match any epoch within the tolerance window. */
|
|
33
|
+
export declare function verifySignetWords(sharedSecret: string, words: string[], timestampMs?: number, config?: SignetWordsConfig): boolean;
|
|
34
|
+
/** Format words with middle-dot separator: "ocean · tiger · marble" */
|
|
35
|
+
export declare function formatSignetWords(words: string[]): string;
|
|
36
|
+
/** Get everything needed for UI display: words, formatted string, and countdown. */
|
|
37
|
+
export declare function getSignetDisplay(sharedSecret: string, timestampMs?: number, config?: SignetWordsConfig): {
|
|
38
|
+
words: string[];
|
|
39
|
+
formatted: string;
|
|
40
|
+
expiresIn: number;
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=signet-words.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signet-words.d.ts","sourceRoot":"","sources":["../src/signet-words.ts"],"names":[],"mappings":"AAWA,4EAA4E;AAC5E,OAAO,EAAE,QAAQ,IAAI,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAEpE,6CAA6C;AAC7C,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,kCAAkC;AAClC,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC,iEAAiE;AACjE,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,oGAAoG;AACpG,eAAO,MAAM,cAAc,KAAK,CAAC;AAKjC,6CAA6C;AAC7C,MAAM,WAAW,iBAAiB;IAChC,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,2DAA2D;AAC3D,wBAAgB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,YAAY,GAAE,MAA6B,GAAG,MAAM,CAGlG;AAED;;;;;oFAKoF;AACpF,wBAAgB,WAAW,CACzB,YAAY,EAAE,UAAU,GAAG,MAAM,EACjC,KAAK,EAAE,MAAM,EACb,SAAS,GAAE,MAA0B,EACrC,OAAO,GAAE,MAAuB,GAC/B,MAAM,EAAE,CAOV;AAED,yDAAyD;AACzD,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,iBAAiB,GAAG,MAAM,EAAE,CAI/G;AAED;6EAC6E;AAC7E,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EAAE,EACf,WAAW,CAAC,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,iBAAiB,GACzB,OAAO,CAkBT;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAEzD;AAED,oFAAoF;AACpF,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,MAAM,EACpB,WAAW,CAAC,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,iBAAiB,GACzB;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAY3D"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Signet Words — Time-based word verification ("signet me")
|
|
2
|
+
// Signet handles identity (who you are). Spoken-token handles token derivation.
|
|
3
|
+
// Uses SPOKEN-DERIVE (HMAC-SHA256) and the spoken-clarity wordlist.
|
|
4
|
+
import { deriveTokenBytes } from 'spoken-token';
|
|
5
|
+
import { encodeAsWords } from 'spoken-token/encoding';
|
|
6
|
+
import { WORDLIST } from 'spoken-token/wordlist';
|
|
7
|
+
import { constantTimeEqual } from './utils.js';
|
|
8
|
+
import { utf8ToBytes } from '@noble/hashes/utils.js';
|
|
9
|
+
import { SignetValidationError } from './errors.js';
|
|
10
|
+
/** The Canary spoken-clarity wordlist used for Signet word verification. */
|
|
11
|
+
export { WORDLIST as SIGNET_WORDLIST } from 'spoken-token/wordlist';
|
|
12
|
+
/** Default: words rotate every 30 seconds */
|
|
13
|
+
export const SIGNET_EPOCH_SECONDS = 30;
|
|
14
|
+
/** Default: 3 words per signet */
|
|
15
|
+
export const SIGNET_WORD_COUNT = 3;
|
|
16
|
+
/** Default: accept +/-1 epoch window for clock skew tolerance */
|
|
17
|
+
export const SIGNET_TOLERANCE = 1;
|
|
18
|
+
/** Maximum supported word count (limited by HMAC output bytes: 32 bytes / 2 bytes per word = 16) */
|
|
19
|
+
export const MAX_WORD_COUNT = 16;
|
|
20
|
+
/** Context string for Signet word derivation via CANARY-DERIVE */
|
|
21
|
+
const SIGNET_CONTEXT = 'signet:verify';
|
|
22
|
+
/** Return the current epoch index for a given interval. */
|
|
23
|
+
export function getEpoch(timestampMs, epochSeconds = SIGNET_EPOCH_SECONDS) {
|
|
24
|
+
if (epochSeconds < 1)
|
|
25
|
+
throw new SignetValidationError('epochSeconds must be >= 1');
|
|
26
|
+
return Math.floor((timestampMs ?? Date.now()) / (epochSeconds * 1000));
|
|
27
|
+
}
|
|
28
|
+
/** Derive N words from a shared secret and epoch number.
|
|
29
|
+
* Uses SPOKEN-DERIVE (HMAC-SHA256) and encodes as spoken-clarity words.
|
|
30
|
+
* @param sharedSecret - Hex string or Uint8Array shared secret (minimum 16 bytes).
|
|
31
|
+
* @param epoch - Time-based epoch counter (uint32).
|
|
32
|
+
* @param wordCount - Number of words to derive (1-16, default 3).
|
|
33
|
+
* @param context - Domain-separation context string (default: 'signet:verify'). */
|
|
34
|
+
export function deriveWords(sharedSecret, epoch, wordCount = SIGNET_WORD_COUNT, context = SIGNET_CONTEXT) {
|
|
35
|
+
if (wordCount < 1 || wordCount > MAX_WORD_COUNT) {
|
|
36
|
+
throw new SignetValidationError(`wordCount must be between 1 and ${MAX_WORD_COUNT}`);
|
|
37
|
+
}
|
|
38
|
+
const bytes = deriveTokenBytes(sharedSecret, context, epoch);
|
|
39
|
+
return encodeAsWords(bytes, wordCount, WORDLIST);
|
|
40
|
+
}
|
|
41
|
+
/** Get the current word sequence for a shared secret. */
|
|
42
|
+
export function getSignetWords(sharedSecret, timestampMs, config) {
|
|
43
|
+
const epochSeconds = config?.epochSeconds ?? SIGNET_EPOCH_SECONDS;
|
|
44
|
+
const wordCount = config?.wordCount ?? SIGNET_WORD_COUNT;
|
|
45
|
+
return deriveWords(sharedSecret, getEpoch(timestampMs, epochSeconds), wordCount);
|
|
46
|
+
}
|
|
47
|
+
/** Verify that the given words match the current epoch (+/- tolerance).
|
|
48
|
+
* Returns true if the words match any epoch within the tolerance window. */
|
|
49
|
+
export function verifySignetWords(sharedSecret, words, timestampMs, config) {
|
|
50
|
+
const epochSeconds = config?.epochSeconds ?? SIGNET_EPOCH_SECONDS;
|
|
51
|
+
const wordCount = config?.wordCount ?? SIGNET_WORD_COUNT;
|
|
52
|
+
const tolerance = config?.tolerance ?? SIGNET_TOLERANCE;
|
|
53
|
+
const currentEpoch = getEpoch(timestampMs, epochSeconds);
|
|
54
|
+
for (let offset = -tolerance; offset <= tolerance; offset++) {
|
|
55
|
+
const candidate = deriveWords(sharedSecret, currentEpoch + offset, wordCount);
|
|
56
|
+
if (candidate.length === words.length) {
|
|
57
|
+
const a = utf8ToBytes(candidate.join('\0'));
|
|
58
|
+
const b = utf8ToBytes(words.join('\0'));
|
|
59
|
+
if (constantTimeEqual(a, b)) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
/** Format words with middle-dot separator: "ocean · tiger · marble" */
|
|
67
|
+
export function formatSignetWords(words) {
|
|
68
|
+
return words.join(' \u00b7 ');
|
|
69
|
+
}
|
|
70
|
+
/** Get everything needed for UI display: words, formatted string, and countdown. */
|
|
71
|
+
export function getSignetDisplay(sharedSecret, timestampMs, config) {
|
|
72
|
+
const epochSeconds = config?.epochSeconds ?? SIGNET_EPOCH_SECONDS;
|
|
73
|
+
const now = timestampMs ?? Date.now();
|
|
74
|
+
const words = getSignetWords(sharedSecret, now, config);
|
|
75
|
+
const formatted = formatSignetWords(words);
|
|
76
|
+
// Seconds until the next epoch boundary
|
|
77
|
+
const epochMs = epochSeconds * 1000;
|
|
78
|
+
const msIntoEpoch = now % epochMs;
|
|
79
|
+
const expiresIn = (epochMs - msIntoEpoch) / 1000;
|
|
80
|
+
return { words, formatted, expiresIn };
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=signet-words.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signet-words.js","sourceRoot":"","sources":["../src/signet-words.ts"],"names":[],"mappings":"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"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { NostrEvent, ParsedCredential } from './types.js';
|
|
2
|
+
/** Query options for filtering events */
|
|
3
|
+
export interface StoreQuery {
|
|
4
|
+
kinds?: number[];
|
|
5
|
+
authors?: string[];
|
|
6
|
+
subjects?: string[];
|
|
7
|
+
since?: number;
|
|
8
|
+
until?: number;
|
|
9
|
+
limit?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* In-memory Signet event store.
|
|
13
|
+
* Stores events indexed by kind, author, and subject for fast queries.
|
|
14
|
+
* Handles replaceable event semantics (kind 30xxx: newer replaces older for same d-tag).
|
|
15
|
+
*/
|
|
16
|
+
export declare class SignetStore {
|
|
17
|
+
private events;
|
|
18
|
+
private byKind;
|
|
19
|
+
private byAuthor;
|
|
20
|
+
private bySubject;
|
|
21
|
+
/** Add an event to the store. Returns true if added (not a duplicate/older). */
|
|
22
|
+
add(event: NostrEvent): boolean;
|
|
23
|
+
/** Remove an event by ID */
|
|
24
|
+
remove(id: string): boolean;
|
|
25
|
+
/** Get an event by ID */
|
|
26
|
+
get(id: string): NostrEvent | undefined;
|
|
27
|
+
/** Check if an event exists */
|
|
28
|
+
has(id: string): boolean;
|
|
29
|
+
/** Total number of events */
|
|
30
|
+
get size(): number;
|
|
31
|
+
/** Query events with filters */
|
|
32
|
+
query(q?: StoreQuery): NostrEvent[];
|
|
33
|
+
/** Get all credentials for a subject */
|
|
34
|
+
getCredentials(subjectPubkey: string): NostrEvent[];
|
|
35
|
+
/** Get the highest-tier credential for a subject */
|
|
36
|
+
getHighestCredential(subjectPubkey: string): ParsedCredential | null;
|
|
37
|
+
/** Get all vouches for a subject */
|
|
38
|
+
getVouches(subjectPubkey: string): NostrEvent[];
|
|
39
|
+
/** Get the community policy for a community */
|
|
40
|
+
getPolicy(communityId: string): NostrEvent | undefined;
|
|
41
|
+
/** Get a verifier credential */
|
|
42
|
+
getVerifierCredential(verifierPubkey: string): NostrEvent | undefined;
|
|
43
|
+
/** Get all challenges against a verifier */
|
|
44
|
+
getChallenges(verifierPubkey: string): NostrEvent[];
|
|
45
|
+
/** Get all revocations for a verifier */
|
|
46
|
+
getRevocations(verifierPubkey: string): NostrEvent[];
|
|
47
|
+
/** Check if a verifier is revoked */
|
|
48
|
+
isRevoked(verifierPubkey: string): boolean;
|
|
49
|
+
/** Get all credentials issued by a verifier */
|
|
50
|
+
getCredentialsByIssuer(issuerPubkey: string): NostrEvent[];
|
|
51
|
+
/** Export all events as JSON */
|
|
52
|
+
export(): string;
|
|
53
|
+
/** Import events from JSON.
|
|
54
|
+
*
|
|
55
|
+
* WARNING: This method validates structural shape and field-size bounds but does
|
|
56
|
+
* NOT verify event signatures. Callers are responsible for verifying cryptographic
|
|
57
|
+
* validity before importing untrusted data. The RelayClient verifies signatures
|
|
58
|
+
* by default — this gap only affects direct callers of store.import(). */
|
|
59
|
+
import(json: string): number;
|
|
60
|
+
/** Clear all events */
|
|
61
|
+
clear(): void;
|
|
62
|
+
private indexEvent;
|
|
63
|
+
private deindexEvent;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,UAAU,EACV,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAGpB,yCAAyC;AACzC,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAiC;IAG/C,OAAO,CAAC,MAAM,CAAkC;IAChD,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,SAAS,CAAkC;IAEnD,gFAAgF;IAChF,GAAG,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO;IA6B/B,4BAA4B;IAC5B,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAS3B,yBAAyB;IACzB,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAIvC,+BAA+B;IAC/B,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAIxB,6BAA6B;IAC7B,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,gCAAgC;IAChC,KAAK,CAAC,CAAC,GAAE,UAAe,GAAG,UAAU,EAAE;IA8DvC,wCAAwC;IACxC,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,UAAU,EAAE;IAKnD,oDAAoD;IACpD,oBAAoB,CAAC,aAAa,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI;IAcpE,oCAAoC;IACpC,UAAU,CAAC,aAAa,EAAE,MAAM,GAAG,UAAU,EAAE;IAK/C,+CAA+C;IAC/C,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAQtD,gCAAgC;IAChC,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAMrE,4CAA4C;IAC5C,aAAa,CAAC,cAAc,EAAE,MAAM,GAAG,UAAU,EAAE;IAKnD,yCAAyC;IACzC,cAAc,CAAC,cAAc,EAAE,MAAM,GAAG,UAAU,EAAE;IAKpD,qCAAqC;IACrC,SAAS,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO;IAI1C,+CAA+C;IAC/C,sBAAsB,CAAC,YAAY,EAAE,MAAM,GAAG,UAAU,EAAE;IAO1D,gCAAgC;IAChC,MAAM,IAAI,MAAM;IAIhB;;;;;8EAK0E;IAC1E,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IA+C5B,uBAAuB;IACvB,KAAK,IAAI,IAAI;IASb,OAAO,CAAC,UAAU;IAwBlB,OAAO,CAAC,YAAY;CAYrB"}
|