# Farm Registry Protocol, version 1.0

Published by Carbon Bridge Services B.V.
Specification licence: CC BY 4.0. Reference implementation licence: MIT.

Canonical location: https://carbonbridge.nl/platform/registry/protocol
Record schema: https://carbonbridge.nl/protocol/farm-registry-record-v1.schema.json

The key words MUST, MUST NOT, SHOULD and MAY are to be read as in RFC 2119.

## 1. Purpose

A farm that joins a carbon, restoration or supply-chain programme is described by a polygon: the outline of the land the programme counts. The same land can be proposed again later, to a state programme, to a nature-based-solutions project that buys the farm, or to another registry. Without a shared layer there is no reliable way to know that the second proposal covers land that is already counted, and no reliable way to show what the land looked like when it was first registered.

This protocol fixes that with three things any platform, programme or registry can run on its own farms:

1. A **record** for each farm polygon, written on the day the farm is onboarded, in one canonical form so the same land always produces the same bytes.
2. A **hash** of that record and an **anchor** of the hash on any ledger with immutable, timestamped entries, so the baseline can be verified by anyone and changed by no one.
3. An **overlap check** that compares every new polygon with the registered ones and reports, in hectares and as a share, where it covers land already registered.

The protocol supplies evidence. It does not decide. The earlier registration stays the reference; the enrolment decision stays with the owner of the programme that received the new polygon.

The protocol is published for adoption. Any implementation that meets the conformance section (§10) is a conforming implementation, whether or not it runs on Sequestra.

## 2. Terms

- **Polygon**: a GeoJSON Polygon (RFC 7946) in WGS84 longitude/latitude, one exterior ring and zero or more holes.
- **Canonical geometry**: the polygon in the single encoding defined in §4.
- **Record**: the JSON document defined in §3, one per farm polygon.
- **Registrant**: the party that writes the record. A platform, a programme owner or a registry.
- **Programme**: anything a polygon can be enrolled in: a carbon project, a restoration or agroforestry programme, a state scheme, a supply-chain insetting arrangement, a registry listing.
- **JCS**: the JSON Canonicalization Scheme of RFC 8785.
- **Geometry hash**, **record hash**, **registry ID**: the three identifiers of §5.
- **Anchor payload**, **anchor digest**, **anchor reference**: the three objects of §6.
- **Ledger**: any system with immutable, timestamped entries that a third party can read back. The protocol names no specific ledger and requires none.
- **Candidate**: a polygon proposed for enrolment that has not yet been checked.

## 3. Record

A record is a JSON object. Its normative shape is the JSON Schema below (draft 2020-12). A conforming registrant MUST write records that validate against it, and MUST store the geometry in canonical form (§4).

```json
{
  "version": "1.0",
  "country": "BR",
  "registrantReference": "0142",
  "geometry": { "type": "Polygon", "coordinates": [[[-45.454173, -21.400855], ...]] },
  "onboardedAt": "2026-03-14T13:20:00Z",
  "attributes": {
    "tenureReference": "MG-TENURE-0142-SAMPLE",
    "landUseHistory": [
      { "from": "2016-01-01", "to": "2026-03-13", "use": "pasture" },
      { "from": "2026-03-14", "use": "restoration" }
    ],
    "imageryReferences": ["0xa1a1...a1a1", "0xb2b2...b2b2"],
    "aboveGroundBiomass": { "value": 12.4, "unit": "t/ha" }
  },
  "enrolments": [{ "programmeId": "restoration-programme-2026", "since": "2026-03-14" }],
  "anchor": { "ledger": "example-ledger", "reference": "entry 184220:7", "anchoredAt": "2026-03-14T14:02:11Z" }
}
```

Members:

