While making things match closely with Rusty's wire protocol, I noticed
he didn't allow for multiple HTLCs. Gotta explain the rationale for that that... will merge the progress in the next commit tomorrow.
This commit is contained in:
parent
bc7bdcd22b
commit
02a9b1e237
105
lnwire/ESCROWED_HTLC_NOTES
Normal file
105
lnwire/ESCROWED_HTLC_NOTES
Normal file
@ -0,0 +1,105 @@
|
||||
NOTE: Not implemented in code, only included as part of the wire protocol for
|
||||
future implementation!
|
||||
|
||||
There are multiple R-value hashes supported in HTLCs in the wire protocol. This
|
||||
is to support conditional multiparty payments, e.g. 2-of-3 "escrow", which is
|
||||
one of the biggest use cases of bitcoin scripting today. An example use case is
|
||||
a 3rd party "escrow" verifies whether a seller should be paid. This design is
|
||||
such that the "escrow" is not a normal escrow which holds custody, but
|
||||
determines who should get the money in the event of non-cooperation.
|
||||
|
||||
In this implementation, we are including *future protocol support* but not
|
||||
writing code yet for 2-of-3, it is to be implemented later. Arbitrary N-of-M
|
||||
can be supported, but let's keep this simple for now!
|
||||
|
||||
How it works: Require 2-of-3 R-value preimages (from 3 hashes) in order for the
|
||||
HTLC to be fulfilled. For each hop in the payment, it requires this 2-of-3
|
||||
condition. The timeout minimum for each hop in the path is at least the minimum
|
||||
agreed contractual escrow timeout. This means each hop consumes a higher amount
|
||||
of time-value (due to much longer timeouts along all channels in the path),
|
||||
which does have greater pressure towards lower hop-distances compared to
|
||||
straight payments.
|
||||
|
||||
This is a slightly different way of thinking about things. It's not signatures
|
||||
that the escrow produces (or for that matters any of the 3-parties in the
|
||||
2-of-3). It's some secret which is revealed to authorize payment. So if the
|
||||
Escrow wants the payment to go through, they disclose the secret (R-value) to
|
||||
the recipient. If the recipient is unable to produce 2-of-3, after the agreed
|
||||
timeout, the sender will be refunded. Sender and receiver can agree to authorize
|
||||
payment in most cases where there is cooperation, escrow is only contacted if
|
||||
there is non-cooperation.
|
||||
|
||||
Supported in the wire protocol for the unit8:
|
||||
1: 1-of-1
|
||||
2: 1-of-2
|
||||
3: 2-of-2
|
||||
4: 1-of-3
|
||||
5: 2-of-3
|
||||
6: 3-of-3
|
||||
|
||||
I think the only ones that really matter are 1-of-1, 2-of-3, and maybe 2-of-2
|
||||
and 3-of-3 (in that order of declining importance).
|
||||
|
||||
Assume the order in the stack is Sender, Recipient, Escrow.
|
||||
|
||||
For PAID 2-of-3 Recipient+Escrow, the HTLC stack is:
|
||||
<BobSig> <0> <RecipientPreimageR> <EscrowPreimageR> <0>
|
||||
|
||||
If it's REFUND because 2-of-3 has not been redeemed in time:
|
||||
<AliceSig> <1>
|
||||
|
||||
Script (we use OP_1/OP_0 to distinctly show computed true/false. 0/1 is for
|
||||
supplied data as part of the sigScript/redeemScript stack):
|
||||
-------------------------------------------------------------------------------
|
||||
//Paid
|
||||
OP_IF
|
||||
//Optional... <CSVDelay> OP_CSV
|
||||
|
||||
OP_HASH160 <EscrowHash> OP_EQUAL
|
||||
//Stack: <BobSig> <0> <RecipientPreimageR> <OP_1>
|
||||
OP_SWAP
|
||||
//Stack: <BobSig> <0> <OP_1> <RecipientPreimageR>
|
||||
OP_HASH160 <RecipientHash> OP_EQUAL
|
||||
//Stack: <BobSig> <0> <OP_1> <OP_1>
|
||||
OP_ADD
|
||||
//Stack: <BobSig> <0> <OP_2>
|
||||
OP_SWAP
|
||||
//Stack: <BobSig> <OP_2> <0>
|
||||
OP_HASH160 <SenderHash> OP_EQUAL
|
||||
//Stack: <BobSig> <OP_2> <OP_0>
|
||||
OP_ADD
|
||||
//Stack: <BobSig> <OP_2>
|
||||
<2> OP_EQUALVERIFY
|
||||
|
||||
<BobPubKey>
|
||||
//Stack: <BobSig> <BobPubKey>
|
||||
//Refund
|
||||
OP_ELSE
|
||||
//Optional... <CSVDelay> OP_CSV
|
||||
|
||||
<HTLCTimeout> OP_CLTV
|
||||
<AlicePubKey>
|
||||
//Stack: <AliceSig> <AlicePubKey>
|
||||
OP_ENDIF
|
||||
OP_CHECKSIG
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Note: It is possible that Alice and Bob may not be Sender, Recipient, nor
|
||||
Escrow!
|
||||
|
||||
If we have all 3 R-values, we only include 2 and include a dummy zero on the
|
||||
third.
|
||||
|
||||
The result? We can do 2-of-3 escrow payments which refund to the sender after a
|
||||
timeout! The Buyer and Seller can agree to redeem and they only need to go to
|
||||
the Escrow if there's a dispute. Each node along the path gets paid or refunded
|
||||
atomically, the same as a single-HTLC payment on Lightning.
|
||||
|
||||
Ta-da! "Smart Contract(TM)" maymay.
|
||||
|
||||
Immediately refundable payments (2-of-3 can immediately cancel a payment) are
|
||||
also possible but requires another payment in the opposite direction with the
|
||||
R-value hashed twice (the R value becomes the H), but that's kind of annoying to
|
||||
write... it's easier to just assume immediate refund can only occur if both
|
||||
buyer+seller agree to cancel the payment immediately (otherwise it will wait
|
||||
until the timeout).
|
@ -31,6 +31,7 @@ type FundingRequest struct {
|
||||
RevocationHash [20]byte
|
||||
Pubkey *btcec.PublicKey
|
||||
DeliveryPkScript PkScript //*MUST* be either P2PKH or P2SH
|
||||
//FIXME: Need a ChangePkScript PkScript for dual-funder CLTV?
|
||||
|
||||
Inputs []*wire.TxIn
|
||||
}
|
||||
@ -137,21 +138,21 @@ func (c *FundingRequest) Validate() error {
|
||||
}
|
||||
|
||||
//PkScript is either P2SH or P2PKH
|
||||
//P2PKH
|
||||
if len(c.DeliveryPkScript) == 25 {
|
||||
//P2PKH
|
||||
//Begins with OP_DUP OP_HASH160 PUSHDATA(20)
|
||||
if !(bytes.Equal(c.DeliveryPkScript[0:3], []byte{118, 169, 20}) &&
|
||||
if !bytes.Equal(c.DeliveryPkScript[0:3], []byte{118, 169, 20}) &&
|
||||
//Ends with OP_EQUALVERIFY OP_CHECKSIG
|
||||
bytes.Equal(c.DeliveryPkScript[23:25], []byte{136, 172})) {
|
||||
!bytes.Equal(c.DeliveryPkScript[23:25], []byte{136, 172}) {
|
||||
//If it's not correct, return error
|
||||
return fmt.Errorf("PkScript only allows P2SH or P2PKH")
|
||||
}
|
||||
//P2SH
|
||||
} else if len(c.DeliveryPkScript) == 23 {
|
||||
//P2SH
|
||||
//Begins with OP_HASH160 PUSHDATA(20)
|
||||
if !(bytes.Equal(c.DeliveryPkScript[0:2], []byte{169, 20}) &&
|
||||
if !bytes.Equal(c.DeliveryPkScript[0:2], []byte{169, 20}) &&
|
||||
//Ends with OP_EQUAL
|
||||
bytes.Equal(c.DeliveryPkScript[22:23], []byte{135})) {
|
||||
!bytes.Equal(c.DeliveryPkScript[22:23], []byte{135}) {
|
||||
//If it's not correct, return error
|
||||
return fmt.Errorf("PkScript only allows P2SH or P2PKH")
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
)
|
||||
|
||||
//4-byte network + 4-byte message id + payload-length 4-byte
|
||||
//Maybe add checksum or something?
|
||||
const MessageHeaderSize = 12
|
||||
|
||||
const MaxMessagePayload = 1024 * 1024 * 32 // 32MB
|
||||
@ -48,7 +47,6 @@ type messageHeader struct {
|
||||
magic wire.BitcoinNet
|
||||
command uint32
|
||||
length uint32
|
||||
//Do we need a checksum here?
|
||||
}
|
||||
|
||||
func readMessageHeader(r io.Reader) (int, *messageHeader, error) {
|
||||
@ -122,7 +120,6 @@ func WriteMessage(w io.Writer, msg Message, pver uint32, btcnet wire.BitcoinNet)
|
||||
hdr.magic = btcnet
|
||||
hdr.command = cmd
|
||||
hdr.length = uint32(lenp)
|
||||
//Checksum goes here if needed... and also add to writeElements
|
||||
|
||||
// Encode the header for the message. This is done to a buffer
|
||||
// rather than directly to the writer since writeElements doesn't
|
||||
@ -189,8 +186,6 @@ func ReadMessage(r io.Reader, pver uint32, btcnet wire.BitcoinNet) (int, Message
|
||||
return totalBytes, nil, nil, err
|
||||
}
|
||||
|
||||
//If we want to use checksums, test it here
|
||||
|
||||
//Unmarshal message
|
||||
pr := bytes.NewBuffer(payload)
|
||||
err = msg.Decode(pr, pver)
|
||||
|
Loading…
Reference in New Issue
Block a user