Skip to main content
audit.domains
A domain appraisal you can check

ODVS v1 - draft specification

ODVS, the Open Domain Valuation Standard, is a working name for a way to reduce a domain appraisal to a small canonical record and hash it, so that anyone can recompute the hashes an appraisal carries and check them without trusting the site that produced it.

Status

Draft v1, hash version 1. The hash construction under hash version 1 is fixed: API responses already carry these hashes, so any change to it needs a new hash version. The text describing it is a draft.

  • Licence: none granted yet. The choice is pending with the site owner.
  • Frozen text: not yet. This document may still be clarified.
  • Editions: not yet published.
  • A second implementation in another language: not yet. Both current implementations are JavaScript.
  • A neutral home: not yet. The specification lives on this site.

Test vectors: every vector's hash is listed below, and each vector's input can already be read in the code the verifier runs. No licence covers those inputs yet. A machine-readable vectors file will be published here once the licence is chosen.

Until all of these hold, ODVS is not yet a standard, and this page does not describe it as one.

Scope

ODVS covers how an appraisal is reduced to a canonical record and hashed. It does not cover the valuation model itself: that is the engine's job, identified by an engine id and version inside the record. The point is that two parties, or two engine versions, can compare appraisals byte for byte.

The canonical record

An appraisal keeps the fields that are a pure function of the domain and nothing else: the normalized domain, the engine id and engine version, the estimated value, the confidence score, the optional low and high bounds, and optional named numeric factor outputs (metrics). Database ids, timestamps, comparable-sale lists and anything else that depends on the environment are left out.

Every number must be finite. NaN, Infinity and -Infinity have no canonical bytes and must be refused; negative zero is folded to 0.

Domain normalization

A single pass, in this order:

  1. Trim with ECMAScript String.prototype.trim(), which removes the ECMAScript whitespace and line-terminator sets, including U+00A0, U+FEFF and U+2028.
  2. Lower-case with ECMAScript String.prototype.toLowerCase(), which is locale-independent and maps U+0130 to i followed by U+0307. Internationalized names stay in Unicode and are never converted to punycode.
  3. Remove a URL scheme such as https://.
  4. Remove everything from the earliest /, ? or #.
  5. Remove one leading www. and then one trailing dot.

Because it is a single pass, normalizing twice can change a result further: www.www.x.com becomes www.x.com, and a second pass would give x.com. Hash version 1 pins the single-pass result, and the vectors below check it. Repeating until nothing changes is a candidate for a later hash version.

InputNormalized
Example.COMexample.com
https://www.Acme.io/path?x=1acme.io
www.www.x.comwww.x.com
x.com..x.com.

Serialization

Every hashed string is canonical JSON: UTF-8, no whitespace, object keys sorted recursively, so the order fields arrive in never matters.

  • Keys sort by UTF-16 code unit, the ECMAScript default sort, not by code point. An emoji key therefore sorts before U+FF5A, and "10" before "9".
  • Numbers are written by ECMAScript Number::toString, for example 1e+21, 1e-7 and 0.30000000000000004.
  • Strings are escaped as ECMAScript JSON.stringify escapes them: quote, backslash and control characters escaped, a lone surrogate written as a \uXXXX escape, and all other non-ASCII characters written as-is.

The three hashes

All three are lowercase hex SHA-256 over canonical JSON.

inputCommitment = SHA256({ d: domain, e: engineId, v: engineVersion })
resultDigest    = SHA256(canonical result fields)
appraisalHash   = SHA256({ commitment: inputCommitment, result: resultDigest, hv: 1 })

The input commitment identifies the computation: the same domain on the same engine version always gives the same commitment. The result digest pins the numbers produced. The appraisal hash is the citable id of the whole appraisal.

The determinism property

For a deterministic engine, two appraisals with the same input commitment must have the same result digest. A pair that shares a commitment but differs in its digest is a determinism violation, and anyone holding both can show it.

Conformance

An implementation conforms to this draft when it reproduces every hash below and refuses each of the 3 non-finite inputs (NaN and plus or minus Infinity). The expected hashes were produced by a separate Node implementation and are checked against this site's own code by its test suite. You can run all 18 vectors against the code this site is serving in the ODVS verifier, in your own browser. The input of each vector is readable in the code the verifier runs; a licensed, machine-readable vectors file follows once the licence is chosen.

VectorappraisalHash
minimalb0d5e7148407ab9f9968eb21c9a8606423a584f3c662741a317a77e01cd030a7
full-with-metricsabf569213629754a3d16af46771bddffc7a664bd82a06ae515a499b684644c94
negative-zero-foldsf8b159c51daef953f292c590e552f733787b8edff33409e88d8e15fa92f8705c
whitespace-www-trailing-dota6bf40ed853d282ca0fc71714d6ecd2f89c2037d86c0d6742d312ba59e8c0280
single-pass-double-www364c18b776bae087cc5f44749c0d447708a5f12c9811335a1dc207934d7317b2
single-pass-double-trailing-dot4aa34d680c883a37558be560760b47e909df0af8bc6bde4371f6fe50281e32f8
single-pass-space-before-pathdf2075857b0f990d5d7f4aed0c89291b3e3fda28ed5bd17804e19d5af6968489
idn-not-punycoded55b98b04596776b9551519e97b2988e204f67cb11c3fbdf19960444354ff098c
ecmascript-trim-setc407304a0347246012d64009fbe8115350505e13b12dfaa146590fcaadd4351f
ecmascript-lowercase-dotted-capital-if3320b463edb8e4283a33b6c7689328a259189002a4d09aa5722aad435ed1de2
metric-key-order-utf169fefd161845b4d859b9c2bdaeb9170a52bad0e6760617c3ad712a09cfc7f90a3
metric-key-lone-surrogatecdbb43315cce8ba9774fb94b4b5040c0ddd37eb99be79f4af020226998952f9c
ecmascript-number-formatdf0db218a6457dde868285cc91275d0f4f0280dafcfcd30fbe122a2541b221ef
string-escapingaf4d16d797c25ce91e3dad1a94475ce5af1855cad12688c4286037ee09255425
empty-metrics00f9b134b211d4d11bce7c40955216693bcc8002408300b9b808d62d950e69c4
proto-metric-keyf65aef64f67d9480336029e02b61a1508ae1c7e09575d8c9550bcdb33517e054
low-without-high4232316d7292798cb8d440d7913c12283a28923c2f4da339280c1b78019163bd
production-identity426263de63edefdaaf455989097c9ff64d4e93670b00967fb4d409fcf4ff72f3