- `version`: the string `"1.0"`.
- `country`: ISO 3166-1 alpha-2 code, upper case, of the country the polygon lies in. The registrant supplies it; it is never derived from the coordinates, so a border farm has one unambiguous ID.
- `registrantReference` (optional): the registrant's own reference for the farm.
- `geometry`: the canonical polygon.
- `onboardedAt`: the instant the farm was onboarded, ISO 8601 in UTC with a trailing `Z`.
- `attributes`: what was recorded on the day. Four members are required; registrants MAY add others, and anything added is hashed with the rest.
  - `tenureReference`: the land-tenure document or cadastral reference the registrant holds for the land.
  - `landUseHistory`: periods of land use, each `{ from, to?, use }` with dates `YYYY-MM-DD`; `to` is omitted while the use continues.
  - `imageryReferences`: SHA-256 content hashes (`0x` + 64 hex) of the imagery the registrant keeps with the record. The images themselves are not part of the record.
  - `aboveGroundBiomass`: the recorded above-ground biomass at onboarding, `{ value, unit: "t/ha" }`. The protocol records the value; how a registrant arrives at it is outside the protocol.
- `enrolments` (optional): programmes the polygon is enrolled in, each `{ programmeId, since }`. Not covered by the record hash (§7).
- `anchor` (optional): where the anchor digest was written (§6.3). Not covered by the record hash.

### 3.1 JSON Schema

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://carbonbridge.nl/protocol/farm-registry-record-v1.schema.json",
  "title": "Farm Registry Record",
  "type": "object",
  "required": ["version", "country", "geometry", "onboardedAt", "attributes"],
  "additionalProperties": false,
  "properties": {
    "version": { "const": "1.0" },
    "country": { "type": "string", "pattern": "^[A-Z]{2}$" },
    "registrantReference": { "type": "string", "minLength": 1, "maxLength": 128 },
    "geometry": {
      "type": "object",
      "required": ["type", "coordinates"],
      "additionalProperties": false,
      "properties": {
        "type": { "const": "Polygon" },
        "coordinates": {
          "type": "array", "minItems": 1,
          "items": {
            "type": "array", "minItems": 4,
            "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": { "type": "number" } }
          }
        }
      }
    },
    "onboardedAt": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?Z$" },
    "attributes": {
      "type": "object",
      "required": ["tenureReference", "landUseHistory", "imageryReferences", "aboveGroundBiomass"],
      "additionalProperties": true,
      "properties": {
        "tenureReference": { "type": "string", "minLength": 1 },
        "landUseHistory": {
          "type": "array",
          "items": {
            "type": "object", "required": ["from", "use"], "additionalProperties": false,
            "properties": {
              "from": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" },
              "to": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" },
              "use": { "type": "string", "minLength": 1 }
            }
          }
        },
        "imageryReferences": { "type": "array", "items": { "type": "string", "pattern": "^0x[0-9a-f]{64}$" } },
        "aboveGroundBiomass": {
          "type": "object", "required": ["value", "unit"], "additionalProperties": false,
          "properties": { "value": { "type": "number", "minimum": 0 }, "unit": { "const": "t/ha" } }
        }
      }
    },
    "enrolments": {
      "type": "array",
      "items": {
        "type": "object", "required": ["programmeId", "since"], "additionalProperties": false,
        "properties": {
          "programmeId": { "type": "string", "minLength": 1 },
          "since": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }
        }
      }
    },
    "anchor": {
      "type": "object", "required": ["ledger", "reference", "anchoredAt"], "additionalProperties": false,
      "properties": {
        "ledger": { "type": "string", "minLength": 1 },
        "reference": { "type": "string", "minLength": 1 },
        "anchoredAt": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?Z$" }
      }
    }
  }
}
```

The downloadable schema file carries the same content with descriptions added.

## 4. Canonical geometry

A polygon has many equivalent GeoJSON encodings. The canonical form picks one so that the same land always produces the same bytes, and therefore the same hash and the same registry ID.

Input: a GeoJSON Polygon in WGS84, longitude then latitude, with at least one ring. Each ring MUST have at least four positions before canonicalisation; an implementation MUST reject anything shorter.

For each ring, in order, an implementation MUST:

1. **Drop elevation.** Only the first two elements of each position are kept.
2. **Validate bounds.** Longitude in [−180, 180], latitude in [−90, 90]; anything else is rejected.
3. **Round** each coordinate to 7 decimal places (about 1 cm at the equator). Every later step operates on these rounded values; implementations SHOULD work on the integer grid of 10⁻⁷ degrees so the geometric tests below are exact.
4. **Open the ring**: drop the closing vertex if it equals the first.
5. **Drop redundant vertices**: any vertex equal to its successor, and any vertex that lies on the straight line through its two neighbours (which also removes spikes that double back). Repeat until no vertex qualifies.
6. **Reject degenerate rings**: fewer than three vertices remain, or the ring's signed area is zero.
7. **Orient** by the right-hand rule of RFC 7946 §3.1.6: the exterior ring counter-clockwise (positive signed area in the longitude/latitude plane), every hole clockwise. Reverse the ring if it runs the other way.
8. **Rotate** the ring to start at its lexicographically smallest vertex: smallest longitude, then smallest latitude.
9. **Close** the ring by appending a copy of its first vertex.

The canonical geometry is `{ "type": "Polygon", "coordinates": [ring₀, ring₁, …] }` with the rings in their original order (exterior first). Canonicalisation is idempotent: canonicalising a canonical geometry returns it unchanged.

Implementations MAY additionally reject self-intersecting rings. Version 1.0 does not require it.

## 5. Hashing

### 5.1 Serialisation

Every hash in this protocol is SHA-256 (FIPS 180-4) over the UTF-8 bytes of a JSON value serialised with the JSON Canonicalization Scheme, RFC 8785: object members sorted by the UTF-16 code units of their names, no whitespace, strings escaped as ECMAScript `JSON.stringify` escapes them, numbers in the ECMAScript shortest round-trip form (`1`, not `1.0`; `1e+21`; `1e-7`; negative zero as `0`). NaN and infinities cannot occur in a record and MUST be rejected.

Hashes are written as 64 lower-case hex characters prefixed `0x`. Comparisons of hashes MUST be case-insensitive and MUST accept the value with or without the prefix.

### 5.2 Geometry hash

`geometryHash = SHA-256( JCS( canonicalGeometry ) )`

The geometry hash identifies the land. Two records of the same outline, in any encoding, have the same geometry hash.

### 5.3 Record hash

`recordHash = SHA-256( JCS( record − anchor − enrolments ) )`

The record hash covers `version`, `country`, `registrantReference` (if present), `geometry` in canonical form, `onboardedAt` and the whole of `attributes`. It excludes `anchor` (which cannot exist before the hash does) and `enrolments` (which change over the life of the record, §7). An implementation that receives a record with non-canonical geometry MUST canonicalise it before hashing, so the hash does not depend on the encoding.

### 5.4 Registry ID

`registryId = "REG-" + country + "-" + upper( first 8 hex characters of geometryHash )`

Example: `REG-BR-F7A61901`. The country comes from the record's `country` member and is never derived from coordinates. The eight hex characters (32 bits) make the ID short enough to print on a document and to say aloud; the full geometry hash remains the identifier of record, and an implementation MUST treat two polygons with the same registry ID but different geometry hashes as different polygons.

## 6. Anchoring

The protocol is chain-agnostic. An anchor is one 32-byte digest written to any ledger with immutable, timestamped entries, together with a reference to where it was written. Nothing in the record itself goes on the ledger.

### 6.1 Anchor payload

```json
{
  "version": "1.0",
  "registryId": "REG-BR-F7A61901",
  "geometryHash": "0xf7a6…",
  "recordHash": "0xcb1f…",
  "onboardedAt": "2026-03-14T13:20:00Z"
}
```

Five members, nothing else. The payload has the same size for every farm and carries no personal data, no coordinates and no attributes.

### 6.2 Anchor digest

`anchorDigest = SHA-256( JCS( anchorPayload ) )`

The ledger entry MUST contain the anchor digest, or commit to it in a way a third party can verify (for instance as the leaf of a published Merkle tree whose root is on the ledger). One generic form, for a ledger that records events with arbitrary data, is a single event `{ "event": "FarmRegistryAnchor", "version": "1.0", "registryId": …, "data": anchorDigest }`. Any immutable, timestamped entry that commits to the same 32 bytes is equally conformant.

### 6.3 Anchor reference

Once written, the registrant records where in its `anchor` member:

```json
{ "ledger": "<ledger name>", "reference": "<where in that ledger>", "anchoredAt": "<ISO 8601 UTC>" }
```

`ledger` and `reference` are free text chosen by the implementer; together they MUST be enough for a third party to read the entry back. `anchoredAt` is the ledger's own timestamp for the entry, which is the time the baseline became verifiable.

### 6.4 Verification

Given a record and the digest read back from the ledger, a verifier:

1. validates the record against the schema (§3) and canonicalises its geometry (§4);
2. recomputes the geometry hash, record hash and registry ID (§5);
3. builds the anchor payload (§6.1) and its digest (§6.2);
4. compares the recomputed digest with the digest from the ledger, case-insensitively.

The anchor is valid when the two digests are equal. Any change to the record's hashed members after anchoring changes the record hash, the payload and the digest, and the comparison fails. Changes to `enrolments` and `anchor` do not affect it.

## 7. Enrolments

A record is written once; its `enrolments` list grows. Each entry is `{ programmeId, since }`: the programme the polygon is enrolled in and the first day of that enrolment. `programmeId` is a string chosen by the programme owner and SHOULD be stable and globally unique within the registrant's context (a URI, or a namespaced identifier).

Enrolments are excluded from the record hash so that adding one does not break the anchor. A registrant that wants later enrolments to be independently verifiable MAY anchor them separately; version 1.0 does not require it.

An enrolment is appended, never edited: a programme that ends is left in the list and the next programme is added after it. The list is therefore a history, and the earliest entry shows which programme first counted the land.

## 8. Overlap check

Before a candidate polygon is enrolled, it is compared with every registered polygon the checker holds.

### 8.1 Projection

Area is measured on an equal-area plane so that hectares mean hectares at any latitude:

- Both the candidate and each registered polygon are canonicalised (§4).
- The projection is the Lambert azimuthal equal-area projection on a sphere of radius 6 371 008.8 m, centred on the candidate: the centre is the arithmetic mean of the candidate's exterior-ring vertices, closing vertex excluded.
- Each polygon is projected vertex by vertex; edges are straight lines in the projected plane.
- The intersection is computed in the projected plane. Its area, and the candidate's area, are the planar (shoelace) areas in square metres, exterior minus holes, divided by 10 000 to give hectares.

An implementation MAY skip any registered polygon whose longitude/latitude bounding box does not touch the candidate's: such a polygon cannot overlap and the result is unchanged.

### 8.2 Thresholds

An overlap is **flagged** when, for any single registered polygon,

- the intersection is at least **0.1 ha**, or
- the intersection is at least **1 %** of the candidate's area.

Both thresholds exist because farms range from a fraction of a hectare to thousands: the absolute threshold catches a strip of a large farm, the relative one catches most of a small one. Intersections below both thresholds are still reported, so the programme owner can see them; they are not flagged. A shared boundary with zero area is not an overlap.

### 8.3 Result

```json
{
  "candidateHectares": 100.0054,
  "overlaps": [
    { "registryId": "REG-BR-EXAMPLE0", "hectares": 25.0012, "shareOfCandidate": 0.249999 }
  ],
  "flagged": true
}
```

`overlaps` lists every registered polygon with a non-zero intersection, largest first (ties by registry ID). `hectares` is rounded to 4 decimals (one square metre); `shareOfCandidate` is `hectares ÷ candidateHectares`, rounded to 6 decimals. `flagged` is true when any entry meets §8.2.

### 8.4 What the check means

The check produces evidence, not a decision.

- **The earlier registration stays the reference.** Where a candidate overlaps a registered polygon, the registered record, with its onboarding date, its anchored baseline and its enrolment history, is the reference for that land. The candidate does not replace it.
- **The decision belongs to the programme owner.** The owner of the programme that received the candidate decides what to do with the evidence: decline the candidate, enrol only the part outside the overlap, or enrol it with the overlap disclosed. The protocol does not take that decision and does not rank programmes.
- **The checker reports what it holds.** A checker can only compare against the polygons it has. Registrants who want the check to be complete share records, or at least geometry hashes and canonical geometries, with each other.

## 9. Conformance

An implementation conforms to Farm Registry Protocol 1.0 when it:

1. writes records that validate against the schema in §3 and stores their geometry in canonical form;
2. canonicalises geometry exactly as §4 specifies, and rejects what §4 rejects;
3. serialises with RFC 8785 and hashes with SHA-256 as §5 specifies, and reproduces every value in the published test vectors (§10);
4. computes the registry ID as §5.4 specifies, from the record's own `country`;
5. builds the anchor payload and digest as §6 specifies, writes the digest to a ledger with immutable, timestamped entries, and records an anchor reference a third party can follow;
6. can verify an anchor as §6.4 specifies, from a record and a digest alone, with no access to the registrant's systems;
7. runs the overlap check of §8 before every enrolment, with the projection, thresholds and result shape given there, and reports its result to the programme owner without taking the enrolment decision itself;
8. appends enrolments and never edits them (§7).

An implementation MAY extend `attributes`, MAY use any ledger, MAY reject self-intersecting rings and MAY add further checks. It MUST NOT change the canonical form, the hashed members, the serialisation, the payload, the thresholds or the result shape, since any of those would make its hashes and results incomparable with everyone else's.

## 10. Test vectors

The file `vectors.json` beside the reference implementation holds one complete sample record and every value a conforming implementation must produce from it. The farm, its reference `0142`, its tenure reference, its dates and its enrolment are fictional sample data; the outline is a plausible farm of about 96 hectares in the south of Minas Gerais, Brazil, and the hashes are reproducible.

Values for the sample record:

| Value | |
| --- | --- |
| Registry ID | `REG-BR-F7A61901` |
| Geometry hash | `0xf7a61901b7e7cd7e806489e6fc538402b255b3c53835a7cee1de1e17e48daa9b` |
| Record hash | `0xcb1f3ad1317cc88f3e3574136bddaecca93c89b1f7420f6f680033a756f70aad` |
| Anchor digest | `0x6d503040b7e23fb262621dd450ddb63ad69f06106b7508893bc146c2bd39963d` |
| Area | 95.37 ha (about 96 ha) |

The anchor payload, serialised with JCS, is:

```
{"geometryHash":"0xf7a61901b7e7cd7e806489e6fc538402b255b3c53835a7cee1de1e17e48daa9b","onboardedAt":"2026-03-14T13:20:00Z","recordHash":"0xcb1f3ad1317cc88f3e3574136bddaecca93c89b1f7420f6f680033a756f70aad","registryId":"REG-BR-F7A61901","version":"1.0"}
```

The file also holds a non-canonical encoding of the same outline (`rawGeometry`) that must canonicalise to the record's geometry, and an overlap example (`overlapExample`): two 1 km squares near the farm, the candidate offset 500 m east and north of the registered one, which the check reports as 25.0012 ha, 25.0 % of the candidate, flagged.

A further fixed value for the simplest case: the unit square `[[0,0],[1,0],[1,1],[0,1],[0,0]]` has geometry hash `0x722fa320a5e6a0e2a931d43252d1e8670f14470892c3f8eb1584de90d2cf1f56`, whatever vertex it starts at and whichever way round it is given.

## 11. Reference implementation

A reference implementation in TypeScript is published with this specification. It is plain code with no framework dependency, runs in Node 20 or later and in browsers, and uses the Web Crypto API for SHA-256. Modules:

- `canonical.ts`: §4.
- `record.ts`: the types, the JSON Schema and a validator for §3.
- `hash.ts`: RFC 8785 serialisation, SHA-256, the geometry hash, record hash and registry ID of §5.
- `anchor.ts`: the payload, digest, anchor reference and verification of §6.
- `overlap.ts`: the projection, check, thresholds and result of §8.
- `registry.test.ts` and `vectors.json`: the tests and the test vectors of §10.

The implementation can be run in a browser on the protocol page, where a record can be pasted and its hashes, registry ID and anchor digest recomputed.

## 12. Licence

This specification is published under the Creative Commons Attribution 4.0 International licence (CC BY 4.0). The reference implementation and the test vectors are published under the MIT licence. Any platform, programme or registry may adopt, implement, copy and redistribute both, with attribution to Carbon Bridge Services B.V.

Copyright 2026 Carbon Bridge Services B.V.
