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.
- package/CANARY.md +1065 -0
- package/INTEGRATION.md +351 -0
- package/LICENSE +21 -0
- package/NIP-CANARY.md +624 -0
- package/README.md +187 -0
- package/SECURITY.md +92 -0
- package/dist/beacon.d.ts +104 -0
- package/dist/beacon.d.ts.map +1 -0
- package/dist/beacon.js +197 -0
- package/dist/beacon.js.map +1 -0
- package/dist/counter.d.ts +37 -0
- package/dist/counter.d.ts.map +1 -0
- package/dist/counter.js +62 -0
- package/dist/counter.js.map +1 -0
- package/dist/crypto.d.ts +111 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +309 -0
- package/dist/crypto.js.map +1 -0
- package/dist/derive.d.ts +68 -0
- package/dist/derive.d.ts.map +1 -0
- package/dist/derive.js +85 -0
- package/dist/derive.js.map +1 -0
- package/dist/encoding.d.ts +56 -0
- package/dist/encoding.d.ts.map +1 -0
- package/dist/encoding.js +98 -0
- package/dist/encoding.js.map +1 -0
- package/dist/group.d.ts +185 -0
- package/dist/group.d.ts.map +1 -0
- package/dist/group.js +263 -0
- package/dist/group.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/nostr.d.ts +134 -0
- package/dist/nostr.d.ts.map +1 -0
- package/dist/nostr.js +175 -0
- package/dist/nostr.js.map +1 -0
- package/dist/presets.d.ts +26 -0
- package/dist/presets.d.ts.map +1 -0
- package/dist/presets.js +39 -0
- package/dist/presets.js.map +1 -0
- package/dist/session.d.ts +114 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +173 -0
- package/dist/session.js.map +1 -0
- package/dist/sync-crypto.d.ts +66 -0
- package/dist/sync-crypto.d.ts.map +1 -0
- package/dist/sync-crypto.js +125 -0
- package/dist/sync-crypto.js.map +1 -0
- package/dist/sync.d.ts +191 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +568 -0
- package/dist/sync.js.map +1 -0
- package/dist/token.d.ts +186 -0
- package/dist/token.d.ts.map +1 -0
- package/dist/token.js +344 -0
- package/dist/token.js.map +1 -0
- package/dist/verify.d.ts +45 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +59 -0
- package/dist/verify.js.map +1 -0
- package/dist/wordlist.d.ts +28 -0
- package/dist/wordlist.d.ts.map +1 -0
- package/dist/wordlist.js +297 -0
- package/dist/wordlist.js.map +1 -0
- package/llms-full.txt +1461 -0
- package/llms.txt +180 -0
- package/package.json +144 -0
package/llms.txt
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# canary-kit
|
|
2
|
+
|
|
3
|
+
> Spoken secrets. Silent alerts. Dead man's switch. canary-kit is a minimal-dependency TypeScript library for deepfake-proof identity verification — spoken-word tokens derived from shared secrets, with built-in duress signalling, liveness heartbeats, and encrypted location beacons. Open protocol.
|
|
4
|
+
|
|
5
|
+
Interactive demo: https://thecryptodonkey.github.io/canary-kit/
|
|
6
|
+
GitHub: https://github.com/TheCryptoDonkey/canary-kit
|
|
7
|
+
|
|
8
|
+
ESM-only. Eight subpath exports: `canary-kit` (main), `canary-kit/token`, `canary-kit/encoding`, `canary-kit/session`, `canary-kit/wordlist`, `canary-kit/nostr`, `canary-kit/beacon`, `canary-kit/sync`.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
npm install canary-kit
|
|
13
|
+
|
|
14
|
+
## Subpath Exports
|
|
15
|
+
|
|
16
|
+
### canary-kit (main entry)
|
|
17
|
+
|
|
18
|
+
Re-exports the full API. Includes group lifecycle, presets, and all protocol primitives.
|
|
19
|
+
|
|
20
|
+
Core derivation:
|
|
21
|
+
- deriveVerificationWord(seedHex, counter) → string
|
|
22
|
+
- deriveVerificationPhrase(seedHex, counter, wordCount) → string[]
|
|
23
|
+
- deriveDuressWord(seedHex, memberPubkeyHex, counter) → string
|
|
24
|
+
- deriveDuressPhrase(seedHex, memberPubkeyHex, counter, wordCount) → string[]
|
|
25
|
+
- verifyWord(spokenWord, seedHex, memberPubkeys, counter, wordCount?) → VerifyResult
|
|
26
|
+
|
|
27
|
+
Group lifecycle:
|
|
28
|
+
- createGroup(config) → GroupState
|
|
29
|
+
- getCurrentWord(state) → string
|
|
30
|
+
- getCurrentDuressWord(state, memberPubkey) → string
|
|
31
|
+
- advanceCounter(state) → GroupState
|
|
32
|
+
- reseed(state) → GroupState
|
|
33
|
+
- addMember(state, pubkey) → GroupState
|
|
34
|
+
- removeMember(state, pubkey) → GroupState
|
|
35
|
+
- syncCounter(state, nowSec?) → GroupState
|
|
36
|
+
|
|
37
|
+
Counter:
|
|
38
|
+
- getCounter(timestampSec, rotationIntervalSec?) → number
|
|
39
|
+
- counterToBytes(counter) → Uint8Array
|
|
40
|
+
- DEFAULT_ROTATION_INTERVAL — 604800 (7 days)
|
|
41
|
+
|
|
42
|
+
Presets:
|
|
43
|
+
- PRESETS — { family, field-ops, enterprise } (wordCount, rotationInterval, description)
|
|
44
|
+
- PresetName — 'family' | 'field-ops' | 'enterprise'
|
|
45
|
+
|
|
46
|
+
Also re-exports all of: canary-kit/token, canary-kit/encoding, canary-kit/beacon, canary-kit/wordlist
|
|
47
|
+
|
|
48
|
+
### canary-kit/token
|
|
49
|
+
|
|
50
|
+
Universal CANARY protocol — context-string-based derivation. Use this to build custom integrations outside the group model.
|
|
51
|
+
|
|
52
|
+
- deriveTokenBytes(secret, context, counter) → Uint8Array
|
|
53
|
+
- deriveToken(secret, context, counter, encoding?) → string
|
|
54
|
+
- deriveDuressTokenBytes(secret, context, identity, counter) → Uint8Array
|
|
55
|
+
- MAX_TOLERANCE — 10 (upper bound for tolerance/maxTolerance)
|
|
56
|
+
- deriveDuressToken(secret, context, identity, counter, encoding, maxTolerance) → string (both required — maxTolerance must match verifier's tolerance)
|
|
57
|
+
- verifyToken(secret, context, counter, input, identities, options?) → TokenVerifyResult
|
|
58
|
+
- deriveLivenessToken(secret, context, identity, counter) → Uint8Array
|
|
59
|
+
- deriveDirectionalPair(secret, namespace, roles, counter, encoding?) → { [role]: string }
|
|
60
|
+
|
|
61
|
+
Types: TokenVerifyResult ({ status: 'valid' | 'duress' | 'invalid', identities? }), VerifyOptions ({ encoding?, tolerance? })
|
|
62
|
+
|
|
63
|
+
### canary-kit/encoding
|
|
64
|
+
|
|
65
|
+
Encode raw HMAC bytes into human-readable formats.
|
|
66
|
+
|
|
67
|
+
- encodeAsWords(bytes, count?, wordlist?) → string[]
|
|
68
|
+
- encodeAsPin(bytes, digits?) → string
|
|
69
|
+
- encodeAsHex(bytes, length?) → string
|
|
70
|
+
- encodeToken(bytes, encoding?) → string
|
|
71
|
+
- DEFAULT_ENCODING — { format: 'words', count: 1 }
|
|
72
|
+
|
|
73
|
+
Type: TokenEncoding — { format: 'words', count?, wordlist? } | { format: 'pin', digits? } | { format: 'hex', length? }
|
|
74
|
+
|
|
75
|
+
### canary-kit/session
|
|
76
|
+
|
|
77
|
+
Role-aware, time-managed two-party verification sessions. Ideal for phone calls and physical handoffs.
|
|
78
|
+
|
|
79
|
+
- createSession(config) → Session
|
|
80
|
+
- generateSeed() → Uint8Array
|
|
81
|
+
- deriveSeed(masterKey, ...components) → Uint8Array
|
|
82
|
+
- SESSION_PRESETS — { call, handoff } (wordCount, rotationSeconds, tolerance, directional)
|
|
83
|
+
- SessionPresetName — 'call' | 'handoff'
|
|
84
|
+
|
|
85
|
+
Session methods:
|
|
86
|
+
- session.counter(nowSec?) → number
|
|
87
|
+
- session.myToken(nowSec?) → string
|
|
88
|
+
- session.theirToken(nowSec?) → string
|
|
89
|
+
- session.verify(spoken, nowSec?) → TokenVerifyResult
|
|
90
|
+
- session.pair(nowSec?) → { [role]: string }
|
|
91
|
+
|
|
92
|
+
### canary-kit/wordlist
|
|
93
|
+
|
|
94
|
+
2048-word spoken-clarity wordlist (en-v1). Based on BIP-39 English, filtered for verbal clarity — no homophones, no phonetic near-collisions.
|
|
95
|
+
|
|
96
|
+
- WORDLIST — readonly string[] (2048 entries)
|
|
97
|
+
- WORDLIST_SIZE — 2048
|
|
98
|
+
- getWord(index) → string
|
|
99
|
+
- indexOf(word) → number (-1 if not found)
|
|
100
|
+
|
|
101
|
+
### canary-kit/nostr
|
|
102
|
+
|
|
103
|
+
Unsigned Nostr event builders for the CANARY protocol (NIP-CANARY). Sign events with your preferred Nostr library.
|
|
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
|
|
112
|
+
|
|
113
|
+
### canary-kit/beacon
|
|
114
|
+
|
|
115
|
+
AES-256-GCM encrypted location beacons and duress alerts for Nostr kind 20800 events.
|
|
116
|
+
|
|
117
|
+
- deriveBeaconKey(seedHex) → Uint8Array
|
|
118
|
+
- encryptBeacon(key, geohash, precision) → Promise\<string\>
|
|
119
|
+
- decryptBeacon(key, content) → Promise\<BeaconPayload\>
|
|
120
|
+
- buildDuressAlert(memberPubkey, location) → DuressAlert
|
|
121
|
+
- encryptDuressAlert(key, alert) → Promise\<string\>
|
|
122
|
+
- decryptDuressAlert(key, content) → Promise\<DuressAlert\>
|
|
123
|
+
|
|
124
|
+
Types: BeaconPayload ({ geohash, precision, timestamp }), DuressAlert ({ type, member, geohash, precision, locationSource, timestamp }), DuressLocation ({ geohash, precision, locationSource })
|
|
125
|
+
|
|
126
|
+
### canary-kit/sync
|
|
127
|
+
|
|
128
|
+
Transport-agnostic group state synchronisation. Authority model with 6 invariants, replay protection, epoch boundaries.
|
|
129
|
+
|
|
130
|
+
- decodeSyncMessage(json) → SyncMessage (validates and parses)
|
|
131
|
+
- encodeSyncMessage(msg) → string (serialises to JSON)
|
|
132
|
+
- applySyncMessage(state, msg, sender?) → SyncResult ({ state, applied, reason? })
|
|
133
|
+
- deriveGroupKey(seed) → Uint8Array (AES-256-GCM group key)
|
|
134
|
+
- deriveGroupSigningKey(seed) → Uint8Array (HMAC signing key)
|
|
135
|
+
- hashGroupTag(groupId) → string (privacy-preserving group tag)
|
|
136
|
+
- encryptEnvelope(key, plaintext) → Promise<string> (AES-256-GCM)
|
|
137
|
+
- decryptEnvelope(key, ciphertext) → Promise<string> (AES-256-GCM)
|
|
138
|
+
|
|
139
|
+
Message types: member-join, member-leave, counter-advance, reseed, beacon, duress-alert, liveness-checkin, state-snapshot
|
|
140
|
+
|
|
141
|
+
## Quick Examples
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// --- Session-based phone verification (bank/insurance call centre) ---
|
|
145
|
+
import { createSession } from 'canary-kit/session'
|
|
146
|
+
|
|
147
|
+
const session = createSession({
|
|
148
|
+
secret: sharedSecretHex,
|
|
149
|
+
namespace: 'aviva',
|
|
150
|
+
roles: ['caller', 'agent'],
|
|
151
|
+
myRole: 'agent',
|
|
152
|
+
preset: 'call', // 30-second rotation, tolerance 1, directional
|
|
153
|
+
theirIdentity: customerId, // enables duress detection
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
// The caller speaks session.theirToken() — the agent hears and verifies:
|
|
157
|
+
const result = session.verify(spokenWord)
|
|
158
|
+
// result.status === 'valid' | 'duress' | 'invalid'
|
|
159
|
+
// result.identities → who signalled duress (if any)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// --- Group-based family verification (weekly rotating passphrase) ---
|
|
164
|
+
import { createGroup, getCurrentWord, getCurrentDuressWord, verifyWord, getCounter } from 'canary-kit'
|
|
165
|
+
|
|
166
|
+
const group = createGroup({ preset: 'family', members: [alicePubkey, bobPubkey] })
|
|
167
|
+
const word = getCurrentWord(group) // e.g. "granite"
|
|
168
|
+
|
|
169
|
+
// On a call, Alice says the current word. Bob verifies:
|
|
170
|
+
const counter = getCounter(Math.floor(Date.now() / 1000), group.rotationInterval)
|
|
171
|
+
const result = verifyWord(spokenWord, group.seed, group.members, counter, group.wordCount)
|
|
172
|
+
// result.status === 'verified' | 'duress' | 'stale' | 'failed'
|
|
173
|
+
|
|
174
|
+
// If Alice is under coercion, she says her duress word instead:
|
|
175
|
+
const duressWord = getCurrentDuressWord(group, alicePubkey) // always distinct from the normal word
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Optional
|
|
179
|
+
|
|
180
|
+
- llms-full.txt: Full API reference with all type signatures, protocol details, and canonical test vectors
|
package/package.json
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "canary-kit",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Deepfake-proof identity verification. Open protocol, minimal dependencies.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=22"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./wordlist": {
|
|
18
|
+
"types": "./dist/wordlist.d.ts",
|
|
19
|
+
"import": "./dist/wordlist.js"
|
|
20
|
+
},
|
|
21
|
+
"./nostr": {
|
|
22
|
+
"types": "./dist/nostr.d.ts",
|
|
23
|
+
"import": "./dist/nostr.js"
|
|
24
|
+
},
|
|
25
|
+
"./beacon": {
|
|
26
|
+
"types": "./dist/beacon.d.ts",
|
|
27
|
+
"import": "./dist/beacon.js"
|
|
28
|
+
},
|
|
29
|
+
"./token": {
|
|
30
|
+
"types": "./dist/token.d.ts",
|
|
31
|
+
"import": "./dist/token.js"
|
|
32
|
+
},
|
|
33
|
+
"./encoding": {
|
|
34
|
+
"types": "./dist/encoding.d.ts",
|
|
35
|
+
"import": "./dist/encoding.js"
|
|
36
|
+
},
|
|
37
|
+
"./session": {
|
|
38
|
+
"types": "./dist/session.d.ts",
|
|
39
|
+
"import": "./dist/session.js"
|
|
40
|
+
},
|
|
41
|
+
"./sync": {
|
|
42
|
+
"types": "./dist/sync.d.ts",
|
|
43
|
+
"import": "./dist/sync.js"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"dist/",
|
|
48
|
+
"LICENSE",
|
|
49
|
+
"CANARY.md",
|
|
50
|
+
"NIP-CANARY.md",
|
|
51
|
+
"INTEGRATION.md",
|
|
52
|
+
"SECURITY.md",
|
|
53
|
+
"llms.txt",
|
|
54
|
+
"llms-full.txt"
|
|
55
|
+
],
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "tsc",
|
|
58
|
+
"dev": "vite --config vite.config.ts",
|
|
59
|
+
"build:app": "vite build --config vite.config.ts",
|
|
60
|
+
"test": "vitest run",
|
|
61
|
+
"test:watch": "vitest",
|
|
62
|
+
"typecheck": "tsc --noEmit",
|
|
63
|
+
"lint": "eslint src/",
|
|
64
|
+
"lint:fix": "eslint src/ --fix",
|
|
65
|
+
"bench": "vitest bench",
|
|
66
|
+
"demo": "npm run build:app && serve docs -l 8787",
|
|
67
|
+
"build:single": "vite build --config vite.singlefile.config.ts && cp dist-single/index.html canary.html && echo '✓ canary.html ready'",
|
|
68
|
+
"test:e2e": "playwright test --config e2e/playwright.config.ts",
|
|
69
|
+
"test:e2e:offline": "playwright test --config e2e/playwright.config.ts --project=offline",
|
|
70
|
+
"test:e2e:online": "playwright test --config e2e/playwright.config.ts --project=online",
|
|
71
|
+
"test:e2e:protocol": "playwright test --config e2e/playwright.config.ts --project=protocol",
|
|
72
|
+
"test:e2e:ui": "playwright test --config e2e/playwright.config.ts --ui",
|
|
73
|
+
"record": "node docs/record/record.js",
|
|
74
|
+
"record:short": "node docs/record/record.js short",
|
|
75
|
+
"record:extended": "node docs/record/record.js extended",
|
|
76
|
+
"record:silent": "node docs/record/record.js --no-voice"
|
|
77
|
+
},
|
|
78
|
+
"keywords": [
|
|
79
|
+
"canary",
|
|
80
|
+
"canary-kit",
|
|
81
|
+
"verification",
|
|
82
|
+
"identity",
|
|
83
|
+
"duress",
|
|
84
|
+
"safe-word",
|
|
85
|
+
"nostr",
|
|
86
|
+
"hmac",
|
|
87
|
+
"totp",
|
|
88
|
+
"deepfake",
|
|
89
|
+
"voice-clone",
|
|
90
|
+
"meshtastic",
|
|
91
|
+
"liveness",
|
|
92
|
+
"dead-mans-switch",
|
|
93
|
+
"coercion-resistance",
|
|
94
|
+
"spoken-word",
|
|
95
|
+
"bidirectional-verification",
|
|
96
|
+
"offline-first",
|
|
97
|
+
"minimal-dependencies",
|
|
98
|
+
"voice-phishing",
|
|
99
|
+
"vishing",
|
|
100
|
+
"anti-fraud",
|
|
101
|
+
"phone-verification",
|
|
102
|
+
"family-safety"
|
|
103
|
+
],
|
|
104
|
+
"author": "TheCryptoDonkey",
|
|
105
|
+
"license": "MIT",
|
|
106
|
+
"repository": {
|
|
107
|
+
"type": "git",
|
|
108
|
+
"url": "https://github.com/TheCryptoDonkey/canary-kit.git"
|
|
109
|
+
},
|
|
110
|
+
"homepage": "https://canary.trotters.cc/",
|
|
111
|
+
"bugs": {
|
|
112
|
+
"url": "https://github.com/TheCryptoDonkey/canary-kit/issues"
|
|
113
|
+
},
|
|
114
|
+
"devDependencies": {
|
|
115
|
+
"@eslint/js": "^10.0.1",
|
|
116
|
+
"@noble/curves": "^2.0.1",
|
|
117
|
+
"@noble/hashes": "^2.0.1",
|
|
118
|
+
"@playwright/test": "^1.58.2",
|
|
119
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
120
|
+
"@semantic-release/git": "^10.0.1",
|
|
121
|
+
"@semantic-release/github": "^11.0.0",
|
|
122
|
+
"@semantic-release/npm": "^13.1.5",
|
|
123
|
+
"@types/node": "^25.3.3",
|
|
124
|
+
"@types/ws": "^8.18.1",
|
|
125
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
126
|
+
"eslint": "^10.0.3",
|
|
127
|
+
"geohash-kit": "^1.3.0",
|
|
128
|
+
"maplibre-gl": "^5.19.0",
|
|
129
|
+
"nostr-tools": "^2.23.3",
|
|
130
|
+
"qrcode-generator": "^2.0.4",
|
|
131
|
+
"semantic-release": "^25.0.0",
|
|
132
|
+
"serve": "^14.2.5",
|
|
133
|
+
"typescript": "^5.7.0",
|
|
134
|
+
"typescript-eslint": "^8.57.0",
|
|
135
|
+
"vite": "^7.3.1",
|
|
136
|
+
"vite-plugin-singlefile": "^2.3.0",
|
|
137
|
+
"vitest": "^3.0.0",
|
|
138
|
+
"ws": "^8.19.0"
|
|
139
|
+
},
|
|
140
|
+
"dependencies": {
|
|
141
|
+
"@scure/bip32": "^2.0.1",
|
|
142
|
+
"@scure/bip39": "^2.0.1"
|
|
143
|
+
}
|
|
144
|
+
}
|