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