# Guarantees and security (/developers/security)



This page states what holds, what enforces it, and how each shape fails.

## Custody [#custody]

The user's wallet holds the keys and signs. Zerochord never takes custody, and no component
holds a user key.

The wallet that signs is the wallet that pays the network fee. No fee sponsoring code path
exists, and `cardano-swap integrity` proves that by scanning the source rather than asserting it.
See [Footprint and measurement](/developers/verification/footprint).

## One signature per commitment [#one-signature-per-commitment]

A user approves exactly one transaction to commit a swap.

For a Kernel route, that transaction settles the swap. For a Commitment route, it creates the
order carrying the binding minimum. A later cancel is its own transaction and its own signature,
because it is a separate decision.

## The minimum output is enforced on chain [#the-minimum-output-is-enforced-on-chain]

The number shown before signing is the number written into the transaction.

The builders take the `Quote` object itself, so there is no second copy of `minimumOut` that could
drift from the one displayed. The command line's `--min-out` can only tighten the bound, and the
builder refuses anything looser than the quote.

The deduction that derives the minimum from the quoted output uses a **ceiling**, so the on-chain
bound is never looser than the slippage allowance implies.

## What enforces the minimum, per shape [#what-enforces-the-minimum-per-shape]

| Route                     | Enforced by                                                                             | Failure mode                                                                                                              |
| ------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Kernel, one or many pools | The venue's own deployed validator, checking the pool outputs the transaction pays      | The transaction fails validation. Nothing settles and nothing fills worse.                                                |
| Commitment, one venue     | The `minimum_receive` in the order datum, checked by the venue's order validator        | The batcher cannot fill without breaking the bound, so the order rests. Cancel it to recover the funds.                   |
| Mixed                     | The settlement validator's `minimum_output`, checked across the whole claim transaction | Claim only succeeds at or above the aggregate. After expiry the refund path returns everything the settlement UTxO holds. |

The Kernel guarantee was proved on preprod rather than assumed: an under-payment of **one base
unit** is rejected by the deployed validator.

The settlement validator's guarantee, stated exactly: &#x2A;*never worse than the quoted minimum, or
recover the pieces.** If one leg fills and another does not, the refund returns a mix of input and
output assets rather than the original input, because the filled leg cannot be reversed. That is
why the router prefers a single class route whenever its net output is within
`routing.singleClassPreferenceBps` of a mixed one.

## Timing is never promised where it is not owned [#timing-is-never-promised-where-it-is-not-owned]

A Kernel route settles in the transaction the user signs.

A Commitment route is filled by a permissioned batcher. The rate is bound by the datum and the
timing is guaranteed by nobody. Every screen that shows such a route says so, from one source of
wording keyed on the route's atomicity, and every one of them offers a cancel path.

Orders are written with an expiry, so an order cannot rest forever. After it passes, anyone may
cancel the order and keep up to the configured tip, with the rest returning to the owner.

## The validity window [#the-validity-window]

The ledger refuses a transaction whose upper validity bound has passed, whatever the validator
would have said.

The deployed Dano Finance validator caps the interval at roughly 360 slots, so a signed swap stops
being submittable within a few minutes. The web application checks the window before sending,
raises `ValidityWindowClosedError` naming the cause, sends nothing, and refetches the quote. It
does not let the node answer with `OutsideValidityIntervalUTxO` and leave the user guessing.

## The double satisfaction guard [#the-double-satisfaction-guard]

Both settlement redeemers measure value by reading the transaction outputs that pay the owner. Two
settlement UTxOs spent together would each read the same outputs and each conclude, correctly on
its own terms, that it was satisfied.

The validator restricts a transaction to exactly **one** input carrying its own payment
credential. With a single claimant there is no shared measurement left to double count. See [The
settlement validator](/developers/onchain/settlement-validator#the-double-satisfaction-guard).

## Cryptographic primitives [#cryptographic-primitives]

| Use                         | Primitive                                                                                                         |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| Asset fingerprints (CIP-14) | blake2b with a 20 byte digest over policy id concatenated with asset name, bech32 encoded with the `asset` prefix |
| Pyth signed price updates   | Ed25519 over the payload bytes, verified against the trusted signers read from the on-chain Pyth state            |
| Validators                  | Plutus V3, compiled by Aiken v1.1.23                                                                              |
| Datums and redeemers        | Plutus data, encoded and decoded by this project's own codec against the blueprint in `onchain/plutus.json`       |

Fingerprints are recomputed on every configuration load and compared against the recorded value,
so an altered policy id throws at load rather than producing a wrong quote later.

Pyth's trusted keys are read from the chain rather than configured or hard coded, because Pyth
rotates them and the on-chain state is what the validator itself checks against. The off-chain and
on-chain checks therefore agree by construction.

## Key handling [#key-handling]

The operator wallet is a command line concern only. It signs deployments, measurement runs and
operator swaps. It never signs for a user, and no user path touches it.

* It lives in `secrets/<network>.operator.json`, under the directory named by
  `CARDANO_SWAP_SECRETS_DIR` or `secrets/` at the repository root.
* It is written with mode 600 and the mode is set again after writing.
* `secrets/` is excluded from version control, along with `*.skey`, `*.vkey`, `*.seed` and
  `*.mnemonic`.
* `wallet create` refuses to overwrite an existing wallet unless `--overwrite` is passed.
* `loadOperator` refuses to load on mainnet unless the caller passes an explicit allow-mainnet
  flag, because every routine operation runs on a test network and a mainnet deployment must be a
  deliberate act.

## Secrets and the browser bundle [#secrets-and-the-browser-bundle]

Anything compiled into a browser bundle is public. Two consequences are enforced in code.

**The browser build supports Koios only.** Blockfrost authenticates with a project id, and a
project id in a bundle every visitor downloads is a published secret. Configuring another primary
provider for a browser deployment throws with that reason.

**The optional Koios bearer token is a rate limit raise, not a credential to protect.** To raise
the limit without publishing a token, put a proxy in front of the provider and point
`providers.koios.baseUrl` at the proxy.

## What the product refuses to build [#what-the-product-refuses-to-build]

* **No rewards, rebates, points or referral payouts.** Nothing pays anyone to transact.
* **No fee sponsoring.** The capability does not exist.
* **No mock price source presented as an oracle, and no fabricated data standing in for a real
  reading.** Where something cannot be obtained, the code throws with the reason. A quote engine
  that quoted from a guessed price would be worse than one that refused to quote.
* **No silent stale data.** A failed quote shows no quote. A failed refetch never leaves previous
  numbers on screen in front of someone about to sign.

## Failure reporting [#failure-reporting]

Every failure names what failed and why.

A provider that cannot be reached is not the same as a pair with no route, and the interface names
each differently, so an outage never reads as a fact about the market.

Where a figure has no meaning, `measure` prints `not computable` and the reason rather than a zero.
Where a baseline cannot be recovered, `audit` prints `baseline not reconstructible` rather than
substituting present day pool state for a past one.

## Related [#related]

<Cards>
  <Card title="The settlement validator" href="/developers/onchain/settlement-validator" />

  <Card title="Execution classes" href="/developers/concepts/execution-classes" />

  <Card title="Footprint and measurement" href="/developers/verification/footprint" />
</Cards>
