canary-kit 0.9.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.
Files changed (69) hide show
  1. package/CANARY.md +1065 -0
  2. package/INTEGRATION.md +351 -0
  3. package/LICENSE +21 -0
  4. package/NIP-CANARY.md +624 -0
  5. package/README.md +187 -0
  6. package/SECURITY.md +92 -0
  7. package/dist/beacon.d.ts +104 -0
  8. package/dist/beacon.d.ts.map +1 -0
  9. package/dist/beacon.js +197 -0
  10. package/dist/beacon.js.map +1 -0
  11. package/dist/counter.d.ts +37 -0
  12. package/dist/counter.d.ts.map +1 -0
  13. package/dist/counter.js +62 -0
  14. package/dist/counter.js.map +1 -0
  15. package/dist/crypto.d.ts +111 -0
  16. package/dist/crypto.d.ts.map +1 -0
  17. package/dist/crypto.js +309 -0
  18. package/dist/crypto.js.map +1 -0
  19. package/dist/derive.d.ts +68 -0
  20. package/dist/derive.d.ts.map +1 -0
  21. package/dist/derive.js +85 -0
  22. package/dist/derive.js.map +1 -0
  23. package/dist/encoding.d.ts +56 -0
  24. package/dist/encoding.d.ts.map +1 -0
  25. package/dist/encoding.js +98 -0
  26. package/dist/encoding.js.map +1 -0
  27. package/dist/group.d.ts +185 -0
  28. package/dist/group.d.ts.map +1 -0
  29. package/dist/group.js +263 -0
  30. package/dist/group.js.map +1 -0
  31. package/dist/index.d.ts +10 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +12 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/nostr.d.ts +134 -0
  36. package/dist/nostr.d.ts.map +1 -0
  37. package/dist/nostr.js +175 -0
  38. package/dist/nostr.js.map +1 -0
  39. package/dist/presets.d.ts +26 -0
  40. package/dist/presets.d.ts.map +1 -0
  41. package/dist/presets.js +39 -0
  42. package/dist/presets.js.map +1 -0
  43. package/dist/session.d.ts +114 -0
  44. package/dist/session.d.ts.map +1 -0
  45. package/dist/session.js +173 -0
  46. package/dist/session.js.map +1 -0
  47. package/dist/sync-crypto.d.ts +66 -0
  48. package/dist/sync-crypto.d.ts.map +1 -0
  49. package/dist/sync-crypto.js +125 -0
  50. package/dist/sync-crypto.js.map +1 -0
  51. package/dist/sync.d.ts +191 -0
  52. package/dist/sync.d.ts.map +1 -0
  53. package/dist/sync.js +568 -0
  54. package/dist/sync.js.map +1 -0
  55. package/dist/token.d.ts +186 -0
  56. package/dist/token.d.ts.map +1 -0
  57. package/dist/token.js +344 -0
  58. package/dist/token.js.map +1 -0
  59. package/dist/verify.d.ts +45 -0
  60. package/dist/verify.d.ts.map +1 -0
  61. package/dist/verify.js +59 -0
  62. package/dist/verify.js.map +1 -0
  63. package/dist/wordlist.d.ts +28 -0
  64. package/dist/wordlist.d.ts.map +1 -0
  65. package/dist/wordlist.js +297 -0
  66. package/dist/wordlist.js.map +1 -0
  67. package/llms-full.txt +1461 -0
  68. package/llms.txt +180 -0
  69. package/package.json +144 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derive.js","sourceRoot":"","sources":["../src/derive.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAG3D,2DAA2D;AAC3D,MAAM,CAAC,MAAM,aAAa,GAAG,cAAc,CAAA;AAE3C,4EAA4E;AAC5E,MAAM,uBAAuB,GAAG,CAAC,CAAA;AAEjC,SAAS,YAAY,CAAC,SAAoB;IACxC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;AAC9C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAe,EAAE,OAAe;IACrE,OAAO,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,CAAC,CAAA;AACrD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB,CACtC,OAAe,EACf,OAAe,EACf,SAAoB;IAEpB,IAAI,SAAS,KAAK,CAAC;QAAE,OAAO,CAAC,sBAAsB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;IACtE,OAAO,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;AACzF,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAe,EACf,eAAuB,EACvB,OAAe,EACf,eAAuB,uBAAuB,EAC9C,UAAqB;IAErB,OAAO,iBAAiB,CAAC,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,CAAC,CAAA;AACjH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAe,EACf,eAAuB,EACvB,OAAe,EACf,SAAoB,EACpB,eAAuB,uBAAuB,EAC9C,UAAqB;IAErB,IAAI,SAAS,KAAK,CAAC;QAAE,OAAO,CAAC,gBAAgB,CAAC,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC,CAAA;IAC3G,OAAO,iBAAiB,CAAC,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;AAC1I,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAwC;IACxE,OAAO,sBAAsB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;AAC1D,CAAC"}
