Skip to content

refactor(attestation): extract attestation-types crate without dcap-qvl - #3245

Closed
pbeza wants to merge 3 commits into
mainfrom
feat/attestation-types-split
Closed

refactor(attestation): extract attestation-types crate without dcap-qvl#3245
pbeza wants to merge 3 commits into
mainfrom
feat/attestation-types-split

Conversation

@pbeza

@pbeza pbeza commented May 15, 2026

Copy link
Copy Markdown
Contributor

Closes #3267

Pure crate split, no behaviour change. Stacked on #3237. Sets up the WASM-size win that the follow-up PR (mpc-contract Promise refactor) will land.

What moves into the new attestation-types crate

  • app_compose, measurements, report_data, tcb_info modules — none of these touched dcap-qvl to begin with.
  • The post-DCAP verification helpers — refactored from private methods on DstackAttestation into free functions in attestation_types::verify_post_dcap, taking the tee_verifier_interface::VerifiedReport mirror instead of the dcap_qvl type. Same logic, byte-for-byte.

What stays in attestation

  • DstackAttestation::verify — the one and only dcap_qvl::verify::verify call site. It now converts the upstream report to the Borsh mirror once and calls the free functions on it.
  • The Collateral newtype and QuoteBytes newtype. Their dedup with the tee-verifier-interface mirrors is deferred to a later PR (tee-authority and friends need a migration path).

What this enables (next PR, stacked on this one)

mpc-contract can depend on attestation-types alone — no dcap-qvl — and call the post-DCAP helpers from a Promise callback that receives the verifier-returned VerifiedReport. That is the actual WASM-size win for NEP-509.

Drive-bys

  • Drops the dead TryFrom<dcap_qvl::verify::VerifiedReport> for Measurements impl (no callers anywhere in the codebase).
  • Re-exports the moved modules from attestation::lib.rs so existing consumers (mpc-attestation, tee-authority, attestation-cli, etc.) keep their import paths unchanged.

