Skip to main content

core_utils/circuit/latest/
constraint.rs

1//! Constraints attached to a [`Gate::ConstrainPlaintextBits`](crate::circuit::Gate) gate.
2//!
3//! The gate takes a plaintext bit batch that each peer supplies locally — typically fetched from a
4//! URL, so peers may hold different bits — and outputs a batch of the same length satisfying the
5//! constraints, which are held as a disjunction of conjunctions ([`ConstraintClause`]). Both sides
6//! are plaintext, so a constraint is a pure deterministic predicate: each peer can evaluate it
7//! without any secure computation. Constraints therefore carry no secret material; keys and
8//! expected values are ordinary plaintext wires.
9//!
10//! Bit-order convention, matching [`Gate::CompressPlaintextPoint`](crate::circuit::Gate) and the
11//! compiler's `Byte`: a byte string is laid out as 8 consecutive bits per byte, least-significant
12//! bit first.
13
14use std::collections::BTreeMap;
15
16use ed25519_dalek::{Signature, VerifyingKey};
17use serde::{Deserialize, Serialize};
18use sha2::{Digest as _, Sha256};
19
20use crate::circuit::{GateIndex, Slice};
21
22/// Nesting allowed in a [`ConstraintExpr`]. Bounds the work a hostile circuit can ask every peer
23/// to do while reconciling, and the recursion in validation.
24pub const MAX_EXPR_DEPTH: usize = 8;
25
26/// Total nodes allowed in a single [`ConstraintExpr`], for the same reason.
27pub const MAX_EXPR_NODES: usize = 64;
28
29#[cfg_attr(test, derive(strum::AsRefStr, strum::VariantNames))]
30/// Hash available to [`ConstraintExpr::Digest`].
31///
32/// Wire-format note: variants must only be appended, never reordered.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
34#[repr(C)]
35pub enum DigestAlgorithm {
36    Sha256,
37}
38
39impl DigestAlgorithm {
40    pub const fn output_bits(&self) -> u32 {
41        match self {
42            DigestAlgorithm::Sha256 => 256,
43        }
44    }
45
46    fn hash(&self, bytes: &[u8]) -> Vec<u8> {
47        match self {
48            DigestAlgorithm::Sha256 => Sha256::digest(bytes).to_vec(),
49        }
50    }
51}
52
53#[cfg_attr(test, derive(strum::AsRefStr, strum::VariantNames))]
54/// Encoding available to [`ConstraintExpr::Decode`].
55///
56/// Wire-format note: variants must only be appended, never reordered.
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
58#[repr(C)]
59pub enum Encoding {
60    /// base64url without padding: RFC 4648 section 5, the alphabet and the omitted padding that
61    /// JWS and COSE use.
62    Base64UrlNoPad,
63}
64
65impl Encoding {
66    /// Width of the decoded form in bits, given the width of the encoded form.
67    ///
68    /// `None` when nothing of that width decodes at all, so an operand that could never be
69    /// evaluated is rejected when the gate is validated rather than by every peer at runtime. For
70    /// unpadded base64 that is any character count congruent to 1 mod 4: a lone trailing character
71    /// carries 6 bits and the byte it would start needs 8.
72    pub const fn decoded_bits(&self, encoded_bits: u32) -> Option<u32> {
73        match self {
74            Encoding::Base64UrlNoPad => {
75                if !encoded_bits.is_multiple_of(8) {
76                    return None;
77                }
78                let chars = encoded_bits / 8;
79                if chars % 4 == 1 {
80                    return None;
81                }
82                // Exact, not a bound: with padding omitted the character count determines the byte
83                // count, which is why this is the variant a fixed-width operand can use.
84                Some(8 * (3 * chars / 4))
85            }
86        }
87    }
88
89    /// Decodes, or `None` if `bytes` is not the one canonical encoding of some byte string.
90    ///
91    /// Strict in both directions that matter here. Only this variant's alphabet is accepted, so
92    /// `+`, `/` and `=` are rejected rather than quietly tolerated; and the spare low bits of a
93    /// short final group must be zero. Laxness in either would give one payload several encodings,
94    /// and peers holding different encodings of the same bytes are distinct candidates as far as
95    /// reconciliation is concerned -- an encoding quirk arriving as a disagreement.
96    fn decode(&self, bytes: &[u8]) -> Option<Vec<u8>> {
97        match self {
98            Encoding::Base64UrlNoPad => {
99                let mut out = Vec::with_capacity(3 * bytes.len() / 4);
100                for group in bytes.chunks(4) {
101                    if group.len() == 1 {
102                        return None;
103                    }
104                    let mut acc = 0u32;
105                    for byte in group {
106                        acc = (acc << 6) | u32::from(base64url_digit(*byte)?);
107                    }
108                    // A group of n characters carries 6n bits and yields n-1 whole bytes. The
109                    // remaining low bits are not part of the output and must be zero.
110                    let whole_bytes = group.len() - 1;
111                    let spare = 6 * group.len() - 8 * whole_bytes;
112                    if acc & ((1 << spare) - 1) != 0 {
113                        return None;
114                    }
115                    acc >>= spare;
116                    for i in (0..whole_bytes).rev() {
117                        out.push((acc >> (8 * i)) as u8);
118                    }
119                }
120                Some(out)
121            }
122        }
123    }
124}
125
126/// One base64url character as its six bits. `None` for anything outside the alphabet, padding
127/// included.
128const fn base64url_digit(byte: u8) -> Option<u8> {
129    match byte {
130        b'A'..=b'Z' => Some(byte - b'A'),
131        b'a'..=b'z' => Some(byte - b'a' + 26),
132        b'0'..=b'9' => Some(byte - b'0' + 52),
133        b'-' => Some(62),
134        b'_' => Some(63),
135        _ => None,
136    }
137}
138
139#[cfg_attr(test, derive(strum::AsRefStr, strum::VariantNames))]
140/// What to do when more than one *distinct* candidate satisfies the clauses.
141///
142/// Peers agreeing is the ordinary case and is never ambiguous, however many peers there are — this
143/// only applies when the satisfying candidates differ from each other, which means the source
144/// served different bytes to different peers and every version of them verified.
145///
146/// Wire-format note: variants must only be appended, never reordered.
147#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
148#[repr(C)]
149pub enum OnAmbiguity {
150    /// Report failure, as for no satisfying candidate at all: zeroed data and a false success bit.
151    ///
152    /// The conservative reading, and the right one when the source is meant to be byte-stable:
153    /// handing the circuit one of several verifying values silently is the failure mode hardest to
154    /// notice.
155    ///
156    /// It is not the right one for a source that mints per request. A JWS carrying `iat` inside
157    /// its signed payload gives two peers two valid responses whose difference no constraint
158    /// could have forbidden, so this refuses every computation over such a source rather than
159    /// the occasional one; `test_two_valid_jws_from_one_issuer_are_ambiguous` is that case.
160    /// The trade between the variants is which single peer gets to misbehave: under `Fail` one
161    /// peer contributing a differently-minted valid response denies the computation, and under
162    /// `TakeSmallestBits` one peer choosing among responses it can obtain valid signatures for
163    /// steers which is used.
164    Fail,
165    /// Take the smallest candidate.
166    ///
167    /// Smallest as a *bit vector*, in this module's LSB-first-per-byte order — which is not the
168    /// smallest byte string, and differs from it whenever the candidates' low bits do: of `0x41`
169    /// and `0x42` this picks `0x42`, whose first bit is 0. Deterministic and independent of the
170    /// order peers answer in, which is what it has to be; just not what "smallest" suggests.
171    ///
172    /// For a source where any correctly-signed answer is acceptable and proceeding beats failing.
173    TakeSmallestBits,
174}
175
176#[cfg_attr(test, derive(strum::AsRefStr, strum::VariantNames))]
177/// How a constraint's operand is built.
178///
179/// Real signed formats often sign something other than a contiguous run of the response: CMS signs
180/// a re-tagged copy of the attribute block, and a detached signature covers a digest of the content
181/// rather than the content. Expressing an operand as a small expression rather than a single slice
182/// is what makes those reachable, and it costs nothing — this is evaluated on plaintext during
183/// reconciliation, so a digest here is a library call rather than thousands of gates.
184///
185/// JWS needs no reconstruction of its signing input -- that is `b64(header) || "." ||
186/// b64(payload)`, a contiguous prefix of the compact serialization, so a slice covers it. What it
187/// needs is the other direction: its signature is base64url where the gate wants raw bytes, which
188/// is [`ConstraintExpr::Decode`].
189///
190/// Wire-format note: variants must only be appended, never reordered.
191#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
192#[repr(C)]
193pub enum ConstraintExpr {
194    /// Bits of the gate's input batch.
195    Slice(Slice),
196    /// A fixed byte string: framing, separators, domain-separation tags.
197    Constant(Vec<u8>),
198    /// The operands' bits, in order.
199    Concat(Vec<ConstraintExpr>),
200    /// The digest of another expression, which must be a whole number of bytes.
201    Digest {
202        algorithm: DigestAlgorithm,
203        of: Box<ConstraintExpr>,
204    },
205    /// A plaintext bit batch on another wire.
206    Wire(GateIndex),
207    /// Another expression decoded, which must be a whole number of bytes and must be a valid
208    /// encoding. Signed formats overwhelmingly transport their signatures in text.
209    ///
210    /// The one operand that is a predicate as much as a value: bytes that do not decode make this
211    /// unevaluable, and an unevaluable operand fails its constraint. So "is well-formed base64url"
212    /// needs no constraint of its own.
213    Decode {
214        encoding: Encoding,
215        of: Box<ConstraintExpr>,
216    },
217}
218
219impl ConstraintExpr {
220    /// Slices of the gate input this expression reads, in order.
221    pub fn slices(&self) -> Vec<&Slice> {
222        match self {
223            ConstraintExpr::Slice(slice) => vec![slice],
224            ConstraintExpr::Constant(_) | ConstraintExpr::Wire(_) => Vec::new(),
225            ConstraintExpr::Concat(parts) => {
226                parts.iter().flat_map(ConstraintExpr::slices).collect()
227            }
228            ConstraintExpr::Digest { of, .. } | ConstraintExpr::Decode { of, .. } => of.slices(),
229        }
230    }
231
232    /// Wires this expression reads, in order. May repeat.
233    pub fn wires(&self) -> Vec<GateIndex> {
234        match self {
235            ConstraintExpr::Wire(wire) => vec![*wire],
236            ConstraintExpr::Slice(_) | ConstraintExpr::Constant(_) => Vec::new(),
237            ConstraintExpr::Concat(parts) => parts.iter().flat_map(ConstraintExpr::wires).collect(),
238            ConstraintExpr::Digest { of, .. } | ConstraintExpr::Decode { of, .. } => of.wires(),
239        }
240    }
241
242    /// Mutable references to the wires this expression reads, in the same order as [`Self::wires`].
243    pub fn wires_mut(&mut self) -> Vec<&mut GateIndex> {
244        match self {
245            ConstraintExpr::Wire(wire) => vec![wire],
246            ConstraintExpr::Slice(_) | ConstraintExpr::Constant(_) => Vec::new(),
247            ConstraintExpr::Concat(parts) => parts
248                .iter_mut()
249                .flat_map(ConstraintExpr::wires_mut)
250                .collect(),
251            ConstraintExpr::Digest { of, .. } | ConstraintExpr::Decode { of, .. } => of.wires_mut(),
252        }
253    }
254
255    pub fn depth(&self) -> usize {
256        match self {
257            ConstraintExpr::Slice(_) | ConstraintExpr::Constant(_) | ConstraintExpr::Wire(_) => 1,
258            ConstraintExpr::Concat(parts) => {
259                1 + parts.iter().map(ConstraintExpr::depth).max().unwrap_or(0)
260            }
261            ConstraintExpr::Digest { of, .. } | ConstraintExpr::Decode { of, .. } => 1 + of.depth(),
262        }
263    }
264
265    pub fn node_count(&self) -> usize {
266        match self {
267            ConstraintExpr::Slice(_) | ConstraintExpr::Constant(_) | ConstraintExpr::Wire(_) => 1,
268            ConstraintExpr::Concat(parts) => {
269                1 + parts.iter().map(ConstraintExpr::node_count).sum::<usize>()
270            }
271            ConstraintExpr::Digest { of, .. } | ConstraintExpr::Decode { of, .. } => {
272                1 + of.node_count()
273            }
274        }
275    }
276
277    /// Length in bits, given a way to look up how wide a wire is.
278    ///
279    /// `Option` rather than a plain `u32` because a wire may not resolve, and to leave room for
280    /// operands whose width is only known once the bytes are in hand -- parsing a DER field, say.
281    /// Validation checks what it can and defers the rest.
282    pub fn static_len<F>(&self, wire_bits: &F) -> Option<u32>
283    where
284        F: Fn(GateIndex) -> Option<u32>,
285    {
286        match self {
287            ConstraintExpr::Slice(slice) => Some(slice.len()),
288            ConstraintExpr::Constant(bytes) => u32::try_from(8 * bytes.len()).ok(),
289            ConstraintExpr::Wire(wire) => wire_bits(*wire),
290            ConstraintExpr::Concat(parts) => parts.iter().try_fold(0u32, |acc, part| {
291                part.static_len(wire_bits)
292                    .and_then(|len| acc.checked_add(len))
293            }),
294            ConstraintExpr::Digest { algorithm, .. } => Some(algorithm.output_bits()),
295            ConstraintExpr::Decode { encoding, of } => of
296                .static_len(wire_bits)
297                .and_then(|bits| encoding.decoded_bits(bits)),
298        }
299    }
300
301    /// Evaluates against the gate's input batch and the values of the wires it reads.
302    ///
303    /// `None` when the expression cannot be evaluated: a slice running past the batch, a wire with
304    /// no value, a digest or a decode over a partial byte, or bytes that are not a valid encoding
305    /// of anything.
306    ///
307    /// `wires` is a `BTreeMap` for size rather than for order. It holds one entry per wire operand
308    /// — a handful at most, and `MAX_EXPR_NODES` caps it at 64 — so an ordered map on `u32`
309    /// keys beats building a hash state and paying SipHash per lookup. Nothing here iterates
310    /// it, so the ordering is not load-bearing today; keeping it ordered is insurance for a
311    /// path where every peer must agree bit for bit, should anyone later log, hash or serialise
312    /// the wire set.
313    pub fn eval(&self, bits: &[bool], wires: &BTreeMap<GateIndex, Vec<bool>>) -> Option<Vec<bool>> {
314        match self {
315            ConstraintExpr::Slice(slice) => slice
316                .get_indices()
317                .into_iter()
318                .map(|i| bits.get(i as usize).copied())
319                .collect(),
320            ConstraintExpr::Constant(bytes) => Some(bytes_to_bits(bytes)),
321            ConstraintExpr::Wire(wire) => wires.get(wire).cloned(),
322            ConstraintExpr::Concat(parts) => {
323                let mut out = Vec::new();
324                for part in parts {
325                    out.extend(part.eval(bits, wires)?);
326                }
327                Some(out)
328            }
329            ConstraintExpr::Digest { algorithm, of } => {
330                let inner = of.eval(bits, wires)?;
331                Some(bytes_to_bits(&algorithm.hash(&bits_to_bytes(&inner)?)))
332            }
333            ConstraintExpr::Decode { encoding, of } => {
334                let inner = of.eval(bits, wires)?;
335                Some(bytes_to_bits(&encoding.decode(&bits_to_bytes(&inner)?)?))
336            }
337        }
338    }
339}
340
341#[cfg_attr(test, derive(strum::AsRefStr, strum::VariantNames))]
342/// Which way a [`PlaintextBitConstraint::Comparison`] bounds its left operand.
343///
344/// Wire-format note: variants must only be appended, never reordered.
345#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
346#[repr(C)]
347pub enum Relation {
348    /// `lhs <= rhs`.
349    AtMost,
350    /// `lhs >= rhs`.
351    AtLeast,
352}
353
354#[cfg_attr(test, derive(strum::AsRefStr, strum::VariantNames))]
355/// Signature scheme used by [`PlaintextBitConstraint::Signature`].
356///
357/// Wire-format note: variants must only be appended, never reordered.
358#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
359#[repr(C)]
360pub enum SignatureScheme {
361    Ed25519,
362}
363
364impl SignatureScheme {
365    /// Length of a signature, in bits.
366    pub const fn signature_bits(&self) -> u32 {
367        match self {
368            SignatureScheme::Ed25519 => 512,
369        }
370    }
371
372    /// Length of a public key, in bits.
373    pub const fn public_key_bits(&self) -> u32 {
374        match self {
375            SignatureScheme::Ed25519 => 256,
376        }
377    }
378}
379
380#[cfg_attr(test, derive(strum::AsRefStr, strum::VariantNames))]
381/// A predicate that the bits entering a [`Gate::ConstrainPlaintextBits`](crate::circuit::Gate) gate
382/// must satisfy.
383///
384/// Operands are [`ConstraintExpr`]s, so a constraint can name what was actually signed rather than
385/// only a contiguous run of the response. Wires read by an expression must themselves be values the
386/// peers already agree on -- a key read from an unconstrained local input only moves the problem up
387/// one level.
388///
389/// Wire-format note: variants must only be appended, never reordered.
390#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
391#[repr(C)]
392pub enum PlaintextBitConstraint {
393    /// `signature` is a valid `scheme` signature of `message` under `public_key`.
394    Signature {
395        scheme: SignatureScheme,
396        signature: ConstraintExpr,
397        message: ConstraintExpr,
398        public_key: ConstraintExpr,
399    },
400    /// The two operands are equal.
401    Equality {
402        bits: ConstraintExpr,
403        expected: ConstraintExpr,
404    },
405    /// `lhs` is ordered against `rhs` as **big-endian byte strings of equal length**, which is
406    /// what bounds a timestamp or a sequence number carried as text.
407    ///
408    /// Bytes, deliberately, and not the bit vector this module otherwise works in: LSB-first
409    /// per byte compares the low bits of each byte first, and the two orders disagree -- of
410    /// `"...001"` and `"...002"` the byte order says the first is smaller and the bit order says
411    /// the second is. The same trap as [`OnAmbiguity::TakeSmallestBits`], and here it would mean
412    /// accepting a value the bound was meant to exclude.
413    ///
414    /// Equal length is required rather than zero-padded, because it is what makes the comparison
415    /// unambiguous: for equal-length operands, lexicographic order over bytes *is* numeric order,
416    /// so fixed-width ASCII decimal needs no parsing. Unequal lengths would leave "shorter means
417    /// smaller" and "shorter is zero-extended" both plausible, so they are refused instead.
418    ///
419    /// A comparison does not count towards a clause's coverage. It bounds its operand rather than
420    /// pinning it, and ten bytes constrained only by `>= T` still leave a peer a wide range to
421    /// vary in.
422    Comparison {
423        relation: Relation,
424        lhs: ConstraintExpr,
425        rhs: ConstraintExpr,
426    },
427}
428
429impl PlaintextBitConstraint {
430    /// The constraint's operands, in wire order.
431    pub fn operands(&self) -> Vec<&ConstraintExpr> {
432        match self {
433            PlaintextBitConstraint::Signature {
434                signature,
435                message,
436                public_key,
437                ..
438            } => vec![signature, message, public_key],
439            PlaintextBitConstraint::Equality { bits, expected } => vec![bits, expected],
440            PlaintextBitConstraint::Comparison { lhs, rhs, .. } => vec![lhs, rhs],
441        }
442    }
443
444    fn operands_mut(&mut self) -> Vec<&mut ConstraintExpr> {
445        match self {
446            PlaintextBitConstraint::Signature {
447                signature,
448                message,
449                public_key,
450                ..
451            } => vec![signature, message, public_key],
452            PlaintextBitConstraint::Equality { bits, expected } => vec![bits, expected],
453            PlaintextBitConstraint::Comparison { lhs, rhs, .. } => vec![lhs, rhs],
454        }
455    }
456
457    /// The operands whose bytes this constraint *pins*, and which therefore count towards a
458    /// clause's coverage.
459    ///
460    /// Not the same as the operands it reads. Reading a byte proves nothing about it; coverage is
461    /// meant to answer "could a peer vary this byte and still satisfy the clause?", and only an
462    /// operand the constraint actually determines answers no.
463    ///
464    /// The distinction is load-bearing for `Signature`. A signature's `public_key` is its
465    /// *authority*, not something it pins: "this key signed this message" says nothing about the
466    /// key being the right one. Counting it would let a clause authenticate itself — a peer puts a
467    /// key, a message and a matching signature in its own proposal, every byte is read, and the
468    /// gate reports success on data that peer signed for itself. Excluding the key means those
469    /// bytes are uncovered unless something else in the clause pins them, which is exactly the
470    /// question that needed asking. It also leaves the legitimate shape working: a key carried in
471    /// the response and bound by a second constraint to a trusted root is covered by that
472    /// constraint.
473    ///
474    /// Note the signature and the message are covered *regardless* of where the key comes from.
475    /// Refusing to cover them when the key is sliced from the batch would be redundant — the key's
476    /// own bytes are already uncovered, so the clause is refused unless something anchors them —
477    /// and it would break the legitimate shape: a key carried in the response and pinned by a
478    /// second constraint leaves the signature and message covered by this one.
479    ///
480    /// `Equality` is the same question without the asymmetry: a side is pinned by the other only
481    /// if the other is anchored. Two slices of the batch compared against each other pin neither,
482    /// since any pair that happens to match satisfies it.
483    pub fn covering_operands(&self) -> Vec<&ConstraintExpr> {
484        match self {
485            PlaintextBitConstraint::Signature {
486                signature, message, ..
487            } => vec![signature, message],
488            PlaintextBitConstraint::Equality { bits, expected } => {
489                match (bits.slices().is_empty(), expected.slices().is_empty()) {
490                    (true, _) => vec![expected],
491                    (_, true) => vec![bits],
492                    _ => Vec::new(),
493                }
494            }
495            // A comparison bounds rather than pins; see the variant's docs.
496            PlaintextBitConstraint::Comparison { .. } => Vec::new(),
497        }
498    }
499
500    /// Slices of the gate input this constraint reads, in order.
501    pub fn slices(&self) -> Vec<&Slice> {
502        self.operands()
503            .into_iter()
504            .flat_map(ConstraintExpr::slices)
505            .collect()
506    }
507
508    /// Wires this constraint reads, in order.
509    pub fn wires(&self) -> Vec<GateIndex> {
510        self.operands()
511            .into_iter()
512            .flat_map(ConstraintExpr::wires)
513            .collect()
514    }
515
516    /// Mutable references to the wires this constraint reads, in the same order as [`Self::wires`].
517    pub fn wires_mut(&mut self) -> Vec<&mut GateIndex> {
518        self.operands_mut()
519            .into_iter()
520            .flat_map(ConstraintExpr::wires_mut)
521            .collect()
522    }
523
524    /// Checks the constraint against `bits`, the gate's input batch, and the values of the wires it
525    /// reads.
526    ///
527    /// Returns `false` rather than erroring on anything malformed -- an unevaluable operand, a
528    /// public key off the curve -- since that is a failed constraint, not a broken circuit.
529    pub fn is_satisfied(&self, bits: &[bool], wires: &BTreeMap<GateIndex, Vec<bool>>) -> bool {
530        match self {
531            PlaintextBitConstraint::Signature {
532                scheme,
533                signature,
534                message,
535                public_key,
536            } => {
537                let (Some(signature), Some(message), Some(public_key)) = (
538                    signature.eval(bits, wires),
539                    message.eval(bits, wires),
540                    public_key.eval(bits, wires),
541                ) else {
542                    return false;
543                };
544                let (Some(signature), Some(message), Some(public_key)) = (
545                    bits_to_bytes(&signature),
546                    bits_to_bytes(&message),
547                    bits_to_bytes(&public_key),
548                ) else {
549                    return false;
550                };
551                match scheme {
552                    SignatureScheme::Ed25519 => {
553                        let (Ok(public_key), Ok(signature)) = (
554                            <[u8; 32]>::try_from(public_key),
555                            <[u8; 64]>::try_from(signature),
556                        ) else {
557                            return false;
558                        };
559                        match VerifyingKey::from_bytes(&public_key) {
560                            Ok(key) => key
561                                .verify_strict(&message, &Signature::from_bytes(&signature))
562                                .is_ok(),
563                            Err(_) => false,
564                        }
565                    }
566                }
567            }
568            PlaintextBitConstraint::Equality {
569                bits: lhs,
570                expected,
571            } => match (lhs.eval(bits, wires), expected.eval(bits, wires)) {
572                (Some(lhs), Some(rhs)) => lhs == rhs,
573                _ => false,
574            },
575            PlaintextBitConstraint::Comparison { relation, lhs, rhs } => {
576                let (Some(lhs), Some(rhs)) = (lhs.eval(bits, wires), rhs.eval(bits, wires)) else {
577                    return false;
578                };
579                // Through bytes, which is where the ordering is defined. `Vec<u8>` compares
580                // lexicographically, so this is the big-endian reading, and equal length makes it
581                // the numeric one too.
582                let (Some(lhs), Some(rhs)) = (bits_to_bytes(&lhs), bits_to_bytes(&rhs)) else {
583                    return false;
584                };
585                if lhs.len() != rhs.len() {
586                    return false;
587                }
588                match relation {
589                    Relation::AtMost => lhs <= rhs,
590                    Relation::AtLeast => lhs >= rhs,
591                }
592            }
593        }
594    }
595}
596
597/// A conjunction of constraints.
598///
599/// A gate holds a disjunction of these, so its constraints form a disjunctive normal form: the gate
600/// is satisfied when *some* clause is, and a clause is satisfied when *all* of its constraints are.
601/// A builder writes an alternative — a second accepted response shape, a fallback signing key — by
602/// adding a clause, rather than by chaining a second gate over the same input, which would
603/// reconcile the peers twice and could settle on a different candidate each time.
604#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
605pub struct ConstraintClause(Vec<PlaintextBitConstraint>);
606
607impl ConstraintClause {
608    pub fn new(constraints: Vec<PlaintextBitConstraint>) -> Self {
609        Self(constraints)
610    }
611
612    pub fn constraints(&self) -> &[PlaintextBitConstraint] {
613        &self.0
614    }
615
616    pub fn is_empty(&self) -> bool {
617        self.0.is_empty()
618    }
619
620    /// Wires this clause's constraints read, in order. May repeat.
621    pub fn wires(&self) -> Vec<GateIndex> {
622        self.0
623            .iter()
624            .flat_map(PlaintextBitConstraint::wires)
625            .collect()
626    }
627
628    /// Mutable references to the wires this clause's constraints read, in the same order as
629    /// [`Self::wires`].
630    pub fn wires_mut(&mut self) -> Vec<&mut GateIndex> {
631        self.0
632            .iter_mut()
633            .flat_map(PlaintextBitConstraint::wires_mut)
634            .collect()
635    }
636
637    /// Checks every constraint in the clause against `bits`, the gate's input batch, and the
638    /// values of the wires they read. Keyed by wire rather than positional, so a constraint that
639    /// reads the same wire twice, or reads them in a different order, needs no special handling.
640    pub fn is_satisfied(&self, bits: &[bool], wires: &BTreeMap<GateIndex, Vec<bool>>) -> bool {
641        self.0
642            .iter()
643            .all(|constraint| constraint.is_satisfied(bits, wires))
644    }
645}
646
647/// Unpacks bytes into bits, least-significant bit first.
648fn bytes_to_bits(bytes: &[u8]) -> Vec<bool> {
649    bytes
650        .iter()
651        .flat_map(|byte| (0..8).map(move |i| (byte >> i) & 1u8 == 1u8))
652        .collect()
653}
654
655/// Packs bits into bytes, least-significant bit first. `None` on a partial trailing byte: an
656/// operand that is not a whole number of bytes cannot be signed over or hashed.
657fn bits_to_bytes(bits: &[bool]) -> Option<Vec<u8>> {
658    if !bits.len().is_multiple_of(8) {
659        return None;
660    }
661    Some(
662        bits.chunks(8)
663            .map(|chunk| {
664                chunk
665                    .iter()
666                    .enumerate()
667                    .fold(0u8, |acc, (i, bit)| acc | (u8::from(*bit) << i))
668            })
669            .collect(),
670    )
671}
672
673#[cfg(test)]
674mod tests {
675    use ed25519_dalek::{Signer, SigningKey};
676
677    use super::*;
678
679    fn bytes_to_bits(bytes: &[u8]) -> Vec<bool> {
680        bytes
681            .iter()
682            .flat_map(|byte| (0..8).map(move |i| (byte >> i) & 1u8 == 1u8))
683            .collect()
684    }
685
686    /// `message || signature`, the layout a signed URL response would arrive in.
687    fn signed_batch(key: &SigningKey, message: &[u8]) -> (Vec<bool>, Vec<bool>) {
688        let signature = key.sign(message);
689        let mut bits = bytes_to_bits(message);
690        bits.extend(bytes_to_bits(&signature.to_bytes()));
691        (bits, bytes_to_bits(key.verifying_key().as_bytes()))
692    }
693
694    fn slice(start: u32, size: u32) -> ConstraintExpr {
695        ConstraintExpr::Slice(Slice::range(start, size, 1).unwrap())
696    }
697
698    /// The key on wire 0.
699    fn wires(public_key: &[bool]) -> BTreeMap<GateIndex, Vec<bool>> {
700        BTreeMap::from([(0, public_key.to_vec())])
701    }
702
703    fn signature_constraint(message_bytes: u32) -> PlaintextBitConstraint {
704        PlaintextBitConstraint::Signature {
705            scheme: SignatureScheme::Ed25519,
706            signature: slice(8 * message_bytes, 512),
707            message: slice(0, 8 * message_bytes),
708            public_key: ConstraintExpr::Wire(0),
709        }
710    }
711
712    #[test]
713    fn test_signature_constraint() {
714        let key = SigningKey::from_bytes(&[7u8; 32]);
715        let message = b"{\"price\":42}";
716        let (bits, public_key) = signed_batch(&key, message);
717        let constraint = signature_constraint(message.len() as u32);
718
719        assert!(constraint.is_satisfied(&bits, &wires(&public_key)));
720
721        // Flipping any message bit invalidates the signature.
722        let mut tampered = bits.clone();
723        tampered[3] = !tampered[3];
724        assert!(!constraint.is_satisfied(&tampered, &wires(&public_key)));
725
726        // So does verifying under a different key.
727        let other = SigningKey::from_bytes(&[9u8; 32]);
728        let other_key = bytes_to_bits(other.verifying_key().as_bytes());
729        assert!(!constraint.is_satisfied(&bits, &wires(&other_key)));
730    }
731
732    #[test]
733    fn test_signature_constraint_rejects_malformed_key() {
734        let key = SigningKey::from_bytes(&[7u8; 32]);
735        let message = b"{\"price\":42}";
736        let (bits, _) = signed_batch(&key, message);
737        // All-ones is not a canonical compressed Edwards point.
738        let public_key = vec![true; 256];
739        assert!(
740            !signature_constraint(message.len() as u32).is_satisfied(&bits, &wires(&public_key))
741        );
742    }
743
744    #[test]
745    fn test_equality_constraint() {
746        let bits = bytes_to_bits(b"header:body");
747        let constraint = PlaintextBitConstraint::Equality {
748            bits: slice(0, 48),
749            expected: ConstraintExpr::Wire(0),
750        };
751        assert!(constraint.is_satisfied(&bits, &wires(&bytes_to_bits(b"header"))));
752        assert!(!constraint.is_satisfied(&bits, &wires(&bytes_to_bits(b"HEADER"))));
753    }
754
755    #[test]
756    fn test_bits_to_bytes_is_lsb_first() {
757        assert_eq!(
758            bits_to_bytes(&bytes_to_bits(&[0x01, 0x80, 0xa5])).unwrap(),
759            [0x01, 0x80, 0xa5]
760        );
761        // A partial trailing byte cannot be signed over or hashed.
762        assert_eq!(bits_to_bytes(&[true; 4]), None);
763    }
764
765    /// The shape that motivated expressions: a signature over something assembled from the
766    /// response rather than a contiguous run of it -- here a constant prefix, a slice, and a digest
767    /// of another slice, which is the CMS/JWS pattern in miniature.
768    #[test]
769    fn test_signature_over_a_composed_message() {
770        let key = SigningKey::from_bytes(&[7u8; 32]);
771        let payload = b"{\"price\":42}";
772
773        // The batch is `payload || signature`, and what is signed is `0x31 || H(payload)`.
774        let signed = {
775            let mut signed = vec![0x31u8];
776            signed.extend(Sha256::digest(payload));
777            signed
778        };
779        let mut bits = bytes_to_bits(payload);
780        bits.extend(bytes_to_bits(&key.sign(&signed).to_bytes()));
781
782        let constraint = PlaintextBitConstraint::Signature {
783            scheme: SignatureScheme::Ed25519,
784            signature: slice(8 * payload.len() as u32, 512),
785            message: ConstraintExpr::Concat(vec![
786                ConstraintExpr::Constant(vec![0x31]),
787                ConstraintExpr::Digest {
788                    algorithm: DigestAlgorithm::Sha256,
789                    of: Box::new(slice(0, 8 * payload.len() as u32)),
790                },
791            ]),
792            public_key: ConstraintExpr::Wire(0),
793        };
794        let public_key = bytes_to_bits(key.verifying_key().as_bytes());
795        assert!(constraint.is_satisfied(&bits, &wires(&public_key)));
796
797        // The digest binds the payload, so tampering with it still breaks the signature even
798        // though no slice of the payload is signed directly.
799        let mut tampered = bits.clone();
800        tampered[3] = !tampered[3];
801        assert!(!constraint.is_satisfied(&tampered, &wires(&public_key)));
802    }
803
804    /// Binding a digest to a value carried elsewhere in the response -- the CMS `messageDigest`
805    /// attribute pattern.
806    #[test]
807    fn test_equality_against_a_digest() {
808        let content = b"the content";
809        let mut bits = bytes_to_bits(content);
810        bits.extend(bytes_to_bits(&Sha256::digest(content)));
811
812        let constraint = PlaintextBitConstraint::Equality {
813            bits: slice(8 * content.len() as u32, 256),
814            expected: ConstraintExpr::Digest {
815                algorithm: DigestAlgorithm::Sha256,
816                of: Box::new(slice(0, 8 * content.len() as u32)),
817            },
818        };
819        assert!(constraint.is_satisfied(&bits, &BTreeMap::new()));
820
821        let mut tampered = bits.clone();
822        tampered[0] = !tampered[0];
823        assert!(!constraint.is_satisfied(&tampered, &BTreeMap::new()));
824    }
825
826    #[test]
827    fn test_static_len_adds_up() {
828        let expr = ConstraintExpr::Concat(vec![
829            ConstraintExpr::Constant(vec![0u8; 3]),
830            slice(0, 5),
831            ConstraintExpr::Digest {
832                algorithm: DigestAlgorithm::Sha256,
833                of: Box::new(ConstraintExpr::Wire(0)),
834            },
835            ConstraintExpr::Wire(1),
836        ]);
837        // 24 constant + 5 slice + 256 digest + 7 wire
838        assert_eq!(
839            expr.static_len(&|wire| Some(wire + 7)),
840            Some(24 + 5 + 256 + 8)
841        );
842        // A wire whose width is unknown makes the whole length unknown, rather than wrong.
843        assert_eq!(expr.static_len(&|_| None), None);
844    }
845
846    #[test]
847    fn test_eval_refuses_a_slice_past_the_batch() {
848        assert_eq!(slice(0, 16).eval(&[true; 8], &BTreeMap::new()), None);
849    }
850
851    #[test]
852    fn test_eval_refuses_a_digest_over_a_partial_byte() {
853        let expr = ConstraintExpr::Digest {
854            algorithm: DigestAlgorithm::Sha256,
855            of: Box::new(slice(0, 4)),
856        };
857        assert_eq!(expr.eval(&[true; 8], &BTreeMap::new()), None);
858    }
859
860    #[test]
861    fn test_depth_and_node_count() {
862        let expr = ConstraintExpr::Concat(vec![
863            slice(0, 1),
864            ConstraintExpr::Digest {
865                algorithm: DigestAlgorithm::Sha256,
866                of: Box::new(ConstraintExpr::Concat(vec![slice(1, 1), slice(2, 1)])),
867            },
868        ]);
869        assert_eq!(expr.depth(), 4);
870        assert_eq!(expr.node_count(), 6);
871    }
872
873    /// A real JWS, produced once from the seed `[7u8; 32]`:
874    ///
875    /// ```python
876    /// import base64
877    /// from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
878    /// b64u = lambda b: base64.urlsafe_b64encode(b).rstrip(b"=")
879    /// sk = Ed25519PrivateKey.from_private_bytes(bytes([7] * 32))
880    /// si = b64u(b'{"alg":"EdDSA"}') + b"." + b64u(b'{"iat":1756100000,"px":"0000004242"}')
881    /// jws = si + b"." + b64u(sk.sign(si))
882    /// ```
883    const JWS: &[u8] = b"eyJhbGciOiJFZERTQSJ9.eyJpYXQiOjE3NTYxMDAwMDAsInB4IjoiMDAwMDAwNDI0MiJ9.\
884                         ZdO1q9RcSfUrdq8UhqZYHVNBHp1OsDLgKG16bQDd-txuigbHkeuG-Bqbu335MrjoPL5Ssq6e\
885                         3mJiJpXOTW6nCw";
886
887    /// The compact serialization's three fields, in bytes: `b64u(header)`, `b64u(payload)`,
888    /// `b64u(signature)`, with a `.` between each pair.
889    const JWS_SIGNING_INPUT_BYTES: u32 = 20 + 1 + 48;
890    const JWS_SIGNATURE_BYTES: u32 = 86;
891
892    /// The constraint a JWS actually needs: the signing input is a contiguous prefix, and the
893    /// signature is that prefix's text-encoded signature sitting after the second `.`.
894    fn jws_constraint() -> PlaintextBitConstraint {
895        PlaintextBitConstraint::Signature {
896            scheme: SignatureScheme::Ed25519,
897            signature: ConstraintExpr::Decode {
898                encoding: Encoding::Base64UrlNoPad,
899                of: Box::new(slice(
900                    8 * (JWS_SIGNING_INPUT_BYTES + 1),
901                    8 * JWS_SIGNATURE_BYTES,
902                )),
903            },
904            message: slice(0, 8 * JWS_SIGNING_INPUT_BYTES),
905            public_key: ConstraintExpr::Wire(0),
906        }
907    }
908
909    #[test]
910    fn test_jws_verifies_through_a_decoded_signature() {
911        let key = SigningKey::from_bytes(&[7u8; 32]);
912        let public_key = bytes_to_bits(key.verifying_key().as_bytes());
913        assert_eq!(JWS.len(), 156);
914        let bits = bytes_to_bits(JWS);
915
916        assert!(jws_constraint().is_satisfied(&bits, &wires(&public_key)));
917
918        // The signature covers the encoded signing input, so flipping a character of the payload
919        // segment invalidates it -- no decoding of the payload required.
920        let mut tampered = JWS.to_vec();
921        tampered[60] ^= 0x01;
922        assert!(!jws_constraint().is_satisfied(&bytes_to_bits(&tampered), &wires(&public_key)));
923    }
924
925    /// A signature that is not valid base64url makes its operand unevaluable, and an unevaluable
926    /// operand fails the constraint. This is the property that lets a decoder be an operand and a
927    /// predicate at once: nothing has to check well-formedness separately.
928    #[test]
929    fn test_a_signature_that_does_not_decode_fails_the_constraint() {
930        let key = SigningKey::from_bytes(&[7u8; 32]);
931        let public_key = bytes_to_bits(key.verifying_key().as_bytes());
932
933        for (what, byte) in [
934            ("padding", b'='),
935            ("standard alphabet", b'+'),
936            ("junk", b'!'),
937        ] {
938            let mut body = JWS.to_vec();
939            body[80] = byte;
940            assert!(
941                !jws_constraint().is_satisfied(&bytes_to_bits(&body), &wires(&public_key)),
942                "a {what} character should not decode"
943            );
944        }
945    }
946
947    /// The same issuer and price, minted in November 2023 -- a token a peer could replay.
948    const JWS_STALE: &[u8] = b"eyJhbGciOiJFZERTQSJ9.eyJpYXQiOjE3MDAwMDAwMDAsInB4IjoiMDAwMDAwNDI0\
949                               MiJ9.Sp-OEJIDpCKuVEuTyxKkMZyNP-2pI86wCfWxN59KPONfJBNC4ILVEMSOdLhl\
950                               kjPEu4XYEgzIyHNoHFbyHQ8cCg";
951
952    /// `iat` sits at payload bytes 7..17, which no operand can address directly -- an expression
953    /// reads slices of the *batch*, and nothing takes a sub-range of a decoded operand.
954    ///
955    /// What makes it reachable is that base64 groups are independent: cut the encoded form at a
956    /// multiple of four characters and that piece decodes on its own. Payload bytes 6..18 are
957    /// groups 2..5, so encoded characters 8..24 -- batch bytes 29..45 -- decode to exactly
958    /// `:1756100000,`, the digits with their two delimiters. The slop is the framing, which is the
959    /// best case: a bound carrying the same framing is comparing the digits and checking the field
960    /// boundaries at once.
961    fn iat_window() -> ConstraintExpr {
962        ConstraintExpr::Decode {
963            encoding: Encoding::Base64UrlNoPad,
964            of: Box::new(slice(8 * 29, 8 * 16)),
965        }
966    }
967
968    fn iat_bound(relation: Relation, bound: &[u8]) -> PlaintextBitConstraint {
969        PlaintextBitConstraint::Comparison {
970            relation,
971            lhs: iat_window(),
972            rhs: ConstraintExpr::Constant(bound.to_vec()),
973        }
974    }
975
976    /// Bounding a timestamp carried as ASCII decimal, with no parsing anywhere: for equal-length
977    /// operands, lexicographic order over bytes is numeric order, and ASCII digits are contiguous
978    /// and ascending.
979    #[test]
980    fn test_a_comparison_bounds_a_text_timestamp() {
981        let not_before = iat_bound(Relation::AtLeast, b":1756000000,");
982        let not_after = iat_bound(Relation::AtMost, b":1757000000,");
983        // A comparison against a constant reads no wires.
984        let no_wires = BTreeMap::new();
985
986        let fresh = bytes_to_bits(JWS);
987        assert!(not_before.is_satisfied(&fresh, &no_wires));
988        assert!(not_after.is_satisfied(&fresh, &no_wires));
989
990        // A token from 2023 fails the lower bound and nothing else, which is the replay this is
991        // for: it verifies perfectly well under the issuer's key.
992        let key = SigningKey::from_bytes(&[7u8; 32]);
993        let stale = bytes_to_bits(JWS_STALE);
994        assert!(jws_constraint().is_satisfied(
995            &stale,
996            &wires(&bytes_to_bits(key.verifying_key().as_bytes()))
997        ));
998        assert!(!not_before.is_satisfied(&stale, &no_wires));
999        assert!(not_after.is_satisfied(&stale, &no_wires));
1000    }
1001
1002    /// The comparison is over bytes, and this is the pair that proves it.
1003    ///
1004    /// `"...001"` and `"...002"` differ in one ASCII digit. As byte strings the first is smaller;
1005    /// as LSB-first bit vectors the second is, because bit 0 of `'1'` is 1 and of `'2'` is 0. A
1006    /// comparison built on the bit order would accept a timestamp its bound was meant to exclude,
1007    /// which is the same unit confusion as `OnAmbiguity::TakeSmallestBits` and much worse here.
1008    #[test]
1009    fn test_a_comparison_is_over_bytes_not_the_bit_vector() {
1010        let lower = b"1756100001";
1011        let higher = b"1756100002";
1012        assert!(lower < higher, "as byte strings");
1013        assert!(
1014            bytes_to_bits(lower) > bytes_to_bits(higher),
1015            "and the other way as LSB-first bit vectors, which is the trap"
1016        );
1017
1018        let no_wires = BTreeMap::new();
1019        let bits = bytes_to_bits(lower);
1020        let at_most = PlaintextBitConstraint::Comparison {
1021            relation: Relation::AtMost,
1022            lhs: slice(0, 8 * 10),
1023            rhs: ConstraintExpr::Constant(higher.to_vec()),
1024        };
1025        let at_least = PlaintextBitConstraint::Comparison {
1026            relation: Relation::AtLeast,
1027            lhs: slice(0, 8 * 10),
1028            rhs: ConstraintExpr::Constant(higher.to_vec()),
1029        };
1030        assert!(
1031            at_most.is_satisfied(&bits, &no_wires),
1032            "1756100001 <= 1756100002"
1033        );
1034        assert!(!at_least.is_satisfied(&bits, &no_wires));
1035    }
1036
1037    /// Unequal widths are refused rather than zero-extended: "shorter means smaller" and "shorter
1038    /// is zero-padded" are both plausible readings, and a comparison that silently picks one is a
1039    /// bound that does not mean what it says.
1040    #[test]
1041    fn test_a_comparison_refuses_unequal_widths() {
1042        let constraint = PlaintextBitConstraint::Comparison {
1043            relation: Relation::AtMost,
1044            lhs: slice(0, 8 * 4),
1045            rhs: ConstraintExpr::Constant(b"12345".to_vec()),
1046        };
1047        assert!(!constraint.is_satisfied(&bytes_to_bits(b"1234"), &BTreeMap::new()));
1048    }
1049
1050    /// Which operands count towards coverage, which is not the same as which are read.
1051    #[test]
1052    fn test_only_pinned_operands_cover() {
1053        let n = |c: PlaintextBitConstraint| c.covering_operands().len();
1054
1055        // A comparison bounds rather than pins.
1056        assert_eq!(n(iat_bound(Relation::AtLeast, b":1756000000,")), 0);
1057
1058        // A signature under an anchored key pins its signature and its message -- but never the
1059        // key itself, which is the constraint's authority rather than something it determines.
1060        assert_eq!(n(jws_constraint()), 2);
1061        assert!(jws_constraint()
1062            .covering_operands()
1063            .iter()
1064            .all(|operand| !matches!(operand, ConstraintExpr::Wire(_))));
1065
1066        // A key sliced out of the batch is covered by nothing, which is what refuses the clause.
1067        // The signature and message are still covered by it -- deliberately, so that a key pinned
1068        // by a *second* constraint leaves a working cert-chain clause.
1069        assert_eq!(n(self_signed_constraint()), 2);
1070        let key_bits = 512 + 8 * 12;
1071        assert!(
1072            self_signed_constraint()
1073                .covering_operands()
1074                .iter()
1075                .flat_map(|operand| operand.slices())
1076                .flat_map(|slice| slice.get_indices())
1077                .all(|index| index < key_bits),
1078            "the key's own bytes must not be covered by its signature"
1079        );
1080
1081        // An equality against something anchored pins the other side, whichever way round it is
1082        // written.
1083        assert_eq!(
1084            n(PlaintextBitConstraint::Equality {
1085                bits: slice(0, 8),
1086                expected: ConstraintExpr::Constant(vec![b'.']),
1087            }),
1088            1
1089        );
1090        assert_eq!(
1091            n(PlaintextBitConstraint::Equality {
1092                bits: ConstraintExpr::Constant(vec![b'.']),
1093                expected: slice(0, 8),
1094            }),
1095            1
1096        );
1097        // Two slices of the batch against each other pin neither.
1098        assert_eq!(
1099            n(PlaintextBitConstraint::Equality {
1100                bits: slice(0, 8),
1101                expected: slice(8, 8),
1102            }),
1103            0
1104        );
1105    }
1106
1107    /// The shape the coverage rule exists to refuse: signature, message and key all sliced out of
1108    /// the peer's own proposal, so the peer signs its own data with a key of its choosing.
1109    ///
1110    /// Every byte is *read*, which is why "is it read?" was the wrong question.
1111    fn self_signed_constraint() -> PlaintextBitConstraint {
1112        PlaintextBitConstraint::Signature {
1113            scheme: SignatureScheme::Ed25519,
1114            signature: slice(0, 512),
1115            message: slice(512, 8 * 12),
1116            public_key: slice(512 + 8 * 12, 256),
1117        }
1118    }
1119
1120    /// And it really does verify — the predicate is satisfied, so nothing but coverage stops it.
1121    #[test]
1122    fn test_a_self_signed_batch_satisfies_its_own_constraint() {
1123        let attacker = SigningKey::from_bytes(&[42u8; 32]);
1124        let message = b"whatever it li";
1125        let message = &message[..12];
1126        let signature = attacker.sign(message);
1127
1128        let mut bits = bytes_to_bits(&signature.to_bytes());
1129        bits.extend(bytes_to_bits(message));
1130        bits.extend(bytes_to_bits(attacker.verifying_key().as_bytes()));
1131
1132        assert!(
1133            self_signed_constraint().is_satisfied(&bits, &BTreeMap::new()),
1134            "a peer can always satisfy a clause whose key it supplies"
1135        );
1136    }
1137
1138    #[test]
1139    fn test_decoded_bits_is_exact_for_unpadded_base64() {
1140        let b64 = Encoding::Base64UrlNoPad;
1141        // A 64-byte signature is 86 characters, and 86 characters are 64 bytes.
1142        assert_eq!(b64.decoded_bits(8 * 86), Some(8 * 64));
1143        assert_eq!(b64.decoded_bits(8 * 4), Some(8 * 3));
1144        assert_eq!(b64.decoded_bits(8 * 2), Some(8));
1145        assert_eq!(b64.decoded_bits(8 * 3), Some(8 * 2));
1146        // One trailing character carries 6 bits and cannot start a byte, so no input of this width
1147        // decodes -- which is a width validation can reject outright.
1148        assert_eq!(b64.decoded_bits(8 * 5), None);
1149        // A partial byte is not a character count at all.
1150        assert_eq!(b64.decoded_bits(4), None);
1151    }
1152
1153    /// Every byte string must have exactly one encoding, or two peers holding the same payload
1154    /// encoded differently would reconcile as two distinct candidates.
1155    #[test]
1156    fn test_decode_rejects_non_canonical_encodings() {
1157        let b64 = Encoding::Base64UrlNoPad;
1158        // "QQ" is `A`: 6 bits used, 4 spare, and the spare ones must be zero. "QR" carries the
1159        // same byte with rubbish in the bits that are not part of it.
1160        assert_eq!(b64.decode(b"QQ"), Some(vec![b'A']));
1161        assert_eq!(b64.decode(b"QR"), None);
1162        // Padded input is the same bytes under a different encoding, so it is refused too.
1163        assert_eq!(b64.decode(b"QQ=="), None);
1164        // A lone trailing character: five characters are four plus one, and one cannot start a
1165        // byte. Six and seven characters are both fine, which is why the check is on the count mod
1166        // four rather than on it being a multiple of four.
1167        assert_eq!(b64.decode(b"QUJDRA"), Some(b"ABCD".to_vec()));
1168        assert_eq!(
1169            b64.decode(b"QUJDRAA"),
1170            Some(vec![b'A', b'B', b'C', b'D', 0])
1171        );
1172        assert_eq!(b64.decode(b"QUJDR"), None);
1173        // The URL alphabet, not the standard one.
1174        assert_eq!(b64.decode(b"-_-_"), Some(vec![0xfb, 0xff, 0xbf]));
1175        assert_eq!(b64.decode(b"+/+/"), None);
1176    }
1177
1178    #[test]
1179    fn test_static_len_of_a_decode() {
1180        let expr = ConstraintExpr::Decode {
1181            encoding: Encoding::Base64UrlNoPad,
1182            of: Box::new(slice(0, 8 * 86)),
1183        };
1184        assert_eq!(expr.static_len(&|_| None), Some(512));
1185        // And it nests, so the ceilings that bound an expression still see it.
1186        assert_eq!(expr.depth(), 2);
1187        assert_eq!(expr.node_count(), 2);
1188        assert_eq!(expr.slices().len(), 1);
1189    }
1190
1191    mod circuit {
1192        use num_bigint::BigUint;
1193        use primitives::random::rng::test_rng;
1194
1195        use super::*;
1196        use crate::{
1197            circuit::{AlgebraicType, Circuit, Gate, Input},
1198            config::DefaultConfig as C,
1199        };
1200
1201        const MESSAGE_BYTES: u32 = 12;
1202        /// `message || signature`
1203        const BATCH_SIZE: u32 = 8 * MESSAGE_BYTES + 512;
1204
1205        fn plaintext_bits(circuit: &mut Circuit<C>, batch_size: u32) -> u32 {
1206            circuit
1207                .add_gate(Gate::Input(Input::Plaintext {
1208                    algebraic_type: AlgebraicType::Bit,
1209                    batch_size,
1210                }))
1211                .unwrap()
1212        }
1213
1214        /// A circuit whose single gate constrains a `message || signature` batch. The public key is
1215        /// gate 0 and the constrained batch gate 1, matching [`signature_constraint`].
1216        fn build(clauses: Vec<ConstraintClause>) -> Result<Circuit<C>, String> {
1217            build_sized(BATCH_SIZE, clauses)
1218        }
1219
1220        fn build_sized(
1221            batch_size: u32,
1222            clauses: Vec<ConstraintClause>,
1223        ) -> Result<Circuit<C>, String> {
1224            let mut circuit = Circuit::<C>::new();
1225            plaintext_bits(&mut circuit, 256);
1226            let x = plaintext_bits(&mut circuit, batch_size);
1227            let gate = circuit
1228                .add_gate(Gate::ConstrainPlaintextBits {
1229                    x,
1230                    clauses,
1231                    on_ambiguity: OnAmbiguity::Fail,
1232                })
1233                .map_err(|e| e.to_string())?;
1234            circuit.add_output(gate).unwrap();
1235            Ok(circuit)
1236        }
1237
1238        /// The single-clause case, which most of these tests only need.
1239        fn one(constraint: PlaintextBitConstraint) -> Vec<ConstraintClause> {
1240            vec![ConstraintClause::new(vec![constraint])]
1241        }
1242
1243        /// The gate's output: `data` followed by the success bit.
1244        fn expect(data: &[bool], ok: bool) -> Vec<BigUint> {
1245            data.iter()
1246                .chain(std::iter::once(&ok))
1247                .map(|b| BigUint::from(*b))
1248                .collect()
1249        }
1250
1251        /// One bit wider than the input: the data, then the success bit.
1252        #[test]
1253        fn test_gate_output_is_the_input_plus_a_success_bit() {
1254            let circuit = build(one(signature_constraint(MESSAGE_BYTES))).unwrap();
1255            let output = circuit.gate_output_unchecked(2);
1256            assert_eq!(output.get_batch_size(), BATCH_SIZE + 1);
1257            assert_eq!(output.get_type(), AlgebraicType::Bit);
1258            assert_eq!(
1259                output.get_form(),
1260                crate::circuit::ShareOrPlaintext::Plaintext
1261            );
1262        }
1263
1264        /// Validation counts the same way the interpreter does: a bound is not a pin, so a clause
1265        /// whose only constraint is a comparison leaves the whole batch uncovered.
1266        #[test]
1267        fn test_a_comparison_alone_does_not_cover_the_batch() {
1268            let err = build(one(PlaintextBitConstraint::Comparison {
1269                relation: Relation::AtLeast,
1270                lhs: slice(0, 8 * 4),
1271                rhs: ConstraintExpr::Constant(vec![0u8; 4]),
1272            }))
1273            .expect_err("a comparison covers nothing");
1274            assert!(err.contains("must be covered"), "{err}");
1275        }
1276
1277        /// Beside a constraint that does pin the batch, the comparison is free: its slices are
1278        /// still range-checked, they just add no coverage of their own.
1279        #[test]
1280        fn test_a_comparison_beside_a_pinning_constraint_validates() {
1281            build(vec![ConstraintClause::new(vec![
1282                signature_constraint(MESSAGE_BYTES),
1283                PlaintextBitConstraint::Comparison {
1284                    relation: Relation::AtLeast,
1285                    lhs: slice(0, 8 * 4),
1286                    rhs: ConstraintExpr::Constant(vec![0u8; 4]),
1287                },
1288            ])])
1289            .expect("pinned by the signature");
1290        }
1291
1292        /// Widths are checked before a peer ever evaluates it, since an operand pair that can never
1293        /// compare is a gate that can only report failure.
1294        #[test]
1295        fn test_validation_refuses_a_comparison_of_unequal_widths() {
1296            let err = build(vec![ConstraintClause::new(vec![
1297                signature_constraint(MESSAGE_BYTES),
1298                PlaintextBitConstraint::Comparison {
1299                    relation: Relation::AtMost,
1300                    lhs: slice(0, 8 * 4),
1301                    rhs: ConstraintExpr::Constant(vec![0u8; 5]),
1302                },
1303            ])])
1304            .expect_err("four bytes against five");
1305            assert!(err.contains("same length"), "{err}");
1306        }
1307
1308        /// `message || signature || key`, wide enough for a clause that reads its key out of the
1309        /// response.
1310        const KEY_IN_RESPONSE_BITS: u32 = 512 + 8 * MESSAGE_BYTES + 256;
1311
1312        /// The clause the coverage rule exists to refuse. It verifies — see
1313        /// `test_a_self_signed_batch_satisfies_its_own_constraint` — so validation is the only
1314        /// thing standing between a peer and a gate that reports success on data it signed itself.
1315        #[test]
1316        fn test_a_self_signed_clause_is_refused() {
1317            let err = build_sized(KEY_IN_RESPONSE_BITS, one(self_signed_constraint()))
1318                .expect_err("a key sliced from the batch is anchored by nothing");
1319            assert!(err.contains("must be covered"), "{err}");
1320        }
1321
1322        /// And the shape that must keep working: the key travels in the response, and a second
1323        /// constraint binds it to a value the circuit author supplied. That is a cert chain in
1324        /// miniature, and it is why the signature and message stay covered even when the key is a
1325        /// slice.
1326        #[test]
1327        fn test_a_key_from_the_response_pinned_by_another_constraint_validates() {
1328            let key_at = 512 + 8 * MESSAGE_BYTES;
1329            build_sized(
1330                KEY_IN_RESPONSE_BITS,
1331                vec![ConstraintClause::new(vec![
1332                    self_signed_constraint(),
1333                    PlaintextBitConstraint::Equality {
1334                        bits: slice(key_at, 256),
1335                        expected: ConstraintExpr::Constant(vec![0u8; 32]),
1336                    },
1337                ])],
1338            )
1339            .expect("the key is pinned by the equality");
1340        }
1341
1342        #[test]
1343        fn test_mock_eval_passes_the_bits_through() {
1344            let key = SigningKey::from_bytes(&[7u8; 32]);
1345            let message = b"{\"price\":42}";
1346            assert_eq!(message.len() as u32, MESSAGE_BYTES);
1347            let (bits, public_key) = signed_batch(&key, message);
1348
1349            let circuit = build(one(signature_constraint(MESSAGE_BYTES))).unwrap();
1350            let inputs = public_key
1351                .iter()
1352                .chain(bits.iter())
1353                .map(|b| BigUint::from(*b))
1354                .collect::<Vec<BigUint>>();
1355            let output = circuit.mock_eval_big_uint(inputs, &mut test_rng());
1356
1357            assert_eq!(output, expect(&bits, true));
1358        }
1359
1360        /// Unsatisfied constraints are reported, not fatal: the data is zeroed and the success bit
1361        /// is false. Being total this way is what lets randomised tests, whose bits will never
1362        /// satisfy a signature, reach anything downstream of the gate.
1363        #[test]
1364        fn test_mock_eval_reports_an_unsatisfied_constraint() {
1365            let key = SigningKey::from_bytes(&[7u8; 32]);
1366            let (mut bits, public_key) = signed_batch(&key, b"{\"price\":42}");
1367            bits[0] = !bits[0];
1368
1369            let circuit = build(one(signature_constraint(MESSAGE_BYTES))).unwrap();
1370            let inputs = public_key
1371                .iter()
1372                .chain(bits.iter())
1373                .map(|b| BigUint::from(*b))
1374                .collect::<Vec<BigUint>>();
1375            let output = circuit.mock_eval_big_uint(inputs, &mut test_rng());
1376
1377            assert_eq!(output, expect(&vec![false; BATCH_SIZE as usize], false));
1378        }
1379
1380        #[test]
1381        fn test_validation_rejects_uncovered_bits() {
1382            // Constrains the signature and all but the last byte of the message.
1383            let err = build(one(PlaintextBitConstraint::Signature {
1384                scheme: SignatureScheme::Ed25519,
1385                signature: slice(8 * MESSAGE_BYTES, 512),
1386                message: slice(0, 8 * (MESSAGE_BYTES - 1)),
1387                public_key: ConstraintExpr::Wire(0),
1388            }))
1389            .unwrap_err();
1390            assert!(
1391                err.contains("clause 0") && err.contains("8 are not"),
1392                "{err}"
1393            );
1394        }
1395
1396        #[test]
1397        fn test_validation_rejects_no_clauses() {
1398            let err = build(vec![]).unwrap_err();
1399            assert!(err.contains("expected at least one clause"), "{err}");
1400        }
1401
1402        #[test]
1403        fn test_validation_rejects_an_empty_clause() {
1404            let err = build(vec![
1405                ConstraintClause::new(vec![signature_constraint(MESSAGE_BYTES)]),
1406                ConstraintClause::new(vec![]),
1407            ])
1408            .unwrap_err();
1409            assert!(err.contains("clause 1 is empty"), "{err}");
1410        }
1411
1412        #[test]
1413        fn test_validation_rejects_out_of_range_slice() {
1414            let err = build(one(signature_constraint(MESSAGE_BYTES + 1))).unwrap_err();
1415            assert!(err.contains("out-of-range"), "{err}");
1416        }
1417
1418        #[test]
1419        fn test_validation_rejects_mis_sized_signature() {
1420            let err = build(one(PlaintextBitConstraint::Signature {
1421                scheme: SignatureScheme::Ed25519,
1422                signature: slice(8 * MESSAGE_BYTES, 256),
1423                message: slice(0, 8 * MESSAGE_BYTES),
1424                public_key: ConstraintExpr::Wire(0),
1425            }))
1426            .unwrap_err();
1427            assert!(
1428                err.contains("expected a 512-bit Ed25519 signature"),
1429                "{err}"
1430            );
1431        }
1432
1433        #[test]
1434        fn test_validation_rejects_mis_sized_public_key() {
1435            let mut circuit = Circuit::<C>::new();
1436            // 128 bits, where Ed25519 wants 256.
1437            plaintext_bits(&mut circuit, 128);
1438            let x = plaintext_bits(&mut circuit, BATCH_SIZE);
1439            let err = circuit
1440                .add_gate(Gate::ConstrainPlaintextBits {
1441                    x,
1442                    on_ambiguity: OnAmbiguity::Fail,
1443                    clauses: one(signature_constraint(MESSAGE_BYTES)),
1444                })
1445                .unwrap_err()
1446                .to_string();
1447            assert!(
1448                err.contains("expected a 256-bit Ed25519 public key"),
1449                "{err}"
1450            );
1451        }
1452
1453        #[test]
1454        fn test_validation_rejects_mis_sized_equality_value() {
1455            let err = build(one(PlaintextBitConstraint::Equality {
1456                // The wire at index 0 holds 256 bits, not `BATCH_SIZE`.
1457                bits: slice(0, BATCH_SIZE),
1458                expected: ConstraintExpr::Wire(0),
1459            }))
1460            .unwrap_err();
1461            assert!(err.contains("must be the same length"), "{err}");
1462        }
1463
1464        /// Two clauses, each a signature under its own key: gate 0 holds the first key, gate 1 the
1465        /// second, gate 2 the constrained batch.
1466        fn build_two_keys() -> Circuit<C> {
1467            let mut circuit = Circuit::<C>::new();
1468            plaintext_bits(&mut circuit, 256);
1469            plaintext_bits(&mut circuit, 256);
1470            let x = plaintext_bits(&mut circuit, BATCH_SIZE);
1471            let clause = |public_key| {
1472                ConstraintClause::new(vec![PlaintextBitConstraint::Signature {
1473                    scheme: SignatureScheme::Ed25519,
1474                    signature: slice(8 * MESSAGE_BYTES, 512),
1475                    message: slice(0, 8 * MESSAGE_BYTES),
1476                    public_key: ConstraintExpr::Wire(public_key),
1477                }])
1478            };
1479            let gate = circuit
1480                .add_gate(Gate::ConstrainPlaintextBits {
1481                    x,
1482                    on_ambiguity: OnAmbiguity::Fail,
1483                    clauses: vec![clause(0), clause(1)],
1484                })
1485                .unwrap();
1486            circuit.add_output(gate).unwrap();
1487            circuit
1488        }
1489
1490        /// A second clause accepts data the first rejects — the fallback-signing-key case.
1491        #[test]
1492        fn test_a_later_clause_can_satisfy_the_gate() {
1493            let circuit = build_two_keys();
1494            let first = SigningKey::from_bytes(&[7u8; 32]);
1495            let second = SigningKey::from_bytes(&[9u8; 32]);
1496            let first_key = bytes_to_bits(first.verifying_key().as_bytes());
1497
1498            // Signed by the *second* key, so only the second clause holds.
1499            let (bits, second_key) = signed_batch(&second, b"{\"price\":42}");
1500            let inputs = first_key
1501                .iter()
1502                .chain(second_key.iter())
1503                .chain(bits.iter())
1504                .map(|b| BigUint::from(*b))
1505                .collect::<Vec<BigUint>>();
1506
1507            let output = circuit.mock_eval_big_uint(inputs, &mut test_rng());
1508            assert_eq!(output, expect(&bits, true));
1509        }
1510
1511        /// Only when *every* clause fails does the gate report failure.
1512        #[test]
1513        fn test_the_gate_reports_failure_only_when_all_clauses_fail() {
1514            let circuit = build_two_keys();
1515            let third = SigningKey::from_bytes(&[11u8; 32]);
1516            let (bits, _) = signed_batch(&third, b"{\"price\":42}");
1517            let first = bytes_to_bits(
1518                SigningKey::from_bytes(&[7u8; 32])
1519                    .verifying_key()
1520                    .as_bytes(),
1521            );
1522            let second = bytes_to_bits(
1523                SigningKey::from_bytes(&[9u8; 32])
1524                    .verifying_key()
1525                    .as_bytes(),
1526            );
1527            let inputs = first
1528                .iter()
1529                .chain(second.iter())
1530                .chain(bits.iter())
1531                .map(|b| BigUint::from(*b))
1532                .collect::<Vec<BigUint>>();
1533            let output = circuit.mock_eval_big_uint(inputs, &mut test_rng());
1534            assert_eq!(output, expect(&vec![false; BATCH_SIZE as usize], false));
1535        }
1536
1537        /// The reason the gate reports rather than aborts: a randomised test feeds bits that will
1538        /// never satisfy a signature, and must still be able to evaluate the circuit and compare
1539        /// outputs instead of dying at this gate.
1540        #[test]
1541        fn test_random_bits_evaluate_to_a_clean_failure() {
1542            use rand::Rng;
1543
1544            let circuit = build(one(signature_constraint(MESSAGE_BYTES))).unwrap();
1545            let mut rng = test_rng();
1546            let inputs = (0..256 + BATCH_SIZE)
1547                .map(|_| BigUint::from(rng.gen::<bool>()))
1548                .collect::<Vec<BigUint>>();
1549
1550            // Deterministic despite the random input, which is what makes the comparison in a
1551            // randomised test meaningful.
1552            let output = circuit.mock_eval_big_uint(inputs, &mut test_rng());
1553            assert_eq!(output, expect(&vec![false; BATCH_SIZE as usize], false));
1554        }
1555
1556        /// Coverage is per clause: a second clause covering only part of the batch is rejected even
1557        /// though the first clause covers all of it.
1558        #[test]
1559        fn test_validation_requires_coverage_from_every_clause() {
1560            let err = build(vec![
1561                ConstraintClause::new(vec![signature_constraint(MESSAGE_BYTES)]),
1562                ConstraintClause::new(vec![PlaintextBitConstraint::Equality {
1563                    bits: slice(0, 256),
1564                    expected: ConstraintExpr::Wire(0),
1565                }]),
1566            ])
1567            .unwrap_err();
1568            assert!(err.contains("clause 1"), "{err}");
1569            assert!(err.contains("must be covered"), "{err}");
1570        }
1571
1572        #[test]
1573        fn test_gate_inputs_are_listed_clause_by_clause() {
1574            let circuit = build_two_keys();
1575            assert_eq!(circuit.gate_unchecked(3).get_inputs(), vec![2, 0, 1]);
1576        }
1577
1578        /// The bounds exist so a hostile circuit cannot make every peer do unbounded work while
1579        /// reconciling, and so validation's own recursion terminates.
1580        #[test]
1581        fn test_validation_rejects_an_over_deep_expression() {
1582            let mut expected = slice(0, 256);
1583            for _ in 0..MAX_EXPR_DEPTH {
1584                expected = ConstraintExpr::Digest {
1585                    algorithm: DigestAlgorithm::Sha256,
1586                    of: Box::new(expected),
1587                };
1588            }
1589            let err = build(one(PlaintextBitConstraint::Equality {
1590                bits: slice(0, 256),
1591                expected,
1592            }))
1593            .unwrap_err();
1594            assert!(err.contains("nests deeper than"), "{err}");
1595        }
1596
1597        #[test]
1598        fn test_validation_rejects_an_over_wide_expression() {
1599            let err = build(one(PlaintextBitConstraint::Equality {
1600                bits: ConstraintExpr::Concat(
1601                    (0..MAX_EXPR_NODES as u32 + 1)
1602                        .map(|i| slice(i, 1))
1603                        .collect(),
1604                ),
1605                expected: ConstraintExpr::Wire(0),
1606            }))
1607            .unwrap_err();
1608            assert!(err.contains("more than"), "{err}");
1609        }
1610
1611        #[test]
1612        fn test_validation_rejects_shared_input() {
1613            let mut circuit = Circuit::<C>::new();
1614            plaintext_bits(&mut circuit, 256);
1615            let x = circuit
1616                .add_gate(Gate::Input(Input::Share {
1617                    algebraic_type: AlgebraicType::Bit,
1618                    batch_size: BATCH_SIZE,
1619                }))
1620                .unwrap();
1621            let err = circuit
1622                .add_gate(Gate::ConstrainPlaintextBits {
1623                    x,
1624                    on_ambiguity: OnAmbiguity::Fail,
1625                    clauses: one(signature_constraint(MESSAGE_BYTES)),
1626                })
1627                .unwrap_err()
1628                .to_string();
1629            assert!(err.contains("is_plaintext"), "{err}");
1630        }
1631    }
1632}