1
0
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:
pm47 2016-01-26 17:57:36 +01:00
parent 95c4606268
commit 83bfc5ac9f
8 changed files with 37 additions and 27 deletions

View File

@ -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)

View File

@ -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)
}

View File

@ -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)

View File

@ -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

View File

@ -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(

View File

@ -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(

View File

@ -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))

View File

@ -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)))