Confiroll holds zero user secrets. Amounts are hidden from the public, from other
employers, and from Confiroll itself. This is what Confiroll-blind means.
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
skauthorizes 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.
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
Fromsk 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.Why privacy holds
Two independent facts combine:- Amounts are encrypted on-chain. A
confidential_transfercarries no plaintext amount argument. The value lives only inside the proof and encrypted event fields. Reading an amount requires a viewing key. - Confiroll never has a viewing key.
sk(and thereforevk) is derived and used only on your device. The server receives a signed transaction, never a secret.
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’sconfidential_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.
- Fork A: channel-account source (rejected)
- Fork B: employer-as-source + fee-bump (chosen)
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.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.
FAQ
If I lose my wallet, can Confiroll restore my confidential key?
If I lose my wallet, can Confiroll restore my confidential key?
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.Why not let Confiroll hold sk to make onboarding easier?
Why not let Confiroll hold sk to make onboarding easier?
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.Does the Stellar key ever touch the confidential math?
Does the Stellar key ever touch the confidential math?
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.