The Core Idea: Rules Written Into Bitcoin
When you make a standard Bitcoin payment, you send BTC to an address, and the recipient can spend it with their private key. That is a simple rule: “this key can spend this output.” Bitcoin Script allows more complex rules. Writz uses a P2WSH - Pay to Witness Script Hash - which can encode multiple spending conditions. The address is a hash of a full script; to spend from it, you reveal the script and satisfy one of its conditions. Writz’s P2WSH script has exactly two conditions: Condition A (normal - loan repaid):The BTC can be spent only if both the user’s signature AND Writz’s protocol signature are present. This is the cooperative release - it happens automatically when a loan is repaid and the Stellar contract generates the co-signature. Condition B (emergency - timelock expiry):
After a defined time-lock (loan duration + a 7-day safety buffer), the user can spend the BTC alone - no Writz signature required. This protects users if the protocol becomes unavailable, goes offline, or ceases to exist. The critical property: neither Writz alone nor the user alone can move the BTC during the active loan period. Both signatures are required for Condition A. The time-lock enforces Condition B.
The Script
- Requires protocol signature (
OP_CHECKSIGVERIFY- fails without it) - Then requires user signature (
OP_CHECKSIG) - Both must be present. Either alone is insufficient.
- Requires the block height to exceed
<locktime>(OP_CHECKLOCKTIMEVERIFY) - Then requires only the user signature
- No protocol involvement needed after the time-lock expires
How the Address Is Derived
locktime is calculated as:
- Loan duration: ~12,960 blocks (90 days × 144 blocks/day)
- Safety buffer: ~1,008 blocks (7 days)
locktime= 900,000 + 12,960 + 1,008 = 913,968
Spending Path A: The Normal Release
When a user repays their USDC loan, the following happens:- The Stellar
writz-lendcontract records the full repayment, closes the position and emits arepay_fullevent. - The relayer’s repay watcher sees the event, builds the release PSBT (Partially Signed Bitcoin Transaction) paying the user’s own key, signs it with the protocol key, and publishes the half-signed PSBT on
writz-lendwithpublish_release_psbt. - The Writz UI fetches it with
get_release_psbtand the user’s Bitcoin wallet adds the user signature. - The PSBT is finalized and broadcast to the Bitcoin network.
- The BTC arrives in the user’s wallet after 1–6 confirmations.
A real Path A release transaction was broadcast and accepted by the Bitcoin Signet mempool:
11932100
- 89,631 satoshis locked; 88,131 satoshis released (Path A co-signed release)
- 149 vbytes at 10.1 sat/vbyte
- Both keys signed the PSBT independently (multi-party flow)
Spending Path B: The Emergency Release
If the time-lock has expired and a user wants to reclaim their BTC without a protocol co-signature:- The user assembles a transaction with
nLockTimeset tolocktime. - The input’s
nSequencemust be set to a value less than0xFFFFFFFF(Writz uses0xFFFFFFFE) - see the warning below. - The transaction is signed with the user’s key only.
- Once the Bitcoin block height exceeds
locktime, the transaction becomes valid. - The user broadcasts it directly to the Bitcoin network - no Writz involvement.
[WARNING]nSequenceinteraction:OP_CHECKLOCKTIMEVERIFYhas a non-obvious interaction withnSequence- if the spending input’snSequenceis0xFFFFFFFF(Bitcoin’s “final” value, which many wallets use by default), the Bitcoin Script interpreter causesCLTVto fail immediately, even if the timelock has genuinely expired. There is no clear error message - the transaction is simply rejected by the network, and it’s easy to mistakenly conclude the timelock hasn’t expired when it has. The Writz frontend and thebitcoin-scriptpackage’sbuildEmergencyTransaction/finalizePathBhelpers already set this correctly and hardcode it (it cannot be overridden by a caller). If you are constructing this recovery transaction manually with a third-party wallet or library instead of using Writz’s own tooling, you must setnSequenceyourself - see Manual Emergency Recovery for a safe, copy-pasteable reference implementation.
Security Properties
What Writz can do:- Co-sign releases (with user agreement) when loans are repaid
- Refuse to co-sign (keep BTC locked) if a loan is still outstanding
- Nothing else
- Move the BTC unilaterally (no user signature = no valid Condition A spend)
- Prevent the user from recovering BTC after the time-lock (Condition B requires no protocol involvement)
- Access the BTC if both the user key and protocol key are lost (standard key management risk)
- Active loans: Users wait for the time-lock expiry, then claim BTC via Condition B. They keep their USDC. The protocol absorbs the loss.
- No active loans: No locked BTC exists; nothing is at risk.
Protocol Co-signing Key Architecture
The protocol co-signing key is used only to co-sign release transactions for fully repaid loans. It can never move Bitcoin alone: every spending path of the script also requires the user’s key, and the user’s time-lock exit does not need it at all. Planned: the protocol side becomes 2-of-3 keys, held separately and written into the P2WSH script itself (see the script form below), so Bitcoin enforces the threshold natively and no single key can co-sign a release. This replaces earlier plans for threshold MPC signing. The signing policy, what the protocol signers can and cannot sign, will be published.Script Roadmap: P2WSH, not Taproot
Writz stays on P2WSH with an absolute block-height time-lock (OP_CHECKLOCKTIMEVERIFY) for mainnet v1. An earlier plan to move to Taproot (P2TR) with a MuSig2 key path is withdrawn, because:
- Every launch wallet can sign a P2WSH miniscript today, while Taproot script-path and MuSig2 signing are not broadly supported.
- The protocol side is planned as 2-of-3 keys. MuSig2 is n-of-n, so a 2-of-3 key path would need threshold Schnorr signing (FROST), which no launch wallet or custody option supports.
- The Soroban contract checks a P2WSH output with one SHA-256 comparison. A Taproot output check would need Schnorr or elliptic-curve arithmetic that Soroban does not provide as host functions.
Next: How SPV Verification Works →