[ F ] Architecture · V5

IPFS & identity storage.

A SciPHR identity is bigger than what the XRP Ledger will hold inline. The encrypted backup of your master key and the DID document that describes your wallet and the devices bound to it are both larger than the ledger's 256-byte field cap. So the ledger keeps a small pointer, and the full object lives in content-addressed storage on IPFS. The pointer is a hash, so the off-chain object can always be checked against it.

Storage · IPFS / content-addressed Pointer · CID + hash DID anchor · ipfs://<cid> On-chain · ≤ 256 bytes

Summary

Two things are too big for the chain: the encrypted envelope and the DID document. Both are pinned to IPFS and anchored on-chain by a content address. For the DID, the clean pattern is a single field: DIDSet URI = ipfs://<did-document-cid>.

[ 01 ]What IPFS is

Content is addressed by its hash.

IPFS, the InterPlanetary File System, is a protocol for content-addressed storage. A normal web URL is a location, it names a server and a path, and trusts whoever runs that server to hand back the right bytes. IPFS instead names content by a CID (content identifier), which is derived from a cryptographic hash of the bytes themselves. The identifier comes directly from the content, not from where it is stored.

The address is a hash

A CID is computed from the content. Identical bytes always produce the identical CID; change one byte and the CID changes completely. There is no server name baked into it.

Tamper-evident by design

Because the CID is the hash, fetching content and re-hashing it proves you got exactly what was requested. A gateway cannot quietly substitute a different object, the address would no longer match.

Location-independent

Any node or gateway that holds the bytes can serve them, and you can verify the result regardless of who served it. Availability is a hosting problem; integrity is settled by the address.

content address

# the CID is derived from the bytes, the same content → the same CID, anywhere

>ipfs://bafkreiet7m…q5fy2a

resolves to the DID document · the CID is the hash of the document bytes

>sciphr:v5:bafybeih2…x9c3#<sha256>

resolves to the encrypted envelope · SHA-256 verified before restore

[ 02 ]Why SciPHR uses it

Why identity objects are stored off-chain.

The XRP Ledger is a settlement and identity layer, not a file store. Its transaction fields are small and metered, the URI fields SciPHR writes are capped at 256 bytes on the path we validate. An identity needs to publish objects that are far larger than that. Content addressing resolves the tension cleanly: put the tiny pointer on-chain where immutability and ordering matter, and put the full object off-chain where size is cheap, with the hash carrying integrity across the boundary.

On-chain · the pointer

Small, ordered, immutable

A CID is a few dozen bytes, it fits inside the 256-byte field with room to spare. The ledger gives it an immutable, timestamped, publicly auditable home, and orders it against every other change to the account.

HoldsCID (+ hash)
Size≤ 256 bytes
PropertyImmutable · ordered
Off-chain · the object

Large, cheap, verifiable

The encrypted envelope and the DID document live in IPFS, where size is not a constraint. The on-chain hash makes the off-chain copy tamper-evident, so nothing is trusted just because a gateway returned it.

HoldsFull object
SizeUnbounded
PropertyHash-verified

A note on custody

Content addressing does not weaken self-custody. The envelope in IPFS is AES-256-GCM sciphrtext that only your device, your iCloud, or your recovery code can open, and the DID document is public metadata by design, no secret key material is ever in it. IPFS is a storage and integrity layer, never a custody one.

[ 03 ]Two objects, two anchors

The envelope and the DID document.

Identity creation publishes two content-addressed objects and writes two on-chain anchors that point at them. They share the same shape, a small reference on-chain, the full object in IPFS, but they live on different ledger objects and use different URI schemes for good reasons.

DEVICE · xCIPHR Assembles two objects locally, before any write encrypted envelope DID document (JSON) IPFS · CONTENT-ADDRESSED Pin store bytes · hash them return a CID per object → envelope CID bafybeih2…x9c3 → DID-doc CID bafkreiet7m…q5fy2a XRP LEDGER · ANCHORS NFTokenMint · URI provenance of the envelope sciphr:v5:<cid>#<sha256> DIDSet · URI the wallet's DID document ipfs://<did-document-cid> pin CID

On-chain holds only pointers

  • The NFT carries a SciPHR-namespaced anchor with an explicit SHA-256, so the backend can recognize a V5 anchor and verify the envelope before restore.
  • The DID carries a standard ipfs:// URI, the CID is itself the hash of the document, so no separate digest is needed.
  • Neither anchor can move funds, decrypt the envelope, or alter the DID, only a valid on-chain signature can change the identity.
Identity creation, drawn · two objects pinned to IPFS, two anchors on the XRP Ledger

Envelope → NFT anchor

The encrypted master-key envelope is anchored on an XLS-20 NFT (taxon 3) as sciphr:v5:<cid>#<sha256>. The SciPHR-namespaced scheme lets the backend recognize a valid V5 anchor and reject legacy payloads, and the explicit hash is checked before any restore.

DID document → DID anchor

The DID document is anchored on the XLS-40 DID object as ipfs://<did-document-cid>. The standard ipfs:// scheme is the natural DID form, and because the CID is already the hash of the document, the address carries its own integrity check.

Why the two schemes differ

The envelope is opaque sciphrtext SciPHR must validate and gate on, so its anchor is namespaced and carries an explicit digest. The DID document is open, resolvable metadata, so it uses the plain ipfs:// form, whose CID is already the digest of the document.

[ 04 ]The DID document

The DID document is larger than a DID field.

A DID document is not one value, it is a small W3C record. For a SciPHR wallet it carries the wallet verification method (an Ed25519 public key) and the Secure Enclave device key (a P-256 key, plus any other enrolled devices), with the authentication and assertion relations between them. Recovery rules live in the account's on-chain signer list, and the envelope is anchored on the NFT, neither is embedded here. Even so, the document does not fit in the XRPL DID fields, which are capped at 256 bytes per field on the path we validate, so the on-chain DID has to point at it.

What the document carries

ContextW3C DID v1
Wallet methodEd25519 · publicKeyHex
Device keyP-256 · publicKeyJwk
Other devicesEnrolled vMethods
Relationsauth · assertion

Public by design. No seed, no private key, no decrypt capability is ever placed in the document.

// DID document, pinned to IPFS, anchored by DIDSet
{
  "@context": ["https://www.w3.org/ns/did/v1"],
  "id": "did:xrpl:testnet:rSciPHRwa11et…",
  "controller": "did:xrpl:testnet:rSciPHRwa11et…",
  "verificationMethod": [
    { "id": "#wallet",           // XRPL wallet
      "type": "Ed25519VerificationKey2020",
      "publicKeyHex": "ED…" },
    { "id": "#device-primary",   // Secure Enclave
      "type": "JsonWebKey2020",
      "publicKeyJwk": { "kty":"EC", "crv":"P-256", "x":"…", "y":"…" } }
  ],
  "authentication": ["#device-primary"],
  "assertionMethod": ["#wallet"]
}
// > too large for a 256-byte DID field
// > so: DIDSet URI = ipfs://<did-document-cid>

The clean pattern

Publish the DID document to IPFS, take its CID, and write one field on-chain: DIDSet URI = ipfs://<did-document-cid>. The DID resolves to a complete, tamper-evident document; the ledger holds a single 256-byte-safe pointer; and the word "CID" in the UI, the backend, and the security model now refers to something that is actually on the chain.

[ 05 ]The tradeoff

What this adds, and what it removes.

This pattern is not free. It adds exactly one dependency to onboarding, and in return it removes a structural mismatch in the current model. The cost is worth naming plainly.

The cost it adds

  • IPFS publishing must succeed before DIDSet, the CID has to exist before the anchor can reference it.
  • Onboarding now has an off-chain write in its critical path, so it needs retry, a health check, and a clear failure state.
  • The DID document's availability depends on the object staying pinned, a hosting responsibility, not a custody one.

The complexity it removes

  • No more UI, backend, and security model that all say "DID document CID" while the chain only holds a bare DID string.
  • The word CID finally refers to something that is actually anchored on-chain and resolvable.
  • Device keys and recovery metadata have one canonical, verifiable home instead of being implied by a string that points nowhere.
  • Resolving the DID and verifying the document become the same standard operation, no SciPHR-specific reconstruction.
ConcernBare DID stringipfs://<did-document-cid>
What's on-chainA DID identifier onlyA DID identifier and a resolvable, hashed pointer
Where the document livesImplied, not addressedIPFS, addressed by its own hash
Integrity of the documentUnverifiable from the chainCID is the hash, self-verifying on fetch
Onboarding dependencyNone addedIPFS publish gates DIDSet
Model coherence"CID" names a thing not on-chain"CID" names exactly what's anchored
[ 06 ]In identity creation

IPFS publishing during onboarding.

During onboarding the DID document is assembled from the wallet and device public keys and published to IPFS; only once its CID exists is DIDSet master-signed by the device with the resulting ipfs:// URI. Publishing is a prerequisite: if it fails, onboarding stops before the anchor is written, so there is no half-state where the chain references a CID that was never published.

  • Generate keys on deviceThe XRPL Ed25519 master and regular keys are created in the Keychain; the Secure Enclave P-256 device key is created in the chip. Nothing leaves the device.
  • Assemble the DID documentA W3C DID document is built from the wallet and device public keys, the Ed25519 wallet method, the P-256 Secure Enclave key, and their authentication and assertion relations. Public by design, no secret material.
  • Publish to IPFSThe document is pinned to content-addressed storage, which returns its CID. This write is on the critical path and is retried; a failure halts onboarding here.
  • Master-sign DIDSetThe device master-signs DIDSet with URI = ipfs://<did-document-cid>, alongside SetRegularKey, SignerListSet, and NFTokenMint.
  • Backend broadcasts, XRPL settlesThe backend relays the signed blob; the ledger validates and settles. The DID now resolves to a complete, verifiable document.
Deep dive What happens if the IPFS publish fails

The publish step is treated as a hard prerequisite, not a best-effort side write. Because the CID must exist before the anchor can name it, the onboarding state machine will not advance to DIDSet until a successful pin returns a CID.

On a transient failure, the backend retries against the SciPHR IPFS endpoint; the document is deterministic, so a retry re-pins the identical bytes to the identical CID with no risk of divergence.

On a hard failure, onboarding surfaces a clear error and stops before any on-chain write that would reference the document. Nothing is anchored, so there is no orphaned pointer to clean up, the user simply retries when storage is reachable. Routine signing, separately, never depends on the DID anchor at all, it uses the local regular key.

[ 07 ]Integrity & resolution

Verifying off-chain content.

The reason this is safe to do off-chain is that integrity travels with the address. Whoever serves the bytes, the SciPHR gateway at ipfs.sciphr.io or any public gateway, the resolved document is bound back to the on-chain DID before it is trusted.

  • DID document, resolved by ipfs://<cid>; the CID is derived from the document's bytes, and the backend rejects any resolved document whose id does not equal the DID being resolved.
  • Encrypted envelope, fetched by its CID and checked against the explicit #<sha256> in the NFT anchor before any restore, anchor finalization, or metadata display.
  • Content-addressed, because the CID is a hash of the bytes, any IPFS client that verifies the address detects substituted content; SciPHR pins and resolves through its own gateway at ipfs.sciphr.io.
  • No custody crosses the boundary, the envelope stays sciphrtext only its owner can open, and the DID document is public metadata, so off-chain storage never holds anything that can sign or decrypt.

Source of truth

The XRP Ledger records which document is yours, the CID it anchors. IPFS serves that document. Because the anchor is a hash, a mismatch between the two is always detectable, which is why a 256-byte on-chain reference is enough even though the document itself is unbounded.

Live example · XRPL testnet

A real xCIPHR identity. Every value below is the actual on-chain record or the pinned object it points to, you can resolve them yourself.

Walletr48ndeVbv1gQLid3DxuLrWmdxUFv8JFRe9
DIDdid:xrpl:testnet:r48ndeVbv1gQLid3DxuLrWmdxUFv8JFRe9
NFT · taxon 300000000EEE139E84FBBD51A4C10075980BBCC057E495CEFFAE36C710115DBD7
NFT URI anchorsciphr:v5:QmdUHjNKvZdgwY1dYaz3am5xt9yTb4HsFXDpcaHjFHBVYf#111e7f4dd78d2e31d944557b4e132e958bf96deab34d06452c373f1301a55629
DID URI anchoripfs://QmWCJuD1PLJYVrkQfuvwSHDehxs68vaKgmKt9MXc4uzjjs

The DID document's id equals the DID above, and the envelope is verified against the anchor's #<sha256> before any restore. The envelope link returns opaque sciphrtext, by design.