Skip to content

feat(verify-quote): deploy-verify a provisioned node for the operator - #260

Merged
samlaf merged 1 commit into
seismicfrom
sl/sei-183-verify-quote-deploy-subcommand
Aug 19, 2026
Merged

feat(verify-quote): deploy-verify a provisioned node for the operator#260
samlaf merged 1 commit into
seismicfrom
sl/sei-183-verify-quote-deploy-subcommand

Conversation

@samlaf

@samlaf samlaf commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Part of SEI-183

SEI-181 gave nodes getDeployVerificationEvidence: send a fresh deployment_nonce, get back a quote over
deploy_verification_binding(network_id, deployment_nonce). Nothing consumed it, so an operator who had just provisioned a box still held only a port check. The relying party is the missing half of that exchange — it has to mint the nonce, recompute the binding from its own manifest copy, and enforce a measurement policy on what comes back.

That party belongs here. This binary already owns the one DCAP path the deploy tooling shells out to, and the frozen binding vector the quoting node is checked against; a second implementation of either would be a second answer to the same question. So the CLI grows a subcommand rather than a sibling: harvest keeps the founding summit-keys check (evidence supplied as a file, binding from --nonce/--node-pubkey/--consensus-pubkey) and deploy challenges a live node itself over --endpoint. --policy and the DCAP knobs are shared, and verification, policy enforcement, and report rendering are one path for both.

deploy mints the nonce and hands it to nobody but the node under test, so a passing quote can only answer this exact request — a cached or replayed envelope fails the binding. Local inputs resolve before the RPC: a mistyped path should not be discovered on the far side of a round-trip. The manifest is parsed as a v1 document before its bytes are hashed into a network_id, which is what separates "you passed the wrong file" from "this node answered for another network" — unparsed, any file hashes to some id and the run fails as a binding mismatch, the alarm that makes an operator burn a box. The request timeout is 120s because quote generation opens the node's TPM exclusively and is serialized in-process, so a busy node answers in seconds.

The success report carries purpose-specific extras — network_id and deployment_nonce for deploy — so an archived report says what was checked, not just that something verified. Both bindings keep frozen vectors, and the new path is covered by a missing manifest, a file that is not a manifest, local inputs failing while the endpoint points nowhere (proving no RPC was attempted), and an unusable endpoint.

The harvest argv moves behind its subcommand, so deploy's harvest step and the docs that quote verify-quote --evidence move with it. Cargo.lock follows the new deps (seismic-attestation-rpc, jsonrpsee, rand).

Part of SEI-183

SEI-181 gave nodes getDeployVerificationEvidence: send a fresh
deployment_nonce, get back a quote over
deploy_verification_binding(network_id, deployment_nonce). Nothing
consumed it, so an operator who had just provisioned a box still held
only a port check. The relying party is the missing half of that
exchange — it has to mint the nonce, recompute the binding from its own
manifest copy, and enforce a measurement policy on what comes back.

That party belongs here. This binary already owns the one DCAP path the
deploy tooling shells out to, and the frozen binding vector the quoting
node is checked against; a second implementation of either would be a
second answer to the same question. So the CLI grows a subcommand rather
than a sibling: `harvest` keeps the founding summit-keys check (evidence
supplied as a file, binding from --nonce/--node-pubkey/--consensus-pubkey)
and `deploy` challenges a live node itself over --endpoint. --policy and
the DCAP knobs are shared, and verification, policy enforcement, and
report rendering are one path for both.

`deploy` mints the nonce and hands it to nobody but the node under test,
so a passing quote can only answer this exact request — a cached or
replayed envelope fails the binding. Local inputs resolve before the RPC:
a mistyped path should not be discovered on the far side of a round-trip.
The manifest is parsed as a v1 document before its bytes are hashed into
a network_id, which is what separates "you passed the wrong file" from
"this node answered for another network" — unparsed, any file hashes to
some id and the run fails as a binding mismatch, the alarm that makes an
operator burn a box. The request timeout is 120s because quote generation
opens the node's TPM exclusively and is serialized in-process, so a busy
node answers in seconds.

The success report carries purpose-specific extras — network_id and
deployment_nonce for `deploy` — so an archived report says what was
checked, not just that something verified. Both bindings keep frozen
vectors, and the new path is covered by a missing manifest, a file that
is not a manifest, local inputs failing while the endpoint points
nowhere (proving no RPC was attempted), and an unusable endpoint.

The harvest argv moves behind its subcommand, so deploy's harvest step
and the docs that quote `verify-quote --evidence` move with it. Cargo.lock
follows the new deps (seismic-attestation-rpc, jsonrpsee, rand).
@samlaf
samlaf requested a review from cdrappi as a code owner August 19, 2026 20:02
@linear-code

linear-code Bot commented Aug 19, 2026

Copy link
Copy Markdown

SEI-183

@samlaf
samlaf merged commit 420eb01 into seismic Aug 19, 2026
5 checks passed
@samlaf
samlaf deleted the sl/sei-183-verify-quote-deploy-subcommand branch August 19, 2026 20:10
samlaf added a commit that referenced this pull request Aug 19, 2026
Fixes SEI-321

#260 gave `harvest` its claims as four flags — --nonce, --node-pubkey,
--consensus-pubkey, --evidence — because verification ran before any
record file existed. It exists now: deploy archives exactly those four
facts together as inputs/harvest/<node>.json, then explodes that file
back into flags to re-verify it, and a third party re-verifying the
archive has to reassemble five hex strings out of it first.

So the record becomes the input format. `harvest --record <file|->`
reads the four fields from one JSON document, and HarvestRecord pins
that schema here: harvest_nonce, node_public_key, consensus_public_key,
evidence. This is the natural home for it — founding_summit_keys_binding
already commits to exactly those three claims, one binding per node.
Whatever the archive keeps alongside them (when the harvest ran, the
report of the verification that passed) is ignored, so an archived file
verifies as-is:

```
    verify-quote harvest --record inputs/harvest/node-1.json \
      --policy measurement-policy-bootstrap.json
```

That one command is the point. The founding archive exists so future
users and joiners can re-run this verification themselves instead of
trusting the founder's verdict, and it now takes one file, this binary,
and the published policy — no deploy-repo tooling in the path.

No security change: the claims were untrusted input as flags and stay
untrusted as fields, because the quote's report_data is what decides
whether the node's keys really are these. A wrong claim still surfaces
as a binding mismatch — which is why each field is decoded up front and
rejected by name, before DCAP spends a collateral round-trip on a
document that was malformed all along.

Tests move with the interface: record fixtures around the same frozen
binding vectors, an archived record carrying harvested_at and a
verification report (the ignored-fields contract), and a record whose
evidence field is not an envelope. Cargo.lock follows the serde derive
the record needs.

Deploy is the only caller and cuts over in the same stroke, so the flag
form is gone with no back-compat path. The holder's response is
unchanged — it never sees the nonce it quotes over as data it owns — and
its comments now name the record its bytes travel inside.
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