[Lightning-dev] Better privacy with SNARKs
aj at erisian.com.au
Tue Nov 17 21:14:36 UTC 2015
An obvious privacy limitation with lightning is that even with onion
routing, differents hops can be associated as being part of the same
transaction due to sharing a common R value. So if you see a HTLC from
Alice to Bob, paying $5 to Bob on receipt of R where SHA(R)=12345..;
and you see another HTLC from Carol to Dave, paying $4.95 to Bob on
receipt of R under the same condition, SHA(R)=12345..., then you know
it's part of the same transaction.
If you could change R at each step in the route, this would go away,
improving payment anonymity and making it harder to attack the system in
That's hard, because as a forwarding node, if you receive a HTLC payable
on R1, then to send a HTLC payable on R2, you need to be able to
calculate R1 from R2 or you'll be out of pocket. But you also can't be
able to calculate R1 *without* R2, or you could just rip off whoever's
making the payment. And, of course you have to know SHA(R2) to forward the
payment at all. And if you only know SHA(R1) and SHA(R2) it's hard to
say anything at all about R1 and R2 because cryptographic hash functions
are designed to make any structural relationships go away.
BUT! I think the magic of SNARKs  lets you do this!
With a SNARK, you can "prove" that you have some secrets (ie, R1 and R2)
that satisfy some programmable condition (ie, SHA(R1)=H1 and SHA(R2)=H2
and R1=R2 XOR X), based on public inputs (H1, H2 and X), without revealing
I think that's pretty safe, because if you receive an HTLC asking for a
preimage for H1, along with instructions in the onion saying ask Bob for
a preimage for H2, and here's X and a proof, then either:
- your forwarded HTLC will fail, and everything's fine
- you'll receive R2, calculate R1=R2 XOR X and see SHA(R1)=H1 as
expected, and everything's fine
- you'll receive R2, calculate R1=R2 XOR X and see SHA(R1) != H1,
which is only possible if the cryptography behind SNARKs are broken
- you'll receive RX, such that H2=SHA(RX) but RX being too
long or too short. If SNARKs aren't broken, this means that you know
R2alt and someone else knows R2 that are different but hash to the
same value, meaning SHA has been broken.
It seems like there are research-level tools out there that actually
make this practical to try out. I've had a go at implementing this using
snarkfront . Using it looks like:
1) initial setup of proof/verification keys
$ ./test_lightning -m keygen > keygen.txt # global setup
2) generate a proof, using a 32 byte secret, and XOR key (64 hex digits)
$ SECRET="the quick brown fox jumps lazily"
$ XOR=$(echo "she sells sea shells" | sha256sum | head -c64)
$ cat keygen.txt |
./test_lightning -m proof -s "$SECRET" -x "$XOR" > proof.txt
3) Verify the proof:
$ cat keygen.txt proof.txt |
./test_lightning -m verify -h "$F" -b "$B" -x "$XOR"
verify proof (6) (5) (4) (3) (2) (1)
proof is verified
4) Verify it doesn't report a valid proof with different inputs:
$ cat keygen.txt proof.txt |
./test_lightning -m verify -h "$B" -b "$F" -x "$XOR"
verify proof (6) (5) (4) (3) (2)
proof is rejected
* the proof/verification key data take up about 100MB -- in theory
one set of this data can be used by everyone; the only catch is
that everyone has to trust that nobody has kept the original random
numbers used to generate it.
* proof/verification key data takes about a minute to generate,
and about 650MB of RAM.
* the proof data itself (which would need to be sent to the node that's
going to switch R's) is just 864 bytes; so it'd use up about 5 hops
worth of onion routing at 192B per hop -- in a 4096 byte packet eg,
you could have four hops, changing R each time; or you could have 9
hops, changing R only three times.
* generating the proof data for a given R1,X pair takes about 10
seconds, and 260MB of RAM
* verifying the proof is quick-ish -- it takes 0.5s on my laptop,
and uses about 150MB of RAM.
For comparison, that last point makes a SNARK verification 500+ times
more expensive than an ECDH operation. If I got my maths right, you
can translate 3c for a linode CPU-hour into 2.5 satoshi for a linode
CPU-second (at $338/BTC), so you're probably looking at a minimum fee
of a few satoshi per SNARK verification, but that's still pretty okay
for transactions of 500 satoshi or more, ie anything more than about a
fifth of a US cent.
The 10s proof generation time is probably more of a limitation -- though
you could generate them in advance easily enough and just store them until
you need to use them, which would avoid lag being a problem at least. But
even then it's still essentially adding up to 30c of additional costs to
your transaction (ie 10s cpu time valued at up to 3c/s), which probably
isn't worthwhile for transactions smaller than a dollar or two.
A drawback is that you'd either (a) have to do all this on the merchant's
side (not just sending SHA(R) to whoever wants to pay you, but sending
SHA(R1), SHA(R2), SHA(R3), SHA(R4), X12, X23, X34, and three proofs,
which would be pretty painful; or (b) you'd have to generate all the
R secrets as a consumer, and you wouldn't get to use the fact that you
know R as evidence that you paid the merchant.
Anyway, it's obviously not ready for prime time today: SNARKs are still
pretty new as a concept; I'm definitely not familiar enough with SNARK
theory to be sure I'm not misusing the concept somehow; snarkfront may not
have implemented the theory fully correctly; and I might not have captured
everything I needed to in order for my "proof" to actually say what I
want it to. So not a great idea to use this to protect real money today.
But still, this seems like it's not all /that/ far from being practical,
and if the crypto's not fundamentally broken, seems like it goes a long
way to filling in the biggest privacy hole in lightning today ...
Code is at https://github.com/ajtowns/snarkfront/ or more directly at:
 This would also improve privacy/anonymity for other applications of
HTLCs, such as atomic swaps across chains:
1 bitcoin, payable on R1 + Alice's sig or timeout + Bob's sig
100 litecoin, payable on R2 + Robert's sig or timeout + Ally's sig
Alice and Bob communicate privately, agreeing to trade 1 BTC for 100
litecoin and revealing their aliases Robert and Ally; Alice generates
R1, R2, and reveals SHA(R1), SHA(R2), R1^R2 and the SNARK proof and
publishes the litecoin payment. Bob verifies the proof, and publishes
the bitcoin payment. Alice claims the bitcoin payment, revealing R1;
Bob calculates R2 and claims the litecoin payment. The swap can take
place trustlessly because Bob knows the only way Alice can claim his
bitcoin is by revealing enough info so he can claim the corresponding
litecoin. But there isn't any on-chain information linking the two
transactions, because R1 and R2 are independent (and could even be
using different hash functions as well as different preimages).
After the funds have been claimed, the private communication is
also completely deniable, since anyone could generate R1^R2 and a
corresponding SNARK proof just using the info on the blockchain.
More information about the Lightning-dev