1. Why Nostr
A dead man's switch requires a signaling mechanism where:
- The user can publish proof-of-life messages
- Multiple independent observers can monitor these messages
- No single party can censor or forge messages
- The protocol survives the failure of any service provider
Nostr (Notes and Other Stuff Transmitted by Relays) satisfies all requirements. It's a simple protocol where users sign messages with their private key and publish to multiple independent relays. Anyone can run a relay; anyone can read from any relay.
2. Nostr Fundamentals
2.1 Identity
Nostr identity is a secp256k1 keypair:
- nsec — Private key (never shared)
- npub — Public key (your identity)
There is no username registration, no account creation, no email verification. Your keypair is your identity. If you control the private key, you control the identity.
2.2 Events
Everything in Nostr is an "event" — a JSON object with a specific structure:
{
"id": "<sha256 hash of serialized event>",
"pubkey": "<hex public key>",
"created_at": 1704067200,
"kind": 1,
"tags": [],
"content": "Hello, world",
"sig": "<BIP-340 Schnorr signature>"
}
The kind field determines event type. Kind 1 is a text note.
Higher kinds are reserved for specific applications.
2.3 Relays
Relays are dumb servers that store and forward events. They don't verify identity, don't require accounts, and can be run by anyone. Users publish to multiple relays for redundancy.
3. EchoLock Event Types
EchoLock defines six custom event kinds in the 30000+ range (replaceable events):
| Kind | Name | Publisher | Purpose |
|---|---|---|---|
| 30078 | Heartbeat | User | Proof-of-life signal |
| 30079 | Share Storage | User | Encrypted share for guardian |
| 30080 | Share Release | Guardian | Released share for recipients |
| 30081 | Message Storage | User | Encrypted message payload |
| 30082 | Guardian Registration | Guardian | Announces availability |
| 30083 | Guardian Acknowledgment | Guardian | Confirms share receipt |
4. Heartbeat Protocol
4.1 Event Structure
{
"kind": 30078,
"pubkey": "<user_npub_hex>",
"created_at": 1704067200,
"tags": [
["d", "<switch_id>"],
["expiry", "1704672000"],
["threshold_hours", "72"],
["guardian", "<guardian1_npub>"],
["guardian", "<guardian2_npub>"],
["guardian", "<guardian3_npub>"],
["guardian", "<guardian4_npub>"],
["guardian", "<guardian5_npub>"]
],
"content": "",
"sig": "<schnorr_signature>"
}
4.2 Key Fields
dtag — Unique switch identifier (allows multiple switches per user)expiry— Unix timestamp when guardians should release if no new heartbeatthreshold_hours— Hours of silence before release (informational)guardiantags — Public keys of designated guardians
4.3 Publishing Flow
async function publishHeartbeat(
switchId: string,
thresholdHours: number,
guardians: string[]
): Promise<void> {
const expiry = Math.floor(Date.now() / 1000) + (thresholdHours * 3600);
const event = {
kind: 30078,
created_at: Math.floor(Date.now() / 1000),
tags: [
['d', switchId],
['expiry', expiry.toString()],
['threshold_hours', thresholdHours.toString()],
...guardians.map(g => ['guardian', g])
],
content: ''
};
const signedEvent = await signEvent(event, userNsec);
// Publish to multiple relays for redundancy
await Promise.allSettled(
RELAY_URLS.map(url => publishToRelay(url, signedEvent))
);
}
5. Guardian Monitoring
5.1 Subscription
Guardians subscribe to heartbeat events from users they protect:
// Guardian subscribes to user's heartbeats
const subscription = {
kinds: [30078],
authors: [userPubkey],
'#d': [switchId]
};
relay.subscribe(subscription, (event) => {
updateLastHeartbeat(event.pubkey, event.tags);
});
5.2 Release Trigger
async function monitorHeartbeats(
userPubkey: string,
switchId: string
): Promise<void> {
while (true) {
const latestHeartbeat = await queryRelays({
kinds: [30078],
authors: [userPubkey],
'#d': [switchId],
limit: 1
});
if (!latestHeartbeat) {
await sleep(CHECK_INTERVAL);
continue;
}
const expiryTag = latestHeartbeat.tags.find(t => t[0] === 'expiry');
const expiry = parseInt(expiryTag[1]);
if (Date.now() / 1000 > expiry) {
// Heartbeat expired — release share
await releaseShare(switchId);
break;
}
await sleep(CHECK_INTERVAL);
}
}
6. Censorship Resistance
6.1 Relay Redundancy
EchoLock publishes to 7+ relays by default. Users can add custom relays. For a heartbeat to be censored, all relays would need to collude or be compromised simultaneously.
6.2 Signature Verification
Every event is signed with BIP-340 Schnorr signatures. Relays cannot:
- Forge heartbeats (would require user's nsec)
- Modify heartbeats (signature would be invalid)
- Backdate heartbeats (created_at is signed)
Guardians verify signatures locally — they don't trust relays to authenticate events.
6.3 No Account Dependency
There is no "Nostr account" that can be banned or suspended. The user's identity is their keypair. As long as they control their nsec, they can publish to any relay that accepts connections.
7. Encrypted Communication
7.1 NIP-44
Private messages between users (and between users and guardians) use NIP-44 encryption — XChaCha20-Poly1305 with a shared secret derived from ECDH.
// Encrypt share to guardian's pubkey
const sharedSecret = secp256k1.getSharedSecret(
userPrivkey,
guardianPubkey
);
const key = hkdf(sharedSecret, 'nip44-v2');
const ciphertext = xchacha20poly1305.encrypt(key, nonce, plaintext);
7.2 Share Release Events
When a guardian releases their share, they encrypt it to each designated recipient's public key:
{
"kind": 30080,
"pubkey": "<guardian_npub_hex>",
"created_at": 1704672000,
"tags": [
["d", "<switch_id>:<guardian_index>"],
["e", "<original_switch_event_id>"],
["p", "<recipient1_npub>"],
["p", "<recipient2_npub>"]
],
"content": "<nip44_encrypted_share>",
"sig": "<schnorr_signature>"
}
8. Recovery Process
Recipients recover the encrypted message by:
- Querying relays for kind:30080 events tagged with their pubkey
- Decrypting each share using their nsec
- Collecting at least 3 shares (threshold)
- Reconstructing AES key via Lagrange interpolation
- Fetching kind:30081 (encrypted message) from relays
- Decrypting message with reconstructed AES key
This entire process requires only Nostr relay queries and local cryptographic operations. No server, no API, no service dependency.
9. Relay Recommendations
EchoLock defaults to relays with:
- High uptime track record
- Geographic distribution
- Different operators (jurisdictional diversity)
- No aggressive content filtering
10. Conclusion
Nostr provides the ideal substrate for dead man's switch heartbeats: cryptographically signed events, censorship-resistant distribution, and zero dependency on any single service provider. The protocol's simplicity — JSON events, secp256k1 signatures, dumb relays — makes it robust against the failure modes that plague centralized alternatives.
Combined with Shamir secret sharing for key distribution and Bitcoin for optional timestamp proofs, Nostr enables dead man's switches that operate autonomously on public infrastructure, controlled entirely by cryptographic keys.