Skip to content

Noir migration: verify_econtent is a no-op in the Dg1 path, and the dg1 crate does not compile under current Noir #2083

Description

@unspecifiedcoder

Summary

Two related migration-state findings in the Noir circuit crate (noir/crates/dg1, package noir). Filed for visibility; no PR is attached see "Why no PR" below.

This is a migration-state correctness gap, not an exploitable production issue: production proving is Circom; the Noir crate is pre-alpha. Severity framed accordingly.

Finding 1 — verify_econtent is an empty no-op in the Dg1 path

noir/crates/dg1/src/dg1/dg1.nr:22-29:

// todo
pub fn verify_econtent(
    eContent: [u8; MAX_ECONTENT_LEN],
    signed_attr: [u8; MAX_SIGNED_ATTR_LEN],
    signed_attr_econtent_hash_offset: u8,
    eContent_padded_length: u16,
    signed_attr_padded_length: u8,
) {}

It is called unconditionally from the main entry point noir/crates/dg1/src/dg1/mod.nr:18-24, so the Noir Dg1() path currently performs no eContent → signedAttr hash binding. The Circom reference enforces this at circuits/circuits/utils/passport/passportVerifier.circom:78-86 (compute SHA256(eContent, eContent_padded_length), assert it equals the ECONTENT_HASH_ALGO_BYTES slice of signed_attr at signed_attr_econtent_hash_offset).

Intended 1:1 parity implementation (mirrors the sibling verify_dg1_and_dg2 in the same file, which already uses std::hash::sha256 directly; avoids depending on the unwired eContent/ module):

let computed = std::hash::sha256::sha256_var(eContent, eContent_padded_length as u64);
for i in 0..HASH_LEN_BYTES {
    assert_eq(computed[i], signed_attr[signed_attr_econtent_hash_offset as u32 + i as u32]);
}

(signed_attr_padded_length is unused by this check it belongs to signed-attr hashing / signature verification, which is out of scope for verify_econtent.)

Finding 2 — dg1 crate does not compile under current Noir

Baseline nargo test --package noir fails with 49 errors before any test runs. Dominant cause: Could not resolve 'poseidon'std::hash::poseidon::bn254::hash_2 / hash_9 no longer resolves (e.g. crates/dg1/src/ofac/ofac_name_dob.nr:34, crates/dg1/src/ofac/ofac_passport_number.nr:16), plus dependent type-inference failures.

Toolchain mismatch: installed nargo 1.0.0-beta.19 (which removed std::hash::poseidon) vs. code targeting older Noir (Nargo.toml: compiler_version >=0.36.0; AGENTS.md pin example: noirup -v v0.31.0). The errors are spread across ofac//utils/ and are unrelated to verify_econtent.

Why no PR

TDD requires a green baseline. With the crate failing to compile, a verify_econtent test cannot be observed failing then passing , the runner aborts on the 49 unrelated errors. Submitting an unverified implementation would violate "no untested code." The parity implementation above is scoped and understood but intentionally not submitted until the crate compiles under a pinned toolchain.

Migration context

Related to #2082 (domain-separation hardening to build into the Noir implementation). Both point to the Noir migration being earlier-stage than the present stubs suggest: the eContent-binding check is absent, and the crate is not buildable on current Noir without a poseidon/std-API port.

Suggested sequencing (for maintainers)

  1. Pin/port the dg1 crate to a known-good Noir version (resolve the std::hash::poseidon drift across ofac/).
  2. Once the crate compiles, land verify_econtent via the parity implementation above with a failing-then-passing test (happy path + a tamper/negative case proving the binding is enforced).

cc @transphorm @ericnakagawa @michaelelliot

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions