Skip to content

Instantly share code, notes, and snippets.

Last active June 27, 2024 17:34
Show Gist options
  • Save phyro/935badc682057f418842c72961cf096c to your computer and use it in GitHub Desktop.
Save phyro/935badc682057f418842c72961cf096c to your computer and use it in GitHub Desktop.

Promise ledger


Below is a description of a transactional payment processor in a form of a trusted server (Ecash) that does two things:

  1. It creates a promise that someone will be able to redeem X coins
  2. Upon providing a proof of promise for X coins, the server verifies the proof and constructs a new promise for next coins

There are no accounts or balances. The server is also unable to link the proofs of promise with promises as this is achieved in a blinded way. It uses Blind DHE to create and verify a promise. A promise is a public key C' (returned by Alice) and the proof of promise is a pair C, secret_message.


Consider I want to pay 3 coins and I have a single proof of promise for 16 coins. The server creates new promises by splitting the total in 2 parts and then each expression as a sequence of promises based on power of 2s:

  1. the sender ends with 3 promises for the values of 8, 4 and 1 which sum to 13.
  2. the receiver ends with 2 promises with the values of 2 and 1.

Each promise has a form of (v, C') where v is the amount and C' is the blind exchange part as described in the Blind DHE gist (the one returned by Alice in the scheme). This means that the server would return 5 promises. The first promise of the sender would be (8, C1'), second (4, C2') and so on. To know with how many coins the proof of promise is associated with, the server keeps a different public key for every power of 2 e.g. public key A1 for 2^0, A2 for 2^1 etc. which are used accordingly for the blind DHE when creating promises.

The sender then shares two proofs of promise with the receiver [(2, C4, secret_msg4), (1, C5, secret_msg5)] where C4 is computed as defined in the gist linked above. The receiver then contacts the server with these, the server validates they were in fact issued and creates new promises for the receiver which are again of the same promise form [(2, C6'), (1, C7')] for which the receiver knows the C and secret_message values. The server has no ability to link the promises with proofs of promise except that they're grouped in the power of 2 anonymity sets. The server has to keep track of proofs that have been already used by holding onto secret_message values to avoid double spends. I think this the server to know they could not have issued more promises than they gave out (for every promise type) while also obtaining a relatively good unlinkability of the transaction graph. Promises for 2^4 coins are likely to have a much bigger anonymity set than promises for 2^20 which makes it similar to cash - noter for $100 are more rare than notes for $10.

Assuming this works, the construction scales linearly with the secret messages of the used proofs of promise.


The server has a single API point /split which takes a pair <amount, [ProofOfPromise]> and returns [Promise]. Since the calculation of the way new promises are split (the number of promises and their values) is deterministic, the sender can compute B' for all the newly returned promises and send them on the API call. The server responds with the set of C' values which act as new promises. The payment works as a single request->response. Note that the server also can't know whether a split call is just the sender splitting the funds or the receiver is claiming the ownership of new promises.


The issuance could be done with PoWs where pows themselves are treated as proof of promise and can be mapped to another promise.


The server could be thought of as only validating the equation Σoutputs - Σinputs = 0. It just so happens that the outputs are encoded differently which makes them very hard to link.

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