Three pure functions, in order.
Every stage is deterministic and side-effect-free. Given the same input and canonicalization version, the output is byte-identical everywhere it runs.
- 01
Harvest & canonicalize
Text is gathered from the protocol title, sections, and optional steps, then reduced to sorted, deduplicated entity sets: unit-normalized quantities, and technique / material / statistics / standards vocabulary, plus a small set of design attributes.
canonicalize(input) - 02
Serialize
The canonical form is written to a single JSON string with a fixed key order and pre-sorted arrays. A source-size counter is deliberately excluded, so trimming whitespace never changes the result.
serializeCanonical(c) - 03
Content-address
That exact string is hashed to a 128-bit digest, rendered as 26 Crockford base32 characters, and prefixed to produce the identifier: rpf_<26 chars>.
fingerprintId(serialized)
What goes in.
The input is a protocol's title and its sections — the 12 structured sections an Olto protocol carries — with the structured step graph included when present. No account, identity, or metadata is read; only the content itself.
interface FingerprintInput {
title?: string | null
sections?: unknown // the protocol's 12 sections
steps?: Array<Record<string, unknown>> | null
}- Title, then sections, then any steps are walked recursively; every string and number encountered is collected (arrays and objects are traversed to a depth of 6).
- Text is harvested case-preserving, so the quantity scanner can tell molar M / mM / nM from length m / mm / nm.
- Harvested text is bounded to 80,000 characters — a guard against pathological inputs, not a limit you meet with a normal protocol.
Numbers become unit-normalized tokens.
A number followed by a recognized unit is parsed and multiplied to a single canonical unit per dimension, then rounded to 6 significant figures and emitted as a dimension-tagged token. Unit matching is case-sensitive and longest-literal-first, so mM wins over M and molarity never collides with length.
Temperatures in °F or K are converted to Celsius and rounded to the nearest degree. The extracted quantity tokens are deduplicated and sorted; up to 300 distinct tokens are retained. Values that render identically after rounding are, by design, treated as the same quantity.
Methods become a controlled vocabulary.
On a lower-cased copy of the text, fixed pattern sets map many phrasings to one tag — immunoblot and western blot both become western-blot. Each set is matched, deduplicated, and sorted; only what the vocabulary recognizes contributes to identity.
Techniques
up to 40pcr · qpcr · western-blot · elisa · crispr · sequencing · flow-cytometry · hplc · mass-spec · centrifugation · microscopy · transfection
Materials & reagents
up to 40dmem · rpmi · fbs · pbs · tris · edta · sds · dmso · trypsin · antibody · primer · buffer
Statistical methods
up to 20t-test · anova · chi-square · regression · mann-whitney · kruskal-wallis · tukey · bonferroni · survival · correlation
Cited standards
up to 16ISO / IEC · ASTM · IEC · USP · EP · EN · OECD · AOAC · CLSI · 21 CFR (matched by code pattern, upper-cased)
Design attributes
A small structured block of methodological signals is also extracted — presence flags plus a replication count — and always contributes to the canonical form.
One canonical string.
The canonical form is serialized to JSON with a fixed key order and arrays that are already sorted, so the bytes are stable. The source-character count is excluded — it is a size metric, not part of the experiment's identity — which is why reformatting or trimming whitespace never changes the fingerprint.
{ "v": 1,
"quantities": [...], // sorted, deduplicated
"techniques": [...],
"reagents": [...],
"stats": [...],
"standards": [...],
"design": { "controls", "replication", "randomization",
"blinding", "sampleSize", "safety", "statistics" } }A 128-bit non-cryptographic content hash.
The serialized string is hashed to a 128-bit digest and rendered as 26 characters. The hash is hand-rolled to run identically in the browser, on the server, and on the edge — it uses only 32-bit integer math (Math.imul and shifts), with no node:crypto and no big-integer literals.
Four 32-bit lanes
Each lane is an independent FNV-1a-style rolling hash seeded with a distinct constant and stepped by a distinct prime; character position is folded in so anagrams do not collide. Two cross-mixing steps per character perturb the lanes against each other.
Avalanche finalizer
The input length is folded into each lane, then every lane is run through Murmur3’s fmix32 finalizer, so a single-character change flips roughly half the output bits.
Base32 rendering
The four 32-bit words are read big-endian into 26 lowercase Crockford base32 characters (the alphabet omits i, l, o and u to stay unambiguous when read aloud or transcribed).
This is a content address, not a signature. There is no secret key and no cryptographic guarantee of preimage, second-preimage, or collision resistance. “Tamper-evidence” here means exactly one thing: anyone can recompute the fingerprint from the public protocol and compare. That detects accidental drift and honest mistakes — it does not, and is not meant to, stop a determined adversary.
The rpf_ prefix, and why the version matters.
- Format: rpf_ followed by 26 Crockford base32 characters (128 bits). A compact rpf_<8> short form exists for display, but the full identifier is what you cite and compare.
- Canonicalization version: the current version is v1, and it is embedded in the serialized form (the v field). Two fingerprints are only meaningfully comparable when they were produced by the same version.
- Evolving the rules: improving the canonicalizer changes the canonical form, and therefore the identifier, for the same protocol. That is expected — a fingerprint identifies content under a specific algorithm version, so the version travels with it.
128 bits, honestly.
Across any realistic corpus of protocols, an accidental collision of two distinct canonical forms is astronomically unlikely at 128 bits. That is the property the fingerprint relies on to serve as an identifier.
Because the hash is non-cryptographic and unkeyed, it is not collision-proof against a determined adversary who deliberately constructs inputs to collide, or who works to craft content matching a target fingerprint. Treat rpf_ as an identifier, not a security boundary: excellent for citation, deduplication, and detecting accidental drift; never a substitute for a signature where an adversarial party is in scope.
Where it can be wrong.
Canonicalization is a deliberate, lossy reduction to a reproducibility backbone. That produces two symmetric error modes — and knowing them is the point of publishing this spec. It is why we say the same experiment usually, not always, yields the same fingerprint.
Different experiments → the same fingerprint.
- Two protocols share the same normalized quantities, techniques, materials, statistics, standards, and design flags but differ in detail the canonicalizer never captures — a specific gene target, cell line, vendor, or the ordering of steps.
- Numeric values that round to the same 6 significant figures, or units that reduce to the same canonical token, are treated as equal.
- Anything outside the recognized vocabularies and quantity grammar is simply not part of the identity, so two experiments can match on the parts that are captured while differing on the parts that are not.
The same experiment → different fingerprints.
- A quantity written in a form the grammar does not match — spelled out ("five milliliters"), a range ("5–10 mL"), or a unit not in the table — is dropped, so an otherwise identical protocol can fingerprint differently.
- A technique, reagent, or statistic phrased outside the recognized patterns, or a typo / unusual abbreviation, is not captured.
- Any change to the canonicalization version produces a different canonical form; fingerprints are only comparable within the same version.
What a fingerprint does not do.
It identifies normalized content per a specific algorithm version. That is a genuinely useful thing — and it is only that thing.
- Prove authorship — anyone with the same content computes the same identifier; it carries no author identity.
- Prove that the method is scientifically valid or correct.
- Prove that the procedure was ever actually performed.
- Provide a trusted timestamp — it contains no time and no external attestation.
- Constitute a digital signature or legal provenance — there is no key, so it cannot bind an identity or resist a determined adversary.
- Reconstruct the protocol — a 128-bit identifier cannot be inverted back into the source text.
The identifier vs. the capsule.
Because the fingerprint is one-way, it cannot reconstruct a protocol. When you need the content back, Olto emits a separate, lossless artifact — the Reproducibility Capsule — that carries the protocol itself and re-derives its fingerprint on decode.
26 characters, one-way, irreversible. An identifier for the canonical form — for citing, deduplicating, and detecting whether that form has changed.
The protocol's title and sections, losslessly compressed into one portable token that decodes offline — and recomputes the fingerprint so a mismatch flags altered content.