Technical: Upcoming Improvements to Lightning Network
Price? Who gives a shit about price when Lightning Network development is a lot more interesting????? One thing about LN is that because there's no need for consensus before implementing things, figuring out the status of things is quite a bit more difficult than on Bitcoin. In one hand it lets larger groups of people work on improving LN faster without having to coordinate so much. On the other hand it leads to some fragmentation of the LN space, with compatibility problems occasionally coming up. The below is just a smattering sample of LN stuff I personally find interesting. There's a bunch of other stuff, like splice and dual-funding, that I won't cover --- post is long enough as-is, and besides, some of the below aren't as well-known. Anyway.....
Yeah the exciting new Lightning Network channel update protocol!
Solves "toxic waste" problem. In the current Poon-Dryja update protocol, old state ("waste") is dangerous ("toxic") because if your old state is acquired by your most hated enemy, they can use that old state to publish a stale unilateral close transaction, which your counterparty must treat as a theft attempt and punish you, causing you to lose funds. With Decker-Russell-Osuntokun old state is not revoked, but is instead gainsaid by later state: instead of actively punishing old state, it simply replaces the old state with a later state.
Allows multiple participants in the update protocol. This can be used as the update protocol for a channel factory with 3 or more participants, for example (channels are not practical for multiple participants since the loss of any one participants makes the channel completely unuseable; it's more sensible to have a multiple-participant factory that splits up into 2-participant channels). Poon-Dryja only supports two participants. Another update protocol, Decker-Wattenhofer, also supports multiple participants, but requires much larger locktimes in case of a unilateral close (measurable in weeks, whereas Poon-Dryja and Decker-Russell-Osuntokun can be measured in hours or days).
It uses nLockTime in a very clever way.
No, it does not solve the "watchtower needed" problem. Decker-Russell-Osuntokun still requires watchtowers if you're planning to be offline for a long time.
What might be confused is that it was initially thought that watchtowers under Decker-Russell-Osuntokun could be made more efficient by having the channel participant update a single "slot" in the watchtower, rather than having to consume one "slot" per update in Poon-Dryja. However, the existence of the "poisoned blob" attack by ZmnSCPxj means that having a replaceable "slot" is risky if the other participant of the channel can spoof you. And the safest way to prevent spoofing somebody is to identify that somebody --- but now that means the watchtower can surveill the activities of somebody it has identified, losing privacy.
Requires base layer change --- SIGHASH_NOINPUT / SIGHASH_ANYPREVOUT. This is still being worked out and may potentially not reach Bitcoin anytime soon.
Determining costs of routes is somewhat harder, and may complicate routefinding algorithms. In particular: every channel today has a "CLTV Delta", a number of blocks by which the total maximum delay of the payment is increased. This maximum delay is the maximum amount of time by which an outgoing payment can be locked, and needs to be reduced for UX purposes. Decker-Russell-Osuntokun will also add a "CSV minimum", a number of blocks, which must be smaller than the delay of an HTLC going through the channel. Current routefinding algos are good at minimizing a summed-up cost (like the "CLTV Delta") so the "CSV minimum" may require discovering / developing new routefinding algos.
Due to the "CSV minimum" above, existing nodes that don't understand Decker-Russell-Osuntokun cannot reliably route over Decker-Russell-Osuntokun channels, as they might not impose this minimum properly.
Multipart payments / AMP
Splitting up large payments into smaller parts!
There are at least three variants of multipart payments: Original, Base, and High.
Original is the original AMP proposed by Lightning Labs. It sacrifices proof-of-payment in order to allow each path to have a different payment hash. This is done by having the payer use a derivation scheme to generate each part's payment preimage from a seed, then having the split the seed (using secret sharing) to each part. The receiver can only reconstruct the seed if all parts reach it.
Base simply uses the same payment hash for all routes. This retains proof-of-payment (i.e. an invoice is undeniably signed by the receiver, including a payment hash in the invoice; public knowledge of the payment preimage is proof that the receiver has in fact received money, and any third party can be convinced of this by being shown the signed invoice and the preimage). The receiver could just take one part of the payment and then claim to be underpaid by the payer and then deny service, but claiming any one part is enough to publish the payment preimage, creating a proof-of-payment: so the receiver can provably be made liable, even if it took just one part, thus the incentive of the receiver is to only take in the payment once all parts have arrived to it.
High requires elliptic curve points / scalars. It combines both Original and Base, retaining proof-of-payment (sacrificed by Original) and ensuring cryptographically-secure waiting for all parts (rather than the mere economically-incentivized of Base). This is done by using elliptic curve homomorphism to addition of scalars to add together the payer-provided preimage (really scalar) of Original with the payee-provided preimage (really scalar) of Base.
Better expected reliability. Channels are limited by capacity. By splitting up into many smaller payments, you can fit into more channels and be more likely to successfully reach the payee.
Capacity on mutiple of your channels can be used to pay. Currently if you have 0.05BTC on one channel and 0.05BTC on another channel, you can't pay 0.06BTC without first rebalancing your channels (and paying fees for the rebalance first, whether the payment succeeds or not). With multipart you can now combine the capacities of multiple of your channels, and only pay fees for combining them if the payment pushes through.
Wumbo payments (oversized payments) come "for free" without having to be explicitly supported by the nodes of the network: you just split up wumbo payments into parts smaller than the wumbo limit.
Multipart will have higher fees. Part of the feerate of each channel is a flat-rate fee. Going through multiple paths means paying more of this flat-rate fee.
It's not clear how to split up payments. Heuristics for payment splitting have to be derived and developed and tested.
Payment points / scalars
Using the magic of elliptic curve homomorphism for fun and Lightning Network profits! Basically, currently on Lightning an invoice has a payment hash, and the receiver reveals a payment preimage which, when inputted to SHA256, returns the given payment hash. Instead of using payment hashes and preimages, just replace them with payment points and scalars. An invoice will now contain a payment point, and the receiver reveals a payment scalar (private key) which, when multiplied with the standard generator point G on secp256k1, returns the given payment point. This is basically Scriptless Script usage on Lightning, instead of HTLCs we have Scriptless Script Pointlocked Timelocked Contracts (PTLCs).
Enables a shit-ton of improvements: payment decorrelation, stuckless payments, noncustodial escrow over Lightning (the Hodl Hodl Lightning escrow is custodial, read the fine print), High multipart.
It's the same coolness that makes Schnorr Signatures cool. ECDSA, despite being based on elliptic curves, is not cool because the hash-the-nonce operation needed to prevent it from infringing Schnorr's fatherfucking patent also prevents ECDSA from using the cool elliptic curve homomorphism of addition over scalars.
Requires Schnorr on Bitcoin layer.
Actually, we can work with 2p-ECDSA without waiting for Schnorr. We get back the nice elliptic curve homomorphism by passing the ECDSA nonce through another cryptosystem, Paillier. This gets us the ability to do Scriptless Script. I think it has only 80-bits security because of going through Paillier though.
Basically the conundrum is: we could implement 2p-ECDSA now, hope we never have to test the 80-bit security anytime soon, then switch to Schnorr with 128-bit security later (which means reimplementing a bunch of things, because the calculations are different and the data that needs to be exchanged between channel participants is very different between the 2p-ECDSA and Schnorr). Reimplementing is painful and is more dev work. If we don't implement with 2p-ECDSA now, though, we will be delaying all the nice elliptic curve goodness (stuckless, noncustodial escrow, payment decorrelation) until Bitcoin gets Schnorr.
Elliptic curve discrete log problem is theoretically quantum-vulnerable. If we can't find a qunatum-resistant homomorphic construction, we'll have to give up the advantages (payment decorrelation, stuckless payments, noncustodial escrow over Lightning) we got from using elliptic curve points and go back to boring old hashes.
Ensuring that payers cannot access data or other digital goods without proof of having paid the provider. In a nutshell: the payment preimage used as a proof-of-payment is the decryption key of the data. The provider gives the encrypted data, and issues an invoice. The buyer of the data then has to pay over Lightning in order to learn the decryption key, with the decryption key being the payment preimage.
Enables data providers to sell data. This could be sensors, livestreams, blogs, articles, whatever.
There's no scheme to determine if the data provider is providing actually-useful data. The data-provider could just stream https://random.org for example. This is a potentially-impossible problem. Even if the data-provider provides a "sample" of the data, and is able to derive some proof that the sample is indeed a true snippet of the encrypted data, the rest of the data outside of the sample might just be random junk.
No more payments getting stuck somewhere in the Lightning network without knowing whether the payee will ever get paid! (that's actually a bit overmuch claim, payments still can get stuck, but what "stuckless" really enables is that we can now safely run another parallel payment attempt until any one of the payment attempts get through). Basically, by using the ability to add points together, the payer can enforce that the payee can only claim the funds if it knows two pieces of information:
The payment scalar corresponding to the payment point in the invoice signed by the payee.
An "acknowledgment" scalar provided by the payer to the payee via another communication path.
This allows the payer to make multiple payment attempts in parallel, unlike the current situation where we must wait for an attempt to fail before trying another route. The payer only needs to ensure it generates different acknowledgment scalars for each payment attempt. Then, if at least one of the payment attempts reaches the payee, the payee can then acquire the acknowledgment scalar from the payer. Then the payee can acquire the payment. If the payee attempts to acquire multiple acknowledgment scalars for the same payment, the payer just gives out one and then tells the payee "LOL don't try to scam me", so the payee can only acquire a single acknowledgment scalar, meaning it can only claim a payment once; it can't claim multiple parallel payments.
Can safely run multiple parallel payment attempts as long as you have the funds to do so.
Needs payment point + scalar
Non-custodial escrow over Lightning
The "acknowledgment" scalar used in stuckless can be reused here. The acknowledgment scalar is derived as an ECDH shared secret between the payer and the escrow service. On arrival of payment to the payee, the payee queries the escrow to determine if the acknowledgment point is from a scalar that the escrow can derive using ECDH with the payer, plus a hash of the contract terms of the trade (for example, to transfer some goods in exchange for Lightning payment). Once the payee gets confirmation from the escrow that the acknowledgment scalar is known by the escrow, the payee performs the trade, then asks the payer to provide the acknowledgment scalar once the trade completes. If the payer refuses to give the acknowledgment scalar even though the payee has given over the goods to be traded, then the payee contacts the escrow again, reveals the contract terms text, and requests to be paid. If the escrow finds in favor of the payee (i.e. it determines the goods have arrived at the payer as per the contract text) then it gives the acknowledgment scalar to the payee.
True non-custodial escrow: the escrow service never holds any funds.
Needs payment point + scalar.
Because elliptic curve points can be added (unlike hashes), for every forwarding node, we an add a "blinding" point / scalar. This prevents multiple forwarding nodes from discovering that they have been on the same payment route. This is unlike the current payment hash + preimage, where the same hash is used along the route. In fact, the acknowledgment scalar we use in stuckless and escrow can simply be the sum of each blinding scalar used at each forwarding node.
Privacy! Multiple forwarding nodes cannot coordinate to try to uncover the payer and payee of each payment.
What's this? I don't make a Technical post for a month and now BitPay is censoring the Hong Kong Free Press? Shit I'm sorry, it's all my fault for not posting a Technical post regularly!! Now posting one so that we have a censorship-free Bitcoin universe! Pay-to-contract and sign-to-contract are actually cryptographic techniques to allow you to embed a commitment in a public key (pay-to-contract) or signature (sign-to-contract). This commitment can be revealed independently of the public key / signature without leaking your private key, and the existence of the commitment does not prevent you from using the public key / signature as a normal pubkey/signature for a normal digital signing algorithm. Both techniques utilize elliptic curve homomorphism. Let's digress into that a little first.
Elliptic Curve Homomorphism
Let's get an oversimplified view of the maths involved first. First, we have two "kinds" of things we can compute on.
One kind is "scalars". These are just very large single numbers. Traditionally represented by small letters.
The other kind is "points". These are just pairs of large numbers. Traditionally represented by large letters.
Now, an "Elliptic Curve" is just a special kind of curve with particular mathematical properties. I won't go into those properties, for the very reasonable reason that I don't actually understand them (I'm not a cryptographer, I only play one on reddit!). If you have an Elliptic Curve, and require that all points you work with are on some Elliptic Curve, then you can do these operations.
Add, subtract, multiply, and divide scalars. Remember, scalars are just very big numbers. So those basic mathematical operations still work on big numbers, they're just big numbers.
"Multiply" a scalar by a point, resulting in a point. This is written as a * B, where a is the scalar and B is a point. This is not just multiplying the scalar to the point coordinates, this is some special Elliptic Curve thing that I don't understand either.
"Add" two points together. This is written as A + B. Again, this is some special Elliptic Curve thing.
The important part is that if you have:
A = a * G B = b * G Q = A + B
q = a + b Q = q * G
That is, if you add together two points that were each derived from multiplying an arbitarry scalar with the same point (G in the above), you get the same result as adding the scalars together first, then multiplying their sum with the same point will yield the same number. Or:
a * G + b * G = (a + b) * G
And because multiplication is just repeated addition, the same concept applies when multiplying:
a * (b * G) = (a * b) * G = (b * a) * G = b * (a * G)
Something to note in particular is that there are few operations on points. One operation that's missing is "dividing" a point by a point to yield a scalar. That is, if you have:
A = a * G
Then, if you know A but don't know the scalar a, you can't do the below:
a = A / G
You can't get a even if you know both the points A and G. In Elliptic Curve Cryptography, scalars are used as private keys, while points are used as public keys. This is particularly useful since if you have a private key (scalar), you can derive a public key (point) from it (by multiplying the scalar with a certain standard point, which we call the "generator point", traditionally G). But there is no reverse operation to get the private key from the public key.
Let's have another mild digression. Sometimes, you want to "commit' to something that you want to keep hidden for now. This is actually important in some games and so on. For example, if you are paying a game of Twenty Questions, one player must first write the object they are thinking of, then fold or hide it in such a way that what they wrote is not visible. Then, after the guessing player has asked twenty questions to narrow down what the object is and has revealed what he or she thinks the object being guessed was, the guessee reveals the object by unfodling and showing the paper. The act of writing down commits you to the specific thing you wrote down. Folding the paper and/or hiding it, err, hides what you wrote down. Later, when you unfold the paper, you reveal your commitment. The above is the analogy to the development of cryptographic commitments.
First you select some thing --- it could be anything, a song, a random number, a promise to deliver products and services, the real identity of Satoshi Nakamoto.
You commit to it by giving it as input to a one-way function. A one-way function is a function which allows you to get an output from an input, but after you perform that there is no way to reverse it and determine the original input knowing only the final output. Hash functions like SHA are traditionally used as one-way functions. As a one-way function, this hides your original input.
You give the commitment (the output of the one-way function given your original input) to whoever wants you to commit.
Later, when somebody demands to show what you committed to (for example after playing Twenty Questions), you reveal the commitment by giving the original input to the one-way function (i.e. the thing you selected in the first step, which was the thing you wanted to commit to).
Whoever challenged you can verify your commitment by feeding your supposed original input to the same one-way function. If you honestly gave the correct input, then the challenger will get the output that you published above in step 3.
Now, sometimes there are only a few possible things you can select from. For example, instead of Twenty Questions you might be playing a Coin Toss Guess game. What we'd do would be that, for example, I am the guesser and you the guessee. You select either "heads" or "tails" and put it in a commitment which you hand over to me. Then, I say "heads" or "tails" and have you reveal your commitment. If I guessed correctly I win, if not you win. Unfortunately, if we were to just use a one-way function like an SHA hash function, it would be very trivial for me to win. All I would need to do would be to try passing "heads" and "tails" to the one-way function and see which one matches the commitment you gave me. Then I can very easily find out what your committed value was, winning the game consistently. In hacking, this can be made easier by making Rainbow Tables, and is precisely the technique used to derive passwords from password databases containing hashes of the passwords. The way to solve this is to add a salt. This is basically just a large random number that we prepend (or append, order doesn't matter) to the actual value you want to commit to. This means that not only do I have to feed "heads" or "tails", I also have to guess the large random number (the salt). If the possible space of large random numbers is large enough, this prevents me from being able to peek at your committed data. The salt is sometimes called a blinding factor.
Hiding commitments in pubkeys! Pay-to-contract allows you to publish a public key, whose private key you can derive, while also being a cryptographic commitment. In particular, your private key is also used to derive a salt. The key insight here is to realize that "one-way function" is not restricted to hash functions like SHA. The operation below is an example of a one-way function too:
h(a) = a * G
This results in a point, but once the point (the output) is known, it is not possible to derive the input (the scalar a above). This is of course restricted to having the input be a scalar only, instead of an arbitrary-length message, but you can add a hash function (which can accept an arbitrary-length input) and then make its output (a fixed-length scalar) as the scalar to use. First, pay-to-contract requires you to have a public and private keypair.
; p is private key P = p * G ; P is now public key
Then, you have to select a contract. This is just any arbitrary message containing any arbitrary thing (it could be an object for Twenty Questions, or "heads" or "tails" for Coin Toss Guessing). Traditionally, this is symbolized as the small letter s. In order to have a pay-to-contract public key, you need to compute the below from your public key P (called the internal public key; by analogy the private key p is the internal private key):
Q = P + h(P | s) * G
"h()" is any convenient hash function, which takes anything of arbitrary length, and outputs a scalar, which you can multiply by G. The syntax "P | s" simply means that you are prepending the point P to the contract s. The cute thing is that P serves as your salt. Any private key is just an arbitrary random scalar. Multiplying the private key by the generator results in an arbitrary-seeming point. That random point is now your salt, which makes this into a genuine bonafide hiding cryptographic commitment! Now Q is a point, i.e. a public key. You might be interested in knowing its private key, a scalar. Suppose you postulate the existence of a scalar q such that:
Q = q * G
Then you can do the below:
Q = P + h(P | s) * G Q = p * G + h(P | s) * G Q = (p + h(P | s)) * G
Then we can conclude that:
q = p + h(P | s)
Of note is that somebody else cannot learn the private key q unless they already know the private key p. Knowing the internal public key P is not enough to learn the private key q. Thus, as long as you are the only one who knows the internal private key p, and you keep it secret, then only you can learn the private key q that can be used to sign with the public key Q (that is also a pay-to-contract commitment). Now Q is supposed to be a commitment, and once somebody else knows Q, they can challenge you to reveal your committed value, the contract s. Revealing the pay-to-contract commitment is done by simply giving the internal public key P (which doubles as the salt) and the committed value contract s. The challenger then simply computes:
P + h(P | s) * G
And verifies that it matches the Q you gave before. Some very important properties are:
If you reveal first, then you still remain in sole control of the private key. This is because revelation only shows the internal public key and the contract, neither of which can be used to learn the internal private key. So you can reveal and sign in any order you want, without precluding the possibility of performing the other operation in the future.
If you sign with the public key Q first, then you do not need to reveal the internal public key P or the contract s. You can compute q simply from the internal private key p and the contract s. You don't even need to pass those in to your signing algorithm, it could just be given the computed q and the message you want to sign!
Anyone verifying your signature using the public key Q is unaware that it is also used as a cryptographic commitment.
Another property is going to blow your mind:
You don't have to know the internal private key p in order to create a commitment pay-to-contract public key Q that commits to a contract s you select.
Q = P + h(P | s) * G
The above equation for Q does not require that you know the internal private key p. All you need to know is the internal public key P. Since public keys are often revealed publicly, you can use somebody else's public key as the internal public key in a pay-to-contract construction. Of course, you can't sign for Q (you need to know p to compute the private key q) but this is sometimes an interesting use. The original proposal for pay-to-contract was that a merchant would publish their public key, then a customer would "order" by writing the contract s with what they wanted to buy. Then, the customer would generate the public key Q (committing to s) using the merchant's public key as the internal public key P, then use that in a P2PKH or P2WPKH. Then the customer would reveal the contract s to the merchant, placing their order, and the merchant would now be able to claim the money. Another general use for pay-to-contract include publishing a commitment on the blockchain without using an OP_RETURN output. Instead, you just move some of your funds to yourself, using your own public key as the internal public key, then selecting a contract s that commits or indicates what you want to anchor onchain. This should be the preferred technique rather than OP_RETURN. For example, colored coin implementations over Bitcoin usually used OP_RETURN, but the new RGB colored coin technique uses pay-to-contract instead, reducing onchain bloat.
Pay-to-contract is also used in the nice new Taproot concept. Briefly, taproot anchors a Merkle tree of scripts. The root of this tree is the contract s committed to. Then, you pay to a SegWit v1 public key, where the public key is the Q pay-to-contract commitment. When spending a coin paying to a SegWit v1 output with a Taprooted commitment to a set of scripts s, you can do one of two things:
Sign directly with the key. If you used Taproot, use the commitment private key q.
Reveal the commitment, then select the script you want to execute in the Merkle tree of scripts (prove the Markle tree path to the script). Then satisfy the conditions of the script.
Taproot utilizes the characteristics of pay-to-contract:
If you reveal first, then you still remain in sole control of the private key.
This is important if you take the Taproot path and reveal the commitment to the set of scripts s. If your transaction gets stalled on the mempool, others can know your commitment details. However, revealing the commitment will not reveal the internal private key p (which is needed to derive the commitment private key q), so nobody can RBF out your transaction by using the sign-directly path.
If you sign with the public key Q first, then you do not need to reveal the internal public key P or the contract s.
This is important for privacy. If you are able to sign with the commitment public key, then that automatically hides the fact that you could have used an alternate script s instead of the key Q.
Anyone verifying your signature using the public key Q is unaware that it is also used as a cryptographic commitment.
Again, privacy. Fullnodes will not know that you had the ability to use an alternate script path.
Taproot is intended to be deployed with the switch to Schnorr-based signatures in SegWit v1. In particular, Schnorr-based signatures have the following ability that ECDSA cannot do except with much more difficulty:
It is possible to generate a single public key that cannot be signed, except by the agreement of multiple signers who each contribute part of the public key. I.e. this is MuSig, which allows to create an n-of-n signing group that has a single public key.
As public keys can, with Schnorr-based signatures, easily represent an n-of-n signing set, the internal public key P can also actually be a MuSig n-of-n signing set. This allows for a number of interesting protocols, which have a "good path" that will be private if that is taken, but still have fallbacks to ensure proper execution of the protocol and prevent attempts at subverting the protocol.
Escrow Under Taproot
Traditionally, escrow is done with a 2-of-3 multisignature script. However, by use of Taproot and pay-to-contract, it's possible to get more privacy than traditional escrow services. Suppose we have a buyer, a seller, and an escrow service. They have keypairs B = b * G, S = s * G, and E = e * G. The buyer and seller then generate a Taproot output (which the buyer will pay to before the seller sends the product). The Taproot itself uses an internal public key that is the 2-of-2 MuSig of B and S, i.e. MuSig(B, S). Then it commits to a pair of possible scripts:
Release to a 2-of-2 MuSig of seller and escrow. This path is the "escrow sides with seller" path.
Release to a 2-of-2 MuSig of buyer and escrow. This path is the "escrow sides with buyer" path.
Now of course, the escrow also needs to learn what the transaction was supposed to be about. So what we do is that the escrow key is actually used as the internal public key of another pay-to-contract, this time with the script s containing the details of the transaction. For example, if the buyer wants to buy some USD, the contract could be "Purchase of 50 pieces of United States Federal Reserve Green Historical Commemoration papers for 0.357 satoshis". This takes advantage of the fact that the committer need not know the private key behind the public key being used in a pay-to-contract commitment. The actual transaction it is being used for is committed to onchain, because the public key published on the blockchain ultimately commits (via a taproot to a merkle tree to a script containing a MuSig of a public key modified with the committed contract) to the contract between the buyer and seller. Thus, the cases are:
Buyer and seller are satisfied, and cooperatively create a signature that spends the output to the seller.
The escrow service never learns it could have been an escrow. The details of their transaction remain hidden and private, so the buyer is never embarrassed over being so tacky as to waste their hard money buying USD.
The buyer and seller disagree (the buyer denies having received the goods in proper quality).
They contact the escrow, and reveal the existence of the onchain contract, and provide the data needed to validate just what, exactly, the transaction was supposed to be about. This includes revealing the "Purchase of 50 pieces of United States Federal Reserve Green Historical Commemoration papers for 0.357 satoshis", as well as all the data needed to validate up to that level. The escrow then investigates the situation and then decides in favor of one or the other. It signs whatever transaction it decides (either giving it to the seller or buyer), and possibly also extracts an escrow fee.
Smart Contracts Unchained
Developed by ZmnSCPxj here: https://zmnscpxj.github.io/bitcoin/unchained.html A logical extension of the above escrow case is to realize that the "contract" being given to the escrow service is simply some text that is interpreted by the escrow, and which is then executed by the escrow to determine where the funds should go. Now, the language given in the previous escrow example is English. But nothing prevents the contract from being written in another language, including a machine-interpretable one. Smart Contracts Unchained simply makes the escrow service an interpreter for some Smart Contract scripting language. The cute thing is that there still remains an "everything good" path where the participants in the smart contract all agree on what the result is. In that case, with Taproot, there is no need to publish the smart contract --- only the participants know, and nobody else has to. This is an improvement in not only privacy, but also blockchain size --- the smart contract itself never has to be published onchain, only the commitment to it is (and that is embedded in a public key, which is necessary for basic security on the blockchain anyway!).
Hiding commitments in signatures! Sign-to-contract is something like the dual or inverse of pay-to-contract. Instead of hiding a commitment in the public key, it is hidden in the signature. Sign-to-contract utilizes the fact that signatures need to have a random scalar r which is then published as the point R = r * G. Similarly to pay-to-contract, we can have an internal random scalar p and internal point P that is used to compute R:
R = P + h(P | s) * G
The corresponding random scalar r is:
r = p + h(P | s)
The signing algorithm then uses the modified scalar r. This is in fact just the same method of commitment as in pay-to-contract. The operations of committing and revealing are the same. The only difference is where the commitment is stored. Importantly, however, is that you cannot take somebody else's signature and then create an alternate signature that commits to some s you select. This is in contrast with pay-to-contract, where you can take somebody else's public key and then create an alternate public key that commits to some s you select. Sign-to-contract is somewhat newer as a concept than pay-to-contract. It seems there are not as many applications of pay-to-contract yet.
Sign-to-contract can be used, like pay-to-contract, to publish commitments onchain. The difference is below:
Signatures are attached to transaction inputs.
Public keys are attached to transaction outputs.
One possible use is in a competitor to Open Timestamps. Open Timestamps currently uses OP_RETURN to commit to a Merkle Tree root of commitments aggregated by an Open Timestamps server. Instead of using such an OP_RETURN, individual wallets can publish a timestamped commitment by making a self-paying transaction, embedding the commitment inside the signature for that transaction. Such a feature can be added to any individual wallet software. https://blog.eternitywall.com/2018/04/13/sign-to-contract/ This does not require any additional infrastructure (i.e. no aggregating servers like in Open Timestamps).
R Reuse Concerns
ECDSA and Schnorr-based signature schemes are vulnerable to something called "R reuse". Basically, if the same R is used for different messages (transactions) with the same public key, a third party with both signatures can compute the private key. This is concerning especially if the signing algorithm is executed in an environment with insufficient entropy. By complete accident, the environment might yield the same random scalar r in two different runs. Combined with address reuse (which implies public key reuse) this can leak the private key inadvertently. For example, most hardware wallets will not have any kind of entropy at all. The usual solution to this is, instead of selecting an arbitrary random r (which might be impossible in limited environments with no available entropy), is to hash the message and use the hash as the r. This ensures that if the same public key is used again for a different message, then the random r is also different, preventing reuse at all. Of course, if you are using sign-to-contract, then you can't use the above "best practice". It seems to me plausible that computing the internal random scalar p using the hash of the message (transaction) should work, then add the commitment on top of that. However, I'm not an actual cryptographer, I just play one on Reddit. Maybe apoelstra or pwuille can explain in more detail. Copyright 2019 Alan Manuel K. Gloria. Released under CC-BY.
Nice Article About How HPB Perform Vs EOS (and so ETH)