Verify the Enclaves
The Nuntius oracle and the Pontifex bridge sign with keys that exist only inside AWS Nitro Enclaves. You can check what runs there yourself. On Robinhood Chain, four checks connect the published source code to the signatures the contracts accept:
| Check | Question it answers | Tooling |
|---|---|---|
| 1. Rebuild | Does the published source produce the measurements (PCR0, PCR1, PCR2) that are pinned on-chain? | Docker, nitro-cli |
| 2. Attest | Is a real Nitro enclave running an image with those measurements right now, and which key does it hold? | Python |
| 3. Pin | Does the contract only admit signers attested for that image? | cast |
| 4. Signers | Is the key from the attestation the only one the contract accepts? | cast |
- “An enclave with these PCRs holds this public key”
expectedPcr0()- verify every price update and claim against that signer
Live Values
Robinhood Chain (chain ID 4663). The bridge and the oracle are built from the same public tree, Kaskad-Lending/kaskad-pontifex-enclave.
| Oracle (Nuntius) | Bridge (Pontifex) | |
|---|---|---|
| Dockerfile | Dockerfile.oracle | Dockerfile.pontifex |
| PCR0 | 5248a0d0ca1bea3044a270078286a7f788dfba98ada7069bdc4db81407e6a3a65c5be8f76e3cd299571621d71cc02296 | 8761b33a338db4fab12c9201eef87e1c582310d59645848c2574061d1e070d71238976ea1af05ad4b4e645004621aac6 |
| PCR1 | 4b4d5b3661b3efc12920900c80e126e4ce783c522de6c02a2a5bf7af3a2b9327b86776f188e4be1c1c404a129dbda493 | 4b4d5b3661b3efc12920900c80e126e4ce783c522de6c02a2a5bf7af3a2b9327b86776f188e4be1c1c404a129dbda493 |
| PCR2 | cb935649ecdfcff7944cd891ae8de97b91c1fd252faa38547c68eb8a447a0acee3ef3244309d38673df49cefce5c4a46 | 053d5d61b98e960c777ea280b7b9550951d3b105689f23bb63d25a2afaf0ff3823ad240aff39f34437289e2761a4a2c1 |
| Pinned in | KaskadPriceOracleV2 0xB4922b744cc395E05Cc962d55FA63FBab0599ec2 | KskdEntry 0x7b1D080d2485E63791468d73418e2B262c25C402 |
| Signer | 0xE4D88bE9756EB3fc5bD28B81642605FBF238d2c9 | 0x7d3B23a631501a35cafe4F00133653A1b90f2CC3 |
| Attestation | https://nuntius.kaskad.live/attestation | https://nuntius.kaskad.live/bridge/attestation |
PCR0 measures the whole enclave image, PCR1 the Linux kernel and bootstrap, PCR2 the application. The contracts store the first 32 of the 48 PCR0 bytes.
These values change when an image is upgraded. The live attestation in step 2 shows the image that runs now.
1. Rebuild the Images
You need x86_64 Linux and Docker, but no Nitro hardware and no AWS account. Use the default builder with the docker driver and the classic image store. The repository README notes that the containerd image store and the docker-container BuildKit driver build the same binary but give a different PCR2, and therefore a different PCR0.
docker buildx inspect default | grep Driver # Driver: docker
docker info --format '{{.Driver}}' # overlay2The build arguments come from host/eif-config/robinhood-mainnet.env in the repository. They hold only public values, such as contract addresses and RPC URLs, and are part of what PCR0 measures.
git clone https://github.com/Kaskad-Lending/kaskad-pontifex-enclave && cd kaskad-pontifex-enclave
git checkout 79c81b806232c325b3b4b01c9d47cb8713e71063 # the commit of the live images
( set -a; . host/eif-config/robinhood-mainnet.env; set +a
docker build --builder default --no-cache -f Dockerfile.pontifex -t pontifex-bridge \
--build-arg SOURCE_DATE_EPOCH=1700000000 \
--build-arg PONTIFEX_EXIT --build-arg PONTIFEX_KSKD --build-arg PONTIFEX_ENTRY \
--build-arg PONTIFEX_CHAIN_ID --build-arg PONTIFEX_IGRA_RPCS \
--build-arg PONTIFEX_VERSION --build-arg PONTIFEX_ANCESTOR_PCRS .
docker build --builder default --no-cache -f Dockerfile.oracle -t oracle-enclave \
--build-arg SOURCE_DATE_EPOCH=1700000000 \
--build-arg KEYEX_ORACLE_REGISTRY --build-arg KEYEX_ORACLE_RH_RPCS --build-arg KEYEX_ORACLE_SAFE \
--build-arg KEYEX_ORACLE_OWNERS --build-arg KEYEX_ORACLE_THRESHOLD --build-arg KEYEX_ORACLE_CHAIN_ID \
--build-arg KEYEX_ORACLE_VERSION --build-arg KEYEX_ORACLE_ANCESTORS --build-arg KEYEX_ORACLE_PEERS . )Measure both images with nitro-cli 1.5.0 from the Amazon Linux 2023 repository. Each command prints a JSON object with PCR0, PCR1 and PCR2:
mkdir -p out
docker run --rm --security-opt label=disable \
-v /var/run/docker.sock:/var/run/docker.sock -v "$PWD/out:/out" \
-e NITRO_CLI_ARTIFACTS=/tmp/nca public.ecr.aws/amazonlinux/amazonlinux:2023 bash -c '
set -e
dnf install -y aws-nitro-enclaves-cli-1.5.0-0.amzn2023 aws-nitro-enclaves-cli-devel-1.5.0-0.amzn2023 >/dev/null
mkdir -p /var/log/nitro_enclaves /tmp/nca
nitro-cli build-enclave --docker-uri pontifex-bridge --output-file /out/pontifex.eif
nitro-cli build-enclave --docker-uri oracle-enclave --output-file /out/oracle.eif'{
"Measurements": {
"HashAlgorithm": "Sha512_48 { .. }",
"PCR0": "8761b33a338db4fab12c9201eef87e1c582310d59645848c2574061d1e070d71238976ea1af05ad4b4e645004621aac6",
"PCR1": "4b4d5b3661b3efc12920900c80e126e4ce783c522de6c02a2a5bf7af3a2b9327b86776f188e4be1c1c404a129dbda493",
"PCR2": "053d5d61b98e960c777ea280b7b9550951d3b105689f23bb63d25a2afaf0ff3823ad240aff39f34437289e2761a4a2c1"
}
}{
"Measurements": {
"HashAlgorithm": "Sha512_48 { .. }",
"PCR0": "5248a0d0ca1bea3044a270078286a7f788dfba98ada7069bdc4db81407e6a3a65c5be8f76e3cd299571621d71cc02296",
"PCR1": "4b4d5b3661b3efc12920900c80e126e4ce783c522de6c02a2a5bf7af3a2b9327b86776f188e4be1c1c404a129dbda493",
"PCR2": "cb935649ecdfcff7944cd891ae8de97b91c1fd252faa38547c68eb8a447a0acee3ef3244309d38673df49cefce5c4a46"
}
}All three PCRs of each image must equal the live values. On 21 October 2026 this recipe, run on commit 79c81b8, reproduced all three PCRs of both images exactly.
Compare PCRs, not the hash of the .eif file. The EIF records its build time outside the measured sections, so the file hash changes on every build while the PCRs do not.
2. Verify the Live Attestation
Each enclave publishes a Nitro attestation document: a COSE_Sign1 structure signed by the Nitro hypervisor through a certificate chain that ends at the AWS Nitro Enclaves root certificate. It carries the PCRs of the running image and the enclave's secp256k1 public key.
The script below fetches a document, checks the certificate chain against the root fingerprint published by AWS, checks the COSE signature, and derives the Ethereum address of the enclave key.
"""Verify a Nitro attestation from a Kaskad enclave endpoint; print PCRs and the signer address."""
import datetime
import hashlib
import json
import sys
import urllib.request
from urllib.parse import parse_qs, urlparse
import cbor2
from Crypto.Hash import keccak
from cryptography import x509
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
# SHA-256 of the AWS Nitro Enclaves root certificate, as published by AWS:
# https://docs.aws.amazon.com/enclaves/latest/user/verify-root.html
AWS_NITRO_ROOT_SHA256 = "641a0321a3e244efe456463195d606317ed7cdcc3c1756e09893f3c68f79bb5b"
COSE_ALG_ES384 = -35
def fail(msg: str) -> None:
sys.exit(f"FAIL: {msg}")
url = sys.argv[1] if len(sys.argv) > 1 else "https://nuntius.kaskad.live/attestation"
# The bridge endpoint signs a caller-chosen ?nonce= into the document, which proves it is fresh.
expected_nonce = parse_qs(urlparse(url).query).get("nonce", [None])[0]
req = urllib.request.Request(url, headers={"User-Agent": "nuntius-verify/1"})
body = json.load(urllib.request.urlopen(req, timeout=20))
# The oracle serves "attestation_doc", the bridge serves "attestation".
doc = bytes.fromhex((body.get("attestation_doc") or body["attestation"]).removeprefix("0x"))
cose = cbor2.loads(doc)
protected, _unprotected, payload, signature = cose.value if isinstance(cose, cbor2.CBORTag) else cose
if cbor2.loads(protected).get(1) != COSE_ALG_ES384:
fail("attestation is not signed with ES384")
att = cbor2.loads(payload)
signed_at = datetime.datetime.fromtimestamp(att["timestamp"] / 1000, datetime.timezone.utc)
# 1. Certificate chain: AWS root -> intermediates -> enclave certificate.
if hashlib.sha256(att["cabundle"][0]).hexdigest() != AWS_NITRO_ROOT_SHA256:
fail("chain does not start at the AWS Nitro root")
chain = [x509.load_der_x509_certificate(c) for c in att["cabundle"]]
leaf = x509.load_der_x509_certificate(att["certificate"])
for parent, child in zip(chain, chain[1:] + [leaf]):
try:
parent.public_key().verify(child.signature, child.tbs_certificate_bytes,
ec.ECDSA(child.signature_hash_algorithm))
except InvalidSignature:
fail(f"broken certificate chain at {child.subject.rfc4514_string()}")
if not child.not_valid_before_utc <= signed_at <= child.not_valid_after_utc:
fail(f"certificate not valid at {signed_at}")
# 2. COSE_Sign1 signature over the document, by the enclave certificate.
sig_structure = cbor2.dumps(["Signature1", protected, b"", payload])
r, s = int.from_bytes(signature[:48], "big"), int.from_bytes(signature[48:], "big")
try:
leaf.public_key().verify(encode_dss_signature(r, s), sig_structure, ec.ECDSA(hashes.SHA384()))
except InvalidSignature:
fail("document signature does not verify")
# 3. The enclave's secp256k1 public key -> Ethereum address.
pub = att["public_key"]
if len(pub) != 65 or pub[0] != 4:
fail("public_key is not an uncompressed secp256k1 key")
address = "0x" + keccak.new(digest_bits=256, data=pub[1:]).hexdigest()[-40:]
# 4. Freshness, when a nonce was requested.
nonce = (att.get("nonce") or b"").hex()
if expected_nonce is not None and nonce != expected_nonce.lower():
fail("document does not carry the requested nonce")
print("signed at ", signed_at.isoformat())
for i in (0, 1, 2):
print(f"PCR{i} ", att["pcrs"][i].hex())
print("PCR0 first 32 B 0x" + att["pcrs"][0][:32].hex())
print("signer address ", address)
print("matches API ", address == body["signer"].lower())
print("nonce ", nonce or "none")pip install cbor2 cryptography pycryptodome
python verify_attestation.py # oracle
python verify_attestation.py "https://nuntius.kaskad.live/bridge/attestation?nonce=$(openssl rand -hex 32)"signed at 2026-10-21T11:57:34.924000+00:00
PCR0 5248a0d0ca1bea3044a270078286a7f788dfba98ada7069bdc4db81407e6a3a65c5be8f76e3cd299571621d71cc02296
PCR1 4b4d5b3661b3efc12920900c80e126e4ce783c522de6c02a2a5bf7af3a2b9327b86776f188e4be1c1c404a129dbda493
PCR2 cb935649ecdfcff7944cd891ae8de97b91c1fd252faa38547c68eb8a447a0acee3ef3244309d38673df49cefce5c4a46
PCR0 first 32 B 0x5248a0d0ca1bea3044a270078286a7f788dfba98ada7069bdc4db81407e6a3a6
signer address 0xe4d88be9756eb3fc5bd28b81642605fbf238d2c9
matches API True
nonce nonesigned at 2026-10-21T11:57:36.736000+00:00
PCR0 8761b33a338db4fab12c9201eef87e1c582310d59645848c2574061d1e070d71238976ea1af05ad4b4e645004621aac6
PCR1 4b4d5b3661b3efc12920900c80e126e4ce783c522de6c02a2a5bf7af3a2b9327b86776f188e4be1c1c404a129dbda493
PCR2 053d5d61b98e960c777ea280b7b9550951d3b105689f23bb63d25a2afaf0ff3823ad240aff39f34437289e2761a4a2c1
PCR0 first 32 B 0x8761b33a338db4fab12c9201eef87e1c582310d59645848c2574061d1e070d71
signer address 0x7d3b23a631501a35cafe4f00133653a1b90f2cc3
matches API True
nonce 8e8ef8654ee699db8fadca22ae91bdaea95696cbecf074c1f8dd8b135f14e0c2- Freshness. The bridge endpoint signs a nonce you choose into the document, which proves the document was produced after you chose it. Use a new random nonce for each request. The oracle endpoint does not take a nonce and serves a document cached for up to five minutes, so check its
signed attime instead. - Tampering. The signature covers the PCRs, the public key and the nonce. Flipping a single bit of PCR0 in a saved document makes the script stop with
FAIL: document signature does not verify.
3. Compare PCR0 With the On-chain Pin
RH=https://rpc.mainnet.chain.robinhood.com
cast call 0xB4922b744cc395E05Cc962d55FA63FBab0599ec2 "expectedPcr0()(bytes32)" --rpc-url $RH # oracle
cast call 0x7b1D080d2485E63791468d73418e2B262c25C402 "expectedPcr0()(bytes32)" --rpc-url $RH # bridge0x5248a0d0ca1bea3044a270078286a7f788dfba98ada7069bdc4db81407e6a3a6
0x8761b33a338db4fab12c9201eef87e1c582310d59645848c2574061d1e070d71Each value equals the first 32 bytes of the PCR0 from steps 1 and 2.
4. Confirm the Signers
cast call 0xB4922b744cc395E05Cc962d55FA63FBab0599ec2 "signerCount()(uint256)" --rpc-url $RH # oracle → 1
cast call 0xB4922b744cc395E05Cc962d55FA63FBab0599ec2 "isValidSigner(address)(bool)" \
0xe4d88be9756eb3fc5bd28b81642605fbf238d2c9 --rpc-url $RH # oracle → true
cast call 0x7b1D080d2485E63791468d73418e2B262c25C402 "signerCount()(uint256)" --rpc-url $RH # bridge → 1
cast call 0x7b1D080d2485E63791468d73418e2B262c25C402 "validSigner(address)(bool)" \
0x7d3B23a631501a35cafe4F00133653A1b90f2CC3 --rpc-url $RH # bridge → trueWhen signerCount() equals the number of signers you verified by attestation, and each of them is valid, the contract accepts no other key.
Igra
For the Igra oracle, run steps 2 to 4:
python verify_attestation.py https://kaskad.live/enclave/attestation # US
python verify_attestation.py https://kaskad.live/enclave-eu/attestation # EU
IGRA=https://rpc.igralabs.com:8545
ORACLE=0xB01993906D14792DC21B7EA75A971D8191AAE402
cast call $ORACLE "expectedPCR0()(bytes32)" --rpc-url $IGRA
cast call $ORACLE "signerCount()(uint256)" --rpc-url $IGRA # 2
cast call $ORACLE "isValidSigner(address)(bool)" 0x544705F5d72E1C6f24bFD7C2E176F2Cd2aA8Dc2A --rpc-url $IGRA # US → true
cast call $ORACLE "isValidSigner(address)(bool)" 0xD35e55c7C2d10472e6d9E7132c88BEeD347D91A7 --rpc-url $IGRA # EU → trueBoth documents carry PCR0 2e66fc4f74006323675a397ab3c6b6eab68f2ccc36f4cf10bb4d0e5c82b2106c097e828df9d8723d8fd0021fc9bc482f, and expectedPCR0() returns its first 32 bytes. On Igra this value is fixed at deployment.
What This Proves and What It Does Not
It proves that each key these contracts accept was held, when the attestation was signed, by an enclave running the image you checked. On Robinhood Chain that image is built from the public source. It keeps the key in enclave memory and hands it only to other attested enclaves, so the operators of the host machines cannot read it.
It does not prove:
- That the code is correct. Verification ties signatures to the source; reviewing what the source does is a separate step.
- Independence from AWS. The root of trust is the AWS Nitro hypervisor and the AWS Nitro root certificate. The checks assume both are sound.
- Liveness. A host operator can stop or delay an enclave. That halts updates or claims; it cannot forge them.

