datumesh_core/lib.rs
1//! Core of the [Datumesh](https://datumesh.com) protocol: strict JSON and RFC 8785 JCS,
2//! canonical forms of scalars, cells, references, descriptors and receipts, cell URIs, key
3//! documents, response envelopes, unit resolution and offline receipt verification.
4//!
5//! The rules are those of the specification, `SPEC.md` and `units.md` at
6//! <https://github.com/datumesh-com/datumesh-spec>. Section numbers in this documentation
7//! refer to `SPEC.md`.
8//!
9//! Every function that can fail returns an [`Error`] carrying one of the codes of SPEC 10.2
10//! ([`ErrorCode`]), the JSON path where the failure was found, and a human detail. Nothing in
11//! this crate panics on any input, touches the network (except `fetch_keys` behind the
12//! `fetch` feature), keeps global state or logs.
13//!
14//! [`verify`] runs SPEC 10.1. Keys are an input ([`Keys`]); recomputation (step 7) asks a
15//! [`Resolver`] for the descriptor, the input receipts, the manifests of slice entries and
16//! the evaluation (with its lineage), lazily ([`MapResolver`] holds them in maps). Since
17//! `spec-0.4.0` a receipt's lineage may cite whole slices of cells ([`Slice`], [`Manifest`]),
18//! and descriptors may carry `signature.order` and argument extents ([`FunctionDescriptor`]).
19//! Since `spec-0.4.1` a mesh may answer `pending` ([`check_envelope_any`], [`Pending`]), and
20//! cross-mesh resolution reads a host's mesh document ([`resolve_mesh`], [`MeshDoc`]); a
21//! verifier needs neither. Since `spec-0.4.2` a reference may be pinned to a function hash
22//! ([`Reference`], [`parse_reference`]), and [`Answer`] checks ([`check_answer`],
23//! [`check_slice_answer`], [`check_batch`]) and builds every envelope, trust `computed`, slice
24//! pages and batches included. Since `spec-0.4.3` a key document may carry a publisher's
25//! `declarations` ([`KeyDocument`], [`Declarations`]), a computed receipt's licence follows
26//! from its inputs' ([`inherit_licence`], and on the verifier's side [`check_entitlement`]),
27//! an `error` envelope may say a foreign mesh was unreachable (`E_FOREIGN_UNAVAILABLE`,
28//! [`ForeignDetail`]), and names under `.invalid` are unrooted ([`is_unrooted`]). Feature `sign` adds `SigningKey` and
29//! `ReceiptBuilder`, which make receipts that verify byte for byte like the specification's.
30//! Feature `fetch` (`fetch_keys`) is deprecated since 0.3.1 and removed at 1.0: fetch key
31//! documents with your own HTTP client and pass the response to [`keys_from_response`].
32//!
33//! ```
34//! use datumesh_core::{verify, Keys, Level};
35//!
36//! let receipt = br#"{"asof":"2024-03-16T09:00:00.000000Z","cell":{"args":{"base":{"type":"string","value":"EUR"},"date":{"type":"date","value":"2024-03-15"},"quote":{"type":"string","value":"USD"}},"fn":"fx_rate","fn_hash":"7b58ecb8fd23b644c87951d0bd609790ff547cbb23fed27186c669ce2ce0aa80","ns":"rates.example.org"},"computed_by":"rates.example.org#d712b32a2cc5cb7c","inputs":[],"issued":"2024-03-16T09:00:00.000000Z","licence":"CC-BY-4.0","proto":"datumesh/1","publisher":"rates.example.org","sig":"ddf61d49ff6593f7b0d94c76f19fefdd27f47778740b3666fc1c3315ec00c5d216f887c4078deb05c83d4d743453c6fecc568473663b3282ac4eda8c3eadcf01","summary":"1 EUR was worth 1.0892 USD at the 15 March 2024 reference rate.","tx":"2024-03-15T16:00:00.000000Z","type":"number","valid":{"date":{"type":"date","value":"2024-03-15"}},"value":"1.0892"}"#;
37//! let key_doc = br#"{"host":"rates.example.org","keys":[{"alg":"ed25519","id":"d712b32a2cc5cb7c","public":"843baeb22bfd0364fb1b7262b367fb3202e7886c69a070fecf8e343fedb1eabf","valid_from":"2020-01-01T00:00:00.000000Z","valid_to":"2024-06-01T00:00:00.000000Z"}],"proto":"datumesh/1"}"#;
38//!
39//! let mut keys = Keys::new();
40//! keys.insert_response("rates.example.org", 200, Some(key_doc));
41//! let result = verify(receipt, &keys, None, None);
42//! assert!(result.ok);
43//! assert_eq!(result.level, Some(Level::Signed));
44//! assert_eq!(result.hash.as_deref(), Some("19a5fe3a744c5792f747fb4c286ce396ab44b95922de4f07937502fe595b6f81"));
45//! ```
46
47#![forbid(unsafe_code)]
48#![deny(missing_docs)]
49
50mod answer;
51mod crypto;
52mod descriptor;
53mod envelope;
54mod error;
55#[cfg(feature = "fetch")]
56mod fetch;
57pub mod json;
58mod keys;
59pub mod lexical;
60mod licence;
61mod mesh;
62mod model;
63mod receipt;
64mod schema;
65#[cfg(feature = "sign")]
66mod sign;
67mod slice;
68#[rustfmt::skip]
69mod tables;
70#[cfg(feature = "testkit")]
71pub mod testkit;
72mod uri;
73mod verify;
74
75pub use answer::{
76 batch_body, check_answer, check_batch, check_slice_answer, Answer, AnswerOutcome, Batch, SlicePage, SliceRequest,
77 TrustLabel,
78};
79pub use crypto::{key_id, sha256, sha256_hex, verify_ed25519};
80pub use descriptor::{
81 resolve_with, resolved_unit, resolved_unit_value, ArgDecl, Descriptor, Extent, FunctionDescriptor, RateSide,
82 Returns, Signature, Unit,
83};
84pub use envelope::{
85 check_envelope, check_envelope_any, check_envelope_extras, AnyEnvelope, Envelope, EnvelopeExtras, Fault,
86 ForeignDetail, InvalidRequest, Outcome, Pending, Quote, Request, Trust,
87};
88pub use error::{Error, ErrorCode, Result};
89pub use mesh::{resolve_mesh, MeshDoc, ResolvedMesh, Retrieval};
90// The re-export is not a use: only callers see the deprecation (Rust 1.97 warns here otherwise).
91#[cfg(feature = "fetch")]
92#[allow(deprecated)]
93pub use fetch::fetch_keys;
94pub use keys::{key_document_from_response, keys_from_response, Key, KeyDoc, KeyDocument, Keys};
95pub use lexical::is_unrooted;
96pub use licence::{
97 check_entitlement, inherit_licence, Declaration, Declarations, Entitlement, Inheritance, LicenceReason,
98 WrongPublisher,
99};
100pub use model::{Arg, Args, Cell, CellRef, Reference, Scalar, Type};
101pub use receipt::{receipt_hash, receipt_payload, Receipt};
102#[cfg(feature = "sign")]
103pub use sign::{ReceiptBuilder, SigningKey};
104pub use slice::{Manifest, Slice};
105pub use uri::{cell_uri_from_https, format_reference, format_uri, https_url, parse_reference, parse_uri};
106pub use verify::{verify, Evaluation, Level, MapResolver, Resolver, VerifyResult};
107
108/// Canonical bytes of a typed scalar given as JSON text (4.1, 6.2).
109pub fn canonicalize_scalar(json: &[u8]) -> Result<Vec<u8>> {
110 Ok(Scalar::from_json(json)?.canonical())
111}
112
113/// Canonical bytes of a cell given as JSON text (4.4, 6.2). Slices are allowed.
114pub fn canonicalize_cell(json: &[u8]) -> Result<Vec<u8>> {
115 Ok(Cell::from_json(json)?.canonical())
116}
117
118/// Canonical bytes of a cell reference given as JSON text (4.3, 6.2).
119pub fn canonicalize_ref(json: &[u8]) -> Result<Vec<u8>> {
120 Ok(CellRef::from_json(json)?.canonical())
121}
122
123/// Canonical bytes of a function descriptor given as JSON text (7, 6.2), `order` and
124/// `extent` included (`spec-0.4.0`; see [`FunctionDescriptor`]).
125pub fn canonicalize_descriptor(json: &[u8]) -> Result<Vec<u8>> {
126 Ok(FunctionDescriptor::from_json(json)?.canonical())
127}
128
129/// The signed payload of a receipt given as JSON text: canonical bytes without `sig` (8.3).
130/// Runs verification steps 1-4 first.
131pub fn canonicalize_receipt(json: &[u8]) -> Result<Vec<u8>> {
132 receipt_payload(json)
133}
134
135/// The cell id of a cell given as JSON text: SHA-256 of its canonical bytes (4.4, 6.3).
136pub fn cell_hash(json: &[u8]) -> Result<String> {
137 Ok(Cell::from_json(json)?.id())
138}
139
140/// The function hash of a descriptor given as JSON text (7, 6.3), `order` and `extent`
141/// included.
142pub fn function_hash(json: &[u8]) -> Result<String> {
143 Ok(FunctionDescriptor::from_json(json)?.hash())
144}
145
146/// Canonical bytes of a slice object given as JSON text (4.4, 6.2; `spec-0.4.0`).
147pub fn canonicalize_slice(json: &[u8]) -> Result<Vec<u8>> {
148 Ok(Slice::from_json(json)?.canonical())
149}