Stargaze — private attestations for physical AI
Physical AI systems — drones, industrial robots, autonomous ground vehicles — emit continuous, sensitive telemetry: routes, sensor readings, task logs, performance metrics. Operators must prove claims about that telemetry to insurers, regulators, and autonomous counterparties. The problem is that proving a claim typically means handing over the evidence.
Stargaze is the privacy-first attestation layer that breaks this trade-off. It lets operators prove cryptographically that a claim is true — a UAV stayed within its corridor, a robot completed a task, a fleet met its SLA — without revealing the underlying telemetry.
Built natively on EAS (Ethereum Attestation Service) on Base, with a Groth16 ZK circuit tier for predicate proofs and optional TEE signing as a hardware root of trust.
geofence circuit and EAS schema definitions are production-ready; the trajectory and aggregate circuits are on the roadmap. SDK packages are in pre-release. Apply to the design-partner program →
Core concepts
Attestation
An attestation is a signed, on-chain record — created by an attester, bound to a schema, optionally targeted at a recipient. Stargaze uses EAS as its attestation primitive: a widely-deployed standard on Base with a maintained SDK and tooling.
Private attestation
An attestation in which the data bundle is stored off-chain (Stargaze: Arweave) and only its Merkle root (disclosureRoot) is written on-chain. The data is never published; the chain holds a tamper-evident commitment to it.
Selective disclosure
The attester later reveals any subset of the original fields to a chosen verifier by producing Merkle inclusion proofs against the disclosureRoot. The verifier checks the proofs against the on-chain root — no decryption, no full-bundle exposure.
ZK predicate proof
Selective disclosure reveals field values. ZK proofs go further: they prove a predicate over field values without revealing the values themselves. Stargaze's Groth16 circuits prove claims like "this trajectory stayed inside this corridor" with zero knowledge of the actual trajectory.
TEE signing
A Trusted Execution Environment binds telemetry to a specific hardware device at capture time, before any user-space software can tamper with it. The TEE produces a deterministic deviceId and a teeSignature over the data hash; both travel with the attestation as evidence of device-bound provenance.
Quick start
Install the SDK, connect a signer, and create your first private attestation. The snippet below uses pre-computed values for the device ID, corridor hash, and ZK proof hash — see the worked example for how those are produced.
TypeScriptimport { StargazeAttest, schemas } from '@stargaze/sdk';
import { JsonRpcProvider, Wallet } from 'ethers';
const provider = new JsonRpcProvider('https://mainnet.base.org');
const signer = new Wallet(process.env.PRIVATE_KEY!, provider);
const client = new StargazeAttest({
chain: 'base', // 'base' | 'base-sepolia'
signer,
storage: 'arweave', // off-chain store for private data
});
// Pre-computed inputs (see the Worked example for how these are produced)
const deviceId = '0x9c1a…'; // bytes32 from DeviceSigner (TEE)
const corridorHash = '0x4f8b…'; // Poseidon hash of the registered corridor
const zkProofHash = '0x71d2…'; // keccak256 of the accepted Groth16 proof
const insurerAddress = '0x1234…';
const { uid } = await client.attest({
schema: schemas.corridorCompliance,
recipient: insurerAddress,
private: true,
data: {
deviceId,
corridorId: corridorHash,
proofHash: zkProofHash,
verifierContract: process.env.GEOFENCE_VERIFIER_ADDRESS!,
flightStart: 1716_400_000_000n, // Unix ms
flightEnd: 1716_401_800_000n,
complianceStatus: true,
},
});
console.log('Attestation UID:', uid);
chain: 'base-sepolia' during development. EAS schema UIDs are deterministic and identical across testnet and mainnet; verifier contract addresses differ per network — keep them in env vars (see Network & contracts).
Installation
npm / pnpm / yarn# Core SDK
npm install @stargaze/sdk
# ZK circuit package — prover, witness generator, verification keys
npm install @stargaze/vault-circuits
# TEE device-signing helpers (platform-specific, optional)
npm install @stargaze/sdk-tee
Peer dependencies
| Package | Version | Notes |
|---|---|---|
| ethers | ^6.0 | or viem ^2.0 — either works |
| snarkjs | ^0.7 | required by @stargaze/vault-circuits |
| Node.js | ≥ 18 | native fetch, crypto.subtle |
Architecture
Stargaze is a three-tier privacy stack. Each tier adds an independent cryptographic guarantee; tiers can be deployed individually or composed end-to-end.
Verification flow
The full attestation lifecycle is six steps. Anyone holding the attestation UID can verify both the data integrity (via Merkle root) and the predicate (via the ZK proof event) — without trusting the attester to be honest about either.
- Sign — device captures telemetry; TEE produces
deviceIdand a signature over thedataHash. - Prove — SDK runs the relevant Groth16 circuit (e.g.
geofence) and submits the proof to the on-chain verifier. - ProofAccepted — on a valid proof, the verifier emits
ProofAccepted(bytes32 proofHash, address attester, bytes32 corridorHash, bytes32 trajectoryHash). This is the on-chain record of acceptance. - Attest — SDK builds the data bundle (including
proofHash), encrypts it to Arweave, computes the MerkledisclosureRoot, and submits the EAS attestation referencing the root. - Disclose — attester sends a selective disclosure (subset of fields + Merkle proofs) to the verifying party.
- Verify — the verifier checks (a) each Merkle proof against the on-chain root, and (b) that a
ProofAcceptedevent exists for the includedproofHash.
EAS layer
EAS is the on-chain attestation primitive. Stargaze registers schemas in the EAS SchemaRegistry and creates attestations against those schemas via the EAS contract on Base.
"Private" in Stargaze means the data bundle lives on Arweave and only the Merkle root is anchored on-chain. The on-chain attestation tx still happens — that is what gives the attestation a verifiable timestamp, schema, and attester address. Selective disclosure is a Stargaze convention layered on top of EAS, not a native EAS feature.
ZK circuit tier
The ZK tier enables predicate proofs — proving a statement about data without revealing the data. Circuits are written in Circom 2 and compiled to Groth16 via snarkjs. Verifier contracts are auto-generated and deployed to Base.
The verifier emits a ProofAccepted event with the proofHash (a deterministic hash of the proof + public signals). The EAS attestation references this hash. To verify the attestation's predicate claim, a third party queries the chain for a matching event — turning "I proved this" into a cheap, public lookup.
TEE layer
The TEE layer is the input root of trust. A device-resident Trusted Execution Environment (TPM, StrongBox, Nitro Enclave) signs the sensor data hash at capture time — before any user-space software can modify it. The resulting teeSignature and deterministic deviceId are committed inside the attestation's data bundle.
TEE signing is optional but strongly recommended: without it, an attester can fabricate sensor data and the rest of the stack will faithfully attest to the fabrication. See Trust model for the honest limits.
EAS Schema library
Stargaze registers a curated set of physical-AI schemas on EAS. All schemas share two structural conventions:
deviceId: bytes32— TEE-derived device identity; deterministic per hardware key.disclosureRoot: bytes32— Merkle root over the full attested data bundle, enabling selective disclosure of any field.
Schema UIDs are deterministic on EAS — computed as keccak256(schema || resolverAddress || revocableFlag) — and therefore identical on Base mainnet and Base Sepolia for the same definition.
stargaze.corridor-compliance.v1 Live
Proves a UAV or robot stayed within a defined operational corridor during a mission — without revealing the route. Backed by the geofence Groth16 circuit.
| Field | Type | Req | Description |
|---|---|---|---|
| deviceId | bytes32 | ✓ | TEE-derived hardware identity |
| corridorId | bytes32 | ✓ | Poseidon hash of the registered corridor polygon |
| proofHash | bytes32 | ✓ | Hash of the Groth16 proof + public signals accepted by GeofenceVerifier |
| verifierContract | address | ✓ | GeofenceVerifier address that emitted ProofAccepted |
| flightStart | uint64 | Mission start, Unix milliseconds | |
| flightEnd | uint64 | Mission end, Unix milliseconds | |
| complianceStatus | bool | true if proof verified on-chain | |
| disclosureRoot | bytes32 | ✓ | Merkle root over the full data bundle |
stargaze.task-completion.v1 Live
Attests that a robot completed a defined task with a verifiable outcome. The full evidence bundle (logs, sensor snapshots, actuator traces) is stored privately on Arweave; only its hash is committed.
| Field | Type | Req | Description |
|---|---|---|---|
| deviceId | bytes32 | ✓ | Hardware identity of executing device |
| taskId | bytes32 | ✓ | Keccak256 hash of the task definition |
| outcomeHash | bytes32 | ✓ | Hash of the outcome evidence bundle |
| completedAt | uint64 | ✓ | Completion timestamp, Unix milliseconds |
| success | bool | ✓ | Whether the task succeeded |
| evidenceRef | string | Arweave URI of the encrypted evidence bundle | |
| disclosureRoot | bytes32 | ✓ | Merkle root over the full data bundle |
stargaze.sensor-provenance.v1 Live
Chain-of-custody attestation for a sensor data bundle. The TEE signature binds the data hash to specific hardware at capture time, so downstream consumers can verify the data wasn't substituted post-capture.
| Field | Type | Req | Description |
|---|---|---|---|
| deviceId | bytes32 | ✓ | Hardware identity |
| sensorType | uint8 | ✓ | 0=GPS · 1=IMU · 2=LiDAR · 3=Camera · 4=Custom |
| dataHash | bytes32 | ✓ | Keccak256 of the encrypted data bundle |
| capturedAt | uint64 | ✓ | Capture timestamp, Unix milliseconds |
| teeSignature | bytes | ✓ | TEE signature over dataHash |
| storageRef | string | Arweave URI of the encrypted data bundle | |
| disclosureRoot | bytes32 | ✓ | Merkle root over the full data bundle |
stargaze.performance-sla.v1 Requires aggregate circuit
ZK-proven aggregate performance claim — fleet uptime, task success rate, latency P95 — over a reporting period. Individual unit metrics stay private; only the aggregate is attested.
| Field | Type | Req | Description |
|---|---|---|---|
| operatorId | bytes32 | ✓ | Operator identity — hash of operator address |
| period | uint64 | ✓ | Reporting period start, Unix epoch (seconds) |
| metricHash | bytes32 | ✓ | ZK-proven aggregate commitment |
| proofHash | bytes32 | ✓ | Hash of the aggregate Groth16 proof |
| slaTarget | uint16 | ✓ | Target in basis points — e.g. 9900 = 99.00% |
| met | bool | ✓ | Whether the SLA target was met |
| disclosureRoot | bytes32 | ✓ | Merkle root over the full data bundle |
Registration
Schemas are registered once per network via the EAS SchemaRegistry. Registration is permissionless — anyone can register a schema; Stargaze's published schemas use a fixed registrant address documented in Network & contracts.
import { SchemaRegistry } from '@ethereum-attestation-service/eas-sdk';
const registry = new SchemaRegistry(SCHEMA_REGISTRY_ADDRESS);
registry.connect(signer);
const schemaString =
'bytes32 deviceId,bytes32 corridorId,bytes32 proofHash,' +
'address verifierContract,uint64 flightStart,uint64 flightEnd,' +
'bool complianceStatus,bytes32 disclosureRoot';
const tx = await registry.register({
schema: schemaString,
resolverAddress: ethers.ZeroAddress, // optional resolver contract
revocable: true,
});
const schemaUID = await tx.wait(); // bytes32 UID, deterministic across chains
console.log('Schema UID:', schemaUID);
Versioning
Schemas evolve by registering new versions (v2, v3, …) rather than mutating v1. Old attestations remain valid against their original schema. Stargaze maintains both schemas and SDK helpers during a transition window of at least 6 months; deprecation is announced in the changelog and reflected in schemas. exports.
Naming convention: stargaze.. Major version bumps for breaking field changes, additive changes get a new schema rather than a backward-compatible patch (EAS schemas are immutable post-registration).
Revocation
All Stargaze schemas are registered with revocable: true. The original attester can revoke an attestation when:
- A device-key compromise is discovered — historical attestations from that key are repudiated.
- The attested predicate is later disproven (e.g. corridor definition was incorrect).
- A dispute resolves against the attester through the staking layer (
StargazeStaking, when deployed).
Revocation preserves the historical record: the attestation stays on-chain with an added revocationTime. Verifiers should always check revocation status before relying on an attestation; the SDK does this automatically in verifyAttestation().
await client.revoke(uid, {
reason: 'Device key rotated — replaced with attestation 0xabc…',
});
// Emits EAS Revoked event; subsequent verifyAttestation(uid) returns false
ZK Circuits
All circuits are written in Circom 2 and compiled to Groth16 via snarkjs. Each circuit ships with a proving key, verification key, an auto-generated Solidity verifier, and a TypeScript prover wrapper in @stargaze/vault-circuits.
The verifier wraps the snarkjs-generated verifyProof with a verifyAndRecord entry point that emits a ProofAccepted event on success — this is what the EAS attestation references via proofHash.
geofence.circom Live
Proves that a trajectory — a sequence of GPS waypoints — stayed entirely within a convex polygon corridor. The waypoints are private; only the corridor definition and a hash binding the proof to a specific trajectory are public.
Signal specification
| Signal | Visibility | Type | Description |
|---|---|---|---|
| trajectory | private | field[N][2] | Lat/lng pairs, scaled ×10⁶ (microdegrees as integers). N = MAX_WAYPOINTS. |
| nWaypoints | private | field | Actual count; remaining slots padded with the last valid waypoint. |
| corridorPoly | private | field[V][2] | Polygon vertices (V ≤ 32); used to derive constraints and to verify corridorHash in-circuit. |
| corridorHash | public | field | Poseidon hash of corridorPoly. Registered alongside the corridor definition; binds the proof to a specific corridor. |
| trajectoryHash | public | field | Poseidon hash of trajectory. Prevents proof reuse across routes. |
| compliant | output | field | 1 if every waypoint satisfies the half-plane constraints of corridorPoly, else 0. |
Performance
Trajectory circuit Q3 2026
Will prove properties of a continuous path without revealing the path: maximum speed at every segment, maximum heading change between adjacent segments, altitude window. Useful for "flew within speed and altitude limits" without exposing the route.
Planned public inputs: speedLimit, headingChangeLimit, altMin, altMax, trajectoryHash. Private inputs: 4-tuple per waypoint (lat, lng, alt, t).
Scaled-aggregate circuit Q3 2026
Will prove fleet-level aggregates — mean, P95, counts, sums — over a set of private scalar inputs. Enables the PerformanceSLA schema: an operator proves fleet uptime was above 99% without revealing which individual units were down.
Planned public inputs: aggregateCommitment (Poseidon hash of the input vector), slaTarget, aggregateValue. Private inputs: per-unit metric array.
Proof generation
TypeScriptimport { proveGeofence, hashCorridor, hashTrajectory } from '@stargaze/vault-circuits';
// 1. Pre-compute the corridor hash (once, when registering the corridor)
const corridorPoly = [
[51_500_000, -150_000], // [lat ×10^6, lng ×10^6]
[51_510_000, -150_000],
[51_510_000, -100_000],
[51_500_000, -100_000],
];
const corridorHash = hashCorridor(corridorPoly);
// 2. Hash the trajectory (binds the proof to this exact route)
const trajectory = [
[51_506_400, -127_800],
[51_506_750, -128_050],
[51_507_100, -127_600],
// … up to MAX_WAYPOINTS (256)
];
const trajectoryHash = hashTrajectory(trajectory);
// 3. Generate the proof
const { proof, publicSignals } = await proveGeofence({
trajectory, // private — never leaves the device
corridorPoly, // private (verified in-circuit against corridorHash)
corridorHash, // public
trajectoryHash, // public
});
// proof → { pi_a: [...], pi_b: [...], pi_c: [...] } (Groth16)
// publicSignals → [corridorHash, trajectoryHash, compliant]
On-chain verification
The verifier wraps the snarkjs-generated raw verifyProof with a verifyAndRecord entry point. verifyAndRecord emits a ProofAccepted event with the proofHash on success — this is the on-chain record that the attestation later references.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IGeofenceVerifier {
event ProofAccepted(
bytes32 indexed proofHash,
address indexed attester,
bytes32 corridorHash,
bytes32 trajectoryHash
);
/// @notice Verify and record a geofence compliance proof.
/// @return proofHash keccak256(abi.encode(a, b, c, input))
function verifyAndRecord(
uint[2] calldata a,
uint[2][2] calldata b,
uint[2] calldata c,
uint[3] calldata input // [corridorHash, trajectoryHash, compliant]
) external returns (bytes32 proofHash);
/// @notice Pure verification (no event, no state) — for off-chain checks.
function verifyProof(
uint[2] calldata a,
uint[2][2] calldata b,
uint[2] calldata c,
uint[3] calldata input
) external view returns (bool);
}
TypeScript — SDK helper
// Submit proof, wait for ProofAccepted, return proofHash
const { proofHash, txHash } = await client.verifyOnChain({
circuit: circuits.geofence,
proof,
publicSignals,
verifierContract: process.env.GEOFENCE_VERIFIER_ADDRESS!,
});
// `proofHash` is now ready to embed in the EAS attestation.
SDK Reference
@stargaze/sdk is the TypeScript client for the full attestation + proof pipeline. It wraps the EAS SDK, the vault-circuits prover, and Arweave storage behind a single composable API.
Client setup
TypeScript — viemimport { StargazeAttest } from '@stargaze/sdk';
import { createWalletClient, http } from 'viem';
import { base } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const walletClient = createWalletClient({ account, chain: base, transport: http() });
const client = new StargazeAttest({
chain: 'base', // 'base' | 'base-sepolia'
signer: walletClient, // ethers Wallet or viem WalletClient
storage: 'arweave', // 'arweave' (default) | 'ipfs'
arweave: {
host: 'arweave.net',
port: 443,
protocol: 'https',
key: arweaveJWK, // JWK for writes; omit for read-only
},
});
Creating attestations
client.attest() creates an EAS attestation. When private: true, the data bundle is serialized, Merkle-encoded, encrypted, stored on Arweave, and only the Merkle root is submitted on-chain.
const { uid, txHash, merkleRoot, storageRef } = await client.attest({
schema: schemas.corridorCompliance,
recipient: insurerAddress,
expirationTime: 0n, // 0n = no expiry
revocable: true,
private: true,
data: {
deviceId, // bytes32 from DeviceSigner
corridorId: corridorHash,
proofHash: zkProofHash,
verifierContract: process.env.GEOFENCE_VERIFIER_ADDRESS!,
flightStart: BigInt(missionStartMs),
flightEnd: BigInt(missionEndMs),
complianceStatus: true,
},
});
// uid → EAS attestation UID (bytes32)
// txHash → Base transaction hash
// merkleRoot → on-chain Merkle root (= disclosureRoot in the data bundle)
// storageRef → Arweave URI of the encrypted bundle
Generating proofs
TypeScriptimport { circuits } from '@stargaze/sdk';
// 1. Compute hashes (the SDK uses the same Poseidon impl as the circuit)
const corridorHash = client.hashCorridor(corridorPoly);
const trajectoryHash = client.hashTrajectory(waypoints);
// 2. Generate the Groth16 proof
const { proof, publicSignals } = await client.prove({
circuit: circuits.geofence,
inputs: {
trajectory: waypoints, // private
corridorPoly, // private (verified in-circuit)
corridorHash, // public
trajectoryHash, // public
},
});
// 3. Submit to the on-chain verifier, get proofHash from the ProofAccepted event
const { proofHash } = await client.verifyOnChain({
circuit: circuits.geofence,
proof,
publicSignals,
verifierContract: process.env.GEOFENCE_VERIFIER_ADDRESS!,
});
// `proofHash` is what goes into the attestation's `proofHash` field
Verification
TypeScript// Local proof verification (no gas, no tx — for testing)
const localOk = await client.verifyLocal({
circuit: circuits.geofence,
proof,
publicSignals,
});
// Full attestation verification:
// 1. fetch attestation from EAS
// 2. check revocation status
// 3. confirm a ProofAccepted event exists for attestation.proofHash
// 4. (if disclosures provided) verify Merkle proofs against disclosureRoot
const result = await client.verifyAttestation(uid, {
disclosure, // optional — verifies Merkle proofs too
});
// result: {
// valid: boolean,
// revoked: boolean,
// proofAccepted: boolean,
// disclosureValid: boolean | null, // null if no disclosure supplied
// attestation: { ... }, // raw EAS attestation
// }
Selective disclosure
Reveal a subset of fields from a private attestation. The result is a self-contained object that any party can verify against the on-chain Merkle root — no SDK required on the verifier side beyond a Base RPC.
TypeScript — attester// Disclose only compliance status + flight window
const disclosure = await client.disclose(uid, [
'complianceStatus',
'flightStart',
'flightEnd',
]);
// disclosure = {
// fields: { complianceStatus: true, flightStart: 1716_400_000_000n, … },
// proofs: { complianceStatus: MerkleProof, flightStart: …, flightEnd: … },
// root: '0x4f…', // matches on-chain disclosureRoot
// uid: '0xabc…',
// }
// Send `disclosure` to the verifier (insurer, regulator, etc.)
TypeScript — verifier
import { verifyDisclosure } from '@stargaze/sdk/verify';
const valid = await verifyDisclosure({
disclosure,
provider: new JsonRpcProvider('https://mainnet.base.org'),
});
// Checks each field's Merkle proof against the on-chain disclosureRoot
// for the attestation UID. Returns true iff all proofs verify and the
// attestation is not revoked.
TEE Integration
The @stargaze/sdk-tee package wraps platform-specific Trusted Execution Environments behind a single DeviceSigner interface. Its output — a deterministic deviceId and a teeSignature over the data hash — is consumed by the main SDK when building attestations.
Supported platforms
| Platform | TEE | Status |
|---|---|---|
| Android (API 28+) | Android Keystore + StrongBox | Supported |
| Linux x86_64 | TPM 2.0 (tpm2-tools) | Supported |
| AWS Nitro Enclaves | NSM SDK | Supported |
| Intel TDX | DCAP remote attestation | In progress |
| Custom hardware | ITEESigner interface | BYO |
Device signing
TypeScript — TPM 2.0 (Linux)import { DeviceSigner } from '@stargaze/sdk-tee';
const signer = await DeviceSigner.fromTPM({
keyHandle: '/tpm/stargaze-device-key',
pcrs: [0, 7], // PCRs to bind into the quote (firmware, secure boot)
});
const { deviceId, signature, quote } = await signer.sign(dataHash);
// deviceId → bytes32, deterministic from the TPM-resident EK
// signature → bytes, ECDSA over dataHash using the device-bound key
// quote → bytes, raw TPM2_Quote — store with the evidence bundle
TypeScript — AWS Nitro Enclave
import { DeviceSigner } from '@stargaze/sdk-tee';
import { randomBytes } from 'node:crypto';
const signer = await DeviceSigner.fromNitro({
nsmDevicePath: '/dev/nsm',
nonce: randomBytes(32), // included in the NSM attestation doc
});
const { deviceId, signature, quote } = await signer.sign(dataHash);
// `quote` is the raw NSM attestation CBOR — verifiable against AWS root CA.
Quote verification
The teeSignature alone proves "someone with the device key signed this." A TEE quote goes further — it proves the signing key resides in a genuine, unmodified secure environment. Quotes ship inside the encrypted evidence bundle (not in the on-chain attestation) and are verified by the third-party recipient before they trust the deviceId.
| Quote format | Verifier trust anchor |
|---|---|
| TPM 2.0 Quote | Device EK certificate chain → vendor root (Infineon, Nuvoton, …) |
| Android Key Attestation | x.509 chain → Google hardware-attestation root |
| AWS NSM Attestation Doc | COSE_Sign1 → AWS Nitro root certificate |
| Intel TDX DCAP | Intel PCS / collateral chain |
import { verifyQuote } from '@stargaze/sdk-tee/verify';
const result = await verifyQuote({
format: 'tpm2', // 'tpm2' | 'android' | 'nitro' | 'tdx'
quote,
expectedDeviceId: deviceId,
expectedPcrs: { 0: pcr0Expected, 7: pcr7Expected }, // policy
});
// result: { valid, vendor, firmwareVersion, attestedAt, pcrs }
Custom TEE
Implement the ITEESigner interface to integrate any hardware enclave or key management system:
interface ITEESigner {
/**
* Sign a data hash using the device-resident TEE.
* @param dataHash 32-byte hash to sign
* @returns deviceId (32 bytes), signature, optional TEE quote
*/
sign(dataHash: Uint8Array): Promise<{
deviceId: Uint8Array; // 32 bytes — stable device identifier
signature: Uint8Array; // variable length
quote?: Uint8Array; // optional raw TEE attestation quote
}>;
}
const signer = DeviceSigner.fromCustom(myTEEImpl);
Worked example
End-to-end: a delivery-drone operator proves to their insurer that flight #4711 stayed inside its assigned corridor — without revealing the route. Every variable name in this example matches the rest of the docs.
Step 1 — Pre-flight (one-time corridor registration)
TypeScript// Run once, at corridor registration time, off-chain.
import { hashCorridor } from '@stargaze/vault-circuits';
const corridorPoly = [
[51_500_000, -150_000],
[51_510_000, -150_000],
[51_510_000, -100_000],
[51_500_000, -100_000],
];
const corridorHash = hashCorridor(corridorPoly);
// → store corridorHash in your fleet management system; share with the insurer.
Step 2 — During flight (on the drone)
The drone's flight controller captures GPS waypoints at 1 Hz. The on-device TEE signs the rolling waypoint buffer at each capture; the resulting deviceId and per-capture signatures are stored alongside the raw waypoints.
Step 3 — Post-flight (on the operator's machine)
TypeScriptimport { StargazeAttest, schemas, circuits } from '@stargaze/sdk';
import { DeviceSigner } from '@stargaze/sdk-tee';
import { Wallet, JsonRpcProvider } from 'ethers';
const provider = new JsonRpcProvider('https://mainnet.base.org');
const signer = new Wallet(process.env.PRIVATE_KEY!, provider);
const client = new StargazeAttest({ chain: 'base', signer, storage: 'arweave' });
// 3a. Load the captured waypoints + the TEE signature over the final dataHash
const waypoints = loadWaypointsFromFlight('flight-4711'); // private
const missionStartMs = waypoints[0].t;
const missionEndMs = waypoints.at(-1).t;
const dataHash = client.hashWaypoints(waypoints);
const tee = await DeviceSigner.fromTPM({ keyHandle: '/tpm/drone-key', pcrs: [0, 7] });
const { deviceId, signature: teeSignature, quote } = await tee.sign(dataHash);
// 3b. Generate the geofence ZK proof
const trajectoryHash = client.hashTrajectory(waypoints);
const { proof, publicSignals } = await client.prove({
circuit: circuits.geofence,
inputs: { trajectory: waypoints, corridorPoly, corridorHash, trajectoryHash },
});
// 3c. Submit the proof to the on-chain verifier — get the proofHash
const { proofHash } = await client.verifyOnChain({
circuit: circuits.geofence,
proof,
publicSignals,
verifierContract: process.env.GEOFENCE_VERIFIER_ADDRESS!,
});
// 3d. Create the private EAS attestation
const { uid } = await client.attest({
schema: schemas.corridorCompliance,
recipient: insurerAddress,
private: true,
data: {
deviceId,
corridorId: corridorHash,
proofHash,
verifierContract: process.env.GEOFENCE_VERIFIER_ADDRESS!,
flightStart: BigInt(missionStartMs),
flightEnd: BigInt(missionEndMs),
complianceStatus: true,
},
// The full bundle (waypoints, teeSignature, quote) is encrypted and stored
// on Arweave; only the Merkle root reaches the chain.
attachments: { waypoints, teeSignature, quote },
});
console.log('Flight 4711 attestation:', uid);
Step 4 — Disclosure to the insurer
TypeScript — operator sideconst disclosure = await client.disclose(uid, [
'complianceStatus',
'flightStart',
'flightEnd',
]);
// Send `disclosure` to the insurer over any channel (email, S3, signed message).
Step 5 — Insurer verification
TypeScript — insurer sideimport { StargazeAttest, verifyDisclosure } from '@stargaze/sdk';
import { JsonRpcProvider } from 'ethers';
const provider = new JsonRpcProvider('https://mainnet.base.org');
const client = new StargazeAttest({ chain: 'base', provider }); // read-only
const result = await client.verifyAttestation(uid, { disclosure });
if (!result.valid) throw new Error('Attestation invalid');
if (result.revoked) throw new Error('Attestation has been revoked');
if (!result.proofAccepted) throw new Error('No matching ProofAccepted event');
if (!result.disclosureValid) throw new Error('Disclosure Merkle proofs failed');
console.log('Flight was compliant:', disclosure.fields.complianceStatus);
console.log('Mission window:', disclosure.fields.flightStart, '→', disclosure.fields.flightEnd);
// Insurer now has cryptographic proof of compliance + a verifiable mission
// window, without ever seeing a single GPS waypoint.
Trust model
Stargaze's security model is layered, and honest about where each layer's guarantees end. We do not claim "verifiable inference" or perfect hardware attestation.
What ZK proofs guarantee
A Groth16 proof that verifies on-chain is sound: no computationally bounded adversary can produce a valid proof for a false statement. If GeofenceVerifier.verifyAndRecord() emits ProofAccepted, the submitted trajectory hash satisfies the circuit constraints for the given corridor.
What TEE signing provides
TEE signing binds the data hash to a specific hardware key established at manufacture or provisioning. An attacker who doesn't control the physical device cannot forge a valid teeSignature for a fabricated hash — raising the cost of fraud from software to physical hardware compromise.
DeviceSigner falls back gracefully when a TEE is unavailable but the attestation should be flagged as non-TEE in that case.What EAS anchoring provides
An EAS attestation is an immutable on-chain record: timestamped by Base, signed by a specific attester address, bound to a schema. It cannot be back-dated or altered after creation. The disclosureRoot commits to the full data bundle at attestation time, and proofHash commits to a specific accepted ZK proof.
complianceStatus requires a matching ProofAccepted event, but the attester can still lie about which corridor was the "correct" one).Economic deterrence
The StargazeStaking contract (optional, pre-deployment) allows operators to post a slashable bond. A dispute mechanism — built on top of the schemas — can slash the bond if a false attestation is proven (e.g. an authorized regulator publishes counter-evidence). This complements the cryptographic guarantees with a financial deterrent calibrated to the stakes of the attestation.
What Stargaze cannot claim
- It cannot prove that sensor hardware was functioning correctly or was properly calibrated.
- It cannot prove that TEE firmware was uncompromised at signing time.
- It is not a substitute for physical inspection, regulatory audit, or insurance underwriting.
- ZK proofs over GPS coordinates do not authenticate GPS signals — receiver-side spoofing is a separate threat vector that requires anti-spoofing hardware or multi-constellation cross-checks.
Network & contracts
Base Mainnet (chain ID 8453)
| Contract | Address |
|---|---|
| EAS | 0x4200000000000000000000000000000000000021 |
| SchemaRegistry | 0x4200000000000000000000000000000000000020 |
| GeofenceVerifier | deployment pending |
| corridor-compliance schema UID | registration pending |
| task-completion schema UID | registration pending |
| sensor-provenance schema UID | registration pending |
Base Sepolia (chain ID 84532)
| Contract | Address |
|---|---|
| EAS | 0x4200000000000000000000000000000000000021 |
| SchemaRegistry | 0x4200000000000000000000000000000000000020 |
| GeofenceVerifier | deployable via forge script — see below |
SchemaRegistry.register derives the UID deterministically from the schema string + resolver + revocable flag, a schema registered with identical parameters on Sepolia and Mainnet shares one UID. Verifier contract addresses do not — keep them in environment variables.
Deploying GeofenceVerifier to Base Sepolia
Shellforge script scripts/DeployVerifier.s.sol:DeployGeofenceVerifier \
--rpc-url https://sepolia.base.org \
--broadcast \
--verify \
--etherscan-api-key $BASESCAN_API_KEY \
--private-key $PRIVATE_KEY
Gas & storage costs
Indicative costs on Base mainnet (≈ $0.10 / Mgas at typical gas price). Storage costs assume the encrypted bundle is 1–3 KB compressed.
| Operation | Gas | USD est. |
|---|---|---|
| Schema registration (one-time) | ~140k | $0.014 |
| GeofenceVerifier deploy (one-time) | ~1.4M | $0.14 |
| verifyAndRecord (geofence proof) | ~285k | $0.029 |
| Create attestation (private) | ~85k | $0.009 |
| Revoke attestation | ~50k | $0.005 |
| Arweave storage (per attestation) | — | $0.005 – $0.02 |
End-to-end per-flight cost (proof + attestation + storage): ~$0.04. Schema and verifier deploys are one-time amortized costs.
Design partners
Stargaze is working with a small number of industrial and logistics operators in the design-partner phase. We're looking for fleets where a compliance, performance, or provenance claim needs to be shared with a third party — insurer, regulator, settlement contract, or supply-chain counterparty — without exposing the underlying data.
What design partners get
- Direct access to the engineering team throughout your pilot.
- Custom schema design for your specific compliance or disclosure requirements.
- Early access to SDK builds and circuit updates.
- A full attestation pilot on your own telemetry — from TEE signing to on-chain proof.
- Co-authorship on the use-case writeup (anonymized by default).
Good fit if you operate
- UAV or drone fleets subject to corridor, altitude, or no-fly-zone compliance requirements.
- Autonomous ground vehicles or industrial robots with task-completion SLAs.
- DePIN nodes that need to prove coverage, uptime, or signal quality without revealing location.
- Any fleet where sensor data is sensitive IP but compliance must be demonstrable.
Apply at stargaze.cc/demo or email enquiries@stargaze.cc. We respond to every qualified request within two business days.