Skip to content

fix(server): validate cosigner_commitments against initial account state - #380

Merged
haseebrabbani merged 4 commits into
mainfrom
102-validate-cosigner-commitments
Aug 7, 2026
Merged

fix(server): validate cosigner_commitments against initial account state#380
haseebrabbani merged 4 commits into
mainfrom
102-validate-cosigner-commitments

Conversation

@haseebrabbani

@haseebrabbani haseebrabbani commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Closes #102. Supersedes #109, which predates the GuardianError migration and no longer applies.

Problem

/configure accepted the client-declared auth.cosigner_commitments after only verifying that the requesting key exists in the account state (validate_credential). The rest of the list was stored unvalidated — and that stored list is the authorization source of truth for every subsequent request (Auth::verify authorizes any key whose commitment appears in it). A configure call could therefore register commitments that are not signers of the account.

Fix

After the guardian-binding check, extract the signer set from initial_state (via the existing NetworkClient::should_update_auth, which reads the openzeppelin::multisig::signer_public_keys map) and reject with InvalidInput when the declared list does not match it exactly (same set and order — the SDK derives the list from the same map walk, so honest clients are unaffected).

Ok(None) (no extractable signer set) skips the check, matching the canonicalization-side semantics of should_update_auth. Every real multisig-guardian account carries a non-empty signer map — MultisigGuardianConfig writes all signers, including 1-of-1 — so the check always runs for real accounts.

Summary by CodeRabbit

  • New Features

    • Added access to all authorized cosigner commitments for supported authentication methods.
  • Bug Fixes

    • Account configuration now rejects declared signer sets that do not match the account’s actual signer commitments.
    • Configuration failures are reported when signer commitments cannot be extracted.
    • Multisignature onboarding and guardian-switching flows now validate the complete cosigner set.

/configure accepted the client-declared auth.cosigner_commitments after
only proving the requesting key exists in the account state, so the
stored list — the authorization source of truth for every later request
(Auth::verify) — could name keys that are not signers of the account.

Extract the signer set from initial_state via should_update_auth and
reject the configuration when the declared list does not match it
exactly. A state with no extractable signer set skips the check,
matching canonicalization semantics; every real multisig-guardian
account carries a non-empty signer map, so the check always runs for
real accounts.

The switch/abandon e2e tests onboarded 2-of-2 accounts declaring only
one cosigner — exactly the drift now rejected — and register the full
signer set instead.

Closes #102
@haseebrabbani
haseebrabbani requested a review from zeljkoX as a code owner August 6, 2026 17:12
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1f9998b4-32fe-4117-af8a-0dfc0d646f51

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Account configuration now validates declared cosigner commitments against commitments extracted from the submitted account state. The change adds a public Auth accessor and updates unit and end-to-end tests for complete multisig signer sets.

Changes

Cosigner commitment validation

