solvi.signature¶
A signature of a trace or a store (64 bytes by default) that names the one changed record and restores its content hash (preview).
A signature of a trace or a store: a few numbers that, besides saying "changed", say WHICH record changed and what its content hash was.
The hash chain (Trace.replay, TraceStorage.verify) is strict integrity. When a record is edited and every hash after it
is recomputed together with the stored head, a head kept elsewhere (verify(anchor=...)) still says "rewritten" — but
not where, and not what was there. A signature kept next to that head answers both, for one changed record:
sig = sign(store) # {"alg": "syndrome", "count", "root": 2 numbers}; keep it where you keep the head
...
locate(store, sig) # None: unchanged; an index: the one record that changed
repair(store, sig) # {"index", "digest": its original content hash, "match": the candidate it equals}
Every signed item is reduced to its content hash (SHA-256; the chain fields prev, hash, id left out, so recomputing
the chain does not move the other items). The code is named in the signature's "alg":
- "syndrome": S0 = Σ h_i and S1 = Σ (i+1)·h_i mod a 256-bit prime — 64 bytes. One change at k by d moves S0 by d and S1 by (k+1)·d: k = ΔS1 / ΔS0, and the original hash is h_k − d, all 32 bytes. Several changes give a k outside the store (except with probability ~n / 2^256): detected, not located. A classical single-error-locating code.
The positional octonion code that was the second "alg" up to 0.7 is an experiment in benchmarks/octonion_signature.py since 0.8: on a flat store it located exactly as the syndrome code does, 4x larger and ~60x slower (benchmarks/trace_signature.py compares the two).
Limits (tests/test_signature.py pins each):
- one changed record is located and its content hash restored; the record itself (its value) only from candidates
(a backup, another replica, the values that fact had elsewhere) — the signature holds a hash, not the record;
- several changed records (a reorder is two) are detected but not located: locate raises NotLocatable;
- a deleted or inserted record shifts every position after it: detected as a count or content change, not located;
- records appended after signing are not covered (sign again or extend(), as with the head); the signed prefix is
checked;
- it is an error-locating code, not a MAC: someone who can rewrite the signature too can forge it. Keep it where you
keep the head.
NotLocatable ¶
Bases: ValueError
The signature does not match, and not because of one changed record (several changed, reordered, deleted or inserted records, or another count).
sign ¶
The signature of a trace, a Response, a TraceStorage (its chained records) or a list of items → {"alg", "count", "root"}: plain JSON — keep it next to the head, in a ticket, a log you do not control.
alg "syndrome" (the only one): root = two numbers mod a 256-bit prime (hex; 64 bytes) — S0 = Σ h_i, S1 = Σ (i+1) h_i over the content hashes; one change at k by d moves them by d and (k+1) d, which gives k and the whole original hash.
extend ¶
The signature after appending new_items to what signature signed, in O(len(new_items)). start: the position
of the first new item (default: signature["count"]).
load ¶
A signature from JSON text, a file path, or a dict (checked: its "alg" says how to read it).
check ¶
Compare obj with a signature taken earlier (its "alg" says which code) → {"ok", "alg", "count", "signed",
"index", "digest", "match", "reason"}.
ok: the first signed items are the ones signed. Otherwise, when one item changed: index (its position; for a trace
0 is the input, i the record i-1), digest (the hex of its original content hash, 32 bytes) and match (the first of candidates — records, or for a trace record also plain values — whose
content hash is that digest; None). When it cannot be located: index None and the reason. Items appended after
signing are not covered and do not count.
locate ¶
None when the signed items are unchanged; else the position of the one that changed (for a Trace / Response 0 is the input, i the record i-1; for a store, its seq). NotLocatable when the change is not one item.
repair ¶
The one changed item → {"index", "digest": hex of its original content hash, "match": the candidate that is the original, or None}; None when nothing changed; NotLocatable as locate. A candidate is a record (a stored dict, a trace Record, bytes) — or, for a trace record, its original value.