Skip to main content
Research preview — The proofs module is under active development.

Location proofs

The ProofsModule handles proof construction and multidimensional verification. A location proof bundles a claim (“I was at location X at time T”) with one or more stamps (evidence from proof-of-location systems). Verification produces a CredibilityVector — not a single score.
import { AstralSDK } from '@decentralized-geo/astral-sdk';

const astral = new AstralSDK({
  chainId: 84532,
  signer: wallet,
  apiUrl: 'https://api.astral.global'
});

// Access via astral.proofs
astral.proofs.create(claim, stamps);
astral.proofs.verify(proof, options);
For conceptual background, see Location Proofs.

proofs.create()

Bundle a claim with stamps into a location proof. This is a synchronous operation.
astral.proofs.create(
  claim: LocationClaim,
  stamps: LocationStamp[]
): LocationProof

Parameters

ParameterTypeRequiredDescription
claimLocationClaimYesThe location claim to prove
stampsLocationStamp[]YesOne or more signed stamps as evidence
Requires at least one stamp.

Example

const claim: LocationClaim = {
  lpVersion: '0.2',
  locationType: 'geojson-point',
  location: { type: 'Point', coordinates: [-122.4194, 37.7749] },
  srs: 'EPSG:4326',
  subject: { scheme: 'eth-address', value: '0x...' },
  radius: 100,
  time: { start: Math.floor(Date.now() / 1000) - 60, end: Math.floor(Date.now() / 1000) }
};

const proof = astral.proofs.create(claim, [stamp1, stamp2]);

proofs.verify()

Verify a location proof. Two modes available:

Local mode (default)

Runs verification in-process. Free and fast. Returns a CredibilityVector.
astral.proofs.verify(
  proof: LocationProof,
  options?: { mode?: 'local' }
): Promise<CredibilityVector>
const vector = await astral.proofs.verify(proof);
// or explicitly:
const vector = await astral.proofs.verify(proof, { mode: 'local' });

console.log(vector.dimensions.spatial.meanDistanceMeters);
console.log(vector.dimensions.temporal.meanOverlap);
console.log(vector.dimensions.validity.signaturesValidFraction);
console.log(vector.dimensions.independence.uniquePluginRatio);
Local verification:
  • Verifies each stamp via its plugin’s verify() method
  • Measures spatial alignment between each stamp and the claim
  • Measures temporal overlap between each stamp and the claim
  • Assesses independence across stamps from different plugins

TEE mode

Runs in a hosted Trusted Execution Environment. Returns a VerifiedLocationProof with an EAS attestation.
astral.proofs.verify(
  proof: LocationProof,
  options: {
    mode: 'tee';
    chainId?: number;
    submitOnchain?: boolean;
    schema?: string;
    recipient?: string;
  }
): Promise<VerifiedLocationProof>
const verified = await astral.proofs.verify(proof, {
  mode: 'tee',
  chainId: 84532,
  submitOnchain: true
});

console.log(verified.attestation.uid);
console.log(verified.credibility.dimensions.spatial);
console.log(verified.remoteAttestation?.platform); // "sgx", "tdx", "sev"

Type guard

Use isVerifiedLocationProof() to narrow the return type:
import { isVerifiedLocationProof } from '@decentralized-geo/astral-sdk';

const result = await astral.proofs.verify(proof, options);

if (isVerifiedLocationProof(result)) {
  // TEE mode — has attestation
  console.log(result.attestation.uid);
} else {
  // Local mode — CredibilityVector
  console.log(result.dimensions.spatial);
}

ProofsModule.exampleWeighting()

Static helper demonstrating how to collapse a CredibilityVector into a single decision value. This is provided as an example — applications should implement their own weighting functions.
const score = ProofsModule.exampleWeighting(vector);

// Example: reject if score is below threshold
if (score > 0.7) {
  console.log('Proof accepted');
}
CredibilityVector intentionally has no single score. The example weighting is for demonstration only. Applications should define their own weighting based on their risk tolerance and use case.

CredibilityVector

The core verification output. Four dimensions, no opinionated scoring.
interface CredibilityVector {
  dimensions: {
    spatial: {
      meanDistanceMeters: number;       // Average distance from stamps to claim
      maxDistanceMeters: number;        // Worst-case distance
      withinRadiusFraction: number;     // 0-1, fraction of stamps within claim radius
    };
    temporal: {
      meanOverlap: number;             // 0-1, average temporal overlap with claim
      minOverlap: number;              // 0-1, worst-case overlap
      fullyOverlappingFraction: number; // Fraction of stamps fully within claim time
    };
    validity: {
      signaturesValidFraction: number;  // 0-1, fraction with valid signatures
      structureValidFraction: number;   // 0-1, fraction with valid structure
      signalsConsistentFraction: number; // 0-1, fraction with consistent signals
    };
    independence: {
      uniquePluginRatio: number;        // 0-1, 1.0 = all stamps from different plugins
      spatialAgreement: number;         // 0-1, how much independent sources agree
      pluginNames: string[];            // List of plugins used
    };
  };
  stampResults: StampResult[];
  meta: {
    stampCount: number;
    evaluatedAt: number;               // Unix seconds
    evaluationMode: 'local' | 'tee' | 'zk';
  };
}

StampResult

Per-stamp assessment within the CredibilityVector:
interface StampResult {
  stampIndex: number;
  plugin: string;
  signaturesValid: boolean;
  structureValid: boolean;
  signalsConsistent: boolean;
  distanceMeters: number;         // Haversine distance to claim
  temporalOverlap: number;        // 0-1 fraction
  withinRadius: boolean;
  details: Record<string, unknown>; // Plugin-specific
}

VerifiedLocationProof

Returned by TEE mode verification. Contains the full credibility vector plus an EAS attestation.
interface VerifiedLocationProof {
  proof: LocationProof;
  credibility: CredibilityVector;
  attestation: {
    uid: string;
    schema: string;
    attester: string;
    recipient: string;
    revocable: boolean;
    refUID: string;
    data: string;
    time: number;
    expirationTime: number;        // 0 = never
    revocationTime: number;        // 0 = not revoked
    signature?: string;
  };
  delegatedAttestation?: {
    signature: string;             // EIP-712
    attester: string;
    deadline: number;
    nonce: number;
  };
  chainId?: number;
  remoteAttestation?: {
    quote: string;
    platform: string;              // "sgx", "tdx", "sev"
    metadata?: Record<string, unknown>;
  };
  evaluationMethod: string;
  evaluatedAt: number;
}

LocationClaim

Defines what the proof is asserting.
interface LocationClaim {
  lpVersion: string;               // e.g., "0.2"
  locationType: string;            // e.g., "geojson-point", "h3-index"
  location: LocationData;          // GeoJSON geometry or string
  srs: string;                     // e.g., "EPSG:4326"
  subject: SubjectIdentifier;      // Who/what was at the location
  radius: number;                  // Spatial uncertainty in meters
  time: TimeBounds;                // { start, end } — Unix seconds
  eventType?: string;              // Optional event classification
}

interface SubjectIdentifier {
  scheme: string;                  // "eth-address", "device-pubkey", "did:pkh"
  value: string;
}

Multi-stamp cross-correlation

Independent plugins increase credibility. When stamps from different proof-of-location systems agree, the independence dimension reflects this:
import { AstralSDK, MockPlugin } from '@decentralized-geo/astral-sdk';
import { ProofModePlugin } from '@location-proofs/plugin-proofmode';

const astral = new AstralSDK({ chainId: 84532, signer: wallet });
astral.plugins.register(new MockPlugin({ lat: 37.7749, lon: -122.4194 }));
astral.plugins.register(new ProofModePlugin());

// Stamps from different systems
const mockStamp = /* ... collect, create, sign with mock plugin ... */;
const proofmodeStamp = /* ... create from ProofMode ZIP bundle ... */;

// Bundle into proof with multiple stamps
const proof = astral.proofs.create(claim, [mockStamp, proofmodeStamp]);

// Verify — cross-correlation is reflected in the vector
const vector = await astral.proofs.verify(proof);

console.log(vector.dimensions.independence.uniquePluginRatio); // 1.0 (all different)
console.log(vector.dimensions.independence.spatialAgreement);  // 0-1
console.log(vector.dimensions.independence.pluginNames);       // ['mock', 'proofmode']
console.log(vector.meta.stampCount);                           // 2
Independent, corroborating evidence from different proof systems strengthens the proof. Redundant stamps from the same plugin don’t add independence — but they don’t subtract either.

Complete example

import { AstralSDK, MockPlugin } from '@decentralized-geo/astral-sdk';
import { ethers } from 'ethers';

const wallet = new ethers.Wallet(process.env.PRIVATE_KEY);

const astral = new AstralSDK({
  chainId: 84532,
  signer: wallet,
  apiUrl: 'https://api.astral.global'
});

astral.plugins.register(new MockPlugin({
  lat: 37.7749,
  lon: -122.4194,
  accuracy: 10
}));

// 1. Collect and create a stamp
const signals = (await astral.stamps.collect({ plugins: ['mock'] }))[0];
const unsigned = await astral.stamps.create({ plugin: 'mock' }, signals);
const stamp = await astral.stamps.sign(
  { plugin: 'mock' },
  unsigned,
  {
    algorithm: 'secp256k1',
    signer: { scheme: 'eth-address', value: wallet.address },
    sign: (data) => wallet.signMessage(data)
  }
);

// 2. Define the claim
const claim = {
  lpVersion: '0.2',
  locationType: 'geojson-point',
  location: { type: 'Point', coordinates: [-122.4194, 37.7749] },
  srs: 'EPSG:4326',
  subject: { scheme: 'eth-address', value: wallet.address },
  radius: 100,
  time: { start: Math.floor(Date.now() / 1000) - 60, end: Math.floor(Date.now() / 1000) }
};

// 3. Bundle into proof
const proof = astral.proofs.create(claim, [stamp]);

// 4. Verify locally
const vector = await astral.proofs.verify(proof);

console.log('Spatial:', vector.dimensions.spatial.meanDistanceMeters, 'm');
console.log('Temporal overlap:', vector.dimensions.temporal.meanOverlap);
console.log('Signatures valid:', vector.dimensions.validity.signaturesValidFraction);

// 5. Or verify in TEE for attestation
const verified = await astral.proofs.verify(proof, {
  mode: 'tee',
  chainId: 84532
});

console.log('Attestation UID:', verified.attestation.uid);

Next: Plugins

Learn about the plugin system and registry