@@ -0,0 +1,56 @@
1
+ /** Encoding options for token output. */
2
+ export type TokenEncoding = {
3
+ format: 'words';
4
+ count?: number;
5
+ wordlist?: readonly string[];
6
+ } | {
7
+ format: 'pin';
8
+ digits?: number;
9
+ } | {
10
+ format: 'hex';
11
+ length?: number;
12
+ };
13
+ /** Default encoding: single word from en-v1 wordlist. */
14
+ export declare const DEFAULT_ENCODING: TokenEncoding;
15
+ /**
16
+ * Encode raw bytes as words using 11-bit indices into a wordlist.
17
+ * Each word uses a consecutive 2-byte slice: readUint16BE(bytes, i*2) % wordlistSize.
18
+ *
19
+ * @param bytes - Raw bytes to encode (must have at least `count * 2` bytes).
20
+ * @param count - Number of words to produce (integer 1–16, default: 1).
21
+ * @param wordlist - Custom wordlist (must be exactly 2048 entries, default: en-v1).
22
+ * @returns Array of lowercase word strings.
23
+ * @throws {RangeError} If count is not an integer 1–16, wordlist is wrong size, or insufficient bytes.
24
+ */
25
+ export declare function encodeAsWords(bytes: Uint8Array, count?: number, wordlist?: readonly string[]): string[];
26
+ /**
27
+ * Encode raw bytes as a numeric PIN with leading zeros.
28
+ * Uses the first ceil(digits * 0.415) bytes, interpreted as a big-endian
29
+ * integer, reduced modulo 10^digits.
30
+ *
31
+ * @param bytes - Raw bytes to encode (must be non-empty).
32
+ * @param digits - Number of PIN digits to produce (integer 1-10, default: 4).
33
+ * @returns Zero-padded numeric string of the specified length.
34
+ * @throws {RangeError} If digits is not an integer 1-10 or bytes is empty.
35
+ */
36
+ export declare function encodeAsPin(bytes: Uint8Array, digits?: number): string;
37
+ /**
38
+ * Encode raw bytes as a lowercase hex string.
39
+ *
40
+ * @param bytes - Raw bytes to encode.
41
+ * @param length - Number of hex characters to produce (integer 1-64, default: 8).
42
+ * @returns Lowercase hex string of the specified length.
43
+ * @throws {RangeError} If length is not an integer 1-64 or insufficient bytes are provided.
44
+ */
45
+ export declare function encodeAsHex(bytes: Uint8Array, length?: number): string;
46
+ /**
47
+ * Encode raw bytes using the specified encoding format.
48
+ * Returns a single string (words are space-joined).
49
+ *
50
+ * @param bytes - Raw bytes to encode.
51
+ * @param encoding - Encoding format: words, pin, or hex (default: single word).
52
+ * @returns Encoded token string (space-joined words, zero-padded PIN, or hex).
53
+ * @throws {RangeError} If encoding parameters are out of range or bytes are insufficient.
54
+ */
55
+ export declare function encodeToken(bytes: Uint8Array, encoding?: TokenEncoding): string;
56
+ //# sourceMappingURL=encoding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoding.d.ts","sourceRoot":"","sources":["../src/encoding.ts"],"names":[],"mappings":"AAGA,yCAAyC;AACzC,MAAM,MAAM,aAAa,GACrB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACjE;IAAE,MAAM,EAAE,KAAK,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,MAAM,EAAE,KAAK,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtC,yDAAyD;AACzD,eAAO,MAAM,gBAAgB,EAAE,aAA6C,CAAA;AAE5E;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,UAAU,EACjB,KAAK,GAAE,MAAU,EACjB,QAAQ,GAAE,SAAS,MAAM,EAAa,GACrC,MAAM,EAAE,CAUV;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,GAAE,MAAU,GAAG,MAAM,CAkBzE;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,GAAE,MAAU,GAAG,MAAM,CASzE;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,GAAE,aAAgC,GAAG,MAAM,CASjG"}
@@ -0,0 +1,98 @@
1
+ import { readUint16BE } from './crypto.js';
2
+ import { WORDLIST } from './wordlist.js';
3
+ /** Default encoding: single word from en-v1 wordlist. */
4
+ export const DEFAULT_ENCODING = { format: 'words', count: 1 };
5
+ /**
6
+ * Encode raw bytes as words using 11-bit indices into a wordlist.
7
+ * Each word uses a consecutive 2-byte slice: readUint16BE(bytes, i*2) % wordlistSize.
8
+ *
9
+ * @param bytes - Raw bytes to encode (must have at least `count * 2` bytes).
10
+ * @param count - Number of words to produce (integer 1–16, default: 1).
11
+ * @param wordlist - Custom wordlist (must be exactly 2048 entries, default: en-v1).
12
+ * @returns Array of lowercase word strings.
13
+ * @throws {RangeError} If count is not an integer 1–16, wordlist is wrong size, or insufficient bytes.
14
+ */
15
+ export function encodeAsWords(bytes, count = 1, wordlist = WORDLIST) {
16
+ if (wordlist.length !== 2048)
17
+ throw new RangeError('Wordlist must contain exactly 2048 entries');
18
+ if (!Number.isInteger(count) || count < 1 || count > 16)
19
+ throw new RangeError('Word count must be an integer 1–16');
20
+ if (bytes.length < count * 2)
21
+ throw new RangeError('Not enough bytes for requested word count');
22
+ const words = [];
23
+ for (let i = 0; i < count; i++) {
24
+ const index = readUint16BE(bytes, i * 2) % wordlist.length;
25
+ words.push(wordlist[index]);
26
+ }
27
+ return words;
28
+ }
29
+ /**
30
+ * Encode raw bytes as a numeric PIN with leading zeros.
31
+ * Uses the first ceil(digits * 0.415) bytes, interpreted as a big-endian
32
+ * integer, reduced modulo 10^digits.
33
+ *
34
+ * @param bytes - Raw bytes to encode (must be non-empty).
35
+ * @param digits - Number of PIN digits to produce (integer 1-10, default: 4).
36
+ * @returns Zero-padded numeric string of the specified length.
37
+ * @throws {RangeError} If digits is not an integer 1-10 or bytes is empty.
38
+ */
39
+ export function encodeAsPin(bytes, digits = 4) {
40
+ if (!Number.isInteger(digits) || digits < 1 || digits > 10)
41
+ throw new RangeError('PIN digits must be an integer 1–10');
42
+ if (bytes.length === 0)
43
+ throw new RangeError('Cannot encode empty byte array as PIN');
44
+ const needed = Math.min(Math.ceil(digits * 0.415), bytes.length);
45
+ const mod = Math.pow(10, digits);
46
+ // Use BigInt accumulation for 9–10 digits to avoid 32-bit overflow in >>> 0
47
+ if (digits >= 9) {
48
+ let bigVal = 0n;
49
+ for (let i = 0; i < needed; i++)
50
+ bigVal = bigVal * 256n + BigInt(bytes[i]);
51
+ return (Number(bigVal % BigInt(mod))).toString().padStart(digits, '0');
52
+ }
53
+ let value = 0;
54
+ for (let i = 0; i < needed; i++) {
55
+ value = (value * 256 + bytes[i]) >>> 0;
56
+ }
57
+ return (value % mod).toString().padStart(digits, '0');
58
+ }
59
+ /**
60
+ * Encode raw bytes as a lowercase hex string.
61
+ *
62
+ * @param bytes - Raw bytes to encode.
63
+ * @param length - Number of hex characters to produce (integer 1-64, default: 8).
64
+ * @returns Lowercase hex string of the specified length.
65
+ * @throws {RangeError} If length is not an integer 1-64 or insufficient bytes are provided.
66
+ */
67
+ export function encodeAsHex(bytes, length = 8) {
68
+ if (!Number.isInteger(length) || length < 1 || length > 64)
69
+ throw new RangeError('Hex length must be an integer 1–64');
70
+ const needed = Math.ceil(length / 2);
71
+ if (bytes.length < needed)
72
+ throw new RangeError(`Not enough bytes: need ${needed}, got ${bytes.length}`);
73
+ let hex = '';
74
+ for (let i = 0; i < needed && i < bytes.length; i++) {
75
+ hex += bytes[i].toString(16).padStart(2, '0');
76
+ }
77
+ return hex.slice(0, length);
78
+ }
79
+ /**
80
+ * Encode raw bytes using the specified encoding format.
81
+ * Returns a single string (words are space-joined).
82
+ *
83
+ * @param bytes - Raw bytes to encode.
84
+ * @param encoding - Encoding format: words, pin, or hex (default: single word).
85
+ * @returns Encoded token string (space-joined words, zero-padded PIN, or hex).
86
+ * @throws {RangeError} If encoding parameters are out of range or bytes are insufficient.
87
+ */
88
+ export function encodeToken(bytes, encoding = DEFAULT_ENCODING) {
89
+ switch (encoding.format) {
90
+ case 'words':
91
+ return encodeAsWords(bytes, encoding.count ?? 1, encoding.wordlist).join(' ');
92
+ case 'pin':
93
+ return encodeAsPin(bytes, encoding.digits ?? 4);
94
+ case 'hex':
95
+ return encodeAsHex(bytes, encoding.length ?? 8);
96
+ }
97
+ }
98
+ //# sourceMappingURL=encoding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoding.js","sourceRoot":"","sources":["../src/encoding.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,185 @@
1
+ import { type PresetName } from './presets.js';
2
+ /** Configuration provided when creating a new group. */
3
+ export interface GroupConfig {
4
+ name: string;
5
+ members: string[];
6
+ /** Named threat-profile preset. Explicit fields override preset values. */
7
+ preset?: PresetName;
8
+ rotationInterval?: number;
9
+ wordCount?: 1 | 2 | 3;
10
+ wordlist?: string;
11
+ /** Counter tolerance for verification: accept tokens within ±tolerance counter values (default: 1). */
12
+ tolerance?: number;
13
+ /**
14
+ * Beacon broadcast interval in seconds (default: 300 = 5 minutes).
15
+ *
16
+ * **Privacy note:** Fixed-interval beacons are correlatable by timing.
17
+ * A relay observer who knows the group's `h` tag can cluster sequential
18
+ * events by publishing cadence to identify individual members or detect
19
+ * when a member goes offline. Applications SHOULD add random jitter to
20
+ * the publish schedule — e.g. ±20–30% of the interval — to reduce
21
+ * timing correlation. The library does not add jitter because it
22
+ * encrypts payloads but does not control publish scheduling.
23
+ */
24
+ beaconInterval?: number;
25
+ /** Geohash precision for normal beacons, 1–11 (default: 6 ≈ 1.2km). */
26
+ beaconPrecision?: number;
27
+ /** Pubkey of the group creator. Only the creator is admin at bootstrap. Must be in `members`. */
28
+ creator?: string;
29
+ }
30
+ /** Persistent state for a canary group. All fields are serialisable. */
31
+ export interface GroupState {
32
+ name: string;
33
+ seed: string;
34
+ members: string[];
35
+ rotationInterval: number;
36
+ wordCount: 1 | 2 | 3;
37
+ wordlist: string;
38
+ /** Time-based counter at group creation. Advances with rotation interval. */
39
+ counter: number;
40
+ /** Burn-after-use offset applied on top of the time-based counter. */
41
+ usageOffset: number;
42
+ createdAt: number;
43
+ /** Counter tolerance for verification: accept tokens within ±tolerance counter values. */
44
+ tolerance: number;
45
+ /** Beacon broadcast interval in seconds. */
46
+ beaconInterval: number;
47
+ /** Geohash precision for normal beacons (1–11). */
48
+ beaconPrecision: number;
49
+ /** Pubkeys with admin privileges (reseed, add/remove others). */
50
+ admins: string[];
51
+ /** Monotonic epoch — increments on reseed. Used for replay protection. */
52
+ epoch: number;
53
+ /** Consumed operation IDs within the current epoch. Cleared on epoch bump. */
54
+ consumedOps: string[];
55
+ /** Timestamp floor: reject messages with timestamps at or below this value (replay protection after consumedOps eviction). */
56
+ consumedOpsFloor?: number;
57
+ }
58
+ /**
59
+ * Create a new group with a freshly generated seed and time-based counter.
60
+ *
61
+ * @param config - Group configuration including name, members, optional preset and overrides.
62
+ * @returns A new {@link GroupState} with a cryptographically secure random seed.
63
+ * @throws {Error} If name is empty, preset is unknown, members contain invalid pubkeys, or config values are out of range.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * const group = createGroup({
68
+ * name: 'Family',
69
+ * members: [alicePubkey, bobPubkey],
70
+ * preset: 'family',
71
+ * })
72
+ * getCurrentWord(group) // "falcon"
73
+ * ```
74
+ */
75
+ export declare function createGroup(config: GroupConfig): GroupState;
76
+ /**
77
+ * Return the current verification word (or space-joined phrase) that all
78
+ * group members should use to authenticate with one another.
79
+ *
80
+ * @param state - Current group state.
81
+ * @returns The current verification word or space-joined phrase.
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * const group = createGroup({ name: 'Family', members: [alice, bob] })
86
+ * const word = getCurrentWord(group) // e.g. "falcon"
87
+ * ```
88
+ */
89
+ export declare function getCurrentWord(state: GroupState): string;
90
+ /**
91
+ * Return the duress word (or phrase) for a specific member at the current
92
+ * counter. Duress words are member-specific and distinct from the verification
93
+ * word, allowing silent distress signalling.
94
+ *
95
+ * @param state - Current group state.
96
+ * @param memberPubkey - 64-character hex pubkey of the member requesting their duress word.
97
+ * @returns The member's duress word or space-joined phrase, distinct from the verification word.
98
+ */
99
+ export declare function getCurrentDuressWord(state: GroupState, memberPubkey: string): string;
100
+ /**
101
+ * Advance the usage offset by one, rotating the current word (burn-after-use).
102
+ * Throws a RangeError if the effective counter would exceed the current
103
+ * time-based counter plus MAX_COUNTER_OFFSET, per CANARY spec §Counter Acceptance.
104
+ * Returns new state — does not mutate the input.
105
+ *
106
+ * @param state - Current group state.
107
+ * @returns New group state with usageOffset incremented by one.
108
+ * @throws {RangeError} If advancing would exceed the time-based counter plus MAX_COUNTER_OFFSET.
109
+ */
110
+ export declare function advanceCounter(state: GroupState): GroupState;
111
+ /**
112
+ * Generate a fresh seed and reset the usage offset to zero.
113
+ * Call this after a security event (e.g. suspected compromise).
114
+ * Returns new state — does not mutate the input.
115
+ *
116
+ * @param state - Current group state.
117
+ * @returns New group state with a fresh cryptographically secure seed and usageOffset reset to zero.
118
+ */
119
+ export declare function reseed(state: GroupState): GroupState;
120
+ /**
121
+ * Add a member to the group. If the pubkey is already present, returns the
122
+ * existing state unchanged (idempotent).
123
+ * Returns new state — does not mutate the input.
124
+ *
125
+ * @param state - Current group state.
126
+ * @param pubkey - 64-character lowercase hex pubkey of the member to add.
127
+ * @returns New group state with the member added, or unchanged state if already present.
128
+ * @throws {Error} If pubkey is not a valid 64-character hex string or group is at maximum capacity.
129
+ */
130
+ export declare function addMember(state: GroupState, pubkey: string): GroupState;
131
+ /**
132
+ * Remove a member from the group's member list.
133
+ *
134
+ * **Important:** This does NOT reseed. In a symmetric-key group, the removed
135
+ * member still possesses the old seed and can derive valid words. Use
136
+ * `removeMemberAndReseed()` instead unless you have a specific reason not to.
137
+ *
138
+ * Returns new state — does not mutate the input.
139
+ *
140
+ * @param state - Current group state.
141
+ * @param pubkey - 64-character lowercase hex pubkey of the member to remove.
142
+ * @returns New group state with the member removed (no-op if not found).
143
+ * @throws {Error} If pubkey is not a valid 64-character hex string.
144
+ */
145
+ export declare function removeMember(state: GroupState, pubkey: string): GroupState;
146
+ /**
147
+ * Remove a member and immediately reseed, atomically invalidating the old seed.
148
+ * This is the recommended way to remove members — ensures forward secrecy by
149
+ * preventing the removed member from deriving future tokens or decrypting
150
+ * future beacons.
151
+ *
152
+ * Returns new state — does not mutate the input.
153
+ *
154
+ * @param state - Current group state.
155
+ * @param pubkey - 64-character lowercase hex pubkey of the member to remove.
156
+ * @returns New group state with the member removed and a fresh seed.
157
+ * @throws {Error} If pubkey is not a valid 64-character hex string.
158
+ */
159
+ export declare function removeMemberAndReseed(state: GroupState, pubkey: string): GroupState;
160
+ /**
161
+ * Dissolve a group, zeroing the seed and clearing all members.
162
+ * Per CANARY spec §Seed Storage: seed MUST be wiped on group dissolution.
163
+ * Preserves name and timestamps for audit trail.
164
+ *
165
+ * Note: zeroing the seed string prevents further token derivation. Full
166
+ * memory erasure of prior string values is not possible in JS — platform-level
167
+ * secure storage should handle that concern. Callers MUST also delete the
168
+ * persisted record (IndexedDB, localStorage, backend) after calling this.
169
+ *
170
+ * Returns new state — does not mutate the input.
171
+ *
172
+ * @param state - Current group state.
173
+ * @returns New group state with seed zeroed, members and admins cleared, and offsets reset.
174
+ */
175
+ export declare function dissolveGroup(state: GroupState): GroupState;
176
+ /**
177
+ * Refresh the counter to the current time window. Call after loading persisted state.
178
+ * Enforces monotonicity: the counter never regresses, preventing clock rollback attacks.
179
+ *
180
+ * @param state - Current group state.
181
+ * @param nowSec - Current unix timestamp in seconds (default: `Date.now() / 1000`).
182
+ * @returns New group state with counter updated and usageOffset reset, or unchanged if counter has not advanced.
183
+ */
184
+ export declare function syncCounter(state: GroupState, nowSec?: number): GroupState;
185
+ //# sourceMappingURL=group.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group.d.ts","sourceRoot":"","sources":["../src/group.ts"],"names":[],"mappings":"AAIA,OAAO,EAAW,KAAK,UAAU,EAAE,MAAM,cAAc,CAAA;AAcvD,wDAAwD;AACxD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,SAAS,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,uGAAuG;IACvG,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;;;;;OAUG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,uEAAuE;IACvE,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,iGAAiG;IACjG,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,wEAAwE;AACxE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAA;IACf,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,0FAA0F;IAC1F,SAAS,EAAE,MAAM,CAAA;IACjB,4CAA4C;IAC5C,cAAc,EAAE,MAAM,CAAA;IACtB,mDAAmD;IACnD,eAAe,EAAE,MAAM,CAAA;IACvB,iEAAiE;IACjE,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAA;IACb,8EAA8E;IAC9E,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,8HAA8H;IAC9H,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAUD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,UAAU,CA4E3D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAIxD;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAIpF;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAU5D;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAEpD;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAOvE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAG1E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAGnF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAS3D;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,UAAU,EACjB,MAAM,GAAE,MAAsC,GAC7C,UAAU,CAIZ"}
package/dist/group.js ADDED
@@ -0,0 +1,263 @@
1
+ import { randomSeed } from './crypto.js';
2
+ import { getCounter, DEFAULT_ROTATION_INTERVAL, MAX_COUNTER_OFFSET } from './counter.js';
3
+ import { deriveToken, deriveDuressToken, MAX_TOLERANCE } from './token.js';
4
+ import { GROUP_CONTEXT } from './derive.js';
5
+ import { PRESETS } from './presets.js';
6
+ const HEX_64_RE = /^[0-9a-f]{64}$/;
7
+ /** Maximum group members. Large groups degrade collision resistance in the 2048-word space. */
8
+ const MAX_MEMBERS = 100;
9
+ /** Validate that a string is a 64-character lowercase hex pubkey. */
10
+ function validatePubkey(pubkey) {
11
+ if (!HEX_64_RE.test(pubkey)) {
12
+ throw new Error(`Invalid member pubkey: expected 64 hex characters, got ${pubkey.length} chars`);
13
+ }
14
+ }
15
+ /**
16
+ * Combine the time-based counter with the usage offset to produce the
17
+ * effective counter used for word derivation.
18
+ */
19
+ function effectiveCounter(state) {
20
+ return state.counter + state.usageOffset;
21
+ }
22
+ /**
23
+ * Create a new group with a freshly generated seed and time-based counter.
24
+ *
25
+ * @param config - Group configuration including name, members, optional preset and overrides.
26
+ * @returns A new {@link GroupState} with a cryptographically secure random seed.
27
+ * @throws {Error} If name is empty, preset is unknown, members contain invalid pubkeys, or config values are out of range.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const group = createGroup({
32
+ * name: 'Family',
33
+ * members: [alicePubkey, bobPubkey],
34
+ * preset: 'family',
35
+ * })
36
+ * getCurrentWord(group) // "falcon"
37
+ * ```
38
+ */
39
+ export function createGroup(config) {
40
+ if (typeof config.name !== 'string' || config.name.length === 0) {
41
+ throw new Error('name must be a non-empty string');
42
+ }
43
+ if (config.preset !== undefined) {
44
+ if (typeof config.preset !== 'string' || !Object.hasOwn(PRESETS, config.preset)) {
45
+ throw new Error(`Unknown preset: "${config.preset}". Valid presets: ${Object.keys(PRESETS).join(', ')}`);
46
+ }
47
+ }
48
+ const now = Math.floor(Date.now() / 1000);
49
+ const base = config.preset !== undefined ? PRESETS[config.preset] : undefined;
50
+ const interval = config.rotationInterval ?? base?.rotationInterval ?? DEFAULT_ROTATION_INTERVAL;
51
+ const wordCount = config.wordCount ?? base?.wordCount ?? 1;
52
+ const tolerance = config.tolerance ?? 1;
53
+ // Validate resolved config values
54
+ if (!Number.isInteger(interval) || interval <= 0) {
55
+ throw new Error(`rotationInterval must be a positive integer, got ${interval}`);
56
+ }
57
+ if (wordCount !== 1 && wordCount !== 2 && wordCount !== 3) {
58
+ throw new Error(`wordCount must be 1, 2, or 3, got ${wordCount}`);
59
+ }
60
+ if (!Number.isInteger(tolerance) || tolerance < 0 || tolerance > MAX_TOLERANCE) {
61
+ throw new RangeError(`tolerance must be an integer 0–${MAX_TOLERANCE}, got ${tolerance}`);
62
+ }
63
+ if (config.beaconInterval !== undefined) {
64
+ if (!Number.isInteger(config.beaconInterval) || config.beaconInterval <= 0) {
65
+ throw new Error(`beaconInterval must be a positive integer, got ${config.beaconInterval}`);
66
+ }
67
+ }
68
+ if (config.beaconPrecision !== undefined) {
69
+ if (!Number.isInteger(config.beaconPrecision) || config.beaconPrecision < 1 || config.beaconPrecision > 11) {
70
+ throw new Error(`beaconPrecision must be an integer between 1 and 11, got ${config.beaconPrecision}`);
71
+ }
72
+ }
73
+ for (const pubkey of config.members) {
74
+ validatePubkey(pubkey);
75
+ }
76
+ const uniqueMembers = new Set(config.members);
77
+ if (uniqueMembers.size !== config.members.length) {
78
+ throw new Error('Duplicate pubkeys in members array');
79
+ }
80
+ if (config.creator !== undefined) {
81
+ validatePubkey(config.creator);
82
+ if (!config.members.includes(config.creator)) {
83
+ throw new Error('creator must be in members');
84
+ }
85
+ }
86
+ if (wordCount === 1 && config.members.length >= 10) {
87
+ console.warn(`[canary-kit] Group has ${config.members.length} members with 1-word encoding. ` +
88
+ `CANARY spec recommends 2+ words for groups of 10+ members to avoid duress collision (~2.2% at 10 members).`);
89
+ }
90
+ return {
91
+ name: config.name,
92
+ seed: randomSeed(),
93
+ members: [...config.members],
94
+ rotationInterval: interval,
95
+ wordCount,
96
+ tolerance,
97
+ wordlist: config.wordlist ?? 'en-v1',
98
+ counter: getCounter(now, interval),
99
+ usageOffset: 0,
100
+ createdAt: now,
101
+ beaconInterval: config.beaconInterval ?? 300,
102
+ beaconPrecision: config.beaconPrecision ?? 6,
103
+ admins: config.creator ? [config.creator] : [],
104
+ epoch: 0,
105
+ consumedOps: [],
106
+ };
107
+ }
108
+ /**
109
+ * Return the current verification word (or space-joined phrase) that all
110
+ * group members should use to authenticate with one another.
111
+ *
112
+ * @param state - Current group state.
113
+ * @returns The current verification word or space-joined phrase.
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * const group = createGroup({ name: 'Family', members: [alice, bob] })
118
+ * const word = getCurrentWord(group) // e.g. "falcon"
119
+ * ```
120
+ */
121
+ export function getCurrentWord(state) {
122
+ const counter = effectiveCounter(state);
123
+ const encoding = state.wordCount === 1 ? undefined : { format: 'words', count: state.wordCount };
124
+ return deriveToken(state.seed, GROUP_CONTEXT, counter, encoding);
125
+ }
126
+ /**
127
+ * Return the duress word (or phrase) for a specific member at the current
128
+ * counter. Duress words are member-specific and distinct from the verification
129
+ * word, allowing silent distress signalling.
130
+ *
131
+ * @param state - Current group state.
132
+ * @param memberPubkey - 64-character hex pubkey of the member requesting their duress word.
133
+ * @returns The member's duress word or space-joined phrase, distinct from the verification word.
134
+ */
135
+ export function getCurrentDuressWord(state, memberPubkey) {
136
+ const counter = effectiveCounter(state);
137
+ const encoding = state.wordCount === 1 ? undefined : { format: 'words', count: state.wordCount };
138
+ return deriveDuressToken(state.seed, GROUP_CONTEXT, memberPubkey, counter, encoding, state.tolerance, state.members);
139
+ }
140
+ /**
141
+ * Advance the usage offset by one, rotating the current word (burn-after-use).
142
+ * Throws a RangeError if the effective counter would exceed the current
143
+ * time-based counter plus MAX_COUNTER_OFFSET, per CANARY spec §Counter Acceptance.
144
+ * Returns new state — does not mutate the input.
145
+ *
146
+ * @param state - Current group state.
147
+ * @returns New group state with usageOffset incremented by one.
148
+ * @throws {RangeError} If advancing would exceed the time-based counter plus MAX_COUNTER_OFFSET.
149
+ */
150
+ export function advanceCounter(state) {
151
+ const timeBased = getCounter(Math.floor(Date.now() / 1000), state.rotationInterval);
152
+ const newEffective = state.counter + state.usageOffset + 1;
153
+ if (newEffective > timeBased + MAX_COUNTER_OFFSET) {
154
+ throw new RangeError(`Cannot advance counter: effective counter ${newEffective} would exceed ` +
155
+ `time-based counter ${timeBased} + MAX_COUNTER_OFFSET (${MAX_COUNTER_OFFSET})`);
156
+ }
157
+ return { ...state, usageOffset: state.usageOffset + 1 };
158
+ }
159
+ /**
160
+ * Generate a fresh seed and reset the usage offset to zero.
161
+ * Call this after a security event (e.g. suspected compromise).
162
+ * Returns new state — does not mutate the input.
163
+ *
164
+ * @param state - Current group state.
165
+ * @returns New group state with a fresh cryptographically secure seed and usageOffset reset to zero.
166
+ */
167
+ export function reseed(state) {
168
+ return { ...state, seed: randomSeed(), usageOffset: 0 };
169
+ }
170
+ /**
171
+ * Add a member to the group. If the pubkey is already present, returns the
172
+ * existing state unchanged (idempotent).
173
+ * Returns new state — does not mutate the input.
174
+ *
175
+ * @param state - Current group state.
176
+ * @param pubkey - 64-character lowercase hex pubkey of the member to add.
177
+ * @returns New group state with the member added, or unchanged state if already present.
178
+ * @throws {Error} If pubkey is not a valid 64-character hex string or group is at maximum capacity.
179
+ */
180
+ export function addMember(state, pubkey) {
181
+ validatePubkey(pubkey);
182
+ if (state.members.includes(pubkey))
183
+ return state;
184
+ if (state.members.length >= MAX_MEMBERS) {
185
+ throw new Error(`Cannot add member: group has reached the maximum of ${MAX_MEMBERS} members`);
186
+ }
187
+ return { ...state, members: [...state.members, pubkey] };
188
+ }
189
+ /**
190
+ * Remove a member from the group's member list.
191
+ *
192
+ * **Important:** This does NOT reseed. In a symmetric-key group, the removed
193
+ * member still possesses the old seed and can derive valid words. Use
194
+ * `removeMemberAndReseed()` instead unless you have a specific reason not to.
195
+ *
196
+ * Returns new state — does not mutate the input.
197
+ *
198
+ * @param state - Current group state.
199
+ * @param pubkey - 64-character lowercase hex pubkey of the member to remove.
200
+ * @returns New group state with the member removed (no-op if not found).
201
+ * @throws {Error} If pubkey is not a valid 64-character hex string.
202
+ */
203
+ export function removeMember(state, pubkey) {
204
+ validatePubkey(pubkey);
205
+ return { ...state, members: state.members.filter((m) => m !== pubkey) };
206
+ }
207
+ /**
208
+ * Remove a member and immediately reseed, atomically invalidating the old seed.
209
+ * This is the recommended way to remove members — ensures forward secrecy by
210
+ * preventing the removed member from deriving future tokens or decrypting
211
+ * future beacons.
212
+ *
213
+ * Returns new state — does not mutate the input.
214
+ *
215
+ * @param state - Current group state.
216
+ * @param pubkey - 64-character lowercase hex pubkey of the member to remove.
217
+ * @returns New group state with the member removed and a fresh seed.
218
+ * @throws {Error} If pubkey is not a valid 64-character hex string.
219
+ */
220
+ export function removeMemberAndReseed(state, pubkey) {
221
+ const removed = removeMember(state, pubkey);
222
+ return reseed(removed);
223
+ }
224
+ /**
225
+ * Dissolve a group, zeroing the seed and clearing all members.
226
+ * Per CANARY spec §Seed Storage: seed MUST be wiped on group dissolution.
227
+ * Preserves name and timestamps for audit trail.
228
+ *
229
+ * Note: zeroing the seed string prevents further token derivation. Full
230
+ * memory erasure of prior string values is not possible in JS — platform-level
231
+ * secure storage should handle that concern. Callers MUST also delete the
232
+ * persisted record (IndexedDB, localStorage, backend) after calling this.
233
+ *
234
+ * Returns new state — does not mutate the input.
235
+ *
236
+ * @param state - Current group state.
237
+ * @returns New group state with seed zeroed, members and admins cleared, and offsets reset.
238
+ */
239
+ export function dissolveGroup(state) {
240
+ return {
241
+ ...state,
242
+ seed: '0'.repeat(64),
243
+ members: [],
244
+ admins: [],
245
+ usageOffset: 0,
246
+ consumedOps: [],
247
+ };
248
+ }
249
+ /**
250
+ * Refresh the counter to the current time window. Call after loading persisted state.
251
+ * Enforces monotonicity: the counter never regresses, preventing clock rollback attacks.
252
+ *
253
+ * @param state - Current group state.
254
+ * @param nowSec - Current unix timestamp in seconds (default: `Date.now() / 1000`).
255
+ * @returns New group state with counter updated and usageOffset reset, or unchanged if counter has not advanced.
256
+ */
257
+ export function syncCounter(state, nowSec = Math.floor(Date.now() / 1000)) {
258
+ const counter = getCounter(nowSec, state.rotationInterval);
259
+ if (counter <= state.counter)
260
+ return state;
261
+ return { ...state, counter, usageOffset: 0 };
262
+ }
263
+ //# sourceMappingURL=group.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group.js","sourceRoot":"","sources":["../src/group.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export { WORDLIST, WORDLIST_SIZE, getWord, indexOf, } from './wordlist.js';
2
+ export { getCounter, counterToBytes, counterFromEventId, DEFAULT_ROTATION_INTERVAL, MAX_COUNTER_OFFSET, } from './counter.js';
3
+ export { GROUP_CONTEXT, deriveVerificationWord, deriveVerificationPhrase, deriveDuressWord, deriveDuressPhrase, deriveCurrentWord, } from './derive.js';
4
+ export { verifyWord, type VerifyResult, type VerifyStatus, } from './verify.js';
5
+ export { createGroup, getCurrentWord, getCurrentDuressWord, advanceCounter, reseed, addMember, removeMember, removeMemberAndReseed, syncCounter, dissolveGroup, type GroupConfig, type GroupState, } from './group.js';
6
+ export { PRESETS, type PresetName, type GroupPreset, } from './presets.js';
7
+ export { deriveBeaconKey, deriveDuressKey, encryptBeacon, decryptBeacon, buildDuressAlert, encryptDuressAlert, decryptDuressAlert, type BeaconPayload, type DuressAlert, type DuressLocation, } from './beacon.js';
8
+ export { MAX_TOLERANCE, deriveTokenBytes, deriveToken, deriveDuressTokenBytes, deriveDuressToken, verifyToken, deriveLivenessToken, type TokenVerifyResult, type VerifyOptions, } from './token.js';
9
+ export { encodeAsWords, encodeAsPin, encodeAsHex, encodeToken, type TokenEncoding, DEFAULT_ENCODING, } from './encoding.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,QAAQ,EACR,aAAa,EACb,OAAO,EACP,OAAO,GACR,MAAM,eAAe,CAAA;AAEtB,OAAO,EACL,UAAU,EACV,cAAc,EACd,kBAAkB,EAClB,yBAAyB,EACzB,kBAAkB,GACnB,MAAM,cAAc,CAAA;AAErB,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,wBAAwB,EACxB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,aAAa,CAAA;AAEpB,OAAO,EACL,UAAU,EACV,KAAK,YAAY,EACjB,KAAK,YAAY,GAClB,MAAM,aAAa,CAAA;AAEpB,OAAO,EACL,WAAW,EACX,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,MAAM,EACN,SAAS,EACT,YAAY,EACZ,qBAAqB,EACrB,WAAW,EACX,aAAa,EACb,KAAK,WAAW,EAChB,KAAK,UAAU,GAChB,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,OAAO,EACP,KAAK,UAAU,EACf,KAAK,WAAW,GACjB,MAAM,cAAc,CAAA;AAErB,OAAO,EACL,eAAe,EACf,eAAe,EACf,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,cAAc,GACpB,MAAM,aAAa,CAAA;AAGpB,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,sBAAsB,EACtB,iBAAiB,EACjB,WAAW,EACX,mBAAmB,EACnB,KAAK,iBAAiB,EACtB,KAAK,aAAa,GACnB,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,aAAa,EACb,WAAW,EACX,WAAW,EACX,WAAW,EACX,KAAK,aAAa,EAClB,gBAAgB,GACjB,MAAM,eAAe,CAAA"}