[ D ] Build SDK · Early access

Developers.

SciPHR is a self-custodial signing layer you call. The wallet is generated and held on the user's device; your application asks the device for a signature, and the backend only prepares the unsigned transaction and broadcasts the signed result. The backend holds no key and cannot sign. Below: the primitives, the integration model, and the flows you'll wire against.

Status

The public SDK and CLI are in active development. The commands and signatures shown here describe the intended developer surface and may change before general availability. Want early access? [email protected]

[ 01 ]Where it fits

A signing layer in your stack.

Your app keeps its own UX and business logic. SciPHR slots in wherever you need a key operation, authentication, a transaction signature, a proof of identity, and the signature happens on the user's own device.

Your app

Requests an action on behalf of a user. Never holds, sees, or transmits a wallet secret.

SciPHR dKMS

The device generates keys, signs locally after validating the transaction, and returns a signed blob. The backend prepares and broadcasts; it never signs or decrypts.

XRP Ledger

Stores the xCIPHR NFT, the DID document, and the recovery signer list; validates and settles the signed transaction.

[ 02 ]Cryptographic primitives

The stack, in one table.

ConcernPrimitiveNotes
Wallet keysEd25519Master + regular keys, generated on device with xrpl.js. Stored in the iOS Keychain.
Key storageiOS KeychainWhenUnlockedThisDeviceOnly, non-synchronizable, biometric-gated (.biometryCurrentSetOrDevicePasscode).
Device gateP-256 · Secure EnclaveWraps/unlocks the keychain material and enforces user presence. Cannot hold the Ed25519 key.
Backup sealAES-256-GCM96-bit IV, 128-bit tag. Seals the master key under a random Wallet Backup Key (WBK).
WBK wrapsSE · iCloud · Argon2idThree independent wraps: Secure Enclave P-256, iCloud Keychain sync, recovery-code (Argon2id).
RecoverySetRegularKey · SignerListSetXRPL-native rotation and M-of-N quorum. SciPHR co-signer below quorum.
IdentityW3C DID · XLS-40did:xrpl:<net>:<addr>, on-chain.
AnchorXRPL NFT · Taxon 3URI sciphr:v5:<cid>#<sha256> → IPFS backup envelope.
[ 03 ]Quickstart

Mint an identity.

A planned developer experience for provisioning a new xCIPHR. Today this onboarding sequence runs inside the SciPHR mobile app; the wallet is generated on device, and the app orchestrates the master-signed onboarding sequence and the NFT mint. The CLI below previews that same flow.

terminal, provision · planned CLI

#scaffold a new identity on testnet

$npx create-xciphr-identity --network testnet

generating Ed25519 master + regular keys · on device… ok

sealing master · AES-256-GCM under WBK… ok

wrapping WBK · Enclave · iCloud · Argon2id… ok

onboarding · SetRegularKey → SignerListSet (master-signed)

pinning envelope · ipfs://QmXjr1iYoRb…

minting xCIPHR NFT · sciphr:v5:<cid>#<sha256> _

Why the order matters

At account creation, while the master key is still active, the device signs in sequence: fund the reserve, SetRegularKey, SignerListSet, mint the NFT, write the DID. The regular key and signer list must exist before any optional master-key disable, once the master is off, they can only be changed by quorum.

[ 04 ]The signing flow

Request a signature.

The backend prepares an unsigned transaction; the device validates every field, signs locally with the regular key, and returns a signed blob the backend broadcasts. Your app only ever handles the request and the signed result.

// 1, backend prepares an UNSIGNED transaction (no key involved)
const unsigned = await sciphr.prepare({ did, action: "send_xrp", to, amount });

// 2, the DEVICE re-validates every field, then signs locally (WYSIWYS)
assertOwnAccount(unsigned.Account);          // must be the user's own address
assertAllowed(unsigned.TransactionType);     // allowlist the UI actually rendered
assertNetwork(unsigned.NetworkID);           // no cross-network replay
const signed = await Wallet.signLocally(unsigned, { keyId: regularKeyId });

// 3, backend broadcasts the signed blob. It never held a key.
const result = await sciphr.broadcast(signed);
// → the most sensitive object you ever hold is an already-signed transaction.
sciphr-backend · endpoints

GET/api/did/resolve/:did, resolve the DID document

POST/api/v5/transactions/prepare, build an unsigned transaction

POST/api/v5/transactions/submit, submit a device-signed blob

POST/api/v5/recovery/rekey/prepare, prepare a quorum re-key

GET/api/v5/wallets/:walletAddress/envelope, fetch the encrypted backup envelope

Guarantee

There is no /sign endpoint and no /decrypt endpoint, by design. The backend cannot sign or open anything; the most sensitive object your code ever receives is a signed transaction, already safe to broadcast.

[ 05 ]The DID document

Multi-device by design.

A W3C-compliant DID document lists every device key authorized for an identity. Adding or revoking a device requires a valid on-chain signature, device keys are listed in it but can't modify it.

{
  "@context": ["https://www.w3.org/ns/did/v1"],
  "id": "did:xrpl:testnet:rKLo4T5Tp…",
  "verificationMethod": [
    { "id": "#wallet",   "type": "Ed25519VerificationKey2020", "publicKeyHex": "ED9434…", "blockchainAccountId": "xrpl:testnet:rKLo4T5Tp…" },
    { "id": "#device-1", "type": "JsonWebKey2020", "publicKeyJwk": { "kty":"EC", "crv":"P-256", "x":"…", "y":"…" } },
    { "id": "#device-2", "type": "JsonWebKey2020", "publicKeyJwk": { "kty":"EC", "crv":"P-256", "x":"…", "y":"…" } }
  ],
  "authentication": ["#device-1", "#device-2"]
}
Deep dive How a device is added on-chain

The new device signs in with Apple ID and generates its own keys on device. The user authorizes a DIDSet transaction that appends the new verificationMethod, signed locally with an existing authorized key. Because only a valid on-chain signature can change the DID, neither a stolen device nor a compromised Apple ID alone can enroll a new key. Revocation removes a method the same way. For catastrophic loss, the signer-list quorum re-keys the account instead.

[ 06 ]Backend invariants

What the backend must never do.

Self-custody has to be falsifiable by code audit. These are hard invariants, enforced in policy, code review, and tests. The backend must not, under any circumstance:

Forbidden

  • Generate a user wallet seed or key.
  • Receive, store, or log a plaintext seed or private key.
  • Possess any key capable of decrypting a backup envelope.
  • Sign or co-sign a user transaction with unilateral authority.
  • Expose any raw decrypt or "open envelope" endpoint.
  • Perform a "migration" that moves a seed server-side.
  • Hold any software-KMS or HSM key claimed to provide custody or recovery of user funds.
Permitted

  • Store and pin encrypted backup envelopes.
  • Prepare unsigned transactions for the device to sign.
  • Validate application policy and rate limits.
  • Record public DID and NFT metadata.
  • Broadcast device-signed transaction blobs.
  • Run a sub-quorum recovery co-signer that can never act alone.

Why the capability is deleted

There is no server-side signer or decrypt path anywhere in the codebase. Isolating or dead-coding such a path would still leave it in the codebase, and as long as that code exists, the honest answer to "can SciPHR sign for a user?" is "yes, that code is here." A non-custodial claim has to be provable by audit, so the capability is never built.

[ 07 ]Enterprise

Audit, compliance, recovery.

Audit trail

Every prepared transaction and broadcast is logged; the ledger keeps an immutable record of every settled signature. Built for review.

Compliance posture

Non-custodial under the applicable control test, AES-256-GCM + Ed25519, Argon2id (OWASP) for recovery codes, device-bound Keychain, and GDPR Article 32 measures.

Recovery

Two tiers: restore the master from the backup envelope (iCloud / recovery code), or re-key via an XRPL signer-list quorum, no SciPHR custody.

Role-based access

Grant, rotate, and revoke device access enforced on the ledger via the DID and signer list, without ever rehypothecating a key.