Sealed-sender capacity sharing: blind signatures, and being honest about what they don't hide.

I implemented Chaum's 1983 RSA blind signatures from scratch to let a trust group share Claude capacity unlinkably. The math is the easy part. The honest part — where the unlinkability stops — is the whole point.

One note up front: the code I'm describing lives in a private repo called mux, and it's currently shelved. There is no PR to click at the bottom of this post, no source link, no “star the repo.” This is a design note, not a release — written up instead of linked because that's the deal this site makes: the sharpest private work gets explained in prose rather than handed over as a tarball. If you only trust claims you can git clone, fair enough; close the tab. The rest is me showing my work on a cryptography problem I built, and on the one judgment call inside it that I care about more than the math.

The problem

Say you have a small trust group — a handful of people who pool a Claude subscription's capacity and lend it to each other when someone's own window is exhausted. The lender holds the live session; the borrower spends against it. Straightforward, except for one thing the group actually cares about: the lender should be able to verify that whoever just spent a token is a valid member of the group, and learn nothing about which member it was.

That's a real requirement, not a paranoid one. If the lender can see every borrow tied to a name, the lender becomes a usage panopticon over the people they're helping — who's working at 3am, who's burning capacity on what, who suddenly went quiet. The group wants the pooling without the surveillance; the lender wants assurance against freeloaders and outsiders. Those two wants look contradictory: verify membership, but don't identify the member.

They're not contradictory. This is exactly the problem David Chaum solved in 1983 with blind signatures, originally for untraceable digital cash. A borrow token is the digital coin; “valid group member” is the bank's signature; “which member” is the thing the bank must not learn.

How a blind signature actually works

