Below is a description of a transactional payment processor in a form of a trusted server (Ecash) that does two things:
- It creates a promise that someone will be able to redeem X coins
- 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:
- the sender ends with 3 promises for the values of 8, 4 and 1 which sum to 13.
- 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.