oxedyne/fe2o3/fe2o3_sbj/src/lib.rs
6.5 KiB, 21 runs
created by r1870400018:22222, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | //! SBJ, Signed Binary JDAT: the file format of the oxeweb. |
| 2 | //! |
| 3 | //! An SBJ file is a signed envelope wrapping a tree of typed nodes encoded in BDAT, JDAT's binary |
| 4 | //! form. The hash of the tree region is the document's permanent address, and the envelope's |
| 5 | //! signature binds that address to its author. |
| 6 | //! |
| 7 | //! The normative description is `SPEC.md` beside this crate. Where the two disagree, this code is |
| 8 | //! wrong. |
| 9 | //! |
| 10 | //! The container is not specific to documents. An envelope declares the schema of its payload, so |
| 11 | //! an oxeweb document (`oxeweb/doc/0`) and a signed administrative command are the same artefact |
| 12 | //! with different payloads and different validators. |
| 13 | |
| 14 | pub mod canon; |
| 15 | pub mod card; |
| 16 | pub mod doc; |
| 17 | pub mod envelope; |
| 18 | pub mod import; |
| 19 | pub mod index; |
| 20 | pub mod key; |
| 21 | pub mod kinds; |
| 22 | pub mod post; |
| 23 | pub mod prelude; |
| 24 | pub mod share; |
| 25 | pub mod text; |
| 26 | pub mod validate; |
| 27 | |
| 28 | /// File magic, `SBJ\0`. |
| 29 | pub const MAGIC: [u8; 4] = [0x53, 0x42, 0x4A, 0x00]; |
| 30 | |
| 31 | /// Format major version implemented here. |
| 32 | pub const VERSION_MAJOR: u16 = 0; |
| 33 | |
| 34 | /// Fixed header length in bytes: magic, major version, envelope length. |
| 35 | pub const HEADER_LEN: usize = 8; |
| 36 | |
| 37 | /// Schema identifier for an oxeweb document payload, which admits the kinds 1 to 13 and no others. |
| 38 | pub const SCHEMA_DOC: &'static str = "oxeweb/doc/0"; |
| 39 | |
| 40 | /// Schema identifier for the browser's own chrome, which admits the document kinds and the `edit` |
| 41 | /// node (§4.2). |
| 42 | pub const SCHEMA_CHROME: &'static str = "oxeweb/chrome/0"; |
| 43 | |
| 44 | /// Schema identifier for an application's tree, which admits the document kinds, the `edit` node and |
| 45 | /// the `surface` node (§4.2). |
| 46 | pub const SCHEMA_APP: &'static str = "oxeweb/app/0"; |
| 47 | |
| 48 | /// Schema identifier for a signed message payload, which is a record rather than a node tree. |
| 49 | /// |
| 50 | /// The first payload here that is not a document, and the reason the container names its schema |
| 51 | /// inside the signing input: a message and a document are the same artefact with different |
| 52 | /// payloads and different validators, and neither can be re-labelled as the other after signing. |
| 53 | pub const SCHEMA_POST: &'static str = "daimond/post/0"; |
| 54 | |
| 55 | /// Schema identifier for a self-signed identity card. |
| 56 | /// |
| 57 | /// A card is what a QR code and a paste carry, where a bare public key cannot: it says which key |
| 58 | /// seals and which signs, carries a display label, and names the key it supersedes. It verifies |
| 59 | /// under the key it carries, which proves the holder composed it and proves nothing at all about |
| 60 | /// who the holder is. |
| 61 | pub const SCHEMA_CARD: &'static str = "daimond/card/0"; |
| 62 | |
| 63 | /// Schema identifier for one person sending another a copy of something they own. |
| 64 | /// |
| 65 | /// Implemented by [`share`], which carries the files, the display name, and the consent bit that |
| 66 | /// says whether any of those files is a program. **A share is a COPY the receiver comes to own**: |
| 67 | /// re-sealed to their key, landing in their workspace as theirs, changeable by them and never seen |
| 68 | /// again by the sender. It is not a live view, so there is no content key that survives an edit, |
| 69 | /// nothing to revoke, and nobody's storage to argue about but the receiver's own. |
| 70 | /// |
| 71 | /// # Why this is a schema and not a fifth reference kind |
| 72 | /// |
| 73 | /// The obvious-looking alternative is a fifth arm on [`post::Target`], and it is wrong three times |
| 74 | /// over. |
| 75 | /// |
| 76 | /// A share **carries** what it sends. The thing shared is a copy the receiver comes to own, sealed |
| 77 | /// to their key, and a [`post::Reference`] is a pointer with a fallback sentence — four of them per |
| 78 | /// message, each field capped at 128 bytes. There is nothing to point AT: the content travels. |
| 79 | /// |
| 80 | /// A share is **private**. The four reference kinds are public anchors, globally named and |
| 81 | /// resolvable by anybody holding a session, and [`post::Target`] says in its own documentation that |
| 82 | /// private, device-local pointers are deliberately absent, because the other party cannot |
| 83 | /// dereference one and an interface must never draw a pressable chip that will always fail. A fifth |
| 84 | /// arm for a private object would make that sentence false. |
| 85 | /// |
| 86 | /// And a share must carry **a consent bit that the signature covers**. Where the thing shared holds |
| 87 | /// executable content, the receiver decides whether to run it, and they can only decide honestly if |
| 88 | /// they can check that the SENDER marked it — a flag a relay could add or strip is not a consent |
| 89 | /// flag. A `Reference` has room for no such thing: it carries exactly two keys, and `from_dat` |
| 90 | /// refuses a third. That is the argument that settles it, since the other two might be argued |
| 91 | /// around and this one cannot. |
| 92 | /// |
| 93 | /// # Why adding it cost nothing signed |
| 94 | /// |
| 95 | /// A schema name reaches the signing input length-prefixed (§1.3), so a third name is unambiguous |
| 96 | /// against every artefact already signed under the first two: no post and no card in existence is |
| 97 | /// weakened, re-addressed or made forgeable by this name coming to exist. That is precisely what |
| 98 | /// the length prefix bought, and it is why this could be reserved in a comment first and |
| 99 | /// implemented afterwards with no migration, and with every fixture already committed staying |
| 100 | /// byte for byte the file it was. |
| 101 | pub const SCHEMA_SHARE: &'static str = "daimond/share/0"; |
| 102 | |
| 103 | /// Limits enforced before a document is trusted. See `SPEC.md` §5. |
| 104 | pub mod limit { |
| 105 | /// Maximum size of the tree region, in bytes. |
| 106 | pub const TREE_BYTES: usize = 4 * 1024 * 1024; |
| 107 | /// Maximum size of the envelope region, in bytes. |
| 108 | pub const ENVELOPE_BYTES: usize = 4 * 1024; |
| 109 | /// Maximum number of nodes in a tree. |
| 110 | pub const NODES: usize = 100_000; |
| 111 | /// Maximum number of `surface` nodes in one tree (§4.2). |
| 112 | /// |
| 113 | /// A surface is the one place in the format where something other than the author's data reaches |
| 114 | /// the screen, and every one of them is a live application instance the host must lay out, budget |
| 115 | /// and present. The number is revisable on evidence, like every other limit here; the commitment |
| 116 | /// made now is that there is one, since a tree that may open unboundedly many instances is a tree |
| 117 | /// that may exhaust the host by being opened. |
| 118 | pub const SURFACES: usize = 8; |
| 119 | /// Maximum nesting depth, enforced by the decoder before verification. |
| 120 | /// |
| 121 | /// Set so that a document at the ceiling decodes within a standard 2 MiB worker-thread stack, |
| 122 | /// since a recursive decoder spends a frame per level and a stack overflow aborts the process |
| 123 | /// rather than returning an error. Sixty-four levels of document nesting is already far beyond |
| 124 | /// anything real, where a deeply structured document reaches perhaps twenty, and it matches the |
| 125 | /// default depth limit of the BDAT decoder in `fe2o3_jdat`. |
| 126 | pub const DEPTH: usize = 64; |
| 127 | } |