datumesh-core
The Rust core of the Datumesh protocol: strict JSON and RFC 8785 canonical bytes, cell URIs, key documents, response envelopes, unit resolution, and offline verification of signed receipts. It implements the specification (SPEC.md and units.md, revision spec-0.4.1), including receipts whose lineage cites whole slices of cells, pending envelopes and cross-mesh resolution, and passes every one of its 491 test vectors for verifier libraries (the 546 formula, formula-eval, excel and decimal vectors are for formula evaluators; this crate evaluates no functions).
Example
[]
= "0.3.5"
Verify the receipt of spec/vectors/receipt/stored-number.json against the key documents the vector carries, then recompute it with the descriptor and value the vector's recompute object supplies:
use ;
use ;
It prints verified signed receipt 19a5fe3a744c5792f747fb4c286ce396ab44b95922de4f07937502fe595b6f81, then the same line with recomputed. A resolver that has the descriptor and inputs but cannot evaluate the function (evaluate returns None) leaves the receipt ok at level signed with code E_EVAL_UNAVAILABLE, one of the two codes of SPEC 10.2 that are not failures; the other, E_INPUT_SUPERSEDED, is reported when the value differs and the evaluation's lineage (Resolver::evaluation) shows an input replaced by a later version of its cell, or a slice by other members: the receipt is historical, not wrong. Passing blob content runs step 7.7, after that too. Only None from descriptor means the descriptor is unavailable: offered text is parsed strictly, and null or any other value that is not the descriptor fails the hash check.
With feature sign, an engine makes receipts: SigningKey::from_seed (deterministic Ed25519, RFC 8032) and ReceiptBuilder, whose sign checks the members as Receipt::from_json would and derives computed_by from the signer host and the key. Signing the members of every vector receipt with the vector's seed reproduces its payload, signature and hash byte for byte.
What is in the crate
json: strict parsing (SPEC 6.1) and RFC 8785 JCS, including ECMAScript number formatting:parse,Value,Value::to_jcs.Scalar,Cell,CellRef,Descriptor,Receipt,KeyDoc,Envelope, andcanonicalize_*,cell_hash,function_hash,receipt_hash.FunctionDescriptorandExtent: descriptors with the optionalsignature.orderand argumentextentofspec-0.4.0.Descriptoris thespec-0.3.0form and refuses them (E_SCHEMA), as before, since it has no field for them;function_hash,canonicalize_descriptor,resolved_unitandverifyread both.SliceandManifest(spec-0.4.0): slice objects (open*and range arguments, version slices), their ids and membership, and manifests with their members hash andinputsentryslice:<slice id>:<members hash>.parse_uri,format_uri,https_url: cell URIs and their HTTPS mapping.verify: SPEC 10.1 steps 1-7, with Ed25519 strictness as step 6 defines it; step 7 through aResolver(MapResolverincluded): descriptors, receipts, and since 0.3.4 the provided methodsmanifest(the manifest of a slice entry) andevaluation(the value with its lineage). A resolver written for 0.3.3 keeps working: it offers no manifests and no lineage.SigningKeyandReceiptBuilderbehind the non-defaultsignfeature.keys_from_responseandKeys.fetch_keys(featurefetch) is deprecated since 0.3.1 and removed at 1.0: fetchhttps://<host>/.well-known/datumesh.jsonwith your own HTTP client and pass the status and body tokeys_from_response.testkit(non-default featuretestkit): a conformance kit forResolverimplementations (see below).check_envelope: response envelopes, bound to the request: the cell id, and a receiptasofat or before the requested one (forokandstalealike).check_envelope_extrasalso returns thefaultof anot_coveredenvelope (Fault).check_envelope_any(spec-0.4.1) checks all nine outcomes,pendingincluded (AnyEnvelope,Pending), and binds a job's result to the original request;Request::with_jobdescribes a request at a job URL.EnvelopeandOutcomeare frozen withoutpending, socheck_envelopeandcheck_envelope_extrasrefuse it (E_ENVELOPE), as before. A mesh builds itspendingenvelopes withPending::newandPending::to_json.resolve_mesh(spec-0.4.1): cross-mesh resolution of a host from its key document and mesh document responses (Retrieval), in the order of SPEC 9.6, giving itsKeyDocandMeshDoc(bindings URL, endpoint templates;MeshDoc::urlfills one in).MeshDoc::from_jsonreads a mesh document on its own. Separate from verification, which never needs it.resolved_unit: the unit of a cell's value.Errorwith anErrorCodefor every code of SPEC 10.2 (E_EVAL_UNAVAILABLE,E_SLICE_MEMBER,E_INPUT_SUPERSEDED,E_JOB_UNKNOWNandE_MESH_DOCincluded), and a JSON path.
No unsafe, no panics on any input, no global state, no logging, no network outside the deprecated fetch_keys. Builds for wasm32-unknown-unknown, with or without sign and testkit. Minimum Rust version 1.97. The public API is frozen until 1.0 except for additions; API-CHANGES-0.3.0.md lists what changed from 0.2.1, API-CHANGES-0.3.1.md what was added in 0.3.1, API-CHANGES-0.3.2.md the (empty) diff of 0.3.2, API-CHANGES-0.3.3.md the additions in 0.3.3, API-CHANGES-0.3.4.md the additions in 0.3.4, API-CHANGES-0.3.5.md the additions in 0.3.5, and CHANGELOG.md the history.
Testing a resolver
A Resolver (a store, a cache, an engine's lookup) can check itself against the protocol with feature testkit, as a dev-dependency:
[]
= { = "0.3.5", = ["testkit"] }
The kit never writes through the resolver. It gives you Fixtures (descriptors, receipts and the versions of stored cells, all canonical JSON, from the specification's vectors plus a few receipts signed for the kit), you load them, and it verifies receipts through your resolver and looks things up in it:
use Kit;
It checks that verify asks the resolver only for what step 7 reaches (through the Recording wrapper), that what the resolver lacks is E_FN_UNAVAILABLE, E_INPUT_UNAVAILABLE or E_EVAL_UNAVAILABLE and that unknown or malformed hashes give None, that descriptor and receipt return exactly the bytes loaded, and that evaluate gets the receipt's asof; for a time-aware resolver, that a cell with versions at several times gives the latest version that answers as of the requested time under the kit's answer rule. Kit::answer_rule sets it: AnswerRule::TxAndAsof (the default) lets a version answer as of A only when its tx <= A and its receipt's asof <= A, as SPEC 5.5 step 5 (D31 revised) requires of an ok response to a request as of A; AnswerRule::TxOnly (the only rule before 0.3.3) only when its tx <= A. The fixtures' EUR/CHF version 1 (tx 2024-03-15T16:00, asof 2024-03-16T09:00) gives no value from its tx up to its asof under TxAndAsof. Each check (check_lazy_calls, check_unavailable, check_bytes, check_asof) can also run on its own. MapResolver passes it (not time-aware).
A resolver that supplies manifests (0.3.4) also loads fixtures.slices (descriptors, the citing receipts and their members, manifests by entry) and asks for Kit::new().slices(true), or runs Kit::check_slices on its own: every receipt citing a slice passes step 7.5 through it (it recomputes, or ends at E_EVAL_UNAVAILABLE), verify asks for each manifest once and then its members in order, a missing manifest or member is E_INPUT_UNAVAILABLE, manifest returns exactly the bytes loaded and None for entries it was not given, and a lineage it reports with an evaluation is made of entries it can back. Without slices(true) the kit is the 0.3.3 kit, so a resolver loaded as before still passes. The slice fixtures' descriptors carry signature.order: read them with FunctionDescriptor.
Repository
crates/datumesh-core: the library.crates/datumesh-cli:datumesh-cli run-vectors(JSON lines in, one result per line out) anddatumesh-cli check [DIR], for the cross-language harness. Not published.spec/: the specification, a git submodule.DEFECTS.md: every place the specification text was not enough to decide something.public-api.txt,public-api-all-features.txt: the public API (cargo public-api -ss) with default features and with--all-features;API-CHANGES-0.3.0.md: the diff against 0.2.1, justified;API-CHANGES-0.3.1.md: the additions in 0.3.1;API-CHANGES-0.3.2.md: the diff of 0.3.2 against 0.3.1;API-CHANGES-0.3.3.md: the additions in 0.3.3;API-CHANGES-0.3.4.md: the additions in 0.3.4;API-CHANGES-0.3.5.md: the additions in 0.3.5.crates/datumesh-core/src/testkit/data.rs: the kit's fixtures, generated fromspec/(and signed with featuresign) bytests/testkit_data.rs, which fails if they drift.fuzz/: cargo-fuzz targets.wasm-smoke/: the WebAssembly smoke test.tools/gen_tables.py: regenerates the embedded unit list and Unicode 15.1 table fromspec/.tools/probes/: oracle probes forspec-0.4.0andspec-0.4.1(slice objects, manifests, descriptors withorderandextent, receipts citing slices,E_INPUT_SUPERSEDED, mesh resolution,pendingenvelopes), run through the reference tool as a black box;crates/datumesh-cli/tests/probes-0.4.0.jsonlholds them with its results.tools/gen_golden.pyderives the runner's expected output from the vectors.
Licensed under Apache-2.0.