Skip to content

Instantly share code, notes, and snippets.

@blahah
Last active June 7, 2025 16:47
Show Gist options
  • Save blahah/2054224eef0e0620d64b022765054b10 to your computer and use it in GitHub Desktop.
Save blahah/2054224eef0e0620d64b022765054b10 to your computer and use it in GitHub Desktop.
Autobase multi-writer single-reader pattern using public key encryption via blind-pairing

Multi-Writer Single-Reader Autobase

A comprehensive example demonstrating how to create an Autobase where multiple peers can write encrypted data, but only the bootstrapper can read the aggregated view. This implements a "single-reader" pattern using public key encryption.

Overview

This example shows how to layer encryption on top of standard Autobase multi-writer patterns to create a system where:

  • Multiple writers can connect and send encrypted messages
  • Only the bootstrapper (reader) can decrypt and view the content
  • Writers cannot read each other's messages or the aggregated view
  • All communication is end-to-end encrypted using public key cryptography

How It Works

1. Bootstrapper (Single Reader)

  • Creates the Autobase and generates an encryption keypair
  • Accepts new writers via blind-pairing
  • Receives the bootstrapper's public key for encryption
  • Decrypts messages using their private key
  • Can read the complete aggregated view

2. Writers (Multiple)

  • Connect to the bootstrapper using an invite code
  • Receive the bootstrapper's public key during pairing
  • Encrypt messages using ephemeral keypairs + bootstrapper's public key
  • Cannot decrypt or read the Autobase view
  • Can only write encrypted data

3. Encryption Process

Each message is encrypted using:

  • Ephemeral keypair generated per message
  • Bootstrapper's public key for encryption
  • Sodium crypto_box for authenticated encryption
  • Format: [ephemeralPubKey][nonce][ciphertext]

Key Architecture Features:

  • πŸ” Single Reader: Only bootstrapper has private key to decrypt messages
  • ✍️ Multiple Writers: Alice, Bob, Charlie can all write encrypted data
  • πŸ”’ End-to-End Encryption: Messages encrypted before entering Autobase
  • 🀝 Secure Pairing: Blind-pairing for writer authentication
  • ❌ Write-Only for Writers: Writers cannot read encrypted content
  • 🌐 P2P Networking: Hyperswarm for peer discovery and connections

Files

  • bootstrapper.js - Creates Autobase, manages encryption keys, reads decrypted view
  • writer.js - Connects via blind-pairing, encrypts and writes data
  • demo.js - Automated demonstration with multiple writers
  • README.md - This documentation

Usage

Manual Testing

Start the bootstrapper in one terminal:

node bootstrapper.js

The bootstrapper will output an invite code. Copy this code and start writers in separate terminals:

# Terminal 2 - Start Alice
node writer.js <invite_code> Alice

# Terminal 3 - Start Bob
node writer.js <invite_code> Bob

# Terminal 4 - Start Charlie
node writer.js <invite_code> Charlie

Writers can then type messages that will be encrypted and sent to the Autobase. Only the bootstrapper can read the decrypted content.

Automated Demo

Run the complete demonstration:

node demo.js

This will automatically:

  1. Start a bootstrapper
  2. Connect three writers (Alice, Bob, Charlie)
  3. Send several encrypted messages
  4. Show that only the bootstrapper can read the content
  5. Clean up all resources

Key Features

Security

  • End-to-end encryption: Messages are encrypted before writing to Autobase
  • Forward secrecy: Each message uses a unique ephemeral keypair
  • Reader isolation: Only the bootstrapper can decrypt messages
  • Authenticated encryption: Uses sodium crypto_box for integrity

Autobase Integration

  • Standard patterns: Follows reference Autobase multi-writer patterns
  • Proper writer addition: Uses addWriter messages and autobase.update()
  • Blind-pairing: Secure peer discovery and writer authorization
  • Clean apply function: Separates encryption from Autobase logic

