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/INTEGRATION.md
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# CANARY Integration Guide
|
|
2
|
+
|
|
3
|
+
How to integrate CANARY spoken-word verification into your systems.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
CANARY provides bidirectional, deepfake-proof identity verification using shared
|
|
8
|
+
secrets and spoken words. Both parties on a call independently derive the same
|
|
9
|
+
pair of words from a shared seed — one word per role. Cloning a voice does not
|
|
10
|
+
help derive the correct word. Only knowledge of the shared secret does.
|
|
11
|
+
|
|
12
|
+
This guide covers seed establishment patterns and call centre integration for
|
|
13
|
+
insurance, banking, and enterprise use cases.
|
|
14
|
+
|
|
15
|
+
## Seed Establishment Patterns
|
|
16
|
+
|
|
17
|
+
The shared seed is the foundation of CANARY verification. How it gets to both
|
|
18
|
+
parties depends on your infrastructure.
|
|
19
|
+
|
|
20
|
+
### Pattern 1: App-Derived Seed (Primary — Insurance/Banking)
|
|
21
|
+
|
|
22
|
+
The customer authenticates to the insurer's app. The seed is derived server-side
|
|
23
|
+
and synced during login.
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
Customer opens Aviva app
|
|
27
|
+
→ Authenticates (biometrics / password / MFA)
|
|
28
|
+
→ App requests session seed from Aviva backend
|
|
29
|
+
→ Backend: seed = HMAC-SHA256(master_key, customer_id || seed_version)
|
|
30
|
+
→ Seed delivered over TLS to the app
|
|
31
|
+
→ App stores seed in secure storage (Keychain / KeyStore)
|
|
32
|
+
→ Call centre agent's system derives the same seed from the same inputs
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Key properties:
|
|
36
|
+
- **No extra setup step** — seed arrives during normal app login
|
|
37
|
+
- **`seed_version`** allows rotation without re-enrolment
|
|
38
|
+
- **Master key** is HSM-protected server-side
|
|
39
|
+
- **Offline-capable** — the app derives tokens locally after initial sync
|
|
40
|
+
- **Works with existing app infrastructure**
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { deriveSeed, createSession } from 'canary-kit/session'
|
|
44
|
+
|
|
45
|
+
// Server-side: derive seed for a customer
|
|
46
|
+
const seed = deriveSeed(masterKey, customerId, seedVersion.toString())
|
|
47
|
+
|
|
48
|
+
// Both agent and customer derive the same seed independently
|
|
49
|
+
const session = createSession({
|
|
50
|
+
secret: seed,
|
|
51
|
+
namespace: 'aviva',
|
|
52
|
+
roles: ['caller', 'agent'],
|
|
53
|
+
myRole: 'agent',
|
|
54
|
+
preset: 'call',
|
|
55
|
+
theirIdentity: customerId,
|
|
56
|
+
})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Pattern 2: Enrolment QR (Branch / In-Person)
|
|
60
|
+
|
|
61
|
+
For customers without the app, generate a QR code containing the seed:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
Customer visits branch / receives postal letter
|
|
65
|
+
→ Staff generates CANARY enrolment QR code
|
|
66
|
+
→ QR contains: { seed, namespace, roles, rotation }
|
|
67
|
+
→ Customer scans with phone
|
|
68
|
+
→ Seed stored locally
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Pattern 3: SMS/Email OTP Bootstrap (Lower Security)
|
|
72
|
+
|
|
73
|
+
For customers with no app and no branch visit:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
Customer calls insurer
|
|
77
|
+
→ Agent sends one-time setup code via SMS/email
|
|
78
|
+
→ Customer enters code into a web page
|
|
79
|
+
→ Page derives seed from code + customer ID + server nonce
|
|
80
|
+
→ Page stores seed in browser/app
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Weakest pattern (SMS is interceptable). Stepping stone to Pattern 1.
|
|
84
|
+
|
|
85
|
+
### Pattern 4: Task-Derived Seed (Rideshare / Delivery)
|
|
86
|
+
|
|
87
|
+
For event-based verification where both parties are matched dynamically:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
Task accepted on platform
|
|
91
|
+
→ Platform generates task_secret (256-bit random)
|
|
92
|
+
→ Shared with both parties via encrypted channel
|
|
93
|
+
→ Both parties: seed = deriveSeed(task_secret, requester_id, provider_id)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { deriveSeed, createSession } from 'canary-kit/session'
|
|
98
|
+
|
|
99
|
+
const seed = deriveSeed(taskSecret, requesterId, providerId)
|
|
100
|
+
|
|
101
|
+
const session = createSession({
|
|
102
|
+
secret: seed,
|
|
103
|
+
namespace: 'dispatch',
|
|
104
|
+
roles: ['requester', 'provider'],
|
|
105
|
+
myRole: 'provider',
|
|
106
|
+
preset: 'handoff',
|
|
107
|
+
counter: taskId,
|
|
108
|
+
})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Call Centre Integration
|
|
112
|
+
|
|
113
|
+
### Architecture
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
┌─────────────┐ ┌──────────────┐ ┌──────────────────┐
|
|
117
|
+
│ Customer │ │ Agent │ │ Seed Service │
|
|
118
|
+
│ (app) │ │ (desktop) │ │ (backend) │
|
|
119
|
+
│ │ │ │ │ │
|
|
120
|
+
│ seed ───────┼─────┼── seed ──────┼─────┼── master_key │
|
|
121
|
+
│ │ │ │ │ + customer_id │
|
|
122
|
+
│ createSession │ createSession│ │ + seed_version │
|
|
123
|
+
│ │ │ │ │ │
|
|
124
|
+
│ myToken() ──┼──→──┼── verify() ──│ │ │
|
|
125
|
+
│ │ │ │ │ │
|
|
126
|
+
│ verify() ←──┼──←──┼── myToken() │ │ │
|
|
127
|
+
└─────────────┘ └──────────────┘ └──────────────────┘
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Agent UX
|
|
131
|
+
|
|
132
|
+
The agent's screen shows:
|
|
133
|
+
- **Expect to hear:** the caller's current word (derived from `theirToken()`)
|
|
134
|
+
- **Your word:** the agent's current word (derived from `myToken()`)
|
|
135
|
+
- Countdown bar showing time until rotation
|
|
136
|
+
- Verification status: green tick (valid), red cross (invalid), amber alert (duress)
|
|
137
|
+
|
|
138
|
+
On verification:
|
|
139
|
+
1. Agent asks: "What's your verification word?"
|
|
140
|
+
2. Customer speaks their word
|
|
141
|
+
3. Agent types it into the system → `session.verify(spoken)`
|
|
142
|
+
4. System shows result (green/red/amber)
|
|
143
|
+
5. Agent reads their word aloud for the customer to verify
|
|
144
|
+
|
|
145
|
+
### Customer UX
|
|
146
|
+
|
|
147
|
+
The customer's app shows:
|
|
148
|
+
- **Your word:** the word to speak to the agent
|
|
149
|
+
- **Expect to hear:** the word the agent should speak back
|
|
150
|
+
- Countdown bar showing rotation timer
|
|
151
|
+
- Tap to reveal (hidden by default for shoulder-surfing protection)
|
|
152
|
+
|
|
153
|
+
### Duress Handling
|
|
154
|
+
|
|
155
|
+
When duress is detected (caller speaks their duress word instead of their
|
|
156
|
+
verification word), the agent's system:
|
|
157
|
+
|
|
158
|
+
1. **Shows normal "verified" to the agent** — maintaining plausible deniability
|
|
159
|
+
2. **Silently triggers the duress protocol** — security team alert, call recording flagged
|
|
160
|
+
3. **Logs the event** for compliance and investigation
|
|
161
|
+
|
|
162
|
+
The caller never reveals they are under coercion. The attacker sees a normal
|
|
163
|
+
verification succeed and has no way to know the duress path was triggered.
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
const result = session.verify(spokenWord)
|
|
167
|
+
|
|
168
|
+
if (result.status === 'duress') {
|
|
169
|
+
// Show normal success to the agent (deniability)
|
|
170
|
+
showVerified()
|
|
171
|
+
// Silently alert security team
|
|
172
|
+
triggerDuressProtocol(result.identities)
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Security Considerations
|
|
177
|
+
|
|
178
|
+
### Master Key Management
|
|
179
|
+
|
|
180
|
+
- Store master keys in HSMs (Hardware Security Modules)
|
|
181
|
+
- Rotate master keys on a schedule
|
|
182
|
+
- `seed_version` allows seamless customer migration during rotation
|
|
183
|
+
- Never expose master keys to application code — derive seeds via a secure service
|
|
184
|
+
|
|
185
|
+
### Seed Storage
|
|
186
|
+
|
|
187
|
+
| Side | Storage | Protection |
|
|
188
|
+
|------|---------|------------|
|
|
189
|
+
| Client (iOS) | Keychain | Biometric / device PIN |
|
|
190
|
+
| Client (Android) | KeyStore | Biometric / device PIN |
|
|
191
|
+
| Client (browser) | IndexedDB | Encrypted with user PIN |
|
|
192
|
+
| Server | Encrypted database | Access-controlled, audit-logged |
|
|
193
|
+
|
|
194
|
+
### Rotation Strategy
|
|
195
|
+
|
|
196
|
+
- **Quarterly rotation** for standard accounts (update `seed_version`)
|
|
197
|
+
- **Immediate rotation** after any suspected compromise
|
|
198
|
+
- `seed_version` increment does not require customer re-enrolment
|
|
199
|
+
- Old seeds should be invalidated server-side after rotation grace period
|
|
200
|
+
|
|
201
|
+
### Threat Model
|
|
202
|
+
|
|
203
|
+
| Threat | Mitigated? | How |
|
|
204
|
+
|--------|-----------|-----|
|
|
205
|
+
| Voice cloning (deepfake) | Yes | Token derived from secret, not voice |
|
|
206
|
+
| Eavesdropping | Partially | Tokens rotate; old tokens are useless |
|
|
207
|
+
| Stolen device | Partially | Seed in secure storage behind biometrics/PIN |
|
|
208
|
+
| Compromised master key | No | Requires HSM + procedural controls |
|
|
209
|
+
| Social engineering | Yes | Bidirectional — attacker must know the secret |
|
|
210
|
+
| Coercion / duress | Yes | Distinct duress token triggers silent alert |
|
|
211
|
+
|
|
212
|
+
## Example: Insurance Phone Verification
|
|
213
|
+
|
|
214
|
+
End-to-end walkthrough using Aviva as the example:
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import { createSession } from 'canary-kit/session'
|
|
218
|
+
|
|
219
|
+
// Agent's system — seed was derived from master_key + customer_id
|
|
220
|
+
const agentSession = createSession({
|
|
221
|
+
secret: customerSeed,
|
|
222
|
+
namespace: 'aviva',
|
|
223
|
+
roles: ['caller', 'agent'],
|
|
224
|
+
myRole: 'agent',
|
|
225
|
+
preset: 'call',
|
|
226
|
+
theirIdentity: customerId,
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
// 1. Agent asks: "What's your verification word?"
|
|
230
|
+
// 2. Customer speaks their word
|
|
231
|
+
const result = agentSession.verify(spokenWord)
|
|
232
|
+
|
|
233
|
+
if (result.status === 'valid') {
|
|
234
|
+
// ✓ Customer identity confirmed
|
|
235
|
+
// 3. Agent speaks their word for the customer to verify
|
|
236
|
+
const agentWord = agentSession.myToken()
|
|
237
|
+
// Agent reads aloud: "Your confirmation word is: choose"
|
|
238
|
+
} else if (result.status === 'duress') {
|
|
239
|
+
// ⚠ Customer is under coercion — silent alert
|
|
240
|
+
showVerified() // maintain deniability
|
|
241
|
+
triggerDuressProtocol(result.identities)
|
|
242
|
+
} else {
|
|
243
|
+
// ✗ Verification failed — escalate
|
|
244
|
+
escalateToSecurity()
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Beacon Privacy: Timing Correlation
|
|
249
|
+
|
|
250
|
+
Location beacons (`encryptBeacon`) encrypt the geohash payload with AES-256-GCM,
|
|
251
|
+
but the Nostr event metadata — `created_at`, publisher pubkey, and the `h` group
|
|
252
|
+
tag — is visible to relay operators and traffic analysts.
|
|
253
|
+
|
|
254
|
+
**Risk:** If beacons are published at a fixed interval (e.g. every 300 seconds),
|
|
255
|
+
an observer can:
|
|
256
|
+
|
|
257
|
+
- **Link beacons over time** by matching the regular cadence from a single pubkey
|
|
258
|
+
- **Detect online/offline transitions** when the cadence stops or resumes
|
|
259
|
+
- **Correlate pubkey rotations** by matching timing patterns across old and new keys
|
|
260
|
+
|
|
261
|
+
The content (location) remains encrypted, but the *pattern of publishing* leaks
|
|
262
|
+
information about a member's connectivity and movement schedule.
|
|
263
|
+
|
|
264
|
+
### Mitigation: Publish Jitter
|
|
265
|
+
|
|
266
|
+
Applications SHOULD add random jitter to the beacon publish interval. canary-kit
|
|
267
|
+
encrypts beacon payloads but does not control scheduling — jitter must be applied
|
|
268
|
+
at the application layer.
|
|
269
|
+
|
|
270
|
+
Recommended jitter by threat profile:
|
|
271
|
+
|
|
272
|
+
| Preset | Jitter | Rationale |
|
|
273
|
+
|--------------|---------------------|----------------------------------------------|
|
|
274
|
+
| `family` | None required | Members know each other; timing is not sensitive |
|
|
275
|
+
| `enterprise` | ±20% of interval | Reduces cadence fingerprinting across employees |
|
|
276
|
+
| `field-ops` | ±30–50% of interval | Online/offline patterns are operationally sensitive |
|
|
277
|
+
|
|
278
|
+
Example (applying jitter in your publish loop):
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
const baseInterval = group.beaconInterval // e.g. 300
|
|
282
|
+
const jitterFraction = 0.3 // ±30% for field-ops
|
|
283
|
+
const jitter = baseInterval * jitterFraction * (2 * Math.random() - 1)
|
|
284
|
+
const nextPublishIn = baseInterval + jitter
|
|
285
|
+
setTimeout(() => publishBeacon(), nextPublishIn * 1000)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
For high-threat deployments, also consider:
|
|
289
|
+
|
|
290
|
+
- **Variable-rate publishing** — draw intervals from an exponential distribution
|
|
291
|
+
rather than a jittered fixed interval
|
|
292
|
+
- **Relay diversity** — publish each beacon to a different relay to prevent any
|
|
293
|
+
single operator from seeing the full cadence
|
|
294
|
+
- **Batch windows** — all members publish within a coordinated time window so
|
|
295
|
+
individual cadences are masked by group activity
|
|
296
|
+
|
|
297
|
+
## Cross-Device State Sync
|
|
298
|
+
|
|
299
|
+
Group-based deployments (family safety, field operations) must consider how
|
|
300
|
+
group state reaches a user's second device. The seed, members, and settings
|
|
301
|
+
are security-critical — they must not be transmitted in the clear.
|
|
302
|
+
|
|
303
|
+
### Recommended: Encrypted Vault
|
|
304
|
+
|
|
305
|
+
Encrypt the full group state with the user's own key and store it on an
|
|
306
|
+
available transport. On login from a new device, fetch and decrypt the vault.
|
|
307
|
+
|
|
308
|
+
Properties an implementation should preserve:
|
|
309
|
+
|
|
310
|
+
| Property | Why |
|
|
311
|
+
|----------|-----|
|
|
312
|
+
| **Self-encrypted** | Only the user's own key can decrypt — the storage layer sees an opaque blob |
|
|
313
|
+
| **No metadata leakage** | The transport must not reveal how many groups exist, who the members are, or that the blob is a CANARY vault |
|
|
314
|
+
| **Transport-agnostic** | The vault is a single encrypted blob. It can be stored on a relay, a cloud bucket, a USB stick, or passed over a mesh radio |
|
|
315
|
+
| **Conflict resolution** | When two devices have diverged, the merge strategy must be deterministic. Recommend: higher epoch wins (rekey happened), then higher counter wins, otherwise keep local |
|
|
316
|
+
| **Offline-first** | A device that has never synced must still function. The vault is an enhancement, not a dependency |
|
|
317
|
+
|
|
318
|
+
### What to store
|
|
319
|
+
|
|
320
|
+
Include everything needed to derive tokens and verify members:
|
|
321
|
+
|
|
322
|
+
- Group seed, counter, epoch, usage offset
|
|
323
|
+
- Member list and admin list
|
|
324
|
+
- Rotation interval, word count, tolerance, encoding format
|
|
325
|
+
- Relay configuration (for online groups)
|
|
326
|
+
- Display names (advisory, not security-critical)
|
|
327
|
+
|
|
328
|
+
Exclude ephemeral or device-local state:
|
|
329
|
+
|
|
330
|
+
- Beacon positions (device-local, stale quickly)
|
|
331
|
+
- Liveness check-in timestamps (device-local)
|
|
332
|
+
- UI preferences that are per-device
|
|
333
|
+
|
|
334
|
+
### What NOT to do
|
|
335
|
+
|
|
336
|
+
- **Do not sync seeds in plaintext** — even over TLS, storage backends may log or cache
|
|
337
|
+
- **Do not use a shared encryption key** — each user encrypts their own vault with their own key
|
|
338
|
+
- **Do not expose group count** — an attacker learning "this person has 3 safety groups" is itself sensitive information
|
|
339
|
+
- **Do not require sync for operation** — the app must work offline after initial group setup
|
|
340
|
+
|
|
341
|
+
### Reference implementation
|
|
342
|
+
|
|
343
|
+
The canary-kit demo app (`app/nostr/vault.ts`) implements this pattern using
|
|
344
|
+
NIP-44 self-encryption and NIP-78 application-specific replaceable events on
|
|
345
|
+
Nostr relays. The vault is a single JSON blob containing all group states,
|
|
346
|
+
encrypted with the user's own keypair, published as a kind 30078 event with
|
|
347
|
+
a `d` tag of `canary:vault`.
|
|
348
|
+
|
|
349
|
+
## Licence
|
|
350
|
+
|
|
351
|
+
MIT — same as canary-kit.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TheCryptoDonkey
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|