Part of the verifier breakout described in docs/design/attestation-verifier-contract.md (#3160).

pbeza added 3 commits May 15, 2026 12:20
…undary

Introduces a no_std crate carrying the Borsh-stable types that cross the
`tee-verifier` contract boundary. The crate has no `dcap-qvl` dependency
so it can be linked into consumer contracts (e.g. `mpc-contract`)
without dragging `ring`/`webpki`/X.509 parsing into their WASM.

Types:

- `QuoteBytes`, `Collateral`: standalone mirrors of `dcap_qvl`'s quote
  bytes and `QuoteCollateralV3`.
- `VerifiedReport`, `Report`, `TDReport10`, `TDReport15`, `EnclaveReport`,
  `TcbStatus`, `TcbStatusWithAdvisory`: Borsh mirrors of the corresponding
  `dcap_qvl` types, fields one-to-one for forward-compat.
- `VerifierError`: error type for future use.

Conversions between these mirrors and the `dcap_qvl` types live in the
verifier crate, not here, to avoid the orphan rule and to keep this
crate `dcap-qvl`-free.

Borsh-schema derives are gated behind an optional `borsh-schema` feature,
enabled by the verifier crate when `cargo near build` generates ABI.

Part of the work described in
docs/design/attestation-verifier-contract.md (PR #3160).
Adds the `tee-verifier` contract — a stateless NEAR contract exposing
one method:

    verify_quote(quote: QuoteBytes, collateral: Collateral) -> VerifiedReport

The method reads the current block timestamp inside the contract and
calls `dcap_qvl::verify::verify(quote, collateral, now)`. On success,
the parsed report is converted to the Borsh mirror types from
`tee-verifier-interface` and returned via Borsh. On verification
failure, the method panics with the upstream error rendered as a
string; callers handle this as `PromiseResult::Failed` in their
callback.

The contract has no state and no admin. All policy (allowlists,
report-data binding, RTMR3 replay, app-compose validation, etc.) is
the caller's responsibility — only the cryptographic dcap-qvl part
lives here.

Includes:
- `tee_verifier::TeeVerifier` (empty state struct, one method).
- `tee_verifier::conversions` (free functions converting between
  `dcap_qvl` types and the mirror types in `tee-verifier-interface`;
  free functions rather than `From` impls because of the orphan rule).
- An integration test (`tests/verify_quote.rs`) that calls
  `verify_quote` directly against the real Dstack quote+collateral
  fixture from `test-utils`, asserting the returned `VerifiedReport`
  has status `UpToDate`, no advisory IDs, and a TD10 report.

WASM size (non-reproducible, default release): ~518 KiB.

This is the v1 verifier from
`docs/design/attestation-verifier-contract.md` (#3160). A follow-up
PR will wire `mpc-contract`'s `submit_participant_info` into it via
Promise + callback.

Stacked on #3235.
…-qvl

Pure crate split, no behaviour change. Sets up the WASM-size win that
the follow-up PR (mpc-contract Promise refactor) will land.

What moves:
- `app_compose`, `measurements`, `report_data`, `tcb_info` modules and
  their assets — none of these touched `dcap-qvl` to begin with.
- The post-DCAP verification helpers (`verify_tcb_status`,
  `verify_report_data`, `verify_rtmr3`, `verify_app_compose`,
  `verify_any_measurements`, `verify_static_rtmrs`,
  `verify_key_provider_digest`, `verify_event_log_rtmr3`,
  `validate_app_compose_config`, `compare_hashes`, `compare_hex_hashes`,
  plus the `OrErr` and `GetSingleEvent` traits and `VerificationError`)
  — refactored from private methods on `DstackAttestation` to free
  functions in `attestation_types::verify_post_dcap`, taking the
  `tee_verifier_interface::VerifiedReport` mirror instead of the
  `dcap_qvl` type.

What stays in `attestation`:
- `DstackAttestation::verify` (the one and only `dcap_qvl::verify::verify`
  call site). It now converts the dcap-qvl-returned report to the
  Borsh mirror once and calls the free functions on it.
- The `Collateral` newtype and `QuoteBytes` newtype. Their dedup with
  the `tee-verifier-interface` mirrors is deferred to a later PR.

What this enables (next PR):
- `mpc-contract` can depend on `attestation-types` alone (no `dcap-qvl`)
  and call the post-DCAP helpers from a Promise callback that receives
  the verifier-returned `VerifiedReport`. That is the actual WASM-size
  win for NEP-509.

Drops the dead `TryFrom<dcap_qvl::verify::VerifiedReport> for Measurements`
impl (no callers anywhere in the codebase).

Re-exports the moved modules from `attestation::lib.rs` so existing
consumers (`mpc-attestation`, `tee-authority`, `attestation-cli`, etc.)
keep their import paths unchanged.

Part of the verifier breakout from
docs/design/attestation-verifier-contract.md (#3160). Stacked on #3237.
@pbeza

pbeza commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Closing this in favor of #3714 + #3715.

@pbeza pbeza closed this Jul 24, 2026
andrei-near pushed a commit that referenced this pull request Aug 1, 2026
…eBytes into the no-dcap-qvl crate

Pure crate split, no behavior change. Sets up the next PR to drop
`dcap-qvl` from `mpc-contract`'s WASM build graph (currently
`mpc-contract -> mpc-attestation -> attestation -> dcap-qvl`).

What moves into `attestation-types`:

- `DstackAttestation` — the wasm-friendly bundle struct
  (`quote`, `collateral`, `tcb_info`). No methods on the struct itself;
  the `dcap_qvl::verify::verify` entry point is a trait method on the
  `attestation` side.
- `Collateral` — field-for-field mirror of `dcap_qvl::QuoteCollateralV3`.
  Borsh wire layout matches the upstream type when `dcap-qvl` is built
  with its `borsh` feature, so on-chain state that previously stored an
  `attestation::collateral::Collateral` (newtype around
  `dcap_qvl::QuoteCollateralV3`) decodes into this type byte-identical
  without migration.
- `QuoteBytes` — unchanged.

What stays in `attestation`:

- `DstackVerify` trait + `impl DstackVerify for DstackAttestation`, which
  is the one and only `dcap_qvl::verify::verify` call site. Defined as
  a trait because inherent impls on foreign types are forbidden in Rust.
- `collateral_from_dcap` / `collateral_to_dcap` free functions for
  converting between `dcap_qvl::QuoteCollateralV3` and the mirror. Used
  by off-chain code (`tee-authority`'s PCCS fetch path, integration
  tests) and re-exported through `mpc_attestation` so the call sites
  don't need to add a direct `attestation` dep.

Why a trait instead of an inherent method:

- The `DstackAttestation` struct lives in `attestation-types`.
- The `verify` method requires `dcap-qvl` (heavy crypto closure).
- Rust forbids inherent impls on foreign types. A trait lets the
  `verify` method be added from the `attestation` crate (which has the
  `dcap-qvl` dep) to the type defined in `attestation-types` (which
  doesn't).

Callers using `dstack_attestation.verify(...)` syntax now need to bring
`DstackVerify` into scope. `mpc-attestation` does this internally; no
other consumers were affected.

Drive-bys:

- `mpc-attestation::collateral_from_dcap` / `mpc_attestation::collateral_to_dcap`
  re-exports so `tee-authority` and `mpc-node` can call them without
  adding an `attestation` direct dep.
- `IntoContractType<Collateral>` / `IntoInterfaceType<dtos::Collateral>`
  impls in `mpc-contract` and `mpc-node` simplified to direct field
  assignment (no more `Collateral::from(QuoteCollateralV3 { ... })`).
- `attestation` crate's deps pruned: drops `borsh`, `derive_more`,
  `serde`, `hex`, `thiserror` (no longer used after the body shrank to
  the trait + conversion helpers); `serde_json` moved to dev-deps.

WASM size impact (non-reproducible release-contract profile): 1,470,007
→ 1,471,144 bytes (+1,137 bytes / 0.07%). Negligible — the next PR
drops `dcap-qvl` from the contract entirely.

Stacked on #3245.
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.

Extract attestation-types crate from attestation (no dcap-qvl)

1 participant