Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

74 changes: 72 additions & 2 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,11 @@ install *args='':
# Phase 2 proof-deploy-system – Deploy proof system contracts
# Phase 3a proof-certmanager-prewarm – Pre-warm CertManager with CA certs
# Phase 3b proof-approve-pcrs – Approve PCR set on verifier
# Phase 3c proof-verify-pcrs – Assert the RUNNING enclave's PCR set is approved
# (drift check; safe to run any time)
# Phase 4 proof-register-key – Register the enclave's generated key on-chain
# (run separately; NOT part of proof-setup)
# Combined proof-setup – Run deploy phases 0a–3b in sequence (does NOT
# Combined proof-setup – Run deploy phases 0a–3c in sequence (does NOT
# run Phase 4 — register the key afterwards with
# proof-register-key, or let the worker
# self-register via `nitro-worker run --auto-register`)
Expand Down Expand Up @@ -562,6 +564,67 @@ proof-approve-pcrs env="alphanet":
fi
echo "PCR set approved."

# Phase 3c – Verify the RUNNING enclave's PCR set is approved on-chain.
#
# Every EIF rollout changes PCR0 (enclave image) and PCR2 (application) while PCR1
# (kernel) stays put. Nothing re-approves the new measurements automatically, so the
# running enclave silently drops off the on-chain allowlist and every registerKey / TEE
# proof reverts.
#
# This measures the enclave that is actually running rather than trusting PCR0/1/2 from
# the shell — trusting the shell would reproduce exactly the drift this is meant to catch.
#
# Required: L1_RPC_URL. Optional: NITRO_ATTESTATION_VERIFIER (else read from the
# {{env}}-nitro.json deployment).
proof-verify-pcrs env="alphanet":
#!/usr/bin/env bash
set -euo pipefail
if [ ! -f "scripts/proof-envs/{{env}}.env" ]; then
echo "Error: unknown env '{{env}}' — create scripts/proof-envs/{{env}}.env to configure it" >&2
exit 1
fi
DEPLOYMENTS_FILE="pkg/contracts/deployments/{{env}}-nitro.json"
if [ -z "${NITRO_ATTESTATION_VERIFIER:-}" ] && [ -f "$DEPLOYMENTS_FILE" ]; then
NITRO_ATTESTATION_VERIFIER=$(jq -r '.nitroAttestationVerifier // empty' "$DEPLOYMENTS_FILE")
fi
: "${NITRO_ATTESTATION_VERIFIER:?NITRO_ATTESTATION_VERIFIER is required (set it or run proof-deploy-nitro first)}"
: "${L1_RPC_URL:?L1_RPC_URL is required}"

echo "Measuring the running enclave…" >&2
eval "$(just proof-get-pcrs {{env}})"
: "${PCR0:?proof-get-pcrs did not return PCR0}"
: "${PCR1:?proof-get-pcrs did not return PCR1}"
: "${PCR2:?proof-get-pcrs did not return PCR2}"
[[ "$PCR0" == 0x* ]] || PCR0="0x$PCR0"
[[ "$PCR1" == 0x* ]] || PCR1="0x$PCR1"
[[ "$PCR2" == 0x* ]] || PCR2="0x$PCR2"

# The verifier stores keccak256 of each raw 48-byte PCR, not the PCR itself.
APPROVED=$(cast call "$NITRO_ATTESTATION_VERIFIER" \
"isPCRSetApproved(bytes32,bytes32,bytes32)(bool)" \
"$(cast keccak "$PCR0")" "$(cast keccak "$PCR1")" "$(cast keccak "$PCR2")" \
--rpc-url "$L1_RPC_URL")

if [ "$APPROVED" != "true" ]; then
echo "" >&2
echo "ERROR: the running enclave's PCR set is NOT approved on ${NITRO_ATTESTATION_VERIFIER}." >&2
echo "" >&2
echo " running enclave:" >&2
echo " PCR0=$PCR0" >&2
echo " PCR1=$PCR1" >&2
echo " PCR2=$PCR2" >&2
if [ -f "$DEPLOYMENTS_FILE" ]; then
echo " approved in $DEPLOYMENTS_FILE:" >&2
jq -r '.approvedPCRSets[]? | " PCR0=\(.pcr0)\n PCR1=\(.pcr1)\n PCR2=\(.pcr2)"' \
"$DEPLOYMENTS_FILE" >&2 || true
fi
echo "" >&2
echo "Until this is approved, registerKey and every TEE proof will revert." >&2
echo "Fix: OWNER_KEY=... just proof-approve-pcrs {{env}}" >&2
exit 1
fi
echo "Running enclave's PCR set is approved on ${NITRO_ATTESTATION_VERIFIER}."

# Phase 4 – Register the enclave's generated signing key on-chain.
# Execs into the running nitro-worker pod (which has vsock access to the
# enclave) and runs `nitro-worker register`, which fetches a public-key
Expand Down Expand Up @@ -704,7 +767,14 @@ proof-setup env="alphanet":
echo "=== Step 3b: Approving PCR set ===" >&2
just dry_run={{dry_run}} proof-approve-pcrs {{env}}

echo "=== Deploy phases 0a-3b complete. ===" >&2
# Verify rather than assume: re-measure the running enclave and confirm the allowlist
# actually accepts it. A dry run approves nothing, so there is nothing to verify.
if [ "{{dry_run}}" = "false" ]; then
echo "=== Step 3c: Verifying the running enclave's PCR set ===" >&2
just proof-verify-pcrs {{env}}
fi

