Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion proofs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Blind logup multiplicities polynomial on non-usable rows for ZK [#312](https://github.com/midnightntwrk/midnight-zk/pull/312)

### Changed
* BREAKING: `halo2_solidity_verifier` decodes accumulator points with the dedicated foreign point `is_id` word: each point now occupies 5 public input words (12 for the fully collapsed accumulator, 10 for the Moonlight point pair), the flag must be 0 or 1, and x/y are not decoded for identity points; verifiers rendered before this change reject the new layout
* BREAKING: `halo2_solidity_verifier` decodes accumulator points with the dedicated foreign point `is_id` word: each point now occupies 5 public input words (12 for the fully collapsed accumulator), the flag must be 0 or 1, and x/y are not decoded for identity points; verifiers rendered before this change reject the new layout
* BREAKING: `halo2_solidity_verifier` `AccumulatorEncoding::point_pair` matches the Moonlight wrap circuit, which constrains both accumulator points to be non-identity: each point is only its x/y coordinate words (`POINT_PAIR_PUBLIC_INPUT_WORDS` is 8), there is no identity flag word, and a point that decodes to the EIP-2537 identity is rejected; point-pair verifiers rendered before this change reject the new layout
* Simplify `CommitmentReference` by removing unused `Chopped` variant [#314](https://github.com/midnightntwrk/midnight-zk/pull/314)
* Split linearization polynomial into non-constant and constant parts, removing the generator point from the MSM [#313](https://github.com/midnightntwrk/midnight-zk/pull/313)
* Remove unnecessary polynomial padding in KZG multi-open [#276](https://github.com/midnightntwrk/midnight-zk/pull/276)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ This directory contains the generated Moonlight wrap verifier contracts that
were deployed to Sepolia, plus the exact runtime bytecode fetched back from the
chain.

> **Status: historical demo deployment, not regularly updated.** These
> contracts were deployed once, on 2026-05-19, and have not been redeployed
> since. They were rendered from a synthetic Moonlight wrap benchmark (dummy
> fold-leaf proofs, a `k = 20` wrap circuit, and a seeded `unsafe_setup` SRS
> whose secret is public, so proofs can be forged against them). They predate
> later changes to the Moonlight wrap circuit and to the generator's
> accumulator public-input layout, so they neither match current generator
> output nor accept current Moonlight wrap proofs. The files below are kept as
> an exact record of what is on-chain.

## Addresses

| Contract | Address |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,8 @@ Two public-input accumulator layouts are supported:
encoded as `lhs point, lhs scalar, rhs point, rhs scalar`, with an optional
fixed-base scalar tail.
- `AccumulatorEncoding::point_pair(...)`: already-collapsed Moonlight wrap
input, encoded as `lhs point, rhs point`; the generated verifier treats both
input, encoded as `lhs.x, lhs.y, rhs.x, rhs.y` with no identity flag words;
both points must be non-identity. The generated verifier treats both
carried scalars as one and rejects fixed-base scalar tails for this layout.

The accumulator layout is intentionally explicit because public input packing is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1313,8 +1313,11 @@ let generator = SolidityGenerator::new(
);
```

The point-pair layout is `lhs point, rhs point`; the generated verifier uses
implicit unit scalars for both sides and rejects fixed-base scalar tails.
The point-pair layout is `lhs.x, lhs.y, rhs.x, rhs.y`; the generated verifier
uses implicit unit scalars for both sides and rejects fixed-base scalar tails.
The Moonlight wrap circuit constrains both points to be non-identity, so
neither point carries an identity flag word and the verifier rejects any point
that decodes to the identity.

Use `SolidityGenerator::try_new` when the public-input layout comes from
caller-controlled metadata and should return a typed `GeneratorError` instead
Expand Down Expand Up @@ -1404,25 +1407,41 @@ rhs_is_id: 1 word
rhs_scalar: 1 word
```

for 12 words in total. The Moonlight point-pair layout is the same without the
two scalar words, for 10 words in total.
for 12 words in total. The Moonlight point-pair layout has neither scalar words
nor identity flag words, because the wrap circuit constrains both points to be
non-identity:

```text
point_words = 2 * coord_words = 4

lhs_x: 2 words
lhs_y: 2 words
rhs_x: 2 words
rhs_y: 2 words
```

for 8 words in total.

If a fixed-base scalar tail exists, it is consumed as RHS MSM scalars for:

- `-G`, represented by `G1_BASE` with scalar negated modulo `r`;
- fixed commitments;
- permutation commitments.

The tail must be empty or contain exactly one scalar for each of these bases
(`1 + #fixed + #permutation` commitments of the verified VK); the generator
rejects any other length with `UnsupportedAccumulatorEncoding`.

The tail bases are sorted lexicographically by their generated names to match
Midfall's `BTreeMap` order.

### 13.2 Coordinate Reconstruction

Coordinates are exposed by Midnight circuits as limbs of `coord - 1`, packed
four limbs per native field element. Each point's coordinates are followed by
a dedicated identity flag word, which the circuit constrains to be `0` or `1`.
When the flag is set the circuit leaves x and y unconstrained, so their limbs
carry no meaning.
four limbs per native field element. In the point-and-scalar layout, each
point's coordinates are followed by a dedicated identity flag word, which the
circuit constrains to be `0` or `1`. When the flag is set the circuit leaves x
and y unconstrained, so their limbs carry no meaning.

The verifier:

Expand All @@ -1438,6 +1457,10 @@ The verifier:
8. Rejects a decoded non-identity point whose coordinates are both zero.
9. Stores the point as a padded G1 tuple.

The point-pair layout has no identity flag word, so the verifier skips steps 1
and 2 and always runs steps 3 to 9 for both points. A point whose coordinates
decode to `(0, 0)`, the EIP-2537 identity encoding, is therefore rejected.

### 13.3 Accumulator Pairing Batch

After `PAIRING_LHS` and `PAIRING_RHS` are fixed, derive:
Expand Down
17 changes: 15 additions & 2 deletions proofs/solidity-verifier/docs/reference/TEAM_DEMO_SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,18 @@ The Moonlight demo proves the final wrap recursion proof from
Solidity verifier with this repository, deploys it into local Prague `revm`, and
verifies the Moonlight proof on-chain.

> **Known issue: the Moonlight part of this demo is currently broken.** The
> default Moonlight branch, `codex/wrap-bench-cherry-picks`, was last updated in
> May 2026. It predates two public-input layout changes: Midfall's dedicated
> foreign-point identity-flag word
> ([EYBlockchain/midfall#15](https://github.com/EYBlockchain/midfall/pull/15))
> and Moonlight's flagless, non-identity wrap accumulator
> ([EYBlockchain/Moonlight#91](https://github.com/EYBlockchain/Moonlight/pull/91)).
> Against this checkout its wrap bench fails before reaching verifier
> generation. The demo needs a Moonlight ref with those benchmark commits
> rebased onto current `master`; until then, run
> `scripts/run_team_demo.sh --skip-moonlight`.

Clone Moonlight next to this verifier repo:

```bash
Expand Down Expand Up @@ -254,8 +266,9 @@ The run is much heavier than the IVC bench. On the demo machine it took about
`CircuitTranscript<sha3::Keccak256>` and with the final outer proof layout set
to no fewer-point-sets so it matches the generated Solidity verifier.
The Solidity path also enables the public accumulator check. Moonlight exposes
its final wrap accumulator as an already-collapsed `(lhs, rhs)` point pair at
instance offset `11`, so the verifier uses
its final wrap accumulator as an already-collapsed, non-identity `(lhs, rhs)`
point pair at instance offset `11`, as `lhs.x, lhs.y, rhs.x, rhs.y` with no
identity flag words. The verifier therefore uses
Comment thread
jtcoolen marked this conversation as resolved.
`AccumulatorEncoding::point_pair(offset, 7, 56)` rather than the Midfall IVC
`point, scalar, point, scalar` layout.

Expand Down
6 changes: 6 additions & 0 deletions proofs/solidity-verifier/scripts/run_team_demo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ SRS_DIR="${SRS_DIR:-"$ROOT_DIR/.srs"}"
LOG_DIR="${LOG_DIR:-"$ROOT_DIR/target/team-demo-logs"}"

MOONLIGHT_DIR="${MOONLIGHT_DIR:-"$DEFAULT_MOONLIGHT_DIR"}"
# Known stale: this branch predates midfall#15 (identity-flag words) and
# Moonlight#91 (flagless wrap accumulator), so the Moonlight step fails against
# current Midfall. Point it at a Moonlight ref rebased onto master, or pass
# --skip-moonlight. See docs/reference/TEAM_DEMO_SETUP.md.
MOONLIGHT_BRANCH="${MOONLIGHT_BRANCH:-codex/wrap-bench-cherry-picks}"
MOONLIGHT_URL="${MOONLIGHT_URL:-git@github.com:EYBlockchain/Moonlight.git}"
MOONLIGHT_HTTPS_URL="${MOONLIGHT_HTTPS_URL:-https://github.com/EYBlockchain/Moonlight.git}"
Expand Down Expand Up @@ -82,6 +86,8 @@ Options:
--moonlight-dir DIR Moonlight checkout, relative to this repo unless absolute.
Defaults to a Moonlight checkout next to the Midfall repo.
--moonlight-branch NAME Moonlight branch. Defaults to codex/wrap-bench-cherry-picks.
That default is currently stale (see TEAM_DEMO_SETUP.md);
use --skip-moonlight until it is updated.
--moonlight-url URL SSH clone URL for Moonlight.
--https Clone Moonlight with the HTTPS URL.
--update-moonlight Fetch and switch Moonlight to the requested branch.
Expand Down
69 changes: 59 additions & 10 deletions proofs/solidity-verifier/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ use crate::lowering::layout;
/// followed by one `0`/`1` identity-flag word. Any public-input words after
/// the fixed accumulator payload are interpreted as the optional RHS
/// fixed-base scalar tail for partially collapsed accumulators.
///
/// [`Self::point_pair`] selects the Moonlight wrap layout instead:
///
/// ```text
/// lhs point coordinates, rhs point coordinates
/// ```
///
/// with the same coordinate encoding but no identity-flag words, no carried
/// scalars, and no fixed-base scalar tail.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AccumulatorEncoding {
/// Offset of accumulator limbs in instances.
Expand All @@ -50,9 +59,11 @@ pub enum AccumulatorEncodingKind {
/// `AssignedAccumulator<S>::as_public_input`: lhs point, lhs scalar, rhs
/// point, rhs scalar, followed by an optional fixed-base scalar tail.
PointAndScalar,
/// Already collapsed point-pair layout: lhs point, rhs point. The generated
/// verifier treats both carried scalars as one and does not accept a
/// fixed-base scalar tail.
/// Already collapsed Moonlight wrap layout: lhs `(x, y)`, rhs `(x, y)`.
/// The circuit constrains both points to be non-identity, so neither point
/// carries an identity-flag word and the generated verifier rejects any
/// point that decodes to the identity. Both carried scalars are implicitly
/// one and a fixed-base scalar tail is not accepted.
PointPair,
}

Expand All @@ -75,9 +86,9 @@ impl AccumulatorEncoding {
pub const SUPPORTED_NUM_LIMB_BITS: usize = layout::accumulator::LIMB_BITS;
/// Public-input words required by the fully collapsed accumulator form.
pub const FULLY_COLLAPSED_PUBLIC_INPUT_WORDS: usize = 12;
/// Public-input words required by an already collapsed `(lhs, rhs)` point
/// pair.
pub const POINT_PAIR_PUBLIC_INPUT_WORDS: usize = 10;
/// Public-input words required by an already collapsed, non-identity
/// `(lhs, rhs)` point pair.
pub const POINT_PAIR_PUBLIC_INPUT_WORDS: usize = 8;

/// Return a new `AccumulatorEncoding`.
pub fn new(offset: usize, num_limbs: usize, num_limb_bits: usize) -> Self {
Expand All @@ -89,7 +100,8 @@ impl AccumulatorEncoding {
}
}

/// Return a point-pair accumulator encoding with implicit unit scalars.
/// Return a point-pair accumulator encoding with implicit unit scalars and
/// no identity flags; both points must be non-identity.
pub fn point_pair(offset: usize, num_limbs: usize, num_limb_bits: usize) -> Self {
Self {
offset,
Expand All @@ -105,10 +117,15 @@ impl AccumulatorEncoding {
self.num_limbs.div_ceil(limbs_per_instance)
}

/// Number of public-input words for one G1 point: x and y coordinates
/// followed by the identity flag.
/// Number of public-input words for one G1 point: x and y coordinates,
/// followed by the identity flag when the layout carries one.
pub(crate) fn point_words(self) -> usize {
2 * self.coordinate_words() + layout::accumulator::IDENTITY_FLAG_WORDS
2 * self.coordinate_words()
+ if self.has_identity_flags() {
layout::accumulator::IDENTITY_FLAG_WORDS
} else {
0
}
}

/// Number of public-input words for the lhs and rhs G1 points.
Expand All @@ -121,6 +138,12 @@ impl AccumulatorEncoding {
matches!(self.kind, AccumulatorEncodingKind::PointAndScalar)
}

/// Whether each public-input point is followed by an identity-flag word.
/// Point-pair accumulators are non-identity and omit the flag.
pub(crate) fn has_identity_flags(self) -> bool {
matches!(self.kind, AccumulatorEncodingKind::PointAndScalar)
}

/// Number of public-input words occupied by the fixed accumulator payload.
fn fixed_payload_words(self) -> usize {
self.point_pair_words()
Expand Down Expand Up @@ -186,6 +209,32 @@ impl AccumulatorEncoding {
}
Ok(tail)
}

/// Validates this encoding against `num_instances` and the fixed bases of
/// the verified VK, returning the fixed-base scalar tail length.
///
/// `num_vk_fixed_bases` is `1 + #fixed + #permutation` commitments: `-G`
/// plus every commitment in `fixed_bases(vk)`. A non-empty tail must hold
/// exactly one RHS scalar per base, which lowering maps onto them in
/// `BTreeMap` name order.
pub(crate) fn validate_for_vk(
self,
num_instances: usize,
num_vk_fixed_bases: usize,
) -> Result<usize, GeneratorError> {
let tail = self.fixed_scalar_count(num_instances)?;
if tail != 0 && tail != num_vk_fixed_bases {
return Err(GeneratorError::UnsupportedAccumulatorEncoding {
offset: self.offset,
num_limbs: self.num_limbs,
num_limb_bits: self.num_limb_bits,
num_instances,
reason: "fixed-base scalar tail must have one scalar for -G and each fixed and \
permutation commitment",
});
}
Ok(tail)
}
}

/// Stable diagnostic view of the identities folded into the quotient numerator.
Expand Down
7 changes: 6 additions & 1 deletion proofs/solidity-verifier/src/builder/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ impl<'a> SolidityGenerator<'a> {
config.num_committed_instances,
)?;
if let Some(acc_encoding) = config.accumulator {
acc_encoding.validate_for_num_instances(config.num_instances)?;
// Rejects trailing words for point pairs and fixed-base scalar
// tails that don't match `vk`, which later lowering assumes were
// validated here.
let num_vk_fixed_bases =
1 + vk.fixed_commitments().len() + vk.permutation().commitments().len();
acc_encoding.validate_for_vk(config.num_instances, num_vk_fixed_bases)?;
}
if let Some((column, rotation)) = vk
.cs()
Expand Down
5 changes: 4 additions & 1 deletion proofs/solidity-verifier/src/lowering/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> {
expected_num_acc_limbs,
expected_num_acc_limb_bits,
expected_acc_has_carried_scalars,
expected_acc_non_identity_points,
) = self
.acc_encoding
.map(|acc_encoding| {
Expand All @@ -201,9 +202,10 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> {
acc_encoding.num_limbs,
acc_encoding.num_limb_bits,
acc_encoding.has_carried_scalars(),
!acc_encoding.has_identity_flags(),
)
})
.unwrap_or((false, 0, 0, 0, false));
.unwrap_or((false, 0, 0, 0, false, false));

let verifier = Halo2Verifier {
template_constants: Default::default(),
Expand Down Expand Up @@ -273,6 +275,7 @@ impl<'params, 'meta> VerifierBuildInputs<'params, 'meta> {
expected_num_acc_limbs,
expected_num_acc_limb_bits,
expected_acc_has_carried_scalars,
expected_acc_non_identity_points,
acc_fixed_bases,
};
verifier
Expand Down
16 changes: 16 additions & 0 deletions proofs/solidity-verifier/src/lowering/render/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,22 @@ pub(crate) struct Halo2Verifier {
/// Whether the public accumulator payload carries explicit LHS/RHS scalar
/// words. Moonlight wrap proofs expose an already collapsed point pair and
/// use implicit unit scalars instead.
///
/// Together with `expected_acc_non_identity_points`, this is derived from
/// `AccumulatorEncodingKind`, so only two combinations are reachable:
/// carried scalars with identity flags (`PointAndScalar`), or neither
/// (`PointPair`). This is intentional, and the accumulator template relies
/// on it.
pub(crate) expected_acc_has_carried_scalars: bool,
/// Whether the public accumulator points are constrained to be
/// non-identity and therefore omit the trailing `0`/`1` identity-flag word.
/// This holds for Moonlight wrap point pairs, which expose only `(x, y)`
/// coordinate words; the point-and-scalar layout keeps the flag.
///
/// This is set exactly when `expected_acc_has_carried_scalars` is not; see
/// that field. It is `false` without an accumulator, so those verifiers
/// render the default flagged decoding helpers.
pub(crate) expected_acc_non_identity_points: bool,
Comment thread
jtcoolen marked this conversation as resolved.
/// Fixed bases serialized by `AssignedAccumulator<S>::as_public_input`
/// for the RHS accumulator MSM, in the exact lexicographic
/// `BTreeMap` order used by midnight-circuits. This is the public
Expand Down Expand Up @@ -1146,6 +1161,7 @@ mod tests {
expected_num_acc_limbs: 0,
expected_num_acc_limb_bits: 0,
expected_acc_has_carried_scalars: false,
expected_acc_non_identity_points: false,
acc_fixed_bases: vec![],
}
}
Expand Down
Loading
Loading