Skip to content

zorch.lnp.wire

What a verifier may believe about a proof it did not produce.

The sibling of transcript.py: that module owns how a ring stack becomes transcript bytes, this one owns what verify is allowed to assume about the values arriving on that wire. Both exist for the same reason — a protocol module that respelled either would fork a convention its own tests cannot see disagreeing.

The rule, one answer across the proof surface rather than per module:

  • Proof fields are a verdict. The prover chose them, so "malformed" is the ordinary case of an adversary sending something, and the honest answer is False. A verifier that raises instead is a total predicate only over well-formed messages, which is not the property its caller needs — anywhere the proof arrives from a network, it is the difference between rejecting and crashing.
  • Statement fields raise. The caller supplied the public parameters, so a malformed one is that caller's own bug. Reporting it as a failed proof is how a parameter mistake becomes a silently always-false verifier, so those keep reaching the raising gates.

Both halves route through a raising gate rather than restating its predicate, so the two ways of asking about one array cannot drift apart.

Each protocol layer applies this through a single _is_well_formed over its whole proof dataclass, rather than per field at the point of use — a per-field habit leaves whichever field nobody remembered ungated, and the field it left was the composite one.

require_signed

require_signed(
    ring: HostSplitRing,
    name: str,
    arr: np.ndarray,
    *lead: int
) -> None

The signed-integer twin of the scheme's ring-stack shape gate.

Witnesses and responses are (lead, d) integer arrays, the challenge the rank-one case (d,). A malformed one fails in this package's vocabulary instead of deep inside a ring op — and a c that is a list, or float coefficients silently truncated by from_signed, is refused rather than quietly scored against the real challenge.

The two failure modes stay distinct, per lattice-frx's own contract rule: TypeError for dtype, ValueError for shape.

Source code in zorch/lnp/wire.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def require_signed(ring: HostSplitRing, name: str, arr: np.ndarray, *lead: int) -> None:
    """The signed-integer twin of the scheme's ring-stack shape gate.

    Witnesses and responses are `(lead, d)` integer arrays, the challenge
    the rank-one case `(d,)`. A malformed one fails in this package's
    vocabulary instead of deep inside a ring op — and a `c` that is a
    list, or float coefficients silently truncated by `from_signed`, is
    refused rather than quietly scored against the real challenge.

    The two failure modes stay distinct, per lattice-frx's own contract
    rule: `TypeError` for dtype, `ValueError` for shape.
    """
    want = (*lead, ring.d)
    if not isinstance(arr, np.ndarray) or not np.issubdtype(arr.dtype, np.integer):
        raise TypeError(
            f"{name} must be a signed integer ndarray, got "
            f"{type(arr).__name__} dtype {getattr(arr, 'dtype', None)!r}"
        )
    if arr.shape != want:
        raise ValueError(f"{name} must have shape {want}, got {arr.shape}")

is_signed

is_signed(
    ring: HostSplitRing, arr: np.ndarray, *lead: int
) -> bool

require_signed asked rather than enforced.

Source code in zorch/lnp/wire.py
63
64
65
66
67
68
69
def is_signed(ring: HostSplitRing, arr: np.ndarray, *lead: int) -> bool:
    """`require_signed` asked rather than enforced."""
    try:
        require_signed(ring, "wire", arr, *lead)
    except (TypeError, ValueError):
        return False
    return True

leading

leading(arr: np.ndarray) -> int

How many entries a stack's leading (per-statement) axis carries, for an array that may not be one at all.

Every protocol layer here sizes a family of functions by this number before gating its shape, so the answer for a malformed argument has to be a number rather than an exception: 0 sends a non-array, or one with no axes, on to the raising shape gate that names the actual problem, instead of an IndexError from indexing shape above it.

Named once for the reason the rest of this module is: two layers that spelled it themselves answered the same malformed statement two different ways.

Source code in zorch/lnp/wire.py
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def leading(arr: np.ndarray) -> int:
    """How many entries a stack's leading (per-statement) axis carries, for
    an array that may not be one at all.

    Every protocol layer here sizes a family of functions by this number
    before gating its shape, so the answer for a malformed argument has to
    be a number rather than an exception: `0` sends a non-array, or one
    with no axes, on to the raising shape gate that names the actual
    problem, instead of an `IndexError` from indexing `shape` above it.

    Named once for the reason the rest of this module is: two layers that
    spelled it themselves answered the same malformed statement two
    different ways.
    """
    shape = getattr(arr, "shape", ())
    return shape[0] if shape else 0

is_stack

is_stack(
    scheme: AbdlopCommitment, arr: np.ndarray, *lead: int
) -> bool

Whether an untrusted lead + (limbs, d) ring stack is usable at all.

The scheme's require_stack asked rather than enforced, extended to the array contract. The range check belongs to this side and not to require_stack because it is exactly what nothing else performs in time: the ring ops downstream do reject a non-canonical operand, and that rejection is the raise this answers first.

Source code in zorch/lnp/wire.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def is_stack(scheme: AbdlopCommitment, arr: np.ndarray, *lead: int) -> bool:
    """Whether an untrusted `lead + (limbs, d)` ring stack is usable at all.

    The scheme's `require_stack` asked rather than enforced, extended to
    the array contract. The range check belongs to this side and not to
    `require_stack` because it is exactly what nothing else performs in
    time: the ring ops downstream *do* reject a non-canonical operand,
    and that rejection is the raise this answers first.
    """
    try:
        scheme.require_stack("wire", arr, *lead)
    except ValueError:
        return False
    return is_canonical(arr, scheme.ring.q_moduli)