If you already know this, skip to the implementation. The half-remembered version: the group admin holds an RSA keypair, and a member wants a token signed without the admin seeing its contents. The member picks a random blinding factor r, multiplies their message m by r^e (the admin's public exponent applied to r), and sends the product. The admin signs that blinded value with its private key — raising it to d — and hands it back. The member divides out r; because RSA is multiplicatively homomorphic, what's left is a clean signature on the original m that verifies under the admin's public key, even though the admin never saw m.

member:  blinded = (m · r^e) mod n          # admin can't read m
admin:   signed  = blinded^d mod n          # signs blindly
member:  sig     = signed · r⁻¹ mod n       # unblind; sig is valid on m

The admin saw m · r^e. The world later sees m and sig. Linking the two requires recovering r — a uniformly random value the admin never received. That's the unlinkability, and it's information-theoretic in the blinding step: the admin's view of the signing request is statistically independent of the token that later shows up. Hold that phrase; it does real work at the end.

Implementing it from the floor

I did not reach for a blind-signature library. Half the point was to build it on Node's own crypto so I understood every byte that moved — which meant doing three things by hand that higher-level APIs hide.

Raw RSA. Node does RSA only with padding bolted on (PKCS#1, OAEP), and padding destroys the multiplicative structure the scheme depends on. I needed literal m^e mod n and c^d mod n with nothing in between — RSA_NO_PADDING, the key as raw modular exponentiation. Raw RSA with no padding is a footgun in almost every other context; here it's the only thing that works, and knowing the difference is the job.

Full-domain hash. You can't sign m directly — signing a structured, attacker-chooseable value with textbook RSA invites existential forgery via the same homomorphism that makes blinding work. So I hash the token into the full domain of the modulus first, using MGF1 over SHA-256 to stretch a digest across the whole width of n. Now “multiply two valid tokens to forge a third” gets you nothing, because the product isn't the hash of anything.

Modular inverse. Unblinding needs r⁻¹ mod n, and there's no built-in for that on a BigInt — so it's the extended Euclidean algorithm, written out and normalized positive. A dozen lines, but an off-by-one in the sign and your unblind silently produces garbage that fails verification with no hint why.

The whole reference implementation is about 550 lines — the honest size of “a blind-signature scheme you can actually read.” Not 50: the corners libraries paper over are most of the work. Not 5,000: the cryptography is genuinely compact once you stop hiding from it.

Testing the things that bite

There are 85 assertions behind it, aimed at the failure modes that matter rather than at line coverage:

  • Tampered signature. Flip a bit of sig, flip a bit of the token it covers, swap a signature from one token onto another — every one has to fail verification. A blind signature that still verifies after you've edited the payload is worthless.
  • Double-spend. Unlinkability fights detection: if you can't see who spent a token, naively you can't see that a token was spent twice. The tests prove a presented token is checked against a spent-set so the same coin can't be redeemed twice, without that check leaking the spender's identity.
  • Two-member unlinkability. The one that demonstrates the property: run the full protocol for two distinct members, collect what the admin observed during signing, and assert the transcript cannot be matched back to which presented token belongs to which member. If that test could be made to pass by cheating, the scheme is theater. It can't.

I trust those 85 assertions more than a paragraph claiming the scheme is “secure,” because they're the adversary's moves written down and shown to fail.

Now the part that actually matters

Everything above is competent but not unusual — blind signatures are a 40-year-old primitive. Here's the part I'd want a reviewer to read twice, because it's where most “we built an anonymous X” projects quietly lie.

The upstream request still lands under the lender's account.

Think about what's physically happening. The borrower spends an unlinkable token, the lender's session relays the actual call to Anthropic, and that call is authenticated as the lender. From the vendor's side, every borrowed request is the lender's request — attributable to one real, named subscription. The blind signature does nothing to that. It was never trying to.

So the precise, bounded claim is: unlinkability holds strictly between members. Member A cannot tell that member B made a given borrow; the lender-as-admin cannot tell which member spent a given token. That is the entire protected surface — genuinely useful for the threat I described, and genuinely useless for a different threat people will be tempted to imagine it covers.

It is not anonymity from Anthropic. The request shape, the timing, the content, and the account are all right there under one name. If your threat model is “I need the vendor not to know this prompt came from me,” this is the wrong tool, full stop, and the design says so in those words. I'd rather lose the person who wanted vendor-anonymity at the door than have them deploy this and discover the gap after they've trusted it with something that mattered. Out loud, in the design, before anyone builds on it.

This is the line I think separates a security engineer from someone shipping the word “anonymous” as decoration. Cryptography gives you a specific guarantee against a specific adversary. The work is naming both, precisely, and refusing to let the strength of the math imply protection it doesn't provide.

Information-theoretic vs. computational, and why the gap is a feature

One more distinction, because it changes what a compromise looks like. The blinding is information-theoretically hiding. The signature's unforgeability rests on RSA — computational, breakable in principle by a large factoring effort or a stolen key. Conflating those two kinds of promise is how people overstate a system.

The payoff lands on what happens if the admin key is compromised. An attacker who steals it can mint valid tokens going forward — bad; the unforgeability assumption has fallen over and they can manufacture membership. But that same attacker, with that same key, cannot retroactively deanonymize past borrows. The link between a historical signing request and the token it became was destroyed by random r values that were never transmitted and don't exist anywhere to steal. There is no decryption key that turns the past back into identities, because the past was never encrypted — it was blinded, which is stronger. Compromise breaks the future of the system; it does not breach the privacy of its history. “If you get fully owned, your old data is still safe” is a meaningfully different posture from “if you get owned, everything ever is readable,” and here it comes for free from the choice of primitive.

The attestation alongside it

One smaller piece rounds out the design. Separate from who spent a token is whether the capacity being lent is what it claims to be — a live, legitimate subscription, served from the instance that says it holds it. So the lending instance produces a signed subscription attestation: an HMAC-bound claim tying “I hold a live session” to the specific dario instance making the assertion. I validated the HMAC against the RFC 4231 known-answer test vectors rather than assuming my construction was correct, and the claim carries a ±5-minute replay window so a captured attestation can't be replayed indefinitely. Modest, but it binds the compliance claim to a thing and a moment instead of letting it float free as an unverifiable assertion.

Why it's shelved, and why I wrote it up anyway

mux is private and parked. The problem is narrow, the audience that needs exactly this guarantee (and not the stronger one it deliberately doesn't offer) is small, and I'd rather it sit shelved than get picked up by someone who skims the word “unlinkable” and skips the threat model — the precise failure mode the whole design exists to prevent.

But the thinking inside it is worth keeping in daylight: a 40-year-old paper implemented from first principles, tested against the adversary's actual moves, and — the load-bearing piece — bounded by an honest statement of where the protection ends. That's what Own Your Stack looks like applied to cryptography. Not “we made it anonymous.” This adversary, this guarantee, this limit, and the discipline to say the limit before someone trusts you past it.

Knowing exactly when this is the wrong tool is the engineering. The math was always going to work.

We build and run software on AI infrastructure that shifts under it — agents, integrations, and the kind of cryptography that's only worth shipping if you're honest about what it doesn't defend. If you're putting real trust assumptions near anything that matters, that's the kind of thing we're good at getting right.

Start a conversation →
← All writing