System Design

onchain vs. Trusted

A precise breakdown of every system invariant, whether it is enforced by onchain logic or depends on a trusted party.


Summary matrix

PropertyEnforced byTrust level
User authenticationSoroban require_authonchain
Token custodySoroban contract escrowonchain
Balance confidentialityElGamal encryptionCryptography
No overdraft (transfer/withdraw)ZK range proofCryptography
Encrypted balance updatesFairyring homomorphic opsonchain
Replay protectiontx_id monotonic counteronchain
ZKP verification (onchain)Fairyring zkp moduleonchain
ZKP verification (off-chain)FairyPort Rust FFIRelayer
Event relay / livenessFairyPort operationRelayer
Private key safetyUser's device / walletUser
Proof generation correctnessSDK codeSDK

Enforced onchain: Stellar Soroban

These properties are guaranteed by the Soroban smart contract runtime and cannot be bypassed by any party, including FairyPort.

User authentication

owner.require_auth() is enforced by the Soroban runtime before any state mutation. Only the account owner can call deposit, transfer, apply_pending, or withdraw.

Token custody (escrow)

Tokens are transferred into the Soroban contract on deposit and returned on withdrawal callback. The contract holds the escrow; no external party can move funds without a valid relayer callback.

Account existence

AccountCore is persisted in Soroban persistent storage. An account is marked exists=true after creation and finalized=true only after FairyPort relays the Fairyring confirmation.

Pending action state

The Soroban contract tracks a pending_action boolean per account. A second action cannot begin while one is in flight, preventing double-submit races.

Fee collection

If a fee is configured, it is collected by the Soroban contract from the sender's fee token balance atomically within the same transaction as the transfer request.

Encrypted balance storage (Stellar side)

Available(owner, token) and Pending(owner, token) ciphertexts are stored in Soroban persistent storage and updated only by the relayer callback after Fairyring confirmation.

Enforced onchain: Fairyring CosmWasm

These properties are guaranteed by the CosmWasm contract and the Fairyring chain, independent of the relayer.

Whitelist enforcement

Only addresses in the allowed_addresses set can call ExecuteMsg on the CosmWasm contract. Checked on every message before any state mutation.

Replay protection

last_processed_id is stored per account. Any tx_id ≤ stored value triggers an idempotent response without re-running cryptographic operations.

Homomorphic balance arithmetic

All balance updates (add, subtract) are performed as point arithmetic on the Ristretto255 curve. No plaintext amounts are ever computed or stored onchain.

ZK proof verification (onchain mode)

When use_offchain_verify=false, the Fairyring zkp module verifies equality proofs, range proofs, and ciphertext validity proofs before accepting any transfer or withdrawal.

Encrypted multi-denom balance store

available_balance and pending_balance are stored as Vec<DenomCipher> one ElGamal ciphertext per denomination per account. New denoms are initialised to encrypted zero.

Pending transfer ledger

PendingTransfer records are stored indexed by (sender, credit_id) and are used to reconstruct idempotent responses on duplicate tx_id handling.

Overflow protection

pending_credit_counter uses checked_add and returns ContractError::Overflow on wrap-around, preventing integer overflow attacks.

Trusted: FairyPort relayer

These properties depend on the relayer operating correctly. Failures are liveness failures (not safety failures) unless noted.

Event relay (liveness)

FairyPort must observe and forward events from both chains. If offline, user operations hang in pending state on Stellar. This is a liveness not safety assumption.

Off-chain ZKP verification (optional)

safety risk in off-chain mode

When use_offchain_verify=true, FairyPort's Rust FFI library verifies ZK proofs before forwarding. A compromised relayer could pass invalid proofs. Use onchain verification to remove this trust.

Message ordering

FairyPort processes events in ledger/block order. If two events from the same account are reordered, tx_id replay protection on Fairyring prevents double application.

Checkpoint persistence

FairyPort persists the last processed ledger/block to LevelDB. On restart, it replays missed events from the checkpoint. Corrupt checkpoints can be deleted to force a full replay.

Dead-letter management

Permanently failed transactions are written to failed_txs.json. Operators must review and potentially manually resubmit these. FairyPort cannot auto-recover from all failure modes.

Trusted: user & SDK

These properties are the user's responsibility and cannot be enforced by any onchain mechanism.

ElGamal private key custody

The user's private key is generated client-side and never leaves the device. Loss of the key means balances cannot be decrypted. No recovery mechanism exists onchain.

Client-side proof generation

ZK proofs are generated in the browser/app using the SDK. A buggy or malicious SDK could generate incorrect proofs. Use the official @fairblock/stellar-stabletrust SDK.

Accurate amount inputs

The user specifies the transfer/withdrawal amount. The SDK encodes this as a plaintext scalar embedded in the ZK proof. No onchain mechanism bounds transfer amounts beyond the range proof.