Suppose Alice wants to route a lightning payment to Carol, who has 5 payment channels with Bob, Zeke, Yan, Xander, and Wilma. Alice may start by getting an invoice from Carol. Before giving it, Carol should make up a random 32 byte secret, derive its pubkey on bitcoin’s elliptic curve by treating it as a private key, and sign 5 messages, one for each of her channel counterparties, telling each counterparty she will give the secret to that counterparty using a scheme to be described shortly, if he holds up his end of the “bargain” encoded in that scheme (again, to be described shortly). Carol should encrypt each message so that only that counterparty can read it, then encode all of the encrypted messages in the invoice she shares with Alice, telling Alice, at the same time, which message is readable by which channel counterparty.
Now it is Alice’s turn. She should find a route to Carol by asking her own peers if they can forward a payment of the desired size to Carol. Let us suppose that Alice also has a channel with Bob and that is the route she picks, and Bob agrees to forward the payment. So the payment will go Alice -> Bob -> Carol. Once this route is agreed upon by Alice and Bob, Bob should decrypt Carol’s message for him and then contact Carol to begin the scheme I spoke of before. The scheme works in the following manner:
Bob wishes to know the secret whose pubkey Carol shared with him in her encrypted message. Bob takes this pubkey and prepares to tweak it; to tweak it, he makes up a new private key, derives its pubkey, and uses elliptic curve addition to “add” his pubkey to the one from Carol. Due to the properties of elliptic curve tweaks, Bob knows that if he learns Carol’s secret, he can add it to the private key he made up, and the result will be the private key to the tweaked public key. This means he will be able to sign messages with that key if he learns Carol’s secret.
With this in mind, Bob should create a smart contract with Carol that has the following script:
[
<Bob can spend after 14 days>,
<Carol can spend immediately if she reveals the secret>
]
The second branch can be encoded using bitcoin’s “siglength” trick: put the pubkey derived from Carol’s secret in the second branch and call OP_CHECKSIG, so that Carol will have to provide a valid signature in order to spend the money, but before her pubkey, put the opcodes OP_SIZE 60 OP_LESSTHANOREQUAL OP_VERIFY. These opcodes will force Carol to use a “small signature” whose r_value is publicly known: 0200000000000000000000003b78ce563f89a0ed9414f5aa28ad0d96d6795f9c63 – the private key of which is 7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1. By sweeping the funds using this signature, Carol will necessarily reveal the secret to Bob, because Bob can compute the secret since he knows the r_value. Note: to prevent this branch from becoming anyone_can_spend the moment Carol tries to spend it, a second signature can be required in this branch, created using a pubkey whose private key only Carol knows.
In this situation, Bob knows that if he funds the address that encodes the above script, he will either learn the secret from Carol, or he will get his money back. He can now propagate this information backward along the route: tell Alice the tweaked pubkey and have Alice prepare to tweak it *again.* To tweak it, she makes up a new private key, derives its pubkey, and uses elliptic curve addition to “add” her pubkey to the one from Bob. Now Alice is in the same position Bob was in: she knows that if she learns Bob’s private key, she can add it to the private key she made up, and the result will be the private key to the tweaked public key. This means she will be able to sign messages with that key if she learns Bob’s private key.
With this in mind, Alice should create a smart contract with Bob that has the following script:
[
<Alice can spend after 15 days>, //note that 15 is greater than 14 – this is important for reasons to be described shortly
<Bob can spend immediately if he reveals the secret>
]
In this situation, Alice knows that if she funds the address that encodes the above script, she will either learn Bob’s private key or she will get her money back. If Alice was not the payment initiator but was just another routing node, she could also propagate this information backward along the route just as Bob propagated it to her, and, if necessary, the prior hop could do the same, and so on and so forth indefinitely til the payment reaches its initiator.
In a comment above I said I would explain why it is important that Alice’s locktime be greater than Bob’s. This is to prevent the following attack: suppose Alice and Carol collude to steal money from Bob. If Alice’s locktime was less than or equal to Bob’s, they could do so in the following manner: have Alice fund her contract and then have Bob fund his; now have Carol wait to resolve her payment; have Alice broadcast her refund transaction (using branch 1) the moment she can do so. If her locktime is less than Bob’s, she can take her money back and then Carol can settle her payment from Bob, meaning Bob lost money but did not get reimbursed. He was robbed.
If, however, Alice’s locktime is the same as Bob’s, things are slightly better, but still bad: Alice can broadcast her refund transaction at the same moment Carol settles her payment from Bob, but Bob can retaliate by immediately using Carol’s secret (which he grabs from the mempool) to counter-broadcast the transaction that settles his payment from Alice. Both transactions try to spend the same utxo at the same time, which means this is a race condition, and miners pick the winner. Thus, Bob can still lose money in this scenario.
If, however, Alice’s timelock is greater than Bob’s, the attack doesn’t work. Bob gets to broadcast his refund transaction a day before Alice gets to broadcast hers, so Bob gets his money back before Alice can take her money back. Which means Carol cannot settle her payment from Bob, because the utxo she would have spent to do so has already been moved by Bob when he broadcasted his refund transaction. So he cannot lose any money, and thus cannot be robbed.
There is a significant privacy advantage to using PTLCs (Point Timelock Contracts) as described above instead of the HTLCs we currently use in the lightning network. The advantage is this: when HTLCs are used in a route with more than 3 hops, if one of the “hop” transactions ends up on the blockchain, anyone before or after that point in the route can observe the payment hash associated with that payment and conclude that it must have been in a route that they were part of too. This is none of their business; they now know that someone along the route dropped the payment onto the blockchain, and by looking at the timelock, they can tell if it was before or after them in the route. If the timelock is smaller than theirs, they know the route continued after the person they forwarded the money to (i.e. they know that person was not the recipient), which is none of their business, and if the timelock is greater than theirs, they know the payment was not sent by whoever immediately preceded them in the route, it was sent by someone else (which is information they ought not to have, because again, it is none of their business).
By using PTLCs, they get to see a secret divulged, but there is no way for them to associate that secret with the route they are part of. This is a privacy advantage: even if someone before or later on the route drops a “hop” transaction onto the chain, other hops on the route do not know anything bad happened.
There is a problem with this scheme as described so far. It is inferior to HTLCs in one important respect, namely this: in an htlc, the preimage received by the sender constitutes a proof of payment; they can cryptographically prove that it corresponds to the hash included in a bolt11 lightning invoice. But the private key Bob sends to Alice in this scheme does not serve as a proof of payment. I can see how Alice could cryptographically prove she paid Bob (if Bob signed a message saying he will only divulge that secret to Alice if she pays him), but I do not see how that proves she paid Carol. I do not currently know how to fix this problem and I solicit guidance from thinkers who may think more clearly than me.
After a few days of thinking, I believe I have a solution to the above-mentioned problem. When Alice gets an invoice from Carol, have Carol make up a keypair whose private key only Carol knows, and sign a message (which she gives to Alice) saying that if Alice learns this secret, it proves the payment, because Carol will only disclose the secret if she gets paid. Then, when Alice makes her swap contract with Bob, have him prove that he can only sweep the money by disclosing the secret; he can do this by subtracting the secret's pubkey from the tweaked pubkey, thus yielding the "secret's pubkey" and the "remainder pubkey," and have him share the private key of the "remainder" pubkey, thus disclosing that to Alice.
Again, using the siglength trick, force him to reveal the "full" private key when he so spends the money, and once he does so, Alice can run the logic in reverse: subtract the secret Bob told her from the tweaked secret, and that will reveal the secret that was used to tweak the key. If every hop along the route does this, every party must learn this tweak data and reveal it in order to sweep the funds from their swap contract, and that means Alice will eventually learn it too, or will not pay any money. If she does not learn the secret, no problem, that just means the payment failed. If she does learn it, great! She now has her proof of payment.
A downside of this solution is, I think it requires three pubkeys to sign the transaction at each hop: one signature will reveal the "custom" tweak, one will reveal the "proof of payment" secret, and a third belongs to the person who's supposed to sweep the money in the "not timelocked" path, to prevent race conditions. This makes the protocol even less efficient, but makes it work and have a cryptographic way to prove you paid someone.