canary-kit 2.6.1 → 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/LICENSE +1 -1
- package/README.md +9 -10
- package/dist/beacon.d.ts +2 -2
- package/dist/beacon.d.ts.map +1 -1
- package/llms-full.txt +99 -94
- package/llms.txt +8 -9
- package/package.json +5 -1
package/LICENSE
CHANGED
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
|
[](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 {
|
|
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` | `
|
|
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
|
|
189
|
-
|
|
|
190
|
-
|
|
|
191
|
-
|
|
192
|
-
|
|
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
|
|
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
|
|
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;
|
package/dist/beacon.d.ts.map
CHANGED
|
@@ -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
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
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
|
-
###
|
|
1027
|
+
### buildGroupStateEvent(params)
|
|
1026
1028
|
|
|
1027
|
-
Build
|
|
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
|
-
|
|
1032
|
+
buildGroupStateEvent(params: GroupStateEventParams): UnsignedEvent
|
|
1031
1033
|
|
|
1032
|
-
|
|
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
|
-
|
|
1038
|
-
|
|
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:
|
|
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
|
-
###
|
|
1054
|
+
### buildStoredSignalEvent(params)
|
|
1046
1055
|
|
|
1047
|
-
Build
|
|
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
|
-
|
|
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
|
-
|
|
1065
|
-
|
|
1061
|
+
interface StoredSignalEventParams {
|
|
1062
|
+
groupId: string
|
|
1063
|
+
signalType: string // e.g. 'counter-advance', 'word-used'
|
|
1064
|
+
encryptedContent: string
|
|
1065
|
+
}
|
|
1066
1066
|
|
|
1067
|
-
|
|
1067
|
+
buildStoredSignalEvent({
|
|
1068
1068
|
groupId: 'alpine-team-1',
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
reseed: true,
|
|
1072
|
-
encryptedContent: '<encrypted update content>',
|
|
1069
|
+
signalType: 'counter-advance',
|
|
1070
|
+
encryptedContent: '<encrypted signal payload>',
|
|
1073
1071
|
})
|
|
1074
|
-
// { kind:
|
|
1072
|
+
// { kind: 30078, content: '...', tags: [['d','ssg/<hash>:counter-advance'],['expiration','...']], created_at: ... }
|
|
1075
1073
|
```
|
|
1076
1074
|
|
|
1077
|
-
###
|
|
1075
|
+
### buildSignalEvent(params)
|
|
1078
1076
|
|
|
1079
|
-
Build
|
|
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
|
-
|
|
1080
|
+
buildSignalEvent(params: SignalEventParams): UnsignedEvent
|
|
1083
1081
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
encryptedContent:
|
|
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:
|
|
1093
|
+
// { kind: 20078, content: '...', tags: [['d','ssg/<hash>'],['t','beacon']], created_at: ... }
|
|
1090
1094
|
```
|
|
1091
1095
|
|
|
1092
|
-
|
|
1096
|
+
### buildRumourEvent(params)
|
|
1093
1097
|
|
|
1094
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
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:
|
|
1118
|
+
// { kind: 14, content: '...', tags: [['p','<alice>'],['subject','ssg:seed-distribution'],['e','<group-id>']], created_at: ... }
|
|
1106
1119
|
```
|
|
1107
1120
|
|
|
1108
|
-
###
|
|
1121
|
+
### hashGroupId(groupId)
|
|
1109
1122
|
|
|
1110
|
-
|
|
1123
|
+
SHA-256 hash a group ID for use in d-tags where privacy matters.
|
|
1111
1124
|
|
|
1112
1125
|
```typescript
|
|
1113
|
-
|
|
1126
|
+
hashGroupId(groupId: string): string
|
|
1114
1127
|
|
|
1115
|
-
|
|
1116
|
-
|
|
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
|
-
|
|
1235
|
+
Uses standard Nostr kinds — no custom event kinds.
|
|
1236
|
+
|
|
1237
|
+
| Kind | Type | KINDS key | Description |
|
|
1227
1238
|
|---|---|---|---|
|
|
1228
|
-
|
|
|
1229
|
-
|
|
|
1230
|
-
|
|
|
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
|
-
|
|
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,
|
|
1364
|
+
import { createGroup, deriveBeaconKey, encryptBeacon } from 'canary-kit'
|
|
1357
1365
|
import {
|
|
1358
1366
|
KINDS,
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
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
|
|
1372
|
-
const groupEvent =
|
|
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
|
-
|
|
1384
|
+
rotationInterval: state.rotationInterval,
|
|
1381
1385
|
})
|
|
1382
1386
|
// sign and publish groupEvent to relays
|
|
1383
1387
|
|
|
1384
|
-
// 3. Send seed to each member via
|
|
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
|
|
1390
|
+
const rumour = buildRumourEvent({
|
|
1387
1391
|
recipientPubkey: memberPubkey,
|
|
1388
|
-
|
|
1392
|
+
subject: 'ssg:seed-distribution',
|
|
1389
1393
|
encryptedContent: '<NIP-44 encrypted seed for this member>',
|
|
1394
|
+
groupEventId: '<signed-group-event-id>',
|
|
1390
1395
|
})
|
|
1391
|
-
//
|
|
1396
|
+
// set pubkey, compute id, then seal (kind 13) and gift-wrap (kind 1059)
|
|
1392
1397
|
}
|
|
1393
1398
|
|
|
1394
|
-
// 4. Publish periodic location beacons
|
|
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 =
|
|
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
|
|
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 — {
|
|
106
|
-
-
|
|
107
|
-
-
|
|
108
|
-
-
|
|
109
|
-
-
|
|
110
|
-
-
|
|
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
|
|
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.
|
|
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
|
}
|