Skip to main content
Research Preview — APIs may change. GitHub

Verifying location proofs

A location proof is a location claim bundled with evidence — stamps from proof-of-location systems. The proof carries everything needed to assess the claim’s credibility. This guide walks you through creating a claim, collecting stamps, bundling them into a proof, and interpreting the verification result.

What is a location proof?

A location proof has two parts:
  • Claim — an assertion that a subject was at a location during a time window
  • Stamps — evidence from one or more proof-of-location systems that support (or contradict) the claim
The verification process evaluates the stamps against the claim and produces a credibility assessment — not a simple yes/no, but a structured evaluation of how strong the evidence is.

Creating a location claim

A claim follows the Location Protocol format and includes the asserted location, time bounds, and spatial uncertainty:
const claim = {
  lpVersion: '0.2',
  locationType: 'geojson-point',
  location: { type: 'Point', coordinates: [-122.4194, 37.7749] },
  srs: 'http://www.opengis.net/def/crs/OGC/1.3/CRS84',
  subject: { scheme: 'eth-address', value: '0x1234...abcd' },
  radius: 100,
  time: { start: Date.now() / 1000 - 60, end: Date.now() / 1000 },
  eventType: 'presence'
};
Key fields:
FieldDescription
locationGeoJSON geometry — where the subject claims to have been
subjectIdentifier for the entity making the claim (Ethereum address, DID, etc.)
radiusSpatial uncertainty in meters — you cannot claim presence at an exact point
timeTemporal bounds as Unix timestamps (start and end)
eventTypeWhat kind of event: "presence", "transaction", "delivery"
The radius field is required. Every location claim involves spatial uncertainty. Claiming a smaller radius requires stronger evidence to achieve the same credibility score.

Collecting stamps

Stamps are evidence from proof-of-location plugins. Each plugin collects signals from its PoL system and produces a stamp:
import { AstralSDK, MockPlugin } from '@decentralized-geo/astral-sdk';

const astral = new AstralSDK({ chainId: 84532 });

// Register a plugin and collect signals. The SDK ships MockPlugin for
// local development; on a real device, evidence comes from a source like
// the ProofMode app. stamps.collect returns an array.
astral.plugins.register(new MockPlugin({ name: 'mock-1', lat: 37.7749, lon: -122.4194 }));

const signals = await astral.stamps.collect({ plugins: ['mock-1'] });
const unsignedStamp = await astral.stamps.create({ plugin: 'mock-1' }, signals[0]);
const stamp1 = await astral.stamps.sign({ plugin: 'mock-1' }, unsignedStamp, deviceSigner);
For stronger verification, collect stamps from multiple independent systems — independence is what cross-correlation rewards, so two stamps from the same source add little. Real independence means genuinely different proof-of-location systems (for example the ProofMode app plus a network-based plugin). Those client plugins are still being built; the shape is the same:
// A second stamp from an independent system (illustrated with a second
// mock instance; in practice this would be a different PoL system).
astral.plugins.register(new MockPlugin({ name: 'mock-2', lat: 37.7750, lon: -122.4193 }));

const witnessSignals = await astral.stamps.collect({ plugins: ['mock-2'] });
const unsignedStamp2 = await astral.stamps.create({ plugin: 'mock-2' }, witnessSignals[0]);
const stamp2 = await astral.stamps.sign({ plugin: 'mock-2' }, unsignedStamp2, nodeSigner);

Bundling into a proof

Combine the claim and stamps into a location proof:
const proof = astral.proofs.create(claim, [stamp1, stamp2]);
A single-stamp proof is valid. Multiple stamps from independent systems enable cross-correlation, which increases confidence.

Submitting to the verify API

Submit the proof for verification:
// `mode: 'tee'` routes to the hosted service; the default 'local' mode
// evaluates in-process and returns the credibility vector directly.
const result = await astral.proofs.verify(proof, { mode: 'tee', chainId: 84532 });
Or via raw HTTP:
curl -X POST https://staging-api.astral.global/verify/v0/proof \
  -H "Content-Type: application/json" \
  -d '{
    "claim": {
      "lpVersion": "0.2",
      "locationType": "geojson-point",
      "location": { "type": "Point", "coordinates": [-122.4194, 37.7749] },
      "srs": "http://www.opengis.net/def/crs/OGC/1.3/CRS84",
      "subject": { "scheme": "eth-address", "value": "0x1234...abcd" },
      "radius": 100,
      "time": { "start": 1706399940, "end": 1706400000 },
      "eventType": "presence"
    },
    "stamps": [ ... ]
  }'

Understanding credibility scores

The verification result is a structured credibility assessment, not a simple pass/fail:
The exact structure of the credibility vector is an open research question and will change. The fields below are illustrative of the current shape, not a stable contract.
{
  "credibility": {
    "dimensions": {
      "spatial": {
        "meanDistanceMeters": 12.5,
        "maxDistanceMeters": 18.3,
        "withinRadiusFraction": 1.0
      },
      "temporal": {
        "meanOverlap": 0.95,
        "minOverlap": 0.90,
        "fullyOverlappingFraction": 0.5
      },
      "validity": {
        "signaturesValidFraction": 1.0,
        "structureValidFraction": 1.0,
        "signalsConsistentFraction": 1.0
      },
      "independence": {
        "uniquePluginRatio": 1.0,
        "spatialAgreement": 0.88,
        "pluginNames": ["mock-1", "mock-2"]
      }
    },
    "stampResults": [ "..." ],
    "meta": { "stampCount": 2, "evaluatedAt": 1706400000, "evaluationMode": "tee" }
  },
  "evaluationMethod": "multifactor-v0",
  "evaluatedAt": 1706400000,
  "attestation": {
    "uid": "0xabc123...",
    "attester": "0x590fdb53..."
  }
}

Credibility dimensions

These dimensions are a preliminary sketch — their exact structure and metrics are an active research area and will change. The credibility vector is a multidimensional assessment grouped into four dimensions — each an object of metrics, not a single score:
DimensionWhat it measures
SpatialHow closely the stamps’ observed locations match the claimed location (mean/max distance, fraction within the claimed radius)
TemporalHow well the stamps’ time windows overlap the claimed time window
ValidityFraction of stamps with valid signatures, structure, and consistent signals
IndependenceHow independent and corroborating the sources are (unique-plugin ratio, spatial agreement)

No single score

There is no top-level confidence field. Collapsing the vector into one number requires deciding which dimensions matter most — that judgment belongs to your application, not to Astral. The SDK ships an exampleWeighting() helper you can use as a starting point, but you’re expected to define your own.
A credibility score is not a probability. Strong metrics mean the evidence is strong — not “an X% chance the claim is true.” Calibrating these to true probabilities is future work.

Cross-correlation

When a proof includes multiple stamps, the verification engine analyzes their relationship:
  • Independence — are the stamps from truly independent systems? Two stamps from the same underlying data source do not add much.
  • Agreement — do the stamps agree on location and time? Independent stamps that corroborate each other significantly boost confidence.

Multi-factor proofs

Multiple stamps from independent systems increase confidence because an attacker would need to compromise multiple unrelated systems simultaneously:
// Single stamp
const singleResult = await astral.proofs.verify(
  astral.proofs.create(claim, [stamp1]),
  { mode: 'tee', chainId: 84532 }
);

// Multi-stamp from independent systems: the independence dimension reflects
// that the evidence is corroborated rather than redundant
const multiResult = await astral.proofs.verify(
  astral.proofs.create(claim, [stamp1, stamp2]),
  { mode: 'tee', chainId: 84532 }
);
// multiResult.credibility.dimensions.independence.uniquePluginRatio → higher
// multiResult.credibility.dimensions.independence.spatialAgreement   → higher
The improvement comes from source independence. Redundant stamps from the same system do not meaningfully strengthen the assessment, but they do not weaken it either.

Choosing the right level of evidence

The level of evidence you need depends on the value of the transaction the proof underpins:
  • Low-stakes (check-in rewards, social proof) — a single stamp from a device attestation plugin may be sufficient.
  • Medium-stakes (delivery verification, access control) — two independent stamps provide meaningful forgery resistance.
  • High-stakes (insurance payouts, land records) — multiple independent stamps with high forgery resistance, plus onchain submission for an immutable audit trail.

Using verified proofs as compute inputs

Verified location proofs can serve as trusted inputs to geocomputation operations. This connects the verification pipeline to the spatial reasoning pipeline:
const verifiedProof = await astral.proofs.verify(proof, { mode: 'tee', chainId: 84532 });

// Use the verified proof as input to a spatial operation. Compute args are
// positional; a verified proof is passed as { verifiedProof } (the whole object).
const result = await astral.compute.contains(
  approvedZonePolygonUID,        // container
  { verifiedProof },             // containee — the verified location proof
  { schema: SCHEMA_UID }
);

Next steps

Location proofs concept

Deeper dive into claims, stamps, and the verification model

Building plugins

Connect a new proof-of-location system to Astral