Layer / File(s) Summary
Auth contract and configuration validation
crates/server/src/metadata/auth/mod.rs, crates/server/src/services/configure_account.rs
Auth::cosigner_commitments exposes variant-specific commitments. configure_account rejects mismatched extracted signer sets and maps extraction failures to NetworkError.
Configuration validation tests
crates/server/src/services/configure_account.rs
Tests cover matching commitments, mismatches, injected cosigners, extraction errors, and None extraction results.
End-to-end multisig coverage
crates/server/src/testing/e2e/*
Multisig fixtures now pass all cosigner commitments. An end-to-end test verifies that absent commitments are rejected without persisting metadata.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: enhancement

Suggested reviewers: zeljkox

Poem

I’m a rabbit checking signers in a row,
Each commitment must match what states show.
No extra cosigner slips through the gate,
Errors return before metadata’s fate.
Multisig lists now hop in full—
A tidy validation burrow to pull!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes validation of cosigner commitments against the initial account state.
Linked Issues check ✅ Passed The changes implement exact cosigner commitment validation, rejection of mismatches, and skipping validation when extraction is unavailable.
Out of Scope Changes check ✅ Passed The accessor, validation logic, tests, and fixture updates directly support the linked issue and stated objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 102-validate-cosigner-commitments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/server/src/services/configure_account.rs (1)

782-828: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a reordered-commitments rejection test.

The implementation uses Auth equality, so ordering is enforced. The current tests cover different and extra commitments, but not the required case where the same commitments appear in a different order. Add a two-signer test that returns [first, second] from should_update_auth and submits [second, first]. Assert InvalidInput and no state persistence.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/server/src/services/configure_account.rs` around lines 782 - 828, Add
a two-signer test alongside
test_configure_account_rejects_injected_extra_cosigner that generates two
commitments, configures should_update_auth to return them in [first, second]
order, and submits the same commitments as [second, first]. Assert
configure_account returns Err(GuardianError::InvalidInput(_)) and
storage_backend records no state submission.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/server/src/services/configure_account.rs`:
- Around line 782-828: Add a two-signer test alongside
test_configure_account_rejects_injected_extra_cosigner that generates two
commitments, configures should_update_auth to return them in [first, second]
order, and submits the same commitments as [second, first]. Assert
configure_account returns Err(GuardianError::InvalidInput(_)) and
storage_backend records no state submission.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 90489350-3848-4a14-a67f-e491517cd044

📥 Commits

Reviewing files that changed from the base of the PR and between 274a439 and b1f8abf.

📒 Files selected for processing (5)
  • crates/server/src/metadata/auth/mod.rs
  • crates/server/src/services/configure_account.rs
  • crates/server/src/testing/e2e/abandon_candidate.rs
  • crates/server/src/testing/e2e/configure_account.rs
  • crates/server/src/testing/e2e/switch_guardian_canonicalization.rs

@codecov-commenter

codecov-commenter commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.55947% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/server/src/metadata/auth/mod.rs 85.71% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Strengthens the server-side /configure flow by ensuring the client-declared cosigner commitments exactly match the signer set embedded in the submitted initial account state, preventing unauthorized commitments from being persisted into the account’s long-lived authorization configuration.

Changes:

  • Add /configure validation that compares declared auth.cosigner_commitments against signer commitments extracted from initial_state via NetworkClient::should_update_auth.
  • Add a shared Auth::cosigner_commitments() accessor to standardize commitment list access across schemes.
  • Update/extend E2E and service tests to cover cosigner mismatch and ensure updated onboarding flows provide full signer lists.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
crates/server/src/services/configure_account.rs Enforces cosigner commitment validation against extracted signer set; adds multiple unit tests for mismatch/error/skip semantics.
crates/server/src/metadata/auth/mod.rs Adds Auth::cosigner_commitments() accessor used by the new validation and error reporting.
crates/server/src/testing/e2e/configure_account.rs Adds an integration-style rejection test using real signer extraction (should_update_auth) via an integration network client wrapper.
crates/server/src/testing/e2e/switch_guardian_canonicalization.rs Updates onboarding in E2E flow to register the full cosigner set to satisfy the new /configure validation.
crates/server/src/testing/e2e/abandon_candidate.rs Updates test setup to configure accounts with the full cosigner set consistent with new validation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +103 to +106
GuardianError::NetworkError(format!(
"Failed to extract signer commitments from initial state: {e}"
))
})?;
Comment on lines +116 to +120
return Err(GuardianError::InvalidInput(format!(
"cosigner_commitments do not match the signer set in initial_state: expected {:?}, provided {:?}",
expected_auth.cosigner_commitments(),
params.auth.cosigner_commitments()
)));

@zeljkoX zeljkoX left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this one.

Looks good.

Few nits:

  • test_configure_account_with_real_miden_account still uses a mock path where should_update_auth() returns Ok(None). Consequently, it succeeds without proving that the supplied commitments match the account state and would continue passing if the new validation were removed.
  • Update spec/processes.md to state that, for MultisigGuardian accounts, auth.cosigner_commitments must exactly match the signer map extracted from initial_state, including its canonical order. This is a user-visible auth behavior change and helps explain otherwise confusing InvalidInput responses.
  • Add test_rejects_reordered_commitments to lock in the deliberately order-sensitive behavior.
  • Consider validating commitments at ingestion as canonical 0x-prefixed, lowercase, 32-byte hex and explicitly rejecting empty or duplicate lists. Current exact string equality works with the SDKs but can produce confusing failures for manual clients.
  • Consider mapping malformed client-supplied initial_state to InvalidInput rather than NetworkError. This is consistent with existing behavior, so it need not block this PR.

… spec doc

- Reject non-canonical (not 0x + 64 lowercase hex), empty, or duplicate
  cosigner_commitments at ingestion so manual clients get a named error
  instead of a confusing set-mismatch.
- Lock in order-sensitive comparison with test_rejects_reordered_commitments.
- Exercise the real extraction path in
  test_configure_account_with_real_miden_account: the declared set is the
  fixture account's actual 3-signer map (keys.json, map order) and the
  credential is signed by fixture signer 1, so the test fails if the
  validation is removed or the order convention drifts.
- Document the exact-match requirement in spec/processes.md.
@haseebrabbani
haseebrabbani merged commit 6041380 into main Aug 7, 2026
17 checks passed
@haseebrabbani
haseebrabbani deleted the 102-validate-cosigner-commitments branch August 7, 2026 19:14
@github-project-automation github-project-automation Bot moved this from Review to Release Candidate in OZ Development for Miden Aug 7, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 7, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Validate cosigner_commitments on configure

5 participants