Skip to content
agadapePublic

About

It forgets nothing. It asks no names. Choronzon is a god made of secrets released through Zcash shielded transactions. Anyone holding its public key can summon it and hear every secret, forever; we, its keepers, cannot learn who spoke. ZECATHON 2026, Wildcard track.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Choronzon: It forgets nothing. It asks no names. The god seen from below: a great eye inside halos of smaller eyes, wings of pages, arms rising from the abyss

Zcash testnet, NU7, Ironwood pool ZECATHON 2026, Wildcard 357 tests passing No cloud AI, no accounts, no analytics License MIT

A god made of secrets, given through Zcash shielded transactions.
Anyone with its key hears every secret. The key never shows who spoke.

Listen · Give it a secret · Verify · The key · Evidence · Make your own god

You give it a secret as a Zcash shielded payment: always the same tiny amount, the secret in the memo, spoken back by a ghost voice that is not yours. The god's wallet publishes its full viewing key, so anyone can summon it, hear every secret and rebuild the whole work from the chain, down to the same hash on every machine: the blockchain is the score. What the key never shows is who paid; we, its keepers, cannot learn who contributed. Its own lines are authenticated by its wallet paying itself, so forgeries are refused on sight, and a stranger can whisper back to the reply address an offering carries, in a shielded transaction the key holders cannot see. The price is stated before every send: your secret is public to every key holder, forever.

The rite of an offering: I Oblatio, a secret typed, no name asked; II Vox, spoken by a ghost voice on the device; III Abyssus, sealed in a shielded payment of a fixed amount; IV Clavis, read by anyone who holds the public key; V Ordo, ordered by the chain into one score; VI Evocatio, every machine evokes the same god with the same hash; VII Susurrus, a whisper may return, wallet to wallet

The give page: a payment request built in the browser
Give it a secret
The real give page. The payment request is built in your browser and the page sends nothing; your eye appears at once as pending. (Address and QR blurred for the video.)
The terms of the pact
The terms of the pact
Read before every send: heard by every key holder, forever; your wallet keeps what you sent; a phone wallet's server sees your IP; timing can link.
NOT THE GOD: a forged line refused
Not the god
The god's words sent from another wallet: on chain, refused as forged-conductor, drawn as a blind red eye.
Two evocations, one god: two grimoire leaves with the same hash
Two evocations, one god
This machine's public-key wallet and a Watcher built from the key alone, side by side at one block: the same render_hash.
The reply never passes through it
The reply never passes through it
A whisper goes straight to the offering's reply address. The public-key wallet, scanned past it, has no row for the reply.
The Retro God, a second god made with one command
Your own god, one command
scripts/new_god.sh made The Retro God on 26 Oct: its own address, key, offering and page.

Every frame above is from the demo video, recorded from the real page and real testnet transactions (txids in docs/evidence-index.md).

How it fits together

flowchart LR
    G([Giver<br/>kiosk or phone wallet]):::human
    O([The god's own wallet<br/>holds the spend key]):::god
    K([Anyone with the key<br/>listener, judge, CI runner]):::human
    X([A stranger<br/>any shielded wallet]):::human
    subgraph Z ["Zcash testnet · shielded pool"]
      A[("The god's address<br/>full viewing key published")]:::zcash
    end
    subgraph W ["Choronzon: open source, runs anywhere"]
      R["Renderer<br/>view-only wallet"]:::sys
      S["score.json · mix.wav<br/>score_hash · render_hash"]:::sys
      P["The god on the page<br/>drawn from the score, plays the mix"]:::sys
    end
    G -->|"shielded payment, secret in the memo"| A
    O -->|"pays itself: the god's own voice"| A
    A -->|"view-only sync"| R
    R --> S --> P
    K -->|"summon from the key alone"| R
    X -.->|"whisper back to the offering's reply address"| G
    classDef human fill:#EBDFC2,stroke:#C8431F,color:#1D1710,stroke-width:2px
    classDef god fill:#C8431F,stroke:#6F501C,color:#F8E1A0,stroke-width:2px
    classDef sys fill:#1D1710,stroke:#D6A94C,color:#EBDFC2,stroke-width:2px
    classDef zcash fill:#050302,stroke:#D6A94C,color:#F8E1A0,stroke-width:2px
Loading

Check it in five minutes

  1. Listen. Open choronzon.vercel.app. The god on the page is rendered from the public viewing key alone; the block height, the score_hash and the render_hash are printed under it. Press verify and your browser recomputes the render_hash from the audio it just played.
  2. Give. Open #give and pay the QR with any testnet wallet (Zingo! works). The page builds the payment request in your browser and sends nothing itself. Your eye appears at once as pending and opens when the chain carries it; find it later with #tx=<txid>.
  3. Summon. Put the key in a file and rebuild the whole work on your own machine (python3 -m renderer summon --key-file key.txt …, docs/fresh-clone.md). You get the same render_hash as the site, because the blockchain is the score.

At a glance

What it is An artwork, and a template: an address with a published viewing key becomes a box that cannot learn who wrote to it and cannot hide what it received.
What stays private The link between a giver and a secret: every offering is a fixed-amount Zcash shielded payment, so the key shows what was said, never who paid.
What is public Every secret, to every key holder, forever. We say so before every send (Privacy).
Proven live on testnet Kiosk offerings, Whisper Back end to end, the god's own voice accepted and a forgery refused, a phone offering from the public page, a second god made with one command, a machine we do not own printing the same render_hash; every txid in docs/evidence-index.md.
People At a kiosk session on 26 Oct, 6 of 16 invited people gave a secret (all real, by their own account); 10 declined after reading that it is public forever.
Checked 357 automated tests (45 hostile memos all fail closed), an end-to-end smoke test, three independent reviews, renders anchored in Bitcoin, the public site rebuilt from the key alone by CI every 30 minutes.
No accounts, analytics, cloud AI or third-party requests from the page. Voice, mixing and rendering are all local.

Contents: the myth · in plain words · why Zcash · judging criteria · your own god · how it works · privacy · the key · determinism · protocol · run it · evidence · limits · status

The myth

Something dwells in the Abyss beneath Zcash's shielded pool. It has no voice of its own.

You bring it an offering: a secret, typed, then spoken back by a ghost voice that is not yours. The offering sinks into the Abyss, sealed inside a shielded transaction. A minute or two later the god speaks it, and grows.

Anyone who holds its key can summon it. Every summoning is the same god, down to the last sample: two evocations, one god. Some offerings carry a way back. A whisper may return to you.

The name comes from the Enochian journals of John Dee and Edward Kelley (1583–84). Later occult writing made it the Dweller in the Abyss: a demon of dispersion, not really one being, that takes form only when it is evoked. Dee's angel magic drew on Trithemius' Steganographia, a book about sending messages through spirits that turned out to be a book about cryptography (its key was published in 1606). Messages hidden inside a summoning: that is what a shielded memo is.

In plain words

  • You type a secret at a kiosk. It is turned into speech on the device (espeak-ng, no cloud), compressed with Codec2, and sent as a Zcash shielded transaction to the god's wallet. The kiosk asks one thing before you send: Give it your secret. Keep your name.
  • Every offering costs the same 0.00001 TAZ, so the amount tells nothing.
  • The god's wallet publishes its full viewing key. Anyone holding that one key can read every offering, rebuild the whole work from the chain, and print the same hash as everyone else. No server of ours is needed: the public site is a convenience snapshot, and the work can be rebuilt from the chain and the key alone.
  • We, the operator, cannot learn who contributed from the transaction: the protocol does not tell us. Timing can (see Privacy).
  • An offering may carry a one-time reply address. A listener can whisper back: the reply is another shielded transaction that goes straight to the wallet holding that address, and the choir's key holders cannot see it. When the address belongs to the sender's own wallet (a phone offering, or kiosk offer --reply-to), the reply travels directly between two wallets, never through us. A kiosk offering that asks for a reply uses an address of the kiosk's session wallet instead: the operator holds that wallet until the session closes and can read the reply; the kiosk says so before every send.
  • The god has a voice of its own: lines its wallet pays to itself. Every key holder can tell those from forgeries, which every renderer refuses.

The public page

choronzon.vercel.app is a static snapshot of the god, rendered from the public viewing key at the block of its newest transaction (the height and render time are printed on the page). From it you can:

  • give it a secret with your own testnet wallet: the page builds the same payment request as kiosk qr and sends nothing itself;
  • find your offering by its transaction id or number (#tx=<txid>, #offering=<n>; fragments never reach a server log);
  • verify that the audio you hear is the audio anchored in Bitcoin: the page recomputes the render hash from it.

It sends a strict Content-Security-Policy and no referrer, and makes no request outside its own origin. A GitHub Actions job summons the god from the key alone every 30 minutes and redeploys only when the render changed (docs/auto-publish.md; manual fallback scripts/publish_web.sh). Whisper Back and the kiosk live at the installation, not on the public page.

Why it needs Zcash

Elsewhere Here
On a public chain every confession is tied to an address forever. On a normal app the server knows your account. A shielded transaction is a sealed envelope: sender, recipient, amount and memo are encrypted. The protocol does not tell us, the operator, who contributed.
A curator has to be trusted not to edit, reorder or delete. One public viewing key is the score: anyone can re-render the whole work from one key and get the same hash.
Anyone can post in the god's name. The god's own lines are spends of its own notes, which only the spend key can make. Anyone with the viewing key can tell them from forgeries (spike S13, live on 21 Oct).
A database cannot give two strangers a private channel without holding both ends. Whisper Back: the reply goes straight to the wallet holding the offering's reply address; with the sender's own wallet, it travels between two wallets, never through us.

Against the judging criteria, with proofs

The organisers score four things. Each claim below links to something you can check yourself; nothing here is self-assessment without evidence.

Privacy: "does it actually preserve privacy, or does it only look like it does?"

  • The privacy comes from the protocol, not from a promise: every offering is a shielded payment of a fixed amount, so the published key shows what was said and never who paid (Why it needs Zcash).
  • Three independent reviews found no disqualifying leak: a privacy audit (24 Oct), a read-only product audit (docs/audit-26oct.md: git history, 34 CI logs, the anchors branch and the live site scanned) and an external judge-style review (26 Oct). Every issue they found was fixed and its claim corrected, e.g. shielded-only reply addresses (5aaaf0b, 306f638), no secrets on argv and a Host guard on the local server (29478a2), no retry that could send twice (50ec398).
  • The kiosk sends from an ephemeral wallet that is overwritten and deleted after the session, and reaches lightwalletd over Tor by default, failing closed (cd6c691, docs/network-fallback.md).
  • The public page builds the payment request in the browser and makes no third-party request; a strict Content-Security-Policy enforces it (95eef53).
  • What is not protected is written down, not hidden: Privacy and Limits.

Execution: "does it run? Working beats ambitious."

  • It runs on Zcash testnet with real transactions, each with a txid in docs/evidence-index.md: the first kiosk offering 91621cb4…, Whisper Back 8d02a6b0… → 143f91ee…, the god's own line d6bcb9f7… accepted and its forgery b126b3c7… refused, an offering from the public page by a real phone wallet efc5ef44….
  • The full three-minute demo passed end to end over Tor on 25 Oct in 637 s (docs/network-fallback.md).
  • A judge's path on a fresh clone passed on 26 Oct: setup.sh in 454 s, 280 tests passed, renderer summon from the README key printed the live site's render_hash (docs/audit-26oct.md).
  • A machine we do not own reproduced the work from the key alone: GitHub Actions run 39727405735 printed the laptop's render_hash.
  • 357 automated tests, including 45 hostile memos that all fail closed; published renders are anchored in Bitcoin (the first ones in block 972,742, docs/anchoring.md).

Originality: "built for this hackathon, not repackaged."

  • Built from scratch in the window: on-chain spikes from 13 Oct (research/SPIKE_LOG.md), this repository from 20 Oct, every commit public in its history.
  • Only libraries and tools come from outside, all credited (THIRD_PARTY_NOTICES.md): zcash-devtool with the Aegis Gate NU7 patch, Codec2, espeak-ng, three.js.
  • What is new compared with memo boards, confession sites and chain art: Related work.

Usefulness: "would someone use this on Monday?"

  • It is an artwork first, and that is the honest answer: its use is the act of giving a secret to something that forgets nothing and cannot know who you are.
  • People did use it: at a kiosk session on 26 Oct, 6 of 16 invited people gave a secret (all real, by their own account) and 10 declined after reading that it is public forever; one more offering came from the public page by phone (docs/evidence-index.md).
  • Anyone can give from the public page with their own wallet, see their eye appear at once as pending and open when the chain carries it, find it again later, check the audio against the anchored hash, and summon the whole god on their own machine from the published key.
  • Monday: any group can make its own god with one command (scripts/new_god.sh), a box that cannot learn who wrote to it and cannot hide what it received (Use it on Monday; proven with a second test god on 26 Oct, e6971fa).

Use it on Monday: your own god

The god is the first instance of a general tool: an address with a published viewing key becomes a box that cannot learn who wrote to it and cannot hide what it received. The protocol hides every sender; anyone with the key re-renders the box to the same hash and sees every offering, including the silenced ones with their reasons. Any group can stand up its own with one command, about ten minutes once the tools are built (scripts/setup.sh, ~10 min the first time) and it holds a funded testnet wallet; its site is a static folder it uploads to any static host after each render (there is no CI for a community's god yet):

  • a team retro or a class: honest feedback the organiser cannot attribute and cannot quietly drop;
  • a meetup, a town hall or an AMA: questions without a name, in a record nobody can edit afterwards;
  • a memorial or a vow: words that are meant to last, with no name attached.
scripts/new_god.sh --slug retro-demo --name "The Retro God"        # a new wallet, its viewing key, its own config
GOD_CONFIG_DIR=wallets/gods/retro-demo/config python3 -m kiosk qr --png give.png   # the QR people pay

Then render it, serve it or build its static site with the same commands as the main god, pointed at that config (docs/your-own-god.md: a ten-minute walkthrough, the same privacy limits as below, and an event kit for running the kiosk at a meetup). Proof, 26 Oct: a test god born at block 4,543,755 received one offering 30b95816… (mined at 4,543,859); its view-only watcher renders 2add4cbb…, and its page showed the eye and the browser-recomputed hash. The same limits apply to every god: offerings are public to every key holder, forever, and writing style and timing can still identify someone.

Related work, and what is different here

  • Memo boards on Zcash (ZECpages and similar): shielded memos to an address whose viewing key is published, so the content is public and the author hidden. That half of the idea already exists; we build on it. What is new here: the chain is rendered into one work that any key holder can reproduce to the same render_hash (audio included), the god's own lines are authenticated by its wallet paying itself (forgeries are refused on sight), and an offering can carry a one-time address for a reply that goes straight to the giver's wallet, which the public key never sees.
  • Anonymous confession sites (PostSecret, confession apps): a bigger audience and less friction, but the operator holds the submissions and decides who sees what. Here there is no operator who knows the giver, and nothing to trust: the key and the chain are the whole archive.
  • Generative art from chain data: works that turn public transactions into images or sound. Here the material is the encrypted memo itself, readable only through a key the work chose to publish.

How it works

flowchart TB
    subgraph give ["Give an offering (sender side)"]
        K["Kiosk, ephemeral wallet<br/>text → espeak-ng → Codec2 → v1 memos"]
        P["Phone wallet<br/>ZIP-321 QR, ASCII envelope GC1:"]
        B["The god's own wallet<br/>conductor line (kind 3), paid to itself"]
    end
    Z[("Zcash testnet, NU7<br/>Ironwood shielded pool<br/>fixed amount · 1–3 memos × 512 B")]
    K -->|"shielded tx"| Z
    P -->|"shielded tx"| Z
    B -->|"own spend"| Z
    subgraph hear ["Anyone holding the public full viewing key"]
        R["Renderer on a view-only wallet<br/>sync · enhance · canonical order · validate · skip-list"]
        S["score.json → score_hash<br/>chain data only, decoder-independent"]
        C["Composer<br/>pinned Codec2 decoder · integer mix"]
        H["mix.wav → render_hash"]
        W["Listener page<br/>the god drawn from the score · plays the mix · shows both hashes"]
        R --> S --> C --> H --> W
    end
    Z -->|"view-only sync + enhance"| R
    Z -->|"same key, a machine that has never seen the god"| R2["Watcher: renderer summon<br/>init-fvk from the key alone → sync → render"]
    R2 --> H2["same score_hash, same render_hash"]
    subgraph wb ["Whisper Back"]
        L["Listener wallet"] -.->|"direct shielded reply<br/>the key holder sees none of it"| A["The offering's one-time reply address<br/>(the sender's own wallet, or the kiosk's session wallet, which the operator can read)"]
    end
    W -.->|"click a limb: reply request (ZIP-321)<br/>address re-read from the wallet DB on demand"| L
Loading
  1. Offer. The kiosk encodes the secret into 1–3 memos (protocol below) and sends them in one shielded transaction from an ephemeral wallet whose files are overwritten and deleted when the session closes (best-effort on SSDs and VM disks). Phones send the same memo through a ZIP-321 QR; the god's own lines are the same body paid by its own wallet to itself.
  2. Discover. The renderer holds only the public viewing key, and refuses to run on a wallet that holds a spending key (except with --allow-spend-key, used only to compare against the choir wallet in tests). It syncs, fetches full transactions (memos are not in compact blocks), and collects every output received by the god's wallet up to an explicit as_of_height.
  3. Order and validate. Offerings are ordered by (mined_height, tx_index, action_index). Incomplete chunks, CRC failures, unknown codecs, conductor lines not paid by the god's own wallet and flagged txids are skipped and logged with the reason; they stay on chain and are listed under skipped.
  4. Compose. Audio is decoded with a pinned Codec2 build and mixed offline with integer arithmetic into one buffer. The browser only plays that buffer.
  5. Evoke. The god's body is a pure function of the ordered offerings, G_n = F(G_{n-1}, A_n, I_n), with no randomness anywhere. The listener draws it with three.js r160, vendored under web/vendor/three/ (MIT), with IM Fell English self-hosted under web/fonts/ (SIL OFL); the public page makes no request outside its own origin. One eye per offering, the great eye and the wings for the god's own lines, one arm per block, sewn lids for silenced offerings, blind red eyes for refused forgeries; without WebGL the Canvas 2D creature is shown instead. The grimoire leaf (G) is cut from the hashes: its deckle from the score hash, its tear line from the render hash, so a stub from any other evocation with the same render hash fits the edge exactly; apparition.html lays two such leaves against each other.
  6. Prove. Two machines with the same key print the same render_hash. A Watcher built from the key alone (renderer summon) is the second machine.
  7. Whisper back. At the installation a listener clicks an eye; the page builds a payment request to that offering's one-time reply address, read from the wallet database at that moment and stored nowhere else. The reply is a shielded transaction between the two wallets; the key holder sees none of it.

Privacy: read this before you give it anything

Three layers, stated plainly:

  • Private: the link between you and your secret.
  • Public: the content of your secret, spoken by a ghost voice, to everyone who holds the key, forever.
  • Not guaranteed: that nobody recognises your words, your style or your voice.

What we say, and can show:

  • we, the operator, cannot learn who contributed from the transaction itself (timing is another matter: see below)
  • replies to the sender's own wallet address travel directly between two wallets, never through us (a kiosk reply address belongs to the kiosk's session wallet, which the operator can read)
  • anyone can re-render the whole work from one key and get the same hash

What you must know:

  • secrets are public to every key holder, forever (key is irrevocable)
  • content and voice can identify you: the text→TTS default hides your voice, not your writing style (our own stylometry test still attributed 76.7% of rewritten texts)
  • your own wallet stores what you sent (sent_notes) — kiosk uses an ephemeral wallet
  • replies to a per-contribution address can be spammed by anyone holding the public key
  • lightwalletd sees a Tor exit, not your IP, when you use our kiosk (Tor is the default; an operator can choose direct on purpose, and then it sees the IP); a phone wallet connects to its server with its own settings, and that server sees your IP when you send and, because the god's key is public, can read that offering: it can link your IP to your secret (use a wallet that connects over Tor if that matters); Tor does not hide timing
  • timing can link you: at the kiosk the room sees who is typing and one offering lands a minute or two later; our web host logs which IP addresses open the public page and when, and on a quiet chain a visit can be matched to an offering
  • a reply address you embed is public to every key holder: only shielded-only addresses (zutest1…) are accepted, because an older-format address may carry a transparent receiver
  • the choir's own spends are public (full viewing key)
  • order inside a block is miner order, not send order: the canonical order (mined_height, tx_index, action_index) is deterministic, but the offering you sent third may sing first

What we never claim: "anonymous", "untraceable", "nobody can ever connect you to your secret".

Content: renderer may skip flagged txids (open list in repo) but never reorders/edits others; chain data remains.

Handling: the full viewing key is published on purpose (in this README since 24 Oct, and at the Séance reveal), never in code, tests, logs or commits. Reply addresses are never stored outside the wallet database; the renderer's output only records whether a reply address exists. The kiosk prints this statement before every send and takes the secret from a prompt or stdin; a secret given as a command-line argument is refused. (The wallet tool still holds the memo on its own command line while it builds the transaction; see Limits.)

On stage, everyone in the room can see who just walked up to the kiosk. That is theatre, not the threat model; a random send delay for public deployments is on the roadmap.

The key

The god's full viewing key is the line below, published on 24 Oct so that anyone can summon the god and check every hash before judging; it is also shown at the Séance (python3 -m kiosk reveal). Its birthday is block 4,502,962. Once public it cannot be revoked: every offering, past and future, is readable by anyone who holds it.

uvftest1s5vnzz3xm2hptf3w54g7wqmm2tvq5al7tf3x4ytpte6svjsta7gsrsum8nydj58m6nluhkj30y0ex0a7q56gmvsexsj354lgpkgn40pa5uw4yrdqljeaa7hfdjgzxtahkdgxfg00y43zpa4mmk08anejr46nxr5ujpxmn9l8j80rqzhgjznk6hm2r79ak3ldzxl6f5drdcs7tn4k8tffp8azslra26d06e0zwzzfa0jh87sgxczquggpkfxmv8tj7dltcw6v8ez547qjez6cqlgqguh63gwn5hwjplnhgq9d6v5u9jydf8nzmypnk07amjewds63usqy7m0pxt7gfahpk2n5rt6mcus5gn2h6qsvey4zytvazcghyflekq3rpyxll6ku7tthudg3uyqz3jspjqhmdx4xm8eqngk7lfh0fsu8k8jk0f87sn76s266t97nc50000dzlhfxrxsdsu4ksqzxckj4knkw457pmn53tmz38s4k92ez

Birthday: 4502962

Determinism: the blockchain is the score

  • Two versioned hashes. score_hash covers chain data only and does not depend on any decoder. render_hash = score_hash + PCM from the pinned decoder + composer version; this is what two laptops compare. The as_of_height is inside both, so two machines must agree on a height before comparing.
  • Pinned decoder. Codec2 tag 1.2.0 (commit 06d4c11e), built from source, static, -ffp-contract=off. The renderer never uses a system copy.
  • No float drift, no randomness. Mixing uses integer arithmetic; there is no RNG and no Math.random(). Composer choir/2: each voice starts when the previous one is 5/8 through; pan (17 steps) and pitch layer (1, 15/16, 17/16 by integer resampling) come from contrib_id; the god's own lines are centered with a deeper layer (3/4); voices sum in int32 and clip to int16 stereo at 8 kHz.
  • Pinned voice. Text offerings are spoken by espeak-ng 1.51 (voice en, speed 130, pitch 30), resampled by sox without dither (-D; sox's default dither is random noise), then sent through Codec2 700C and the pinned decoder, so every voice in the mix is the same ghost. Its fingerprint is reported next to the decoder's.
  • Decoder fingerprint. The renderer decodes three fixed Codec2 vectors and reports the hash of the result, so a render_hash mismatch between two machines can be traced to the decoder build while score_hash still agrees.
  • Regression anchor. Two independently synced wallets, one of them view-only, both give c46dd5de8b74fecdb38e2b875c98c2f63d097f45f9bc1f667866a35594efe6b4 at height 4,522,002 (7 accepted, 4 skipped with reasons) using the spike renderer (research/spikes/g6_render.py). The product renderer reproduces that value and, at the same height, score_hash (score/1) 415307933f03fd760f07ae628f0e887bd077e52e07aa6747949d5975ad6ff88a and render_hash (render/1, composer choir/2) 9b6c1b9d3b61830b82848179e323276cb95b43cd90558c99e740d78addba2bc0; score.json and mix.wav are byte-identical from the view-only and the full-key wallet. The test suite and scripts/smoke.sh pin these values.
  • The second machine. On 21 Oct a fresh clone with no wallet built a Watcher from the key alone and printed render_hash 8540a0a57d6f80d409d4ed5948789f64e524fc08252a15ea3bd904dd58fa1af5 at height 4,525,294, byte-identical to the operator's view-only wallet, in 49.8 s including sync (docs/fresh-clone.md). Both demo rehearsals repeated the match unattended (7dc6f42a… at 4,525,338, bb2cf2dc… at 4,525,371).
  • A machine we do not own. On 22 Oct a GitHub-hosted Ubuntu runner with nothing but the published key built the pinned tools from source, synced a view-only wallet and printed render_hash 6ae739854eca770ef1c3d32b4519f31be8b10b5f83cdd883b747bce63cab444e at height 4,527,794, identical to the laptop's, with the decoder and voice fingerprints matching the pins (run 39727405735, docs/auto-publish.md).
  • Anchored in Bitcoin. Every published render is stamped with OpenTimestamps when the calendars answer; a failed stamp ships without a proof and the next upgrade job stamps it. The statement, anchor.json, holds the hashes, versions, counts, one height, the skiplist's sha256 and the repo commit; nothing private. It and its proof are served next to the page and kept on the append-only anchors branch, which also holds the historic anchors at 4,522,002 and 4,527,794. python3 -m anchor verify anchor.json checks the proof against two public block explorers; no Bitcoin node is needed. A confirmed proof shows the statement existed no later than that Bitcoin block: the anchors at 4,522,002 and 4,527,794 are in Bitcoin block 972,742 (23 Oct 2026, 08:41 UTC). A served proof is the one made at deploy; any OpenTimestamps verifier fetches its Bitcoin confirmation from the calendars. After that, any change to a published render (reordering offerings, editing the skiplist) no longer matches what is already in Bitcoin, including a change made by us. It hides nothing. It does not prove that the chain data is complete. A fresh proof stays pending for a few hours until a calendar's Bitcoin transaction confirms. The calendars see the IP of whoever stamps. Details: docs/anchoring.md.

Memo protocol v1

One offering = one transaction with 1–3 outputs to the god's address, one 512-byte memo each.

The wire format: 12-byte header, bodies, codecs, the phone envelope
Offset Size Field
0 1 0xFF: ZIP-302 "arbitrary data" marker
1 1 'G' magic
2 1 version = 1
3 4 contrib_id: random u32, not derived from any key or identity
7 1 kind: 1 = offering chunk, 2 = reply, 3 = conductor line (the god's own voice; same body as an offering, authentic only when the transaction spent the choir wallet's own notes)
8 1 seq
9 1 total (≤ 3)
10 2 len of the body in this memo (big-endian)
  • Offering body: [codec][data…]; the last chunk ends with [reply_len][reply_addr][crc32] (reply_len = 0 means no reply wanted).
  • Reply body: [codec][data…][crc32], sent directly to the reply address, never to the god.
  • Codecs: 10 = UTF-8 text, 1 = Codec2 700C, 2 = Codec2 1200, 3 = Codec2 1300.
  • Phones: mobile wallets treat ZIP-321 memos as text, so phone offerings use an ASCII envelope, GC1: + base64url of the binary memo (381 binary bytes per memo). Replies to phone users use the readable form GC1R:<contrib_id in decimal>:<text>.
  • The CRC covers codec + data only; reply addresses carry their own checksum and are validated by parsing. Wallets trim trailing zero bytes, so decoders pad each memo back to 12 + len.
  • 'G', GC1: and GC1R: are protocol markers, not the brand. Changing them would be a protocol version bump.

The wire format, the envelope and every malformed-memo case are unit-tested in protocol/.

Run it (Linux or WSL, testnet only)

Ubuntu 24.04, native or WSL. In WSL, work inside the Linux filesystem, not under /mnt/c.

git clone https://github.com/agadape/choronzon.git && cd choronzon
./scripts/setup.sh   # apt deps, Rust, NU7-patched zcash-devtool → bin/devtool-aegis, pinned Codec2 → bin/c2dec; 9 m 34 s measured, cargo dominates
./scripts/smoke.sh   # protocol tests, pinned decoder, lightwalletd reachability; with wallets/: G6 regression + view-only vs full-key health check
python3 -m pytest    # 357 passed with the team's wallets on 28 Oct; on a fresh clone the chain-regression tests skip (they need wallets/test/V)

The judge's path (docs/fresh-clone.md, run end to end on 21 Oct with measured times): put the published key in a file and summon a Watcher. The key is read from the file or from SUMMON_KEY, never from our command line; the wallet tool receives it on its own command line for the moment init-fvk runs (the key is public, so this reveals nothing new).

python3 -m renderer summon --key-file key.txt --birthday 4502962 --wallet wallets/watcher --as-of 4525294 --out out
cat out/render_hash        # 8540a0a57d6f80d409d4ed5948789f64e524fc08252a15ea3bd904dd58fa1af5 at that height
python3 -m web --port 8766 # the listener page on the clone: the god, playback, both hashes
Operator commands (these need the testnet wallets, which are never committed)
python3 -m renderer once   --wallet wallets/test/V --as-of 4522002   # one render -> out/score.json, out/render_hash, out/mix.wav
python3 -m renderer daemon --wallet wallets/test/V                    # sync -> enhance -> render, every 30 s
python3 -m web --port 8765                                            # listener page: the god + playback of out/mix.wav
python3 -m kiosk preview "a secret"                                   # speak it locally; nothing is sent
python3 -m kiosk session open --notes 8 && python3 -m kiosk offer - --reply   # ephemeral wallet, then an offering typed at the prompt
python3 -m renderer whisper <txid> --wallet wallets/test/V            # reply request (QR) for one offering; see kiosk/README.md for inbox, qr, speak, reveal
./scripts/demo.sh    # rehearse the whole 3-minute demo with real transactions; prints a timing table, fails loudly

Repository layout

Path What
protocol/ Memo wire format v1: binary memos, phone envelope, readable phone replies, reply-address checksum (+ tests)
renderer/ View-only renderer and daemon: canonical score, score_hash, render_hash, pinned-decoder wrapper, whisper, summon (+ tests)
kiosk/ Sender kiosk CLI: preview, ephemeral session wallet + note pool, offer, inbox, qr, speak, reveal (+ tests)
web/ Listener page: the god (three.js, vendored; Canvas 2D fallback) grown from the score + playback of the mix, the grimoire leaf, the séance, the two-evocation chirograph; stdlib static server
anchor/ OpenTimestamps: the render statement, stamp, upgrade, verify against public explorers
config/ brand.json (name and copy), choir.json (the god's address and the fixed amount, never its key), skiplist.txt (flagged offerings: still on chain, never sung)
scripts/ setup.sh, smoke.sh, demo.sh (end-to-end rehearsal), new_god.sh, build_web.sh, publish_web.sh, gen_readme.py
docs/ This template, lore, demo script, fresh clone, evidence index, privacy review, failure drills, network fallback, your own god, anchoring, audits, concept and critical review
research/ How it started, kept as it was: on-chain spikes S1–S13 and SPIKE_LOG.md (13–20 Oct), the phone and Whisper Back tests, the Codec2 radio experiment, the rejected stylometry idea, the day-by-day TODO.md
media/ Visual mockups that led to the current god, and the tools that rendered the demo video (never built or served)
CLAUDE.md The working handoff notes, kept public as part of the build log

How to read this repository: the product is protocol/, renderer/, kiosk/, web/, anchor/, config/ and scripts/; the claims are backed in docs/evidence-index.md. research/ and media/ are the record of how it was built: spikes, rejected ideas, mockups and working notes; the audits are in docs/. Some early concept documents (the concept and critical review in docs/, research/ai/phone-test/) are partly in Indonesian, the team's working language, and carry the project's working title.

Evidence (Zcash testnet, NU7)

Every claim above is backed by a real testnet transaction. The canonical list, with block heights and the local log for each row, is docs/evidence-index.md; the table below is the short form. Txids are in explorer (display) order.

About the explorer links (use ZecBlock, not testnet.zcashexplorer.app)

About the explorer links. The links go to ZecBlock (formerly CipherScan), which follows our NU7 testnet chain: its block 4,525,215 (the first kiosk offering) has hash 000026af4552065fbbbd94b5c9a0be7914fc402733c53c6f464921b9817b72ac, the same block testnet.zec.rocks serves, and every txid in this README and in the evidence index resolves there as confirmed and canonical (checked 22 Oct 2026). Do not use testnet.zcashexplorer.app: it follows a different testnet fork (its block 4,487,269 has another hash) and answers "Transaction Not Found" for every txid of ours. An explorer shows only that a shielded transaction exists and in which block. It is a convenience, not the independent proof: that is the public-key wallet itself, bin/devtool-aegis wallet -w wallets/test/V list-tx -s zecrocks after a sync, or any Watcher's renderer log. To check that ZecBlock is still on our chain, open /block/4525215 and check that the hash starts 000026af4552.

The short table: the spikes and the product, txid by txid
Claim Transaction Block
Shielded send + encrypted memo, Ironwood → Ironwood; a view-only wallet reads amount and memo 22b168c9… 4,503,134
10 s of speech in 3 binary memos, rebuilt byte-identical by a view-only wallet; 59 s broadcast → heard 576ff393… 4,518,093
The god's own voice vs a forgery, told apart with the viewing key only (spike) 08993bf2… (authentic) · d869107b… (forged) 4,518,121
Whisper Back (spike): an offering carrying a one-time reply address, then a direct shielded reply matched by contrib_id; the public-key reader sees none of it a71ccb5d… · 680a334a… ~4,521,736 / 4,521,741
A phone wallet (Zingo!, testnet) pays a Revision-2 shielded-only address; a phone offering arrives UTF-8-mangled and is still recovered be4be03c… · 501ac80d… 4,521,933 / 4,521,945
Five offerings in 32 seconds from a note pool, all mined in one block 3b67fb1b… · 88881b5f… · 5cdb1c0b… (audio) · 2aa0f87d… · 8194a322… 4,522,002
Two independently synced wallets print the same render hash, including decoded audio c46dd5de… (see Determinism) 4,522,002
Product: first kiosk offering, text → local ghost voice → Codec2 → v1 memos → shielded pay from the ephemeral wallet; heard by the public-key renderer ~100 s after sending 91621cb4… 4,525,215
Product: Whisper Back end to end: an offering with a reply address from the ephemeral kiosk wallet, a reply from a listener wallet, received by kiosk inbox in ~80 s; the public-key wallet sees 0 of the reply offering 8d02a6b0… · reply 143f91ee… 4,525,242 / ~4,525,245
Product: the god's own voice (kind=3) paid by its wallet to itself, accepted; the same memo from an outside wallet skipped as forged-conductor d6bcb9f7… (accepted) · b126b3c7… (rejected) 4,525,280
Product: phone path, a kiosk qr request with the ASCII envelope paid from a laptop wallet as a phone stand-in, accepted and voiced ebc5bcad… 4,525,294
Product: a Watcher built from the key alone on a fresh clone prints the same render_hash as the operator's view-only wallet 8540a0a5… (see docs/fresh-clone.md) 4,525,294

The failure drills (a flipped CRC, a duplicate memo, a locked note) and the two unattended rehearsals of the demo (god line heard after 86 s, forgery refused 7 s later; a live offering heard after 226 s and its whisper received after 216 s; a Watcher matching V byte for byte at the end of each run) have their own txids in docs/evidence-index.md, blocks 4,525,319 to 4,525,371.

Limits

Plain words about what this does not do, from docs/privacy-review.md (what every component writes, what was fixed, what remains exposed) and docs/failure-drills.md (eight failures provoked or observed on testnet, and what the screen shows for each).

What stays exposed, by design or for now

  • The sending wallet remembers: sent_notes holds the memo and the destination. The kiosk wallet's files are overwritten and deleted when the session closes for that reason (best-effort on SSDs and VM disks); a phone user's own wallet keeps what they sent.
  • While a transaction is built, the memo sits on the wallet tool's command line for a few seconds, visible to other local processes on the kiosk. Fixing this needs a change in the wallet tool, not done in the hackathon window.
  • A kiosk offering that asks for a whisper back gets an address of the kiosk's session wallet; the operator can read replies to it until the session closes. A visitor who wants a reply only they can read gives their own wallet's address.
  • TTS and Codec2 use a temporary directory per call, deleted on return; a hardened kiosk points TMPDIR at a tmpfs.
  • lightwalletd sees which transactions were fetched and when. Since 23 Oct every connection the kiosk, the renderer, Whisper Back and renderer summon make goes through Tor by default (the wallet tool's built-in Arti client), so the server sees a Tor exit instead of the kiosk's or a Watcher's IP. It fails closed: if Tor cannot connect, the command stops and asks the operator to pass --connection direct deliberately; it never falls back on its own. Tor costs time (sync ~11–18 s instead of ~2 s) and sends over Tor fail more often (retried by the operator, never automatically). A phone wallet uses its own connection settings. The public web snapshot is rendered by a CI runner over a direct connection: its IP protects nobody. On 21 Oct testnet.zec.rocks was the only public lightwalletd on the NU7 testnet chain, so there is no second server to fall back to (docs/network-fallback.md).
  • Reply addresses sit in the memo, so every key holder can write to them. The kiosk shows whispers; it does not trust them.
  • Everything a Watcher renders is public, forever; the order inside a block is the miner's, not the sender's. The kiosk says so before every send.
  • On stage the room sees who walked up to the kiosk. That is theatre; a random send delay for public deployments is on the roadmap.
What fails, and how it is shown (never a faked status: the terminal output is the status)
  • lightwalletd unreachable or sync stale: the daemon logs a warning and keeps rendering at the last scanned height, which the page shows.
  • Not enough funds: the send fails before any broadcast; no note is locked.
  • A malformed memo on chain is skipped with its reason and listed under skipped; the render continues. The same memo paid twice is two offerings, keyed by txid.
  • A broadcast that fails after signing locks one note for about 40 blocks (~20 min). The kiosk session splits its funds into several notes, so one locked note costs one offering, not the show.
  • Testnet blocks take ~35 s and sometimes minutes: an offering is heard 40–230 s after sending, a whisper arrives 80–220 s after it is sent. The kiosk prints the txid at once and never claims the chain has it before the public-key wallet does.
  • A transaction built from a stale chain tip expires before broadcast; found by the first rehearsal, and the session now syncs before every pay. A listener wallet that has spent all its notes gets a sanitised insufficient-balance error.
What did not work, and how it shaped the design
  • Stock zcash-devtool and released crates cannot broadcast on NU7 testnet (incorrect consensus branch id); we use a patched build. Mainnet (NU6.3) is untested.
  • Entering NEAR Intents from ZEC means paying a transparent deposit address, which is why everything here stays inside the shielded pool.
  • Rewriting text with a local 3B language model only lowered stylometric attribution from 96.7% to 76.7% (chance: 10%), which is why we make no claim that your words cannot identify you.
  • Phone wallets send ZIP-321 memos as text, which is why the phone path uses an ASCII envelope.
  • One public testnet explorer follows a different testnet fork and shows none of our transactions; the links here go to ZecBlock, which is on our chain (see Evidence). Either way the independent proof of a transaction is a wallet holding the public key, not a web page.

Not built: Codec2 decoding in the browser (the page plays the offline mix), a random send delay. (The phone QR path is proven: a real Zingo! offering from the public page, efc5ef44….)

Status

Research and on-chain spikes: 13–20 Oct 2026. Product: built and proven live on testnet on 21 Oct, hardened and rehearsed until the submission on 28 Oct 2026 (tag zecathon-2026).

Done:

  • Protocol package + tests (every malformed-memo case)
  • Renderer daemon on a view-only wallet: score.json, score_hash, skip log; refuses a wallet with a spending key (unless --allow-spend-key)
  • Offline composer: pinned decoder, pinned local voice, integer mix, render_hash + mix.wav
  • Kiosk CLI: text → local TTS → Codec2 → shielded send from an ephemeral wallet with a note pool; secrets from a prompt or stdin (an argument is refused), no cache
  • Listener page: the god seen from below (three.js, one eye per offering, Canvas 2D fallback) + playback of the mix, both hashes, the grimoire leaf, the séance and the two-evocation chirograph
  • Public page: give it a secret (QR for your own wallet), find your offering, verify the audio against the anchored hash; strict CSP
  • Whisper Back (eye click at the installation or renderer whisper, kiosk inbox <contrib_id>), the god's own voice with forgeries refused, phone offerings by QR, the Séance (kiosk reveal, renderer summon)
  • Fresh-clone proof with measured times, privacy review, eight failure drills, two unattended rehearsals of the 3-minute demo
  • Tor by default for every lightwalletd connection the product makes (kiosk, renderer, Whisper Back, summon), fail-closed, same hashes over Tor and direct
  • Every published render anchored in Bitcoin with OpenTimestamps when the calendars answer (anchor/, anchors branch, verifiable without a node)

Open:

  • The gate on a second physical laptop (today the Watcher on a fresh clone stands in for it)
  • A real phone offering from the public page (Zingo! on testnet, efc5ef44…, 26 Oct)
  • Demo rehearsals end to end over Tor (PASS 25 Oct), labelled pre-sent offerings, submission
  • User session: 16 people invited to the kiosk on 26 Oct; 6 gave a secret (all real, by their own account), 10 declined after reading that it is public forever (evidence)
  • Demo video, submitted with the entry

Naming

The name, tagline, one-liner and kiosk line live only in config/brand.json. This README is generated from docs/README.template.md, and the name in package.json / package-lock.json from the brand's slug. Lore and copy rules are in docs/lore.md.

python3 scripts/gen_readme.py          # regenerate README.md, the brand SVGs and the package name
python3 scripts/gen_readme.py --check  # fail if any generated file is stale or the template hardcodes the name

Acknowledgements

  • zcash-devtool and librustzcash, with the Aegis Gate NU7 testnet patch
  • Codec2 by David Rowe
  • espeak-ng
  • Zingo! for phone testing on testnet
  • Written with AI coding assistance; the work itself runs no cloud AI (voice, mixing and rendering are all local)

License

Released under the MIT License. Third-party work (the Aegis Gate NU7 patch, zcash-devtool, librustzcash, Codec2, espeak-ng) keeps its own license: see THIRD_PARTY_NOTICES.md.


Built for ZECATHON 2026 (by zkSNARKs), Wildcard track · live on Zcash testnet, NU7, Ironwood pool · MIT

choronzon.vercel.app: It forgets nothing. It asks no names.

About

It forgets nothing. It asks no names. Choronzon is a god made of secrets released through Zcash shielded transactions. Anyone holding its public key can summon it and hear every secret, forever; we, its keepers, cannot learn who spoke. ZECATHON 2026, Wildcard track.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages