Veydrin Rating Standard
A decentralized, git-backed standard for verifiable software ratings and usage counters. Cryptographically signed, publicly auditable, platform-independent. No app store required.
The Veydrin Rating Standard (VRS) defines a decentralized system for collecting, storing, and displaying software ratings and usage counters. Every record is a cryptographically signed JSON document committed to a public git repository. No centralized service controls the data. No platform gatekeeper mediates trust.
All VRS documents MUST be serialized as JSON (RFC 8259) and encoded as UTF-8. The RECOMMENDED file extension is .vrs.json.
VRS defines two record types:
VRS is not a child of VODS. It is a sibling protocol that references VODS for contribution gating. Both protocols share the same design philosophy: offline-capable, privacy-respecting, publicly verifiable, and free from platform dependency.
VRS applies to all software published by The Veydrin Order. Any third-party organization or individual MAY adopt VRS by implementing the conformance requirements in §10.
VRS does not define: the HTTP API for record submission (transport is implementation-defined), server provisioning or deployment, migration from existing rating systems, or user interface design beyond the requirements in §9. A VODS export is a structured data document conforming to the Veydrin Open Data Standard; the export_id is its unique UUID v4 identifier assigned at creation time.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC 2119] [RFC 8174] when, and only when, they appear in ALL CAPITALS, as shown here.
A counter record is an anonymous signal that an installation of the software exists. It carries no assessment and requires no prior contribution. Any user MAY submit a counter record at any time by tapping a single button in the application.
| Field | Type | Required | Description |
|---|---|---|---|
vrs_version | string | [req] | MUST be "1.0". |
record_type | string | [req] | MUST be "counter". |
record_id | string | [req] | UUID v4, unique per record. Generated client-side. |
source_app | string | [req] | Reverse-domain package identifier (e.g., "org.veydrin.apiara"). MUST match ^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)+$. Max 256 characters. |
source_app_version | string | [req] | Application version per Semantic Versioning 2.0.0. Max 64 characters. |
timestamp | string | [req] | ISO 8601 with timezone (RFC 3339). Time of record creation. Servers SHOULD reject timestamps more than 5 minutes in the future. |
public_key | string | [req] | Raw 32-byte Ed25519 public key, Base64-encoded (standard alphabet, with padding). Exactly 44 characters. |
signature | string | [req] | Raw 64-byte Ed25519 signature, Base64-encoded (standard alphabet, with padding). Exactly 88 characters. Computed over the canonical record (see §5). |
null value. The canonical form for signing includes only fields that are present in the record.
A rating record is a quality assessment of the software. It carries a star value (1–5) and an optional free-text comment. A rating record MUST include a contribution_proof that demonstrates genuine usage. VRS defines two proof types:
vods:{export_id} — References the export_id of a previously accepted VODS export. The server MUST verify the export exists in the dataset. Used by applications that contribute data publicly (e.g., Apiara submitting OHDS data to Ojarim).counter:{record_id} — References the record_id of a previously accepted VRS counter record from the same public_key. The server MUST verify the counter exists and the public keys match. Used by applications whose data is private and never contributed (e.g., financial or health software).The proof type prefix (vods: or counter:) MUST be present. Each application declares which proof type it requires. Applications that contribute data publicly SHOULD require vods: proofs for the stronger guarantee.
| Field | Type | Required | Description |
|---|---|---|---|
vrs_version | string | [req] | MUST be "1.0". |
record_type | string | [req] | MUST be "rating". |
record_id | string | [req] | UUID v4, unique per record. |
source_app | string | [req] | Reverse-domain package identifier. Same format as counter record. Max 256 characters. |
source_app_version | string | [req] | Semantic version per SemVer 2.0.0. Max 64 characters. |
timestamp | string | [req] | ISO 8601 with timezone (RFC 3339). Servers SHOULD reject future timestamps. |
stars | integer | [req] | Integer from 1 to 5 inclusive. Whole stars only. |
comment | string | [opt] | Free-text comment. Maximum 500 Unicode scalar values. MUST NOT contain personal data (see §11). If absent, MUST be omitted entirely (not null). |
contribution_proof | string | [req] | Proof of genuine usage. Format: vods:{export_id} or counter:{record_id}. Server MUST verify the referenced record exists and is valid. See proof types above. |
public_key | string | [req] | Raw 32-byte Ed25519 public key, Base64-encoded. Exactly 44 characters. |
signature | string | [req] | Raw 64-byte Ed25519 signature, Base64-encoded. Exactly 88 characters. |
{
"vrs_version": "1.0",
"record_type": "counter",
"record_id": "b7e4f2a1-9c83-4d1f-a562-3e8b01cc7f94",
"source_app": "org.veydrin.apiara",
"source_app_version": "2.0.0",
"timestamp": "2026-03-31T08:14:00-05:00",
"public_key": "MCowBQYDK2VwAyEA...",
"signature": "dGhpcyBpcyBhIHNp..."
}
{
"vrs_version": "1.0",
"record_type": "rating",
"record_id": "a1f8c3d2-7b04-4e9a-b331-6f2a08dd2c93",
"source_app": "org.veydrin.apiara",
"source_app_version": "2.0.0",
"timestamp": "2026-03-31T09:22:00-05:00",
"stars": 5,
"comment": "Best beekeeping app I have used. The varroa tracking alone is worth it.",
"contribution_proof": "a3f8c2e1-04b7-4d9e-b221-7f3a09cc1d82",
"public_key": "MCowBQYDK2VwAyEA...",
"signature": "c2lnbmF0dXJlIGRh..."
}
{
"vrs_version": "1.0",
"record_type": "rating",
"record_id": "d4e2b1a3-8f07-4c5d-9912-1a3b04ee5f76",
"source_app": "org.veydrin.apiara",
"source_app_version": "2.0.0",
"timestamp": "2026-03-31T11:45:00-05:00",
"stars": 4,
"contribution_proof": "f7c1d3e4-2a08-4b6f-8e33-9d5c02bb8a41",
"public_key": "MCowBQYDK2VwAyEA...",
"signature": "YW5vdGhlciBzaWdu..."
}
{
"source_app": "org.veydrin.apiara",
"counter_total": 42,
"rating_count": 0,
"rating_average": null,
"rating_distribution": {
"1": 0, "2": 0, "3": 0, "4": 0, "5": 0
},
"last_updated": "2026-03-31T10:00:00Z"
}
null, empty string, or whitespace. Servers MUST reject whitespace-only comment values.rating_count is 0, rating_average MUST be null. All rating_distribution values MUST be 0.contribution_proof with a different public_key MUST be accepted — the proof validates the contribution, not the device.On first launch, the application MUST generate an Ed25519 keypair and store it in secure local storage. The private key MUST NOT leave the device. The public key is embedded in every VRS record submitted by that installation.
If the application is uninstalled and reinstalled, a new keypair is generated. The previous keypair is lost. This is by design — it provides natural rate-limiting without centralized enforcement.
Before signing, the record MUST be serialized to its canonical form: all fields except signature, serialized as a JSON object with keys sorted lexicographically, no whitespace, UTF-8 encoded. The signature is computed over this canonical byte string.
// Canonical form for signing (no whitespace, keys sorted, no signature field):
{"contribution_proof":"a3f8...","public_key":"MCow...","record_id":"a1f8...","record_type":"rating","source_app":"org.veydrin.apiara","source_app_version":"2.0.0","stars":5,"timestamp":"2026-03-31T09:22:00-05:00","vrs_version":"1.0"}
Ed25519 as defined in RFC 8032. Implementations MUST use Ed25519 (pure mode, not Ed25519ctx or Ed25519ph). The signature is Base64-encoded (standard alphabet, with padding) and placed in the signature field.
signature. Adding arbitrary extension fields would break the canonical form and invalidate signatures, or require every implementation to sort and serialize unknown fields identically — a source of interoperability failures. VRS is intentionally minimal. New capabilities are added through version bumps (vrs_version), not extensions. This is a deliberate design choice, not an oversight.
The following test vectors use a real Ed25519 keypair. Implementations MUST be able to verify these signatures. All three records share the same keypair.
Public key (Base64): 27uBCPa4dYsnFMSQVTVwCbKo28or/2AQ8XFDJp8ro3g=
Vector 1 — Counter record
// Canonical form (input to sign):
{"public_key":"27uBCPa4dYsnFMSQVTVwCbKo28or/2AQ8XFDJp8ro3g=","record_id":"b7e4f2a1-9c83-4d1f-a562-3e8b01cc7f94","record_type":"counter","source_app":"org.veydrin.apiara","source_app_version":"2.0.0","timestamp":"2026-03-31T08:14:00-05:00","vrs_version":"1.0"}
// Signature (Base64):
9HBf+Dl6Zzu35Veunyqik25jxxv/spRfZRJIvbuo+C7olErB+D4+vN9znim8D1i+vXmgLbNQfds0+FAHpzk7DA==
Vector 2 — Rating record (with comment)
// Canonical form:
{"comment":"Excellent varroa tracking.","contribution_proof":"a3f8c2e1-04b7-4d9e-b221-7f3a09cc1d82","public_key":"27uBCPa4dYsnFMSQVTVwCbKo28or/2AQ8XFDJp8ro3g=","record_id":"a1f8c3d2-7b04-4e9a-b331-6f2a08dd2c93","record_type":"rating","source_app":"org.veydrin.apiara","source_app_version":"2.0.0","stars":5,"timestamp":"2026-03-31T09:22:00-05:00","vrs_version":"1.0"}
// Signature:
ZsauOCF9UjGXPZjWqMg2Vjva8sQuJhmZta+HQ8VQHhzPpFrNn7S68s61fWmIPT0mQRXsBLAY469h7PdrFHDyBw==
Vector 3 — Rating record (no comment)
// Canonical form (note: comment field absent, not null):
{"contribution_proof":"f7c1d3e4-2a08-4b6f-8e33-9d5c02bb8a41","public_key":"27uBCPa4dYsnFMSQVTVwCbKo28or/2AQ8XFDJp8ro3g=","record_id":"d4e2b1a3-8f07-4c5d-9912-1a3b04ee5f76","record_type":"rating","source_app":"org.veydrin.apiara","source_app_version":"2.0.0","stars":4,"timestamp":"2026-03-31T11:45:00-05:00","vrs_version":"1.0"}
// Signature:
MlXvB2rZLrqGHGsUR2OKEBJ5jN9hSC4/nqy1mNIMzj2gsM6g22beK82JZPR4Twjze7vj5uMjzkeNk4YOAo6HDQ==
null).
VRS records are stored in a public git repository. Each application has its own directory. Records are stored as individual JSON files named by their record_id.
ratings/
org.veydrin.apiara/
counters/
b7e4f2a1-9c83-4d1f-a562-3e8b01cc7f94.json
...
ratings/
a1f8c3d2-7b04-4e9a-b331-6f2a08dd2c93.json
...
org.ninthstar.covara/
counters/
...
ratings/
...
The repository MUST be publicly readable. Write access is controlled by the repository owner (the organization publishing the software). Records are committed by the server after validation.
When the server commits a counter or rating record, it SHOULD assign a sequential supporter number to the record. This number represents the order in which the record was accepted (e.g., the 247th counter record receives supporter number 247). The supporter number is returned in the server response and MAY be displayed in the application as a personal identifier (e.g., "#247").
The supporter number is NOT stored in the signed record itself — it is assigned server-side after validation. It is stored in the aggregate.json file and optionally in a lightweight index file for lookup.
Each application directory SHOULD contain an aggregate.json file that is regenerated after each new record. This file provides a pre-computed summary for efficient in-app display.
| Field | Type | Required | Description |
|---|---|---|---|
source_app | string | [req] | Package identifier matching the directory name. |
counter_total | integer | [req] | Total accepted counter records. |
rating_count | integer | [req] | Total accepted rating records. |
rating_average | number|null | [req] | Arithmetic mean of all non-quarantined stars values, rounded to one decimal (half-up). null when rating_count is 0. |
rating_distribution | object | [req] | Keys "1" through "5", each an integer count of ratings at that star level. |
last_updated | string | [req] | ISO 8601 timestamp of last regeneration. |
{
"source_app": "org.veydrin.apiara",
"counter_total": 1847,
"rating_count": 312,
"rating_average": 4.7,
"rating_distribution": {
"1": 3,
"2": 5,
"3": 18,
"4": 72,
"5": 214
},
"last_updated": "2026-03-31T10:00:00Z"
}
For applications with more than 100,000 records, repository operators SHOULD consider:
git gc --aggressive) to reduce on-disk size.archive/ branch while preserving the aggregate on the main branch.counters/2026/, counters/2027/ to limit directory size.The aggregate file MUST always reflect the full count regardless of archival — it is the single source of truth for display purposes.
Before committing a record to the repository, the server MUST perform all of the following checks:
vrs_version field is a supported version.record_type is "counter" or "rating".record_id is a valid UUID v4 and does not already exist in the repository.signature is valid against the public_key using the canonical form (§5).stars value (if rating) is an integer from 1 to 5 inclusive.comment (if present) does not exceed 500 UTF-8 characters.public_key and source_app combination for the same record_type.contribution_proof value matches the export_id of an accepted VODS export in the associated dataset.If any check fails, the server MUST reject the record and MUST NOT commit it.
Applications displaying VRS data MAY verify individual records by checking the Ed25519 signature against the embedded public key. Applications that fetch only the aggregate.json file are trusting the repository owner's computation — this is acceptable because the raw data is always publicly available for independent verification.
quarantined/ directory with a commit message explaining the reason, but it MUST NOT be destroyed. The git history preserves evidence.
VRS does not claim to be immune to manipulation. It claims to make manipulation expensive, visible, and auditable.
Rating records require a valid contribution_proof. For data-contributing apps (vods: proof), an attacker must generate a valid VODS export that passes schema validation — producing realistic beekeeping or cultivation data is real effort for zero payoff. For private apps (counter: proof), the attacker must have submitted a counter first, proving an actual installation exists. The vods: gate is stronger; the counter: gate is appropriate for applications whose data cannot be shared publicly.
Each installation generates exactly one Ed25519 keypair. Submitting multiple ratings for the same application requires multiple installations, each with its own keypair, each with its own valid VODS contribution. This is not impossible, but it is slow and effortful.
The entire ratings repository is public. Anyone can:
A flood of ratings from keys with minimal or suspiciously similar contributions is visible to anyone who looks. Transparency is the deterrent.
Servers SHOULD implement rate limiting on new record submissions. A RECOMMENDED baseline: no more than 10 new records per source IP per hour. This prevents automated flooding while permitting normal usage patterns.
Conforming applications MUST display both the counter total and the rating aggregate within the application. The display SHOULD include:
Applications SHOULD fetch the aggregate.json file from the repository and cache it locally. The cache SHOULD be refreshed no more than once per 24 hours to avoid unnecessary load on the repository host.
VRS defines four conformance targets:
VRS Counter-Only Profile: A client that implements only counter functionality. MUST satisfy R1, R2, R3, R4 (counter only), R6 (counter display), R7. Rating rules do not apply. This profile lowers the adoption barrier for applications that do not use VODS.
VRS Full Profile: A client that implements both counter and rating functionality. MUST satisfy all rules R1–R8.
In addition to the rules below, all statements using MUST, MUST NOT, REQUIRED, SHALL, or SHALL NOT elsewhere in this specification are normative requirements on the applicable conformance target.
A conforming VRS implementation MUST satisfy all of the following rules:
vrs_version field MUST be set to the version this implementation targets.
source_app. The application SHOULD persist a local flag indicating whether each record type has been submitted.
contribution_proof with a valid proof type prefix (vods: or counter:). The referenced record MUST exist and be valid. The application MUST NOT submit a rating until the proof prerequisite is met.
The Ed25519 public key embedded in each record acts as a pseudonym. It does not reveal the user's identity, device, or location. However, if the same public key appears in both a counter and a rating for the same application, those records are linkable. This linkage is by design — it enables deduplication — but implementations SHOULD document it for users.
The contribution_proof field links a rating to a specific VODS export. In anonymous mode VODS exports, the export_id is a random UUID that does not reveal identity. In tracked mode, the export_id is still a UUID but may be correlated with a longitudinal pseudonym in the dataset. Users who submit ratings after tracked mode exports SHOULD be aware of this potential correlation.
Free-text comments MAY contain personal information inadvertently. Applications SHOULD warn users before submission: "Your comment will be publicly visible in the ratings repository. Do not include personal information." Server-side processing MAY reject comments that match common PII patterns (email addresses, phone numbers), but this is NOT REQUIRED.
Submission of a VRS record constitutes user-initiated disclosure. Applications MUST NOT submit records without explicit user action (tap or click). Applications SHOULD display what data will be submitted before the user confirms. Where GDPR or similar regulations apply, this user action constitutes consent; implementers MUST determine the appropriate legal basis for their jurisdiction.
VRS records are pseudonymous and contain no directly identifying information. Under GDPR Article 17 (right to erasure), whether a pseudonymous public key constitutes personal data depends on jurisdiction and context. If erasure is required, the record MAY be replaced with a tombstone record containing only record_id and a deletion notice, and the aggregate MUST be recomputed.
| Threat | Attack vector | Mitigation | Residual risk |
|---|---|---|---|
| Sybil attack | Multiple installations to submit many ratings | Each requires valid VODS contribution + new keypair | Possible with effort; pattern visible in repo |
| Replay | Re-submitting a valid record | UUID dedup + pubkey dedup per source_app | None — server rejects duplicates |
| Key compromise | Stolen private key | Blast radius: max 2 records (1 counter + 1 rating) | Acceptable — no revocation needed |
| Repository compromise | Attacker gains write access to ratings repo | Git history tamper-evident; MUST NOT force-push | Detectable via commit audit |
| Timestamp manipulation | Backdated or future-dated records | Server SHOULD reject timestamps >5min in future | Backdating not prevented; low impact |
| DoS / flooding | Mass record submissions | Rate limiting (10/IP/hour RECOMMENDED); max 4096 byte records | Server operator must implement rate limiting |
| MITM on submission | Intercepting record in transit | Clients SHOULD verify server TLS certificates; records are signed | Tampered record fails signature check |
| Malicious mirror | Fake aggregate.json served from compromised CDN | Consumers MAY verify by counting actual files in repo | Low — aggregate is convenience, files are truth |
The git history of the ratings repository provides a tamper-evident log. Any modification to a committed record is visible in the commit history. Repository owners MUST NOT force-push or rewrite history on the ratings branch. Consumers of VRS data MAY verify repository integrity by checking that no force-pushes have occurred.
Each record has a unique record_id (UUID v4). The server MUST reject duplicate record_id values. Additionally, the deduplication rule (one public_key per source_app per record_type) prevents replaying a record with a new UUID — the public key would already be present.
If a private key is compromised, the attacker can submit one counter and one rating (if they also possess a valid contribution proof). The blast radius is exactly two records. There is no mechanism for key revocation — the simplicity of VRS depends on not needing one. Two records is an acceptable maximum damage per compromised key.
| Term | Definition |
|---|---|
| Counter | A VRS record type that signals usage of an application. No assessment, no prerequisites. |
| Rating | A VRS record type that assesses software quality (1–5 stars). Requires proof of contribution. |
| Contribution proof | The export_id of a VODS export that has been accepted into the associated dataset. Links a rating to real usage. |
| Canonical form | The deterministic JSON serialization used for signing: keys sorted lexicographically, no whitespace, no signature field, UTF-8 encoded. |
| Installation keypair | An Ed25519 key pair generated on first application launch. The private key never leaves the device. The public key is embedded in all VRS records from that installation. |
| Aggregate file | A pre-computed summary (aggregate.json) of all counter and rating records for a given application. Regenerated after each new record. |
| Quarantined record | A record moved to the quarantined/ directory after being identified as fraudulent. The record is preserved for evidence but excluded from aggregates. |
| Source app | The package identifier (e.g., org.veydrin.apiara) that uniquely identifies the application submitting the record. |
VRS follows Semantic Versioning 2.0.0. The version number appears in the vrs_version field of every record as "MAJOR.MINOR".
Deprecation policy: features MAY be deprecated in a MINOR release. Deprecated features MUST NOT be removed earlier than the next MAJOR release.
VRS is maintained by The Veydrin Order. The specification is authored and reviewed by the project leads (see protocols repository AUTHORS). All design decisions are recorded as Codeberg issues or commit messages in the repository history.
Change process: Anyone may propose changes by opening an issue on the protocols repository. Proposals are reviewed by The Veydrin Order maintainers. Acceptance criteria: the change must improve interoperability, security, or adoption without breaking existing conforming implementations (unless a MAJOR version bump is warranted). Editorial corrections may be applied without an issue.
Errata: Errors discovered after publication may be corrected as inline fixes tagged with [Erratum YYYY-MM-DD] in the changelog. Errata do not change the version number. A list of active errata is maintained in the changelog section.
Code of conduct: Participants in VRS development are expected to engage respectfully and constructively. The Veydrin Order reserves the right to moderate interactions on its repositories.
All intellectual property is published under CC-BY-4.0 with a royalty-free patent commitment — no contributor may encumber VRS with patent claims.
When verifying signatures, the canonical form MUST include all fields present in the record (except signature), sorted lexicographically, regardless of whether the verifier recognizes them. This ensures that a VRS 1.0 verifier can verify records produced by VRS 1.1 that contain new fields.
Conforming consumers MUST ignore unknown fields when processing VRS records. Unknown fields MUST NOT cause validation failure.
The names "VRS" and "Veydrin Rating Standard" are not trademarked. Implementers MAY use the phrases "conforms to VRS v1.0" or "VRS-compatible" without a trademark license, provided the implementation genuinely conforms to this specification.
If The Veydrin Order ceases to operate, stewardship of VRS transfers to the community via the CC-BY-4.0 license. Any party may fork and continue development. The specification and all associated schemas are permanently available in the public repository.
| Version | Date | Changes |
|---|---|---|
v1.0 | 2026-03-31 | Initial release. Counter and rating record types. Ed25519 signing. Git-backed repository structure. Contribution-proof gating for ratings. |