echo "=== Deploy phases 0a-3c complete. ===" >&2
echo "The game is registered but not activated; run 'just proof-activate-system {{env}}' after readiness checks." >&2
echo "Next: register the enclave signing key (Phase 4) with 'just proof-register-key {{env}}'," >&2
echo " or run the worker with '--auto-register' so it self-registers on startup." >&2
6 changes: 6 additions & 0 deletions pkg/contracts/deployments/alphanet-nitro.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
"nitroEnclaveKeyRegistry": "0x4b3FE81C5b59f469622dBcA5FF2b2B1ABBC808a4",
"nitroProofVerifier": "0xaF6dbf1e90C47A4eDE64EF948b75800633Eb8839",
"approvedPCRSets": [
{
"verifier": "0x52AE16Ec4FBf14c0Ff9B06229C1cd2403b4A22d0",
"pcr0": "0x5354c41cc47717d3843e9aff37d5b3cbd35d03e219ecce5a1e5498773d4d06df2b22a022a96e542a292ff5c9f01f9a07",
"pcr1": "0x0343b056cd8485ca7890ddd833476d78460aed2aa161548e4e26bedf321726696257d623e8805f3f605946b3d8b0c6aa",
"pcr2": "0x44eb23fd7c4c12048b27a4fa3ed157c366603c5e1298f7aaeb184c4b4438040a283a5a112282c6439552faffbd56ec7c"
},
{
"verifier": "0x52AE16Ec4FBf14c0Ff9B06229C1cd2403b4A22d0",
"pcr0": "0xb94235a87364f955e068bc0493bf807c8c4bfa752893245a9117c90b444032187d6ae78b8eac89a527fd7b39878bc6f4",
Expand Down
29 changes: 29 additions & 0 deletions proofs/metrics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ pub const METRICS_PROOF_JOBS_CLAIMED: &str = "proof_jobs.claimed";
pub const METRICS_PROOF_JOBS_COMPLETED: &str = "proof_jobs.completed";
/// End-to-end worker proof-job attempt duration.
pub const METRICS_PROOF_JOB_DURATION_SECONDS: &str = "proof_job.duration_seconds";
/// Whether this worker's enclave signing key is registered on-chain.
pub const METRICS_ENCLAVE_KEY_REGISTERED: &str = "enclave_key.registered";
/// Enclave key registration attempts, by outcome.
pub const METRICS_ENCLAVE_REGISTRATION_ATTEMPTS: &str = "enclave_key.registration_attempts";

/// Registers shared metric descriptions.
pub fn describe_metrics() {
Expand Down Expand Up @@ -86,6 +90,31 @@ pub fn describe_metrics() {
metrics::Unit::Seconds,
"End-to-end worker proof-job attempt duration by backend and outcome."
);
metrics::describe_gauge!(
METRICS_ENCLAVE_KEY_REGISTERED,
metrics::Unit::Count,
"1 when this worker's enclave signing key is registered on-chain, 0 otherwise. \
A worker holding 0 leases no proof jobs, because proofs signed by an unregistered \
key do not verify."
);
metrics::describe_counter!(
METRICS_ENCLAVE_REGISTRATION_ATTEMPTS,
metrics::Unit::Count,
"Enclave key registration attempts by outcome (registered, already_registered, failed)."
);
}

/// Sets the enclave-key registration gauge.
///
/// Emitted eagerly at `0` on startup so the "never registered" case is a visible zero rather
/// than an absent series that a threshold monitor would silently ignore.
pub fn set_enclave_key_registered(registered: bool) {
metrics::gauge!(METRICS_ENCLAVE_KEY_REGISTERED).set(if registered { 1.0 } else { 0.0 });
}

/// Records an enclave key registration attempt and its outcome.
pub fn increment_enclave_registration_attempts(outcome: &'static str) {
metrics::counter!(METRICS_ENCLAVE_REGISTRATION_ATTEMPTS, "outcome" => outcome).increment(1);
}

/// Updates the latest finalized L2 block gauge.
Expand Down
10 changes: 8 additions & 2 deletions proofs/nitro/src/attestation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,14 +506,20 @@ pub fn leaf_cert_pubkey_xy(doc: &[u8]) -> Result<[u8; 96], AttestationError> {
)
})?;

let verifying_key = extract_p384_key(&cert_der)?;
cert_pubkey_xy(&cert_der)
}

/// Extracts any certificate's P-384 public key as the uncompressed `x || y` coordinate pair
/// (96 bytes, without the SEC1 `0x04` prefix).
pub fn cert_pubkey_xy(cert_der: &[u8]) -> Result<[u8; 96], AttestationError> {
let verifying_key = extract_p384_key(cert_der)?;
let point = verifying_key.to_encoded_point(false);
let bytes = point.as_bytes();

// Uncompressed SEC1 encoding is `0x04 || X (48) || Y (48)` = 97 bytes.
if bytes.len() != 97 || bytes[0] != 0x04 {
return Err(AttestationError::CertChain(format!(
"unexpected leaf public key encoding ({} bytes, prefix 0x{:02x})",
"unexpected public key encoding ({} bytes, prefix 0x{:02x})",
bytes.len(),
bytes.first().copied().unwrap_or(0)
)));
Expand Down
4 changes: 4 additions & 0 deletions proofs/nitro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ pub mod cose;
/// See [`p384_hints::collect_hints`] for the primary entry point.
pub mod p384_hints;

/// Pre-warming the on-chain `CertManager` cert cache so `registerKey` can verify the
/// attestation's certificate bundle. See [`prewarm::build_prewarm_plan`].
pub mod prewarm;

/// On-chain enclave key registration (calldata builder + self-registration flow).
#[cfg(all(feature = "enclave", target_os = "linux"))]
pub mod register;
Expand Down
Loading
Loading