Skip to content

fix(nipopow): pack_interlinks key encoding — use first-occurrence position#866

Open
mwaddip wants to merge 2 commits into
ergoplatform:developfrom
mwaddip:fix/nipopow-pack-interlinks-jvm-compat
Open

fix(nipopow): pack_interlinks key encoding — use first-occurrence position#866
mwaddip wants to merge 2 commits into
ergoplatform:developfrom
mwaddip:fix/nipopow-pack-interlinks-jvm-compat

Conversation

@mwaddip
Copy link
Copy Markdown

@mwaddip mwaddip commented May 19, 2026

pack_interlinks encodes ExtensionKV.key[1] as a sequential distinct-group counter; JVM uses the input-vector index of each duplicate-run's first element. The mismatch is invisible internally (unpack ignores key[1]) but Merkle leaves hash differently from JVM, so check_interlinks_proof and is_better_than reject every real mainnet proof.

Verified against mainnet block 1784124: 11/11 leaf hashes match JVM post-fix (2/11 pre-fix). Also fixes panic on empty input.

mwaddip and others added 2 commits May 19, 2026 02:39
Adds two tests to ergo-nipopow/src/nipopow_algos.rs covering
NipopowAlgos::pack_interlinks:

- pack_interlinks_keys_are_first_occurrence_positions: asserts that the
  ExtensionKV key[1] for each duplicate-run encodes the input-vector
  index of the run's first element (JVM Ergo encoding), not a sequential
  distinct-group counter. Test currently FAILS against the pre-fix
  pack_interlinks which emits sequential ix_distinct_block_ids.

- pack_interlinks_empty_returns_empty: pins the no-panic behavior on
  empty input.

Verified empirically against mainnet block 1784124 nipopow proof
(captured 2026-05-13): the JVM-compat key encoding makes 11/11 leaf
hashes in interlinksProof.indices match against an external TS port.
The pre-fix sequential encoding matches only 2/11.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ition

Replaces the sequential `ix_distinct_block_ids` counter with the
input-vector index of each duplicate-run's first element. Matches JVM
Ergo's `NipopowAlgos.packInterlinks` (ergoplatform/ergo,
`src/main/scala/org/ergoplatform/modifiers/history/popow/NipopowAlgos.scala`)
which encodes ExtensionKV keys as
`[INTERLINK_VECTOR_PREFIX, position_of_first_occurrence_in_interlinks_vector]`.

## The bug

`unpack_interlinks` filters by `key[0] == INTERLINK_VECTOR_PREFIX` only
and never reads `key[1]`, so sigma-rust round-trips its own buggy
output internally. The divergence is observable only at the Merkle-leaf
hash level: a kv-leaf's hash depends on the full kv-bytes including
`key[1]`, so a sigma-rust-packed leaf hashes differently from a
JVM-packed leaf.

Concrete impact:
- `PoPowHeader::check_interlinks_proof` FAILS on real mainnet proofs
  because the expected Merkle root it computes (via the buggy pack)
  doesn't match the root the proof's walk-up reaches (built from
  JVM-packed leaves).
- `NipopowProof::is_better_than` rejects all real mainnet proofs as
  "invalid" because it calls `is_valid()` → `has_valid_proofs()` →
  `check_interlinks_proof`, which fails for any block with at least
  one duplicate-run starting past position 1 (≈ every mainnet block).

## Validation

Verified against mainnet block 1784124 nipopow proof (captured
2026-05-13 via ergo-node-rust): with this fix, all 11 leaf hashes in
the proof's interlinksProof.indices match the JVM-generated leaves
exactly. Pre-fix matches only 2 (positions 0 and 1 happen to align
because distinct_ix=0,1 also = first_pos=0,1 for the first two runs).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
mwaddip added a commit to mwaddip/ergots that referenced this pull request May 19, 2026
… landed)

Sigma-rust upstream PR ergoplatform/sigma-rust#866 landed 2026-05-19 and
was cherry-picked to integration/ergots (worktree HEAD 6ba9d524). Our
fixture-gen now uses NipopowAlgos::pack_interlinks / PoPowHeader::
check_interlinks_proof / NipopowProof::is_better_than directly.

### Equivalence test (passed before removal)

After reverting all 9 call sites in fixture-gen but BEFORE deleting the
JVM-compat module, ran cargo run --release → all regenerated fixture
JSON files byte-identical to committed → proves patched sigma-rust
output matches our prior JVM-compat workaround exactly.

### Changes

- fixture-gen/src/cmds/interlinks_jvm.rs: DELETED
- fixture-gen/src/cmds/mod.rs: removed `pub mod interlinks_jvm;`
- 9 call sites reverted to sigma-rust APIs across batch_merkle.rs,
  compare.rs, nipopow_proof.rs, popow_header.rs
- packages/nipopow/src/merkle.ts: packInterlinks JSDoc updated (code
  unchanged — was already JVM-compat; now agrees with patched upstream)
- facts/nipopow.md: removed "sigma-rust divergences" subsection; kept
  only the unrelated extensionRoot-anchoring known limitation
- docs/specs/2026-05-19-sigma-rust-pack-interlinks-upstream-prompt.md:
  marked SUPERSEDED with PR #866 link; kept for historical context

313 nipopow + 140 avltree + 2658 ergoscript = 3111 tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
mwaddip added a commit to mwaddip/ergo-node-rust that referenced this pull request May 24, 2026
Picks up PR ergoplatform/sigma-rust#866 cherry-picked onto
ergo-node-integration — `pack_interlinks` now encodes each kv-pair's
`key[1]` as the input-vector index of each duplicate-run's first
element, matching the JVM. Pre-fix sigma-rust used a sequential
distinct-group counter, so `check_interlinks_proof` rejected every
real-mainnet proof.

Effect on this node: light-bootstrap's KMZ17 §4.3 multi-peer
comparison via `is_better_than` now actually compares proofs.
Pre-fix behavior was silent degradation to "first valid proof wins"
because every challenger comparison errored. Full-mode operators
unaffected — they never run light bootstrap.

Workspace builds clean, tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant