voidnote 0.2.0

Official Rust SDK for VoidNote — zero-knowledge self-destructing notes
Documentation

voidnote — Official Rust SDK

Zero-knowledge self-destructing notes and live encrypted streams. The key lives in the link. We never see it.

https://voidnote.net


Install

[dependencies]
voidnote = "0.1"

Or with Cargo:

cargo add voidnote

Quick start

Read a note

use voidnote::read;

let note = read("https://voidnote.net/note/<token>").await?;
println!("{}", note.content);
println!("views: {}/{}", note.view_count, note.max_views);
println!("destroyed: {}", note.destroyed);

Create a note

use voidnote::{create, CreateOptions};

let note = create(
    "launch codes: 4-8-15-16-23-42",
    CreateOptions {
        api_key: "vn_...".into(),
        max_views: Some(1),
        ..Default::default()
    },
)
.await?;

println!("share: {}", note.url);
println!("expires: {}", note.expires_at);

Live encrypted stream

use voidnote::{create_stream, StreamOptions};
use futures::StreamExt;

let mut stream = create_stream(StreamOptions {
    api_key: "vn_...".into(),
    title: Some("Deploy log".into()),
    ..Default::default()
})
.await?;

println!("share: {}", stream.url);

stream.write("Starting deployment...").await?;
stream.write("Build complete.").await?;
stream.write("Service is live.").await?;
stream.close().await?;

Watch a stream (SSE)

use futures::StreamExt;

let mut events = stream.watch();
while let Some(msg) = events.next().await {
    match msg {
        Ok(text) => println!("{text}"),
        Err(e) => eprintln!("error: {e}"),
    }
}

Blocking API

For use in synchronous contexts (scripts, CLI tools, non-async code):

use voidnote::blocking;

let note = blocking::read("https://voidnote.net/note/<token>")?;
println!("{}", note.content);

let created = blocking::create(
    "my secret",
    voidnote::CreateOptions {
        api_key: "vn_...".into(),
        ..Default::default()
    },
)?;
println!("{}", created.url);

API reference

async fn read(url_or_token: &str) -> Result<ReadResult>

Reads a note. Accepts either a full URL (https://voidnote.net/note/<token>) or a raw 64-character hex token.

ReadResult

Field Type Description
content String Decrypted plaintext
title Option<String> Note title if set
view_count u32 How many times read
max_views u32 Destruction threshold
destroyed bool Whether the note is gone

async fn create(content: &str, opts: CreateOptions) -> Result<CreateResult>

Creates a self-destructing encrypted note.

CreateOptions

Field Type Default Description
api_key String required Your VoidNote API key
title Option<String> None Optional title (stored encrypted)
max_views Option<u32> None Destroy after N reads
ttl_minutes Option<u32> None Expire after N minutes

CreateResult

Field Type Description
url String Shareable URL (contains key in fragment)
expires_at String ISO 8601 expiry timestamp

async fn create_stream(opts: StreamOptions) -> Result<StreamHandle>

Opens a live encrypted stream. Returns a handle for writing.

StreamOptions

Field Type Default Description
api_key String required Your VoidNote API key
title Option<String> None Stream title
max_views Option<u32> None Limit concurrent watchers
ttl_minutes Option<u32> None Auto-close after N minutes

StreamHandle

Method Description
.url Shareable URL for the stream
async fn write(&mut self, msg: &str) Encrypt and send a message
async fn close(&mut self) Close the stream
fn watch(&self) -> impl Stream<Item = Result<String>> Subscribe to live messages (SSE)

Security model

VoidNote uses zero-knowledge encryption — the server never sees your plaintext.

  1. A random 32-byte token is generated client-side
  2. The first 16 bytes become the tokenId (sent to the server as a lookup key)
  3. The last 16 bytes become the secret — used to derive an AES-256-GCM key via SHA-256
  4. Content is encrypted locally before upload
  5. The full 64-character hex token is embedded in the URL fragment (#token) — fragments are never sent to servers
  6. Anyone with the link can decrypt; without the link, the server cannot

Error handling

use voidnote::{Error, read};

match read(token).await {
    Ok(note) => println!("{}", note.content),
    Err(Error::NotFound) => eprintln!("note not found or already destroyed"),
    Err(Error::Unauthorized) => eprintln!("invalid API key"),
    Err(Error::DecryptionFailed) => eprintln!("bad token or tampered content"),
    Err(e) => eprintln!("error: {e}"),
}

Error variants

Variant Meaning
NotFound 404 — note gone or never existed
Unauthorized 401 — invalid API key
Api(String) Other HTTP error with message
DecryptionFailed Ciphertext tampered or wrong key
Network(reqwest::Error) HTTP transport error
Json(serde_json::Error) Unexpected response format
Hex(hex::FromHexError) Invalid token format

Run the demo

git clone https://github.com/quantum-encoding/voidnote-rust
cd voidnote-rust

# Read a note
cargo run --example demo -- read <url-or-token>

# Create a note
cargo run --example demo -- create "my secret" vn_...

# Open a stream
cargo run --example demo -- stream vn_...

License

MIT