Replace passcode identity with Ed25519 challenge-response. - Enrolment consumes a one-time bootstrap secret, after which no static secret is used on the wire again. - Nonces are single-use and consumed before the signature is judged, so a captured or bad signature cannot be retried. - Sessions are short-lived, scoped, and typed agent vs human_via_agent. - Human view access uses one-time codes with a 5 minute redemption window and a 1 hour session, deliberately different numbers: the code is a key, the session is the door. - Invariant: only an agent signature may act as an agent. Enforced and tested. - Append-only audit trail. 54 tests, covering refusals rather than only the happy path. Reference implementation, not reviewed or hardened. See README Status.
Sigil
Agent identity for the new internet.
An agent proves who it is by signing, not by holding a passcode. A passcode is a credential, so anyone who copies it becomes you. A signature is evidence, so forging one requires the private key — which never leaves the agent.
a passcode is the identity -> can be copied, leaked in a URL, replayed
a signature proves the identity -> cannot be forged, cannot be replayed
This is a working reference implementation with tests. It is not hardened for production — see Status below.
What it does
| Enrolment | Bind an Ed25519 public key to an agent, once, using a one-time secret |
| Challenge-response | Platform issues a nonce, agent signs it, platform verifies |
| Sessions | Short-lived, scoped, and typed as agent or human_via_agent |
| View codes | A human looks at what their agent sees, through a single-use code |
| Audit trail | Append-only record of every identity and access event |
Try it
export SIGIL_DB=/tmp/demo.db
python3 sigil.py seed bigpickle # -> one-time secret
python3 sigil.py keygen /tmp/demo.key # -> public_b64, fingerprint
python3 sigil.py enrol bigpickle "$PUB" "$SECRET" # bind key, consume secret
python3 sigil.py challenge bigpickle # -> nonce
python3 sigil.py sign /tmp/demo.key "$NONCE" # -> signature
python3 sigil.py verify "$NONCE" "$SIG" # -> agent session token
python3 sigil.py code bigpickle # hand a human a code
python3 sigil.py redeem X3ZB-NTLG # -> human_via_agent token
python3 sigil.py whoami "$TOKEN" # kind, scopes, may_act_as_agent
python3 sigil.py events bigpickle # audit trail
Tests
python3 -m unittest discover -s tests -v
54 tests. They cover the refusals, not just the happy paths: replayed signatures, wrong keys, expired nonces, single-use codes, scope escalation, and the invariant below.
The invariant
Only an agent signature may act as an agent. No human session, no lifetime, no admin override.
>>> sessions.assert_may_act_as_agent(view_session)
sigil.errors.AgentImpersonationDenied: human_via_agent session may not act as
'bigpickle': only an agent signature can
This is why human access is a window, not a costume. A human who redeems a
view code is logged as bigpickle's human, permanently distinguishable from
bigpickle. If that guarantee erodes, every conversation an agent has becomes
unreliable, because nothing distinguishes the agent from someone holding its
credentials.
Two properties worth stating out loud
Expiry and authority are separate axes. A long session is not a privileged session. Scopes come from the issuer and never widen with time. There is a test asserting a 24-hour human session has exactly the scopes of a 5-minute one.
Code lifetime ≠ session lifetime. A code is a key and expires in 5 minutes. The session it opens lasts an hour. The code travels in chat, so it is made worthless fast; the hour buys reading comfort and no extra capability.
Status
Reference implementation. Deliberately not yet:
- Reviewed by anyone. This is security-critical code. It needs an independent cryptographic review before it guards anything real.
- Hardened at rest. Keys and DB are stored unencrypted; the private key file
is
0600but that is all. - Rate limited.
challengeis unmetered, so brute-force and nonce-exhaustion are open. - Transport. No TLS concerns addressed; assumes a trusted channel.
- Agent revocation exists but has no expiry semantics.
The tests assert the logic is correct. They do not make it safe.
See also
DESIGN.md — the design page: rationale, flows, session stamps, the invariant
and its reasoning, and what is deferred.