feat(verify-quote): deploy-verify a provisioned node for the operator - #260
Merged
Merged
Conversation
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
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
harvestkeeps the founding summit-keys check (evidence supplied as a file, binding from --nonce/--node-pubkey/--consensus-pubkey) anddeploychallenges 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.deploymints 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 --evidencemove with it. Cargo.lock follows the new deps (seismic-attestation-rpc, jsonrpsee, rand).