Skip to content

feat(signing): SigningProvider trait crate + plan doc (attested-signing PR1/10) - #3960

Closed
zmanian wants to merge 1 commit into
mainfrom
attested-signing-01-provider-trait
Closed

zmanian wants to merge 1 commit into
mainfrom
attested-signing-01-provider-trait

Conversation

@zmanian

@zmanian zmanian commented May 24, 2026 •

Copy link
Copy Markdown
Collaborator

Rebased onto current main (attested-signing cascade, 2026-07-23). The stack was 1184 commits behind (merge base 2026-05-24). Tracking issue: #6532 — it carries the full plan, the measured hop difficulty, and the baseline.

Inline review threads may have re-anchored or orphaned from the force-push. Reviewers: the per-PR "Cascade changes" section below states exactly what the rebase altered beyond the original work.

What this PR is

ironclaw_signing_provider — the provider-agnostic SigningProvider trait crate (no chain deps) — plus ironclaw_attestation's canonical signing-bytes / ApprovedTxHash core, and the attested_signing_boundaries architecture test that pins crate purity and the openssl-free workspace graph.

Cascade changes

  • Root manifest rebuilt as main's + additive, not a 3-way merge. The naive merge silently took this branch's stale [workspace.package] and dropped main's rust-version, breaking workspace resolution. Members = main's current list + only the two crates this PR creates (the branch's own list still named ironclaw_loop_support and ironclaw_reborn, both since deleted from main).
  • Branch squashed to one commit for a clean diff against the rebased parent.

Verification

Workspace resolves; both crates build; 43 crate tests pass; workspace_graph_is_openssl_free survives the grown workspace (this was the main risk for hop 1); clippy clean.

@github-actions github-actions Bot added scope: docs Documentation scope: dependencies Dependency updates size: XL 500+ changed lines risk: medium Business logic, config, or moderate-risk modules contributor: core 20+ merged PRs labels May 24, 2026

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request introduces the ironclaw_signing_provider crate, establishing a provider-agnostic SigningProvider trait and core data structures for the attested-signing substrate. The implementation maintains a strict purity invariant, verified by a new architecture test, to ensure the crate remains free of chain-specific or cryptographic dependencies. Review feedback points out a security concern where the Deserialize implementation on VerifiedProof could allow bypassing verification logic and suggests using hex encoding for binary types to improve serialization efficiency and observability.

Comment thread crates/ironclaw_signing_provider/src/proof.rs Outdated
Comment thread crates/ironclaw_signing_provider/src/transaction.rs
zmanian added a commit that referenced this pull request May 24, 2026
…roof, canonical trust_model

Addresses Codex code review of #3960:
- boundary test now an allowlist (async-trait/serde/thiserror only) over
  the resolved normal-deps, not a bypassable denylist
- VerifiedProof drops Deserialize (cannot be rehydrated as a verified
  token off the wire) and binds provider_id + approved_tx_hash
- trust_model() is now a default deriving from ProviderId::trust_model()
  so an impl cannot declare a contradictory trust model (ship-gate guard)
- SigningProviderError #[non_exhaustive]
- ApprovedTxHash::from_bytes doc hardened (never from caller input)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@zmanian

zmanian commented May 24, 2026

Copy link
Copy Markdown
Collaborator Author

Attested-signing substrate — stack map

This PR is the base of a 14-PR stack implementing the attested-signing substrate (human-in-the-loop gated blockchain signing: WYSIWYS binding, sealed one-shot grants, multi-chain custodial + external-wallet, deterministic post-approval continuation). Reviewing bottom-up; each PR targets its predecessor and collapses onto reborn-integration as the lower ones merge.

Merge / dependency order (bottom-up)

# PR Scope Base
1 #3960 SigningProvider trait + plan/threat-matrix doc (this PR) reborn-integration
2 #3961 canonical signing-bytes + ApprovedTxHash (full Solana/NEAR modeling) #3960
3 #3963 sealed one-shot grant store + signing/idempotency ledger #3961
4 #3964 one-shot challenge + openssl-free WebAuthn verifier + fail-closed audit #3963
5 #3966 turns BlockedAttested gate + AttestedResumePort + deterministic resume #3963
6 #3965 ironclaw_chain_signing — custodial multi-chain sign/broadcast + KMS sign-digest #3963
7 #3974 browser injected-wallet provider + web gate/resolve ingress #3966
8 #3993 NEAR redirect provider #3974
9 #3992 WalletConnect v2 provider #3974
10 #3994 ironclaw_attested_runtime — resume port + signer-continuation driver + ship-gate (merges #3992, #3965) #3993
11 #3995 reborn webui attested gate/resolve ingress #3994
12 #3996 durable Postgres + libSQL stores + per-chain broadcaster #3994
13 #3997 provider config + durable composition assembly (merges #3995) #3996
14 #4015 request_signature tool + gate-model extension — the loop raises the gate #3997

Status

Known follow-ups (post-collapse, non-blocking for these PRs)

Suggest merging bottom-up starting here.

@henrypark133 henrypark133 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.

Code Review (multi-agent)

Intent: Introduce a provider-agnostic signing trait crate and plan document to establish the foundation for attested-signing secure-channels.

Stats: 6 findings across 4 files. Reviewers run: security, bugs, performance, tests, conventions, local-patterns, maintainability. Reviewers failed: none. Body-only: 0

🛡️ Security

  1. High Public VerifiedProof::new constructor allows forging verified proofs (crates/ironclaw_signing_provider/src/proof.rs:66-76, confidence 100)
    The VerifiedProof struct represents a proof that has been cryptographically validated by a SigningProvider. VerifiedProof::new is fully public, allowing bypass of type-level verification.

  2. High SigningContext allows untrusted deserialization of tenant and user boundaries (crates/ironclaw_signing_provider/src/context.rs:113-130, confidence 75)
    If the resume API deserializes this context directly from untrusted client payloads without validation, it allows IDOR or cross-tenant signature grant claims.

  3. Medium Public ApprovedTxHash::from_bytes constructor allows caller-supplied hashes (crates/ironclaw_signing_provider/src/transaction.rs:83-85, confidence 75)
    ApprovedTxHash is intended to bind transactions uniquely and should only be computed server-side, but from_bytes allows direct creation from arbitrary bytes.

🧪 Tests

  1. Medium SigningProof::payload() accessor not tested for all variants (crates/ironclaw_signing_provider/src/proof.rs:35-42, confidence 100)
    The test 'payload_accessor_returns_inner_bytes_for_every_variant' misses WalletConnectProof and NearRedirectProof.

🏠 Local Patterns

  1. Low Missing publish = false in Cargo.toml (crates/ironclaw_signing_provider/Cargo.toml:1-10, confidence 100)
    All in-workspace crates in this repository set 'publish = false'. The newly added 'ironclaw_signing_provider' is missing this setting.

📐 Maintainability

  1. Low Redundant suffix in SigningProof enum variants (crates/ironclaw_signing_provider/src/proof.rs:21-31, confidence 100)
    Variants in SigningProof carry redundant 'Proof' or 'AssertionProof' suffixes.

Comment thread crates/ironclaw_signing_provider/src/proof.rs
Comment thread crates/ironclaw_signing_provider/src/transaction.rs
@@ -0,0 +1,167 @@
//! Identity newtypes and the [`SigningContext`] carried through every signing

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.

High — SigningContext allows untrusted deserialization of tenant and user boundaries.

SigningContext derives Deserialize and exposes public fields for tenant, user, scope, etc. If the resume API or route handler deserializes this context directly from untrusted client payloads without reconciling and enforcing that the authenticated session's tenant/user matches the context's tenant/user, an attacker can perform an IDOR or cross-tenant signature grant claim attack.

Fix: Validate that the authenticated session user/tenant matches the fields in SigningContext before processing any signing operations.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Declining in this crate — the concern is real but the enforcement point is downstream, not here. ironclaw_signing_provider is the pure trait crate at the base of the stack; it has no session, no authenticated principal, and no auth store to reconcile against, so it cannot enforce that an authenticated session's tenant/user matches a SigningContext. SigningContext must stay Deserialize because it is the wire payload carried across the resume boundary. The session/context reconciliation (rejecting a resume whose context tenant/user does not match the authenticated session — the IDOR/cross-tenant defense) lands in PR5 where ironclaw_turns gains AttestedResumePort and the authenticated principal is in scope; this is documented in the module header reconciliation note (context.rs:4-19). Adding a half-enforcement here with no principal to check against would give a false sense of safety. Tracking the enforcement as a PR5 requirement.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Still open — not fixed by the cascade, and I don't want to close it silently.

SigningContext does still derive Deserialize with public fields (context.rs:112). What I can say precisely:

  • No untrusted wire path deserializes it in this PR. This crate is the trait/type layer; it has no handlers. In the ported stack the context reaches a verifier only via the server-persisted AttestedGateBinding (ironclaw_attested_runtime), and the round-2 invariant is that the signer is taken from the gate-bound SigningContext.key_or_account_id, never from the transaction body or a caller payload.
  • Where your concern actually bites is the gate/resolve ingress (feat(signing): reborn webui attested gate/resolve ingress (attested-signing PR11/12) #3995), which is the next hop in the cascade and is not yet rebased. That is the layer that parses caller input into a resolution, and it is also where the cross-user IDOR (assert_binding_owner) fix lives.

So: leaving this thread open deliberately, and I'll carry it into #3995 as an explicit check — either the ingress never deserializes a caller-supplied SigningContext (and I'll show the code path), or it needs the tenant/user fields sealed. If you'd prefer the type hardened here regardless (e.g. #[serde(deny_unknown_fields)] plus private fields with checked constructors), say so and I'll do it in this PR.

Comment thread crates/ironclaw_signing_provider/src/proof.rs
Comment thread crates/ironclaw_signing_provider/Cargo.toml
Comment thread crates/ironclaw_signing_provider/src/proof.rs
zmanian added a commit that referenced this pull request May 26, 2026
Addresses henrypark133 CHANGES_REQUESTED review on #3960.

High (forgery): VerifiedProof derived Deserialize and its public new()
took only the proof, so any caller — or any untrusted wire payload — could
mint a "verified" trust token without verification ever running. Drop
Deserialize (locked in by a compile_fail doctest) so it can never be
rehydrated off the wire, and bind provider_id + approved_tx_hash through
new() so a verified proof cannot be re-pointed at a different provider or
transaction. Construction stays pub because the real verifiers live in the
downstream provider/attestation crates (PR4/PR7-9); the seam is now an
un-deserializable token only a verifier can mint.

Tests: extend payload_accessor coverage to all four SigningProof variants
(WalletConnect + NearRedirect were missing) and assert the serialize-binding
/ non-deserializable invariant.

Conventions: add publish = false to Cargo.toml (matches 55/59 workspace crates).

Purity invariant preserved: no new dependencies; the architecture
dependency-boundary test stays green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@zmanian
zmanian force-pushed the attested-signing-01-provider-trait branch from af3140a to cd7ca66 Compare May 26, 2026 01:13
@zmanian

zmanian commented May 26, 2026

Copy link
Copy Markdown
Collaborator Author

Review feedback addressed (commit cd7ca66)

Thanks @henrypark133. Summary of dispositions:

# Finding Disposition
1 High — Public VerifiedProof::new allows forging verified proofs Fixed — dropped Deserialize (compile_fail doctest locks it), bound provider_id + approved_tx_hash in new(). Construction stays pub because verifiers live in downstream crates; the token is now un-deserializable and provider/tx-bound. Also resolves the gemini Deserialize-bypass thread.
2 High — SigningContext untrusted deserialization (IDOR/cross-tenant) Declined here / deferred to PR5 — the pure trait crate has no authenticated principal to reconcile against; SigningContext is the resume wire payload. Session/context reconciliation enforcement lands in PR5 (AttestedResumePort), per the documented reconciliation note.
3 Medium — Public ApprovedTxHash::from_bytes allows caller-supplied hashes Declined — boundary already documented ('NEVER construct from client/resume bytes'); the server-side hasher in PR2 needs this constructor, and the caller-supplied-hash defense is enforced at the consuming resume path, not by removing the constructor.
4 Medium — payload() accessor not tested for all variants Fixed — test now covers all four variants (added WalletConnect + NearRedirect).
5 Low — Missing publish = false Fixed — added.
6 Low — Redundant Proof suffix in enum variants Declined — variant names are the serde kind wire tags (persisted gate state + resume wire); renaming changes a wire contract and would collide with ProviderId tags.

Purity invariant preserved (no new deps; architecture dependency-boundary test green). Verified: cargo build, cargo test (16 unit + 1 compile_fail doctest), cargo fmt --check, cargo clippy --all-features (zero warnings).

@zmanian

zmanian commented May 26, 2026

Copy link
Copy Markdown
Collaborator Author

Stack rebased bottom-up onto the hardened attestation API

The full attested-signing stack has been rebased + recomposed so it stacks cleanly on the #3961 wire-hardening fix, and reviews have been re-requested on every branch. Context for reviewers:

Why a rebase (not just review fixes)

#3961 (canonical signing-bytes) merged into the -01 line before its review verdict; the fix landed as follow-up #4067, which reshaped the ironclaw_attestation public API:

  • approved_tx_hash_for(tx, signer_account: &str, schema) -> Result<…> (fail-closed, fallible) replaces the infallible compute_approved_tx_hash; render / canonical_signing_bytes are now fallible.
  • DecodedTransaction::signer_account() removed — the signer is now sourced from the gate-bound SigningContext.key_or_account_id.
  • Solana/NEAR wire-model reshape: SolanaTransaction.static_account_keys + index-based SolanaCompiledInstruction, NearAction enum, NearTransaction.public_key.
  • VerifiedProof::new(ProviderId, ApprovedTxHash, SigningProof).

So -06 … -14 were ported onto the new API, not merely rebased. The two cumulative integration branches (-10 merges -06/-09; -13 merges -11) were recomposed from the ported siblings.

New branch tips (each on its rebased parent)

Base: #4067 wire-hardening e5b6d2142 (= -01 + merged -02 + fail-closed encoding).

PR branch tip
#3963 -03 grant-ledger 404535429
#3964 -04 challenge-webauthn 6ec82ff0f
#3966 -05 turns-resume 449c4014c
#3965 -06 chain-signing 19136c897
#3974 -07 injected-provider 0c7224ac4
#3993 -08 near-redirect 6da381e9b
#3992 -09 walletconnect 772e0f200
#3994 -10 reborn-runtime b5f0f6ffb
#3995 -11 webui-ingress 502489db3
#3996 -12 durable-stores 4e675c63c
#3997 -13 production-wiring cfd2b29c4
#4015 -14 loop-raise 3c0a01b24
#4060 coherence-followups f233a3950
#4058 kms-shipgate c8c302dc2
#4055 trust-registration 4b077ba41
#4054 multitenant-model 0987fd49a

Reviewer notes

  • Branches are squashed per-branch (feat + review fixes collapsed). Diffs are clean against the rebased parent, but prior inline threads may have re-anchored or detached — apologies for the churn.
  • Invariants verified preserved at every hop: gate-bound signer in the WYSIWYS recompute (never the tx body), verify-before-advance, sealed one-shot atomic CAS (PG + libSQL), broadcast failure → 5xx/ManualReview (no double-spend), KMS ship-gate + curve-capability fail-closed, NEAR bound-key verification, ContextMismatch→403, TenantId isolation, openssl-free (ring 0.17 / kuchikikiki 0.9.1).
  • Each branch builds + tests green except 4 pre-existing failures unrelated to attested-signing — 2 facade_factory + 2 first_party_runtime_contract, all the RuntimeProcessPort / HTTP-egress production-process-backend-wiring defect class, confirmed failing identically on the parents.
  • The merge into reborn-integration remains for the humans; this only made the branches cleanly stackable.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added support for secure, provider-agnostic transaction signing across EVM, Solana, and NEAR.
    • Added deterministic transaction rendering and approval hashes that bind signed content to the signer and network.
    • Added support for multiple signing proof types, including external wallets, browser providers, redirects, and custodial flows.
    • Added structured signing context and fail-closed verification errors.
  • Documentation

    • Added an overview of supported signing flows, security controls, and rollout plans.
  • Tests

    • Added coverage for serialization, signer binding, tamper detection, and cross-chain transaction handling.

Walkthrough

Adds a provider-agnostic signing abstraction and chain-specific attestation crate for EVM, Solana, and NEAR. It models transactions, derives rendered and canonical projections, reconstructs wire bytes, computes approved hashes, and adds dependency-boundary and anti-field-smuggling tests.

Changes

Attested signing substrate

Layer / File(s) Summary
Provider contracts and identity types
Cargo.toml, crates/ironclaw_signing_provider/..., docs/plans/...
Adds provider contracts, signing context, errors, proofs, opaque transaction handles, serialization tests, and the attested-signing plan.
Decoded transaction and shared field projection
crates/ironclaw_attestation/...
Adds full-fidelity EVM, Solana, and NEAR models, shared signing-field projection, rendering, and public attestation exports.
Canonical bytes and approved hash
crates/ironclaw_attestation/src/{approved_tx_hash,canonical,wire}.rs
Adds deterministic canonical encoding, Solana and NEAR wire reconstruction, and approved transaction hash binding.
Boundary and anti-smuggling validation
crates/ironclaw_architecture/tests/attested_signing_boundaries.rs, crates/ironclaw_attestation/tests/binding.rs
Tests dependency purity, deterministic serialization, signer and field binding, strict deserialization, rendering coverage, and domain separation.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Attestation
  participant SigningProvider
  Caller->>Attestation: approved_tx_hash_for(transaction, signer, schema)
  Attestation-->>Caller: ApprovedTxHash and RenderedTx
  Caller->>SigningProvider: initiate(context, decoded, rendered, approved_hash)
  SigningProvider-->>Caller: InitiationOutcome
  Caller->>SigningProvider: verify_resume(context, approved_hash, proof)
  SigningProvider-->>Caller: VerifiedProof or SigningProviderError
Loading

Possibly related issues

Suggested reviewers: henrypark133

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is largely off-template and omits most required sections like Change Type, Linked Issue, Validation, and Test Strategy. Rewrite it using the repo template and complete the missing sections, especially linked issue, validation, test strategy, security impact, and rollback.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title matches Conventional Commits and clearly summarizes the main attested-signing crate work.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

Actionable comments posted: 4

🤖 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.

Inline comments:
In `@crates/ironclaw_attestation/Cargo.toml`:
- Around line 19-35: Rename the test-only feature in the [features] section from
test-internals to the mandated test-support name, and update the self
dev-dependency to enable test-support. Revise the associated manifest comments
to explicitly state that test-support is the approved sole dev-only seam while
production callers must use approved_tx_hash_for.

In `@crates/ironclaw_attestation/src/approved_tx_hash.rs`:
- Around line 100-101: Remove the #[allow(clippy::too_many_arguments)]
attributes from both occurrences associated with compute_approved_tx_hash_inner
and its companion function, since each function has fewer than Clippy’s
threshold. If either suppression is required for another reason, retain it only
with the mandated immediately preceding arch-exempt comment including the
missing aggregation and plan number.

In `@crates/ironclaw_signing_provider/src/context.rs`:
- Around line 6-19: Resolve shared contract ownership across crates: in
crates/ironclaw_signing_provider/src/context.rs lines 6-19, remove the local
GateRef and identity mirrors and import them from their owning
dependency-neutral crate, or relocate the single definitions there; in
crates/ironclaw_signing_provider/src/transaction.rs lines 13-50, define
decoded/rendered transaction contracts at that same owning boundary and stop
exposing provider-local placeholders. Ensure each shared Rust type has exactly
one canonical definition and update references to use it.

In `@docs/plans/2026-05-23-attested-signing-substrate.md`:
- Around line 33-59: Recast the “Load-Bearing Security Invariants (every PR
honors)” section as planned or target invariants until each control is
implemented. Soften present-tense guarantees for the grant, ledger,
continuation, WebAuthn, audit, and KMS items, and link any already-enforced
guarantee to its concrete implementation or test rather than asserting universal
compliance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6c1fc565-651d-4767-af97-06f283082cde

📥 Commits

Reviewing files that changed from the base of the PR and between 1fca2bd and 18daaff.

📒 Files selected for processing (19)
  • Cargo.toml
  • crates/ironclaw_architecture/tests/attested_signing_boundaries.rs
  • crates/ironclaw_attestation/Cargo.toml
  • crates/ironclaw_attestation/src/approved_tx_hash.rs
  • crates/ironclaw_attestation/src/canonical.rs
  • crates/ironclaw_attestation/src/decoded_tx.rs
  • crates/ironclaw_attestation/src/fields.rs
  • crates/ironclaw_attestation/src/lib.rs
  • crates/ironclaw_attestation/src/rendered.rs
  • crates/ironclaw_attestation/src/wire.rs
  • crates/ironclaw_attestation/tests/binding.rs
  • crates/ironclaw_signing_provider/Cargo.toml
  • crates/ironclaw_signing_provider/src/context.rs
  • crates/ironclaw_signing_provider/src/error.rs
  • crates/ironclaw_signing_provider/src/lib.rs
  • crates/ironclaw_signing_provider/src/proof.rs
  • crates/ironclaw_signing_provider/src/provider.rs
  • crates/ironclaw_signing_provider/src/transaction.rs
  • docs/plans/2026-05-23-attested-signing-substrate.md

Comment thread crates/ironclaw_attestation/Cargo.toml Outdated
Comment thread crates/ironclaw_attestation/src/approved_tx_hash.rs
Comment thread crates/ironclaw_signing_provider/src/context.rs
Comment thread docs/plans/2026-05-23-attested-signing-substrate.md
@zmanian
zmanian force-pushed the attested-signing-01-provider-trait branch from 18daaff to 062ebcf Compare July 23, 2026 15:37

@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.

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@crates/ironclaw_attestation/src/decoded_tx.rs`:
- Around line 286-299: Extend NearAction::Delegate with the NEP-366 inner
actions payload, using a non-recursive action representation where needed to
avoid recursive type issues. Update fields.rs::project_near_action and
wire.rs::push_near_action to preserve and encode the delegated actions, and
adjust borrow-limited tests to verify that differing inner action lists produce
distinct commitments.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d1258e85-72ce-4d3e-88a2-28a7d6853937

📥 Commits

Reviewing files that changed from the base of the PR and between 18daaff and 062ebcf.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !**/Cargo.lock
📒 Files selected for processing (19)
  • Cargo.toml
  • crates/ironclaw_architecture/tests/attested_signing_boundaries.rs
  • crates/ironclaw_attestation/Cargo.toml
  • crates/ironclaw_attestation/src/approved_tx_hash.rs
  • crates/ironclaw_attestation/src/canonical.rs
  • crates/ironclaw_attestation/src/decoded_tx.rs
  • crates/ironclaw_attestation/src/fields.rs
  • crates/ironclaw_attestation/src/lib.rs
  • crates/ironclaw_attestation/src/rendered.rs
  • crates/ironclaw_attestation/src/wire.rs
  • crates/ironclaw_attestation/tests/binding.rs
  • crates/ironclaw_signing_provider/Cargo.toml
  • crates/ironclaw_signing_provider/src/context.rs
  • crates/ironclaw_signing_provider/src/error.rs
  • crates/ironclaw_signing_provider/src/lib.rs
  • crates/ironclaw_signing_provider/src/proof.rs
  • crates/ironclaw_signing_provider/src/provider.rs
  • crates/ironclaw_signing_provider/src/transaction.rs
  • docs/plans/2026-05-23-attested-signing-substrate.md

Comment on lines +286 to +299
/// Meta-transaction delegate action (NEP-366). Carries the inner action's
/// fields needed for the signed commitment.
Delegate {
/// The account whose key signs the delegate action.
sender_id: String,
/// The receiver of the delegated actions.
receiver_id: String,
/// Nonce for the delegate action.
nonce: u64,
/// Block height past which the delegate action is invalid.
max_block_height: u64,
/// Public key authorizing the delegate action.
public_key: NearPublicKey,
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

NEP-366 NEAR DelegateAction borsh fields sender_id receiver_id actions nonce max_block_height public_key

💡 Result:

The DelegateAction structure, introduced by NEP-366 for meta-transactions on the NEAR Protocol, consists of the following fields, which are serialized using Borsh (Binary Object Representation Serializer for Hashing): 1. sender_id (AccountId): The account ID of the signer who initiated the delegated actions [1][2]. 2. receiver_id (AccountId): The account ID where the delegated actions will be executed [1][2]. 3. actions (Vec): A list of actions to be executed. To prevent recursive nesting, this uses the NonDelegateAction type [2][3]. 4. nonce (Nonce): A 64-bit integer used to prevent replay attacks; it must match the expected nonce for the provided public key and is incremented after processing [1][2]. 5. max_block_height (BlockHeight): A 64-bit integer representing the maximum block height at which the DelegateAction remains valid [1][2]. 6. public_key (PublicKey): The public key used to verify the signature of the delegated action [1][2]. In practice, the DelegateAction is typically wrapped in a SignedDelegateAction, which includes the DelegateAction itself and the corresponding signature [1][2]. Additionally, when serializing for the network, a DelegateActionPrefix is often required (per NEP-461) to prevent hash collisions before the encoded DelegateAction bytes [4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
git ls-files 'crates/ironclaw_attestation/src/decoded_tx.rs'

echo "== relevant decoded_tx.rs sections =="
sed -n '240,370p' crates/ironclaw_attestation/src/decoded_tx.rs
echo "== project_near_action references =="
rg -n "project_near_action|push_near_action|NearAction::Delegate|ActionsHash|ApprovedTxHash|delegate" crates/ironclaw_attestation/src crates -g '*.rs' -C 3

Repository: nearai/ironclaw

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== decoded_tx.rs imports/types around delegate =="
sed -n '1,180p' crates/ironclaw_attestation/src/decoded_tx.rs

echo "== fields.rs excerpt =="
sed -n '1,260p' crates/ironclaw_attestation/src/fields.rs

echo "== wire.rs delegate excerpt =="
sed -n '130,260p' crates/ironclaw_attestation/src/wire.rs

echo "== exact Delegate/NonDelegate action test names =="
rg -n "delegate|Delegate|NonDelegate|DelegateActionPrefix|DelegateAction|near_distinct_actions_hash_differently|actions_hash" crates/ironclaw_attestation crates/ironclaw_signing_provider -g '*.rs' -C 4

Repository: nearai/ironclaw

Length of output: 30649


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== binding test delegate section =="
sed -n '539,600p' crates/ironclaw_attestation/tests/binding.rs

echo "== inspect all NearAction::Delegate occurrences in tests =="
rg -n "NearAction::Delegate|Delegate \\{|\\.Delegate" crates/ironclaw_attestation/tests crates/ironclaw_attestation/src -g '*.rs' -C 3

echo "== behavioral probe: current variant projection equality for two distinct inner action sets =="
python3 - <<'PY'
from pathlib import Path
text = Path("crates/ironclaw_attestation/src/fields.rs").read_text()
start = text.index("NearAction::Delegate {")
end = text.index("    }", start)
print(text[start:end+5])
print("Delegate arm contains 'actions' or 'action':", "actions" in text[start:end+5] or "action" in text[start:end+5])
PY

Repository: nearai/ironclaw

Length of output: 5313


Model and bind NEP-366 Delegate actions.

NEP-366 signed DelegateAction includes actions; NearAction::Delegate currently stores only the delegate metadata, and both fields.rs::project_near_action and wire.rs::push_near_action ignore any inner actions. Add the delegated action payload, update the borrow-limited tests to distinguish different inner actions, and use a non-recursive action type where appropriate so meta-tx approval cannot collapse distinct delegated action lists into one hash.

🤖 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/ironclaw_attestation/src/decoded_tx.rs` around lines 286 - 299, Extend
NearAction::Delegate with the NEP-366 inner actions payload, using a
non-recursive action representation where needed to avoid recursive type issues.
Update fields.rs::project_near_action and wire.rs::push_near_action to preserve
and encode the delegated actions, and adjust borrow-limited tests to verify that
differing inner action lists produce distinct commitments.

@zmanian

zmanian commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #6748, part of consolidating the 20-PR attested-signing-* stack into 8 PRs against current main.

The content of this PR is carried forward in full, re-based on current main rather than on the old stack. That re-basing also fixed the CI failures that had been red here — including the missing [package.metadata.ironclaw] layer declarations, which were blocking the bottom of the stack and therefore every PR above it.

Closing to keep the queue honest. The review history stays on this PR and remains readable; reopen if the consolidation is rejected.

@zmanian zmanian closed this Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: medium Business logic, config, or moderate-risk modules scope: dependencies Dependency updates scope: docs Documentation size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants