blockchainkit.proofs#
Proofs: zero-knowledge and succinct proof systems.
How a prover convinces a verifier that a computation is correct, cheaply and without revealing its secrets: polynomial identity testing and sum-check, interactive proofs and PCPs, Merkle-committed arguments, polynomial commitments, circuits and QAPs, pairing-based SNARKs (Groth16, PLONK) and their trusted setups, private payments, and transparent systems from hashes and discrete logs (Bulletproofs, FRI, STARKs, Halo). Everything runs over small fields and groups whose numbers can be inspected by hand.
How a prover convinces a verifier that a computation is correct, cheaply and without revealing its secrets: polynomial identity testing and sum-check, interactive proofs and PCPs, Merkle-committed arguments, KZG commitments, circuits and QAPs, Groth16 and PLONK with their trusted setups, private payments, and the transparent systems Bulletproofs, FRI, STARKs and Halo.
Every public name below is re-exported by the subpackage: import it as
bk.proofs.<name>. The plotting helpers are the exception: import them
explicitly from blockchainkit.proofs.visualizers, which loads
Matplotlib.
Results#
Shared types and result containers for blockchainkit.proofs.
- blockchainkit.proofs.core.base.Polynomial#
Coefficients, constant term first, with no trailing zeros.
- class blockchainkit.proofs.core.base.IdentityTest(equal, trials, witness)[source]#
Bases:
objectThe outcome of testing two polynomials for equality at random points.
- Variables:
- Parameters:
- class blockchainkit.proofs.core.base.CNF(num_variables, clauses)[source]#
Bases:
objectA Boolean formula in conjunctive normal form, and its arithmetization.
- Parameters:
Examples
>>> from blockchainkit.proofs import CNF >>> xor = CNF(2, ((1, 2), (-1, -2))) >>> xor.count_solutions(), xor.evaluate((1, 0), 97), xor.evaluate((1, 1), 97) (2, 1, 0)
- evaluate(point, prime)[source]#
Evaluate the arithmetized formula at a point of the field.
A literal becomes
xor1 - x, a clause becomes1 - prod(1 - literal), and the formula the product of its clauses. On 0/1 inputs this is the formula’s truth value.
- class blockchainkit.proofs.core.base.SumcheckRun(claim, true_sum, polynomials, challenges, accepted)[source]#
Bases:
objectOne run of the sum-check protocol between a prover and a verifier.
- Variables:
claim (
int) – The sum the prover asserted.true_sum (
int) – The actual sum over the Boolean cube.polynomials (
tupleofPolynomial) – The univariate polynomial the prover sent in each round.challenges (
tupleofint) – The verifier’s random field element after each round.accepted (
bool) – Whether every round check and the final oracle query passed.
- Parameters:
- class blockchainkit.proofs.core.base.QBF(quantifiers, formula)[source]#
Bases:
objectA fully quantified Boolean formula: one quantifier per variable, then a CNF.
- Parameters:
quantifiers (
str) –"A"(for all) or"E"(there exists) for variables 1, 2, …formula (
blockchainkit.proofs.core.base.CNF) – The quantifier-free matrix.
Examples
>>> from blockchainkit.proofs import CNF, QBF >>> QBF("AE", CNF(2, ((1, 2), (-1, -2)))).evaluate() # for all x, some y differs True
- class blockchainkit.proofs.core.base.TQBFRound(operator, variable, polynomial, challenge)[source]#
Bases:
objectOne round of Shamir’s protocol: an operator, its variable, and the prover’s polynomial.
- Variables:
operator (
str) –"A"(product),"E"(complemented product) or"L"(linearization).variable (
int) – The variable it acts on, from 1.polynomial (
Polynomial) – The prover’s claim for the inner expression as a polynomial in that variable.challenge (
int) – The random value the verifier then assigned to the variable.
- Parameters:
- class blockchainkit.proofs.core.base.TQBFRun(claim, true_value, rounds, accepted)[source]#
Bases:
objectOne run of the interactive proof that a quantified Boolean formula is true.
- Variables:
claim (
int) – The truth value the prover asserted, 1 or 0.true_value (
int) – The formula’s actual value.rounds (
tupleofblockchainkit.proofs.core.base.TQBFRound)accepted (
bool)
- Parameters:
- class blockchainkit.proofs.core.base.QuadraticSystem(num_variables, equations)[source]#
Bases:
objectQuadratic equations over GF(2): the NP-complete language of the Hadamard PCP.
- Parameters:
Examples
>>> from blockchainkit.proofs import QuadraticSystem >>> system = QuadraticSystem(2, ((((0, 1),), 1), (((0, 0), (1, 1)), 0))) >>> system.solutions() # u0 u1 = 1 and u0 + u1 = 0 ((1, 1),)
- class blockchainkit.proofs.core.base.HadamardProof(linear, quadratic)[source]#
Bases:
objectThe exponentially long proof of the Hadamard PCP.
- Variables:
- Parameters:
- class blockchainkit.proofs.core.base.PCPRun(accepted, queries, failed_test)[source]#
Bases:
objectThe PCP verifier’s verdict.
- Variables:
- Parameters:
- class blockchainkit.proofs.core.base.KilianRun(root, accepted, queries, communication, proof_length)[source]#
Bases:
objectOne run of Kilian’s interactive argument: a Merkle-committed PCP, opened where queried.
- Variables:
root (
bytes) – The prover’s commitment to the whole PCP.accepted (
bool) – Whether every opening matched the root and the PCP verifier accepted.queries (
int) – PCP bits opened.communication (
int) – Bytes the prover sent: the root, then each bit with its Merkle path.proof_length (
int) – Bits in the PCP itself, which the prover never sends.
- Parameters:
- class blockchainkit.proofs.core.base.Opening(position, bit, path)[source]#
Bases:
objectOne PCP bit and its Merkle path.
- Parameters:
position (int)
bit (int)
path (MerkleProof)
- path: MerkleProof#
- class blockchainkit.proofs.core.base.CSProof(root, salt, openings)[source]#
Bases:
objectMicali’s computationally sound proof: a root, a salt, and the openings it selects.
- Variables:
root (
bytes) – Merkle root of the PCP.salt (
int) – Hashed with the root to select the queries; varying it is how a cheating prover grinds.openings (
tupleofblockchainkit.proofs.core.base.Opening) – The queried bits, in the order the verifier reads them.
- Parameters:
- class blockchainkit.proofs.core.base.SRS(powers, curve)[source]#
Bases:
objectA structured reference string:
[tau**i] Gfor i = 0, …, max_degree.- Variables:
powers (
tupleofPoint)curve (
blockchainkit.crypto.systems.curves.Curve) – The pairing curve the points lie on.
- Parameters:
- class blockchainkit.proofs.core.base.KZGOpening(point, value, witness)[source]#
Bases:
objectA claimed evaluation
f(point) = valueand its witness[q(tau)] G.
- class blockchainkit.proofs.core.base.Contribution(srs, public)[source]#
Bases:
objectOne participant’s update to a ceremony: the new string and
[secret] G.
- blockchainkit.proofs.core.base.Row#
One sparse row of an R1CS matrix, as
(variable index, coefficient)pairs.
- class blockchainkit.proofs.core.base.R1CS(prime, num_variables, num_public, a, b, c)[source]#
Bases:
objectA rank-1 constraint system:
<a_j, z> * <b_j, z> = <c_j, z>for every row j.- Variables:
- Parameters:
- class blockchainkit.proofs.core.base.QAP(prime, domain_size, a, b, c, target)[source]#
Bases:
objectA quadratic arithmetic program: one polynomial per variable and matrix.
- Variables:
prime (
int)domain_size (
int) – n, the size of the domain{w**j}of roots of unity.target (
Polynomial) –t(X) = X**n - 1, which vanishes on the domain.
- Parameters:
- class blockchainkit.proofs.core.base.QAPDivision(left, right, output, quotient, remainder)[source]#
Bases:
objectA B - C = H t + Rfor one witness.- Variables:
quotient (
Polynomial) – H(X).remainder (
Polynomial) – R(X), zero exactly when the witness satisfies every constraint.
- Parameters:
- class blockchainkit.proofs.core.base.Groth16Trapdoor(tau, alpha, beta, gamma, delta)[source]#
Bases:
objectThe setup’s toxic waste: five secret field elements that must be destroyed.
Anyone who keeps them can prove false statements.
- class blockchainkit.proofs.core.base.VerifyingKey(alpha, beta, gamma, delta, public_query, curve)[source]#
Bases:
objectWhat a Groth16 verifier needs: four points and one point per public input.
- Variables:
public_query (
tupleofPoint) –[(beta A_i(tau) + alpha B_i(tau) + C_i(tau)) / gamma] Gfor the constant and each public input.
- Parameters:
- class blockchainkit.proofs.core.base.ProvingKey(r1cs, domain_size, a_query, b_query, private_query, h_query, verifying_key)[source]#
Bases:
objectWhat a Groth16 prover needs: the circuit and the encoded QAP at the secret point.
- Variables:
domain_size (
int)b_query (a_query,) –
[A_i(tau)] Gand[B_i(tau)] Gfor every variable.private_query (
tupleofPoint) –[(beta A_i(tau) + alpha B_i(tau) + C_i(tau)) / delta] Gfor the private variables.h_query (
tupleofPoint) –[tau**k t(tau) / delta] Gfor k = 0, …, n - 2.verifying_key (
blockchainkit.proofs.core.base.VerifyingKey)
- Parameters:
- verifying_key: VerifyingKey#
- class blockchainkit.proofs.core.base.Groth16Proof(a, b, c)[source]#
Bases:
objectThree group elements, whatever the size of the circuit.
- class blockchainkit.proofs.core.base.Note(owner, value, rho, randomness)[source]#
Bases:
objectA Zerocash note: who owns it, its value, its unique serial seed, and commitment randomness.
- Variables:
- Parameters:
- class blockchainkit.proofs.core.base.SpendTransaction(root, nullifier, new_commitment, public_value, proof)[source]#
Bases:
objectWhat a Zerocash spend publishes: root, nullifier, new commitment, amount and proof.
- Parameters:
root (int)
nullifier (int)
new_commitment (int)
public_value (int)
proof (Groth16Proof)
- proof: Groth16Proof#
- class blockchainkit.proofs.core.base.PlonkKey(prime, n, omega, wiring, selectors, sigmas, sigma_labels, selector_commitments, sigma_commitments)[source]#
Bases:
objectA preprocessed PLONK circuit: selector and permutation polynomials, and their commitments.
- Variables:
prime (
int)n (
int) – Rows of the padded circuit, a power of two.omega (
int) – Generator of the domain of n-th roots of unity.wiring (
tupleoftupleofint) – The variables on each row’s left, right and output wires.selectors (
tupleofPolynomial) –q_L, q_R, q_O, q_M, q_C.sigmas (
tupleofPolynomial) – The permutation, one polynomial per wire column.sigma_labels (
tupleoftupleofint) – The permutation’s values on the domain.sigma_commitments (selector_commitments,) – What the verifier holds.
- Parameters:
- class blockchainkit.proofs.core.base.PlonkProof(wires, z, t, evaluations, opening, shifted_opening)[source]#
Bases:
objectFive commitments, fourteen evaluations and two opening witnesses.
- Variables:
wires (
tupleofPoint) – Commitments to the wire polynomials a, b, c.z (
Point) – Commitment to the running product of the permutation argument.t (
Point) – Commitment to the quotient polynomial.evaluations (
tupleofint) – a, b, c, sigma_1..3, q_L, q_R, q_O, q_M, q_C, z and t at zeta, then z atzeta omega.shifted_opening (opening,) – Batched KZG witnesses at zeta and at
zeta omega.
- Parameters:
- class blockchainkit.proofs.core.base.RangeProof(commitment, a, s, t1, t2, tau_x, mu, t_hat, left, right, final_a, final_b)[source]#
Bases:
objectA Bulletproofs range proof for the commitment
V = g**v h**gamma.- Variables:
commitment (
int) –t2 (a, s, t1,) – Commitments to the bit vectors, the blinding vectors, and the coefficients of t(X).
t_hat (tau_x, mu,) – The blinding of t(x), the blinding of A S**x, and t(x).
right (left,) – L and R from each halving round of the inner-product argument.
final_b (final_a,) – The last scalars of the folded vectors.
- Parameters:
- class blockchainkit.proofs.core.base.IPAOpening(point, value, left, right, final, g_final)[source]#
Bases:
objectAn inner-product-argument opening of a polynomial commitment.
- Variables:
- Parameters:
- class blockchainkit.proofs.core.base.HaloAccumulator(challenges, g_final)[source]#
Bases:
objectA deferred claim:
g_finalcommits toprod (1 + c_j X**(2**(k-j))).- Variables:
- Parameters:
- class blockchainkit.proofs.core.base.AccumulationProof(opening)[source]#
Bases:
objectThe opening that merges several accumulators into one.
- Parameters:
opening (IPAOpening)
- opening: IPAOpening#
- class blockchainkit.proofs.core.base.FRILayerOpening(low, low_path, high, high_path)[source]#
Bases:
objectThe two values of one layer that fold together, f(x) and f(-x), with their Merkle paths.
- Parameters:
low (int)
low_path (MerkleProof)
high (int)
high_path (MerkleProof)
- low_path: MerkleProof#
- high_path: MerkleProof#
- class blockchainkit.proofs.core.base.FRIQuery(index, layers)[source]#
Bases:
objectOne query: a position of the first layer and its openings in every layer.
- Parameters:
index (int)
layers (tuple[FRILayerOpening, ...])
- layers: tuple[FRILayerOpening, ...]#
- class blockchainkit.proofs.core.base.FRIProof(roots, final, queries)[source]#
Bases:
objectA FRI proof: one Merkle root per layer, the final constant, and the queries.
- Variables:
final (
int)queries (
tupleofblockchainkit.proofs.core.base.FRIQuery)
- Parameters:
- class blockchainkit.proofs.core.base.TraceOpening(values, paths)[source]#
Bases:
objectTrace values at x, g x and g**2 x, with their Merkle paths.
- Parameters:
paths (tuple[MerkleProof, ...])
- paths: tuple[MerkleProof, ...]#
- class blockchainkit.proofs.core.base.StarkProof(trace_root, fri, trace_openings)[source]#
Bases:
objectA STARK: the trace’s Merkle root, a FRI proof, and the trace openings it needs.
- Variables:
trace_root (
bytes) – Commitment to the low-degree extension of the trace.fri (
blockchainkit.proofs.core.base.FRIProof) – Proves that the constraint composition has low degree.trace_openings (
tupleofblockchainkit.proofs.core.base.TraceOpening) – Two per FRI query, one for each value of the first layer.
- Parameters:
trace_root (bytes)
fri (FRIProof)
trace_openings (tuple[TraceOpening, ...])
- trace_openings: tuple[TraceOpening, ...]#
Fields, polynomials and transcripts#
Univariate polynomials over a prime field, and the fast Fourier transform.
A polynomial is a tuple of coefficients, constant term first, with no
trailing zeros: (5, 0, 1) is 5 + x**2 and () is the zero
polynomial. Every function takes the field modulus as prime; it
defaults to FIELD_PRIME, the BabyBear prime 15 * 2**27 + 1, whose
multiplicative group has a subgroup of every power-of-two order up to
2**27. Those subgroups are the evaluation domains of the FFT, of FRI, and
of PLONK.
- blockchainkit.proofs.utils.polynomials.FIELD_PRIME = 2013265921#
The BabyBear prime 2013265921, used by RISC Zero and SP1. Its multiplicative group has order 2**27 * 15, so power-of-two domains up to 2**27 points exist.
- Type:
- blockchainkit.proofs.utils.polynomials.FIELD_GENERATOR = 31#
A generator of the multiplicative group of
FIELD_PRIME.- Type:
- blockchainkit.proofs.utils.polynomials.polynomial(coefficients, prime=2013265921)[source]#
Reduce coefficients into the field and drop trailing zeros.
>>> from blockchainkit.proofs import polynomial >>> polynomial([3, -1, 0], prime=7) (3, 6)
- blockchainkit.proofs.utils.polynomials.degree(poly)[source]#
Return the degree, or -1 for the zero polynomial.
- blockchainkit.proofs.utils.polynomials.poly_add(left, right, prime=2013265921)[source]#
Add two polynomials.
- blockchainkit.proofs.utils.polynomials.poly_scale(poly, scalar, prime=2013265921)[source]#
Multiply every coefficient by
scalar.
- blockchainkit.proofs.utils.polynomials.poly_sub(left, right, prime=2013265921)[source]#
Subtract
rightfromleft.
- blockchainkit.proofs.utils.polynomials.poly_mul(left, right, prime=2013265921)[source]#
Multiply two polynomials by schoolbook convolution.
>>> from blockchainkit.proofs import poly_mul >>> poly_mul([1, 1], [1, 1], prime=97) (1, 2, 1)
- blockchainkit.proofs.utils.polynomials.poly_divmod(numerator, denominator, prime=2013265921)[source]#
Divide with remainder:
numerator = quotient * denominator + remainder.- Raises:
ZeroDivisionError –
denominatoris the zero polynomial.- Parameters:
- Return type:
Examples
>>> from blockchainkit.proofs import poly_divmod >>> poly_divmod([-1, 0, 1], [-1, 1], prime=97) # (x**2 - 1) / (x - 1) ((1, 1), ())
- blockchainkit.proofs.utils.polynomials.poly_eval(poly, x, prime=2013265921)[source]#
Evaluate at
xby Horner’s rule.>>> from blockchainkit.proofs import poly_eval >>> poly_eval([5, 0, 1], 3, prime=97) 14
- blockchainkit.proofs.utils.polynomials.vanishing_polynomial(roots, prime=2013265921)[source]#
Return the monic polynomial whose roots are
roots: the product of (x - root).
- blockchainkit.proofs.utils.polynomials.interpolate(points, prime=2013265921)[source]#
Return the unique polynomial of degree below
len(points)throughpoints.Lagrange’s formula, in O(n**2) field operations.
- Raises:
ValueError – Two points share an x-coordinate.
- Parameters:
- Return type:
Examples
>>> from blockchainkit.proofs import interpolate >>> interpolate([(0, 5), (1, 6), (2, 9)], prime=97) # 5 + x**2 (5, 0, 1)
- blockchainkit.proofs.utils.polynomials.root_of_unity(n, prime=2013265921)[source]#
Return an element of multiplicative order exactly
n, a power of two.The domain
{w**i}of the FFT.nmust divideprime - 1.>>> from blockchainkit.proofs import root_of_unity >>> w = root_of_unity(8) >>> pow(w, 8, 2013265921), pow(w, 4, 2013265921) != 1 (1, True)
- blockchainkit.proofs.utils.polynomials.domain(n, prime=2013265921, offset=1)[source]#
Return the
npointsoffset * w**iof a (shifted) power-of-two subgroup.
- blockchainkit.proofs.utils.polynomials.evaluate_on_domain(poly, n, prime=2013265921, offset=1)[source]#
Evaluate at the
npoints ofdomain()with the radix-2 FFT.Costs O(n log n) field operations instead of the O(n**2) of evaluating point by point. The polynomial’s degree must be below
n.>>> from blockchainkit.proofs import evaluate_on_domain, poly_eval, domain >>> evaluate_on_domain([1, 2, 3], 4) == tuple(poly_eval([1, 2, 3], x) for x in domain(4)) True
- blockchainkit.proofs.utils.polynomials.interpolate_on_domain(values, prime=2013265921, offset=1)[source]#
Inverse of
evaluate_on_domain(): the polynomial takingvalueson the domain.>>> from blockchainkit.proofs import evaluate_on_domain, interpolate_on_domain >>> interpolate_on_domain(evaluate_on_domain([4, 0, 7], 8, offset=31), offset=31) (4, 0, 7)
A Fiat-Shamir transcript: the prover’s messages hashed into the verifier’s challenges.
Fiat and Shamir (1986) replaced an interactive verifier by a hash of
everything said so far. Transcript keeps a running SHA-256 state:
append absorbs a labelled message, and challenge squeezes an
integer below a modulus that depends on every message before it. The
prover and the verifier run the same transcript, so they derive the same
challenges, and the prover cannot choose its messages after seeing them.
- blockchainkit.proofs.utils.transcript.Message = int | bytes | None | collections.abc.Sequence['Message']#
Integers, bytes,
None(a point at infinity), or sequences of them.
- class blockchainkit.proofs.utils.transcript.Transcript(protocol)[source]#
Bases:
objectA labelled, append-only Fiat-Shamir transcript.
- Parameters:
protocol (
bytes) – Names the protocol, so challenges never transfer between protocols.
Examples
>>> from blockchainkit.proofs import Transcript >>> prover, verifier = Transcript(b"demo"), Transcript(b"demo") >>> prover.append("commitment", 42); verifier.append("commitment", 42) >>> prover.challenge("c", 97) == verifier.challenge("c", 97) True
Group helpers for the commitment schemes: independent generators and linear combinations.
- blockchainkit.proofs.utils.groups.independent_generators(count, label, group=DHGroup(p=4611686018427377339, q=2305843009213688669, g=4))[source]#
Derive
countgenerators of the order-q subgroup whose mutual logs nobody knows.Each one hashes the group, a label and an index into the subgroup, as
pedersen_generators()does for one. Vector commitments need them: whoever knew a relation between two generators could open a commitment two ways.>>> from blockchainkit.proofs import independent_generators >>> len(set(independent_generators(8, "demo"))) 8
Interactive proofs#
The Schwartz-Zippel lemma: testing polynomial identities at random points (1980).
Two different polynomials of total degree at most d agree on at most a d / p fraction of the points of F^n. To test whether two expressions define the same polynomial, evaluate both at a random point: if they are equal the test always passes, and if not it fails with probability at least 1 - d / p. Every proof system in this subpackage reduces its statement to such a test.
- blockchainkit.proofs.systems.identity.schwartz_zippel_bound(total_degree, field_size)[source]#
Return d / p, the most that two distinct polynomials agree on a random point.
>>> from blockchainkit.proofs import schwartz_zippel_bound >>> schwartz_zippel_bound(3, 97) 0.030927835051546393
- blockchainkit.proofs.systems.identity.identity_test(left, right, num_variables, prime=2013265921, *, trials=1, seed=0)[source]#
Test whether two polynomial functions are equal by evaluating them at random points.
- Parameters:
left (
collections.abc.Callable) – Each takes a sequence ofnum_variablesfield elements and returns an integer; they are compared moduloprime.right (
collections.abc.Callable) – Each takes a sequence ofnum_variablesfield elements and returns an integer; they are compared moduloprime.num_variables (
int) – Number of variables.prime (
int) – Field modulus.trials (
int) – Independent random points to try.seed (
int) – Seed of the verifier’s randomness.
- Returns:
equalisFalseas soon as a point separates them.- Return type:
Examples
>>> from blockchainkit.proofs import identity_test >>> square = lambda v: (v[0] + v[1]) ** 2 >>> expanded = lambda v: v[0] ** 2 + 2 * v[0] * v[1] + v[1] ** 2 >>> identity_test(square, expanded, 2).equal True >>> identity_test(square, lambda v: v[0] ** 2 + v[1] ** 2, 2).equal False
The sum-check protocol of Lund, Fortnow, Karloff and Nisan (1990).
A prover claims that a polynomial f in n variables sums to H over
the Boolean cube {0, 1}**n. Summing directly takes 2**n evaluations. In
round i, the prover instead sends the univariate polynomial
g_i(X) = sum of f(r_1, …, r_{i-1}, X, b_{i+1}, …, b_n) over the bs,
the verifier checks g_i(0) + g_i(1) against the previous claim and fixes X to a random r_i. After n rounds the verifier evaluates f once, at (r_1, …, r_n). A false claim survives each round with probability at most d / p (Schwartz-Zippel), so with probability at most n d / p.
- blockchainkit.proofs.systems.sumcheck.multilinear_extension(values, point, prime=2013265921)[source]#
Evaluate the multilinear extension of a table of 2**n values at a field point.
The extension is the unique polynomial of degree at most one in each variable that takes
values[b]at each Boolean vector b, read as a binary number with the first variable most significant.>>> from blockchainkit.proofs import multilinear_extension >>> multilinear_extension([3, 5, 7, 9], [1, 0], 97) # the table entry at b = 10 7 >>> multilinear_extension([3, 5, 7, 9], [2, 0], 97) # 3 + 4 * x1 + 2 * x2, beyond the cube 11
- blockchainkit.proofs.systems.sumcheck.sumcheck(f, num_variables, degree, prime=2013265921, *, claim=None, seed=0)[source]#
Run the sum-check protocol for the sum of
fover {0, 1}**n.The prover is honest when
claimis the true sum (the default). Given a false claim, it lies as well as it can: each round’s polynomial is consistent with the previous claim and differs from the true one by a polynomial withdegreeroots, in case the verifier picks one.- Parameters:
f (
collections.abc.Callable) – A polynomial function ofnum_variablesfield elements, of degree at mostdegreein each variable.num_variables (
int) – Number of variables n.degree (
int) – The degree bound the verifier enforces on each round’s polynomial.prime (
int) – Field modulus.claim (
int, optional) – The sum the prover asserts.seed (
int) – Seed of the verifier’s random challenges.
- Return type:
Examples
>>> from blockchainkit.proofs import CNF, sumcheck >>> formula = CNF(3, ((1, 2), (-2, 3))) >>> run = sumcheck(lambda x: formula.evaluate(x, 97), 3, formula.degree, 97) >>> run.claim, run.accepted (4, True) >>> sumcheck(lambda x: formula.evaluate(x, 97), 3, formula.degree, 97, claim=5).accepted False
- blockchainkit.proofs.systems.sumcheck.verify_sumcheck(f, claim, polynomials, challenges, degree, prime=2013265921)[source]#
Check a sum-check transcript: the verifier’s side of
sumcheck().Each polynomial must have degree at most
degreeand satisfyg_i(0) + g_i(1) = g_{i-1}(r_{i-1})(the claim for i = 1); finallyg_n(r_n)must equalf(r_1, ..., r_n), the one evaluation of f the verifier makes. The verifier is sound only if each challenge was drawn after its polynomial was sent.
Shamir’s IP = PSPACE: an interactive proof for quantified Boolean formulas (1990).
A quantified formula such as “for all x, there exists y: phi(x, y)” is
arithmetized like a CNF, with “for all” becoming a product,
A f = f(0) f(1), and “there exists” a complemented product,
E f = 1 - (1 - f(0))(1 - f(1)). The prover convinces the verifier of
the formula’s value by sum-check-style rounds, one per operator, peeling
operators from the outside in.
Each product doubles degrees, so after n quantifiers the polynomials
would have degree about 2**n. Shen’s linearization operator,
L_i f = (1 - x_i) f(x_i = 0) + x_i f(x_i = 1), which agrees with f on
0/1 inputs and is linear in x_i, is inserted after every quantifier to keep
every polynomial of degree at most max(2, degree of phi).
- blockchainkit.proofs.systems.ip_pspace.tqbf_operators(qbf, *, linearize=True)[source]#
List the arithmetized operators of a formula from the outside in.
With linearization, the quantifier on x_i is followed by
L_1, ..., L_i: the order of Shen’s proof.>>> from blockchainkit.proofs import CNF, QBF, tqbf_operators >>> tqbf_operators(QBF("AE", CNF(2, ((1, 2),)))) (('A', 1), ('L', 1), ('E', 2), ('L', 1), ('L', 2))
- blockchainkit.proofs.systems.ip_pspace.tqbf_protocol(qbf, prime=2013265921, *, claim=None, seed=0, linearize=True)[source]#
Run the interactive proof of a quantified Boolean formula’s value.
The prover evaluates every inner expression by brute force, which is exponential but needs only polynomial space; the verifier does polynomial work. A false claim survives with probability at most (rounds * degree) / prime.
- Parameters:
qbf (
blockchainkit.proofs.core.base.QBF) – The formula.prime (
int) – Field modulus.claim (
int, optional) – The value the prover asserts, 1 (true) or 0 (false); the true value by default. A false claim makes the prover lie as well as it can.seed (
int) – Seed of the verifier’s challenges.linearize (
bool) – Insert Shen’s linearization operators. Without them the protocol is still sound but the degrees grow exponentially.
- Return type:
Examples
>>> from blockchainkit.proofs import CNF, QBF, tqbf_protocol >>> formula = QBF("AE", CNF(2, ((1, 2), (-1, -2)))) >>> tqbf_protocol(formula).accepted, tqbf_protocol(formula, claim=0).accepted (True, False)
PCPs and arguments#
Probabilistically checkable proofs: the Hadamard PCP for quadratic equations (1992).
The PCP theorem (Arora and Safra; Arora, Lund, Motwani, Sudan and Szegedy, 1992) says every NP statement has a proof that a verifier checks by reading a constant number of its bits, chosen at random, and rejecting a false statement with constant probability. This module implements the first step of the ALMSS proof: an exponentially long proof checked with 14 queries.
The statement is that a system of quadratic equations over GF(2) has a
solution u. The proof writes out the Hadamard encodings of u and of
u (x) u: every parity <u, x> and every <u (x) u, y>. The verifier
tests that both tables are linear functions (Blum, Luby and Rubinfeld,
1990), that the second is the tensor square of the first, and that a
random combination of the equations holds, reading each table through
self-correction, f(x) = f(x + s) - f(s), so that a few corrupted entries
cannot be targeted.
- blockchainkit.proofs.systems.pcp.Oracle#
Reads one bit of a proof at a position of the concatenated tables.
- blockchainkit.proofs.systems.pcp.hadamard_proof(assignment)[source]#
Encode an assignment u as the tables of
<u, x>and<u (x) u, y>.- Parameters:
assignment (
collections.abc.Sequenceofint) – Between 1 and 4 bits; the second table has 2**(n**2) entries.- Return type:
Examples
>>> from blockchainkit.proofs import hadamard_proof >>> proof = hadamard_proof([1, 0, 1]) >>> len(proof.linear), len(proof.quadratic) (8, 512)
- blockchainkit.proofs.systems.pcp.run_pcp_verifier(system, oracle, rng, repetitions)[source]#
Run the Hadamard PCP verifier against any proof oracle.
Positions below 2**n read the linear table; position 2**n + y reads entry y of the quadratic table. Each repetition makes 14 queries: linearity of both tables (3 + 3), tensor consistency (6), and one random combination of the equations (2).
- blockchainkit.proofs.systems.pcp.pcp_verify(system, proof, *, repetitions=1, seed=0)[source]#
Check a Hadamard proof by reading 14 random bits per repetition.
A correct proof of a satisfiable system always passes. If the system has no solution, every proof is rejected with constant probability per repetition, so
repetitionsdrives the error down exponentially.Examples
>>> from blockchainkit.proofs import QuadraticSystem, hadamard_proof, pcp_verify >>> system = QuadraticSystem(2, ((((0, 1),), 1), (((0, 0),), 1))) # u0 u1 = 1, u0 = 1 >>> pcp_verify(system, hadamard_proof([1, 1]), repetitions=10).accepted True
- Parameters:
system (QuadraticSystem)
proof (HadamardProof)
repetitions (int)
seed (int)
- Return type:
Succinct arguments from Merkle commitments: Kilian (1992) and Micali’s CS proofs (1994).
A PCP is short to check but long to send: the Hadamard proof of an n-variable system has 2**n + 2**(n**2) bits. Kilian’s argument sends only a Merkle root of the PCP. The verifier then picks its random queries, and the prover opens each queried bit with its authentication path. The prover cannot change an answer after committing without finding a hash collision, so communication drops to the root plus a logarithmic path per query.
Micali removed the interaction: the queries are derived by hashing the root, so the prover writes the whole argument down at once, a computationally sound proof. Soundness then rests on the hash behaving like a random oracle, and a cheating prover can grind: change the commitment (here, a salt) and rehash until the queries miss its lies.
- blockchainkit.proofs.systems.kilian.commit_pcp(proof)[source]#
Build a Merkle tree whose leaf i is bit i of the PCP, one byte per leaf.
- Parameters:
proof (HadamardProof)
- Return type:
- blockchainkit.proofs.systems.kilian.kilian_argument(system, proof, *, repetitions=1, seed=0, respond=None)[source]#
Run Kilian’s interactive argument for a quadratic system.
The prover commits to
proofand sends the root; the verifier then draws the PCP queries fromseed; the prover answers each with a bit and the Merkle path of the committed bit.- Parameters:
proof (
blockchainkit.proofs.core.base.HadamardProof) – The PCP the prover commits to.repetitions (
int) – Repetitions of the PCP verifier.seed (
int) – Seed of the verifier’s queries, drawn after the root is sent.respond (
collections.abc.Callable, optional) – A cheating prover’s answer for each queried position, in place of the committed bit. The path it sends is the committed one.
- Return type:
Examples
>>> from blockchainkit.proofs import QuadraticSystem, hadamard_proof, kilian_argument >>> system = QuadraticSystem(3, ((((0, 1), (2, 2)), 1),)) >>> run = kilian_argument(system, hadamard_proof([1, 1, 0]), repetitions=4) >>> run.accepted, run.queries (True, 56)
- blockchainkit.proofs.systems.kilian.micali_proof(system, proof, *, repetitions=1, salt=0)[source]#
Write a non-interactive CS proof: the queries come from hashing the root and a salt.
Examples
>>> from blockchainkit.proofs import ( ... QuadraticSystem, hadamard_proof, micali_proof, verify_cs_proof) >>> system = QuadraticSystem(3, ((((0, 1), (2, 2)), 1),)) >>> cs = micali_proof(system, hadamard_proof([1, 1, 0]), repetitions=4) >>> verify_cs_proof(system, cs, repetitions=4) True
- Parameters:
system (QuadraticSystem)
proof (HadamardProof)
repetitions (int)
salt (int)
- Return type:
Pairings and polynomial commitments#
A toy bilinear pairing on a supersingular curve, the engine of KZG, Groth16 and PLONK.
A pairing is a map e from two copies of a group of prime order r into a
third, with e(aP, bQ) = e(P, Q)**(a b). It lets a verifier check one
multiplication between hidden exponents, which is what every
pairing-based SNARK needs.
The curve y**2 = x**3 + x over F_p, with p = 3 mod 4, is supersingular:
it has p + 1 points, and its pairing values live in F_(p**2), the
complex-like numbers a + b i with i**2 = -1. The distortion map
(x, y) -> (-x, i y) makes the reduced Tate pairing, computed by
Miller’s algorithm, symmetric and nondegenerate on the subgroup of order
r. This is the Boneh-Franklin construction (2001) with r = 2013265921,
the BabyBear prime, and p = 12 r - 1, a 35-bit prime. Discrete logarithms
in a group that small take seconds, so this curve teaches the algebra and
nothing about security; deployed systems use BN254 or BLS12-381.
- blockchainkit.proofs.systems.pairing.GTElement#
An element
a + b iof F_(p**2), as(a, b).
- blockchainkit.proofs.systems.pairing.PAIRING_CURVE = Curve(p=24159191051, a=1, b=0, generator=(4010265589, 9478310149), order=2013265921, name='toy-pairing')#
y**2 = x**3 + xover the prime 12 * 2013265921 - 1, with a generator of order 2013265921 (the BabyBear field prime, so scalars are elements ofFIELD_PRIME).- Type:
- blockchainkit.proofs.systems.pairing.pairing(left, right, curve=Curve(p=24159191051, a=1, b=0, generator=(4010265589, 9478310149), order=2013265921, name='toy-pairing'))[source]#
Compute the reduced Tate pairing
e(left, distort(right)).- Parameters:
left (
Point) – Points of the order-r subgroup; the point at infinity pairs to 1.right (
Point) – Points of the order-r subgroup; the point at infinity pairs to 1.curve (
blockchainkit.crypto.systems.curves.Curve) – A supersingular curvey**2 = x**3 + xwith p = 3 mod 4.
- Returns:
An r-th root of unity in F_(p**2).
- Return type:
Examples
>>> from blockchainkit.proofs import PAIRING_CURVE as E, pairing, gt_power >>> from blockchainkit.crypto import multiply >>> G = E.generator >>> pairing(multiply(3, G, E), multiply(5, G, E)) == gt_power(pairing(G, G), 15) True
- blockchainkit.proofs.systems.pairing.gt_multiply(left, right, curve=Curve(p=24159191051, a=1, b=0, generator=(4010265589, 9478310149), order=2013265921, name='toy-pairing'))[source]#
Multiply two pairing values in F_(p**2).
- blockchainkit.proofs.systems.pairing.gt_power(element, exponent, curve=Curve(p=24159191051, a=1, b=0, generator=(4010265589, 9478310149), order=2013265921, name='toy-pairing'))[source]#
Raise a pairing value to an integer power, reduced modulo the group order.
Kate-Zaverucha-Goldberg polynomial commitments (2010).
A trusted setup publishes [tau**i] G for a secret tau and then forgets
tau. A commitment to f is the single point C = [f(tau)] G, computed
from the published powers without knowing tau. To prove f(z) = y, the
prover sends W = [q(tau)] G for the quotient
q(X) = (f(X) - y) / (X - z),
which is a polynomial exactly when f(z) = y. The verifier checks the divisibility at the hidden point tau with one pairing equation,
e(C - [y] G, G) = e(W, [tau] G - [z] G).
Commitments and openings are one group element each, whatever the degree.
Whoever knows tau can open a commitment to any value: see
forge_opening().
- blockchainkit.proofs.systems.kzg.trusted_setup(max_degree, secret, curve=Curve(p=24159191051, a=1, b=0, generator=(4010265589, 9478310149), order=2013265921, name='toy-pairing'))[source]#
Publish
[secret**i] Gfor i up tomax_degree: a structured reference string.secretis the toxic waste tau. A single party running this knows it;contribute()spreads it over many parties so that one honest one suffices.>>> from blockchainkit.proofs import trusted_setup >>> trusted_setup(4, secret=1234).max_degree 4
- blockchainkit.proofs.systems.kzg.kzg_commit(poly, srs)[source]#
Commit to a polynomial:
[f(tau)] G, a linear combination of the published powers.Examples
>>> from blockchainkit.proofs import trusted_setup, kzg_commit, kzg_open, kzg_verify >>> srs = trusted_setup(4, secret=1234) >>> commitment = kzg_commit([5, 0, 1], srs) # 5 + x**2 >>> opening = kzg_open([5, 0, 1], 3, srs) >>> opening.value, kzg_verify(commitment, opening, srs) (14, True)
- blockchainkit.proofs.systems.kzg.kzg_open(poly, point, srs)[source]#
Evaluate at
pointand prove it with a commitment to the quotient polynomial.- Parameters:
- Return type:
- blockchainkit.proofs.systems.kzg.kzg_verify(commitment, opening, srs)[source]#
Check
e(C - [y] G, G) = e(W, [tau] G - [z] G); malformed inputs return False.
- blockchainkit.proofs.systems.kzg.forge_opening(commitment, point, value, secret, srs)[source]#
Open a commitment to any value, using the toxic waste tau.
W = (C - [y] G) / (tau - z)satisfies the verification equation whatever y is, so a party that kept tau can prove false evaluations. This is why the setup’s secret must be destroyed.
Trusted-setup ceremonies: Zcash’s parameter generation (2016) and the powers of tau.
A pairing-based SNARK needs [tau**i] G for a tau nobody knows. In a
ceremony, each participant in turn multiplies the current string by its
own secret s, raising every power to (tau s)**i, and then destroys s.
The final tau is the product of all the secrets, so it stays unknown
unless every participant kept theirs: one honest participant is enough.
Anyone can check each contribution with pairings: it publishes [s] G,
and the new string must satisfy
e(new_1, G) = e(old_1, [s] G) and e(new_(i+1), G) = e(new_i, new_1),
the first proving the update used s, the second that the result is still a list of consecutive powers. Bowe, Gabizon and Miers (2017) made the ceremony scale to any number of participants, as Zcash’s Powers of Tau (2017-2018) and Ethereum’s KZG ceremony (2023) did.
- blockchainkit.proofs.systems.ceremony.start_ceremony(max_degree, curve=Curve(p=24159191051, a=1, b=0, generator=(4010265589, 9478310149), order=2013265921, name='toy-pairing'))[source]#
Return the trivial string with tau = 1: every power is the generator.
>>> from blockchainkit.proofs import start_ceremony, contribute, verify_contribution >>> srs = start_ceremony(4) >>> step = contribute(srs, 1234) >>> verify_contribution(srs, step) True
- blockchainkit.proofs.systems.ceremony.contribute(srs, secret)[source]#
Multiply the hidden tau by
secret: power i is multiplied bysecret**i.The participant publishes the new string and
[secret] G, then must forgetsecret.- Parameters:
- Return type:
- blockchainkit.proofs.systems.ceremony.verify_contribution(before, contribution)[source]#
Check with pairings that a contribution updated
beforeby a known secret.It fails if the new string is not a list of consecutive powers, if it does not build on
before, or if the contribution is degenerate.- Parameters:
before (SRS)
contribution (Contribution)
- Return type:
Circuits and SNARKs#
Arithmetic circuits and rank-1 constraint systems (R1CS).
SNARKs prove statements written as arithmetic over a prime field. A rank-1 constraint system lists constraints of the form
<a_j, z> * <b_j, z> = <c_j, z>,
over a witness vector z = (1, public inputs, private values). Additions and multiplications by constants are free: they only change the linear combinations. Each multiplication of two unknowns costs one constraint.
Circuit builds an R1CS while computing the witness: a
Wire is a linear combination of variables that also carries its
value, so x * x * x + x + 5 written with Circuit.mul() produces
both the constraints and the satisfying assignment.
- class blockchainkit.proofs.systems.circuits.Wire(terms, value, prime)[source]#
Bases:
objectA linear combination of circuit variables, and its value in the current witness.
Wires support
+,-, negation, and multiplication by an integer constant. Multiplying two wires is a constraint: useCircuit.mul().- terms#
- value#
- prime#
- class blockchainkit.proofs.systems.circuits.Circuit(prime=2013265921)[source]#
Bases:
objectBuild an R1CS and its witness together.
Declare every public input first, then private inputs and gates. The witness vector is
(1, public..., private...).- Parameters:
prime (
int) – Field modulus; defaults to the BabyBear prime, the scalar field ofPAIRING_CURVE.
Examples
>>> from blockchainkit.proofs import Circuit >>> circuit = Circuit() >>> out = circuit.public(35) >>> x = circuit.private(3) >>> x3 = circuit.mul(circuit.mul(x, x), x) >>> circuit.assert_equal(x3 + x + 5, out) >>> r1cs = circuit.r1cs() >>> r1cs.num_constraints, r1cs.is_satisfied(circuit.witness()) (5, True)
- assert_boolean(wire)[source]#
Add the constraint
wire * (1 - wire) = 0: the wire is 0 or 1.- Parameters:
wire (Wire)
- Return type:
None
- r1cs()[source]#
Freeze the constraints into an R1CS.
One row
x_i * 0 = 0is appended for the constant and for each public input, as Bellman and snarkjs do: it makes the public inputs’ polynomials linearly independent, so a Groth16 proof binds them even if a public input appears in no other constraint.- Return type:
Quadratic arithmetic programs: Gennaro, Gentry, Parno and Raykova (2013), used by Pinocchio.
An R1CS with m constraints is m separate equations. A QAP turns them into
one polynomial identity. Assign constraint j to the point w**j of a
power-of-two domain, and let A_i(X) interpolate column i of the first
matrix over the domain (likewise B_i and C_i). For a witness z,
A(X) = sum z_i A_i(X), B(X) = sum z_i B_i(X), C(X) = sum z_i C_i(X)
agree with the constraint values at each w**j, so every constraint
holds exactly when A(X) B(X) - C(X) vanishes on the whole domain, that
is, when it is divisible by the target t(X) = X**n - 1:
A(X) B(X) - C(X) = H(X) t(X).
Pinocchio (Parno, Howell, Gentry and Raykova, 2013) checked this divisibility at one secret point with pairings, giving the first practical general-purpose SNARK; Groth16 refined it.
- blockchainkit.proofs.systems.qap.domain_size(r1cs)[source]#
The smallest power of two, at least 2, that holds one point per constraint.
- blockchainkit.proofs.systems.qap.r1cs_to_qap(r1cs)[source]#
Interpolate each column of the R1CS matrices over the domain of roots of unity.
Examples
>>> from blockchainkit.proofs import Circuit, r1cs_to_qap, qap_divide >>> circuit = Circuit() >>> out, x = circuit.public(35), circuit.private(3) >>> circuit.assert_equal(circuit.mul(circuit.mul(x, x), x) + x + 5, out) >>> qap = r1cs_to_qap(circuit.r1cs()) >>> qap.domain_size, qap_divide(qap, circuit.witness()).satisfied (8, True)
- blockchainkit.proofs.systems.qap.qap_divide(qap, witness)[source]#
Form A, B and C for a witness and divide
A B - Cby the target polynomial.The witness satisfies the R1CS exactly when the remainder is zero.
- Parameters:
- Return type:
Groth’s succinct non-interactive argument (2016): three group elements per proof.
The setup encodes the QAP of a circuit at a secret point tau, blinded by secrets alpha, beta, gamma and delta. With prover randomness r and s,
A = alpha + sum z_i A_i(tau) + r delta, B = beta + sum z_i B_i(tau) + s delta, C = (sum over private i of z_i K_i(tau) + H(tau) t(tau)) / delta + s A + r B - r s delta,
and the verifier checks one pairing equation,
e(A, B) = e(alpha, beta) e(sum over public i of x_i L_i, gamma) e(C, delta),
which holds, by the QAP identity, exactly when A B - C is divisible by
the target. Proofs are three points and verification three pairings plus
one per public input, whatever the circuit; r and s make every proof a
fresh, zero-knowledge one.
This implementation uses the symmetric toy pairing of
pairing, so A, B and C lie in one
group; Groth’s paper and deployments use asymmetric pairings, with B in a
second group.
- blockchainkit.proofs.systems.groth16.groth16_setup(r1cs, trapdoor, curve=Curve(p=24159191051, a=1, b=0, generator=(4010265589, 9478310149), order=2013265921, name='toy-pairing'))[source]#
Encode a circuit’s QAP at the trapdoor’s secret point.
The QAP polynomials are evaluated at tau directly through the Lagrange basis of the domain,
L_j(tau) = (tau**n - 1) w**j / (n (tau - w**j)), without ever writing them out.- Parameters:
r1cs (
blockchainkit.proofs.core.base.R1CS) – Over the curve’s scalar field.trapdoor (
blockchainkit.proofs.core.base.Groth16Trapdoor) – Nonzero secrets, with tau outside the domain. Destroy them after.curve (
blockchainkit.crypto.systems.curves.Curve) – The pairing curve.
- Returns:
Its
verifying_keyattribute is the verifier’s key.- Return type:
- blockchainkit.proofs.systems.groth16.groth16_prove(proving_key, witness, *, seed=0)[source]#
Prove that
witnesssatisfies the circuit, revealing only its public inputs.- Parameters:
proving_key (
blockchainkit.proofs.core.base.ProvingKey)witness (
collections.abc.Sequenceofint) –(1, public..., private...), for example fromwitness().seed (
int) – Seed for the blinding scalars r and s; distinct seeds give distinct proofs of the same statement.
- Raises:
ValueError – The witness does not satisfy the circuit.
- Return type:
- blockchainkit.proofs.systems.groth16.groth16_verify(verifying_key, public_inputs, proof)[source]#
Check
e(A, B) = e(alpha, beta) e(L, gamma) e(C, delta); malformed proofs return False.Examples
>>> from blockchainkit.proofs import (Circuit, Groth16Trapdoor, groth16_setup, ... groth16_prove, groth16_verify) >>> circuit = Circuit() >>> out, x = circuit.public(35), circuit.private(3) >>> circuit.assert_equal(circuit.mul(circuit.mul(x, x), x) + x + 5, out) >>> key = groth16_setup(circuit.r1cs(), Groth16Trapdoor(11, 22, 33, 44, 55)) >>> proof = groth16_prove(key, circuit.witness(), seed=1) >>> vk = key.verifying_key >>> groth16_verify(vk, [35], proof), groth16_verify(vk, [36], proof) (True, False)
- Parameters:
verifying_key (VerifyingKey)
proof (Groth16Proof)
- Return type:
PLONK: gates, permutation arguments and a universal setup (2019).
Gabizon, Williamson and Ciobotaru wrote a circuit as n gates, each
q_L a + q_R b + q_O c + q_M a b + q_C = 0,
over the gate’s left, right and output wires a, b, c, with the selectors q fixing the gate’s kind. That a wire carries the same value in two gates is a separate copy constraint: the wire values must be unchanged by a permutation sigma of the 3n wire positions. PLONK checks it with a grand product. With random beta and gamma,
prod over positions (w + beta id + gamma) = prod over positions (w + beta sigma + gamma)
holds whp only if the values are constant on every cycle of sigma. The prover builds the running product z(X) over the domain, z(1) = 1 and
z(w X) prod (w_k + beta sigma_k(X) + gamma) = z(X) prod (w_k + beta k_k X + gamma),
so gates, copies, and z(1) = 1 all become polynomial identities that
vanish on the domain, combined with powers of a random alpha and divided by
X**n - 1. Every polynomial is a KZG commitment, opened at a random zeta.
Unlike Groth16, the setup is universal: one powers-of-tau string serves
every circuit up to its size.
This version opens every polynomial at zeta instead of using PLONK’s linearization, and adds no blinding, so it is succinct and sound but not zero-knowledge. There are no public inputs: constants go in q_C.
- blockchainkit.proofs.systems.plonk.K1 = 31#
The coset shifts that label the right and output wire columns.
- blockchainkit.proofs.systems.plonk.K2 = 961#
The coset shifts that label the right and output wire columns.
- class blockchainkit.proofs.systems.plonk.PlonkCircuit(prime=2013265921)[source]#
Bases:
objectBuild PLONK gates over numbered variables.
Examples
>>> from blockchainkit.proofs import PlonkCircuit >>> circuit = PlonkCircuit() >>> x = circuit.variable(3) >>> x3 = circuit.mul(circuit.mul(x, x), x) >>> circuit.assert_constant(circuit.add(x3, x), 30) # x**3 + x = 30 >>> len(circuit.gates), circuit.is_satisfied() (4, True)
- Parameters:
prime (int)
- gate(left, right, output, *, ql=0, qr=0, qo=0, qm=0, qc=0)[source]#
Add the gate
ql a + qr b + qo c + qm a b + qc = 0on three variables.
- blockchainkit.proofs.systems.plonk.plonk_setup(circuit, srs)[source]#
Preprocess a circuit: interpolate and commit to its selectors and permutation.
The same
srsserves every circuit whose padded size n satisfies3 n <= srs.max_degree.- Parameters:
circuit (PlonkCircuit)
srs (SRS)
- Return type:
- blockchainkit.proofs.systems.plonk.permutation_product(key, rows, beta, gamma)[source]#
Return the running product z_0 = 1, z_1, …, z_n of the copy-constraint argument.
The last value is 1 when every copy constraint holds, and differs from 1 whp otherwise.
- blockchainkit.proofs.systems.plonk.plonk_prove(key, srs, rows)[source]#
Prove that wire values satisfy the gates and the copy constraints.
- Parameters:
key (
blockchainkit.proofs.core.base.PlonkKey) – Fromplonk_setup().srs (
blockchainkit.proofs.core.base.SRS) – The setup the key was made with.rows (
collections.abc.Sequenceofcollections.abc.Sequenceofint) – Left, right and output values per gate, asPlonkCircuit.wire_values()returns them.
- Raises:
ValueError – A gate fails, or a copy constraint fails (the running product does not return to 1).
- Return type:
Examples
>>> from blockchainkit.proofs import (PlonkCircuit, plonk_setup, plonk_prove, ... plonk_verify, trusted_setup) >>> circuit = PlonkCircuit() >>> x = circuit.variable(3) >>> circuit.assert_constant(circuit.add(circuit.mul(circuit.mul(x, x), x), x), 30) >>> srs = trusted_setup(12, secret=4321) >>> key = plonk_setup(circuit, srs) >>> plonk_verify(key, srs, plonk_prove(key, srs, circuit.wire_values())) True
- blockchainkit.proofs.systems.plonk.plonk_verify(key, srs, proof)[source]#
Recompute the challenges, check the identity at zeta, and check both batched openings.
- Parameters:
key (PlonkKey)
srs (SRS)
proof (PlonkProof)
- Return type:
Zerocash: private payments from notes, nullifiers and a SNARK (2014).
Ben-Sasson, Chiesa, Garman, Green, Miers, Tromer and Virza hid the sender,
receiver and amount of every payment. Coins are notes (owner, value,
rho, r), and the ledger stores only their hiding commitments, in a Merkle
tree. To spend a note, its owner publishes
its nullifier
PRF_sk(rho), which the ledger records so the note cannot be spent twice, yet which nobody can link to the commitment,the commitment to a new note for the payee, and
a zk-SNARK that some commitment in the tree opens to a note owned by the spender, with this nullifier and enough value for the new note.
The ledger learns that some unspent note paid someone, and nothing else.
This model uses Groth16 on the toy pairing, MiMC (Albrecht et al., 2016) as the arithmetic-friendly hash, and one input and one output per spend, with a public amount leaving the pool. Zerocash used SHA-256 inside its circuits, two inputs and two outputs per transaction, and encryption of the new note for the payee.
- blockchainkit.proofs.systems.zerocash.MIMC_ROUNDS = 12#
Rounds of MiMC-7 over the BabyBear field: ceil(log_7 p).
- Type:
- blockchainkit.proofs.systems.zerocash.VALUE_BITS = 16#
Note values are range-checked to this many bits inside the circuit.
- Type:
- blockchainkit.proofs.systems.zerocash.mimc_hash(left, right, prime=2013265921)[source]#
Compress two field elements with MiMC-7 in Miyaguchi-Preneel mode.
Each round computes
x = (x + key + c_i)**7withkey = left, starting fromx = right; the output isx + 2 key + right. Seven is the smallest exponent coprime top - 1, so each round permutes the field, and it costs four multiplications in a circuit.>>> from blockchainkit.proofs import mimc_hash >>> mimc_hash(1, 2) == mimc_hash(1, 2) != mimc_hash(2, 1) True
- blockchainkit.proofs.systems.zerocash.mimc_gadget(circuit, left, right)[source]#
Constrain a new wire to
mimc_hash(left, right): 4 constraints per round.
- blockchainkit.proofs.systems.zerocash.owner_key(secret_key)[source]#
The public address of a spending key:
H(sk, 0).
- blockchainkit.proofs.systems.zerocash.note_commitment(note)[source]#
H(H(owner, value), H(rho, r)): hiding thanks to r, binding thanks to the hash.
- blockchainkit.proofs.systems.zerocash.nullifier(secret_key, rho)[source]#
H(sk, rho): only the owner can compute it, and it reveals nothing about the note.
- blockchainkit.proofs.systems.zerocash.spend_circuit(depth, secret_key, note, siblings, index, new_note, public_value)[source]#
Build the spend statement and its witness.
Public inputs: the Merkle root, the nullifier, the new commitment and the amount leaving the pool. The circuit checks that the spender owns the note, that its commitment is a leaf on the path to the root, that the nullifier is derived from it, that the new commitment is well formed, and that
value = new value + public valuewith the new value in[0, 2**16). Without that range check, a “negative” new valuep - kwould mint k coins.
- blockchainkit.proofs.systems.zerocash.spend_setup(depth, trapdoor)[source]#
Run the Groth16 setup for the spend circuit of a tree of the given depth.
- Parameters:
depth (int)
trapdoor (Groth16Trapdoor)
- Return type:
- class blockchainkit.proofs.systems.zerocash.ShieldedPool(depth, verifying_key)[source]#
Bases:
objectThe ledger side of Zerocash: commitments, nullifiers, and proof checks.
- Parameters:
depth (
int) – The note-commitment tree holds2**depthnotes.verifying_key (
blockchainkit.proofs.core.base.VerifyingKey) – Fromspend_setup()for the same depth.
Examples
>>> from blockchainkit.proofs import (Groth16Trapdoor, Note, ShieldedPool, owner_key, ... prove_spend, spend_setup) >>> key = spend_setup(2, Groth16Trapdoor(5, 6, 7, 8, 9)) >>> pool = ShieldedPool(2, key.verifying_key) >>> alice, bob = 1111, 2222 >>> coin = Note(owner_key(alice), 100, rho=1, randomness=77) >>> index = pool.mint(coin) >>> payment = Note(owner_key(bob), 60, rho=2, randomness=88) >>> spend = prove_spend(key, pool, alice, coin, index, payment, public_value=40) >>> pool.spend(spend), pool.supply (1, 60)
- property commitments: tuple[int, ...]#
Every note commitment in order, which is all an observer sees of the notes.
- spend(transaction)[source]#
Check a spend and record it; return the new note’s index.
- Raises:
ValueError – Unknown root, reused nullifier, amount out of range, or invalid proof.
- Parameters:
transaction (SpendTransaction)
- Return type:
- blockchainkit.proofs.systems.zerocash.prove_spend(proving_key, pool, secret_key, note, index, new_note, public_value, *, seed=0)[source]#
Spend
note(atindexin the pool) intonew_noteplus a public amount.- Raises:
ValueError – The statement is false: a key that does not own the note, values that do not balance, or a new value outside
[0, 2**16).- Parameters:
proving_key (ProvingKey)
pool (ShieldedPool)
secret_key (int)
note (Note)
index (int)
new_note (Note)
public_value (int)
seed (int)
- Return type:
Transparent proofs#
Bulletproofs: short range proofs without a trusted setup (2018).
Confidential amounts hide a value v in a Pedersen commitment
V = g**v h**gamma, which is useless unless the value is shown to lie in
[0, 2**n): otherwise a “negative” output mints money. Bünz, Bootle,
Boneh, Poelstra, Wuille and Maxwell write v’s bits as a vector a_L, set
a_R = a_L - 1, and reduce the three facts
<a_L, 2**n> = v, a_L o a_R = 0, a_L - a_R - 1 = 0
with random challenges y and z to one inner product t = <l, r>. The
prover commits to l and r with vectors of independent generators, and
proves the inner product by halving: each round it sends two group
elements L and R, the verifier sends a challenge x, and both fold the
vectors and generators to half their length,
a’ = x a_lo + x**-1 a_hi, G’ = G_lo**(x**-1) o G_hi**x.
After log2(n) rounds one scalar pair remains: a proof of
2 log2(n) + 4 group elements and 5 scalars, with no trusted setup,
because the generators are hashed into the group (Bootle, Cerulli,
Chaidos, Groth and Petit, 2016).
This version works in the 62-bit Schnorr group
TEACHING_GROUP
(multiplicatively); deployments use elliptic curves such as Curve25519.
- blockchainkit.proofs.systems.bulletproofs.range_commitment(value, blinding)[source]#
The Pedersen commitment
g**value h**blindingthat a range proof is about.
- blockchainkit.proofs.systems.bulletproofs.range_proof(value, blinding, bits=32, *, seed=0)[source]#
Prove that the commitment to
valuehides a number in[0, 2**bits).- Parameters:
- Raises:
ValueError –
valueis not below2**bits: no proof exists.- Return type:
Examples
>>> from blockchainkit.proofs import range_proof, verify_range_proof >>> proof = range_proof(1000, blinding=77, bits=16) >>> verify_range_proof(proof, bits=16), len(proof.left) (True, 4)
- blockchainkit.proofs.systems.bulletproofs.verify_range_proof(proof, bits=32)[source]#
Check a range proof: one equation for t, then the folded inner-product argument.
Malformed proofs return False.
- Parameters:
proof (RangeProof)
bits (int)
- Return type:
FRI: fast Reed-Solomon interactive oracle proofs of proximity (2018).
Ben-Sasson, Bentov, Horesh and Riabzev showed how to check, with a few
hash-based queries, that a committed list of N values is (close to) the
evaluations of a polynomial of degree below d, a Reed-Solomon codeword.
Split f into even and odd parts, f(X) = f_e(X**2) + X f_o(X**2); a
random alpha folds them into one polynomial of half the degree on a
domain of half the size,
f’(x**2) = (f(x) + f(-x)) / 2 + alpha (f(x) - f(-x)) / (2 x).
The prover commits to each layer with a Merkle tree, and after log2(d) folds the polynomial is a constant. The verifier picks random positions and checks each fold from two Merkle-opened values. A function far from every low-degree polynomial stays far after folding whp, so each query catches it with constant probability: no trusted setup, only hashes.
Domains are cosets offset * <w> of power-of-two subgroups of the
BabyBear field; the challenges come from a Fiat-Shamir transcript.
- blockchainkit.proofs.systems.fri.fri_prove(evaluations, degree_bound, *, offset=31, num_queries=8, transcript=None)[source]#
Commit to evaluations on
offset * <w>and prove they have degree belowdegree_bound.- Parameters:
evaluations (
collections.abc.Sequenceofint) – Values atoffset * w**ifor i < N, with N a power of two.degree_bound (
int) – A power of two, at most N / 2; N / degree_bound is the blowup.offset (
int) – The coset shift.num_queries (
int) – Positions the verifier checks.transcript (
blockchainkit.proofs.utils.transcript.Transcript, optional) – Continue an existing Fiat-Shamir transcript, as a STARK does.
- Return type:
Examples
>>> from blockchainkit.proofs import evaluate_on_domain, fri_prove, fri_verify >>> values = evaluate_on_domain(range(1, 9), 32, offset=31) # degree 7 on 32 points >>> proof = fri_prove(values, 8) >>> fri_verify(proof, domain_size=32, degree_bound=8) True
- blockchainkit.proofs.systems.fri.fri_verify(proof, *, domain_size, degree_bound, offset=31, num_queries=8, transcript=None)[source]#
Check every query’s Merkle openings and folds, down to the final constant.
The query positions must be the ones the transcript dictates, so a caller that continues the transcript (a STARK) can trust
proof.queries[k].indexonce this returns True.
STARKs: scalable, transparent arguments of knowledge from hashes (2018).
Ben-Sasson, Bentov, Horesh and Riabzev proved computations with nothing
but a hash function: no trusted setup, no pairings, and (as far as is
known) no quantum weakness. The computation is written as an execution
trace with algebraic constraints between consecutive rows (an AIR).
Here the trace is the Fibonacci sequence, a_0 = a_1 = 1 and
a_(i+2) = a_(i+1) + a_i, over T steps, and the claim is a_(T-1).
Interpolate the trace over the subgroup <g> of order T as a polynomial
f, so f(g**i) = a_i. Each constraint becomes a divisibility: the
transition holds on rows 0 to T-3 exactly when
(f(g**2 X) - f(g X) - f(X)) (X - g**(T-2)) (X - g**(T-1)) / (X**T - 1)
is a polynomial, and each boundary value when (f(X) - a) / (X - g**i)
is. The prover evaluates f on a larger coset (the low-degree extension),
commits to it with a Merkle tree, combines the quotients with random
coefficients, and proves with FRI that the combination has low degree.
The verifier recomputes the combination at FRI’s query positions from
Merkle-opened trace values at x, g x and g**2 x. A false claim leaves a
quotient that is not a polynomial, which FRI rejects.
- blockchainkit.proofs.systems.stark.fibonacci_trace(steps)[source]#
The first
stepsFibonacci numbers modulo the BabyBear prime, from 1, 1.>>> from blockchainkit.proofs import fibonacci_trace >>> fibonacci_trace(8) (1, 1, 2, 3, 5, 8, 13, 21)
- blockchainkit.proofs.systems.stark.stark_prove(steps, *, claimed=None, blowup=8, num_queries=16)[source]#
Prove that the Fibonacci sequence from 1, 1 reaches
claimedafterstepsterms.- Parameters:
steps (
int) – T, a power of two, at least 4.claimed (
int, optional) – The claimed last term; the true one by default. A false claim still yields a “proof”, which fails verification.blowup (
int) – The low-degree extension’s size over T: the code’s inverse rate.num_queries (
int) – FRI queries; each also opens the trace.
- Return type:
Examples
>>> from blockchainkit.proofs import stark_prove, stark_verify >>> proof = stark_prove(16) >>> stark_verify(proof, steps=16, result=987), stark_verify(proof, steps=16, result=988) (True, False)
- blockchainkit.proofs.systems.stark.stark_verify(proof, *, steps, result, blowup=8, num_queries=16)[source]#
Check FRI on the composition, then its values against the opened trace.
Halo: recursive proof composition without a trusted setup (2019).
A proof that verifies another proof, which verified another, can compress an entire blockchain’s history into one constant-size proof, if verification is cheap enough to run inside a proof. Inner-product arguments need no setup, but verifying one costs a multi-exponentiation of length n. Bowe, Grigg and Hopwood noticed that this expensive step has a special form: after the log2(n) challenges c_j of the halving rounds, the verifier needs
G_final = prod G_i**s_i, where sum s_i X**i = g(X) = prod (1 + c_j X**(2**(k-j))),
a commitment to a polynomial g that anyone can evaluate in O(log n).
So the verifier does the cheap part, and keeps (c, G_final) as an
accumulator: a claim to be checked later. Two accumulators merge into
one by opening a random combination of their G_finals at a random point,
which is again an inner-product argument, with its own deferred claim. A
chain of any length then needs one O(n) check at the very end.
This module provides the inner-product polynomial commitment and its accumulation, in the 62-bit teaching group. Halo itself ran the cheap verifier inside a circuit over a cycle of two elliptic curves (Tweedledum and Tweedledee); here the verifier runs in Python. Commitments carry no blinding, so they are binding but not hiding.
- blockchainkit.proofs.systems.halo.ipa_generators(size)[source]#
Return
sizeindependent generators G_i, then one more, U, for the evaluation.
- blockchainkit.proofs.systems.halo.ipa_commit(coefficients, generators)[source]#
Commit to a polynomial of degree below n:
prod G_i**a_i.>>> from blockchainkit.proofs import ipa_commit, ipa_generators >>> gens = ipa_generators(4) >>> ipa_commit([1, 2], gens) == ipa_commit([1, 2, 0, 0], gens) True
- blockchainkit.proofs.systems.halo.challenge_polynomial(challenges, point)[source]#
Evaluate
g(X) = prod (1 + c_j X**(2**(k-j)))at a point in O(log n).>>> from blockchainkit.proofs import challenge_polynomial >>> challenge_polynomial([2, 3], 5) == (1 + 2 * 5**2) * (1 + 3 * 5) True
- blockchainkit.proofs.systems.halo.ipa_open(coefficients, point, generators, *, label=b'halo-ipa')[source]#
Prove the value of a committed polynomial at
pointby halving.Each round sends
L = prod G_hi**a_lo U**<a_lo, b_hi>andR = prod G_lo**a_hi U**<a_hi, b_lo>, whereb = (1, x, x**2, ...), and foldsa' = a_lo + c**-1 a_hi,b' = b_lo + c b_hi,G' = G_lo G_hi**c.Examples
>>> from blockchainkit.proofs import ipa_commit, ipa_generators, ipa_open, ipa_verify >>> gens = ipa_generators(8) >>> opening = ipa_open([3, 1, 4, 1, 5], 2, gens) >>> opening.value, ipa_verify(ipa_commit([3, 1, 4, 1, 5], gens), opening, gens) (109, True)
- blockchainkit.proofs.systems.halo.ipa_defer(commitment, opening, generators, *, label=b'halo-ipa')[source]#
Do the cheap part of verification and return the deferred claim about G_final.
This takes O(log n) group operations: it folds the commitment with L and R and evaluates g(x) without the generators, trusting the opening’s
g_final. It returnsNoneif the final equation fails, and otherwise an accumulator thatdecide()must eventually check.- Parameters:
commitment (int)
opening (IPAOpening)
label (bytes)
- Return type:
HaloAccumulator | None
- blockchainkit.proofs.systems.halo.decide(accumulator, generators)[source]#
The expensive O(n) check: is
g_finalreallyprod G_i**s_i?- Parameters:
accumulator (HaloAccumulator)
- Return type:
- blockchainkit.proofs.systems.halo.ipa_verify(commitment, opening, generators)[source]#
Verify an opening completely: the deferred checks, then
decide()at once.- Parameters:
commitment (int)
opening (IPAOpening)
- Return type:
- blockchainkit.proofs.systems.halo.accumulate(accumulators, generators)[source]#
Merge deferred claims into one: open
sum rho**j g_j(X)at a random point.The prover knows each g_j’s coefficients from its challenges, so it can open the combination of the G_finals; if any G_final were wrong, it could not. The result’s own deferred claim is the new accumulator.
- Parameters:
accumulators (Sequence[HaloAccumulator])
- Return type:
- blockchainkit.proofs.systems.halo.verify_accumulation(accumulators, proof, generators)[source]#
Check an accumulation step in O(k log n) and return the new accumulator, or
None.Examples
>>> from blockchainkit.proofs import (ipa_generators, ipa_commit, ipa_open, ipa_defer, ... accumulate, verify_accumulation, decide) >>> gens = ipa_generators(8) >>> claims = [ipa_defer(ipa_commit(f, gens), ipa_open(f, 5, gens), gens) ... for f in ([1, 2, 3], [4, 5, 6, 7])] >>> merged = verify_accumulation(claims, accumulate(claims, gens), gens) >>> decide(merged, gens) True
- Parameters:
accumulators (Sequence[HaloAccumulator])
proof (AccumulationProof)
- Return type:
HaloAccumulator | None
Plotting#
Plotting helpers for blockchainkit.proofs: polynomials over a field, proof sizes, and wiring.
- blockchainkit.proofs.visualizers.plots.plot_copy_constraints(key, *, ax=None)[source]#
Draw a PLONK circuit’s wire grid and the cycles of its copy-constraint permutation.
Each row is a gate and each column a wire (a, b, c); positions joined by a line must carry the same value.
- Parameters:
key (
blockchainkit.proofs.core.base.PlonkKey) – Fromplonk_setup().ax (
matplotlib.axes.Axes, optional) – Axes to draw on; a new figure is created if omitted.
- Return type:
- blockchainkit.proofs.visualizers.plots.plot_field_polynomials(polynomials, prime, *, ax=None)[source]#
Scatter each polynomial’s value at every point of a small field, and ring where they agree.
- Parameters:
polynomials (
collections.abc.Mappingofstrtocollections.abc.Sequenceofint) – Labelled coefficient lists, constant term first.prime (
int) – A field small enough to plot point by point (at most 2000).ax (
matplotlib.axes.Axes, optional) – Axes to draw on; a new figure is created if omitted.
- Return type:
- blockchainkit.proofs.visualizers.plots.plot_proof_sizes(series, *, xlabel='statement size', ylabel='bytes', ax=None)[source]#
Draw how proof sizes grow, on log-log axes, one line per series.
- Parameters:
series (
collections.abc.Mappingofstrtocollections.abc.Sequenceoftuple) – Labelled(x, y)points, for example(trace length, proof bytes).xlabel (
str) – Axis labels.ylabel (
str) – Axis labels.ax (
matplotlib.axes.Axes, optional) – Axes to draw on; a new figure is created if omitted.
- Return type: