feat: ML-DSA-65, did:key, named hash algorithm, model provenance, log tiles and witnesses
- hashing.algorithm names the hash function in every document; a consumer refuses one it does not implement (doc.hash.algorithm) - ML-DSA-65 (FIPS 204) is an implemented signature scheme: pure-Python reference in tools/oracle/mldsa.py checked against NIST ACVP vectors, a receipt signed by the new roster key dana, and signature vectors for key generation and a deterministic signature - roster keys may carry a did:key, which the validator checks against the public key (doc.roster.did), and a witness purpose for parties that cosign log checkpoints - model provenance: a correction's author and an annotation's generator name the model with provider, version and the hashes of its weights, prompt and parameters, and where it ran - the transparency log gains the tlog-tiles static layout (fixtures/records/log/), a project settings.log with origin, witnesses and threshold, and an acceptance rule; the fixture checkpoint is cosigned by a witness - store.sql: roster_keys accepts cose and ml-dsa-65 and gains a did column - spec chapters 01, 08, 11, 18, 20 and 23, the rationale, glossary, fixture, vector and tool guides updated; stress harness covers ML-DSA Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
127 files changed
+996
-141
No files matched your search
+50
-4
@@ -26,7 +26,7 @@ from tools.oracle import anchors as AN # noqa: E402
|
||||
from tools.oracle import corrections as CO # noqa: E402
|
||||
from tools.oracle import crypto, db, fingerprint as FP, grade as GR, hashing, jcs, receipts as RC, rights as RI # noqa: E402
|
||||
from tools.oracle import package as PK # noqa: E402
|
||||
from tools.oracle import keys as K, log as LG, targets as TG, vrf # noqa: E402
|
||||
from tools.oracle import keys as K, log as LG, mldsa, targets as TG, vrf # noqa: E402
|
||||
import unicodedata # noqa: E402
|
||||
from tools.oracle.model import Doc # noqa: E402
|
||||
from tools.oracle.notation import fold_math # noqa: E402
|
||||
@@ -129,6 +129,8 @@ def check_records():
|
||||
R = p("fixtures", "records")
|
||||
roster = {k["id"]: k for k in js(os.path.join(R, "roster.json"))["keys"]}
|
||||
for dirpath, _, files in os.walk(R):
|
||||
if os.path.relpath(dirpath, R).split(os.sep)[0] == "log":
|
||||
continue # the log's tiles and checkpoint are checked by check_log
|
||||
for f in sorted(files):
|
||||
path = os.path.join(dirpath, f)
|
||||
rel = os.path.relpath(path, R)
|
||||
@@ -573,7 +575,16 @@ def check_vectors():
|
||||
for c in V("merkle.jsonl"):
|
||||
ok("vectors", crypto.merkle_root(c["items"]) == c["root"], "merkle " + c["id"])
|
||||
for c in V("signatures.jsonl"):
|
||||
if "secret" in c:
|
||||
if "did" in c:
|
||||
ok("vectors", crypto.did_key(c["scheme"], bytes.fromhex(c["public"])) == c["did"], c["id"])
|
||||
elif c["id"].startswith("ml-dsa-65"):
|
||||
pk, sk = mldsa.keygen(bytes.fromhex(c["seed"]))
|
||||
ok("vectors", pk.hex() == c["public"], c["id"] + ": key generation")
|
||||
if "signature" in c:
|
||||
sig = bytes.fromhex(c["signature"])
|
||||
ok("vectors", mldsa.sign(sk, bytes.fromhex(c["message"])) == sig and mldsa.verify(pk, bytes.fromhex(c["message"]), sig)
|
||||
and not mldsa.verify(pk, b"x", sig), c["id"] + ": signature")
|
||||
elif "secret" in c:
|
||||
sk = bytes.fromhex(c["secret"])
|
||||
ok("vectors", crypto.ed25519_public(sk).hex() == c["public"]
|
||||
and crypto.ed25519_sign(sk, c["message"].encode()).hex() == c["signature"], c["id"])
|
||||
@@ -692,6 +703,7 @@ def check_vectors():
|
||||
"checkpoint %s: verify" % c["id"])
|
||||
bad = c["note"].replace(c["origin"], c["origin"] + "x", 1)
|
||||
ok("vectors", LG.verify_checkpoint(bad, ROSTER_BY_ID_CHECK)[1] == [], "checkpoint %s: a changed note verifies" % c["id"])
|
||||
ok("vectors", sorted(LG.tiles(c["entries"])) == c["tiles"], "checkpoint %s: tile paths" % c["id"])
|
||||
|
||||
|
||||
def check_determinism():
|
||||
@@ -792,8 +804,8 @@ def check_sql():
|
||||
for m in prj["members"]:
|
||||
conn.execute("INSERT INTO members VALUES (?,?,?,?)", (prj["key"], m["person"], m["role"], jcs.dumps(m["keys"])))
|
||||
for k in js(os.path.join(R, "roster.json"))["keys"]:
|
||||
conn.execute("INSERT INTO roster_keys VALUES (?,?,?,?,?,?,?,?,?)",
|
||||
("smithlab", k["id"], jcs.dumps(k["person"]), k["role"], k["scheme"], k["public"],
|
||||
conn.execute("INSERT INTO roster_keys VALUES (?,?,?,?,?,?,?,?,?,?)",
|
||||
("smithlab", k["id"], jcs.dumps(k["person"]), k["role"], k["scheme"], k["public"], k.get("did"),
|
||||
jcs.dumps(k["purposes"]), k["registered_at"], k["revoked_at"]))
|
||||
dg = js(os.path.join(R, "digitization.json"))
|
||||
conn.execute("INSERT INTO digitizations VALUES (?,?,?,?,?,?,?,?)",
|
||||
@@ -1003,6 +1015,39 @@ def check_counts():
|
||||
% (rel, i, word, what, have[what]))
|
||||
|
||||
|
||||
def check_log():
|
||||
"""The log fixture (spec/23): its tiles hold the example's receipts and roster, its checkpoint is signed by the
|
||||
team and cosigned by the witness, the project accepts it, and a checkpoint short of witnesses is refused."""
|
||||
R = p("fixtures", "records")
|
||||
roster = {k["id"]: k for k in js(os.path.join(R, "roster.json"))["keys"]}
|
||||
log_dir = os.path.join(R, "log")
|
||||
entries, note = LG.read_log(log_dir)
|
||||
receipts = jl(p("fixtures", "packages", HALE, "receipts.jsonl"))
|
||||
want = [LG.entry(r) for r in receipts] + [LG.entry(js(os.path.join(R, "roster.json")))]
|
||||
ok("log", entries == want, "the entries tiles do not hold the example's receipts and roster in order")
|
||||
parsed, verified = LG.verify_checkpoint(note, roster)
|
||||
ok("log", parsed and parsed["root"] == LG.root(entries) and parsed["size"] == len(entries), "checkpoint root or size")
|
||||
ok("log", verified == ["ana", "wit"], "the checkpoint is not signed by the team and the witness: %s" % verified)
|
||||
for path, data in LG.tiles(entries).items():
|
||||
full = os.path.join(log_dir, *path.split("/"))
|
||||
ok("log", os.path.exists(full) and open(full, "rb").read() == data, "tile %s differs" % path)
|
||||
ok("log", LG.tile_path(0, 1234067) == "tile/0/x001/x234/067" and LG.tile_path("entries", 5, 9) == "tile/entries/005.p/9",
|
||||
"tile paths are not encoded as transparency logs share them")
|
||||
settings = js(os.path.join(R, "project.json"))["settings"]["log"]
|
||||
ok("log", LG.accept_checkpoint(note, roster, settings) == (True, []), "the project does not accept its checkpoint")
|
||||
ok("log", not LG.accept_checkpoint(note, roster, dict(settings, threshold=2))[0], "two witnesses were not required")
|
||||
ok("log", not LG.accept_checkpoint(note, roster, dict(settings, origin="other"))[0], "another origin was accepted")
|
||||
alone = LG.sign_checkpoint(settings["origin"], entries, [("wit", __import__("tools.build", fromlist=["secret"]).secret("wit"))])
|
||||
ok("log", not LG.accept_checkpoint(alone, roster, settings)[0], "a checkpoint without the team's key was accepted")
|
||||
# The ML-DSA-65 receipt verifies under dana's key, and fails under a tampered payload.
|
||||
rec = js(os.path.join(R, "receipt-ml-dsa.json"))
|
||||
ok("log", rec["signature"]["scheme"] == "ml-dsa-65" and RC.verify_signature(rec, roster), "the ML-DSA receipt does not verify")
|
||||
ok("log", not RC.verify_signature(dict(rec, at="2026-10-13T09:00:01Z"), roster), "a changed ML-DSA receipt verifies")
|
||||
ok("log", RC.receipt_status(rec, receipts + [rec], roster) == "valid", "the ML-DSA receipt is not valid")
|
||||
for k in roster.values():
|
||||
ok("log", crypto.did_key(k["scheme"], base64.b64decode(k["public"])) == k["did"], "did of %s" % k["id"])
|
||||
|
||||
|
||||
def check_doc_values():
|
||||
"""Every node ID, record ID or 64-hex hash the prose quotes exists in the fixtures or vectors, so the guides
|
||||
never describe a fixture that no longer reads that way."""
|
||||
@@ -1145,6 +1190,7 @@ def main():
|
||||
check_prose()
|
||||
check_links()
|
||||
check_doc_values()
|
||||
check_log()
|
||||
check_site()
|
||||
for k in sorted(COUNTS):
|
||||
print("%-17s %5d checks" % (k, COUNTS[k]))
|
||||
|
||||
Reference in new issue
Block a user