The FlowStake Protocol

A Portable, Self-Verifying Container Format
for Attested Physical Activity
Renat Razumov
FlowStake LLC  ·  flowstake.com
Specification draft v0.9 · 31 July 2026 · .fsp / media type application/vnd.flowstake.proof+cbor
Abstract. GPX, TCX and FIT succeeded because they made activity data portable: any device can write them and any platform can read them. None of them makes activity data credible — a FIT file asserts that a track exists and says nothing about whether a human produced it, which is why every consequence attached to activity data (records, prizes, eligibility, insurance pricing) currently rests on trusting whoever supplied the file. This document specifies the FlowStake Proof container (.fsp): a deterministic CBOR structure that binds a Merkle commitment over an activity track to hardware device attestations, mutually signed encounter records witnessed by other athletes, and a likelihood-ratio evidence score, under a single content address. An .fsp file is offline-verifiable: any party can recompute the verdict from the file alone, without contacting FlowStake or trusting it. It is selectively disclosable: an athlete may prove a 21.1 km run at 10,000:1 confidence while disclosing no coordinates, because the evidence survives redaction of the track. And it is complementary rather than competitive: an .fsp wraps an existing FIT or GPX payload rather than replacing it. We specify the container, the Merkle Mountain Range commitment enabling the prefix bindings on which encounter evidence depends, the normative verification procedure, three privacy profiles, and the binding of the content address to on-chain settlement.

1  Introduction

1.1  The gap the existing formats leave

The interchange formats in use today were designed for fidelity and portability, not provenance. GPX (2002) carries coordinates and timestamps; TCX adds structured training data; FIT adds efficient binary encoding and a rich sensor model. All three assume a cooperative author. Each is trivially synthesised: writing a FIT file describing a 2:30 marathon requires no marathon.

This was adequate while activity data was a personal record. It is inadequate now that activity data carries consequence. The companion paper Proof of Physical Activity [1] establishes the structural reason a device cannot close this gap alone: hardware attestation authenticates computation, not reality, and the sensor interface is a trust boundary the enclave does not see across. Evidence that a human moved must therefore come, in part, from other humans.

What is missing is not a better sensor format. It is a container for evidence — one that travels with the activity, survives redaction, and can be checked by a party who trusts neither the athlete nor the platform.

1.2  Design goals

GoalConsequence for the design
G1 Offline-verifiableAll evidence needed for a verdict is inside the file. No network call, no API key, no trust in the issuer.
G2 Selectively disclosableRedacting the track must not invalidate the evidence. Proof of performance must be separable from disclosure of location.
G3 ComplementaryWraps FIT/GPX/TCX rather than replacing them. Adoption must not require abandoning existing tooling.
G4 DeterministicByte-identical re-serialisation, so the content address is stable across implementations.
G5 Extensible without forksUnknown fields are preserved and ignored; new evidence types are additive.
G6 Privacy-preserving by defaultEphemeral identifiers, quantised encounters; no athlete becomes a trackable beacon by participating.

1.3  Requirements language

must, must not, should, may are to be interpreted as in BCP 14 [2].

2  Terminology

activity rootThe content address of a proof; the digest binding all other components.
banOne unit of log10 likelihood odds. Evidence strength is denominated in bans [1].
encounterA mutually signed record of two athletes' physical co-presence at a quantised time and place.
MMRMerkle Mountain Range: an append-only accumulator supporting efficient prefix roots (§5).
prefix rootThe MMR root over track samples up to a given index — what an encounter commits to.
profileA disclosure level: FULL, REDACTED or MINIMAL (§8).
subjectThe athlete to whom the proof pertains, identified by public key.
verifierAny party evaluating a proof. Not necessarily FlowStake.

3  Container overview

An .fsp file is a single deterministically encoded CBOR map [3] with integer keys. Integer keys keep the encoding compact and stable across localisations; a registry of assigned keys appears in §12.

FSP = {
   0: version,        ; uint    — protocol version (this document: 1)
   1: profile,        ; uint    — 0 FULL | 1 REDACTED | 2 MINIMAL
   2: subject,        ; map     — athlete identity and device attestation (§4)
   3: activity,       ; map     — sport, interval, aggregate metrics (§4.3)
   4: track,          ; map     — MMR root, sample count, optional samples (§5)
   5: encounters,     ; array   — signed encounter records (§6)
   6: evidence,       ; map     — evidence terms and total in bans (§7)
   7: endorsements,   ; array   — verifier signatures, on-chain anchors (§9)
   8: source,         ; map     — wrapped FIT/GPX/TCX payload or digest (§10)
   9: extensions      ; map     — namespaced, ignored-if-unknown (§12)
}

Keys 0–6 are mandatory in every profile. Key 7 is optional and carries third-party assertions. Keys 8–9 are optional.

Normative — canonical encoding. Encoders must emit deterministically encoded CBOR per RFC 8949 §4.2.2: definite-length items, shortest-form integers, map keys sorted in bytewise lexicographic order of their encodings, and no indefinite-length strings. Two conforming encoders given the same logical content must produce identical bytes. All digests in this specification are computed over such an encoding.

4  Subject and activity

4.1  Subject

subject = {
   0: pubkey,          ; bstr  — Ed25519 public key, 32 bytes
   1: key_attestation, ; map   — platform key attestation (§4.2), optional
   2: alias            ; tstr  — display handle, optional, never identity-bearing
}

The subject is identified by key, not by account. A proof therefore remains valid independent of the issuing platform's user database — a property that matters for G1: a verifier checks a signature, not a login.

4.2  Device attestation

key_attestation = {
   0: platform,   ; tstr — "apple.appattest" | "android.playintegrity" | "none"
   1: assertion,  ; bstr — platform-specific attestation blob
   2: nonce,      ; bstr — challenge the assertion was produced over
   3: asserted_at ; uint — epoch seconds
}

Attestation establishes that the signing key resides in hardware and that the producing software was unmodified. Per [1] §4 this bounds an adversary's software capability but does not constrain the physical world; verifiers must not treat a valid attestation as evidence that an activity occurred. Its contribution is bounded by the solo ceiling of §7.3.

4.3  Activity

activity = {
   0: sport,        ; tstr — "running" | "cycling" | "walking" | "swimming" | ...
   1: started_at,   ; uint — epoch seconds
   2: ended_at,     ; uint
   3: distance_m,   ; uint
   4: moving_s,     ; uint
   5: elevation_m,  ; int, optional
   6: metrics       ; map, optional — sport-specific aggregates (cadence, power, HR)
}

Aggregates are retained in every profile, including MINIMAL. This is deliberate: the claim "21.1 km in 1:29:14" is the assertion being proven, and it must survive redaction of the coordinates that would otherwise disclose where.

5  Track commitment

5.1  Why a Merkle Mountain Range

Encounter evidence requires committing to the track so far at the moment two athletes meet ([1] Theorem 1). A conventional balanced Merkle tree cannot produce prefix roots without recomputation, and the prefix set is not known until the activity ends. A Merkle Mountain Range [4] — an append-only accumulator maintained as a list of perfect binary subtrees — supports appending a leaf in amortised O(1), and yields a root over the first n leaves at any point, which is exactly the prefix commitment required. It is therefore the structure this specification mandates.

track = {
   0: mmr_root,     ; bstr  — 32 bytes, root over all samples
   1: count,        ; uint  — number of samples committed
   2: sample_hash,  ; tstr  — "sha256"
   3: samples,      ; array — omitted in MINIMAL; redacted in REDACTED (§8)
   4: redactions    ; array — [{from, to, reason}], present iff profile = REDACTED
}

sample = [ t, lat_e7, lon_e7, alt_cm, acc_cm, flags ]   ; fixed-order array

leaf_i   = SHA-256( 0x00 || canonical_cbor(sample_i) )
parent   = SHA-256( 0x01 || left || right )
prefix_root(n) = bag_peaks( peaks of the MMR after n appends )

Domain separation bytes 0x00/0x01 prevent second-preimage attacks that conflate leaves with internal nodes [5]. Coordinates are integers in units of 10−7 degrees; floating-point encodings must not be used, since their canonicalisation is implementation-dependent and would break G4.

Normative — rounding. Conversion from a floating-point sensor reading to the integer sample must round half away from zero. Language defaults disagree: JavaScript's Math.round breaks ties toward +∞ (so −1.5 → −1 while 1.5 → 2), C's round() breaks ties away from zero, and Python's round() uses banker's rounding. Left unspecified, three conforming-looking encoders derive three different integers from one GPS fix, and therefore three different content addresses for one activity. Exact halves are rare at 10−7 degree resolution, which is exactly why an unspecified tie-break would survive testing and then break a single file, once, unreproducibly.
Normative — inclusion and prefix proofs. An implementation must be able to produce, for any committed sample, an inclusion proof against mmr_root, and for any ncount, the prefix root over the first n samples. Verifiers must reject an encounter whose committed prefix root does not match the prefix root recomputed at the sample index nearest the encounter's timestamp.

6  Encounter records

The evidentiary core. Full protocol semantics — radio exchange, binding modes, timing windows — are specified in the companion engineering document [6]; this section defines only the serialised artefact and its validation.

encounter = {
   0: peer_pubkey,   ; bstr — counterparty Ed25519 key
   1: t_q,           ; uint — epoch seconds, quantised to 30 s
   2: geo,           ; tstr — geohash, precision 7 (~76 × 152 m)
   3: self_prefix,   ; bstr — subject's MMR prefix root at t_q
   4: peer_prefix,   ; bstr — counterparty's prefix root at t_q
   5: nonce,         ; bstr — 16 bytes; 8 contributed by each party
   6: medium,        ; uint — 0 SERVER | 1 BLE | 2 UWB | 3 OPTICAL | 4 VIDEO
   7: binding,       ; uint — 0 DIRECT | 1 DEFERRED
   8: self_sig,      ; bstr — Ed25519 over canonical_cbor(keys 0..7)
   9: peer_sig       ; bstr
}
Normative — encounter validation. A verifier must reject an encounter unless all hold: (a) both signatures verify over the canonical encoding of keys 0–7; (b) self_prefix equals the prefix root recomputed from the subject's own track at the sample index nearest t_q; (c) t_q lies within [started_at, ended_at]; (d) the subject's own position at t_q, where disclosed, lies within the geohash cell of key 2 expanded by 150 m; (e) nonce is unique within the file. Condition (b) is what makes retroactive track editing detectable and must not be skipped when samples are present.

Note that (b) is checkable from the file alone even though the counterparty's track is absent: the subject's own prefix root is recomputable from the subject's own samples. The counterparty's peer_prefix is not checkable offline — it requires the counterparty's file — which is why cross-party graph consistency is an endorsement (§9), asserted by a party that has seen both.

7  Evidence

evidence = {
   0: params_id,     ; tstr — identifier of the parameter set, e.g. "fs-v1"
   1: params_digest, ; bstr — SHA-256 of the canonical parameter document
   2: terms,         ; map  — { term_name: bans }  (signed decimal, 4 dp)
   3: total_bans,    ; tstr — decimal string; sum of terms after caps
   4: tier,          ; tstr — "BRONZE"|"SILVER"|"GOLD"|"PLATINUM", optional
   5: caps_applied   ; array — names of caps that bound the total, e.g. ["solo_ceiling"]
}

7.1  Denomination

Evidence is denominated in bans (log10 likelihood odds), which add for conditionally independent sources [1] §3. Posterior probability under a prior of even odds follows as P = 1 − 1/(1 + 10total). Decimal strings are used rather than CBOR floats so that total_bans is reproducible byte-for-byte across platforms.

7.2  Parameter binding

A score is meaningless without the parameters that produced it. params_digest binds the proof to a specific, publishable parameter document. A verifier that does not recognise params_id must either fetch and check the document against its digest, or report the evidence as unevaluated — it must not silently substitute its own parameters, which would produce a different verdict under the same file.

7.3  Caps are part of the format

The solo ceiling of [1] Corollary 1 is not an implementation detail; it is a claim about what the file can prove. Where device-local terms are bounded by it, the encoder must record "solo_ceiling" in caps_applied, so a verifier can distinguish a proof that reached its evidentiary limit from one that merely scored low.

8  Privacy profiles

The property that distinguishes this format from FIT: the evidence survives redaction of the track. Encounter signatures cover prefix roots, not coordinates; aggregates are retained independently. An athlete can therefore prove performance without disclosing route.

Table 1. Disclosure profiles. Evidence remains verifiable in all three.
ProfileTrack samplesEncounter geoOffline-verifiable claims
FULL (0)alldisclosed everything, including prefix recomputation (§6b)
REDACTED (1)privacy zones and endpoints removed; redactions lists the excised rangesdisclosed all except prefix checks inside redacted ranges
MINIMAL (2)none — root and count only unchanged — signed, immutable signatures, aggregates, evidence total, tier
Normative — redaction integrity. mmr_root and count must be those of the complete track, unchanged by redaction. Re-rooting a redacted track would sever every encounter binding and silently destroy the evidence.

Redaction must not modify encounter records in any way — including coarsening geo. Every encounter field is covered by both parties' signatures and contributes to encounters_root; mutating one post hoc invalidates the signatures, changes activity_root, and destroys precisely the evidence redaction is meant to preserve. Coarser encounter geography is a protocol parameter — sign a shorter geohash at exchange time — not a redaction step. (This corrects an error in an earlier draft, caught by the reference implementation's root-invariance test.)

A verifier encountering a REDACTED or MINIMAL profile must skip prefix recomputation for unavailable ranges and must report those checks as not performed rather than as passed.

The last clause matters more than it appears. A verifier that reports unperformed checks as passed converts a privacy feature into a forgery vector; the distinction between verified and not checked must reach the consumer of the verdict.

9  Endorsements and the activity root

activity_root = SHA-256( 0x02 ||
                 canonical_cbor([ version, subject_digest, activity_digest,
                                  mmr_root, encounters_root, evidence_digest ]) )

encounters_root = MMR root over SHA-256(canonical_cbor(encounter_i)), i ascending

The activity root is the content address: a stable 32-byte identifier for the proof, independent of profile — since redaction alters neither mmr_root nor the encounter set.

endorsement = {
   0: kind,      ; uint — 0 VERIFIER | 1 GRAPH_CONSISTENCY | 2 CHIP_TIMED | 3 ONCHAIN
   1: issuer,    ; bstr — issuer public key or contract address
   2: statement, ; map  — kind-specific assertion
   3: issued_at, ; uint
   4: sig        ; bstr — over canonical_cbor([activity_root, kind, issuer, statement, issued_at])
}

Endorsements carry claims that cannot be checked from the file alone: GRAPH_CONSISTENCY (a party has seen both sides of the encounters and found no contradiction), CHIP_TIMED (an official timing system corroborates the result), ONCHAIN (the root is anchored). They are optional and additive: a verifier that trusts no issuer still obtains a verdict from keys 0–6, merely a weaker one. Trust is therefore opt-in and explicit, which is the property that lets the format outlive any particular issuer.

9.1  On-chain binding

For settlement, activity_root is signed as an EIP-712 typed message and consumed by a contract that never sees the underlying track:

Settlement(bytes32 activityRoot, address athlete, uint256 totalBansE4,
           uint64  notAfter,      uint256 challengeId)

totalBansE4 is the evidence total scaled by 104, allowing a contract to require, say, a Platinum threshold (≥ 40000) without floating-point arithmetic. A settlement contract thus enforces evidentiary quality on-chain while holding no personal data — the separation of verification from value described in [1] §12.

10  Interoperability

An .fsp should wrap rather than replace the athlete's existing file:

source = {
   0: format,   ; tstr — "fit" | "gpx" | "tcx"
   1: digest,   ; bstr — SHA-256 of the original bytes
   2: embedded  ; bstr — the original bytes, optional
}

This yields a migration path that costs adopters nothing: devices continue to emit FIT, platforms continue to parse it, and the proof travels alongside. Conversion from .fsp to GPX is lossy but always possible; conversion from GPX to a meaningful .fsp is not possible at all, because the evidence does not exist in the source. That asymmetry is the point of the format.

11  Verification procedure (normative)

A conforming verifier must perform the following in order, and must report each step as passed, failed, or not performed.
  1. Decode. Parse as deterministic CBOR; re-encode and confirm byte equality. Reject on mismatch (G4).
  2. Version. Reject unknown major versions. Preserve unknown keys (§12).
  3. Subject. Validate pubkey length; if key_attestation is present, verify it against the platform's published roots. A failed attestation must not abort verification — it zeroes the attestation term.
  4. Track. If samples are present, recompute the MMR root over count leaves and compare to mmr_root. Reject on mismatch.
  5. Encounters. For each, apply §6 (a)–(e). Discard those that fail; do not reject the file.
  6. Activity coherence. Check aggregates against the track where disclosed, and against sport-specific physical identities [1] §5 — for running, v = f·L within envelope; for cycling, tan θ = v2/(r g) where gyroscope data is present.
  7. Evidence. Resolve params_id, confirm params_digest, recompute terms from surviving evidence, and confirm total_bans. Where recomputation is impossible under the profile, report not performed.
  8. Endorsements. Verify signatures. Apply only those from issuers the verifier independently trusts.
  9. Root. Recompute activity_root and compare against any external reference (on-chain anchor, cited identifier).

The verdict must be reported as a total in bans together with the list of checks not performed. A bare tier label must not be presented without that list, since a Platinum tier computed with four checks skipped is not a Platinum proof.

12  Versioning and extensions

Version 1 assigns container keys 0–9; 10–31 are reserved for future core use, and 32+ are available to registrations. Extensions are namespaced:

extensions = { "com.example.hrv": { ... }, "org.itra.index": { ... } }

Decoders must preserve unknown keys when re-serialising, so that a proof passing through an intermediate tool retains its content address. Adding an evidence term is a minor change (unknown terms contribute zero and are listed as not evaluated); changing a digest construction, the canonical encoding, or the semantics of an existing key is a major change requiring a version bump.

13  Security considerations

ThreatMitigationResidual
Track edited after the factEncounter prefix bindings (§6b) Ranges after the last encounter remain editable
Fabricated encountersTwo signatures; independence weighting [1] §7 Genuine collusion between real athletes
Sybil counterpartiesHardware-attested keys raise identity cost Attested devices can still be bought
Replay of a whole proofNonces; notAfter in settlement Verifiers must track spent roots
Redaction abused to hide contradictionsUnperformed checks reported explicitly (§8) Consumers may ignore the report
Parameter substitutionparams_digest binding (§7.2) Requires published parameter documents
Key compromiseNon-extractable hardware keys; revocation via endorsement Proofs issued before revocation stay valid
Cryptographic obsolescenceAlgorithm agility via sample_hash; version bump for suite changes Long-lived proofs may need re-anchoring

Two properties are worth stating plainly. First, a proof is evidence, not a verdict: it carries a likelihood ratio, and the decision threshold belongs to the verifier, who alone knows what is at stake. Second, absence of evidence is not evidence of fabrication — a solo run in a sparse network scores low because nobody was there, not because it did not happen. Consumers that conflate the two will exclude honest athletes in low-density regions, which is an equity failure as much as a technical one.

14  Why a format rather than an API

An API makes consumers dependent on the issuer's availability, pricing and continued existence. A format does not. GPX has outlived its authoring company by two decades and is read by software that has never contacted Topografix; that durability is a property of the artefact, not the organisation.

The strategic consequence is that verification and value can separate cleanly. FlowStake issues proofs and operates the network that makes strong proofs possible; settlement contracts, insurers, race organisers and ranking systems consume them without asking permission and without trusting the issuer — because §11 lets them check for themselves. Adoption of a verifiable format is not a partnership; it is a decision by the consumer alone. That is the property this specification exists to create.

References

  1. R. Razumov, "Proof of Physical Activity: Verifiable Human Movement from Adversarial Devices and Chance Encounters," FlowStake LLC, draft v0.9, 2026.
  2. S. Bradner, "Key words for use in RFCs to Indicate Requirement Levels," BCP 14, RFC 2119, 1997; B. Leiba, RFC 8174, 2017.
  3. C. Bormann and P. Hoffman, "Concise Binary Object Representation (CBOR)," RFC 8949, 2020 — §4.2 deterministic encoding.
  4. P. Todd, "Merkle Mountain Ranges," 2016; and the append-only log construction of B. Laurie, A. Langley, E. Kasper, "Certificate Transparency," RFC 6962, 2013.
  5. Second-preimage resistance in Merkle trees: RFC 6962 §2.1 leaf/node domain separation.
  6. FlowStake, "Encounter Protocol — data model, BLE exchange, RPCs, verification pass," internal specification, 2026.
  7. S. Haber and W. S. Stornetta, "How to time-stamp a digital document," Journal of Cryptology 3(2), 1991.
Status. Draft v0.9, for review. The container layout, MMR commitment and verification procedure are stable enough to implement against; key assignments in §12 may shift before v1.0, and the evidence parameter document referenced by params_id is not yet calibrated — see [1] §13.1. Reference implementation and test vectors to follow. © 2026 FlowStake LLC.