From 41c6d4f1abdf9a2c765cbd617360d0d0cd2d7b7c Mon Sep 17 00:00:00 2001 From: log Date: Sat, 10 Oct 2026 00:41:03 +0000 Subject: size 104819 --- reveal/v5-000017/check.py | 319 ++++++++++++++++++++++++++++++++++ reveal/v5-000017/manifest.txt | 8 + reveal/v5-000017/manifest.txt.ots | Bin 0 -> 654 bytes reveal/v5-000017/r33591412.ct | Bin 0 -> 24606 bytes reveal/v5-000017/r33591412.nostr.json | 1 + reveal/v5-000017/r33591412.tlock | 11 ++ reveal/v5-000017/r34743412.ct | Bin 0 -> 24514 bytes reveal/v5-000017/r34743412.nostr.json | 1 + reveal/v5-000017/r34743412.tlock | 11 ++ reveal/v5-000017/seal.json | 43 +++++ 10 files changed, 394 insertions(+) create mode 100644 reveal/v5-000017/check.py create mode 100644 reveal/v5-000017/manifest.txt create mode 100644 reveal/v5-000017/manifest.txt.ots create mode 100644 reveal/v5-000017/r33591412.ct create mode 100644 reveal/v5-000017/r33591412.nostr.json create mode 100644 reveal/v5-000017/r33591412.tlock create mode 100644 reveal/v5-000017/r34743412.ct create mode 100644 reveal/v5-000017/r34743412.nostr.json create mode 100644 reveal/v5-000017/r34743412.tlock create mode 100644 reveal/v5-000017/seal.json diff --git a/reveal/v5-000017/check.py b/reveal/v5-000017/check.py new file mode 100644 index 0000000..7f86b8d --- /dev/null +++ b/reveal/v5-000017/check.py @@ -0,0 +1,319 @@ +#!/usr/bin/env python3 +""" +Check Echo's self-revealing ledger seals (`sha256-live-v5`) without trusting Echo. + + python3 check.py SEAL_DIR [SEAL_DIR ...] [--tle PATH] [--offline] + +Needs Python 3.11+, the `cryptography` package (ChaCha20-Poly1305) and drand's `tle` binary +(`go install github.com/drand/tlock/cmd/tle@v1.2.0`). It imports nothing of Echo's: a checker that needs the thing it +checks is not a checker, which is why it is copied, whole, into every seal directory. + +Each SEAL_DIR is one seal: `manifest.txt` (the exact text the seal's digest is the sha256 of), `seal.json`, and per +reveal group `r.tlock` (the group's key, time-locked to that drand round) and `r.ct` (the group, +encrypted under that key), and, once the group has been revealed, `r.held.json` (how many of its withheld +lines Echo publishes no salt for, and why) and any number of `r.salts..json`: each the salts of withheld +lines Echo decided to open and had not erased, each with its line. `manifest.txt.ots`, when present, is an +OpenTimestamps proof over the manifest: `ots verify manifest.txt.ots` checks it against Bitcoin. + +What it checks, from the files alone and the beacon: + 1. the manifest hashes to the seal's digest, and each seal names the one before it (when several are given); + 2. each group's ciphertext and locked key hash to what the manifest commits, and the key is locked to the + manifest's round of the manifest's chain; + 3. for every group whose round the beacon has signed: the round's signature as each relay serves it, the key + unlocked with it, the group decrypted, its bytes hashed to the committed digest, its lines and withheld lines + counted against the committed counts, its lines in id order, and every line's reveal day recomputed from its + own settling instant under the seal's named rule (`utc-day-after-close`: the first 00:00 UTC after + `settles_at`, else after the deadline); + 4. for every opened group with releases: each names this seal, round and group and its own number; each released + salt and line hash to the withheld line's committed hash; no line is settled twice; and a hold, where there is + one, names the group and counts no more lines than are withheld; + 5. once every group of every seal from the first is open and every withheld line released: the union of their v4 + lines, sorted by id, rehashes to the v4 digest the last seal follows, so no sealed forecast was left out of a + reveal. Where a withheld line has no released salt (held, erased, or not yet released) that digest cannot be + rebuilt, and this says so and why: what is checked then is only that every forecast the v4 seal covers is in a + group, counted. A withheld line is never called verified. + +A group whose round is not yet signed, or whose withheld lines have no release yet, is reported as pending or held, +not as a failure. Exit 0 when everything checkable passed, 1 when anything did not match. +""" +from __future__ import annotations + +import argparse +import base64 +import hashlib +import json +import re +import subprocess +import sys +import urllib.request +import zlib +from datetime import datetime, timedelta, timezone +from pathlib import Path + + +def sha(b: bytes) -> str: + return hashlib.sha256(b).hexdigest() + + +def parse_manifest(text: str) -> dict: + lines = text.split("\n") + if lines[0] != "sha256-live-v5": + raise ValueError(f"not a v5 manifest: {lines[0]!r}") + m = {"groups": []} + for ln in lines[1:]: + word, _, rest = ln.partition(" ") + if word == "prev": + m["prev"] = None if rest == "-" else rest + elif word == "follows": + seq, digest, n = rest.split(" ") + m["follows"] = {"seq": int(seq), "digest": digest, "n": int(n)} + elif word == "covers": + m["covers"] = rest.split(" ") + elif word == "variant": + m["variant"] = json.loads(rest) + elif word == "counts": + m["n"], m["n_withheld"], m["n_groups"] = map(int, rest.split(" ")) + elif word == "group": + r, at, n, w, digest, ct, key, late = rest.split(" ") + m["groups"].append({"round": int(r), "reveal_at": at, "n": int(n), "n_withheld": int(w), + "digest": digest, "ct": ct, "key": key, "late": late == "1"}) + else: + raise ValueError(f"unknown manifest line {ln!r}") + return m + + +def stanza(locked: str) -> tuple[int, str] | None: + body = "".join(ln for ln in locked.strip().splitlines() if not ln.startswith("-----")) + m = re.search(rb"-> tlock (\d+) ([0-9a-f]{64})", base64.b64decode(body)) + return (int(m.group(1)), m.group(2).decode()) if m else None + + +def round_at(beacon: dict, at: datetime) -> int: + t = at.timestamp() + whole = int(t) + (t != int(t)) + if whole <= beacon["genesis_time"]: + return 1 + return -(-(whole - beacon["genesis_time"]) // beacon["period"]) + 1 + + +def reveal_day(closes: datetime) -> datetime: + d = closes.astimezone(timezone.utc).date() + timedelta(days=1) + return datetime(d.year, d.month, d.day, tzinfo=timezone.utc) + + +def parse_line(ln: str) -> dict: + """One line: `id|issued_at|deadline|forecaster|p|settlement|anchoropen:` or `...withheld:`.""" + numeric, tab, text = ln.partition("\t") + if not tab: + raise ValueError("a line without its text layer") + fid, issued, deadline, forecaster, p, rest = numeric.split("|", 5) + dec = json.JSONDecoder() + settle, end = dec.raw_decode(rest) + if rest[end] != "|": + raise ValueError("settlement not followed by the anchor") + anchor, end2 = dec.raw_decode(rest, end + 1) + if end2 != len(rest): + raise ValueError("trailing bytes after the anchor") + out = {"id": int(fid), "issued_at": issued, "deadline": deadline, "forecaster": forecaster, "p": float(p), + "settlement": settle, "anchor": anchor} + if text.startswith("open:"): + out["v4"] = text[5:] + if not out["v4"].startswith(f"{fid}|"): + raise ValueError("the text layer is another forecast's line") + elif re.fullmatch(r"withheld:[0-9a-f]{64}", text): + out["withheld"] = text[9:] + else: + raise ValueError(f"unknown text layer {text[:20]!r}") + return out + + +def beacon_round(beacon: dict, rnd: int) -> dict: + """What each relay serves for the round: its signature, or why there is none.""" + out = {} + for relay in beacon["relays"]: + url = f"{relay.rstrip('/')}/{beacon['chain_hash']}/public/{rnd}" + try: + with urllib.request.urlopen(url, timeout=20) as r: # noqa: S310 — the seal's own relays + out[relay] = json.loads(r.read()).get("signature") + except Exception as exc: # noqa: BLE001 — report, go on + out[relay] = f"unavailable ({type(exc).__name__})" + return out + + +def unlock(tle: str, beacon: dict, locked: str) -> bytes | None: + """The key, or None while the round is unsigned. Each relay is tried in turn.""" + errors = [] + for relay in beacon["relays"]: + r = subprocess.run([tle, "-d", "-n", relay.rstrip("/") + "/", "-c", beacon["chain_hash"]], + input=locked.encode(), capture_output=True, timeout=120, check=False) + if r.returncode == 0: + return r.stdout + if b"too early" in r.stderr: + return None + errors.append(f"{relay}: {r.stderr.decode(errors='replace').strip()[-160:]}") + raise RuntimeError("; ".join(errors)) + + +def release(d: Path, seal_digest: str, g: dict, lines: list[dict], say) -> tuple[dict[int, str], int, str | None]: + """An opened group's releases and hold, checked against the group: (id -> the v4 line each released salt opens, + how many withheld lines were opened and found erased, and the hold's reason where Echo published one).""" + r = g["round"] + shut = {x["id"]: x["withheld"] for x in lines if "withheld" in x} + out, erased, seen, bad = {}, set(), set(), [] + files = sorted(d.glob(f"r{r}.salts.*.json"), key=lambda p: int(p.name.split(".")[-2])) + for k, path in enumerate(files): + rel = json.loads(path.read_text()) + say(rel.get("format") == "echo-reveal-salts/1" and rel.get("seal") == seal_digest + and rel.get("round") == r and rel.get("group") == g["digest"] and rel.get("n") == k + and path.name == f"r{r}.salts.{k}.json", + f"round {r}: release {k} names this seal, round and group, and follows the one before") + for x in rel.get("released", []): + i, line = x["id"], x["line"] + opens = (i in shut and i not in seen and line.startswith(f"{i}|") + and sha(bytes.fromhex(x["salt"]) + line.encode()) == shut[i]) + if opens: + out[i] = line + else: + bad.append(i) + seen.add(i) + for i in rel.get("erased", []): + if i not in shut or i in seen: + bad.append(i) + erased.add(i) + seen.add(i) + if files: + say(not bad, f"round {r}: {len(out)} released lines open their committed hashes, {len(erased)} opened lines " + f"found erased, none settled twice" + (f" ({len(bad)} do not, e.g. {bad[:3]})" if bad else "")) + why = None + hold = d / f"r{r}.held.json" + if hold.exists(): + h = json.loads(hold.read_text()) + say(h.get("format") == "echo-reveal-held/1" and h.get("seal") == seal_digest and h.get("round") == r + and h.get("group") == g["digest"] and 0 <= h.get("held", -1) <= len(shut), + f"round {r}: the hold names this seal, round and group, and counts at most its withheld lines") + why = str(h.get("why") or "no reason given") + left = len(shut) - len(out) - len(erased) + if left: + print(f" .... round {r}: {left} withheld lines have no released salt: counted, not rehashed" + + (f" (Echo publishes none: {why})" if why else " (no release or hold published yet)")) + return out, len(erased), why + + +def check(dirs: list[Path], tle: str = "tle", offline: bool = False) -> int: + from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 + + failed, pending, withheld, held, erased, union = 0, 0, 0, 0, 0, {} + reasons: set[str] = set() + first_prev = "unset" + + def say(ok: bool, what: str) -> None: + nonlocal failed + failed += not ok + print(f" {'PASS' if ok else 'FAIL'} {what}") + + prev_digest = None + seals = [] + for d in dirs: + text = (d / "manifest.txt").read_bytes() + row = json.loads((d / "seal.json").read_text()) + seals.append((d, text, row, parse_manifest(text.decode()))) + seals.sort(key=lambda s: s[2]["seq"]) + last = None + for i, (d, text, row, m) in enumerate(seals): + print(f"v5 seal {row['seq']}: {m['n']} forecasts in {m['n_groups']} reveal groups, {m['n_withheld']} withheld, " + f"follows v4 seal {m['follows']['seq']}") + say(sha(text) == row["digest"], f"sha256(manifest.txt) is the seal's digest {row['digest'][:16]}…") + say(len(m["groups"]) == m["n_groups"] and sum(g["n"] for g in m["groups"]) == m["n"] + and sum(g["n_withheld"] for g in m["groups"]) == m["n_withheld"], "the manifest's counts add up") + if i == 0: + first_prev = m["prev"] + else: + say(m["prev"] == prev_digest, "it names the seal before it") + prev_digest, last = row["digest"], m + b = m["variant"]["beacon"] + for g in m["groups"]: + r = g["round"] + ct, locked = (d / f"r{r}.ct").read_bytes(), (d / f"r{r}.tlock").read_text() + say(sha(ct) == g["ct"] and sha(locked.encode()) == g["key"], + f"round {r}: the ciphertext and the locked key are the committed ones") + say(stanza(locked) == (r, b["chain_hash"]), f"round {r}: the key is locked to that round of that chain") + say(round_at(b, datetime.fromisoformat(g["reveal_at"])) == r, f"round {r} is signed at {g['reveal_at']}") + if not offline: + sigs = beacon_round(b, r) + good = {s for s in sigs.values() if s and not s.startswith("unavailable")} + print(f" .... round {r} signature from {len(sigs)} relays: " + + ("not yet signed" if not good else f"{len(good)} distinct: {min(good)[:24]}…")) + if len(good) > 1: + say(False, f"round {r}: relays disagree on its signature") + key = unlock(tle, b, locked) + if key is None: + pending += 1 + print(f" .... round {r} ({g['reveal_at']}) not yet signed: {g['n']} forecasts stay locked") + continue + try: + plain = zlib.decompress(ChaCha20Poly1305(key).decrypt(b"\x00" * 12, ct, None)) + except Exception as exc: # noqa: BLE001 — a finding, not a crash + say(False, f"round {r}: the ciphertext does not open under its key ({type(exc).__name__})") + continue + say(sha(plain) == g["digest"], f"round {r}: the opened group hashes to its committed digest") + lines = [parse_line(ln) for ln in plain.decode().split("\n")] + ids = [x["id"] for x in lines] + say(len(lines) == g["n"] and sum("withheld" in x for x in lines) == g["n_withheld"], + f"round {r}: {len(lines)} lines, {g['n_withheld']} withheld, as committed") + say(ids == sorted(set(ids)), f"round {r}: lines in id order, none twice") + due = datetime.fromisoformat(g["reveal_at"]) + if m["variant"].get("reveal") == "utc-day-after-close": + wrong = [x["id"] for x in lines if reveal_day(datetime.fromisoformat( + (x["settlement"] or {}).get("settles_at") or x["deadline"])) != due] + say(not wrong, f"round {r}: every line belongs to this reveal day" + + (f" ({len(wrong)} do not, e.g. {wrong[:3]})" if wrong else "")) + else: + print(f" .... round {r}: reveal rule {m['variant'].get('reveal')!r} is not one this checker " + "recomputes; which lines belong to the day is unchecked") + opened, gone, why = release(d, row["digest"], g, lines, say) + for x in lines: + if x["id"] in union: + say(False, f"forecast {x['id']} is in two groups") + union[x["id"]] = x.get("v4") or opened.get(x["id"]) + shut = sum(1 for x in lines if "withheld" in x and x["id"] not in opened) - gone + erased += gone + if why is None: + withheld += shut + else: + held += shut + reasons.add(why) + if last is not None: + if first_prev is not None: + print(" .... completeness: the first seal given is not the chain's first; give every seal from it") + elif pending: + print(f" .... completeness: {pending} groups still locked; it is checkable once all have opened") + elif withheld or held or erased: + told = [f"{withheld} withheld lines not yet released"] if withheld else [] + told += ([f"{held} withheld lines held, for which Echo publishes no salt ({'; '.join(sorted(reasons))})"] + if held else []) + told += [f"{erased} erased stay hashes"] if erased else [] + print(f" .... completeness: {', '.join(told)}: those lines can be counted but not rehashed, so the v4 " + f"lines cannot all be rebuilt and the v4 digest is NOT checked; {len(union)} forecasts against " + f"the v4 seal's {last['follows']['n']}") + say(len(union) == last["follows"]["n"], "every forecast the v4 seal covers is in a group, counted") + else: + got = sha("\n".join(union[k] for k in sorted(union)).encode()) + say(got == last["follows"]["digest"] and len(union) == last["follows"]["n"], + f"the union of {len(union)} lines rehashes to v4 seal {last['follows']['seq']}'s digest") + print("\nNOT VERIFIED" if failed else f"\nVERIFIED{f' ({pending} groups not yet revealed)' if pending else ''}") + return 1 if failed else 0 + + +def main(argv: list[str]) -> int: + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("dirs", nargs="*", default=[str(Path(__file__).resolve().parent)], + help="seal directories, any order (default: the one this script sits in)") + ap.add_argument("--tle", default="tle", help="the tle binary that unlocks a key once its round is signed") + ap.add_argument("--offline", action="store_true", + help="do not fetch each round's signature from the relays for display; tle still asks them") + args = ap.parse_args(argv) + return check([Path(d) for d in args.dirs], tle=args.tle, offline=args.offline) + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/reveal/v5-000017/manifest.txt b/reveal/v5-000017/manifest.txt new file mode 100644 index 0000000..c307f22 --- /dev/null +++ b/reveal/v5-000017/manifest.txt @@ -0,0 +1,8 @@ +sha256-live-v5 +prev 0cd1c8961817e4c0dc619fe20aaee125803f10c015a73b46968584890932d48f +follows 47 e41a99ce4ba46c30407f22c347ffd216ab757896912049d270712e92dcfb2a9c 53470 +covers 2026-10-09T23:07:57.505658Z 2026-10-10T00:40:08.667562Z +variant {"beacon":{"chain_hash":"52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971","genesis_time":1692803367,"period":3,"relays":["https://api.drand.sh","https://api2.drand.sh","https://api3.drand.sh","https://drand.cloudflare.com"],"scheme":"bls-unchained-g1-rfc9380"},"cipher":"chacha20poly1305-zlib6-nonce0","gate":{"default":"withheld: a kept salt can be released later; an opened text can never be withdrawn","opened":"none","why":"no decision by Echo yet"},"line":"numericopen: | numericwithheld:sha256(salt||v4 line)","reveal":"utc-day-after-close","salts":"key ring: one content a seal, one part a line","tool":{"name":"tle","sha256":"69157d8c14a1d390d1b7744946288f83d4b10748471d5447f9abb77a5902c541","version":"tlock v1.2.0 -- github.com/drand/tlock"}} +counts 849 849 2 +group 33591412 2026-11-02T00:00:00.000000Z 426 426 1f875527d2c06344dfa588c3d12588cd2053c75c5059887d128a50270b944ef1 19c46a05f70c3f41f0a1eddb7d7a0d18e8f7fd27755fe2363410e318cadb4d25 f5f0e45c19fc399e18e3326bcb1d916912e4028cf5d944c0a09717d5e9fe7af6 0 +group 34743412 2026-12-12T00:00:00.000000Z 423 423 565b11a4a8c453e45bfbfcd355b622d86ce7ba128938ac3160b0b1aeedf17562 41e10e30d169beb997873e387494123c21b753d3e9c55d5290e0310422fa1c13 c99a6bd6100ab3e2fc4de46d9e890884bafe7ad37b9034fa760254b9ae7da7b4 0 \ No newline at end of file diff --git a/reveal/v5-000017/manifest.txt.ots b/reveal/v5-000017/manifest.txt.ots new file mode 100644 index 0000000..e5936d6 Binary files /dev/null and b/reveal/v5-000017/manifest.txt.ots differ diff --git a/reveal/v5-000017/r33591412.ct b/reveal/v5-000017/r33591412.ct new file mode 100644 index 0000000..f2948e2 Binary files /dev/null and b/reveal/v5-000017/r33591412.ct differ diff --git a/reveal/v5-000017/r33591412.nostr.json b/reveal/v5-000017/r33591412.nostr.json new file mode 100644 index 0000000..f8cb359 --- /dev/null +++ b/reveal/v5-000017/r33591412.nostr.json @@ -0,0 +1 @@ +{"content":"-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHRsb2NrIDMzNTkxNDEyIDUyZGI5YmE3\nMGUwY2MwZjZlYWY3ODAzZGQwNzQ0N2ExZjU0Nzc3MzVmZDNmNjYxNzkyYmE5NDYw\nMGM4NGU5NzEKclprNlFVMVpqZEZEYUtGODU5NU5Oc1BhNGVQVVA5bUJER2hNNlRu\nT1lDemtXcGxHdFJGNmZmanFXelV5UGQ2VgpGbFlSS3l6NXM2dmkxU2dONU9sMEtM\nWmR4c2lIaUJCd3JDQ2lBa1FuKzJEampURld5Nk95WHo0WlBWR256UDNmCmlJbHhO\nZ2huUnhJTkZaaGNyVHFycEkzT0FMaGRSemFBUmxLd0J0bEVXN00KLS0tIFFTazk3\nNkpmUTk4N1F4L0E0a0pBKytUaW9mUWFTQ2N3b081eUtNWTFaVXcKXPVRtePRIa2u\nahlGeaIhXXktOzQe1NcqTaqQDooOBGtzSpJPn3bYzR7DmC3EfOuBzrnMQw6N9jg4\noMlxKFfa8A==\n-----END AGE ENCRYPTED FILE-----\n","tags":[["t","echo-ledger-reveal"],["round","33591412"],["reveal_at","2026-11-02T00:00:00.000000Z"],["chain","52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971"],["digest","1f875527d2c06344dfa588c3d12588cd2053c75c5059887d128a50270b944ef1"],["ct","19c46a05f70c3f41f0a1eddb7d7a0d18e8f7fd27755fe2363410e318cadb4d25"],["seal","96bc56257175d4d0f443178387ce7d3af598dbb1c85dd7c226c21e38f4b270a0"]]} diff --git a/reveal/v5-000017/r33591412.tlock b/reveal/v5-000017/r33591412.tlock new file mode 100644 index 0000000..436938d --- /dev/null +++ b/reveal/v5-000017/r33591412.tlock @@ -0,0 +1,11 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHRsb2NrIDMzNTkxNDEyIDUyZGI5YmE3 +MGUwY2MwZjZlYWY3ODAzZGQwNzQ0N2ExZjU0Nzc3MzVmZDNmNjYxNzkyYmE5NDYw +MGM4NGU5NzEKclprNlFVMVpqZEZEYUtGODU5NU5Oc1BhNGVQVVA5bUJER2hNNlRu +T1lDemtXcGxHdFJGNmZmanFXelV5UGQ2VgpGbFlSS3l6NXM2dmkxU2dONU9sMEtM +WmR4c2lIaUJCd3JDQ2lBa1FuKzJEampURld5Nk95WHo0WlBWR256UDNmCmlJbHhO +Z2huUnhJTkZaaGNyVHFycEkzT0FMaGRSemFBUmxLd0J0bEVXN00KLS0tIFFTazk3 +NkpmUTk4N1F4L0E0a0pBKytUaW9mUWFTQ2N3b081eUtNWTFaVXcKXPVRtePRIa2u +ahlGeaIhXXktOzQe1NcqTaqQDooOBGtzSpJPn3bYzR7DmC3EfOuBzrnMQw6N9jg4 +oMlxKFfa8A== +-----END AGE ENCRYPTED FILE----- diff --git a/reveal/v5-000017/r34743412.ct b/reveal/v5-000017/r34743412.ct new file mode 100644 index 0000000..00b3d7d Binary files /dev/null and b/reveal/v5-000017/r34743412.ct differ diff --git a/reveal/v5-000017/r34743412.nostr.json b/reveal/v5-000017/r34743412.nostr.json new file mode 100644 index 0000000..7525ea0 --- /dev/null +++ b/reveal/v5-000017/r34743412.nostr.json @@ -0,0 +1 @@ +{"content":"-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHRsb2NrIDM0NzQzNDEyIDUyZGI5YmE3\nMGUwY2MwZjZlYWY3ODAzZGQwNzQ0N2ExZjU0Nzc3MzVmZDNmNjYxNzkyYmE5NDYw\nMGM4NGU5NzEKcVJ1V0xaTlhrZUkrcDRTWlQzTy9kc0lYZjdnTVhFbTFMcE1wblVH\nSVA2dGpzd25OcFBiU1pjZ0ZqM3pGVXhPRQpHTzkwOStVU3NieTJJeGJXOG5QSWU2\nS1BjNThjejVCTEF4cmRmY0xVQkM3M2JkWUlxRFVvQ3N2UFV2MVpTcXhICmJ4bTVx\nVVVNZjBIZ1l1clA4Y2hySENNd1FIaWxNOFlIanVaNHlZQWFNbWcKLS0tIDhWcFBL\naGxnbmhkQSs1WmFmTStRNGhZa1dYY2J1VGJidUs1VmYrYkNYcVUKe+jlXWMsvyaX\nvRk4OnsxZj7xkM0INpmH6i0HjFgK1pn1kLeLUVYdyCLydQrMvIDpXej2jFB6A1Ka\njU2vFbdDTg==\n-----END AGE ENCRYPTED FILE-----\n","tags":[["t","echo-ledger-reveal"],["round","34743412"],["reveal_at","2026-12-12T00:00:00.000000Z"],["chain","52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971"],["digest","565b11a4a8c453e45bfbfcd355b622d86ce7ba128938ac3160b0b1aeedf17562"],["ct","41e10e30d169beb997873e387494123c21b753d3e9c55d5290e0310422fa1c13"],["seal","96bc56257175d4d0f443178387ce7d3af598dbb1c85dd7c226c21e38f4b270a0"]]} diff --git a/reveal/v5-000017/r34743412.tlock b/reveal/v5-000017/r34743412.tlock new file mode 100644 index 0000000..950fb27 --- /dev/null +++ b/reveal/v5-000017/r34743412.tlock @@ -0,0 +1,11 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHRsb2NrIDM0NzQzNDEyIDUyZGI5YmE3 +MGUwY2MwZjZlYWY3ODAzZGQwNzQ0N2ExZjU0Nzc3MzVmZDNmNjYxNzkyYmE5NDYw +MGM4NGU5NzEKcVJ1V0xaTlhrZUkrcDRTWlQzTy9kc0lYZjdnTVhFbTFMcE1wblVH +SVA2dGpzd25OcFBiU1pjZ0ZqM3pGVXhPRQpHTzkwOStVU3NieTJJeGJXOG5QSWU2 +S1BjNThjejVCTEF4cmRmY0xVQkM3M2JkWUlxRFVvQ3N2UFV2MVpTcXhICmJ4bTVx +VVVNZjBIZ1l1clA4Y2hySENNd1FIaWxNOFlIanVaNHlZQWFNbWcKLS0tIDhWcFBL +aGxnbmhkQSs1WmFmTStRNGhZa1dYY2J1VGJidUs1VmYrYkNYcVUKe+jlXWMsvyaX +vRk4OnsxZj7xkM0INpmH6i0HjFgK1pn1kLeLUVYdyCLydQrMvIDpXej2jFB6A1Ka +jU2vFbdDTg== +-----END AGE ENCRYPTED FILE----- diff --git a/reveal/v5-000017/seal.json b/reveal/v5-000017/seal.json new file mode 100644 index 0000000..0725e11 --- /dev/null +++ b/reveal/v5-000017/seal.json @@ -0,0 +1,43 @@ +{ + "algorithm": "sha256-live-v5", + "anchor_ref": "sha256:96bc56257175d4d0f443178387ce7d3af598dbb1c85dd7c226c21e38f4b270a0", + "covers_after": "2026-10-09T23:07:57.505658Z", + "covers_through": "2026-10-10T00:40:08.667562Z", + "digest": "96bc56257175d4d0f443178387ce7d3af598dbb1c85dd7c226c21e38f4b270a0", + "follows_seq": 47, + "n_forecasts": 849, + "n_groups": 2, + "n_withheld": 849, + "prev_digest": "0cd1c8961817e4c0dc619fe20aaee125803f10c015a73b46968584890932d48f", + "prev_seq": 16, + "sealed_at": "2026-10-10T00:40:35.447200Z", + "seq": 17, + "variant": { + "beacon": { + "chain_hash": "52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971", + "genesis_time": 1692803367, + "period": 3, + "relays": [ + "https://api.drand.sh", + "https://api2.drand.sh", + "https://api3.drand.sh", + "https://drand.cloudflare.com" + ], + "scheme": "bls-unchained-g1-rfc9380" + }, + "cipher": "chacha20poly1305-zlib6-nonce0", + "gate": { + "default": "withheld: a kept salt can be released later; an opened text can never be withdrawn", + "opened": "none", + "why": "no decision by Echo yet" + }, + "line": "numericopen: | numericwithheld:sha256(salt||v4 line)", + "reveal": "utc-day-after-close", + "salts": "key ring: one content a seal, one part a line", + "tool": { + "name": "tle", + "sha256": "69157d8c14a1d390d1b7744946288f83d4b10748471d5447f9abb77a5902c541", + "version": "tlock v1.2.0 -- github.com/drand/tlock" + } + } +} -- cgit v1.2.3