<?xml version="1.0" encoding="utf-8"?>
<!-- Source of the Internet-Draft for spec/core.md (FoodBlock 1.6). Render with: xml2rfc (text and html) on this file
     Where this draft and spec/core.md, spec/canonical.md and spec/protocol.json differ, those files govern. -->
<!DOCTYPE rfc [
  <!ENTITY nbsp "&#160;">
  <!ENTITY zwsp "&#8203;">
]>
<rfc xmlns:xi="http://www.w3.org/2001/XInclude" category="info" docName="draft-reynolds-foodblock-core-01"
     ipr="trust200902" submissionType="independent" version="3" xml:lang="en" sortRefs="true" symRefs="true" tocInclude="true">
  <front>
    <title abbrev="FoodBlock Core">FoodBlock Core: Signed, Hash-Addressed Blocks for Food Records</title>
    <seriesInfo name="Internet-Draft" value="draft-reynolds-foodblock-core-01"/>
    <author fullname="Patrick Reynolds" initials="P." surname="Reynolds">
      <organization>FOODX WORLD LIMITED</organization>
      <address><email>hello@foodx.network</email></address>
    </author>
    <date year="2026" month="October" day="18"/>
    <keyword>FoodBlock</keyword>
    <keyword>content addressing</keyword>
    <keyword>digital signature</keyword>
    <keyword>JSON canonicalization</keyword>
    <abstract>
      <t>FoodBlock is a small data format and a set of checking rules for records of food: what was made, moved, promised or allowed, and by whom.
      A record is a block of three fields (type, state and refs) signed by its author.
      The identity of a block is the hash of its canonical bytes, which are the JSON Canonicalization Scheme (RFC 8785) applied to a block that also follows a strict writing profile.
      Authors link their blocks into per-key chains.
      This document defines the block, the canonical form, the signature suites, the chain, the messages that carry a block, and the function valid() that decides whether a block is sound against the blocks a checker holds.
      It is the core only: an implementation of this document alone can make, sign, check and chain blocks.
      Rules for identities, rotation, consent, promises, privacy and node-to-node exchange are in separate modules that build on this core.</t>
    </abstract>
    <note title="Status of this draft">
      <t>This is an independent submission describing FoodBlock 1.6 (year zero: there is no earlier version in the protocol).
      The normative source is spec/core.md, spec/canonical.md and spec/protocol.json in the FoodBlock repository, with conformance vectors in spec/vectors.json.
      This draft restates the core for review; where it and the source differ, the source governs.</t>
    </note>
  </front>
  <middle>
    <section anchor="intro"><name>Introduction</name>
      <t>Food moves through many hands: growers, makers, carriers, sellers and eaters.
      Each keeps its own records, and no record can be checked by a stranger without trusting whoever holds it.
      FoodBlock makes the smallest agreement strangers need: who said it, under what authority, to whom, with what consent, and how it settled.
      Everything else (drafts, beliefs, goals, budgets, rankings) stays on each participant's own node and never becomes a block.</t>
      <t>A block is immutable and append-only.
      A change is a new block that links to the old one.
      Because a block's identity is the hash of its bytes, any two parties that hold the same bytes hold the same block, and a reader never needs to trust a name it was sent.</t>
      <t>This document specifies the core.
      Modules (identity, authority, promises, folds, privacy, nodes, strangers, circles and others) are out of scope except where this document must refer to them.</t>
    </section>

    <section anchor="terms"><name>Terminology</name>
      <t>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 <xref target="RFC2119"/> <xref target="RFC8174"/> when, and only when, they appear in all capitals, as shown here.</t>
      <dl>
        <dt>Block:</dt><dd>A JSON object with exactly the members "type", "state" and "refs" (<xref target="block"/>).</dd>
        <dt>Id:</dt><dd>The hash of a block's canonical bytes, in the hash of the suite of the key that signed it.</dd>
        <dt>Identity:</dt><dd>An "actor" block with no author (a genesis). Its id is the identity's id.</dd>
        <dt>Key:</dt><dd>A public signing key that speaks for an identity.</dd>
        <dt>Chain:</dt><dd>The sequence of blocks signed by one key, each naming the one before in "prev".</dd>
        <dt>Suite:</dt><dd>A named combination of hash, signature algorithm and key agreement (<xref target="suites"/>).</dd>
        <dt>Context:</dt><dd>The set of blocks a checker holds when it runs valid(). It never includes a clock.</dd>
        <dt>Node:</dt><dd>The software that holds a participant's blocks and speaks for them.</dd>
        <dt>Closed type, open type:</dt><dd>Four types carry authority and are closed: "actor", "promise", "transfer" and "grant". Any other type is open.</dd>
      </dl>
      <t>Fields and refs are written in code font in the source specification; here they appear in quotation marks.</t>
    </section>

    <section anchor="block"><name>The Block</name>
      <t>A block is a JSON object <xref target="RFC8259"/> of three members and nothing else:</t>
      <sourcecode type="json"><![CDATA[
{ "type": <string>, "state": <object>, "refs": <object> }
]]></sourcecode>
      <ul>
        <li>"type" says what kind of act the block is. A type is a string matching the regular expression <tt>^[a-z][a-z0-9_]*$</tt>.</li>
        <li>"state" holds data.</li>
        <li>"refs" holds links to other blocks. A link is one id, or a set of ids written as a sorted array.</li>
      </ul>
      <t>Seven types are known. Each has a mood that says what the block claims. "actor" (a person, agent, circle or contact) and "thing" (anything held, found or written) exist. "observe" (a claim), "transform" (things change) and "transfer" (owner, holder or place changes) are. "promise" must. "grant" may.</t>
      <t>The four closed types have fields fixed by the specification, and a node MUST refuse a closed block that breaks it.
      Every other type is open: a node checks an open block only against this document, then stores it and passes it on.
      A fold (a function from a set of blocks to an answer, defined in the folds module) reads the types it knows; an open block it does not know gives no authority, moves no owner and binds no one.</t>
      <t>Seven reference keys, the verbs, link one block to another by meaning: "updates" (a new version of), "answers" (the reply to, and the only way to consent), "confirms" and "defeats" (more or less true), "for" (what the block serves), "derives_from" and "same_as". The keys "prev" and "after" link blocks by order only. The complete lists are in protocol.json.</t>
      <t>The id of a block is the hash of its canonical bytes (<xref target="canonical"/>). A reader MUST recompute the id from the bytes and MUST NOT trust an id it was sent.</t>
    </section>

    <section anchor="canonical"><name>Canonical Form</name>
      <t>The canonical bytes of a block are the output of the JSON Canonicalization Scheme <xref target="RFC8785"/> applied to the block, where the block already follows the strict writing rules below:</t>
      <sourcecode type="pseudocode"><![CDATA[
id        = Hash_suite( JCS(block) )
signature = Sign_alg( private_key, JCS(block) )
block     = { "type": type, "state": state, "refs": refs }
]]></sourcecode>
      <t>Hash_suite is the hash of the suite of the signing key. SHA-256 <xref target="RFC6234"/> is written as 64 lowercase hexadecimal characters. Any other hash is written with its name as a prefix, such as "sm3:" followed by 64 hexadecimal characters.</t>
      <t>RFC 8785 fixes the bytes: object members sorted by UTF-16 code units at every depth, no insignificant whitespace, strings in UTF-8 <xref target="RFC3629"/> with the shortest escapes, numbers as ECMAScript writes them. JCS does not fix content, so a writer MUST follow these strict writing rules, and a reader MUST refuse (and MUST NOT repair) a block that breaks one:</t>
      <ol>
        <li>The block is exactly "type", "state" and "refs".</li>
        <li>No "null" appears anywhere, including inside arrays. No number has a fraction or an exponent. A value with a fraction is written as a decimal string matching <tt>^-?(0|[1-9][0-9]*)(\.[0-9]+)?$</tt>.</li>
        <li>Integers lie within -(2^53 - 1) to 2^53 - 1. Negative zero is refused.</li>
        <li>Every string and every object key is in Unicode Normalization Form C <xref target="UAX15"/> and holds no unpaired surrogate.</li>
        <li>A string array directly under a "refs" key is a set. It is written in ascending UTF-16 order with no repeats. Every other array keeps its written order. A "refs" key inside "state" is an ordinary key.</li>
        <li>No object has a duplicate key.</li>
        <li>"state.audience" is "public" or a sorted list of commitments with no repeats.</li>
      </ol>
      <t>Times are UTC to the millisecond, written exactly as <tt>YYYY-MM-DDTHH:MM:SS.sssZ</tt> (a strict subset of the format of <xref target="RFC3339"/>), and MUST be real calendar instants. Dates are <tt>YYYY-MM-DD</tt>. Durations are ISO 8601 with at least one unit. Hexadecimal is lowercase. The canonical body of a block MUST NOT exceed 65536 octets (protocol.json, "limits.block_bytes").</t>
      <t>A compact binary form of a public block <xref target="RFC8949"/> exists for constrained links. It is a transport only: a receiver rebuilds the JSON first, and the id never covers CBOR bytes.</t>
    </section>

    <section anchor="suites"><name>Suites</name>
      <t>A suite names a hash, a signature algorithm and an envelope together. A key names its suite in "state.suite"; when absent the suite is "std". A block is hashed with the hash of the suite of the key that signed it and verified with that key's algorithm.</t>
      <table anchor="suite-table">
        <name>Suites defined by FoodBlock 1.6</name>
        <thead><tr><th>Suite</th><th>Hash</th><th>Signature</th><th>Signing key (lowercase hex)</th><th>Signature (hex)</th></tr></thead>
        <tbody>
          <tr><td>std</td><td>SHA-256</td><td>Ed25519 <xref target="RFC8032"/></td><td>SPKI DER, 32-octet key <xref target="RFC8410"/></td><td>64 octets</td></tr>
          <tr><td>p256</td><td>SHA-256</td><td>ECDSA P-256 with SHA-256</td><td>SPKI DER, 65-octet key</td><td>r, s (32 octets each), or passkey form</td></tr>
          <tr><td>sm</td><td>SM3</td><td>SM2 with SM3</td><td>SPKI DER, 65-octet key</td><td>r then s, 32 octets each</td></tr>
        </tbody>
      </table>
      <t>A signing key is written as lowercase hexadecimal of a SubjectPublicKeyInfo (SPKI) DER encoding: the fixed prefix 302a300506032b6570032100 then the 32-octet key for "std"; the prefix 3059301306072a8648ce3d020106082a8648ce3d030107034200 then the 65-octet uncompressed SEC1 point for "p256"; the prefix 3059301306072a8648ce3d020106082a811ccf5501822d034200 then the 65-octet point for "sm". The "sm" suite uses the SM2 user id "1234567812345678". A "p256" signature is either the 64-octet ECDSA signature (r then s) or a longer passkey form, a WebAuthn assertion that carries its own challenge, which a reader tells apart by length.</t>
      <t>The encryption key and envelope of each suite (X25519 <xref target="RFC7748"/> with HKDF <xref target="RFC5869"/> and AES-256-GCM for "std") belong to the privacy module and are not needed to make, sign, check or chain public blocks. A post-quantum suite is one further row once its sizes are fixed in the conformance vectors. A reader MUST refuse a block whose key names a suite it does not know ("unknown_suite"). Moving an identity to a new suite is a key grant that replaces a key and names the new suite.</t>
    </section>

    <section anchor="values"><name>Numbers, Time and Text</name>
      <t>Signed content holds exactly these value kinds: object, array, string, integer and boolean. It never holds null or a floating point number. A count, a sequence number and a money amount in minor units are integers. Any fractional quantity is a decimal string. Which fields hold times and dates is listed in protocol.json ("time_fields", "date_fields"). A reader that finds a value outside these rules MUST refuse the block and MUST NOT correct it.</t>
    </section>

    <section anchor="genesis"><name>The Genesis</name>
      <t>A genesis is the one block with no author. It is an "actor" with no "author", "prev", "after" or "seq", signed by its own key. Its id is the id of the identity.</t>
      <sourcecode type="json"><![CDATA[
{ "type": "actor",
  "state": { "kind": ..., "at": ..., "audience": "public",
             "public_key_sign": ... },
  "refs": {} }
]]></sourcecode>
      <t>The suite, an encryption key and recovery fields are optional. A genesis is always public. A person adds an encryption key, a recovery key, more devices or a custodian later, each by a key grant, a "grant" block of kind "key" defined by the identity module.</t>
    </section>

    <section anchor="chain"><name>The Chain</name>
      <t>Each signing key has its own chain. Every block but a genesis carries "refs.author" (the identity's id), "refs.prev", "state.seq", "state.at" and "state.audience".</t>
      <ul>
        <li>"refs.prev" names the key's previous block. A key's first block names the block that brought the key in: the genesis, or a key grant.</li>
        <li>"state.seq" is one more than the "seq" of the "prev" block. A key's first block has seq 1. The one exception is a skip, a block marked "state.skip": true, which may jump to any higher seq (a device that lost its counter writes one).</li>
        <li>"state.at" is when the author says the block was written, on the author's clock. An "observe", "transform" or "transfer" MAY omit it when its signer has no clock.</li>
      </ul>
      <t>A core node knows one signing key per identity: the genesis key. It stores a block signed by any other key and counts it for nothing.</t>
      <t>Two blocks of one key that name the same "prev" prove that the key's holder lied (an equivocation). Both are kept, so anyone can publish the proof, and neither binds anyone.</t>
    </section>

    <section anchor="messages"><name>Messages</name>
      <t>A block travels as a message, a UTF-8 JSON object:</t>
      <sourcecode type="json"><![CDATA[
{ "version": "1.6",
  "block": { "type": ..., "state": ..., "refs": ... },
  "signature": "<hex>" }
]]></sourcecode>
      <t>The signature is by the signing key over the canonical bytes of "block". A suite other than "std" adds "suite". When a block is carried in an e-mail attachment or file the media type is application/foodblock+json (<xref target="iana"/>).</t>
    </section>

    <section anchor="required"><name>Required Fields</name>
      <t>Every block but a genesis carries the five fields of <xref target="chain"/>. In addition:</t>
      <ul>
        <li>"actor": "state.kind".</li>
        <li>"transfer": "refs.what", and at least one of "refs.to_owner", "refs.to_holder", "refs.to_place" or "state.count". A payment (a transfer carrying "state.money") is exempt from "refs.what".</li>
        <li>"promise": "refs.what" or "state.what"; an ask also carries "terms.expires".</li>
        <li>"grant": "state.kind".</li>
        <li>An answer (a block with "refs.answers") carries "state.status" and is exempt from the other required fields of its type. The single exception is a counter-offer.</li>
        <li>An open type: nothing beyond the common five.</li>
      </ul>
      <t>The fields each closed type may carry are listed in protocol.json ("fields"). A closed block with any other field MUST be refused ("undeclared_field") unless a schema the block names declares it.</t>
    </section>

    <section anchor="valid"><name>The Function valid()</name>
      <t>valid(block, context) takes a block and the set of blocks the checker holds. It never reads a clock. It runs the eight checks below in order and stops at the first check that finds anything. Its refusal is every code that one check found, one per code, in the order protocol.json lists them ("refusals"). valid() is true when no check finds anything.</t>
      <ol>
        <li>The block is exactly "type", "state" and "refs" and follows <xref target="canonical"/>, strict writing included ("not_canonical").</li>
        <li>Every value follows <xref target="values"/> and its grammar ("float", "integer_range", "decimal", "grammar", "time", "calendar", "shape", "type_name", "unknown_suite").</li>
        <li>The canonical body is at most the block-size limit, and "refs.after" names at most 16 blocks ("size_limit", "after_limit").</li>
        <li>A genesis follows <xref target="genesis"/> ("bad_genesis"). Any other block has the common five fields, and its audience is public or a sorted list of commitments ("chain", "at_missing", "audience").</li>
        <li>Every block it needs is in the context ("unmet"). The needs are listed in protocol.json: every block needs its "prev"; a grant also its parent and the block it counts to; a promise also the grant it acts under and the promise it answers or stops. Authority depends on these blocks, so they are causal dependencies. A block that lacks one is not judged: its refusal is "unmet" alone, and a node waits. When the needed blocks arrive the block is judged once, with every other check.</li>
        <li>The id sent is the hash of the bytes ("bad_hash"). The signing key is a key of the author that speaks for it, and its signature verifies ("bad_signature", "void_key", "unknown_suite", "not_own_rotation", "recovery_scope").</li>
        <li>"prev" and "seq" fit the key's chain, or the block is a skip ("chain").</li>
        <li>If the type is closed, it has its required fields and no undeclared ones, and the checks of its module pass ("required", "undeclared_ref", "undeclared_field", and the module codes in protocol.json).</li>
      </ol>
      <t>Checks 1 to 4 read the bytes alone, so a block that fails one is refused at once whatever it lacks. A block that passes every check may still count for nothing in a later fold; valid() says only that the bytes are sound.</t>
      <t>An implementation conforms when it reproduces the canonical strings, ids, signatures and refusal codes of the published conformance vectors, and refuses what they refuse. Two conforming implementations give the same id for the same block.</t>
    </section>

    <section anchor="invariants"><name>Invariants</name>
      <t>The core keeps these invariants. (Numbering follows the source specification.)</t>
      <ol start="1" type="1">
        <li>A block's identity is the hash of its canonical bytes; nothing else names it.</li>
        <li>A block never changes; a change is a new block.</li>
        <li>A key's blocks form one chain: no two of its blocks name the same "prev". Two that do prove a lie. Both are kept, and neither binds anyone else.</li>
      </ol>
      <t>Further, authority depends on blocks, and they are causal dependencies: a block waits until it holds every block it needs and is then judged once. No node holds a block that another would have judged otherwise for want of what it lacked (source invariant 25). A finite-state model of this waiting rule, of equivocation, consent, allocation and key rotation is published beside the specification and is exhaustively checked in its continuous integration.</t>
    </section>

    <section anchor="security"><name>Security Considerations</name>
      <t>The considerations of <xref target="RFC3552"/> apply. The following are specific to this format.</t>
      <t><strong>Integrity and authenticity.</strong> A block's id is a collision-resistant hash of its canonical bytes, and an author's signature covers the same bytes. A reader that recomputes the id and verifies the signature need not trust the sender. The strength of both is that of the chosen suite. A suite that is later broken is replaced by a key grant that moves an identity to a new suite; blocks already signed remain valid under the old suite's verification, so a reader that continues to accept a broken suite accepts forgeries under it. Readers SHOULD refuse suites they consider broken.</t>
      <t><strong>Canonicalization.</strong> Two parties that disagree on the canonical bytes of a block disagree on its id. This is why the strict writing rules are mandatory and why a reader refuses rather than repairs: repairing would let two different byte strings claim one id's authority, or one byte string two. NFC and duplicate-key rules close the usual JSON ambiguities (visually identical keys, parser differences on duplicate keys). Implementations MUST use a JSON parser that detects duplicate keys, or a check that does.</t>
      <t><strong>Signature malleability and signed content.</strong> A signature is over the canonical bytes, and the id is the hash of the same bytes, so a third party cannot alter a signed block without changing its id. A reader MUST verify the signature with a key that speaks for the author in the context, not with a key the message names for itself.</t>
      <t><strong>Equivocation and withholding.</strong> An author can sign two blocks that name one "prev". The core does not prevent this; it makes it provable, since both blocks are kept and either can be shown. A node can be shown only one branch for a time, so a reader that needs more assurance asks for the author's latest heads and names them in "after". An attacker who withholds blocks can delay a reader's view but cannot make a reader accept a block that lacks what it needs: such a block waits.</t>
      <t><strong>Resource use.</strong> Blocks are bounded in size (65536 octets) and a block's "after" set is bounded (16). Waiting blocks, which are held only until what they need arrives, consume storage; a node SHOULD bound them and expire them after a time it sets, and MAY refuse a push. A node SHOULD apply the sequence-gap limit published in protocol.json when reporting gaps, since a block may claim a very large "seq".</t>
      <t><strong>Replay.</strong> A signed block is public evidence of what its author said; replaying it adds nothing the author did not say. Requests between nodes carry a counter or a challenge against replay (nodes module); the core carries no clock and so offers none.</t>
      <t><strong>Clocks.</strong> "state.at" is the author's claim, not evidence. Nothing in the core that gives, takes or orders authority reads another author's "at". Where a rule needs a clock it names whose, and the answer is a local view, never an agreement.</t>
      <t><strong>Key loss and compromise.</strong> The core knows one key per identity. Rotation, recovery keys, devices and custodians are in the identity module; an implementation that supports them MUST NOT let a rotated-out key's later blocks count.</t>
      <t><strong>Authority is not implied.</strong> A node stores and passes on every sound block, whatever its type. Storing a block grants its author nothing; an open block of an unknown type binds no one.</t>
    </section>

    <section anchor="privacy"><name>Privacy Considerations</name>
      <t>The considerations of <xref target="RFC6973"/> apply. Blocks are append-only and can be copied, so a public block cannot be unsent. Authors SHOULD therefore publish only what strangers must agree on: who said it, under what authority, to whom, with what consent, and how it settled. Drafts, beliefs, goals, limits, budgets and rankings are not blocks and stay on the author's node.</t>
      <t>A core node writes and reads public blocks only. A private block carries "state.audience" as a sorted list of commitments, each a salted hash of one recipient, and its body is encrypted to that audience (privacy module). Even then a public header of the form { author, seq, prev, hash } remains visible so that anyone can check a chain, its gaps and its forks; the header reveals the existence and timing structure of an author's activity, and authors who need to hide that cannot use this format. Commitments use a fresh 16-octet random salt per block per recipient to prevent dictionary reversal of low-entropy values such as phone numbers. A hashed phone number can be reversed, so nothing about a refusal of contact (a STOP) is published.</t>
      <t>Linkability is inherent: all blocks of an identity are linked by "author" and by the chain. A person who wants unlinkable activity uses separate identities. A custodian holding a person's keys sees what it signs; the format requires that a held key always says so.</t>
    </section>

    <section anchor="iana"><name>IANA Considerations</name>
      <t>This document requests the registrations below. If they are not accepted, the identifiers remain private conventions of the FoodBlock protocol.</t>
      <section><name>Media Types</name>
        <t>IANA is requested to register the media types "application/foodblock+json" and "application/foodblock+cbor" in the "Media Types" registry <xref target="RFC6838"/>.</t>
        <dl>
          <dt>Type name:</dt><dd>application</dd>
          <dt>Subtype names:</dt><dd>foodblock+json; foodblock+cbor</dd>
          <dt>Required parameters:</dt><dd>N/A</dd>
          <dt>Optional parameters:</dt><dd>N/A</dd>
          <dt>Encoding considerations:</dt><dd>foodblock+json is UTF-8 JSON <xref target="RFC8259"/>; foodblock+cbor is binary CBOR <xref target="RFC8949"/>.</dd>
          <dt>Security considerations:</dt><dd>See <xref target="security"/> of this document.</dd>
          <dt>Interoperability considerations:</dt><dd>See <xref target="canonical"/>. The id and signature of a block are over its JSON Canonicalization Scheme bytes in both forms.</dd>
          <dt>Published specification:</dt><dd>This document.</dd>
          <dt>Applications that use this media type:</dt><dd>Nodes, SDKs and e-mail or messaging clients that exchange FoodBlock messages (an attachment with file extension .foodblock).</dd>
          <dt>Fragment identifier considerations:</dt><dd>N/A</dd>
          <dt>Person and e-mail address to contact for further information:</dt><dd>Patrick Reynolds, hello@foodx.network</dd>
          <dt>Intended usage:</dt><dd>COMMON</dd>
          <dt>Restrictions on usage:</dt><dd>None</dd>
          <dt>Author and change controller:</dt><dd>The author of this document.</dd>
        </dl>
      </section>
      <section><name>Well-Known URI</name>
        <t>IANA is requested to register the following in the "Well-Known URIs" registry <xref target="RFC8615"/>. A node publishes a discovery document at this location (nodes module); that document is outside the core defined here.</t>
        <dl>
          <dt>URI suffix:</dt><dd>foodblock</dd>
          <dt>Change controller:</dt><dd>The author of this document.</dd>
          <dt>Specification document:</dt><dd>This document, and the FoodBlock nodes module.</dd>
          <dt>Status:</dt><dd>permanent</dd>
        </dl>
      </section>
      <section><name>Protocol Registries</name>
        <t>This document creates no IANA registry. The lists of types, verbs, suites and refusal codes are closed lists in the protocol file (protocol.json) that every implementation loads; additions are made by a new protocol version, not by registration.</t>
      </section>
    </section>
  </middle>
  <back>
    <references><name>References</name>
      <references><name>Normative References</name>
        <xi:include href="https://www.rfc-editor.org/refs/bibxml/reference.RFC.2119.xml"/>
        <xi:include href="https://www.rfc-editor.org/refs/bibxml/reference.RFC.8174.xml"/>
        <xi:include href="https://www.rfc-editor.org/refs/bibxml/reference.RFC.8785.xml"/>
        <xi:include href="https://www.rfc-editor.org/refs/bibxml/reference.RFC.8259.xml"/>
        <xi:include href="https://www.rfc-editor.org/refs/bibxml/reference.RFC.3629.xml"/>
        <xi:include href="https://www.rfc-editor.org/refs/bibxml/reference.RFC.6234.xml"/>
        <xi:include href="https://www.rfc-editor.org/refs/bibxml/reference.RFC.8032.xml"/>
        <xi:include href="https://www.rfc-editor.org/refs/bibxml/reference.RFC.8410.xml"/>
        <xi:include href="https://www.rfc-editor.org/refs/bibxml/reference.RFC.3339.xml"/>
        <reference anchor="UAX15" target="https://www.unicode.org/reports/tr15/">
          <front><title>Unicode Normalization Forms</title>
            <author><organization>The Unicode Consortium</organization></author>
            <date year="2025"/></front>
          <seriesInfo name="Unicode Standard Annex" value="15"/>
        </reference>
      </references>
      <references><name>Informative References</name>
        <xi:include href="https://www.rfc-editor.org/refs/bibxml/reference.RFC.3552.xml"/>
        <xi:include href="https://www.rfc-editor.org/refs/bibxml/reference.RFC.6973.xml"/>
        <xi:include href="https://www.rfc-editor.org/refs/bibxml/reference.RFC.5869.xml"/>
        <xi:include href="https://www.rfc-editor.org/refs/bibxml/reference.RFC.7748.xml"/>
        <xi:include href="https://www.rfc-editor.org/refs/bibxml/reference.RFC.8949.xml"/>
        <xi:include href="https://www.rfc-editor.org/refs/bibxml/reference.RFC.6838.xml"/>
        <xi:include href="https://www.rfc-editor.org/refs/bibxml/reference.RFC.8615.xml"/>
      </references>
    </references>
    <section anchor="example" numbered="false"><name>Appendix A. Example</name>
      <t>The input block (ids shortened):</t>
      <sourcecode type="json"><![CDATA[
{ "type": "thing",
  "state": { "name": "Sourdough",
             "price": { "minor": 450, "currency": "GBP" },
             "weight": { "value": "0.8", "unit": "kg" } },
  "refs": { "author": "abc123", "of": "def456" } }
]]></sourcecode>
      <t>Its canonical bytes (shown wrapped; the bytes contain no line break):</t>
      <sourcecode type="json"><![CDATA[
{"refs":{"author":"abc123","of":"def456"},"state":{"name":
"Sourdough","price":{"currency":"GBP","minor":450},"weight":
{"unit":"kg","value":"0.8"}},"type":"thing"}
]]></sourcecode>
      <t>Its id with the "std" suite is 7bda2dba2c55126f32646c376de02d3f7d91a21ff956336649307dd6e7c0d51d.</t>
    </section>
  </back>
</rfc>
