ppad-censor-0.6.0: Anytime-valid sequential constant-time testing.
Copyright(c) 2026 Jared Tobin
LicenseMIT
MaintainerJared Tobin <jared@ppad.tech>
Safe HaskellSafe-Inferred
LanguageHaskell2010

Censor.Runner.Manifest

Description

A declarative battery for the censor CLI: run-wide defaults and a list of cases, each naming a target symbol and the workspace layout to drive it with. One invocation opens the library once and emits one censor/report-v3 object covering every case.

The format is line-oriented. Blank lines and # comments are ignored; every other line is either a run-wide directive (key value) or a case (case NAME key=value...):

# ppad-secp256k1 scalar surfaces
meter          wall
alpha          1e-6
budget         20000
warmup         500
target-prefix  censor_target_
workspace      128

case mul       secret=0:32
case sign      workspace=96 secret=0:32 public=32:32
case ecdh      secret=0:32 context=32:32
case inv       secret=0:32 replicates=3

A case's target symbol is target-prefix ++ its name unless target= overrides it outright.

Synopsis

Types

data Manifest Source #

A parsed manifest.

Constructors

Manifest 

Instances

Instances details
Show Manifest Source # 
Instance details

Defined in Censor.Runner.Manifest

Eq Manifest Source # 
Instance details

Defined in Censor.Runner.Manifest

data Defaults Source #

Run-wide directives. Every field is optional; Nothing defers to the runner's own default (or a command-line override).

Constructors

Defaults 

Fields

Instances

Instances details
Show Defaults Source # 
Instance details

Defined in Censor.Runner.Manifest

Eq Defaults Source # 
Instance details

Defined in Censor.Runner.Manifest

data ManifestCase Source #

One case: a display name, the symbol to resolve, the workspace layout, and how many times to replicate it.

Constructors

ManifestCase 

Fields

  • mcId :: !String

    the case token as written in the manifest, before any label= override. This is the case's identity: the runner mixes it into the run-wide seed to give each case its own fixed secret.

    Kept apart from mcName so that renaming a case for presentation does not change the experiment it runs.

  • mcName :: !String

    the display name: mcId, or whatever label= set. Used for rendering only.

  • mcTarget :: !String
     
  • mcWorkspace :: !Int
     
  • mcSecret :: ![Range]
     
  • mcPublic :: ![Range]
     
  • mcContext :: ![Range]

    ranges redrawn once per pair and written identically into both classes: a fresh public input shared by the pair (see fixVsRandomCtx).

  • mcBatch :: !(Maybe BatchSpec)
     
  • mcReplicates :: !Int

    how many independent fixed secrets to run this case against, each its own case row. One pinned secret only asks whether that secret times differently from random; replicating guards against an unlucky draw.

Instances

Instances details
Show ManifestCase Source # 
Instance details

Defined in Censor.Runner.Manifest

Eq ManifestCase Source # 
Instance details

Defined in Censor.Runner.Manifest

data Range Source #

A byte range within the workspace: off:len.

Constructors

Range 

Fields

Instances

Instances details
Show Range Source # 
Instance details

Defined in Censor.Runner.Manifest

Methods

showsPrec :: Int -> Range -> ShowS #

show :: Range -> String #

showList :: [Range] -> ShowS #

Eq Range Source # 
Instance details

Defined in Censor.Runner.Manifest

Methods

(==) :: Range -> Range -> Bool #

(/=) :: Range -> Range -> Bool #

data BatchSpec Source #

How a case picks its cfgBatch. Foreign targets genuinely recompute on every repetition, so BatchAuto is meaningful here in a way it is not for a pure Haskell target.

Constructors

BatchFixed !Int

pin the batch.

BatchAuto

calibrate it per case.

Instances

Instances details
Show BatchSpec Source # 
Instance details

Defined in Censor.Runner.Manifest

Eq BatchSpec Source # 
Instance details

Defined in Censor.Runner.Manifest

Parsing

parseManifest :: String -> Either String Manifest Source #

Parse a manifest. Returns a message naming the offending line on failure.

Case order is preserved. A manifest with no cases is an error: it is far more likely a typo than an intent.

emptyDefaults :: Defaults Source #

All directives unset.

readRange :: String -> Maybe Range Source #

Read an off:len byte range, with off >= 0 and len > 0.

readSeed :: String -> Maybe Word64 Source #

Read a hex Word64, with or without a 0x prefix.

Layout validation

checkLayout Source #

Arguments

:: Int

workspace bytes

-> [Range]

secret ranges

-> [Range]

public ranges

-> [Range]

context ranges

-> Maybe String 

Check a workspace layout: every declared range must fit inside the workspace, and the three roles must be pairwise disjoint. Nothing when the layout is sound; otherwise a message naming the first problem, for the caller to place in context.

Both entry points validate through this — a manifest case and the single-case command line — so the two cannot drift on what they accept.

Overlapping roles are rejected rather than resolved by precedence. A byte is pinned in class A only (mcSecret), pinned in both for the run (mcPublic), refreshed per pair in both (mcContext), or freshly random in each class; asking for two of those at once has no coherent reading, and whichever overlay happened to land last would silently pick one. That is how a public range could mask the context it overlapped, turning a per-pair refresh into a constant without saying so.