live · mainnetoc · docs
specs · api · guides
docs / quickstart

Quickstart

Three things to do — in order, once. After this, sending and receiving are one click.

1. Register your device

Visit /app in a browser, enter your Bitcoin address, pick your wallet (UniSat, Xverse, Leather — or paste a signature from any signmessage-capable wallet), and sign the binding statement. The browser stores a 32-byte X25519 secret in IndexedDB and publishes a public kind-30078 event to four Nostr relays.

You never do this step again for this address in this browser.

2. Send a message

On /app, click + new and paste the recipient's Bitcoin address. The thread opens. Type a message or attach files and press send. The app looks up the recipient on Nostr, verifies the BIP-322 binding on their device record, seals the envelope, publishes a gift-wrap so the recipient's inbox auto-delivers, and exposes a share URL / .lock / QR on the resulting bubble so you can also hand the envelope off out-of-band any time.

Every sent message is a sealed vault by definition — same crypto, same identity binding, same envelope structure. The URL encodes the full envelope in its fragment, so nothing touches a server.

https://oc-lock-web.vercel.app/unlock#eyJ2Ijoy…

Share the URL through any channel: Signal, email, paper QR code, carrier pigeon.

3. Receive a message

Open the share URL. The app:

  1. Recomputes the envelope id and verifies the sender's BIP-322 signature.
  2. Looks up your local device key by device_id.
  3. Derives shared = X25519(device_sk, eph_pk) from the recipient entry.
  4. Unwraps the content key under HKDF(shared, salt=nonce_ct).
  5. Decrypts the payload with AES-256-GCM.

Total on-screen time: < 3 seconds.

4. Hand off an envelope

Every sent bubble exposes three affordances: copy url, download .lock, show qr. Same envelope, three ways to deliver it out-of-band if the Nostr auto-delivery isn't convenient (counterpart isn't on Lock yet, in-person hand-off, air-gapped recipient). See chat transport for the full delivery model and security implications for the trust model.

Using the SDK

If you want to embed OC Lock in another app — Node, a browser extension, a Nostr client — the three packages are published from the oc-packages monorepo.

yarn add @orangecheck/lock-core @orangecheck/lock-crypto @orangecheck/lock-device

Seal:

import { seal } from '@orangecheck/lock-core/seal';
import { utf8Encode } from '@orangecheck/lock-crypto';

const envelope = await seal({
    payload: utf8Encode('hi bob'),
    sender: {
        address: 'bc1qalice…',
        signMessage: async (msg) => walletSignBIP322(msg),
    },
    recipients: [{ address: 'bc1qbob…', device_id: '…', device_pk: '…' }],
});

Unseal:

import { unseal } from '@orangecheck/lock-core/seal';

const result = await unseal({
    envelope,
    device: { device_id: '…', secretKey: localDeviceSecret },
    verifyBip322: async (msg, sig, addr) => myVerifier(msg, sig, addr),
});
const plaintext = new TextDecoder().decode(result.payload);

Device-key lifecycle:

import {
    buildBindingStatement,
    finalizeDeviceEvent,
    generateDeviceKey,
} from '@orangecheck/lock-device';

const kp = generateDeviceKey();
const statement = buildBindingStatement({
    address: btcAddress,
    device_pk: kp.device_pk,
    device_id: kp.device_id,
    created_at: kp.created_at,
});
const signature = await wallet.signBIP322(statement);
const event = finalizeDeviceEvent({
    deviceSk: kp.device_sk,
    address: btcAddress,
    device_id: kp.device_id,
    device_pk: kp.device_pk,
    bindingStatement: statement,
    bindingSigBase64: signature,
});
// publish `event` to Nostr relays of your choice

Layered with OrangeCheck

Need a sybil filter on your inbox? Gate unseal on an OrangeCheck check:

import { unseal } from '@orangecheck/lock-core/seal';
import { check } from '@orangecheck/sdk';

const ok = await check({
    addr: envelope.from.address,
    minSats: 100_000,
    minDays: 30,
});
if (!ok.ok) throw new Error('stake too low');

const out = await unseal({ envelope, device, verifyBip322 });

Next