Styx ferry messaging
Forward-secret messaging and VSL ferry proof control plane using Hopper crypto, precompile checks, and verifier CPI.
Hopper port of the Styx ferry and messaging control plane. The sample keeps Signal-style X3DH, Double Ratchet state, and XChaCha20-Poly1305 payload encryption in the client protocol, while Hopper enforces the deterministic on-chain boundaries: signed prekeys, bounded message envelopes, proof input checks, and verifier CPI.
What Hopper checks
- The ferry config stores a pinned verifier program, a Keccak-derived BN254 field domain separator, and aggregate message/prekey/proof counters.
publish_prekey_bundlerequires an Ed25519 precompile sibling instruction and checks that its offset table points at the owner key, signed prekey, and signed-prekey signature supplied to Hopper.send_ratchet_messageaccepts only non-empty bounded ciphertext, updates a monotonic counter, and emits the ratchet key plus sealed-message hash for off-chain delivery/indexing.submit_zk_ferryaccepts exactly the Styx 513-byte v2 proof envelope, checks public input domain/base-fee/fee-tier fields, and CPIs into the pinned verifier program.
Instruction Surface
| Tag | Handler | Purpose |
|---|---|---|
0 |
init_config |
Create the ferry config, derive the Styx VSL domain separator, and pin a verifier program. |
1 |
publish_prekey_bundle |
Publish a user prekey bundle after verifying the signed prekey with an Ed25519 precompile sibling instruction. |
2 |
refresh_prekey_bundle |
Rotate the one-time-prekey root and update the bounded prekey count. |
3 |
init_thread |
Create a sender-to-recipient ratchet thread. |
4 |
send_ratchet_message |
Emit a bounded ciphertext envelope and update monotonic ratchet state. |
5 |
submit_zk_ferry |
Validate the Styx proof envelope, check fee/domain public inputs, and CPI into the verifier. |
VSL Proof Boundary
The proof format is versioned and fixed-size:
byte 0 proof version, currently 2
bytes 1..257 proof bytes consumed by the verifier
bytes 257..513 eight 32-byte public inputs
Public inputs are read in this order: root, nullifier, output commitment 0, output commitment 1, asset id, domain separator, fee tier id, and base fee. The domain separator must equal Hopper's config value, the base fee must match the configured lamports value, and the fee tier must stay within the program cap.
Local Checks
cargo check -p hopper-styx-ferry
cargo run -q -p hopper-cli -- solana-check --manifest-path examples/hopper-styx-ferry/Cargo.toml
Build with the Solana SBF toolchain when you want to deploy the sample:
cargo build-sbf -- -p hopper-styx-ferry
solana program deploy target/deploy/hopper_styx_ferry.so --url devnet
