When you use a cryptocurrency exchange, the flow is simple: you send coins to an address, and the service sends different coins back.
But there's a gap. Between sending your deposit and receiving your payout, you're trusting the service completely. There's no contract, no proof, no verifiable commitment. The service could change the rate, swap the destination, or simply not pay out — and you'd have no cryptographic evidence of what was promised.
We built a system that eliminates this gap.
The problem: trust without proof
Traditional exchanges solve this with legal contracts, compliance departments, and regulatory oversight. That works — but requires accounts, identity verification, and data retention.
Instant exchange services skip all of that. You paste an address, send crypto, get crypto back. The trade-off: simplicity and speed, but zero proof of the terms you agreed to.
We wanted both: no account required, but cryptographic proof of every exchange.
The solution: signed documents
Before a user sends any funds, our system generates a Guarantee — a plain-text document containing the complete exchange snapshot:
-----BEGIN 0TRACE DOCUMENT-----
Type: GUARANTEE
Service: 0trace
Order: 8f3a...
From Asset: BTC
To Asset: XMR
Quoted Deposit: 0.05 BTC
Quoted Payout: 12.847 XMR
Rate: 1 BTC = 256.94 XMR
Rate Type: FIXED
Deposit Address: bc1q...
Payout Address: 4A8Bx...
Expires: 2026-05-03T15:00:00.000Z
-----END 0TRACE DOCUMENT-----
-----BEGIN SIGNATURE-----
a3f8c9...
-----END SIGNATURE-----
-----BEGIN PUBLIC KEY-----
302a300506032b6570...
-----END PUBLIC KEY-----
This document is signed with Ed25519 on an isolated server. The user downloads it as a .txt file before sending any money.
After the exchange completes, a Receipt is generated — same structure, but containing the actual settled amounts and transaction hashes for both the deposit and payout.
Why Ed25519
- RSA-2048: 256-byte signatures, slow, non-deterministic
- ECDSA P-256: compact signatures, but requires a random nonce — nonce reuse breaks the key (this has happened in production systems)
- Ed25519: 64-byte signatures, 32-byte keys, deterministic (same input = same signature), fast, native Web Crypto API support
Deterministic signing means no risk of nonce reuse. Compact output keeps the document small. Native browser support means verification needs zero external dependencies.
Signer isolation
The signing key never touches the web server.
The web server builds the document text and sends it to a separate signer machine over an internal network. The signer holds the private key, signs the text, and returns the hex-encoded signature. The web server assembles the final file.
Even if the web server is fully compromised, the attacker cannot forge signatures — the key material lives on a separate machine with no inbound access except the signing endpoint.
Signing (Node.js)
import { sign, createPrivateKey } from 'node:crypto';
function signDocumentText(text) {
const key = createPrivateKey({
key: Buffer.from(process.env.SIGNING_KEY, 'hex'),
type: 'pkcs8',
format: 'der',
});
return sign(null, Buffer.from(text, 'utf8'), key).toString('hex');
}
The null first argument means "use the algorithm from the key." For Ed25519, Node.js applies signing directly — no separate hash step, Ed25519 uses SHA-512 internally.
Offline verification (Web Crypto API)
Verification happens entirely in the browser. No network requests. The user pastes the document into a verify page, and the browser checks the signature locally:
async function verifyDocument(rawDocument) {
const parsed = parseSignedDocument(rawDocument);
const publicKey = await crypto.subtle.importKey(
'spki',
hexToBytes(PUBLISHED_PUBLIC_KEY),
'Ed25519',
false,
['verify']
);
return crypto.subtle.verify(
'Ed25519',
publicKey,
hexToBytes(parsed.signature),
new TextEncoder().encode(parsed.text)
);
}
Three design decisions that matter:
The public key is hardcoded in the client, not extracted from the document. The document includes the key for reference, but verification always uses the published key from source code. This prevents an attacker from substituting both the signature and the key.
Web Crypto API works in all modern browsers including Tor Browser. No npm packages, no WASM, no external libraries.
The format is human-readable. Not JWT, not protobuf, not binary. You can cat the file and read every field.
What gets signed
Only the text between -----BEGIN 0TRACE DOCUMENT----- and -----END 0TRACE DOCUMENT----- is signed. The signature and public key blocks are not part of the signed content. This makes the boundary unambiguous — you always know exactly what bytes were committed to.
Two documents, two moments
Guarantee — available while the order is pending, before the user sends funds. Contains the quoted rate, both addresses, amounts, and expiry time. This is the service's cryptographic commitment: "send X to this address, receive Y at that address."
Receipt — available after the payout. Contains actual settled amounts and both transaction hashes, verifiable on their respective blockchains. Proof that the exchange happened as committed.
Why plain text, not JWT
- JWTs require base64 decoding to read. Users shouldn't need tooling to see what they agreed to.
- JSON key ordering is not guaranteed by spec. Plain text has no ordering ambiguity.
- Plain text works everywhere: email it, print it, store it in a password manager. No parser needed.
What this means
A user who exchanges BTC for XMR through 0trace gets:
- A Guarantee proving what was committed — signed before they sent money
- A Receipt proving the exchange completed — with on-chain TX hashes
- Offline verification that works years later, without our servers being online
- No account, no registration, no identity attached to any of it
The signed document is the closest thing to a trustless receipt that a centralized exchange can offer.
0trace is an instant private cryptocurrency exchange. Every order is backed by an Ed25519 signed Guarantee and Receipt, verifiable offline at 0trace.io/verify.
Top comments (0)