Cryptographic Foundations

This document collects the cryptography that not.bot™ depends on into one place: the BLS12-381 signature scheme and its aggregation property, multiparty computation at the level of what each protocol guarantees, key management on the user's device, revocation bitfields, faucet coins, and dummy transactions. It explains why each primitive was chosen and what property it provides. The protocol structures these primitives sit inside are covered in did:julia Technical Specification (Doc #15), and the shipped Chialisp puzzle set is documented in Chialisp Code Reference (Doc #19). This document does not repeat the protocol tables or code-level constants from those documents.


BLS12-381 and signature aggregation

did:julia uses BLS signatures on the BLS12-381 curve for every signature in the system: DID authentication, credential claims, message signing, and asset control. The choice of curve is a hard requirement for two reasons that point the same way: BLS12-381 is the native signature scheme of the Chia blockchain, and signature aggregation is the property the credential model depends on.

BLS signatures have a property no other scheme in common use provides at this level: any number of signatures, from any number of independent signers, combine into one fixed-size signature. The combination is non-interactive. The signers do not coordinate, do not exchange messages, and do not need to know about each other. A verifier checks one aggregate signature and learns that every underlying signature is valid.

A 96-byte BLS signature stays 96 bytes whether it represents one signer or fifty.

A complete did:julia signature runs larger than a bare BLS signature, because it is not a bare BLS signature. It is the signer's whole identity acting. The signed object carries the embedded DID, so a recipient reads the signer's identity from the signature with no PKI lookup, and it carries the structured contents the identity signed rather than the curve point alone. A raw BLS12-381 curve point is 96 bytes; a whole-identity signature runs to roughly 3.5 kilobytes uncompressed, near 2 kilobytes compressed. The protocol structure of the signed object is in did:julia Technical Specification (Doc #15).

The Chia substrate uses it natively

BLS12-381 is the signature scheme Chia uses for every coin. A coin spend is authorized by a BLS signature that the consensus rules check at low cost, so a did:julia identity, built from Chia coins, uses BLS for on-chain authorization. This reaches credentials, not only coin control: a credential claim presented on-chain is verified inside Chialisp, which has a native operator for BLS signatures. BLS was a key reason Julia Social selected Chia: the curve the identity system needs is the curve the chain already verifies. The next section gives the second reason, which holds even apart from on-chain cost.

Why claim composition needs it

The second reason, the one no alternative curve could meet, is credential-claim composition. A not.bot presentation can carry claims from many independent issuers at once: a not.bot credential from Julia Social, an age credential, a professional license from a licensing board, an employment credential from an employer. Each issuer signed its own claim with its own key, at its own time, with no knowledge of the others.

A normal presentation carries a few claims, so the saving in verification work and in signature size is slight. Aggregating those claims into one signature provides atomicity. The presenter's authentication signature and every claim's issuer signature combine into a single signature over the whole presentation. The verifier receives an all-or-nothing unit: it can confirm the bundle, but it cannot separate out one claim's signature and keep it. A verifier cannot disaggregate a presentation to lift a subset of claims and reuse them for another purpose, and a party that collects presentations cannot build a stockpile of reusable credentials. The one route that could undo this, a signature-subtraction attack, is closed by a required nonce, described under Nonce protection and signature subtraction below.

Composition is the requirement that singles out BLS. Coin ownership and multisig authorization also benefit from aggregation, but they could be served by other constructions. Claim composition across uncoordinated issuers cannot: the alternatives are either interactive, like Schnorr multisig, or never reach constant size, like the research-stage half-aggregation of Schnorr and EdDSA. The protocol-level treatment is in did:julia Technical Specification (Doc #15), and the public positioning argument is in Why Chia? (Doc #10).

The BLS-only constraint

did:julia supports BLS12-381 keys and no others. It does not support P-256, secp256k1, Ed25519, or passkey keys at the identity layer. None of those provides the constant-size non-interactive aggregation claim composition needs. Julia Social accepts the constraint because the capability it buys is structural to the rest of the system.


Key management

A not.bot identity is controlled by a BLS12-381 key (and derivations) held on the user's device. The not.bot app stores aliases, credentials, contacts, private keys, and the encrypted recovery bundle in an encrypted database. The database encryption key sits in the device's secure element: the iOS Secure Enclave or an equivalent Android TEE. The secure element is tamper-resistant hardware designed to keep a key from being extracted even when the operating system is compromised.

The BLS signing keys live inside the encrypted database, not inside the secure element. No shipping secure-enclave or TEE hardware supports the BLS12-381 key type that Chia uses, so the keys cannot be sealed in hardware the way an Ed25519 key could be. The secure element protects the database encryption key, which gives defense in depth, and the BLS keys remain accessible to the app software when the database is open. The app does not allow enrollment on an Android device that lacks hardware-backed key storage.

Every signing operation requires biometric authentication or the device passcode. A signature is produced when a human present at the device authorizes it, not when software decides to.

Key rotation, recovery, and pre-rotated keys are the mechanisms that protect against key loss and compromise; Recovery (Doc #6D) covers them from the user's side and did:julia Technical Specification (Doc #15) covers the on-chain mechanics.


Multiparty computation

Julia Social credentials must be computed by two or more parties together, in a way that produces a correct, signed result without any single party learning the inputs that went into it. not.bot uses multiparty computation (MPC) for this. An MPC is a protocol in which multiple parties compute a function together over their private inputs and learn only the output.

This holds for every credential Julia Social issues. Even the personal-information credentials taken from a passport, the user's name, gender, and nationality, are produced this way, so Julia Social signs them without seeing their values. The subsections below cover the cases where the computation does more than sign a value blind.

This document describes what each MPC accomplishes and what it guarantees. It does not describe the protocol internals, the circuits, or the constructions.

The not.bot ID

Every alias holds a not.bot credential proving it belongs to a verified human. The credential carries a not.bot ID, computed through an MPC between Julia Social and the user's app. Neither party can predict or steer the value. Julia Social receives the not.bot ID to construct and sign the credential, and the value cannot be connected back to the user's passport data. The not.bot ID later supports law enforcement identification when a valid demand exists; Law Enforcement and Accountability (Doc #9) describes that path.

Age credentials

Age credentials state a user's exact age, age-over thresholds such as over-18 and over-21, and age brackets. Julia Social must sign these without learning the user's age or birthdate. A three-party MPC among the not.bot app, Julia Social, and the Escrow Server (operated by Praxis, the independent escrow agent) produces age credentials with the right values, signed by Julia Social, without Julia Social or the Escrow Server learning the birthdate. As a user crosses into a new threshold or bracket, fresh credentials reflect the change without Julia Social or the Escrow Server learning that the crossing happened. Age credentials expire monthly and refresh through the same protocol.

Site Pass

A Site Pass is a pairwise identifier unique to one human-site combination. The same human at the same site produces the same Site Pass regardless of which alias is used, which gives a site sybil resistance: it can recognize one human behind two aliases without learning anything else. Site Passes for different sites are cryptographically unlinkable, even if those sites collude.

Site Pass computation runs an MPC with three participants: the user's app, the site's not.bot Verify instance, and Julia Social. Julia Social and the site never interact while executing the protocol, so Julia Social cannot learn which site a user is creating a Site Pass with.

DID-holder uniqueness for servers

A user's device proves it is the sole holder of its keys through the secure element. A server cannot rely on that hardware. not.bot Verify establishes single-holder control of its DID through an MPC that runs during each interaction. Possessing a copy of the key material is not enough: a second server instance running the same keys cannot stand in as the legitimate holder, because the protocol detects the conflict. This is the same process-binding guarantee that honest.bot™ provides for AI agents, where identity binds to a running process rather than to a key. honest.bot is a roadmap product (targeted for Q4 2026); the underlying MPC runs in production today inside not.bot Verify. honest.bot Verifiable Agent Identity (Doc #4) covers the agent model.

Entropy in credential data

Many credential values carry little information on their own. An over-18 credential is one of two values, true or false. Even a first or family name holds negligible entropy in cryptographic terms. A credential whose form depended on its value alone would be open to frequency analysis: an observer comparing credentials could infer the value, or match two credentials that carry the same value. Every credential incorporates entropy from a cryptographically secure source, so two credentials over the same value never share a form, and no frequency or entropy analysis can recover the value or link the credentials.

The architectural privacy claim across all of this holds: Julia Social cannot access user identity data. The MPC guarantees enforce it by construction, not by promise.


Revocation bitfields

An issuer needs to revoke individual credentials without contacting every verifier and without revealing which credential was revoked. did:julia records revocation in a Merkleized bitfield carried in the issuer key singleton. Each issued credential maps to a position in the field. A set bit marks a credential as revoked, and a verifier checks the bit during presentation.

The bitfield is Merkleized so an update touches one on-chain transaction and a verifier can prove the state of one position without holding the whole field. The issuer key holder can update revocation on its own, or the issuer DID can command an update. A coarser control exists alongside it: melting an issuer key invalidates every credential that key ever signed at once. The exact bitfield sizes and update mode numbers are code-level details and live only in Chialisp Code Reference (Doc #19); the credential-side treatment of cascade revocation is in Credentials, Presentations, and Selective Disclosure (Doc #6B).

The position an issuer assigns within the bitfield is a privacy property. Each credential claim takes one index in the issuer's on-chain field, and the field is public. An issuer that hands out indices in issuance order writes that order onto the chain: a reader who compares two credential claims learns which was issued first. An issuer assigns indices so they do not track issuance order, for example by drawing each index at random across the field, which keeps issuance order and volume off the chain. This is the mechanism. The issuer-facing rule for assigning indices is in Credentials, Presentations, and Selective Disclosure (Doc #6B).


Faucet coins

Creating and operating a Julia DID requires small amounts of XCH to fund coins and pay blockchain fees. A user never holds XCH. The faucet supplies the value through a construction that the user cannot divert and a tax authority cannot treat as the user receiving cryptocurrency.

Julia Social mints batches of faucet coins. To fund a user operation, Julia Social signs a faucet coin spend with a concurrent-spend assertion that references the user's coin, producing an incomplete spend bundle. The user's spend asserts back at the faucet coin. Neither spend can confirm without the other, so the faucet coin cannot be redirected to any other purpose. A RESERVE_FEE condition pins the value to the blockchain fee. The user never holds, receives, or accounts for XCH. The protocol-level description is in did:julia Technical Specification (Doc #15).


Dummy transactions

A public blockchain records the timing and structure of every transaction. Without protection, several alias DIDs rekeying in the same block would form a distinctive on-chain pattern that ties them to one person. Julia Social defeats that correlation with decoys.

Julia Social operates a service that submits faucet-funded spends performing DID creation, rekey, and recovery operations. The decoys match the structure of real user transactions, and an outside observer cannot tell a decoy from a real operation. The added noise frustrates timing analysis, clustering, and identity correlation. The formal observer-protection assertions are in Privacy Architecture (Doc #7).


Nonce protection and signature subtraction

Signature aggregation introduces an attack worth guarding against. An attacker who holds an aggregate signature and one of its component signatures could subtract the component out and recover a reusable signature over the remaining part of the message. This is a signature-subtraction attack, and it would let an attacker lift a single claim out of a presentation and replay it elsewhere.

did:julia prevents signature subtraction with nonces. Credential and message presentations include a nonce announcement that binds the aggregate signature to that specific presentation. A signature carrying a presentation-bound nonce cannot be disaggregated into a clean signature over a single claim, so a component cannot be extracted and reused. Receiving a holder presentation does not let the recipient re-present the underlying claim as their own. The protocol structure for nonces is in did:julia Technical Specification (Doc #15).


Self-certifying identifiers

A Julia DID is a cryptographic commitment to the key that created it. The prelauncher coin embeds the original BLS public key, so the launcher ID that serves as the DID identifier commits to that key. Because every state change is a recorded spend, a DID's full key history verifies from genesis.

This makes a DID self-certifying. A verifier can confirm, with no prior record of the DID and no blockchain access at presentation time, that the DID was created with a specific key, that the current key is the legitimate successor, and that the presenter controls it. The presenter reveals the original key and every spend that changed the DID, with one aggregated BLS signature covering them all. An offline verifier holding only a snapshot of an issuer's signing keys and revocation bitfields can then check a credential from a DID it has never seen. The protocol-level treatment of self-certifying lineage and offline verification is in did:julia Technical Specification (Doc #15).