Getting Started

This guide shows the minimal end-to-end workflow for Quantitative Information Flow (QIF):

Imports

from qiflib.core.secrets import Secrets
from qiflib.core.channel import Channel
from qiflib.core.hyper import Hyper
from qiflib.core.gvulnerability import GVulnerability
from qiflib.core.luncertainty import LUncertainty

Create a Set of Secrets

A set of secrets is defined by labels and a prior distribution over those labels.

  • The list of labels must have at least 2 elements.

  • The prior must be a valid probability distribution (non-negative entries summing to 1) and match the number of labels.

# Two secrets with a non-uniform prior
labels = ["x0", "x1"]
prior = [0.7, 0.3]
secrets = Secrets(labels, prior)

Update the prior later (optional):

secrets.update_prior([0.5, 0.5])

Create a Channel

A channel encodes the conditional probabilities \(C[x][y] = p(y\mid x)\).

  • outputs is the list of observable outputs (at least one).

  • channel is an \(n\) times m matrix whose rows are probability distributions (each row sums to 1).

  • The number of rows equals the number of secrets; the number of columns equals the number of outputs.

outputs = ["y0", "y1", "y2"]

# C[x][y] = p(y|x). Each row sums to 1.
C = [
   [0.6, 0.3, 0.1], # p(y|x0)
   [0.1, 0.4, 0.5], # p(y|x1)
]

channel = Channel(secrets, outputs, C)

Update the prior through the channel (optional):

channel.update_prior([0.2, 0.8])

Create a Hyper-distribution

A hyper-distribution bundles:

  • the joint distribution \(J[x,y] = p(x)p(y\mid x)\),

  • the outer distribution \(p(y)\),

  • the inners (posterior) distributions \(p(x\mid y)\),

  • and reduces equivalent/zero-probability posteriors.

hyper = Hyper(channel)

If you change the prior later:

hyper.update_prior([0.5, 0.5])

hyper.joint, hyper.outer, hyper.inners, and hyper.num_posteriors are recomputed

Create a g-Vulnerability Function

The \(g\)-vulnerability models an adversary’s gain for choosing action \(w\) when the true secret is \(x\). You can provide either:

  • a gain matrix G[w][x] with shape (#actions, #secrets), or

  • a callable g(w, x) -> float from which the matrix is built.

Matrix form:

actions = ["guess_x0", "guess_x1"]

# gain 1 if we guess x0/x1 when secret is x0/x1
G = [
   [1.0, 0.0],
   [0.0, 1.0],
]

g = GVulnerability(secrets, actions, G)

Function form (equivalent to the matrix above):

def gfun(w, x):
   # reward 1.0 for a correct guess, 0.0 otherwise
   return 1.0 if w == x else 0.0

g = GVulnerability(secrets, actions, gfun)

Create an ℓ-Uncertainty (Loss) Function

The \(\ell\)-uncertainty models an adversary’s loss for action \(w\) when the true secret is \(x\). As with \(g\), you can provide a loss matrix or a callable.

Matrix form:

actions = ["guess_x0", "guess_x1"]

L = [
   [0.0, 1.0], # loss if we choose action "guess_x0"
   [1.0, 0.0], # loss if we choose action "guess_x1"
]

ell = LUncertainty(secrets, actions, L)

Function form:

def lfun(w, x):
   # 0 loss if the guess matches, 1 otherwise
   return 0.0 if w == x else 1.0

ell = LUncertainty(secrets, actions, lfun)

Calculate the Prior Vulnerability (and Uncertainty)

  • Prior \(g\)-vulnerability: \(\max_w \sum_x p(x) \, g(w,x)\)

  • Prior \(\ell\)-uncertainty: \(\min_w \sum_x p(x) \, \ell(w,x)\)

prior_vuln = g.prior_vulnerability()
prior_unc = ell.prior_uncertainty()

print("Prior g-vulnerability:", prior_vuln)
print("Prior ℓ-uncertainty:", prior_unc)

Calculate the Posterior Vulnerability (and Uncertainty)

Given the hyper:

  • Posterior \(g\)-vulnerability: \(\sum_y \max_w \sum_x p(x,y)\, g(w,x)\)

  • Posterior \(\ell\)-uncertainty: \(\sum_y \min_w \sum_x p(x,y)\, \ell(w,x)\)

post_vuln = g.posterior_vulnerability(hyper)
post_unc = ell.posterior_uncertainty(hyper)

print("Posterior g-vulnerability:", post_vuln)
print("Posterior ℓ-uncertainty:", post_unc)