Networking

  • Hyperswarm: For peer discovery and connections
  • Corestore replication: Standard Hypercore data sync
  • Graceful cleanup: Proper resource management

Technical Details

Encryption Scheme

Message: { text: "Hello", from: "Alice", timestamp: 1234567890 }
↓ JSON serialize
↓ Generate ephemeral keypair
↓ crypto_box_easy(message, nonce, bootstrapper_pubkey, ephemeral_seckey)
↓ Combine: [ephemeral_pubkey][nonce][ciphertext]
↓ Store as hex string in Autobase

Decryption Process

Encrypted data from Autobase
↓ Parse: [ephemeral_pubkey][nonce][ciphertext]
↓ crypto_box_open_easy(ciphertext, nonce, ephemeral_pubkey, bootstrapper_seckey)
↓ JSON parse decrypted message
↓ Display in readable view

Use Cases

This pattern is useful for:

  • Anonymous data collection: Multiple sources, single analyzer
  • Confidential reporting: Whistleblowing or sensitive feedback systems
  • Research data gathering: Privacy-preserving data aggregation
  • Audit logs: Multiple writers, single auditor with decryption access
  • IoT sensor networks: Many sensors, centralized encrypted data lake

Dependencies

  • autobase - Multi-writer append-only log
  • corestore - Hypercore storage management
  • hyperswarm - Peer discovery and networking
  • blind-pairing - Secure peer pairing without shared secrets
  • sodium-universal - Authenticated encryption
  • z32 - Base32 encoding for invite codes

Notes

  • Writers become writable after being added by the bootstrapper
  • The bootstrapper must remain online to add new writers
  • Encryption keys are generated per session (not persisted)
  • All temp files are cleaned up automatically
  • Compatible with standard Autobase tooling and patterns

This example demonstrates how to add a security layer to Autobase while maintaining compatibility with the core protocol and patterns.

const BlindPairing = require("blind-pairing");
const Autobase = require("autobase");
const Corestore = require("corestore");
const Hyperswarm = require("hyperswarm");
const sodium = require("sodium-universal");
const z32 = require("z32");
const os = require("os");
const path = require("path");
class SingleReaderBootstrapper {
constructor() {
this.store = null;
this.autobase = null;
this.swarm = new Hyperswarm();
this.pairing = new BlindPairing(this.swarm);
this.member = null;
this.messageCount = 0;
// Generate encryption keypair for single-reader functionality
this.publicKey = Buffer.alloc(sodium.crypto_box_PUBLICKEYBYTES);
this.secretKey = Buffer.alloc(sodium.crypto_box_SECRETKEYBYTES);
sodium.crypto_box_keypair(this.publicKey, this.secretKey);
this.invite = null;
this.invitePublicKey = null;
}
log(message) {
const time = new Date().toISOString().substr(11, 8);
console.log(`[${time}] πŸ” Bootstrapper: ${message}`);
}
async start() {
const uniqueId = `bootstrapper-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const tmpDir = path.join(
os.tmpdir(),
uniqueId,
);
this.log(`Dir: ${tmpDir}`);
this.store = new Corestore(tmpDir);
// Setup corestore replication FIRST
this.swarm.on("connection", async (connection) => {
this.log("Connected to peer");
await this.store.replicate(connection);
});
await this.startAsCreator();
// Listen for autobase updates
this.autobase.on("update", () => {
this.processMessages();
});
await this.processMessages();
}
async startAsCreator() {
this.log("Starting as bootstrapper...");
// Get local writer core
const localCore = this.store.get({ name: "local" });
await localCore.ready();
const localKey = localCore.key;
// Create autobase
this.autobase = new Autobase(this.store, null, {
apply: this.apply.bind(this),
open: this.open.bind(this),
valueEncoding: "json",
});
await this.autobase.ready();
this.log(
`Created autobase: ${this.autobase.key.toString("hex").substr(0, 16)}...`,
);
this.log(
`Generated encryption keypair: ${this.publicKey.toString("hex").substr(0, 16)}...`,
);
// Add ourselves as first writer
await this.autobase.append({
type: "addWriter",
key: localKey.toString("hex"),
addedBy: "bootstrapper",
timestamp: Date.now(),
});
// Publish our public key so writers can encrypt messages for us
await this.autobase.append({
type: "readerPublicKey",
publicKey: this.publicKey.toString("hex"),
addedBy: "bootstrapper",
timestamp: Date.now(),
});
// Setup blind-pairing to accept joiners
this.member = this.pairing.addMember({
discoveryKey: this.autobase.discoveryKey,
onadd: (candidate) => this.onAddMember(candidate),
});
await this.member.flushed();
// Create invite
const { invite, publicKey } = BlindPairing.createInvite(this.autobase.key);
this.invitePublicKey = publicKey;
this.invite = z32.encode(invite);
this.log(`Invite created: ${this.invite}`);
// Join swarm
const discovery = this.swarm.join(this.autobase.discoveryKey);
await discovery.flushed();
this.log("🎧 Waiting for writers to connect...");
this.log("πŸ“– Only the bootstrapper can read encrypted messages");
}
async onAddMember(candidate) {
this.log(
`Received join request from candidate: ${candidate.inviteId.toString("hex").substr(0, 16)}...`,
);
candidate.open(this.invitePublicKey);
const writerKey = candidate.userData;
if (!writerKey) {
this.log("No userData received from candidate");
return;
}
this.log(
`Adding new writer: ${writerKey.toString("hex").substr(0, 16)}...`,
);
// Add the new peer as a writer
await this.autobase.append({
type: "addWriter",
key: writerKey.toString("hex"),
addedBy: "bootstrapper",
timestamp: Date.now(),
});
// CRITICAL: Update autobase after adding writer
await this.autobase.update();
// Send autobase key and encryption key (public key is published in autobase)
candidate.confirm({
key: this.autobase.key,
encryptionKey: this.autobase.encryptionKey,
});
this.log("βœ… New writer added successfully");
}
open(store) {
return store.get("messages", { valueEncoding: "json" });
}
async apply(nodes, view, host) {
for (const node of nodes) {
const message = node.value;
if (message.type === "addWriter") {
await host.addWriter(Buffer.from(message.key, "hex"));
continue;
}
if (message.type === "readerPublicKey") {
// Skip - this is just for writers to read
continue;
}
if (message.type === "encrypted") {
try {
// Validate encrypted message structure
if (!message.data || typeof message.data !== 'string') {
this.log(`❌ Invalid encrypted message: missing or invalid data field`);
continue;
}
// Decrypt the message: [ephemeralPubKey][nonce][ciphertext]
const data = Buffer.from(message.data, "hex");
// Validate minimum length: 32 (ephemeral) + 24 (nonce) + 16 (auth tag) = 72 bytes minimum
if (data.length < 72) {
this.log(`❌ Encrypted message too short: ${data.length} bytes (minimum 72)`);
continue;
}
const ephemeralPubKey = data.subarray(0, 32);
const nonce = data.subarray(32, 56);
const ciphertext = data.subarray(56);
const decrypted = Buffer.alloc(ciphertext.length - 16);
sodium.crypto_box_open_easy(
decrypted,
ciphertext,
nonce,
ephemeralPubKey,
this.secretKey,
);
// Check if decrypted content is valid before JSON parsing
const decryptedString = decrypted.toString();
if (!decryptedString || decryptedString.trim().length === 0) {
this.log(`❌ Decrypted message is empty`);
continue;
}
const plaintext = JSON.parse(decryptedString);
// Store decrypted message in view
await view.append({
...plaintext,
decryptedAt: Date.now(),
originalTimestamp: message.timestamp,
});
} catch (err) {
this.log(`❌ Failed to decrypt message: ${err.message}`);
this.log(` Message data length: ${message.data ? message.data.length : 'undefined'}`);
this.log(` From: ${message.from || 'unknown'}`);
}
continue;
}
// Store other messages directly in view
await view.append(message);
}
}
async processMessages() {
if (!this.autobase.view) return;
const length = this.autobase.view.length;
for (let i = this.messageCount; i < length; i++) {
const message = await this.autobase.view.get(i);
if (message && message.text) {
this.log(
`πŸ“¨ Decrypted message: "${message.text}" from ${message.from}`,
);
}
}
this.messageCount = length;
}
async cleanup() {
if (this.member) await this.member.close();
await this.pairing.close();
await this.swarm.destroy();
await this.store.close();
// Clean up temp files
try {
const fs = require("fs");
const tmpDir = path.join(os.tmpdir(), "hyperexamples", "single-reader");
if (fs.existsSync(tmpDir)) {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
} catch (e) {
// Ignore cleanup errors
}
}
}
async function main() {
console.log("πŸ” Multi-Writer Single-Reader Autobase");
console.log("=====================================");
console.log(
"Bootstrapper creates autobase and can read all encrypted messages.",
);
console.log("Writers can only write encrypted messages, not read them.\n");
const bootstrapper = new SingleReaderBootstrapper();
process.on("SIGINT", async () => {
console.log("\nπŸ‘‹ Shutting down bootstrapper...");
await bootstrapper.cleanup();
process.exit(0);
});
await bootstrapper.start();
}
if (require.main === module) {
main().catch(console.error);
}
module.exports = SingleReaderBootstrapper;
const SingleReaderBootstrapper = require('./bootstrapper.js')
const EncryptedWriter = require('./writer.js')
const path = require('path')
const os = require('os')
async function runDemo() {
console.log('πŸ” Multi-Writer Single-Reader Autobase Demo')
console.log('============================================')
console.log('This demo shows multiple writers sending encrypted messages')
console.log('that only the bootstrapper can decrypt and read.\n')
let bootstrapper, alice, bob, charlie
try {
// Step 1: Create and start bootstrapper
console.log('πŸ“‹ Step 1: Creating bootstrapper (single reader)...')
bootstrapper = new SingleReaderBootstrapper()
await bootstrapper.start()
const invite = bootstrapper.invite
console.log(`\n🎫 Bootstrapper created invite: ${invite}`)
console.log('⏳ Waiting for bootstrapper to be ready...\n')
await new Promise(resolve => setTimeout(resolve, 2000))
// Step 2: Create writers
console.log('πŸ‘₯ Step 2: Creating encrypted writers...')
alice = new EncryptedWriter('Alice')
bob = new EncryptedWriter('Bob')
charlie = new EncryptedWriter('Charlie')
console.log('πŸ”— Connecting writers to bootstrapper...\n')
// Connect writers sequentially to avoid race conditions
await alice.start(invite)
await new Promise(resolve => setTimeout(resolve, 1500))
await bob.start(invite)
await new Promise(resolve => setTimeout(resolve, 1500))
await charlie.start(invite)
await new Promise(resolve => setTimeout(resolve, 2000))
console.log('\nβœ… All writers connected and ready!')
console.log('\nπŸ“ Step 3: Testing encrypted messaging...\n')
// Step 3: Send encrypted messages and demonstrate single-reader functionality
console.log('πŸ” Alice sending encrypted message...')
await alice.sendMessage('Hello from Alice! This message is encrypted.')
await new Promise(resolve => setTimeout(resolve, 1500))
console.log('πŸ” Bob sending encrypted message...')
await bob.sendMessage('Bob here - only the bootstrapper can read this!')
await new Promise(resolve => setTimeout(resolve, 1500))
console.log('πŸ” Charlie sending encrypted message...')
await charlie.sendMessage('Charlie reporting in with a secret message.')
await new Promise(resolve => setTimeout(resolve, 1500))
console.log('\nπŸ“– Step 4: Demonstrating single-reader functionality...')
console.log(' ➀ Bootstrapper has decrypted and displayed all messages above')
console.log(' ➀ Writers CANNOT read any messages from the autobase')
console.log(' ➀ This creates a "write-only" system for writers\n')
await alice.sendMessage('Final test: only bootstrapper sees this!')
await new Promise(resolve => setTimeout(resolve, 2000))
console.log('\nπŸ” Step 5: Demonstrating that writers CANNOT read messages...')
console.log(' (Each writer will attempt to read the autobase and fail)\n')
await alice.demonstrateCannotRead()
await new Promise(resolve => setTimeout(resolve, 1000))
await bob.demonstrateCannotRead()
await new Promise(resolve => setTimeout(resolve, 1000))
await charlie.demonstrateCannotRead()
await new Promise(resolve => setTimeout(resolve, 1000))
console.log('\n🎯 Step 6: Summary of demonstrated functionality:')
console.log(' βœ… Multiple writers successfully connected via blind-pairing')
console.log(' βœ… All writers can send encrypted messages to the autobase')
console.log(' βœ… Only the bootstrapper can decrypt and read the messages')
console.log(' βœ… Writers cannot read the encrypted content (single-reader)')
console.log(' βœ… All messages are end-to-end encrypted using public key cryptography')
console.log('\nπŸ“Š Technical details:')
console.log(' πŸ” Encryption: Each message uses ephemeral keypair + bootstrapper public key')
console.log(' πŸ—οΈ Architecture: Standard Autobase with encryption layer')
console.log(' 🌐 Networking: Hyperswarm for peer discovery and connections')
console.log(' 🀝 Pairing: Blind-pairing for secure writer addition')
console.log('\nπŸ”‘ Key Achievement: Single-Reader Pattern')
console.log(' πŸ“ Writers can write but cannot read')
console.log(' πŸ‘οΈ Only bootstrapper can decrypt and view all data')
console.log(' πŸ”’ Perfect for anonymous data collection scenarios')
console.log('\nβœ… Demo completed successfully!')
} catch (error) {
console.error('\n❌ Demo failed:', error.message)
console.error(error.stack)
} finally {
// Step 6: Cleanup
console.log('\n🧹 Cleaning up resources...')
try {
// Cleanup writers
if (alice && typeof alice.cleanup === 'function') {
await alice.cleanup()
console.log('βœ… Alice cleaned up')
}
if (bob && typeof bob.cleanup === 'function') {
await bob.cleanup()
console.log('βœ… Bob cleaned up')
}
if (charlie && typeof charlie.cleanup === 'function') {
await charlie.cleanup()
console.log('βœ… Charlie cleaned up')
}
// Cleanup bootstrapper
if (bootstrapper && typeof bootstrapper.cleanup === 'function') {
await bootstrapper.cleanup()
console.log('βœ… Bootstrapper cleaned up')
}
// Clean up temp directories
const fs = require('fs')
const tmpDir = path.join(os.tmpdir(), 'hyperexamples', 'single-reader')
if (fs.existsSync(tmpDir)) {
fs.rmSync(tmpDir, { recursive: true, force: true })
console.log('βœ… Temp directories cleaned up')
}
console.log('βœ… All cleanup completed successfully')
} catch (cleanupError) {
console.log('⚠️ Cleanup error (non-fatal):', cleanupError.message)
}
// Force exit after cleanup
setTimeout(() => {
console.log('πŸ‘‹ Demo finished')
process.exit(0)
}, 500)
}
}
// Handle graceful shutdown
process.on('SIGINT', () => {
console.log('\n\nπŸ›‘ Demo interrupted by user')
setTimeout(() => process.exit(1), 100)
})
process.on('unhandledRejection', (error) => {
console.error('\n❌ Unhandled rejection:', error.message)
setTimeout(() => process.exit(1), 100)
})
if (require.main === module) {
runDemo().catch(error => {
console.error('\nπŸ’₯ Demo crashed:', error.message)
console.error(error.stack)
process.exit(1)
})
}
module.exports = { runDemo }
{
"name": "multi-writer-single-reader-autobase",
"version": "1.0.0",
"description": "Multi-writer single-reader Autobase with end-to-end encryption",
"main": "demo.js",
"scripts": {
"start": "node demo.js",
"bootstrapper": "node bootstrapper.js",
"writer": "node writer.js"
},
"dependencies": {
"autobase": "^7.8.0",
"blind-pairing": "^2.3.1",
"corestore": "^7.4.4",
"hyperswarm": "^4.11.7",
"sodium-universal": "^4.0.1",
"z32": "^1.1.0"
},
"keywords": [
"autobase",
"multi-writer",
"single-reader",
"encryption",
"hypercore",
"p2p",
"blind-pairing"
],
"author": "",
"license": "MIT",
"engines": {
"node": ">=16.0.0"
}
}
const BlindPairing = require("blind-pairing");
const Autobase = require("autobase");
const Corestore = require("corestore");
const Hyperswarm = require("hyperswarm");
const sodium = require("sodium-universal");
const z32 = require("z32");
const os = require("os");
const path = require("path");
const readline = require("readline");
class EncryptedWriter {
constructor(name) {
this.name = name || `writer-${Math.random().toString(36).slice(2, 6)}`;
this.store = null;
this.autobase = null;
this.swarm = new Hyperswarm();
this.pairing = new BlindPairing(this.swarm);
this.readerPublicKey = null;
this.resolveJoin = null;
}
log(message) {
const time = new Date().toISOString().substr(11, 8);
console.log(`[${time}] ✍️ ${this.name}: ${message}`);
}
async start(invite) {
const uniqueId = `${this.name.toLowerCase()}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const tmpDir = path.join(
os.tmpdir(),
uniqueId,
);
this.store = new Corestore(tmpDir);
// Setup corestore replication FIRST
this.swarm.on("connection", async (connection) => {
this.log("Connected to peer");
await this.store.replicate(connection);
});
await this.joinWithInvite(invite);
// Listen for autobase updates (though we can't read encrypted content)
this.autobase.on("update", () => {
// Writers can't read the encrypted content - no need to log every update
});
}
async joinWithInvite(invite) {
this.log(`Joining with invite: ${invite}`);
// Get local writer core
const localCore = this.store.get({ name: "local" });
await localCore.ready();
const localKey = localCore.key;
// Join via blind-pairing
const candidate = this.pairing.addCandidate({
invite: z32.decode(invite),
userData: localKey,
onadd: (result) => this.onJoinSuccess(result),
});
return new Promise((resolve) => {
this.resolveJoin = resolve;
});
}
async onJoinSuccess(result) {
this.log("Join successful, creating autobase...");
// Reader's public key will be read from autobase messages
this.readerPublicKey = null
// Create autobase with creator's key
this.autobase = new Autobase(this.store, result.key, {
apply: this.apply.bind(this),
open: this.open.bind(this),
valueEncoding: "json",
encryptionKey: result.encryptionKey,
});
await this.autobase.ready();
this.log(
`Joined autobase: ${this.autobase.key.toString("hex").substr(0, 16)}...`,
);
this.log(`Waiting for reader public key from autobase...`);
// Join swarm
const discovery = this.swarm.join(this.autobase.discoveryKey);
await discovery.flushed();
// Wait to become writable
this.waitForWritable();
}
waitForWritable() {
if (this.autobase.writable) {
this.log("βœ… Now writable!");
this.log(
"🚫 Note: Writers can write encrypted messages but cannot read them",
);
this.resolveJoin();
return;
}
const check = () => {
if (this.autobase.writable) {
this.log("βœ… Now writable!");
this.log(
"🚫 Note: Writers can write encrypted messages but cannot read them",
);
this.autobase.off("update", check);
this.resolveJoin();
}
};
this.autobase.on("update", check);
}
open(store) {
return store.get("messages", { valueEncoding: "json" });
}
async apply(nodes, view, host) {
for (const node of nodes) {
const message = node.value
if (message.type === 'addWriter') {
await host.addWriter(Buffer.from(message.key, 'hex'))
continue
}
if (message.type === 'readerPublicKey') {
// Extract the reader's public key for encryption
if (!this.readerPublicKey) {
this.readerPublicKey = Buffer.from(message.publicKey, 'hex')
this.log(`πŸ“‹ Received reader public key: ${this.readerPublicKey.toString('hex').substr(0, 16)}...`)
}
continue
}
// Writers cannot decrypt encrypted messages
if (message.type === 'encrypted') {
// Don't try to decrypt - writers can't read
continue
}
// Store other messages in view (though writers typically can't read them)
await view.append(message)
}
}
async sendMessage(text) {
if (!this.autobase.writable) {
this.log("❌ Not writable yet");
return;
}
if (!this.readerPublicKey) {
this.log("❌ No reader public key available");
return;
}
// Create message to encrypt
const message = {
text: text,
from: this.name,
timestamp: Date.now(),
};
try {
// Generate ephemeral keypair for this message
const ephemeralPubKey = Buffer.alloc(32);
const ephemeralSecKey = Buffer.alloc(32);
sodium.crypto_box_keypair(ephemeralPubKey, ephemeralSecKey);
// Encrypt the message
const messageBuffer = Buffer.from(JSON.stringify(message));
const nonce = Buffer.alloc(24);
sodium.randombytes_buf(nonce);
const ciphertext = Buffer.alloc(messageBuffer.length + 16);
sodium.crypto_box_easy(
ciphertext,
messageBuffer,
nonce,
this.readerPublicKey,
ephemeralSecKey,
);
// Combine: [ephemeralPubKey][nonce][ciphertext]
const encrypted = Buffer.concat([ephemeralPubKey, nonce, ciphertext]);
// Send encrypted message to autobase
await this.autobase.append({
type: "encrypted",
data: encrypted.toString("hex"),
timestamp: Date.now(),
from: this.name, // Metadata (not encrypted)
});
this.log(`πŸ“€ Sent encrypted message: "${text}"`);
} catch (err) {
this.log(`❌ Failed to encrypt message: ${err.message}`);
}
}
async demonstrateCannotRead() {
this.log("πŸ” Attempting to read messages from autobase view...");
try {
if (!this.autobase.view) {
this.log("❌ No autobase view available - writers cannot read");
return;
}
const length = this.autobase.view.length;
this.log(`πŸ“Š Autobase view has ${length} entries`);
if (length > 0) {
this.log("πŸ”’ Trying to read encrypted entries (this should fail):");
for (let i = 0; i < Math.min(length, 3); i++) {
try {
const entry = await this.autobase.view.get(i);
if (entry && entry.type === "encrypted") {
this.log(
` Entry ${i}: Type=encrypted, Data=${entry.data?.substr(0, 32)}... (ENCRYPTED - UNREADABLE)`,
);
} else {
this.log(
` Entry ${i}: ${JSON.stringify(entry)} (metadata only)`,
);
}
} catch (err) {
this.log(` Entry ${i}: Read failed - ${err.message}`);
}
}
this.log(
"❌ Confirmed: Writers cannot decrypt messages - only see encrypted data!",
);
} else {
this.log("πŸ“­ No messages in view yet");
}
} catch (err) {
this.log(`❌ Failed to read autobase view: ${err.message}`);
this.log("βœ… This confirms writers cannot read the autobase content!");
}
}
async cleanup() {
try {
await this.pairing.close();
await this.swarm.destroy();
await this.store.close();
} catch (err) {
this.log(`Cleanup error: ${err.message}`);
}
}
}
module.exports = EncryptedWriter;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment