> ## Documentation Index
> Fetch the complete documentation index at: https://docs.astral.global/llms.txt
> Use this file to discover all available pages before exploring further.

# ProofMode

> Mobile device proofs via the ProofMode app

<Warning>
  **Alpha** — The ProofMode plugin is in early development. See below for what's available and what's coming next.
</Warning>

# ProofMode plugin

[ProofMode](https://proofmode.org) is a mobile app (iOS and Android) that collects device evidence — GPS coordinates, network context, device metadata, PGP signatures, and SafetyNet/Play Integrity attestations. The `@location-proofs/plugin-proofmode` package integrates ProofMode into the Astral SDK's standard plugin pipeline and verifies stamp consistency.

## How it works

1. The user captures evidence in the ProofMode app on their phone
2. They export a ZIP bundle from the app
3. Your application passes the ZIP data through the standard SDK pipeline
4. The plugin parses it into an `UnsignedLocationStamp` and can verify its structure and signals

```
ProofMode App → ZIP export → stamps.create() → stamps.sign() → stamps.verify()
```

## Installation

```bash theme={null}
npm install @location-proofs/plugin-proofmode
```

**GitHub:** [github.com/location-proofs/plugin-proofmode](https://github.com/location-proofs/plugin-proofmode)

## Registration

```typescript theme={null}
import { AstralSDK } from '@decentralized-geo/astral-sdk';
import { ProofModePlugin } from '@location-proofs/plugin-proofmode';

const astral = new AstralSDK({ chainId: 84532, signer: wallet });
astral.plugins.register(new ProofModePlugin());
```

## Plugin properties

```typescript theme={null}
plugin.name        // 'proofmode'
plugin.version     // '0.1.0'
plugin.runtimes    // ['node', 'browser']
plugin.description // 'ProofMode device-based location proofs with PGP signatures
                   //  and hardware attestation'
```

***

## Standard pipeline

ProofMode implements the standard `create()` and `verify()` interface methods, so it works with the SDK's `StampsModule`.

### Collecting evidence

Evidence collection happens in the [ProofMode app](https://proofmode.org) on the user's device. The app captures GPS coordinates, sensor data, PGP signatures, and hardware attestations, then exports a ZIP bundle. Your application receives this ZIP and passes it through the SDK pipeline below.

### create()

Pass the ZIP bundle as `signals.data.zipData`:

```typescript theme={null}
import { readFileSync } from 'fs';

const zipData = new Uint8Array(readFileSync('proofmode-export.zip'));

const unsigned = await astral.stamps.create(
  { plugin: 'proofmode' },
  {
    plugin: 'proofmode',
    timestamp: Math.floor(Date.now() / 1000),
    data: { zipData }
  }
);
```

Under the hood, `create()` parses the ZIP and extracts GPS coordinates, timestamps, PGP signatures, SafetyNet tokens, and device metadata into a structured `UnsignedLocationStamp`.

### verify()

Verify a ProofMode stamp's internal validity:

```typescript theme={null}
const result = await astral.stamps.verify(stamp);
```

Checks performed:

* **Structure** — LP version is `'0.2'`, plugin is `'proofmode'`, location/signals/temporalFootprint present
* **Signatures** — At least one signature exists with non-empty value and signer info
* **Signal consistency:**
  * Latitude and longitude are in valid ranges
  * Location provider and accuracy are consistent
  * SafetyNet JWT structure is valid (base64url-encoded, 3 parts)
  * `Location.Time` matches `temporalFootprint` within 3600 seconds

Returns `StampVerificationResult`:

```typescript theme={null}
{
  valid: boolean,
  signaturesValid: boolean,
  structureValid: boolean,
  signalsConsistent: boolean,
  details: { ... }   // ProofMode-specific verification details
}
```

***

## Lower-level API

For more control over the parsing step, you can use the plugin's helpers directly:

### parseBundle()

Parse a ProofMode ZIP export into a structured bundle.

```typescript theme={null}
const plugin = new ProofModePlugin();
const bundle = plugin.parseBundle(zipData);
```

| Parameter | Type         | Description                             |
| --------- | ------------ | --------------------------------------- |
| `zipData` | `Uint8Array` | Raw ZIP file data from ProofMode export |

Returns `ParsedBundle`:

```typescript theme={null}
interface ParsedBundle {
  metadata: ProofModeMetadata;   // Parsed signal data
  publicKey?: string;            // ASCII-armored PGP public key
  metadataSignature?: Uint8Array; // PGP detached signature of metadata
  mediaSignature?: Uint8Array;   // PGP detached signature of media
  safetyNetToken?: string;       // Google SafetyNet/Play Integrity JWT
  otsProof?: Uint8Array;         // OpenTimestamps proof
  mediaFile?: Uint8Array;        // The media file data
  mediaFileName?: string;
  expectedHash?: string;         // SHA-256 hash from bundle filename
  files: BundleFile[];           // All raw files in bundle
}
```

### createStampFromBundle()

Create an unsigned location stamp from a parsed bundle:

```typescript theme={null}
const unsigned = plugin.createStampFromBundle(bundle);
```

This is what `create()` calls internally after parsing.

***

## What the ZIP bundle contains

A ProofMode export ZIP typically includes:

| File                                | Description                          |
| ----------------------------------- | ------------------------------------ |
| `<hash>.proof.csv` or `.proof.json` | Location and device metadata signals |
| `pubkey.asc`                        | ASCII-armored PGP public key         |
| `<file>.asc`                        | PGP detached signatures              |
| `<file>.gst`                        | Google SafetyNet/Play Integrity JWT  |
| `<file>.ots`                        | OpenTimestamps proof                 |
| Media file                          | Photo or video captured during proof |

### Signal fields

The metadata file contains 30+ signal fields:

| Category   | Fields                                                                                                                                                          |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Location   | `Location.Latitude`, `Location.Longitude`, `Location.Provider`, `Location.Accuracy`, `Location.Altitude`, `Location.Bearing`, `Location.Speed`, `Location.Time` |
| Network    | `CellInfo`, `WiFi.MAC`, `IPv4`, `IPv6`, `Network`                                                                                                               |
| Device     | `DeviceID`, `Hardware`, `Manufacturer`, `Model`                                                                                                                 |
| ProofMode  | `ProofHash`, `FileHash`, `MimeType`, `File.Name`, `File.Size`                                                                                                   |
| Timestamps | `DateCreated`, `Timestamp`                                                                                                                                      |

***

## Verification scope

### What verify checks today

* Stamp structure conforms to Location Protocol v0.2
* Signatures array is non-empty with valid signer info
* GPS coordinates are within valid ranges
* Signal consistency (provider/accuracy, timestamps alignment)
* SafetyNet JWT structure (3-part base64url format)

### Deferred to v1+

* Full PGP cryptographic signature verification (checking signatures against the public key)
* SafetyNet/Play Integrity certificate chain verification
* OpenTimestamps proof verification

***

## Coming soon

<Info>
  **`collect()` — In-app evidence collection** is planned for developers building React Native apps. This will allow triggering ProofMode evidence collection directly from your app, rather than requiring the user to export a ZIP. This is under active development.
</Info>

***

## Example

```typescript theme={null}
import { AstralSDK } from '@decentralized-geo/astral-sdk';
import { ProofModePlugin } from '@location-proofs/plugin-proofmode';
import { readFileSync } from 'fs';

const astral = new AstralSDK({ chainId: 84532, signer: wallet });
astral.plugins.register(new ProofModePlugin());

// Load a ProofMode ZIP export
const zipData = new Uint8Array(readFileSync('proofmode-export.zip'));

// Create stamp through the standard pipeline
const unsigned = await astral.stamps.create(
  { plugin: 'proofmode' },
  { plugin: 'proofmode', timestamp: Math.floor(Date.now() / 1000), data: { zipData } }
);

console.log('Stamp location:', unsigned.location);
console.log('Stamp time:', unsigned.temporalFootprint);

// Sign the stamp
const stamp = await astral.stamps.sign(
  { plugin: 'proofmode' },
  unsigned,
  signer
);

// Verify
const result = await astral.stamps.verify(stamp);
console.log('Valid:', result.valid);
console.log('Structure:', result.structureValid);
console.log('Signals consistent:', result.signalsConsistent);

// Use in a proof with stamps from other plugins
const proof = astral.proofs.create(claim, [stamp, otherStamp]);
const vector = await astral.proofs.verify(proof);
```
