Protocol Engineering Feels Different
→ I have spent most of my time as a developer building applications: APIs, dashboards, full-stack products where the hardest questions are usually about features, UX, and shipping speed. When I started working on escrow for a decentralized marketplace during Summer of Bitcoin, the nature of the work changed immediately.
→ In application development, you can often patch around a wrong assumption. Add a migration, fix the bug, ship a hotfix. In protocol engineering, a wrong assumption does not fail at runtime in a friendly way. It fails as lost funds, broken invariants, or a design that cannot be extended without rewriting everything underneath it.
→ Summer of Bitcoin is a global open-source internship program that places developers into real Bitcoin and Lightning projects with stipends and mentors. My project is building a P2PK Cashu escrow system: a way for buyers and sellers to trade without either party having to trust the other, and without a platform holding their money in the middle.
→ On paper, the flow is almost insultingly simple. A buyer pays. Funds sit in escrow. The seller delivers. Funds release. That sentence is four lines long. Making it true in a system where nobody is the trusted third party is where months of work live.
Buyer pays
↓
Escrow
↓
Seller delivers
↓
Funds released
→ On Amazon, eBay, or any centralized marketplace, this flow is boring infrastructure. The platform holds the payment. The platform decides when to release it. The platform absorbs disputes. Trust is outsourced to a company with lawyers, bank accounts, and a terms-of-service page.
→ In a decentralized marketplace built on Nostr and Bitcoin, there is no company in the middle. Messages are signed with cryptographic keys. Payments are ecash proofs, not rows in a SQL database. The moment you remove the trusted party, escrow stops being a feature and becomes a protocol problem.
→ That is why this project became interesting to me. Not because escrow is novel, but because every comfortable shortcut disappears the moment trust cannot be assumed.
The Trust Problem
→ Traditional payment and marketplace systems solve trust by inserting themselves into the transaction. Amazon, eBay, Stripe, and PayPal all work the same way at a high level: one entity becomes the party everyone agrees to trust.
→ The buyer pays the platform, not really the seller. The seller ships because the platform will release payment if conditions are met. If something goes wrong, the platform adjudicates, refunds, or bans accounts. The marketplace controls money. That control is the product.
→ Decentralized systems invert that model on purpose. Nobody controls money in the sense a bank or platform does. Users hold keys. Users hold proofs. Messages propagate through relays without a central server deciding who is allowed to speak. The design goal is censorship resistance and user sovereignty, not convenience borrowed from Web2.
→ That inversion is why escrow is fundamentally harder here. You cannot call an internal API to freeze a wallet. You cannot update a balance column. You cannot email support to reverse a charge. Every guarantee the system offers must come from cryptography, protocol rules, and carefully separated responsibilities, not from an operator with admin access.
What makes decentralized systems difficult is not moving money. It is coordinating trust without introducing someone who must be trusted.
→ Escrow in this context is not a UI state machine with a "pending" badge. It is a question of who can move value, under what conditions, and with whose authorization. Get any of those wrong and you have not built escrow. You have built a complicated way to lose sats.
Understanding the Stack
→ Before writing escrow logic, I had to understand what each layer of the stack is responsible for. These systems are not interchangeable. Bitcoin does not replace Nostr. Cashu does not replace Bitcoin. Each layer solves a different problem, and escrow only works when you respect those boundaries.
Bitcoin
↓
Cashu
↓
Nostr
↓
Shopstr
↓
Escrow
Bitcoin: settlement layer
Bitcoin is where value ultimately settles. It provides scarcity, finality at the base layer, and the economic security that makes everything above it meaningful. Cashu mints peg ecash to Bitcoin; when users care about redeemability, they care about Bitcoin backing the system.
Cashu: ecash layer
Cashu is a Chaumian ecash protocol built on Bitcoin. It lets users hold and transfer value as cryptographic proofs instead of on-chain transactions for every payment. That makes small, fast payments practical. It also changes who "holds" money: not a bank balance, but proofs in a wallet.
Nostr: communication layer
Nostr is a simple, decentralized protocol for signed events. Listings, messages, and marketplace metadata can flow through relays without a central database. Nostr tells you who said what. It does not, by itself, tell you who is allowed to spend ecash.
Shopstr: marketplace layer
Shopstr is a censorship-resistant peer-to-peer marketplace built on Nostr with Bitcoin and Lightning payments. It composes identity, listings, messaging, and wallets into something that feels like a product. Escrow sits at the top of this stack as the mechanism that binds payment to delivery without a platform custodian.
→ The mistake I see often in conversation is treating this as one technology. It is four problems stacked: settlement, transfer, coordination, and product. Escrow touches all of them, but the guarantees at each layer are different.
What Is Cashu?
→ Cashu is built on ideas that predate Bitcoin apps by decades: Chaumian ecash. A mint issues blind signatures on tokens. Users can prove they own a certain amount without the mint necessarily knowing which token is which at every step, depending on configuration and usage. Privacy and portability are first-class goals, not afterthoughts.
→ In normal banking, the bank stores your balance. You log in and see a number. The bank can freeze your account, audit your history, and reverse transactions through internal policy.
→ In Cashu, the user stores cryptographic proofs. A proof is evidence that a mint owes you value. Spending means transforming proofs with the mint's cooperation according to protocol rules. Ownership is not "an account" but possession of valid secrets and signatures.
Bank stores balances
Trust the institution. Access through login. Policy governs disputes.
User stores cryptographic proofs
Trust the protocol and mint behavior. Possession governs spend authority.
Core concepts
- Mints: institutions or services that issue and redeem ecash against Bitcoin
- Proofs: the bearer instruments representing value inside the mint
- Ownership: whoever can present and transform proofs according to rules
- Privacy: amounts and flows can be protected differently than on-chain transparency
This difference matters for escrow because you cannot "lock funds in an account." You lock proofs under conditions encoded in the token itself. The wallet is not a database row. It is a bag of obligations and keys.
What Is P2PK?
→ Standard Cashu ecash behaves like a bearer instrument in the simplest mental model: whoever has the proof can spend it. Hand someone a banknote and they can spend it. Hand someone a valid proof and, in ordinary ecash, they can melt or swap it with the mint.
→ P2PK ecash (pay-to-public-key) changes that rule. Tokens are locked to a specific public key. Only the holder of the matching private key can redeem or transform them in the ways the protocol allows. Possession of the proof alone is not enough. You need proof plus the right key.
→ I am deliberately staying at the conceptual level here. The specification defines exact proof formats, signature schemes, and wallet behaviors. What matters for understanding escrow is the authorization model, not the line-by-line implementation.
Whoever has proof can spend it
Simple transfers. High risk if proofs leak. Escrow cannot rely on "we will both be honest" because honesty is not enforceable.
Only holder of matching private key can redeem it
Conditional release. A buyer can lock payment to a key controlled by escrow logic. The seller cannot unilaterally grab funds by intercepting a message.
→ Why does this matter for escrow? Because escrow is exactly a problem of conditional release. The buyer wants assurance that payment will not disappear unless delivery happens. The seller wants assurance that payment exists and will release when obligations are met. P2PK gives you a cryptographic hook: value moves only when the right key participates.
→ You still need application logic for timeouts, disputes, and user experience. But the floor of the system is protocol-level authorization, not a pinky promise enforced by a moderator.
→ NUT-11 is the Cashu specification that defines P2PK behavior. Reading it was not optional background reading. It was the difference between designing escrow and designing something that sounds like escrow but violates ownership semantics.
My First Wrong Assumption
→ Shopstr already uses Nostr keypairs for identity. Users sign events. Listings and messages are tied to npubs. When I started thinking about escrow, the path of least resistance in my head was obvious:
Nostr keys
↓
Escrow signing
→ It seemed reasonable. Identity already lives there. Users already understand keys in the Nostr sense. Why introduce another key hierarchy?
→ After reading specifications and working through how Cashu wallets actually authorize spends, that model broke. Escrow signing belongs to Cashu wallet keys, not Nostr identity keys:
Cashu wallet keys
↓
Escrow signing
→ The distinction is between identity and authorization. Nostr keys answer questions like "who published this listing" and "who sent this message." Cashu wallet keys answer "who is allowed to transform this proof." Those are related in a product sense but not interchangeable in a protocol sense.
→ Conflating them would mean escrow state tied to social identity in ways that do not survive wallet restores, device changes, or separation of concerns between chat and money. Worse, it invites models where proving you are a user on the marketplace is treated as proving you can move ecash, which is simply false.
Identity tells you who someone is in the network. Authorization tells you who can move value. Escrow lives entirely in the second category. Treating them as one problem is how systems lose money quietly.
Reading Before Coding
→ The most productive days on this project have not been the days with the most lines of code. They have been the days spent building an accurate mental model of what the protocol already guarantees and what it refuses to guarantee.
What that looked like in practice
- Studying Cashu specifications, especially NUT-11 for P2PK
- Tracing how earlier escrow experiments in the ecosystem approached locking and release
- Mapping reclaim paths and timeout behavior on paper before touching wallet code
- Asking what ownership means at the proof level, not the UI level
→ Several implementation ideas I had sketched on whiteboards disappeared after reading the spec carefully. Not because they were hard to code, but because they were impossible under the rules of the system.
→ Locktimesdo not behave like cron jobs in an application server. They are protocol constraints with specific meanings about when a proof can or cannot be transformed. Assuming "time passed, therefore automatic outcome" is an application mindset applied to a protocol surface.
→ Reclaim paths define who can recover value under which conditions. Designing escrow without understanding reclaim is how you accidentally build a system where the wrong party can pull funds back, or nobody can, or everybody can if they race.
→ Ownership semantics determine whether holding a proof, holding a key, or holding both is required at each stage. Escrow is a choreography of those states. If you do not know which stage you are in, you cannot reason about safety.
Reading specifications is not procrastination when the cost of being wrong is user funds. It is the cheapest insurance available.
Building Foundations Before Features
→ The temptation on any escrow project is to demo the happy path quickly: buyer pays, seller ships, money releases, screenshot for the blog. That path is a trap if the wallet layer underneath cannot support correct ownership over time.
→ Most of the early engineering went into foundations that users will never see directly. That is intentional. Protocol systems fail when visible features outrun invisible invariants.
→ Invisible infrastructure is boring until it prevents a catastrophic bug. In protocol work, boring is the goal.
One Rule Emerged Quickly
Wallet Creation
≠
Wallet Persistence
→ These two phrases sound like the same feature in a product meeting. In a protocol system they are different operations with different failure modes.
→ Wallet creation is generating a new identity: new keys, new namespace, new relationship to proofs. It should happen rarely and deliberately.
→ Wallet persistenceis saving and loading an existing identity without mutation. It should happen often and predictably. Every save operation must not accidentally create a new wallet because some code path conflated "ensure wallet exists" with "mint fresh keys."
→ Identity regeneration is dangerous in escrow contexts. If a wallet identity changes underneath locked proofs, the authorization story you thought you had may simply cease to exist. Users do not experience that as a bug. They experience it as stolen or vanished money.
→ Protocol systems need stable identities the way databases need stable primary keys. You can migrate data. You cannot casually rotate the primary key column because a save handler ran twice.
Things That Broke My Mental Model
→ Application developers build intuition from frameworks that hide complexity. Protocol work reverses that: the complexity is the product. These are a few assumptions that felt obvious and were wrong.
Lock expires → buyer automatically refunded
Expiration in the protocol sense constrains when certain transformations are valid. It does not automatically route value back to the buyer like a scheduled job reversing a Stripe charge. Someone with the right authorization still has to act, and the reclaim path must exist and be understood.
If the seller has the listing, they can claim payment
Listings are Nostr events. Payment is proof authorization. Being the seller in the UI does not mean you hold the keys that can redeem P2PK-locked ecash. Escrow must never confuse social roles with cryptographic roles.
Escrow state can live only in the UI
Screens lie. Refreshes lie. Tabs close. The source of truth is what proofs exist, what keys control them, and what the mint will accept. Application state must reconstruct from that, not the other way around.
One wallet per user is enough for every flow
Users may need separation between everyday spending wallets and escrow-specific identities. Combining everything into one key for convenience recreates the blast radius of a single compromised key.
→ I keep a running list of these now. Not as shame, but as documentation. Every wrong assumption is a test case waiting to be written.
What Has Been Hardest So Far
→ Surprisingly, the hardest part has not been writing code. TypeScript errors are loud. Failing tests are loud. Wrong protocol assumptions are quiet until they are catastrophic.
→ Uncertainty is constant. Multiple designs can be technically defensible. Choosing one means living with its constraints for every feature that comes later: disputes, partial releases, multi-hop trades, refunds.
→ Tradeoffs do not resolve cleanly. More flexibility for users often means more attack surface. Stricter protocol adherence often means worse UX unless you invest heavily in explanation and recovery flows.
→ Architecture decisions compound. Where you store wallet identities, how you separate creation from persistence, how you parse proofs at the boundary between mint and app: these choices look small on day three and enormous on day sixty.
→ Long-term thinking is exhausting because shipping culture rewards visible progress. Protocol engineering rewards correct invariants. Those timelines do not align naturally. Learning to trust the slower timeline is part of the job.
→ Protocol engineering feels different because responsibility does not sit with a platform operator. It sits with the design. There is no support team to call when the spec and the product disagree. You fix the design.
Lessons Learned
1Read specifications before you architect
NUT-11 and the surrounding Cashu documentation are not optional reading for P2PK escrow. They are the contract. Implementation ideas that sound clever in a diagram often die in a single paragraph of the spec. Reading first saves weeks of building the wrong thing confidently.
2Architecture matters earlier than it feels comfortable
In apps, you can refactor your way out of many mistakes. In protocol work, early boundaries harden into load-bearing walls. Separating identity from authorization, creation from persistence, and proof handling from UI state are not premature optimizations. They are the foundation.
3Backward compatibility is a feature
Users already have wallets. Proofs already exist in the wild. Any escrow rollout that breaks existing flows for convenience is not a feature launch. It is a migration incident. Treating compatibility as a safety requirement changes how you design migrations and defaults.
4Identity and authorization are different problems
Nostr answers who communicated. Cashu answers who can spend. Escrow requires both layers to cooperate but must never merge them. The lesson I expect to relearn in new forms throughout this project is simple: do not use the solution to one problem as the solution to another.
Where Things Stand Now
→ The project is past the pure research phase and into structured implementation, but it is not at a polished end-to-end escrow demo yet. That is intentional given how much invisible groundwork had to exist first.
→ The foundations are in place: proof handling at the boundary, wallet identity separation, persistence rules that treat creation and saving as distinct operations, and migration thinking for existing users.
→ The identity layer for Cashu wallets is established enough that escrow-specific flows can attach to stable keys instead of overloading Nostr identity.
→ Escrow architecture is taking shape as a set of clear responsibilities: what the protocol guarantees, what the wallet must enforce, and what the marketplace UI orchestrates. The gaps that remain are mostly in integration and validation, not in misunderstanding the problem.
What Comes Next
→ The roadmap from here is easier to describe at a high level than it is to execute. That is normal for protocol work.
1Escrow ownership integration
- Wire P2PK-locked proofs into the marketplace payment flow with correct key ownership
- Ensure escrow wallets are used for authorization, not user identity keys
- Define clear handoffs between buyer, seller, and locked proof state
2Claim flows
- Implement safe release when trade conditions are met
- Handle reclaim and timeout paths according to protocol semantics, not UI guesses
- Validate that only authorized keys can transform proofs at each step
3End-to-end validation
- Exercise full buyer-to-seller flows on test mints
- Stress edge cases around expiration, cancellation, and conflicting actions
- Document failure modes users might actually encounter
4Production hardening
- Review migration paths for existing wallet users
- Tighten error handling and recovery UX without weakening guarantees
- Treat security review as ongoing, not a final checkbox
Closing Thoughts
→ If there is one thing I want someone to take from this writeup, it is that protocol engineering is not about writing code first. It is about building accurate mental models of what the system already promises, what it cannot promise, and what your application is allowed to add without lying to users.
→ Escrow on a centralized platform is a workflow. Escrow on Bitcoin and Cashu is a composition of settlement, ecash authorization, decentralized messaging, and product logic. Skip any layer in your understanding and you will ship something that looks right in a demo and fails in production.
→ I came into this project wanting to build a feature. I am staying because it is teaching me how to think. How to read specs before sketching APIs. How to separate identity from authorization. How to treat wallet persistence as seriously as database transactions. How to notice when my intuition is imported from Web2 and does not transfer.
The most valuable thing gained so far hasn't been code. It has been learning how to think about systems where trust is not assumed.
→ If you are building on open protocols, whether or not you ever touch Shopstr, I hope the specifics here help. Read the NUTs. Draw the layers. List your assumptions and try to break them on paper before they break someone's balance. The work is slow. It is also the point.
