Skip to content

Instantly share code, notes, and snippets.

@phyro
Last active June 7, 2022 20:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save phyro/1046022377fcb1886a1b4f6500f23773 to your computer and use it in GitHub Desktop.
Save phyro/1046022377fcb1886a1b4f6500f23773 to your computer and use it in GitHub Desktop.

Contract wall

Suppose party A wants to share some data with party B. We want to do that in a way to prevent spam without giving much information about the data. The main idea is to have a centralized service which produces blind signatures for outputs in the utxo set. These signatures are used to store data on the service (e.g. partially completed contracts). This makes sure that the service can't reliably link the output with a contract while also preventing spam attacks.

Suppose we have a central service S that provides three endpoints:

  1. /claim - claims storage coupons for an output that is in the UTXO set. Returns 2 blind signatures
  2. /store - receives an unused unblinded signature and stores data with a certain ttl (e.g. 3 days)
  3. /list - lists all the public data

Claim

Claim is an operation that receives an output from the UTXO set with ComSignature and, if it has not produced blind signatures for that output commitment yet, it returns two blind signatures (similar like it's done in Chaumian ecash). The service keeps track of the commitments(utxos) it produced the blind signatures for. Let's call these blind signatures s1 and s2.

Store

Store is an operation that receives a single unblinded signature and, if that signature has not been used yet, the service stores the data and adds the signature to the list of used signatures. Stored data is expired after 3 days.

List

This operation returns a list of all current public data.

Usage

Suppose Alice has an output O1 on the chain. She calls claim for O1 to obtain two blind signatures (s1, s2) from the service. She now wants to send some coins to Bob and prepares the encrypted slate for Bob. She calls store with s1 to store the signature on the service. Since Bob needs to store the signed contract, she encrypts the remaining blind signature s2 for Bob so he's able to use it to store the signed contract. Note that after Bob calls store we have two contracts (step1 and step2) which are just two separate store calls.

The communication with the service would need to be done through some anonymous channel to avoid learning the IPs.

Noise creation

Since we're using payjoin and sometimes contributing multiple inputs in a transaction, we could be sending encrypted dummy data to the service just to produce noise in the list of contracts.

Properties

  1. The service can't provably link a contract to an output on the chain, it can only do heuristic based on timing correlations. In theory, the contract could be done by any output that redeemed the blind signatures.
  2. A spam attacker could store 2 contracts per on-chain output. This makes it free for the normal users to store, but if someone wanted to perform a spam attack with N contracts, they'd need to have claimed N/2 outputs which means they'd have to pay onchain fees for these. Even if the attacker had money to do that, this means the attacker must be paying fees on the chain for these outputs (or has done so in the past), so they're providing security to the network. We won't complain.

Open questions

When the receiver starts the contract, they may not have a blind signature available. What to do here?

Not sure yet. Perhaps the sender could share the blind signature with the receiver if the receiver didn't have any on-chain output.

The list of spent signature grows forever. Can we improve this?

To solve the ever growing nullifier set, we simply add another endpoint that communicates the current public key for service S. The service also communicates the next public key the service will use. By swapping the keys, the service can forget old blind signatures.

Should we give some more blind signatures in case they have to redo the dance?

The service could return 4 blind signatures to account for re-spend transactions.

Could we make contracts indistinguishable?

Yes. One way would be to do what is described here. Note that the dummy field doesn't need to be a part of the slatepack. We could in this case simply provide a layer that adds this dummy data and encrypt that. The data is encrypted so we can augment the structure all we want.

@VzxPLnHqr
Copy link

I am trying to digest what you are proposing. Being unfamiliar with the intricacies of coinjoin, can you give a little bit more of a lead-in, some motivation, as to what this proposed service solves? If we set the technical details aside, what I gather from the above is that this centralized service can function as a minimally-trusted, even possibly untrusted, coordinator for "contracts," and the contracts you are most considering the service useful for, at least initially, is coinjoin. Do I have that mostly correct?

@phyro
Copy link
Author

phyro commented Jun 7, 2022

Thanks for reading @VzxPLnHqr . It's not really about coinjoins, it's general data sharing with a very specific application in this case. This gist was created to attempt to solve a very specific problem in Grin. Transactions in Grin are interactive as they require sharing some information between the sender and the receiver to even create a transaction. I was trying to come up with an idea of where the two parties can drop this data (partial transaction information) without them having to communicate directly with one another. So you want an intermediary where they store encrypted data and this has to come in a form of a service. This problem can be generalized to "how to provide chain users with a place to store data". The main idea is the following: given an unspent output on chain, a user can provide a proof of output ownership (a signature) and thus receive 10 credits for storage. Each credit can be used to store 10kb of data for a certain time e.g. 3 days so the user can spend all 10 credits at once or use these separately. You never provide a blind signature for an output you've already done that. This way you prevent spamming because onchain outputs come with a cost due to taking bytes on the chain while also providing chain users a free off-chain storage service. The blind signatures part is just to add untraceability and prevent the service from being able to link an output with the data stored on the service.
In theory, this could be used for many more things. I like the term "family of provable costliness" that Somsen used, this is just another way of achieving this from what I can tell. You could do a lot of things from that e.g. issue credits for a free haircut to incentivize people using Bitcoin or use these ownership proofs to somehow issue a new token (which I don't recommend).

@VzxPLnHqr
Copy link

@phyro I see. Thank you for the additional clarity. One other question pertains to the economics: given that a confirmed output can be signed and provided to the service in exchange for storage credits, why do you fix the storage credits at a constant? An output which contains more of the underlying currency (bitcoin, or, in your example, grin), should be issued more storage credits, no? Or are you saying that the fixed ratio of 1 output to N storage credits (10 in your example) is necessary to maximize the privacy properties?

@phyro
Copy link
Author

phyro commented Jun 7, 2022

@VzxPLnHqr

why do you fix the storage credits at a constant? An output which contains more of the underlying currency (bitcoin, or, in your example, grin), should be issued more storage credits, no?

In my specific case that I was solving for, the answer to this is no as you don't really care (or know) the amount an output holds - Grin has blinded amounts with Confidential transactions. On Bitcoin, this could be taken into account, but you have to be aware that this introduces a bit different dynamics because you don't have exact 1:1 correlation with what was paid for that output e.g. for a fee of 1 output, you can get 10 credits or 10000000 credits, depending on the amount. This means that a person with a large bag of bitcoins could store more bytes and you could create a spam vector if wealth distribution was very bad. In the end it really depends on what you're trying to solve, but yes, in theory you could issue credits based on the amount an output holds.

@VzxPLnHqr
Copy link

Got it. Thanks. I had forgotten that Grin does not expose amounts. That makes sense. In any event, this sort of a "service" seems like something which could be offered by a nostr relay which make itself available over various networks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment