oc · me
§ security · honest current state

What OC stores. What can move funds. What happens if OC disappears.

What OrangeCheck actually stores, what can move funds, and what happens if it disappears — each answered with file paths from the in-tree audit. Who holds the sats is derived on /custody.

§ what OC stores about you

every field, every store, every category.

The list below is what an auditor would find in OC’s production infrastructure today — Supabase rows on ochk.io, Upstash Redis indexes on me.ochk.io, OTS calendar entries (public), Nostr relays (public). None of it is key material that can sign from a user’s own Bitcoin wallet. The envelope store is not inert, though — an accrued balance is recomputed from those signed envelopes on every read, which is why who holds that balance is a separate question, derived on /custody.

fieldwhere it livesnotes
account rowoc-www · Supabase `accounts` tabledid_oc (opaque · sha256-derived random; same shape regardless of signup path), display_name, nostr_npub, created_at, last_signed_in_at. The underlying email or BTC address is in linked_identities, encrypted at rest (email) or stored plaintext (BTC, since BTC addresses are public on-chain). No password hash. No private key. No mnemonic.
session jtioc-www · Supabase `sessions` revocation listjust the JWT id, account id, issued_at, revoked_at. The session JWT itself lives only as a cookie on the user’s browser, signed by OC’s Ed25519 auth key.
envelopes (per billable event)oc-me-web · Upstash Redis (events-store)signed envelope: a routing id, project_key, user_address, fee breakdown, occurred_at — its content address is sha256 of the canonical bytes. Each envelope is signed at write time with the me.ochk.io envelope-signing key (Ed25519, public JWK at me.ochk.io/.well-known/oc-envelope-jwks.json) — distinct from the auth host JWT key. Indexed by both project_key AND user_address; see "the cross-site graph" section below.
rebind envelopes (per custody-state transition)oc-me-web · Upstash Redis (rebind-store)records every fedimint_threshold ⇄ fedimint_client ⇄ bip322 transition. Same content-addressed scheme + envelope-signing key as billable events. Surfaced on /verify/[id] and /me/identity (the user's own custody-state timeline).
projects, webhooks, sends, payments, attest tiers, notificationsoc-me-web · Upstash Redis (per-store)project_key + IntegratorPriceConfig + JWK URL pattern + domain verification state. Webhook endpoint URLs + rolling latency stats. Outflow ledger entries. All metadata; no signing capability.
OpenTimestamps proofspublic · OTS calendarspublic by design. Anyone can fetch + verify against Bitcoin headers without OC online.
Nostr-published eventspublic · 4 relays (relay.damus.io, nos.lol, relay.snort.social, relay.ochk.io)kind 30087. Independent of OC; relays continue to serve if OC vanishes.
OTP token (email-OTP signin · 10-min window only)oc-www · stateless JWT carried by the browserNOT stored server-side. {email, sha256(code), exp} signed with the OC auth key; the user holds it between /start and /verify.
§ what enables fund movement

nothing can move your own wallet. your accrued sats are a different question.

OrangeCheck holds your accrued sats during bring-up — it is operating every role until independent operators take them. Federation custody and graduation to your own keys are what replace it. That line is derived from the federation registry and the rail posture rather than written here, so the live answer is always the one on /custody.

§ for BIP-322 users

You hold your own Bitcoin wallet. OC sees only signatures and public addresses. No mnemonic, no private key, no recovery factor that can move your funds reaches OC infrastructure at any point. If OC vanished tomorrow, your wallet is unaffected.

§ for email-OTP users (current state)

Where email-OTP signup is headed: a federation-held wallet behind the identity, with the user holding its recovery secret. The browser-side client that would have provisioned one was removed with the guardians, so that is a design to rebuild, not a dormant feature waiting on a binding. The target is four guardian seats with distinct hardware-backed operator keys held by independent operators — operational independence enforced by operator-key diversity, so the “OC cannot move funds” invariant holds once the seats are key-diverse. During bootstrap OC operates the seats, so who currently holds the keys — and whether a federation is live at all — is derived and shown on /custody, never asserted here. Users can bind a Bitcoin address now, but binding is not settlement. The payout rail is still forming, so every settlement is refused; earnings accrue and stay claimable. On-chain payouts are deferred in the deployed payer, which settles to Lightning addresses only. Status: /custody.

Architectural note (per the in-tree audit, §2.4): no OC-operated process holds key material that could move a user’s own wallet. BIP-322 keys live in the user’s wallet, and the OC database is metadata only. What OC does hold during bring-up is the executor’s settlement signing key — the process that actually pays out — and the sats users have earned here, which are OrangeCheck’s liability until a federation takes that role over. Federation key shares live with the guardian seats, and during bootstrap OC operates those seats. Who holds a balance right now is derived on /custody.

§ graduation · the exit door

every sign-in is a bitcoin onboarding event.

The federation is the on-ramp, not the destination. “No custody” only holds in a world where users actually exercise the exit; if users accumulate sats and never graduate, OC is functionally custodial regardless of the threshold scheme. The exit door — federation custody to self-custody — is the product thesis, not a v2 feature.

§ fedimint_threshold

Federation custody on the OC-introduced federation. The target is 3-of-4 threshold guardians holding the key shares, with OC the company holding none — where email-OTP signups are headed, not where they are. Until a federation is bound and off bootstrap, OrangeCheck holds those sats. Whether one is bound, and who holds its keys, is derived on /custody.

§ fedimint_client

Federation custody on a federation the user picked themselves — same reputation graph, different guardian set, recorded as a rebind envelope. Reserved and not producible: the browser-side client this mode names was removed, so no account carries it and a bound federation would not make it reachable.

§ bip322

The user signs with their own Bitcoin key — that half is live, and OC never holds key material for it. Moving accrued sats into those keys is the other half. Graduation to your own keys is not live: no federation is bound yet. /me/graduate.

The user's opaque did:oc identity is stable across all three custody states · the underlying email or BTC may change (rotate, link more) but the public identifier stays the same. Stamps, attestations, agent delegations, votes, and bonded pledges all key on did:oc — the reputation graph travels with the identity on every rebind. Each transition is recorded as an anchored rebind envelope, viewable on /verify/<id> like any other envelope.

/me/graduate ↗
§ what happens if OC disappears

four scenarios, walked through honestly.

OC is acquired by a hostile owner
funds
BIP-322 users' own wallets: untouched — OC never held key material for them. Sats accrued HERE are a different matter and there is no federation standing between them and a new owner: OrangeCheck holds your accrued sats during bring-up — it is operating every role until independent operators take them. Federation custody and graduation to your own keys are what replace it. A hostile acquirer of OrangeCheck inherits that position, and graduation to your own keys is not live: no federation is bound yet. Once guardians are independent operators (vetted via /operator) the new owner cannot make threshold-signed withdrawals without corrupting enough of them to reach threshold, which the charter prohibits — that protection arrives with key-diversity, and /custody derives the current tier.
identity
New owner controls OC_AUTH_PRIVATE_JWK so could mint sessions. They cannot forge BIP-322 signatures from your wallet.
historical envelopes
All anchored envelopes verify offline forever. Independent of OC infra.
signing new envelopes
BIP-322 users sign as before — the new owner cannot forge those signatures. Email-OTP users hold no key here, so whoever runs OC signs on their behalf; a new owner inherits that. Handing the role to a federation is what ends it.
OC bankrupt; ceases operations
funds
BIP-322 users' own wallets are untouched — OC never held key material for them. Sats accrued here are the other axis: there is no stored balance, it is recomputed from signed envelopes, and it is a claim on whoever holds it. OrangeCheck holds your accrued sats during bring-up — it is operating every role until independent operators take them. Federation custody and graduation to your own keys are what replace it.
identity
Auth host goes dark. JWK published at ochk.io stops serving. Existing sessions valid until expiry.
historical envelopes
All anchored envelopes verify offline forever.
signing new envelopes
BIP-322 users sign anywhere — a Bitcoin wallet needs no OC infrastructure. Email-OTP sign-in goes through the OC auth host, so that path stops when OC does. Moving it to a federation is what fixes it.
OC's auth host key leaked (oc_session JWT signing key)
funds
An attacker minting sessions cannot sign from your Bitcoin wallet — no OC key reaches it. They can act as you inside me.ochk.io, including requesting a cash-out against your accrued balance. Mitigation: rotate the auth host JWK and revoke active sessions.
identity
Attacker can mint sessions for any account. Mitigation: rotate ochk.io/.well-known/jwks.json and revoke the compromised kid.
historical envelopes
Existing anchored envelopes are signed under the SEPARATE me.ochk.io envelope key, not the auth host key — they verify regardless. OTS-anchored history is intact under both keys' published JWKs.
signing new envelopes
Auth-key compromise is unrelated to envelope signing; new envelopes continue to sign under the me.ochk.io envelope key. Mitigation focus is on revoking active sessions and re-issuing the auth host JWK.
OC's me.ochk.io envelope-signing key leaked
funds
No fund movement is possible. The envelope key only signs records; it has no authority over user wallets, federation balances, or oc_session tokens.
identity
Unaffected.
historical envelopes
Past envelopes signed under the leaked kid stay verifiable; the published JWKS continues to expose the old kid for offline verification of the historical record. Trust in those signatures is degraded (an attacker holding the key could forge a same-kid envelope), but the ROW must still appear in the events-store / rebind-store / payments index to be discoverable — and OC controls those writes.
signing new envelopes
Mitigation: rotate OC_ENVELOPE_PRIVATE_HEX, deploy a new kid, JWKS publishes both for the overlap window. New envelopes sign under the new kid.
an OC-affiliated guardian's share seized by a regulator
funds
OC's architectural commitment is to NOT run a guardian in the target — independent operators via the program at /operator. During bootstrap OC operates the seats to bring a federation up, so this holds fully once the federation reaches key-diversity (the current tier is derived on /custody). If an independent guardian operator is later seized, the federation keeps signing as long as the remaining guardians reach threshold (e.g. 3-of-4).
identity
Unchanged.
historical envelopes
Unchanged.
signing new envelopes
Unchanged.
§ recovery

what happens if you forget something.

OC holds no key material that can sign from a BIP-322 user's own wallet, and no mnemonic for anyone — which is also why OC cannot recover an account for you. Recovery is the user's own wallet on the BIP-322 path. On the email-OTP path it is the email itself today; the federation operator's mediated re-bind is what replaces that.

forgets…BIP-322 pathemail-OTP path
their wallet (lost device, forgot seed)standard Bitcoin wallet recovery — your seed phrase, your problem. OC cannot recover for you. Sat balance is on chain or in the wallet’s mempool, recoverable only via the seed.You signed in with email, so you hold no key here. OrangeCheck signs on your behalf. There is no browser-held mnemonic to lose on this path, and none to recover with. A federation's mediated re-bind is what recovery becomes; whether a federation is bound — and who holds an accrued balance meanwhile — is derived and shown on /custody, not asserted here.
access to their emailno impact — BIP-322 users do not use email at all.email is the only credential on this path today, so losing it loses the sign-in — OC has no second way to verify you. Linking a Bitcoin address to the same did:oc removes that dependency. The federation operator's mediated flow is what replaces it; whether a federation is bound is derived from the registry (see /custody), not claimed here.
compromised emailno impact.attacker can sign in as the user with a fresh OTP. Mitigation: as soon as the user notices, rotate the recovery email + revoke active sessions via /me/settings. Envelopes already anchored on Bitcoin stay verifiable; new envelopes signed under the compromised period can be flagged via the standard session-revocation flow at the auth host.
§ the cross-site events graph

currently a policy commitment, not architectural enforcement.

The events-store on me.ochk.io keys envelopes by both project_key AND user_address (see oc-me-web/src/lib/developer/events-store.ts — the events:by_user:<addr>: index returns every event for a given user across every site that integrated). OC commits not to join those tables; that is a policy commitment, not a cryptographic one.

§ honest current state

A single Upstash SCAN over the by_user index returns every event for any user across all integrating sites. The OC billing engine requires this join to credit the right user wallet. Today, that graph is reachable in plain storage; the protection is policy + audit-log surveillance, not architecture.

§ planned v2 path

Per-site identity derivation: the user’s wallet derives a blinded identifier per integrator from a shared secret + project_key. OC sees (project_key, blinded_user_id) and cannot link blinded_user_id values across projects without the user’s secret. Aggregate billing computes via blinded coupons. Active research; not yet shipped.

§ published abuse limits

the rate caps are public so legitimate users can see they're below them.

Limits are intentionally generous for legitimate users and visibly tight enough that scripted sybil farming is unprofitable. They evolve with usage data; this page is the canonical source of truth.

limitvaluewhy
class A · per identity · per day5bounds new-account-bonus farming. legitimate users join a few sites a week, not five a day.
class A · per identity · per month30monthly cap on durable state-transitions per OC identity.
single-site contribution to a user's monthly earnings≤ 60%prevents a malicious site from inflating one user's stack to make a wash-trade flow look organic.
review threshold · class A · per month≥ $50flags an identity whose monthly class-A earnings exceed this threshold for human review. class B and C are self-bounded and never trigger.
drop window · max length92 daysthe platform ceiling on integrator drop schedules — no payout window may hold an earner's vested share longer, and an open window's boundary can only move earlier.

responsible disclosure

Security issues — wire-format bugs, signature-verification gaps, envelope confusion attacks, custody-related concerns — go to security@ochk.io. Triage in 48 hours. We coordinate disclosure with the affected protocol repos in github.com/orangecheck.

The internal NON_CUSTODIAL_AUDIT.md (in-tree at oc-me-web) walks the eight audit questions in §2 of the audit addendum with file paths. It is the source of truth this page renders for users. If you find something that page misses, the contact line above gets it triaged.