canary-kit 2.6.2 → 2.7.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/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # canary-kit
2
2
 
3
+ **Nostr:** [`npub1mgvlrnf5hm9yf0n5mf9nqmvarhvxkc6remu5ec3vf8r0txqkuk7su0e7q2`](https://njump.me/npub1mgvlrnf5hm9yf0n5mf9nqmvarhvxkc6remu5ec3vf8r0txqkuk7su0e7q2)
4
+
3
5
  > Deepfake-proof identity verification. Open protocol, minimal dependencies.
4
6
 
5
7
  [![npm](https://img.shields.io/npm/v/canary-kit)](https://www.npmjs.com/package/canary-kit)
@@ -148,7 +150,7 @@ import { createSession } from 'canary-kit/session' // just sessions
148
150
  import { deriveToken } from 'canary-kit/token' // just derivation
149
151
  import { encodeAsWords } from 'canary-kit/encoding' // just encoding
150
152
  import { WORDLIST } from 'canary-kit/wordlist' // just the wordlist
151
- import { buildGroupEvent } from 'canary-kit/nostr' // just Nostr
153
+ import { buildGroupStateEvent } from 'canary-kit/nostr' // just Nostr
152
154
  import { encryptBeacon } from 'canary-kit/beacon' // just beacons
153
155
  import { applySyncMessage } from 'canary-kit/sync' // just sync protocol
154
156
  ```
@@ -172,7 +174,7 @@ See [SECURITY.md](SECURITY.md) for vulnerability disclosure and known limitation
172
174
  | `canary-kit/token` | `deriveToken`, `verifyToken`, `deriveDuressToken`, `deriveLivenessToken` |
173
175
  | `canary-kit/encoding` | `encodeAsWords`, `encodeAsPin`, `encodeAsHex` |
174
176
  | `canary-kit` | `createGroup`, `getCurrentWord`, `verifyWord`, `addMember`, `reseed` |
175
- | `canary-kit/nostr` | `buildGroupEvent`, `buildBeaconEvent`, + 4 more builders |
177
+ | `canary-kit/nostr` | `buildGroupStateEvent`, `buildSignalEvent`, `buildStoredSignalEvent`, `buildRumourEvent` |
176
178
  | `canary-kit/beacon` | `encryptBeacon`, `decryptBeacon`, `buildDuressAlert` |
177
179
  | `canary-kit/sync` | `applySyncMessage`, `encodeSyncMessage`, `deriveGroupKey` |
178
180
  | `canary-kit/wordlist` | `WORDLIST`, `getWord`, `indexOf` |
@@ -185,14 +187,11 @@ The full protocol specification is in [CANARY.md](CANARY.md). The Nostr binding
185
187
 
186
188
  | Event | Kind | Type |
187
189
  |---|---|---|
188
- | Group announcement | `38800` | Replaceable |
189
- | Seed distribution | `28800` | Ephemeral |
190
- | Member update | `38801` | Replaceable |
191
- | Reseed | `28801` | Ephemeral |
192
- | Word used | `28802` | Ephemeral |
193
- | Encrypted location beacon | `20800` | Ephemeral |
194
-
195
- Content is encrypted with **NIP-44**. Events may carry a **NIP-40** `expiration` tag.
190
+ | Group state / stored signals | `30078` | Parameterised replaceable |
191
+ | Real-time signals | `20078` | Ephemeral |
192
+ | Seed distribution / member updates | `14` → `1059` | NIP-17 gift wrap (kind 14 rumour sealed + wrapped) |
193
+
194
+ Content is encrypted with **NIP-44**. Group state events use the `ssg/` d-tag namespace. Seed distribution and member updates use **NIP-17** gift wrapping (kind 14 rumour → kind 13 seal → kind 1059 gift wrap). Events may carry a **NIP-40** `expiration` tag.
196
195
 
197
196
  ## For AI Assistants
198
197
 
package/dist/beacon.d.ts CHANGED
@@ -27,7 +27,7 @@ export declare function deriveBeaconKey(seedHex: string): Uint8Array;
27
27
  * @throws {Error} If seedHex is not a valid 64-character hex string.
28
28
  */
29
29
  export declare function deriveDuressKey(seedHex: string): Uint8Array;
30
- /** Decrypted content of a kind 20800 location beacon event. */
30
+ /** Decrypted content of an encrypted location beacon (kind 20078 signal). */
31
31
  export interface BeaconPayload {
32
32
  geohash: string;
33
33
  precision: number;
@@ -56,7 +56,7 @@ export declare function encryptBeacon(key: Uint8Array, geohash: string, precisio
56
56
  export declare function decryptBeacon(key: Uint8Array, content: string): Promise<BeaconPayload>;
57
57
  /** Duress propagation scope. */
58
58
  export type DuressScope = 'group' | 'persona' | 'master';
59
- /** Decrypted content of a duress alert beacon (kind 20800, AES-GCM encrypted). */
59
+ /** Decrypted content of a duress alert beacon (kind 20078 signal, AES-GCM encrypted). */
60
60
  export interface DuressAlert {
61
61
  type: 'duress';
62
62
  member: string;
@@ -1 +1 @@
1
- {"version":3,"file":"beacon.d.ts","sourceRoot":"","sources":["../src/beacon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AA0BH;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAG3D;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAG3D;AA2CD,+DAA+D;AAC/D,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,CAAC,CAmBxB;AAMD,gCAAgC;AAChC,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAA;AAExD,kFAAkF;AAClF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAA;IAC9C,SAAS,EAAE,MAAM,CAAA;IACjB,oGAAoG;IACpG,KAAK,EAAE,WAAW,CAAA;IAClB,qGAAqG;IACrG,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,0EAA0E;AAC1E,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,QAAQ,GAAG,UAAU,CAAA;CACtC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,cAAc,GAAG,IAAI,EAC/B,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,WAAW,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GACxD,WAAW,CAmCb;AAED;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,WAAW,CAAC,CA2CtB"}
1
+ {"version":3,"file":"beacon.d.ts","sourceRoot":"","sources":["../src/beacon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AA0BH;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAG3D;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAG3D;AA2CD,6EAA6E;AAC7E,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,CAAC,CAmBxB;AAMD,gCAAgC;AAChC,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAA;AAExD,yFAAyF;AACzF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAA;IAC9C,SAAS,EAAE,MAAM,CAAA;IACjB,oGAAoG;IACpG,KAAK,EAAE,WAAW,CAAA;IAClB,qGAAqG;IACrG,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,0EAA0E;AAC1E,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,QAAQ,GAAG,UAAU,CAAA;CACtC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,cAAc,GAAG,IAAI,EAC/B,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,WAAW,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GACxD,WAAW,CAmCb;AAED;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,WAAW,CAAC,CA2CtB"}
package/llms-full.txt CHANGED
@@ -38,7 +38,7 @@ import { deriveToken, deriveDuressToken, verifyToken, deriveLivenessToken, deriv
38
38
  import { encodeAsWords, encodeAsPin, encodeAsHex, encodeToken } from 'canary-kit/encoding'
39
39
  import { createSession, generateSeed, deriveSeed, SESSION_PRESETS } from 'canary-kit/session'
40
40
  import { WORDLIST, WORDLIST_SIZE, getWord, indexOf } from 'canary-kit/wordlist'
41
- import { KINDS, buildGroupEvent, buildSeedDistributionEvent, buildMemberUpdateEvent, buildReseedEvent, buildWordUsedEvent, buildBeaconEvent } from 'canary-kit/nostr'
41
+ import { KINDS, buildGroupStateEvent, buildStoredSignalEvent, buildSignalEvent, buildRumourEvent, hashGroupId } from 'canary-kit/nostr'
42
42
  import { deriveBeaconKey, encryptBeacon, decryptBeacon, buildDuressAlert, encryptDuressAlert, decryptDuressAlert } from 'canary-kit/beacon'
43
43
  import { applySyncMessage, decodeSyncMessage, encodeSyncMessage, deriveGroupKey, deriveGroupSigningKey, hashGroupTag, encryptEnvelope, decryptEnvelope } from 'canary-kit/sync'
44
44
  ```
@@ -177,7 +177,7 @@ interface Session {
177
177
 
178
178
  // ── Beacon ────────────────────────────────────────────────────────────────────
179
179
 
180
- /** Decrypted content of a kind 20800 location beacon event. */
180
+ /** Decrypted content of an encrypted location beacon (kind 20078 signal). */
181
181
  interface BeaconPayload {
182
182
  geohash: string
183
183
  precision: number
@@ -1007,117 +1007,126 @@ WORDLIST[2047] // 'zoo'
1007
1007
 
1008
1008
  ## canary-kit/nostr
1009
1009
 
1010
- Nostr event builders for the CANARY NIP. All builders return unsigned events (`UnsignedEvent`) — sign with your own Nostr library (e.g. `nostr-tools`).
1010
+ Nostr event builders for SSG (Simple Shared Secret) groups. Uses standard Nostr kinds — no custom event kinds. All builders return unsigned events (`UnsignedEvent`) — sign with your own Nostr library (e.g. `nostr-tools`).
1011
+
1012
+ Three event kinds are used:
1013
+ - **kind 30078** (parameterised replaceable) — group state and stored signals. The d-tag uses the `ssg/` namespace prefix.
1014
+ - **kind 20078** (ephemeral) — real-time signals between group members.
1015
+ - **kind 14** (rumour / unsigned inner event) — NIP-17 gift-wrapped DMs carrying seed distribution, reseed, and other private payloads.
1011
1016
 
1012
1017
  ### KINDS
1013
1018
 
1014
1019
  ```typescript
1015
1020
  const KINDS = {
1016
- group: 38_800, // replaceable — group announcement
1017
- seedDistribution: 28_800, // ephemeral — seed delivery to member
1018
- memberUpdate: 38_801, // replaceable add/remove member
1019
- reseed: 28_801, // ephemeral — new seed broadcast
1020
- wordUsed: 28_802, // ephemeral — burn-after-use notification
1021
- beacon: 20_800, // ephemeral — encrypted location beacon
1021
+ groupState: 30_078, // parameterised replaceable — group state and stored signals
1022
+ signal: 20_078, // ephemeral — real-time signals
1023
+ giftWrap: 1_059, // NIP-17 gift wrap (consumer wraps kind 14 rumours)
1022
1024
  } as const
1023
1025
  ```
1024
1026
 
1025
- ### buildGroupEvent(params)
1027
+ ### buildGroupStateEvent(params)
1026
1028
 
1027
- Build a kind 38800 group announcement event.
1029
+ Build an unsigned kind 30078 group state event. The d-tag uses plaintext `ssg/<groupId>` since content is NIP-44 encrypted. Includes NIP-32 labels for the SSG namespace and p-tags for each member.
1028
1030
 
1029
1031
  ```typescript
1030
- buildGroupEvent(params: GroupEventParams): UnsignedEvent
1032
+ buildGroupStateEvent(params: GroupStateEventParams): UnsignedEvent
1031
1033
 
1032
- buildGroupEvent({
1034
+ interface GroupStateEventParams {
1035
+ groupId: string
1036
+ members: string[] // hex pubkeys
1037
+ encryptedContent: string // NIP-44 encrypted group config
1038
+ rotationInterval?: number // seconds between word rotations
1039
+ tolerance?: number // counter tolerance window
1040
+ expiration?: number // NIP-40 expiration (unix seconds)
1041
+ }
1042
+
1043
+ buildGroupStateEvent({
1033
1044
  groupId: 'alpine-team-1',
1034
- name: 'Alpine Team',
1035
1045
  members: ['<alice-pubkey>', '<bob-pubkey>'],
1046
+ encryptedContent: '<NIP-44 encrypted group config>',
1036
1047
  rotationInterval: 86_400,
1037
- wordCount: 2,
1038
- wordlist: 'en-v1',
1039
- encryptedContent: '<NIP-44 encrypted group metadata>',
1040
- expiration: Math.floor(Date.now() / 1000) + 30 * 86_400, // 30 days
1048
+ tolerance: 1,
1049
+ expiration: Math.floor(Date.now() / 1000) + 30 * 86_400,
1041
1050
  })
1042
- // { kind: 38800, content: '...', tags: [['d','alpine-team-1'],['name','Alpine Team'],['p','<alice>'],['p','<bob>'],['rotation','86400'],['words','2'],['wordlist','en-v1'],['expiration','...']], created_at: ... }
1051
+ // { kind: 30078, content: '...', tags: [['d','ssg/alpine-team-1'],['p','<alice>'],['p','<bob>'],['L','ssg'],['l','group','ssg'],['rotation','86400'],['tolerance','1'],['expiration','...']], created_at: ... }
1043
1052
  ```
1044
1053
 
1045
- ### buildSeedDistributionEvent(params)
1054
+ ### buildStoredSignalEvent(params)
1046
1055
 
1047
- Build a kind 28800 seed distribution event, sent privately to each member.
1056
+ Build an unsigned kind 30078 stored signal event. The d-tag uses a SHA-256 hash of the group ID (for privacy) scoped by signal type: `ssg/<SHA256(groupId)>:<signalType>`. Includes a 7-day NIP-40 expiration tag.
1048
1057
 
1049
1058
  ```typescript
1050
- buildSeedDistributionEvent(params: SeedDistributionParams): UnsignedEvent
1051
-
1052
- buildSeedDistributionEvent({
1053
- recipientPubkey: '<alice-pubkey>',
1054
- groupEventId: '<kind-38800-event-id>',
1055
- encryptedContent: '<NIP-44 encrypted seed for alice>',
1056
- })
1057
- // { kind: 28800, content: '...', tags: [['p','<alice>'],['e','<group-id>']], created_at: ... }
1058
- ```
1059
-
1060
- ### buildMemberUpdateEvent(params)
1061
-
1062
- Build a kind 38801 member update event.
1059
+ buildStoredSignalEvent(params: StoredSignalEventParams): UnsignedEvent
1063
1060
 
1064
- ```typescript
1065
- buildMemberUpdateEvent(params: MemberUpdateParams): UnsignedEvent
1061
+ interface StoredSignalEventParams {
1062
+ groupId: string
1063
+ signalType: string // e.g. 'counter-advance', 'word-used'
1064
+ encryptedContent: string
1065
+ }
1066
1066
 
1067
- buildMemberUpdateEvent({
1067
+ buildStoredSignalEvent({
1068
1068
  groupId: 'alpine-team-1',
1069
- action: 'add',
1070
- memberPubkey: '<charlie-pubkey>',
1071
- reseed: true,
1072
- encryptedContent: '<encrypted update content>',
1069
+ signalType: 'counter-advance',
1070
+ encryptedContent: '<encrypted signal payload>',
1073
1071
  })
1074
- // { kind: 38801, ... tags: [['d','alpine-team-1'],['action','add'],['p','<charlie>'],['reseed','true']], ... }
1072
+ // { kind: 30078, content: '...', tags: [['d','ssg/<hash>:counter-advance'],['expiration','...']], created_at: ... }
1075
1073
  ```
1076
1074
 
1077
- ### buildReseedEvent(params)
1075
+ ### buildSignalEvent(params)
1078
1076
 
1079
- Build a kind 28801 reseed event.
1077
+ Build an unsigned kind 20078 ephemeral signal event. The d-tag uses a SHA-256 hash of the group ID (for privacy). A t-tag carries the signal type. No expiration tag — ephemeral events are not stored by relays.
1080
1078
 
1081
1079
  ```typescript
1082
- buildReseedEvent(params: ReseedParams): UnsignedEvent
1080
+ buildSignalEvent(params: SignalEventParams): UnsignedEvent
1083
1081
 
1084
- buildReseedEvent({
1085
- groupEventId: '<kind-38800-event-id>',
1086
- reason: 'member_removed',
1087
- encryptedContent: '<NIP-44 encrypted new seed>',
1082
+ interface SignalEventParams {
1083
+ groupId: string
1084
+ signalType: string // e.g. 'beacon', 'word-used', 'counter-advance'
1085
+ encryptedContent: string
1086
+ }
1087
+
1088
+ buildSignalEvent({
1089
+ groupId: 'alpine-team-1',
1090
+ signalType: 'beacon',
1091
+ encryptedContent: '<AES-256-GCM encrypted beacon payload>',
1088
1092
  })
1089
- // { kind: 28801, ... tags: [['e','<group-id>'],['reason','member_removed']], ... }
1093
+ // { kind: 20078, content: '...', tags: [['d','ssg/<hash>'],['t','beacon']], created_at: ... }
1090
1094
  ```
1091
1095
 
1092
- Valid reason values: `'member_removed'` | `'compromise'` | `'scheduled'` | `'duress'`
1096
+ ### buildRumourEvent(params)
1093
1097
 
1094
- ### buildWordUsedEvent(params)
1098
+ Build an unsigned kind 14 rumour event for NIP-17 gift wrapping. The consumer must set the `pubkey` field before computing the event ID, then seal (NIP-44 encrypt + kind 13) and gift-wrap (kind 1059) the rumour.
1095
1099
 
1096
- Build a kind 28802 word-used (burn-after-use) notification event.
1100
+ Used for seed distribution, reseed notifications, and member updates — anything that must be sent privately to a specific member.
1097
1101
 
1098
1102
  ```typescript
1099
- buildWordUsedEvent(params: WordUsedParams): UnsignedEvent
1103
+ buildRumourEvent(params: RumourEventParams): UnsignedEvent
1104
+
1105
+ interface RumourEventParams {
1106
+ recipientPubkey: string // 64-char hex pubkey
1107
+ subject: string // e.g. 'ssg:seed-distribution', 'ssg:reseed', 'ssg:member-update'
1108
+ encryptedContent: string // NIP-44 encrypted payload
1109
+ groupEventId?: string // optional reference to the group state event
1110
+ }
1100
1111
 
1101
- buildWordUsedEvent({
1102
- groupEventId: '<kind-38800-event-id>',
1103
- encryptedContent: '<encrypted burn notification>',
1112
+ buildRumourEvent({
1113
+ recipientPubkey: '<alice-pubkey>',
1114
+ subject: 'ssg:seed-distribution',
1115
+ encryptedContent: '<NIP-44 encrypted seed>',
1116
+ groupEventId: '<group-state-event-id>',
1104
1117
  })
1105
- // { kind: 28802, ... tags: [['e','<group-id>']], ... }
1118
+ // { kind: 14, content: '...', tags: [['p','<alice>'],['subject','ssg:seed-distribution'],['e','<group-id>']], created_at: ... }
1106
1119
  ```
1107
1120
 
1108
- ### buildBeaconEvent(params)
1121
+ ### hashGroupId(groupId)
1109
1122
 
1110
- Build a kind 20800 encrypted location beacon event.
1123
+ SHA-256 hash a group ID for use in d-tags where privacy matters.
1111
1124
 
1112
1125
  ```typescript
1113
- buildBeaconEvent(params: BeaconEventParams): UnsignedEvent
1126
+ hashGroupId(groupId: string): string
1114
1127
 
1115
- buildBeaconEvent({
1116
- groupId: 'alpine-team-1',
1117
- encryptedContent: '<AES-256-GCM encrypted beacon payload>',
1118
- expiration: Math.floor(Date.now() / 1000) + 3600, // 1 hour
1119
- })
1120
- // { kind: 20800, ... tags: [['h','alpine-team-1'],['expiration','...']], ... }
1128
+ hashGroupId('alpine-team-1')
1129
+ // '3a7f...' (64-char hex)
1121
1130
  ```
1122
1131
 
1123
1132
  ---
@@ -1223,16 +1232,15 @@ Used with `createSession({ preset: '...' })`.
1223
1232
 
1224
1233
  ## Nostr Event Kinds
1225
1234
 
1226
- | Kind | Type | Name | Description |
1235
+ Uses standard Nostr kinds no custom event kinds.
1236
+
1237
+ | Kind | Type | KINDS key | Description |
1227
1238
  |---|---|---|---|
1228
- | 38800 | Replaceable | `group` | Group announcement with member list and configuration |
1229
- | 28800 | Ephemeral | `seedDistribution` | Encrypted seed delivery to a specific member |
1230
- | 38801 | Replaceable | `memberUpdate` | Add or remove a group member (with optional reseed) |
1231
- | 28801 | Ephemeral | `reseed` | New seed broadcast after compromise or member removal |
1232
- | 28802 | Ephemeral | `wordUsed` | Burn-after-use notification — advance counter for all members |
1233
- | 20800 | Ephemeral | `beacon` | AES-256-GCM encrypted location beacon |
1239
+ | 30078 | Parameterised replaceable | `groupState` | Group state (d-tag `ssg/<groupId>`) and stored signals (d-tag `ssg/<hash>:<signalType>`) |
1240
+ | 20078 | Ephemeral | `signal` | Real-time signals between group members (beacon, word-used, counter-advance) |
1241
+ | 14 → 1059 | NIP-17 gift wrap | `giftWrap` | Seed distribution, reseed, member updates (kind 14 rumour sealed + wrapped) |
1234
1242
 
1235
- Replaceable events (38xxx) use the `d` tag for addressability. Ephemeral events (28xxx, 20xxx) are not stored by relays.
1243
+ Kind 30078 events use the `ssg/` d-tag namespace prefix. Kind 20078 events hash the group ID in the d-tag for privacy. Kind 14 rumours are sealed (kind 13) and gift-wrapped (kind 1059) by the consumer — the library builds the unsigned rumour only.
1236
1244
 
1237
1245
  ---
1238
1246
 
@@ -1350,15 +1358,15 @@ state = removeMember(state, '<charlie-pubkey>')
1350
1358
 
1351
1359
  ### Nostr Group Integration
1352
1360
 
1353
- Publish group state and encrypted seeds to Nostr relays so all members can synchronise.
1361
+ Publish group state and encrypted seeds to Nostr relays so all members can synchronise. Uses standard Nostr kinds (30078, 20078, NIP-17 gift wrap) — no custom event kinds.
1354
1362
 
1355
1363
  ```typescript
1356
- import { createGroup, syncCounter, deriveBeaconKey, encryptBeacon } from 'canary-kit'
1364
+ import { createGroup, deriveBeaconKey, encryptBeacon } from 'canary-kit'
1357
1365
  import {
1358
1366
  KINDS,
1359
- buildGroupEvent,
1360
- buildSeedDistributionEvent,
1361
- buildBeaconEvent,
1367
+ buildGroupStateEvent,
1368
+ buildSignalEvent,
1369
+ buildRumourEvent,
1362
1370
  } from 'canary-kit/nostr'
1363
1371
 
1364
1372
  // 1. Create the group
@@ -1368,37 +1376,34 @@ let state = createGroup({
1368
1376
  preset: 'field-ops',
1369
1377
  })
1370
1378
 
1371
- // 2. Publish a kind 38800 group event (encrypted content via NIP-44)
1372
- const groupEvent = buildGroupEvent({
1379
+ // 2. Publish a kind 30078 group state event (encrypted content via NIP-44)
1380
+ const groupEvent = buildGroupStateEvent({
1373
1381
  groupId: 'alpine-team-1',
1374
- name: state.name,
1375
1382
  members: state.members,
1376
- rotationInterval: state.rotationInterval,
1377
- wordCount: state.wordCount,
1378
- wordlist: state.wordlist,
1379
1383
  encryptedContent: '<NIP-44 encrypted group config>',
1380
- expiration: Math.floor(Date.now() / 1000) + 30 * 86_400,
1384
+ rotationInterval: state.rotationInterval,
1381
1385
  })
1382
1386
  // sign and publish groupEvent to relays
1383
1387
 
1384
- // 3. Send seed to each member via kind 28800 (one event per member)
1388
+ // 3. Send seed to each member via NIP-17 gift wrap (kind 14 rumour → seal → wrap)
1385
1389
  for (const memberPubkey of state.members) {
1386
- const seedEvent = buildSeedDistributionEvent({
1390
+ const rumour = buildRumourEvent({
1387
1391
  recipientPubkey: memberPubkey,
1388
- groupEventId: '<signed-group-event-id>',
1392
+ subject: 'ssg:seed-distribution',
1389
1393
  encryptedContent: '<NIP-44 encrypted seed for this member>',
1394
+ groupEventId: '<signed-group-event-id>',
1390
1395
  })
1391
- // sign and publish seedEvent
1396
+ // set pubkey, compute id, then seal (kind 13) and gift-wrap (kind 1059)
1392
1397
  }
1393
1398
 
1394
- // 4. Publish periodic location beacons (kind 20800)
1399
+ // 4. Publish periodic location beacons as kind 20078 ephemeral signals
1395
1400
  const beaconKey = deriveBeaconKey(state.seed)
1396
1401
  const encryptedBeacon = await encryptBeacon(beaconKey, 'gcpvjb', 6)
1397
1402
 
1398
- const beaconEvent = buildBeaconEvent({
1403
+ const beaconEvent = buildSignalEvent({
1399
1404
  groupId: 'alpine-team-1',
1405
+ signalType: 'beacon',
1400
1406
  encryptedContent: encryptedBeacon,
1401
- expiration: Math.floor(Date.now() / 1000) + state.beaconInterval * 2,
1402
1407
  })
1403
1408
  // sign and publish beaconEvent
1404
1409
 
package/llms.txt CHANGED
@@ -100,19 +100,18 @@ Session methods:
100
100
 
101
101
  ### canary-kit/nostr
102
102
 
103
- Unsigned Nostr event builders for the CANARY protocol (NIP-CANARY). Sign events with your preferred Nostr library.
103
+ Unsigned Nostr event builders for SSG (Simple Shared Secret) groups. Uses standard Nostr kinds — no custom event kinds. Sign events with your preferred Nostr library.
104
104
 
105
- - KINDS — { group: 38800, seedDistribution: 28800, memberUpdate: 38801, reseed: 28801, wordUsed: 28802, beacon: 20800 }
106
- - buildGroupEvent(params) → UnsignedEvent
107
- - buildSeedDistributionEvent(params) → UnsignedEvent
108
- - buildMemberUpdateEvent(params) → UnsignedEvent
109
- - buildReseedEvent(params) → UnsignedEvent
110
- - buildWordUsedEvent(params) → UnsignedEvent
111
- - buildBeaconEvent(params) → UnsignedEvent
105
+ - KINDS — { groupState: 30078, signal: 20078, giftWrap: 1059 }
106
+ - buildGroupStateEvent(params) → UnsignedEvent — kind 30078, d-tag `ssg/<groupId>`, p-tags for members, NIP-32 labels
107
+ - buildStoredSignalEvent(params) → UnsignedEvent — kind 30078, d-tag `ssg/<SHA256(groupId)>:<signalType>`, 7-day expiration
108
+ - buildSignalEvent(params) → UnsignedEvent — kind 20078 ephemeral, d-tag `ssg/<SHA256(groupId)>`, t-tag for signal type
109
+ - buildRumourEvent(params) → UnsignedEvent — kind 14 rumour for NIP-17 gift wrapping (seed distribution, reseed, member updates)
110
+ - hashGroupId(groupId) → string — SHA-256 hash a group ID for privacy-preserving d-tags
112
111
 
113
112
  ### canary-kit/beacon
114
113
 
115
- AES-256-GCM encrypted location beacons and duress alerts for Nostr kind 20800 events.
114
+ AES-256-GCM encrypted location beacons and duress alerts. Beacons are published as kind 20078 ephemeral signal events.
116
115
 
117
116
  - deriveBeaconKey(seedHex) → Uint8Array
118
117
  - encryptBeacon(key, geohash, precision) → Promise\<string\>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canary-kit",
3
- "version": "2.6.2",
3
+ "version": "2.7.0",
4
4
  "description": "Deepfake-proof identity verification. Open protocol, minimal dependencies.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -143,5 +143,9 @@
143
143
  "@scure/bip39": "^2.0.1",
144
144
  "nsec-tree": "^1.4.0",
145
145
  "spoken-token": "^2.0.2"
146
+ },
147
+ "funding": {
148
+ "type": "lightning",
149
+ "url": "lightning:thedonkey@strike.me"
146
150
  }
147
151
  }