mirror of
https://github.com/ACINQ/eclair.git
synced 2024-11-20 02:27:32 +01:00
fixed numerous bugs related to htlc scripts
This commit is contained in:
parent
95c4606268
commit
83bfc5ac9f
@ -250,7 +250,7 @@ class Channel(val blockchain: ActorRef, val params: OurChannelParams, val anchor
|
||||
val state = ChannelState(them = ChannelOneSide(0, 0, Seq()), us = ChannelOneSide((anchorInput.amount - ourParams.commitmentFee) * 1000, 0, Seq()))
|
||||
val ourRevocationHash = Crypto.sha256(ShaChain.shaChainFromSeed(ourParams.shaSeed, 0))
|
||||
val (ourCommitTx, ourSigForThem) = sign_their_commitment_tx(ourParams, theirParams, TxIn(OutPoint(anchorTx.hash, anchorOutputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil, state, ourRevocationHash, theirRevocationHash)
|
||||
them ! open_anchor(anchorTx.txid, anchorOutputIndex, anchorInput.amount, ourSigForThem)
|
||||
them ! open_anchor(anchorTx.hash, anchorOutputIndex, anchorInput.amount, ourSigForThem)
|
||||
goto(OPEN_WAIT_FOR_COMMIT_SIG) using DATA_OPEN_WAIT_FOR_COMMIT_SIG(ourParams, theirParams, anchorTx, anchorOutputIndex, Commitment(0, ourCommitTx, state, theirRevocationHash))
|
||||
|
||||
case Event(CMD_CLOSE(_), _) => goto(CLOSED)
|
||||
|
@ -3,20 +3,33 @@ package fr.acinq.eclair.channel
|
||||
import fr.acinq.bitcoin.Crypto._
|
||||
import fr.acinq.bitcoin._
|
||||
import fr.acinq.eclair._
|
||||
import lightning.{update_add_htlc, open_anchor, open_channel}
|
||||
import lightning.locktime.Locktime.{Seconds, Blocks}
|
||||
import lightning.{locktime, update_add_htlc, open_anchor, open_channel}
|
||||
|
||||
/**
|
||||
* Created by PM on 21/01/2016.
|
||||
*/
|
||||
object Scripts {
|
||||
|
||||
def locktime2long_csv(in: locktime): Long = in match {
|
||||
case locktime(Blocks(blocks)) => blocks
|
||||
// FIXME : we adopt Element's alpha convention while BIP68 is not enabled
|
||||
case locktime(Seconds(seconds)) => 500000000 + seconds
|
||||
}
|
||||
|
||||
def locktime2long_cltv(in: locktime): Long = in match {
|
||||
case locktime(Blocks(blocks)) => blocks
|
||||
case locktime(Seconds(seconds)) => seconds
|
||||
}
|
||||
|
||||
def isLess(a: Seq[Byte], b: Seq[Byte]): Boolean = memcmp(a.dropWhile(_ == 0).toList, b.dropWhile(_ == 0).toList) < 0
|
||||
|
||||
def lessThan(output1: TxOut, output2: TxOut) : Boolean = (output1, output2) match {
|
||||
def lessThan(output1: TxOut, output2: TxOut): Boolean = (output1, output2) match {
|
||||
case (TxOut(amount1, script1), TxOut(amount2, script2)) if amount1 == amount2 => memcmp(script1.toList, script2.toList) < 0
|
||||
case (TxOut(amount1, _), TxOut(amount2, _)) => amount1 < amount2
|
||||
}
|
||||
|
||||
def permuteOutputs(tx: Transaction) : Transaction = tx.copy(txOut = tx.txOut.sortWith(lessThan))
|
||||
def permuteOutputs(tx: Transaction): Transaction = tx.copy(txOut = tx.txOut.sortWith(lessThan))
|
||||
|
||||
def multiSig2of2(pubkey1: BinaryData, pubkey2: BinaryData): BinaryData = if (isLess(pubkey1, pubkey2))
|
||||
BinaryData(Script.createMultiSigMofN(2, Seq(pubkey1, pubkey2)))
|
||||
@ -49,7 +62,7 @@ object Scripts {
|
||||
(signedTx, 0)
|
||||
}
|
||||
|
||||
def anchorPubkeyScript(pubkey1: BinaryData, pubkey2: BinaryData) : BinaryData = Script.write(pay2sh(multiSig2of2(pubkey1, pubkey2)))
|
||||
def anchorPubkeyScript(pubkey1: BinaryData, pubkey2: BinaryData): BinaryData = Script.write(pay2sh(multiSig2of2(pubkey1, pubkey2)))
|
||||
|
||||
def redeemSecretOrDelay(delayedKey: BinaryData, lockTime: Long, keyIfSecretKnown: BinaryData, hashOfSecret: BinaryData): Seq[ScriptElt] = {
|
||||
// @formatter:off
|
||||
@ -63,7 +76,7 @@ object Scripts {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
def scriptPubKeyHtlcSend(ourkey: BinaryData, theirkey: BinaryData, value: Long, htlc_abstimeout: Long, locktime: Long, commit_revoke: BinaryData, rhash: BinaryData): Seq[ScriptElt] = {
|
||||
def scriptPubKeyHtlcSend(ourkey: BinaryData, theirkey: BinaryData, htlc_abstimeout: Long, locktime: Long, rhash: BinaryData, commit_revoke: BinaryData): Seq[ScriptElt] = {
|
||||
// @formatter:off
|
||||
OP_HASH160 :: OP_DUP ::
|
||||
OP_PUSHDATA(ripemd160(rhash)) :: OP_EQUAL ::
|
||||
@ -77,7 +90,7 @@ object Scripts {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
def scriptPubKeyHtlcReceive(ourkey: BinaryData, theirkey: BinaryData, value: Long, htlc_abstimeout: Long, locktime: Long, commit_revoke: BinaryData, rhash: BinaryData): Seq[ScriptElt] = {
|
||||
def scriptPubKeyHtlcReceive(ourkey: BinaryData, theirkey: BinaryData, htlc_abstimeout: Long, locktime: Long, rhash: BinaryData, commit_revoke: BinaryData): Seq[ScriptElt] = {
|
||||
// @formatter:off
|
||||
OP_HASH160 :: OP_DUP ::
|
||||
OP_PUSHDATA(ripemd160(rhash)) :: OP_EQUAL ::
|
||||
@ -94,12 +107,12 @@ object Scripts {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
def makeCommitTx(ourFinalKey: BinaryData, theirFinalKey: BinaryData, theirDelay: Long, anchorTxId: BinaryData, anchorOutputIndex: Int, revocationHash: BinaryData, channelState: ChannelState): Transaction =
|
||||
def makeCommitTx(ourFinalKey: BinaryData, theirFinalKey: BinaryData, theirDelay: locktime, anchorTxId: BinaryData, anchorOutputIndex: Int, revocationHash: BinaryData, channelState: ChannelState): Transaction =
|
||||
makeCommitTx(inputs = TxIn(OutPoint(anchorTxId, anchorOutputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil, ourFinalKey, theirFinalKey, theirDelay, revocationHash, channelState)
|
||||
|
||||
// this way it is easy to reuse the inputTx of an existing commitmentTx
|
||||
def makeCommitTx(inputs: Seq[TxIn], ourFinalKey: BinaryData, theirFinalKey: BinaryData, theirDelay: Long, revocationHash: BinaryData, channelState: ChannelState): Transaction = {
|
||||
val redeemScript = redeemSecretOrDelay(ourFinalKey, theirDelay, theirFinalKey, revocationHash: BinaryData)
|
||||
def makeCommitTx(inputs: Seq[TxIn], ourFinalKey: BinaryData, theirFinalKey: BinaryData, theirDelay: locktime, revocationHash: BinaryData, channelState: ChannelState): Transaction = {
|
||||
val redeemScript = redeemSecretOrDelay(ourFinalKey, locktime2long_csv(theirDelay), theirFinalKey, revocationHash: BinaryData)
|
||||
|
||||
val tx = Transaction(
|
||||
version = 1,
|
||||
@ -111,12 +124,12 @@ object Scripts {
|
||||
),
|
||||
lockTime = 0)
|
||||
|
||||
val sendOuts = channelState.them.htlcs.map(htlc => {
|
||||
TxOut(htlc.amountMsat, pay2sh(scriptPubKeyHtlcSend(ourFinalKey, theirFinalKey, htlc.amountMsat, htlc.expiry, theirDelay, htlc.rHash, htlc.revocationHash)))
|
||||
})
|
||||
val receiveOuts = channelState.us.htlcs.map(htlc => {
|
||||
TxOut(htlc.amountMsat, pay2sh(scriptPubKeyHtlcReceive(ourFinalKey, theirFinalKey, htlc.amountMsat, htlc.expiry, theirDelay, htlc.rHash, htlc.revocationHash)))
|
||||
})
|
||||
val sendOuts = channelState.them.htlcs.map(htlc =>
|
||||
TxOut(htlc.amountMsat / 1000, pay2sh(scriptPubKeyHtlcSend(ourFinalKey, theirFinalKey, locktime2long_cltv(htlc.expiry), locktime2long_csv(theirDelay), htlc.rHash, revocationHash)))
|
||||
)
|
||||
val receiveOuts = channelState.us.htlcs.map(htlc =>
|
||||
TxOut(htlc.amountMsat / 1000, pay2sh(scriptPubKeyHtlcReceive(ourFinalKey, theirFinalKey, locktime2long_cltv(htlc.expiry), locktime2long_csv(theirDelay), htlc.rHash, revocationHash)))
|
||||
)
|
||||
val tx1 = tx.copy(txOut = tx.txOut ++ sendOuts ++ receiveOuts)
|
||||
permuteOutputs(tx1)
|
||||
}
|
||||
|
@ -7,7 +7,9 @@ import akka.io.Tcp.{Received, Write}
|
||||
import akka.util.ByteString
|
||||
import com.trueaccord.scalapb.GeneratedMessage
|
||||
import fr.acinq.bitcoin._
|
||||
import fr.acinq.eclair.Globals._
|
||||
import fr.acinq.eclair._
|
||||
import fr.acinq.eclair.blockchain.PollingWatcher
|
||||
import fr.acinq.eclair.channel.{INPUT_NONE, Channel, OurChannelParams, AnchorInput}
|
||||
import fr.acinq.eclair.crypto.LightningCrypto._
|
||||
import fr.acinq.eclair.io.AuthHandler.Secrets
|
||||
@ -72,6 +74,7 @@ class AuthHandler(them: ActorRef, blockchain: ActorRef, our_anchor: Boolean) ext
|
||||
log.info(s"initializing channel actor")
|
||||
val anchorInput_opt = if (our_anchor) Some(AnchorInput(1000000L, OutPoint(Hex.decode("7727730d21428276a4d6b0e16f3a3e6f3a07a07dc67151e6a88d4a8c3e8edb24").reverse, 1), SignData("76a914e093fbc19866b98e0fbc25d79d0ad0f0375170af88ac", Base58Check.decode("cU1YgK56oUKAtV6XXHZeJQjEx1KGXkZS1pGiKpyW4mUyKYFJwWFg")._2))) else None
|
||||
val channel_params = OurChannelParams(Globals.default_locktime, Globals.commit_priv, Globals.final_priv, Globals.default_mindepth, Globals.default_commitfee, "sha-seed".getBytes())
|
||||
val blockchain = context.system.actorOf(Props(new PollingWatcher(bitcoin_client)), name = "blockchain")
|
||||
val channel = context.system.actorOf(Props(new Channel(blockchain, channel_params, anchorInput_opt)), name = "alice")
|
||||
channel ! INPUT_NONE
|
||||
goto(IO_NORMAL) using Normal(channel, s)
|
||||
|
@ -72,12 +72,6 @@ package object eclair {
|
||||
Crypto.encodeSignature(r, s) :+ SIGHASH_ALL.toByte
|
||||
}
|
||||
|
||||
implicit def locktime2long(in: locktime): Long = in match {
|
||||
case locktime(Blocks(blocks)) => blocks
|
||||
// FIXME : we adopt Element's alpha convention while BIP68 is not enabled
|
||||
case locktime(Seconds(seconds)) => 500000000 + seconds
|
||||
}
|
||||
|
||||
@tailrec
|
||||
def memcmp(a: List[Byte], b: List[Byte]): Int = (a, b) match {
|
||||
case (x, y) if (x.length != y.length) => x.length - y.length
|
||||
|
@ -32,7 +32,7 @@ class ClaimReceivedHtlcSpec extends FunSuite {
|
||||
val revokeCommitHash = Crypto.sha256(revokeCommit)
|
||||
}
|
||||
|
||||
val htlcScript = scriptPubKeyHtlcReceive(Alice.finalPubKey, Bob.finalPubKey, 10, 1000, 2000, Bob.revokeCommitHash, Bob.Rhash)
|
||||
val htlcScript = scriptPubKeyHtlcReceive(Alice.finalPubKey, Bob.finalPubKey, 1000, 2000, Bob.revokeCommitHash, Bob.Rhash)
|
||||
|
||||
// this tx sends money to our HTLC
|
||||
val tx = Transaction(
|
||||
|
@ -31,7 +31,7 @@ class ClaimSentHtlcSpec extends FunSuite {
|
||||
val revokeCommitHash = Crypto.sha256(revokeCommit)
|
||||
}
|
||||
|
||||
val htlcScript = scriptPubKeyHtlcSend(Alice.finalPubKey, Bob.finalPubKey, 10, 1000, 2000, Alice.revokeCommitHash, Alice.Rhash)
|
||||
val htlcScript = scriptPubKeyHtlcSend(Alice.finalPubKey, Bob.finalPubKey, 1000, 2000, Alice.revokeCommitHash, Alice.Rhash)
|
||||
|
||||
// this tx sends money to our HTLC
|
||||
val tx = Transaction(
|
||||
|
@ -95,7 +95,7 @@ class ProtocolSpec extends FlatSpec {
|
||||
txIn = TxIn(OutPoint(commitTx, 0), Array.emptyByteArray, 0xffffffffL) :: Nil,
|
||||
txOut = TxOut(10, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(hash160(Bob.finalPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
|
||||
lockTime = 0)
|
||||
val redeemScript = redeemSecretOrDelay(ours.finalKey, theirs.delay, theirs.finalKey, Bob.H)
|
||||
val redeemScript = redeemSecretOrDelay(ours.finalKey, locktime2long_csv(theirs.delay), theirs.finalKey, Bob.H)
|
||||
val sig = Transaction.signInput(tx, 0, Script.write(redeemScript), SIGHASH_ALL, Bob.finalKey)
|
||||
val sigScript = OP_PUSHDATA(sig) :: OP_PUSHDATA(Bob.R) :: OP_PUSHDATA(Script.write(redeemScript)) :: Nil
|
||||
tx.updateSigScript(0, Script.write(sigScript))
|
||||
|
@ -57,7 +57,7 @@ abstract class TestHelper(_system: ActorSystem) extends TestKit(_system) with Im
|
||||
node ! open_channel(ourParams.delay, ourRevocationHash, ourCommitPubKey, ourFinalPubKey, WILL_CREATE_ANCHOR, Some(ourParams.minDepth), ourParams.commitmentFee)
|
||||
val (anchorTx, anchorOutputIndex) = makeAnchorTx(ourCommitPubKey, theirParams.commitPubKey, anchorInput.amount, anchorInput.previousTxOutput, anchorInput.signData)
|
||||
// we fund the channel with the anchor tx, so the money is ours
|
||||
val state = ChannelState(them = ChannelOneSide(0, 0, Seq()), us = ChannelOneSide(anchorInput.amount - ourParams.commitmentFee, 0, Seq()))
|
||||
val state = ChannelState(them = ChannelOneSide(0, 0, Seq()), us = ChannelOneSide(anchorInput.amount - ourParams.commitmentFee, ourParams.commitmentFee, Seq()))
|
||||
// we build our commitment tx, leaving it unsigned
|
||||
val ourCommitTx = makeCommitTx(ourFinalPubKey, theirParams.finalPubKey, theirParams.delay, anchorTx.hash, anchorOutputIndex, ourRevocationHash, state)
|
||||
channelDesc = channelDesc.copy(ourCommitment = Some(Commitment(0, ourCommitTx, state, theirRevocationHash)))
|
||||
@ -98,7 +98,7 @@ abstract class TestHelper(_system: ActorSystem) extends TestKit(_system) with Im
|
||||
node ! open_channel(ourParams.delay, ourRevocationHash, ourCommitPubKey, ourFinalPubKey, WONT_CREATE_ANCHOR, Some(ourParams.minDepth), ourParams.commitmentFee)
|
||||
val their_open_anchor = expectMsgClass(classOf[open_anchor])
|
||||
// we fund the channel with the anchor tx, so the money is ours
|
||||
val state = ChannelState(them = ChannelOneSide(their_open_anchor.amount - ourParams.commitmentFee, 0, Seq()), us = ChannelOneSide(0, 0, Seq()))
|
||||
val state = ChannelState(them = ChannelOneSide(their_open_anchor.amount - ourParams.commitmentFee, ourParams.commitmentFee, Seq()), us = ChannelOneSide(0, 0, Seq()))
|
||||
// we build our commitment tx, leaving it unsigned
|
||||
val ourCommitTx = makeCommitTx(ourFinalPubKey, theirParams.finalPubKey, theirParams.delay, their_open_anchor.txid, their_open_anchor.outputIndex, ourRevocationHash, state)
|
||||
channelDesc = channelDesc.copy(ourCommitment = Some(Commitment(0, ourCommitTx, state, theirRevocationHash)))
|
||||
|
Loading…
Reference in New Issue
Block a user