Skip to main content
This is the heart of Confiroll. The design goal is a single invariant:
Confiroll holds zero user secrets. Amounts are hidden from the public, from other employers, and from Confiroll itself. This is what Confiroll-blind means.
Everything else (the two keys, browser-side proving, the Fork B fee-bump, the employer-held escrow) exists to make that invariant true while still being usable with real wallets and gas-free for you.

Two secrets per user

Every actor, employer and contractor alike, holds two independent secrets:

Stellar ed25519 key

A standard Stellar keypair (G... address). It sources and signs the on-chain transaction. This is what SEP-10 authenticates and what the fee-bump wraps. Lives in the wallet.

Confidential scalar sk

A field element unrelated to the Stellar key. It authorizes movement of the hidden balance inside the zero-knowledge proof, and decrypts your own amounts. Lives on the device.

Why two keys

The two secrets do two different jobs, and neither can do the other’s:
  • The Stellar ed25519 key authorizes transactions. It sources the on-chain call, signs the envelope, and satisfies require_auth. It is what SEP-10 challenges and what the fee-bump wraps. It says nothing about amounts.
  • The confidential scalar sk authorizes and reads value. It proves you own the hidden balance you are spending, and it decrypts amounts encrypted to you. It never signs a Stellar transaction.
Splitting these means the on-chain identity that the network sees is fully separated from the value layer. The chain and any observer see a signed transfer between two G-addresses. Only a holder of the right viewing key can read the amount. One key authorizes, the other key decrypts and proves, and they are cryptographically independent.

Derivation

From sk and the token’s address, the client derives everything else. The Stellar key is never involved in the confidential math: H is a Grumpkin generator with no known discrete-log relationship to Stellar’s base point, so the two key systems are cryptographically independent. Because vk folds in addr_f, a key set derived for one confidential-token deployment is meaningless against another.
In the custody model, sk is derived on your device as a KDF over a wallet signature on a fixed Confiroll-domain message, so your confidential key is deterministic from your wallet and never has to be stored or uploaded. The testnet client scripts also generate sk locally, as a fresh random scalar per run. Either way, sk never reaches a Confiroll server.

How sk is derived and used, on-device

The full life of the confidential key stays on your machine. Nothing in this sequence sends a secret to a server:
1

Sign a fixed domain message

Your wallet signs a fixed Confiroll-domain message with your Stellar ed25519 key. This is a plain signTransaction-style signature, and the message is constant, so the same wallet always produces the same signature.
2

Derive sk from the signature

The client runs a KDF over that signature to produce the scalar sk. Because the input is deterministic, sk is deterministic: you can re-derive it on any device with the same wallet, and you never have to store or back it up separately.
3

Derive the rest locally

From sk and addr_f the client computes vk, Y, and PVK with the derivations in the table above. Y and PVK are public; sk and vk stay on the device.
4

Prove and decrypt with sk, never send it

sk and vk feed the bb.js prover (for transfer and withdraw) and the decryptor (to read your own balances). Only public outputs leave the browser: a signed transaction, a proof, and public keys. The secret itself never travels.
Because sk derives from a wallet signature, it inherits the wallet’s security. Keep the wallet safe. There is no server-side copy of sk to fall back on, which is the point: Confiroll cannot recover it, and neither can an attacker who breaches Confiroll.

Why privacy holds

Two independent facts combine:
  1. Amounts are encrypted on-chain. A confidential_transfer carries no plaintext amount argument. The value lives only inside the proof and encrypted event fields. Reading an amount requires a viewing key.
  2. Confiroll never has a viewing key. sk (and therefore vk) is derived and used only on your device. The server receives a signed transaction, never a secret.
So the only parties who can read a payout’s amount are the payer, the payee, and the holder of the escrow key, never Confiroll, and never the public.
Privacy here is confidentiality, not anonymity. The transaction graph (who paid whom, and when) is public on-chain. Confiroll hides amounts, not the existence or the parties of a payment. Confiroll never claims payments are “unlinkable.”

What Confiroll can and cannot do

The invariant becomes concrete when you list it as capabilities. Confiroll’s reach stops at public data and fee sponsorship: The pattern: Confiroll can help you pay (by sponsoring the fee) and can help others pay you (by publishing your public keys), but it can never spend, read, or recover.

Fork B: the pivotal authorization decision

The confidential token’s confidential_transfer(from, to, data) calls from.require_auth(). How that auth is satisfied depends on who is the transaction source, and that choice decides whether the whole thing is usable and non-custodial.
If a Confiroll/SDP channel account is the tx source, require_auth(from) surfaces as a Soroban auth entry that the employer must sign with signAuthEntry. That path is broken in Freighter and unimplemented in a large share of wallets. It only works with a custodial in-process key or a smart-contract wallet. Rejected.
Fork B is the only path that is simultaneously non-custodial, 0-XLM, and works with real wallets. It’s why the core endpoint is POST /transfer (fee-bump a browser-signed transfer), not a custodial “send payment” call. See Fee sponsorship for the sponsor and its guard-rails.

Where every secret lives

Confiroll’s only keys are operational: the fee-bump sponsor, the testnet USDC faucet issuer, and SDP’s channel/distribution accounts. None of them can read an amount or spend your balance.

Consequences

Because proving, sk derivation, and signing are all client-side, the confidential heavy-lifting lives in the browser, and payroll-api stays thin:
  • Client-side: wallet signature to sk; zero-knowledge proving (bb.js WASM) for transfer and withdraw; contractor self-registration; employer-held escrow decryption for disclosure.
  • Server-side: session auth; the Fork B fee-bump submit; a directory of public data (recipient address + public viewing key); the testnet faucet.
Escrow-key custody is what keeps Confiroll blind: because the employer holds the disclosure key for their own payroll, compliance disclosures work without Confiroll ever being able to read a payout.

FAQ

No. sk derives on your device from a wallet signature, and there is no server-side copy. Restore the wallet and you can re-derive sk deterministically from the same signature. Lose the wallet with no backup, and neither you nor Confiroll can recover the key. That is the direct cost of Confiroll holding zero user secrets.
Holding sk would let Confiroll decrypt every amount, which breaks the Confiroll-blind invariant. The whole architecture exists so that no single Confiroll breach can expose payouts. Onboarding friction (a contractor must register a wallet and self-register before a first payout) is the accepted trade for that guarantee.
No. The Stellar ed25519 key sources and signs transactions and satisfies require_auth. The confidential math runs over Grumpkin with the scalar sk, and H has no known discrete-log relationship to Stellar’s base point. The only crossover is that a wallet signature seeds the KDF that produces sk; after that, the two key systems are independent.