Repository navigation
Source release. The tag is the source; the public tree at v1.3.0 passes the release gate (the four public protocol suites on a clean clone: 166 tests, pytest + cryptography + pqcrypto, no native wheel).
Attached: the two vendored eidolon_crypto 0.1.0 wheels (the compiled engine — Windows x64 and manylinux_2_34 x86_64) and SHA256SUMS. pip install eidolon_crypto-0.1.0-cp39-abi3-<platform>.whl after pip install -r requirements.txt. No desktop build in this release — the last desktop build is v1.1.1. The matching Cipher runtime (1.3.0, with Escrow Nexus and the genesis trust root compiled in) is published as cipher-runtime-20260916 on Oykdo/cipher.
Verify a download with sha256sum -c SHA256SUMS.
What changed since v1.2.0
Fixed
- Escrow Nexus hardening (
src/protocols/escrow_7d, audit of 2026-09-15). No change
to the v1 wire format — a frozen golden envelope (tests/vectors/escrow_7d_v1.json)
now pins it. Every escrow-level error derives fromEscrowError
(EscrowStoreError,FormatError,ConditionError,SealError,UnsealError;
bad argument types still raiseTypeError);retrieve_documentonly lets
KeyError/EscrowStoreError/UnsealErrorescape, andverify_integrity
reports an unreadable file or a malformed id as(False, reason)instead of
raising. A non-strlabel is refused at deposit (it used to seal an envelope
whose MAC could never verify again;Nonestill means no label). A corrupt,
truncated or newer-format.escrow7d— or a file whose innerescrow_iddoes not
match its file name — no longer crasheslist_escrowsand is no longer hidden
either:list_unreadable_escrows()reports it, the CLI list shows it, "Verify →
all" counts it as failed and Delete can remove it. Read-only calls no longer
create the vault's escrow directory (EscrowStorenow requires the 16-hex
depositor prefix the API always passed). The reader version is a constant of its
own (READER_VERSION) instead of being aliased to the schema version. CLI: an
interrupted prompt (Ctrl-C / EOF) now cancels — it used to be read as "accept the
default", which wrote the decrypted document to~/Downloads; Ctrl-C during an
action returns to the escrow menu instead of quitting the launcher; an invalid or
negative time-lock input is asked again instead of silently depositing without a
lock; retrieve asks before overwriting an existing file (exactOVERWRITE, as
Delete requires exactDELETE), acceptsQ, and defaults to the home directory
rather than the working directory when~/Downloadsis absent. Store: an orphan
<id>.escrow7d.tmpleft by a crash between write and rename is ignored by every
read path and removed by the next save once it is older than a minute (a write in
flight elsewhere is never touched). New public
tests:tests/test_escrow_7d_api_store.py(lifecycle on disk, isolation, corrupt
files, error contract, golden vector, RFC 5869 HKDF vectors, CLI safety). vault_migrationCLI no longer imports the launcher. Like the escrow CLI,
src/protocols/vault_migration/cli.pycarries its own console helpers (same
palette, colours only on a terminal and unlessNO_COLORis set): a public
protocol package imports nothing private, in a clone or in the full tree.
Changed
- The document escrow protocol is called Escrow Nexus. Display name only: the
package pathsrc/protocols/escrow_7d, the.escrow7dsuffix, the producer tag
and the v1 golden vector are unchanged, so existing envelopes open as before.
The launcher entry[X]reads "Escrow Nexus" and describes what the protocol
does (sealed, time-locked document envelopes) instead of "post-quantum". The
"7-day" wording of the 1.2.0 entry below was a misnomer: no seven-day semantic
exists in the protocol; a time lock is whatever date the depositor chooses. - Packaging: a wheel or sdist built from the full working tree now contains
only the public packages (src,src.daemon,src.protocols,config) —
the same set a clone yields.pyproject.tomlandMANIFEST.inused to list
src.identity,src.api,src.uiandsrc.utilsunder "public", so
python -m buildfrom the private tree packaged Tier 2 code (verified: 49
private files, plus two untracked config files, in a wheel built before the
change; 44 tracked public files after). Nothing installs that distribution today;
this closes the path before anything does. OwnerSignatureis now a real release condition (src/protocols/escrow_7d).
It accepts the vault's 64-hex id (vault_id_from_key(vault_key), or
OwnerSignature.for_vault_key(vault_key)) or the 16-hex
depositor_vault_id_prefixshown in listings — an envelope sealed with the
prefix used to be locked forever. The requester identity is derived from the
key that opens the envelope and can no longer be supplied throughcontext.
seal()refuses anOwnerSignaturenaming another vault (such an escrow could
never be opened, since only the depositing key verifies the MAC), and an
identifier that is neither form raisesConditionError. The check runs on the
serialised conditions — the bytes the MAC binds — and a prefix is stored as the
full id, so every envelope this version writes also opens with the previous
reader. Composite nesting is bounded (MAX_CONDITION_DEPTH= 32) at seal and at
unseal, so the verdict never depends on the reader's stack. Stored envelopes with
a full-idOwnerSignatureopen exactly as before.- Escrow Nexus said plainly. The package,
src/protocols/__init__.pyand the
threat model (docs/THREAT_MODEL.md§5.4) now state what Phase 1 is: symmetric
256-bit primitives (no post-quantum KEM or signature), a session key derived
by HKDF (not wrapped), time locks enforced by the key holder's own clock, and
cleartext metadata (label, conditions, deposit time, size, depositor prefix).
The wire format has a specification,docs/ESCROW_7D_FORMAT.md, and the v1
format is pinned bytests/vectors/escrow_7d_v1.json. The escrow CLI
no longer imports the launcher (a public package importing private code): it
carries its own console helpers, coloured only on a terminal and unless
NO_COLORis set. The store fsyncs before renaming, reports an envelope copied
from another vault's directory as unreadable instead of listing it, and the list
column readsnone(notowner) when an escrow has no condition. Packaging:
src.protocolsis now part of the wheel (pyproject.toml) and the sdist
(MANIFEST.in).
Removed
docs/CHANGELOG.md(pre-rename, January 2026) is no longer in the public
tree: it described modules and an escrow design that do not exist here and
contradicted this file.CHANGELOG.mdat the repository root is the only
changelog. Package metadata (pyproject.toml,setup.cfg,CONTRIBUTING.md)
now carries the project's name and public contact address.
Added
-
Escrow Nexus in the Cipher runtime (
cipher-runtime escrow …, runtime 1.3.0).
deposit,list,show,retrieve,verify,delete— one JSON line per
call, on the model ofsphere …. The vault key is derived from the.psnx
in-process; the document only ever travels as a file (--filein,--out
out), never inside the JSON; envelopes go to the same store as the launcher's
[X]menu, so both sides see the same escrows.deposittakes an optional
--release-after <ISO 8601>time lock and--owner-only;retrievenever
overwrites without--overwrite;deleterequires--confirm.error_code
tells the client what happened without parsing the message:not_found,
locked(withrelease_after),integrity,unreadable,exists,
invalid_input,confirmation_required. Public API:check_release(escrow_id, vault_key)answers "wouldretrieve_documentsucceed now?" — MAC and release
conditions — without decrypting, with the exact reasonretrieve_document
would raise;list/showreport it asreleasable/reason/
release_after. -
Genesis root 1 (
config/genesis/). The genesis ceremony was held offline on
2026-09-16:root.jsonis the signed genesis root (21 186 sphere mints,
distribution220f67fd…, Merkle root28f7b9e5…, issued
2026-09-16T03:25:14Z, SLH-DSA-SHA2-128s signature),issuer_public.jsonthe
issuer's public key6dad3cb1…3e31f8, andtrust.jsonnow pins the issuer and
the root's record hash89fb26b1…f0053enext to the anchor key. Verifiable with
the public verifier:GenesisRoot.from_dict(root).verify(issuer_pk). The anchor
serves this root; the first key window (vaults 1–500) is released. -
Connect escrow primitives (
src/api/server.py,src/identity/vault_identity.py).
What an application running a postal escrow on top of Eidolon (CardSwap)
was missing, app-authenticated (Connect secret + approved app) rather
than bound to a user JWT:POST /connect/vault/seal/openseal small
records (≤ 16 KiB) under a key derived per (app, subject) from a new
EIDOLON_SEAL_SECRET(generated and persisted like the JWT secret, never
the same value) — the app keeps the blob, Eidolon keeps the key; and
POST /connect/vault/economy/holds(+/{id}/release,/{id}/forfeit,
GET /{id}) lock EIDOLON on a vault as a behavioural bond: the amount
leaves the balance at hold time, comes back on release, and on forfeit is
credited to a counterparty vault (or burnt) and counted as spent.
Holds live inidentities/economic_holds.json, every move is in the
vault's operations trail (hold,hold_release,hold_forfeit,
hold_forfeit_credit), andGET /connect/vault/economy/{vault_id}now
reportseidolon_held. The legacy demo/vault/encryptstays
demo-gated. -
Eidos witness (
src/protocols/eidos_witness, Tier 1). A byte-exact port of
what the Eidos atelier's Witness page needs to judge without replaying:
Eidos's WOTS+ derivation (wots), XMSS validator signatures (xmss), the
signed head againstfederation.json(tete), UTXO root and inclusion
proofs (preuve),etat.jsonreading (etat), transaction core / witnesses /
base64 encapsulation and coin selection (envoi,coinselect), the
eidos.carnetexchange format written and re-read byte-identical to the
atelier (carnet), and theEIDOLON_EIDOS_ACTIFdossier — signed head +
output + proof — judged offline (actif). Format in
docs/EIDOS_WITNESS_FORMAT.md; vectors are Eidos's ownvecteurs.jsonplus
cross-implementation WOTS+ vectors againstsphere_ledger.hash_sig. -
Eidos coffre in the vault (Tier 2 client). A vault holds Eidos assets:
it keeps its coffre state in a sealed sidecar under
identities/vault_data/<prefix>/eidos/(no seed is ever written), syncs with
the published state, signs spends with write-before-return of burnt keys,
exports/importseidos.carnet, and files judged Eidos assets. -
Vault migration now carries
identities/vault_data/<prefix>/eidos/
(vault_state/vault_data/…in the archive). -
Sphere file: batched finality (
sphere_ledger, Tier 1). The
EIDOLON_SPHEREcontainer gains two optional fields,checkpointand
checkpoint_proof: the anchor's signed checkpoint and the sum-tree proof
that one of the file's heads is a leaf of it.verify_spheretreats them
like a receipt (same anchor-in-force rule, unknown anchor counts for
nothing, foreign anchor is an error) and reportsfinal_by
("receipt"/"checkpoint") andcheckpoint_seq;
verify_checkpoint_inclusionapplies the rule to a checkpoint obtained
separately. Spec:docs/SPHERE_LEDGER_FORMAT.md§4–§6. -
Sphere custody client in the Cipher runtime (
cipher-runtime sphere …,
runtime 1.2.0).list,claim,transfer,import,export,sync,
mailbox,verify,trust— one JSON line per call. A vault's one-time
keys are re-derived from its vault key (nothing to back up); a signed
transfer is written to disk before it is submitted and is never re-signed
for the same head; an imported file is verified offline against a trust
root compiled into the runtime (issuer key, anchor keys, pinned genesis
roots —config/genesis/trust.json) and then confronted with the anchor
(« local heads = anchor heads »); every sphere is shown as finale or
en attente. Sphere files live under
identities/vault_data/<prefix>/spheres/and travel with the vault
(vault_migration) or by export/import, never by directory copy. -
Anchor API:
GET /api/v1/sphere/owned(spheres held by the caller's
vault) andGET /api/v1/sphere/claim/instances(the caller's genesis slots
and their state);GET /{sphere_id}/filenow carries the latest checkpoint
and proof when they include the head. -
Sphere client:
burn,reissue-key,queue(runtime 1.2.1).
sphere burn --confirmends a sphere's custody chain (the file stays in
the inventory as its own tombstone, state brûlée; the anchor refuses any
record after it);sphere reissue-keyretires the key controlling a sphere
in favour of the next derived one without moving it (--forcerevokes a
signed, unsubmitted transfer by a second signature — the one deliberate
exception to « a key signs once », settled by the anchor);sphere queue
lists the caller's claims the anchor deferred for lack of a released key
window, andsyncnow reports them (queued) as well as spheres burnt
from another device (burned). All three follow the transfer rule: read
the head at the anchor, write the signed record before submitting it,
never sign twice for the same head. -
Sphere client: anchor error codes (runtime 1.2.1).
error_codenow
says where a failure comes from, so Cipher decides without parsing the
message:anchor_unreachable(no answer, 408, 429, 5xx — retry with
back-off),not_enrolled(the anchor does not know this vault: enrolment
is the lock server's, retry much later),anchor_refused(any other anchor
error — fork, unknown sphere, empty mailbox…);wallet_refusedis kept for
the client's own refusals (stale head, key already signed, foreign file).
WalletError.codecarries it in-process. The HTTP status stays in the
message, in parentheses, for older clients; a submission the anchor did
not answer on the merits (a halted anchor's 503, not only a cut) is now
reported as unreachable, the signed record waiting inpending/as before.