boundcheck

package
v0.9.15 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 22, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Overview

Package boundcheck is a generic, application-agnostic homomorphic bound check (ARES-BC, generalizing ARES v2.6 SC-5). Between input submission and scoring, it computes — per party uniformly — a single safe-to-decrypt squared magnitude ‖x - c‖² (public center c) over the party's encrypted input, threshold-decrypts it across the participant quorum, and aborts the session via an application-supplied ViolationHandler when the value falls outside the committed [lo, hi].

NormCircuit (c=0, the SC-5 embedding norm check) and DistanceBoundCircuit (geo-radius, multi-dimensional resource budgets) are the built-in squared-magnitude circuits. Inputs must be multi-dimensional: the phase refuses dim < 2, because for a scalar the squared magnitude reveals the value up to a sign (scalar range needs a homomorphic-comparison circuit). Circuit depth is determined offline with pkg/ares/crypto/fhecalib.

The phase computes each enc_check and Provides them under CtxBoundCheckCiphers; the consuming application is responsible for unicasting them to participants, who reply with bound_check.partial (their partial decrypt of each check ciphertext).

Index

Constants

View Source
const (
	CtxEncryptedInputs       = "boundcheck.encrypted_inputs"  // map[string][]byte: party -> lineage-committed ciphertext
	CtxInputDim              = "boundcheck.input_dim"         // int: slot count of each input vector
	CtxEvalKeyBundle         = "boundcheck.eval_key_bundle"   // serialized cgo.EvalKeyFinal
	CtxJointPublicKey        = "boundcheck.joint_public_key"  // []byte: pk_joint
	CtxBoundCheckCiphers     = "boundcheck.check_ciphertexts" // map[string][]byte: party -> enc_check (phase Provides; app unicasts)
	CtxBoundCheckCommitments = "boundcheck.check_commitments" // map[string][]byte: party -> H(enc_check ‖ H(enc_x) ‖ session_id)

	// CtxBoundCheckHandle / CtxBoundCheckFuse let a shared-runner session-service
	// supply a PER-SESSION fhecalib.ContextHandle and fuse function via the
	// SessionContext (set by the app's trigger at BeginSession), for apps that
	// cannot bake a single handle into a process-shared Phase. They are honored
	// only when the construction-time handle/fuse (NewPhaseWithCrypto) are nil.
	CtxBoundCheckHandle = "boundcheck.handle" // fhecalib.ContextHandle (server-set)
	CtxBoundCheckFuse   = "boundcheck.fuse"   // func(partials [][]byte, nSlots int) ([]float64, error) (server-set)
)

Context keys: the app's input-submit phase Provides these; the bound phase Requires them. (Use defaults.CtxParticipants for the participant set — NOT a boundcheck-local copy.)

View Source
const MsgBoundPartial = "bound_check.partial"

MsgBoundPartial is the message type each party replies with: its partial decrypt of the broadcast check value.

Variables

This section is empty.

Functions

This section is empty.

Types

type Bound

type Bound struct{ Lo, Hi float64 }

Bound is the closed interval the decrypted check value must lie in.

type BoundCircuit

type BoundCircuit interface {
	fhecalib.CircuitUnderTest
	Bound() Bound
	// Dim returns the expected input-vector dimension (slot count) for this
	// circuit. Enter rejects sessions where CtxInputDim != Dim() to prevent
	// silently summing the wrong number of slots and producing a wrong norm.
	Dim() int
}

BoundCircuit is the homomorphic map producing the single safe-to-decrypt check value for one encrypted input, plus the bound the value must satisfy. It is also a fhecalib.CircuitUnderTest, so the exact circuit run at phase runtime is the one calibrated for depth.

type DistanceBoundCircuit

type DistanceBoundCircuit struct {
	Center []float64
	Lo, Hi float64
}

DistanceBoundCircuit checks ‖x - Center‖² ∈ [Lo, Hi] (public Center). Covers geographic-radius and multi-dimensional resource budgets.

func (DistanceBoundCircuit) Bound

func (c DistanceBoundCircuit) Bound() Bound

func (DistanceBoundCircuit) Dim

func (c DistanceBoundCircuit) Dim() int

Dim returns the expected input-vector dimension for this circuit.

func (DistanceBoundCircuit) Eval

