docs / why v2
docs/why v2

Why v2

A retrospective on the LOCK Protocol as it stood in 2024–2025, why the prior web implementation failed to ship, and the design principles that shaped OC Lock v2.

What v1 tried to do

LOCK v1.0 proposed Bitcoin-enforced access control using adaptor signatures. The decryption key k is cryptographically locked such that spending a specific Bitcoin UTXO reveals k on-chain. The recipient cannot decrypt without broadcasting a valid transaction.

LOCK v1.1 pivoted to Proof-of-Access (PoA): drop adaptor signatures, keep the chain as an oracle. A vault declares unlock rules ("a tx spending ≥ 10,000 sats from address X confirmed at or after block Y"); clients validate unlock attempts against those rules.

In both versions, the chain is load-bearing for every seal and every unseal.

Why v1.0 failed

No browser-compatible adaptor signature library

Adaptor signatures require elliptic-curve scalar arithmetic over secp256k1 with careful side-channel resistance. The production-quality implementations live in C (libsecp256k1 with the musig module) and Rust. There was no shippable WASM build in 2024–2025 that a web app could just import.

Attempts to port the math to pure JavaScript either never completed, or shipped a "simplified" version that embedded the secret directly in the vault file:

The current implementation has a fundamental misunderstanding of how adaptor signatures work in the LOCK protocol. The code implements a 'simplified' adaptor signature that just embeds the secret k directly in the template, which completely defeats the purpose of the protocol.

— internal postmortem, late 2025

This is not a bug. This is the cryptography being impossible to ship in the target environment.

PSBT loops are UX death

Even if the crypto worked, the recipient had to:

  1. Receive a PSBT from the sender.
  2. Import into a desktop wallet (Sparrow, Electrum, Coldcard).
  3. Sign it.
  4. Export the signed PSBT.
  5. Upload or broadcast manually.
  6. Wait 10+ minutes for confirmation.
  7. Have the web client scan the chain and extract k from the witness.
  8. Decrypt.

Eight steps. Three outside the browser. Success rate for non-technical users: zero.

Why v1.1 didn't fix it

PoA removed the WASM dependency. It did not remove the transaction loop.

  • Sender — must broadcast a binding transaction before the vault is valid. Users (reasonably) asked "why am I spending sats to encrypt a file?"
  • Recipient — must broadcast an unlock transaction with an exact satoshi amount to an authorized address. Fat-finger the amount → failure with cryptic error. Forget the fee → unconfirmed indefinitely.
  • Both — wait for confirmations.

From the v1.1 whitepaper's own product Q&A:

UX pain points for mainstream users:

  • Crafting on-chain transactions without exposing them to timing risks.
  • Understanding why an unlock failed (wrong amount, unconfirmed, fee too low).
  • Waiting for confirmations when block space is congested.
  • Handling multiple wallets or hardware signers in multi-device workflows.

These are not bugs. These are inherent to "Bitcoin transaction in the critical path." You cannot make a 10-minute wait feel fast.

The other cliffs

Beyond the two big failures, v1.x had smaller onboarding cliffs that compounded:

  1. Recipient must publish a BIP-322 signature before receiving anything. Pre-registration friction kills viral loops.
  2. SEAL and metadata are separate. Lost metadata = lost vault.
  3. Vault ids depend on binding txid. Any change = new id, broken references.
  4. No recovery. Lost private key = dead vault.
  5. Client-tracked unlock counters. Unenforceable across devices.

What we kept

  • Bitcoin addresses as identities. An address is a public commitment to a key. Right layer for identity.
  • ECDH metadata encryption with the recipient's long-term key. Elegant; no out-of-band key exchange. v2 keeps this, with X25519 device keys instead of raw Bitcoin pubkeys.
  • BIP-322 as the proof-of-control primitive. All major wallets support it. Verifiable offline.
  • Decoupling storage from access. A .lock envelope can live anywhere.

What we explicitly discarded

  • On-chain transactions in the critical path for sealing. Gone.
  • On-chain transactions in the critical path for unsealing (default case). Gone in identity mode; retained as opt-in for commerce.
  • Adaptor signatures. Gone. No WASM dependency.
  • PSBT round-trips. Gone. BIP-322 signMessage is the only wallet interaction.
  • Recipient pre-registration via one-off BIP-322 sig publication. Replaced by device-key records on Nostr.
  • Binding txid as vault id component. Gone. Envelope id is the SHA-256 of the canonical envelope.

The design principles that survived

  1. Bitcoin does one thing: proves control of an address. Everything else goes to a different layer — Nostr for discovery, the web app for UX, X25519 for crypto.
  2. One signing ceremony per device, forever. Everything after first-run is zero-click.
  3. Standard, audited crypto only. X25519, HKDF-SHA256, AES-256-GCM. All widely available. No hand-rolled primitives.
  4. Offline-verifiable. Given the envelope, the sender's address, and the recipient's device record, anyone can verify authenticity without a server.
  5. Escape hatches for commerce. Payment mode exists. It requires a trusted relay. We named it instead of pretending it doesn't.

What we still don't have

  • Trustless payment mode. Requires DLC oracles, BIP-118 (ANYPREVOUT), or a similar covenant. Not shippable today.
  • Full forward secrecy with zero server state. Double ratchet needs state sync. Probably not worth the UX cost for the primary use case.
  • Post-quantum readiness. Not attempted. A PQ variant would wrap content_key in Kyber-768.

Bottom line

v2 doesn't claim to be a clever protocol. It claims to be a shippable one. That's the whole point.