steddle / seal
The fingerprint and canonical JSON that Steddle's products store proof with: SHA-256 in one spelling, and RFC 8785 over I-JSON.
Requires
- php: ^8.3
- ext-mbstring: *
Requires (Dev)
- pestphp/pest: ^4.0 || ^5.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Steddle Seal
The fingerprint and canonical JSON that Steddle's products store proof with.
Three parts. A fingerprint is a SHA-256 value written one way, so two systems that store the same bytes agree on its text. Canonical JSON turns data into one exact byte sequence (RFC 8785 over I-JSON), so anyone who holds the data gets the same fingerprint. A statement is what a seal stamps: a JSON object that names its form and points at files by their fingerprints. SPEC.md says how a seal is made and checked. Plain PHP: no framework, nothing registered, no requirement beyond ext-mbstring.
Installation
composer require steddle/seal
Requires
- PHP 8.3 or later, with
ext-mbstring
Quick start
use Steddle\Seal\Canonical; use Steddle\Seal\Fingerprint; use Steddle\Seal\Json; $record = Json::decode('{ "b": [1.0, 1e0], "a": null }'); Canonical::json($record); // {"a":null,"b":[1,1]} (string) Fingerprint::of(Canonical::json($record)); // sha256:7773f186...c753bb05
The same record, written with other spacing, member order or number forms, gives the same canonical bytes and so the same fingerprint.
Fingerprint
Fingerprint::of($bytes)hashes bytes.Fingerprint::read($text)takes 64 hex characters in either case, bare or aftersha256:.- A fingerprint prints
sha256:and 64 lowercase hex characters. Two are equal when their 32 bytes are. - A short form, white space, a trailing line break or another prefix is refused, never trimmed.
Canonical JSON
Json::decode($text)reads I-JSON and refuses a member name twice, NaN or an infinity, half a surrogate pair, and any text that isn't JSON.Canonical::json($value)writes members sorted by UTF-16 code units and numbers as ECMAScript prints them.- Every refusal is a
Refusedexception. Itswhyis a fixed code (duplicate_name,not_finite,lone_surrogate,not_json,wrong_length,not_hex,unknown_prefix), so callers can branch on it. Where one member is at fault,pointergives its RFC 6901 JSON Pointer, such as/records/3/id.
Statement
Statement::of($value)andStatement::read($json)take what a seal stamps: one JSON object that names itsformand points at files and data with references. They refuse a statement that breaks the rules in SPEC.md, with the rule aswhy, andStatement::problems($value)lists every rule it breaks.bytes()is its RFC 8785 form,fingerprint()the fingerprint of those bytes, andreferences()every reference by its JSON Pointer.Reference::to($fingerprint, $over)points at a file (bytes), at data in its canonical form (rfc8785) or at another form by name.
Vectors
vectors/ is the contract: fingerprint.json, canonical.json and statement.json state every input and its expected output or refusal, RFC 8785's number table among them. Another implementation in any language is equal to this one when it gives the same answer on every case.
composer install vendor/bin/pest
Changelog
See CHANGELOG.md.
Credits
License
The code is MIT. See LICENSE. The test vectors in vectors/ are CC BY 4.0, as their notice says. The mark in art/ is Steddle's and covered by neither (notice).