func (c DistanceBoundCircuit) Eval(h fhecalib.ContextHandle, enc [][]byte) ([]byte, error)

func (DistanceBoundCircuit) Expected

func (c DistanceBoundCircuit) Expected(in [][]float64) []float64

func (DistanceBoundCircuit) Inputs

func (c DistanceBoundCircuit) Inputs() [][]float64

Inputs returns the center itself (squared distance 0 — a representative in-bound point for calibration).

func (DistanceBoundCircuit) Name

type NormCircuit

type NormCircuit struct {
	Eps  float64
	NDim int
}

NormCircuit is the SC-5 embedding norm check: center 0, check value ‖x‖², bound [1-Eps, 1+Eps]. NDim is the embedding dimension (the number of CKKS slots summed); it must match the input vector length at runtime (CtxInputDim).

func (NormCircuit) Bound

func (c NormCircuit) Bound() Bound

func (NormCircuit) Dim

func (c NormCircuit) Dim() int

Dim returns the expected input-vector dimension for this circuit.

func (NormCircuit) Eval

func (c NormCircuit) Eval(h fhecalib.ContextHandle, enc [][]byte) ([]byte, error)

func (NormCircuit) Expected

func (NormCircuit) Expected(in [][]float64) []float64

func (NormCircuit) Inputs

func (c NormCircuit) Inputs() [][]float64

Inputs returns a representative unit-norm vector of length NDim (for depth calibration).

func (NormCircuit) Name

func (NormCircuit) Name() string

type Params

type Params struct {
	EpsNorm float64 // noise floor; nu <= EpsNorm is treated as in-bound
	NuHard  float64 // hard-violation threshold on the distance outside the bound
}

Params are ARES-core's detection bands (distinct from any app penalty curve).

func DefaultParams

func DefaultParams() Params

DefaultParams mirror ARES v2.6 SC-5 reference values.

type Phase

type Phase struct {
	// contains filtered or unexported fields
}

Phase is the ARES v2.6 Phase-1c bound-check round. One instance is shared across every session that uses it; per-session state lives in SessionContext.

Construct via NewPhase (stub mode, no FHE at runtime) or NewPhaseWithCrypto (real mode, FHE-backed Enter and Exit).

func NewPhase

func NewPhase(
	circuit BoundCircuit,
	handler ViolationHandler,
	params Params,
	entryState phase.SessionState,
	exitState phase.SessionState,
) *Phase

NewPhase returns a Phase in stub mode (Enter/Exit no-op the FHE path). Suitable for compose-time structural tests and pipelines where crypto is injected later via NewPhaseWithCrypto.

entryState is the session state that triggers this phase (commonly defaults.StateScoring). exitState is the state to transition to on completion.

func NewPhaseWithCrypto

func NewPhaseWithCrypto(
	circuit BoundCircuit,
	handler ViolationHandler,
	params Params,
	entryState phase.SessionState,
	exitState phase.SessionState,
	handle fhecalib.ContextHandle,
	fuse func(partials [][]byte, nSlots int) ([]float64, error),
) *Phase

NewPhaseWithCrypto returns a Phase in real mode. handle is a provisioned fhecalib.ContextHandle (e.g. from fhecalib.NewContextHandle); fuse is the partial-fusion function (e.g. a closure over cgo.FuseCKKSPartialsForContract bound to the session's ContractParams).

Neither handle nor fuse is called on the happy path when no violations occur and Enter reaches the no-compute branch — they are invoked only when CtxEncryptedInputs is non-empty (Enter) and when partials have been accumulated (Exit).

func (*Phase) CheckComplete

func (p *Phase) CheckComplete(ctx *phase.SessionContext) bool

CheckComplete returns true once every participant has submitted a MsgBoundPartial reply (N-of-N quorum).

func (Phase) ConsumedMessageTypes

func (Phase) ConsumedMessageTypes() []string

ConsumedMessageTypes lists the partial-decrypt reply type.

func (*Phase) Enter

func (p *Phase) Enter(ctx *phase.SessionContext) error

Enter enforces invariant #5 (dim refusal), then computes enc_check_i for every party in CtxEncryptedInputs and stores the results in CtxBoundCheckCiphers. In stub mode (handle == nil), the FHE compute is skipped and CtxBoundCheckCiphers is not set; structural tests that do not need real ciphertexts can use stub mode.

The consuming app must read CtxBoundCheckCiphers immediately after Enter and unicast each enc_check_i to the corresponding participant.

func (*Phase) EntryState

func (p *Phase) EntryState() phase.SessionState

func (*Phase) Exit

func (p *Phase) Exit(ctx *phase.SessionContext) error

Exit assembles the N-party quorum of partial decrypts for each check ciphertext, fuses them, classifies the result against the circuit's Bound, and invokes the ViolationHandler for violating parties before aborting with ErrAppAttributable. In stub mode (fuse == nil), Exit returns nil unconditionally.

Each sender's MsgBoundPartial payload is a JSON-serialised map[string][]byte keyed by checkedParty → that sender's partial of that check ciphertext. Exit collects, for each checked party i, all N senders' partials for i into a [][]byte slice of length N and calls fuse(partialsForI, 1). This is the correct threshold-decrypt quorum: all N parties must contribute a partial per ciphertext for the fusion to produce a valid plaintext.

On violation:

  1. handler.OnViolation is called for every violating party (sequentially).
  2. A random jitter sleep is introduced to prevent timing oracles.
  3. A generic fmt.Errorf("%w: bound violation", ErrAppAttributable) is returned — no party identifiers or fused values are included to avoid leaking information to the transport layer.

On all-OK: returns nil; the runner advances the session to ExitState.

func (*Phase) ExitState

func (p *Phase) ExitState() phase.SessionState

func (Phase) InternalStates

func (Phase) InternalStates() []phase.SessionState

InternalStates returns nil — the bound-check round has no sub-states.

func (Phase) Lifetime

func (Phase) Lifetime() phase.Lifetime

Lifetime is per-session: check ciphertexts and partial decrypts are not reusable across sessions.

func (Phase) Name

func (Phase) Name() string

func (*Phase) OnMessage

func (p *Phase) OnMessage(ctx *phase.SessionContext, _ string, from string, payload []byte) error

OnMessage accumulates each party's partial-decrypt map into the internal bucket. Each MsgBoundPartial payload is a JSON-serialised map[string][]byte keyed by checkedParty → that sender's partial of that check ciphertext. Multiple submissions from the same sender overwrite (only the most recent map is kept per sender, which is the correct N-of-N pattern).

func (Phase) Provides

func (Phase) Provides() phase.ContextSchema

Provides declares the SessionContext keys this phase writes. The consuming application MUST read CtxBoundCheckCiphers after Enter returns and unicast enc_check_i to each participant before their MsgBoundPartial reply is expected.

CtxBoundCheckCommitments is also written by Enter (in real mode) alongside CtxBoundCheckCiphers. Each commitment binds the server-computed enc_check_i to the lineage-committed enc_x_i via

H(enc_check_i ‖ H(enc_x_i) ‖ session_id)

Client-side verification of the commitment is the consuming app's responsibility: the framework produces and stores the binding but does not enforce it on the protocol path.

func (Phase) Requires

func (Phase) Requires() phase.ContextSchema

Requires declares the SessionContext keys this phase reads from the preceding submission phase. The consuming app's submission phase must Provide all of these.

Note: CtxSecretShares is deliberately NOT required here. The server does not hold individual secret shares; each party's partial-decrypt blob already encodes its contribution and FuseCKKSPartialsForContract operates directly on the wire blobs.

func (Phase) RunsAt

func (Phase) RunsAt() phase.RunsAt

RunsAt is inline: the round is triggered by session state and driven by inbound MsgBoundPartial messages.

type Severity

type Severity int

Severity classifies a bound-check outcome.

const (
	SeverityOK Severity = iota
	SeveritySoft
	SeverityHard
)

type ViolationHandler

type ViolationHandler interface {
	// OnViolation is called synchronously before the jitter sleep and the
	// session abort. The returned error is advisory (for application-layer
	// logging or side effects); the framework discards it and aborts the
	// session regardless of what OnViolation returns.
	OnViolation(ctx *phase.SessionContext, party string, nu float64, sev Severity) error
}

ViolationHandler is the application boundary: invoked once per violating party before the session aborts. nu is the distance outside the bound; the app maps it to a domain penalty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL