From 6451bb432a20a983352bde74086629b406926504 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 21 May 2019 14:41:08 +0200 Subject: [PATCH 01/43] Use deterministic key channel path for LOCAL -> REMOTE channel opening --- .../eclair/blockchain/EclairWallet.scala | 2 +- .../bitcoind/BitcoinCoreWallet.scala | 4 ++-- .../electrum/ElectrumEclairWallet.scala | 2 +- .../fr/acinq/eclair/channel/Channel.scala | 2 +- .../main/scala/fr/acinq/eclair/io/Peer.scala | 19 ++++++++++++------- .../scala/fr/acinq/eclair/TestConstants.scala | 10 ++++++++-- .../acinq/eclair/blockchain/TestWallet.scala | 2 +- .../a/WaitForAcceptChannelStateSpec.scala | 2 +- ...itForFundingCreatedInternalStateSpec.scala | 2 +- 9 files changed, 28 insertions(+), 17 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/EclairWallet.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/EclairWallet.scala index a2e2973284..001f814f21 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/EclairWallet.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/EclairWallet.scala @@ -30,7 +30,7 @@ trait EclairWallet { def getFinalAddress: Future[String] - def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long): Future[MakeFundingTxResponse] + def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long, lockUnspent: Boolean = true): Future[MakeFundingTxResponse] /** * Committing *must* include publishing the transaction on the network. diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala index a6a54f4982..caf01fbda9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala @@ -89,7 +89,7 @@ class BitcoinCoreWallet(rpcClient: BitcoinJsonRPCClient)(implicit ec: ExecutionC } } - override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long): Future[MakeFundingTxResponse] = { + override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long, lockUnspent: Boolean = true): Future[MakeFundingTxResponse] = { // partial funding tx val partialFundingTx = Transaction( version = 2, @@ -98,7 +98,7 @@ class BitcoinCoreWallet(rpcClient: BitcoinJsonRPCClient)(implicit ec: ExecutionC lockTime = 0) for { // we ask bitcoin core to add inputs to the funding tx, and use the specified change address - FundTransactionResponse(unsignedFundingTx, _, fee) <- fundTransaction(partialFundingTx, lockUnspents = true, feeRatePerKw) + FundTransactionResponse(unsignedFundingTx, _, fee) <- fundTransaction(partialFundingTx, lockUnspents = lockUnspent, feeRatePerKw) // now let's sign the funding tx SignTransactionResponse(fundingTx, true) <- signTransactionOrUnlock(unsignedFundingTx) // there will probably be a change output, so we need to find which output is ours diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumEclairWallet.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumEclairWallet.scala index ed8dea4e01..10c84f4d71 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumEclairWallet.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumEclairWallet.scala @@ -36,7 +36,7 @@ class ElectrumEclairWallet(val wallet: ActorRef, chainHash: ByteVector32)(implic def getXpub: Future[GetXpubResponse] = (wallet ? GetXpub).mapTo[GetXpubResponse] - override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long): Future[MakeFundingTxResponse] = { + override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long, lockUnspent: Boolean = true): Future[MakeFundingTxResponse] = { val tx = Transaction(version = 2, txIn = Nil, txOut = TxOut(amount, pubkeyScript) :: Nil, lockTime = 0) (wallet ? CompleteTransaction(tx, feeRatePerKw)).mapTo[CompleteTransactionResponse].map(response => response match { case CompleteTransactionResponse(tx1, fee1, None) => MakeFundingTxResponse(tx1, 0, fee1) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 2935da2c70..d28484802a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -1704,7 +1704,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } def handleCommandError(cause: Throwable, cmd: Command) = { - log.error(s"${cause.getMessage} while processing cmd=${cmd.getClass.getSimpleName} in state=$stateName") + log.error(s"${cause.getMessage} while processing cmd=${cmd.getClass.getSimpleName} in state=$stateName", error) cause match { case _: ChannelException => () case _ => log.error(cause, s"msg=$cmd stateData=$stateData ") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index eadf81caa2..9b343e8614 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -24,7 +24,8 @@ import akka.actor.{ActorRef, FSM, OneForOneStrategy, PoisonPill, Props, Status, import akka.event.Logging.MDC import akka.util.Timeout import fr.acinq.bitcoin.Crypto.PublicKey -import fr.acinq.bitcoin.{ByteVector32, DeterministicWallet, MilliSatoshi, Protocol, Satoshi} +import fr.acinq.bitcoin.DeterministicWallet.KeyPath +import fr.acinq.bitcoin.{ByteVector32, Crypto, DeterministicWallet, MilliSatoshi, Protocol, Satoshi, TxIn} import fr.acinq.eclair.blockchain.EclairWallet import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.TransportHandler @@ -36,6 +37,7 @@ import scodec.Attempt import scodec.bits.ByteVector import scala.compat.Platform +import scala.concurrent.Await import scala.concurrent.duration._ import scala.util.Random @@ -46,6 +48,8 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: Actor import Peer._ + implicit val ec = context.system.dispatcher + startWith(INSTANTIATING, Nothing()) when(INSTANTIATING) { @@ -484,7 +488,10 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: Actor def createNewChannel(nodeParams: NodeParams, funder: Boolean, fundingSatoshis: Long, origin_opt: Option[ActorRef]): (ActorRef, LocalParams) = { val defaultFinalScriptPubKey = Helpers.getFinalScriptPubKey(wallet, nodeParams.chainHash) - val localParams = makeChannelParams(nodeParams, defaultFinalScriptPubKey, funder, fundingSatoshis) + val fundingInput = Await.result(wallet.makeFundingTx(defaultFinalScriptPubKey, Satoshi(fundingSatoshis), Globals.feeratesPerKw.get().blocks_6, lockUnspent = false).map { fundingResponse => + fundingResponse.fundingTx.txIn.head + }, 60 seconds) + val localParams = makeChannelParams(nodeParams, defaultFinalScriptPubKey, funder, fundingSatoshis, fundingInput) val channel = spawnChannel(nodeParams, origin_opt) (channel, localParams) } @@ -577,11 +584,9 @@ object Peer { // @formatter:on - def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long): LocalParams = { - val entropy = new Array[Byte](16) - secureRandom.nextBytes(entropy) - val bis = new ByteArrayInputStream(entropy) - val channelKeyPath = DeterministicWallet.KeyPath(Seq(Protocol.uint32(bis, ByteOrder.BIG_ENDIAN), Protocol.uint32(bis, ByteOrder.BIG_ENDIAN), Protocol.uint32(bis, ByteOrder.BIG_ENDIAN), Protocol.uint32(bis, ByteOrder.BIG_ENDIAN))) + def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, fundingInput: TxIn): LocalParams = { + val inputEntropy = Crypto.sha256(fundingInput.outPoint.hash).take(4).toLong(signed = false) + val channelKeyPath = KeyPath(Seq(47, 2, inputEntropy, 0)) makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder, fundingSatoshis, channelKeyPath) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index c807936f55..a006fdec42 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -19,6 +19,7 @@ package fr.acinq.eclair import java.sql.{Connection, DriverManager} import fr.acinq.bitcoin.Crypto.PrivateKey +import fr.acinq.bitcoin.DeterministicWallet.KeyPath import fr.acinq.bitcoin.{Block, ByteVector32, Script} import fr.acinq.eclair.NodeParams.BITCOIND import fr.acinq.eclair.crypto.LocalKeyManager @@ -28,6 +29,7 @@ import fr.acinq.eclair.io.Peer import fr.acinq.eclair.router.RouterConf import fr.acinq.eclair.wire.{Color, NodeAddress} import scodec.bits.ByteVector + import scala.concurrent.duration._ /** @@ -103,7 +105,9 @@ object TestConstants { nodeParams = nodeParams, defaultFinalScriptPubKey = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32, compressed = true).publicKey)), isFunder = true, - fundingSatoshis).copy( + fundingSatoshis, + KeyPath(Seq(1, 2, 3, 4L)) + ).copy( channelReserveSatoshis = 10000 // Bob will need to keep that much satoshis as direct payment ) } @@ -167,7 +171,9 @@ object TestConstants { nodeParams = nodeParams, defaultFinalScriptPubKey = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32, compressed = true).publicKey)), isFunder = false, - fundingSatoshis).copy( + fundingSatoshis, + KeyPath(Seq(1, 2, 3, 4L)) + ).copy( channelReserveSatoshis = 20000 // Alice will need to keep that much satoshis as direct payment ) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/TestWallet.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/TestWallet.scala index 05136c672f..62fbc39c8d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/TestWallet.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/TestWallet.scala @@ -33,7 +33,7 @@ class TestWallet extends EclairWallet { override def getFinalAddress: Future[String] = Future.successful("2MsRZ1asG6k94m6GYUufDGaZJMoJ4EV5JKs") - override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long): Future[MakeFundingTxResponse] = + override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long, lockUnspent: Boolean = true): Future[MakeFundingTxResponse] = Future.successful(TestWallet.makeDummyFundingTx(pubkeyScript, amount, feeRatePerKw)) override def commit(tx: Transaction): Future[Boolean] = Future.successful(true) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala index b71fd9de45..17e9c8418b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala @@ -41,7 +41,7 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp override def withFixture(test: OneArgTest): Outcome = { val noopWallet = new TestWallet { - override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long): Future[MakeFundingTxResponse] = Promise[MakeFundingTxResponse].future // will never be completed + override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long, lockUnspent: Boolean = true): Future[MakeFundingTxResponse] = Promise[MakeFundingTxResponse].future // will never be completed } val setup = if (test.tags.contains("mainnet")) { init(TestConstants.Alice.nodeParams.copy(chainHash = Block.LivenetGenesisBlock.hash), TestConstants.Bob.nodeParams.copy(chainHash = Block.LivenetGenesisBlock.hash), wallet = noopWallet) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala index 2e3edfe5fa..f8e6d53127 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala @@ -40,7 +40,7 @@ class WaitForFundingCreatedInternalStateSpec extends TestkitBaseClass with State override def withFixture(test: OneArgTest): Outcome = { val noopWallet = new TestWallet { - override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long): Future[MakeFundingTxResponse] = Promise[MakeFundingTxResponse].future // will never be completed + override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long, lockUnspent: Boolean = true): Future[MakeFundingTxResponse] = Promise[MakeFundingTxResponse].future // will never be completed } val setup = init(wallet = noopWallet) import setup._ From 6c3c306bbcdec180a7149a6f93f0c1a589444871 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 21 May 2019 15:07:47 +0200 Subject: [PATCH 02/43] Factor out function to compute the deterministic keypath from the funding input --- eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index 9b343e8614..aa029d489f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -584,9 +584,13 @@ object Peer { // @formatter:on - def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, fundingInput: TxIn): LocalParams = { + def makeChannelKeyPathFromOutpoint(fundingInput: TxIn): KeyPath = { val inputEntropy = Crypto.sha256(fundingInput.outPoint.hash).take(4).toLong(signed = false) - val channelKeyPath = KeyPath(Seq(47, 2, inputEntropy, 0)) + KeyPath(Seq(47, 2, inputEntropy, 0)) + } + + def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, fundingInput: TxIn): LocalParams = { + val channelKeyPath = makeChannelKeyPathFromOutpoint(fundingInput) makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder, fundingSatoshis, channelKeyPath) } From 9436c78efa6fa8bc48e79a4bbca8314733a6b405 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 29 May 2019 17:16:41 +0200 Subject: [PATCH 03/43] WIP funder channel opening flow --- .../eclair/blockchain/EclairWallet.scala | 3 + .../bitcoind/BitcoinCoreWallet.scala | 5 + .../electrum/ElectrumEclairWallet.scala | 4 + .../blockchain/electrum/ElectrumWallet.scala | 3 + .../fr/acinq/eclair/channel/Channel.scala | 94 +++++++++++++++++-- .../acinq/eclair/channel/ChannelTypes.scala | 6 +- .../main/scala/fr/acinq/eclair/io/Peer.scala | 53 +++-------- .../scala/fr/acinq/eclair/TestConstants.scala | 5 +- 8 files changed, 120 insertions(+), 53 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/EclairWallet.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/EclairWallet.scala index 001f814f21..550edc4daa 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/EclairWallet.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/EclairWallet.scala @@ -32,6 +32,8 @@ trait EclairWallet { def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long, lockUnspent: Boolean = true): Future[MakeFundingTxResponse] + def signTransactionComplete(tx: Transaction): Future[Transaction] + /** * Committing *must* include publishing the transaction on the network. * @@ -67,3 +69,4 @@ trait EclairWallet { } final case class MakeFundingTxResponse(fundingTx: Transaction, fundingTxOutputIndex: Int, fee: Satoshi) +final case class SignFundingTxResponse(fundingTx: Transaction, fundingTxOutputIndex: Int, fee: Satoshi) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala index caf01fbda9..409255942f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWallet.scala @@ -61,6 +61,11 @@ class BitcoinCoreWallet(rpcClient: BitcoinJsonRPCClient)(implicit ec: ExecutionC def signTransaction(tx: Transaction): Future[SignTransactionResponse] = signTransaction(Transaction.write(tx).toHex) + override def signTransactionComplete(tx: Transaction): Future[Transaction] = signTransaction(tx).map { + case SignTransactionResponse(signedTx, true) => signedTx + case _ => throw new IllegalStateException("Signed transaction is not complete") + } + def getTransaction(txid: ByteVector32): Future[Transaction] = rpcClient.invoke("getrawtransaction", txid.toString()) collect { case JString(hex) => Transaction.read(hex) } def publishTransaction(tx: Transaction)(implicit ec: ExecutionContext): Future[String] = publishTransaction(Transaction.write(tx).toHex) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumEclairWallet.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumEclairWallet.scala index 10c84f4d71..fcef53d3be 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumEclairWallet.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumEclairWallet.scala @@ -36,6 +36,10 @@ class ElectrumEclairWallet(val wallet: ActorRef, chainHash: ByteVector32)(implic def getXpub: Future[GetXpubResponse] = (wallet ? GetXpub).mapTo[GetXpubResponse] + override def signTransactionComplete(tx: Transaction): Future[Transaction] = { + (wallet ? SignTransaction(tx)).mapTo[SignTransactionResponse].map(_.tx) + } + override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long, lockUnspent: Boolean = true): Future[MakeFundingTxResponse] = { val tx = Transaction(version = 2, txIn = Nil, txOut = TxOut(amount, pubkeyScript) :: Nil, lockTime = 0) (wallet ? CompleteTransaction(tx, feeRatePerKw)).mapTo[CompleteTransactionResponse].map(response => response match { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWallet.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWallet.scala index 04ea4a1cf3..1541204a4c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWallet.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWallet.scala @@ -548,6 +548,9 @@ object ElectrumWallet { case class IsDoubleSpent(tx: Transaction) extends Request case class IsDoubleSpentResponse(tx: Transaction, isDoubleSpent: Boolean) extends Response + case class SignTransaction(tx: Transaction) extends Request + case class SignTransactionResponse(tx: Transaction) extends Response + sealed trait WalletEvent /** * diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index d28484802a..b36172785f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -20,11 +20,12 @@ import akka.actor.{ActorRef, FSM, OneForOneStrategy, Props, Status, SupervisorSt import akka.event.Logging.MDC import akka.pattern.pipe import fr.acinq.bitcoin.Crypto.{PublicKey, Scalar, sha256} +import fr.acinq.bitcoin.DeterministicWallet.KeyPath import fr.acinq.bitcoin._ import fr.acinq.eclair._ import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.channel.Helpers.{Closing, Funding} -import fr.acinq.eclair.crypto.{ShaChain, Sphinx} +import fr.acinq.eclair.crypto.{KeyManager, ShaChain, Sphinx} import fr.acinq.eclair.io.Peer import fr.acinq.eclair.payment._ import fr.acinq.eclair.router.Announcements @@ -33,7 +34,7 @@ import fr.acinq.eclair.wire.{ChannelReestablish, _} import scodec.bits.ByteVector import scala.compat.Platform -import scala.concurrent.ExecutionContext +import scala.concurrent.{Await, ExecutionContext} import scala.concurrent.duration._ import scala.util.{Failure, Success, Try} @@ -91,6 +92,54 @@ object Channel { case class RemoteError(e: Error) extends ChannelError // @formatter:on + def makeChannelKeyPathFromInput(fundingInput: TxIn): KeyPath = { + val inputEntropy = Crypto.sha256(fundingInput.outPoint.hash).take(4).toLong(signed = false) + KeyPath(Seq(47, 2, inputEntropy, 0)) + } + + def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, fundingInput: TxIn): LocalParams = { + val channelKeyPath = makeChannelKeyPathFromInput(fundingInput) + makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder, fundingSatoshis, channelKeyPath) + } + + def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, channelKeyPath: DeterministicWallet.KeyPath): LocalParams = { + LocalParams( + nodeParams.nodeId, + channelKeyPath, + dustLimitSatoshis = nodeParams.dustLimitSatoshis, + maxHtlcValueInFlightMsat = nodeParams.maxHtlcValueInFlightMsat, + channelReserveSatoshis = Math.max((nodeParams.reserveToFundingRatio * fundingSatoshis).toLong, nodeParams.dustLimitSatoshis), // BOLT #2: make sure that our reserve is above our dust limit + htlcMinimumMsat = nodeParams.htlcMinimumMsat, + toSelfDelay = nodeParams.toRemoteDelayBlocks, // we choose their delay + maxAcceptedHtlcs = nodeParams.maxAcceptedHtlcs, + defaultFinalScriptPubKey = defaultFinalScriptPubKey, + isFunder = isFunder, + globalFeatures = nodeParams.globalFeatures, + localFeatures = nodeParams.localFeatures) + } + + // creates a funding transaction without signatures, this is used to lock an output and derive the channel keypath. + def createFundingWithNoSignatures(wallet: EclairWallet, keyManager: KeyManager, fundingSatoshis: Satoshi, fundingTxFeeratePerKw: Long): MakeFundingTxResponse = { + // this LOCKS the outpoints that we'll use in the funding transaction, the resulting TX is NOT signed + // later in the process we change the output scriptPubkey and ask the wallet to sign + val tempKeyPath = KeyPath(Seq(1, 2, 3, 4L)) + val scriptPubKey = Script.write(Script.pay2wsh(Scripts.multiSig2of2(keyManager.fundingPublicKey(tempKeyPath).publicKey, keyManager.fundingPublicKey(tempKeyPath).publicKey))) + val funding = Await.result( + wallet.makeFundingTx(pubkeyScript = scriptPubKey, fundingSatoshis, fundingTxFeeratePerKw), + 10 seconds // TODO change? + ) + + // remove our invalid sig + val fundingNoSignatures = funding.copy( fundingTx = funding.fundingTx.copy( + txIn = funding.fundingTx.txIn.map(_.copy( + signatureScript = ByteVector.empty, + witness = ScriptWitness.empty + )) + )) + + fundingNoSignatures + } + } class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId: PublicKey, blockchain: ActorRef, router: ActorRef, relayer: ActorRef, origin_opt: Option[ActorRef] = None)(implicit ec: ExecutionContext = ExecutionContext.Implicits.global) extends FSM[State, Data] with FSMDiagnosticActorLogging[State, Data] { @@ -146,9 +195,17 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId startWith(WAIT_FOR_INIT_INTERNAL, Nothing) when(WAIT_FOR_INIT_INTERNAL)(handleExceptions { - case Event(initFunder@INPUT_INIT_FUNDER(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, _, localParams, remote, _, channelFlags), Nothing) => + case Event(initFunder@INPUT_INIT_FUNDER(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, remote, _, channelFlags), Nothing) => context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, true, temporaryChannelId)) forwarder ! remote + + // this LOCKS the outpoints that we'll use in the funding transaction, the resulting TX is NOT signed + // later in the process we change the output scriptPubkey and ask the wallet to sign + val funding = createFundingWithNoSignatures(wallet, keyManager, Satoshi(fundingSatoshis), fundingTxFeeratePerKw) + val defaultFinalScriptPubKey = Helpers.getFinalScriptPubKey(wallet, nodeParams.chainHash) + val localParams = makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder = false, fundingSatoshis, funding.fundingTx.txIn.head) + log.info(s"using localParams=$localParams") + val open = OpenChannel(nodeParams.chainHash, temporaryChannelId = temporaryChannelId, fundingSatoshis = fundingSatoshis, @@ -167,7 +224,20 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId htlcBasepoint = keyManager.htlcPoint(localParams.channelKeyPath).publicKey, firstPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 0), channelFlags = channelFlags) - goto(WAIT_FOR_ACCEPT_CHANNEL) using DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder, open) sending open + + val nextStateData = INPUT_INIT_FUNDER_WITH_PARAMS( + initFunder.temporaryChannelId, + initFunder.fundingSatoshis, + initFunder.pushMsat, + initFunder.initialFeeratePerKw, + initFunder.fundingTxFeeratePerKw, + localParams, + initFunder.remote, + initFunder.remoteInit, + initFunder.channelFlags + ) + + goto(WAIT_FOR_ACCEPT_CHANNEL) using DATA_WAIT_FOR_ACCEPT_CHANNEL(nextStateData, open, funding) sending open case Event(inputFundee@INPUT_INIT_FUNDEE(_, localParams, remote, _), Nothing) if !localParams.isFunder => forwarder ! remote @@ -317,7 +387,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId }) when(WAIT_FOR_ACCEPT_CHANNEL)(handleExceptions { - case Event(accept: AcceptChannel, d@DATA_WAIT_FOR_ACCEPT_CHANNEL(INPUT_INIT_FUNDER(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, localParams, _, remoteInit, _), open)) => + case Event(accept: AcceptChannel, d@DATA_WAIT_FOR_ACCEPT_CHANNEL(INPUT_INIT_FUNDER_WITH_PARAMS(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, _, localParams, _, remoteInit, _), open, funding)) => log.info(s"received AcceptChannel=$accept") Try(Helpers.validateParamsFunder(nodeParams, open, accept)) match { case Failure(t) => handleLocalError(t, d, Some(accept)) @@ -339,9 +409,17 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId globalFeatures = remoteInit.globalFeatures, localFeatures = remoteInit.localFeatures) log.debug(s"remote params: $remoteParams") + val localFundingPubkey = keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey, remoteParams.fundingPubKey))) - wallet.makeFundingTx(fundingPubkeyScript, Satoshi(fundingSatoshis), fundingTxFeeratePerKw).pipeTo(self) + + // update the funding TX with the actual scriptPubkey + val finalFundingTx = funding.fundingTx.copy( + txOut = TxOut(Satoshi(fundingSatoshis), fundingPubkeyScript) :: Nil + ) + // add our signature to the funding transaction + wallet.signTransactionComplete(finalFundingTx).map(SignFundingTxResponse(_, funding.fundingTxOutputIndex, funding.fee)).pipeTo(self) + goto(WAIT_FOR_FUNDING_INTERNAL) using DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, accept.firstPerCommitmentPoint, open) } @@ -363,7 +441,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId }) when(WAIT_FOR_FUNDING_INTERNAL)(handleExceptions { - case Event(MakeFundingTxResponse(fundingTx, fundingTxOutputIndex, fundingTxFee), DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, remoteFirstPerCommitmentPoint, open)) => + case Event(SignFundingTxResponse(fundingTx, fundingTxOutputIndex, fundingTxFee), DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, remoteFirstPerCommitmentPoint, open)) => // let's create the first commitment tx that spends the yet uncommitted funding tx val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.maxFeerateMismatch) require(fundingTx.txOut(fundingTxOutputIndex).publicKeyScript == localCommitTx.input.txOut.publicKeyScript, s"pubkey script mismatch!") @@ -1704,7 +1782,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } def handleCommandError(cause: Throwable, cmd: Command) = { - log.error(s"${cause.getMessage} while processing cmd=${cmd.getClass.getSimpleName} in state=$stateName", error) + log.error(s"${cause.getMessage} while processing cmd=${cmd.getClass.getSimpleName} in state=$stateName", cause) cause match { case _: ChannelException => () case _ => log.error(cause, s"msg=$cmd stateData=$stateData ") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala index 5cab113be6..b13021dab7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala @@ -21,6 +21,7 @@ import java.util.UUID import akka.actor.ActorRef import fr.acinq.bitcoin.Crypto.{Point, PublicKey} import fr.acinq.bitcoin.{ByteVector32, DeterministicWallet, OutPoint, Satoshi, Transaction} +import fr.acinq.eclair.blockchain.MakeFundingTxResponse import fr.acinq.eclair.crypto.Sphinx import fr.acinq.eclair.transactions.CommitmentSpec import fr.acinq.eclair.transactions.Transactions.CommitTx @@ -77,7 +78,8 @@ case object ERR_INFORMATION_LEAK extends State 8888888888 Y8P 8888888888 888 Y888 888 "Y8888P" */ -case class INPUT_INIT_FUNDER(temporaryChannelId: ByteVector32, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, fundingTxFeeratePerKw: Long, localParams: LocalParams, remote: ActorRef, remoteInit: Init, channelFlags: Byte) +case class INPUT_INIT_FUNDER(temporaryChannelId: ByteVector32, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, fundingTxFeeratePerKw: Long, remote: ActorRef, remoteInit: Init, channelFlags: Byte) +case class INPUT_INIT_FUNDER_WITH_PARAMS(temporaryChannelId: ByteVector32, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, fundingTxFeeratePerKw: Long, localParams: LocalParams, remote: ActorRef, remoteInit: Init, channelFlags: Byte) case class INPUT_INIT_FUNDEE(temporaryChannelId: ByteVector32, localParams: LocalParams, remote: ActorRef, remoteInit: Init) case object INPUT_CLOSE_COMPLETE_TIMEOUT // when requesting a mutual close, we wait for as much as this timeout, then unilateral close case object INPUT_DISCONNECTED @@ -149,7 +151,7 @@ case class RemoteCommitPublished(commitTx: Transaction, claimMainOutputTx: Optio case class RevokedCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[Transaction], mainPenaltyTx: Option[Transaction], htlcPenaltyTxs: List[Transaction], claimHtlcDelayedPenaltyTxs: List[Transaction], irrevocablySpent: Map[OutPoint, ByteVector32]) final case class DATA_WAIT_FOR_OPEN_CHANNEL(initFundee: INPUT_INIT_FUNDEE) extends Data -final case class DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder: INPUT_INIT_FUNDER, lastSent: OpenChannel) extends Data +final case class DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder: INPUT_INIT_FUNDER_WITH_PARAMS, lastSent: OpenChannel, unsignedFundingTx: MakeFundingTxResponse) extends Data final case class DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, remoteFirstPerCommitmentPoint: Point, lastSent: OpenChannel) extends Data final case class DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, remoteFirstPerCommitmentPoint: Point, channelFlags: Byte, lastSent: AcceptChannel) extends Data final case class DATA_WAIT_FOR_FUNDING_SIGNED(channelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingTx: Transaction, fundingTxFee: Satoshi, localSpec: CommitmentSpec, localCommitTx: CommitTx, remoteCommit: RemoteCommit, channelFlags: Byte, lastSent: FundingCreated) extends Data diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index aa029d489f..0db46ab291 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -259,23 +259,23 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: Actor stay case Event(c: Peer.OpenChannel, d: ConnectedData) => - val (channel, localParams) = createNewChannel(nodeParams, funder = true, c.fundingSatoshis.toLong, origin_opt = Some(sender)) + val channel = spawnChannel(nodeParams, origin_opt = Some(sender)) c.timeout_opt.map(openTimeout => context.system.scheduler.scheduleOnce(openTimeout.duration, channel, Channel.TickChannelOpenTimeout)(context.dispatcher)) val temporaryChannelId = randomBytes32 val channelFeeratePerKw = Globals.feeratesPerKw.get.blocks_2 val fundingTxFeeratePerKw = c.fundingTxFeeratePerKw_opt.getOrElse(Globals.feeratesPerKw.get.blocks_6) - log.info(s"requesting a new channel with fundingSatoshis=${c.fundingSatoshis}, pushMsat=${c.pushMsat} and fundingFeeratePerByte=${c.fundingTxFeeratePerKw_opt} temporaryChannelId=$temporaryChannelId localParams=$localParams") - channel ! INPUT_INIT_FUNDER(temporaryChannelId, c.fundingSatoshis.amount, c.pushMsat.amount, channelFeeratePerKw, fundingTxFeeratePerKw, localParams, d.transport, d.remoteInit, c.channelFlags.getOrElse(nodeParams.channelFlags)) + log.info(s"requesting a new channel with fundingSatoshis=${c.fundingSatoshis}, pushMsat=${c.pushMsat} and fundingFeeratePerByte=${c.fundingTxFeeratePerKw_opt} temporaryChannelId=$temporaryChannelId") + channel ! INPUT_INIT_FUNDER(temporaryChannelId, c.fundingSatoshis.amount, c.pushMsat.amount, channelFeeratePerKw, fundingTxFeeratePerKw, d.transport, d.remoteInit, c.channelFlags.getOrElse(nodeParams.channelFlags)) stay using d.copy(channels = d.channels + (TemporaryChannelId(temporaryChannelId) -> channel)) case Event(msg: wire.OpenChannel, d: ConnectedData) => d.transport ! TransportHandler.ReadAck(msg) d.channels.get(TemporaryChannelId(msg.temporaryChannelId)) match { case None => - val (channel, localParams) = createNewChannel(nodeParams, funder = false, fundingSatoshis = msg.fundingSatoshis, origin_opt = None) + val channel = spawnChannel(nodeParams, origin_opt = None) val temporaryChannelId = msg.temporaryChannelId - log.info(s"accepting a new channel to $remoteNodeId temporaryChannelId=$temporaryChannelId localParams=$localParams") - channel ! INPUT_INIT_FUNDEE(temporaryChannelId, localParams, d.transport, d.remoteInit) + log.info(s"accepting a new channel to $remoteNodeId temporaryChannelId=$temporaryChannelId") + channel ! INPUT_INIT_FUNDEE(temporaryChannelId, ???, d.transport, d.remoteInit) channel ! msg stay using d.copy(channels = d.channels + (TemporaryChannelId(temporaryChannelId) -> channel)) case Some(_) => @@ -486,15 +486,12 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: Actor case DISCONNECTED -> _ if nodeParams.autoReconnect && stateData.address_opt.isDefined => cancelTimer(RECONNECT_TIMER) } - def createNewChannel(nodeParams: NodeParams, funder: Boolean, fundingSatoshis: Long, origin_opt: Option[ActorRef]): (ActorRef, LocalParams) = { - val defaultFinalScriptPubKey = Helpers.getFinalScriptPubKey(wallet, nodeParams.chainHash) - val fundingInput = Await.result(wallet.makeFundingTx(defaultFinalScriptPubKey, Satoshi(fundingSatoshis), Globals.feeratesPerKw.get().blocks_6, lockUnspent = false).map { fundingResponse => - fundingResponse.fundingTx.txIn.head - }, 60 seconds) - val localParams = makeChannelParams(nodeParams, defaultFinalScriptPubKey, funder, fundingSatoshis, fundingInput) - val channel = spawnChannel(nodeParams, origin_opt) - (channel, localParams) - } +// def createNewChannel(nodeParams: NodeParams, funder: Boolean, fundingSatoshis: Long, origin_opt: Option[ActorRef]): ActorRef = { +// val defaultFinalScriptPubKey = Helpers.getFinalScriptPubKey(wallet, nodeParams.chainHash) +// // val localParams = makeChannelParams(nodeParams, defaultFinalScriptPubKey, funder, fundingSatoshis, fundingInput) +// val channel = spawnChannel(nodeParams, origin_opt) +// channel +// } def spawnChannel(nodeParams: NodeParams, origin_opt: Option[ActorRef]): ActorRef = { val channel = context.actorOf(Channel.props(nodeParams, wallet, remoteNodeId, watcher, router, relayer, origin_opt)) @@ -584,32 +581,6 @@ object Peer { // @formatter:on - def makeChannelKeyPathFromOutpoint(fundingInput: TxIn): KeyPath = { - val inputEntropy = Crypto.sha256(fundingInput.outPoint.hash).take(4).toLong(signed = false) - KeyPath(Seq(47, 2, inputEntropy, 0)) - } - - def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, fundingInput: TxIn): LocalParams = { - val channelKeyPath = makeChannelKeyPathFromOutpoint(fundingInput) - makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder, fundingSatoshis, channelKeyPath) - } - - def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, channelKeyPath: DeterministicWallet.KeyPath): LocalParams = { - LocalParams( - nodeParams.nodeId, - channelKeyPath, - dustLimitSatoshis = nodeParams.dustLimitSatoshis, - maxHtlcValueInFlightMsat = nodeParams.maxHtlcValueInFlightMsat, - channelReserveSatoshis = Math.max((nodeParams.reserveToFundingRatio * fundingSatoshis).toLong, nodeParams.dustLimitSatoshis), // BOLT #2: make sure that our reserve is above our dust limit - htlcMinimumMsat = nodeParams.htlcMinimumMsat, - toSelfDelay = nodeParams.toRemoteDelayBlocks, // we choose their delay - maxAcceptedHtlcs = nodeParams.maxAcceptedHtlcs, - defaultFinalScriptPubKey = defaultFinalScriptPubKey, - isFunder = isFunder, - globalFeatures = nodeParams.globalFeatures, - localFeatures = nodeParams.localFeatures) - } - /** * Peer may want to filter announcements based on timestamp * diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index a006fdec42..08f72962e9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -22,6 +22,7 @@ import fr.acinq.bitcoin.Crypto.PrivateKey import fr.acinq.bitcoin.DeterministicWallet.KeyPath import fr.acinq.bitcoin.{Block, ByteVector32, Script} import fr.acinq.eclair.NodeParams.BITCOIND +import fr.acinq.eclair.channel.Channel import fr.acinq.eclair.crypto.LocalKeyManager import fr.acinq.eclair.db._ import fr.acinq.eclair.db.sqlite._ @@ -101,7 +102,7 @@ object TestConstants { maxPaymentAttempts = 5 ) - def channelParams = Peer.makeChannelParams( + def channelParams = Channel.makeChannelParams( nodeParams = nodeParams, defaultFinalScriptPubKey = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32, compressed = true).publicKey)), isFunder = true, @@ -167,7 +168,7 @@ object TestConstants { maxPaymentAttempts = 5 ) - def channelParams = Peer.makeChannelParams( + def channelParams = Channel.makeChannelParams( nodeParams = nodeParams, defaultFinalScriptPubKey = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32, compressed = true).publicKey)), isFunder = false, From 3fd125eb93e315bff98795fff680d3943e0f9602 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 31 May 2019 15:34:02 +0200 Subject: [PATCH 04/43] WIP funder channel opening flow --- .../fr/acinq/eclair/channel/Channel.scala | 30 +++++++++++++------ .../eclair/wire/LightningMessageTypes.scala | 2 ++ .../acinq/eclair/blockchain/TestWallet.scala | 7 ++++- .../electrum/ElectrumWalletSpec.scala | 3 +- .../fr/acinq/eclair/channel/FuzzySpec.scala | 2 +- .../acinq/eclair/channel/ThroughputSpec.scala | 2 +- .../states/StateTestsHelperMethods.scala | 2 +- .../a/WaitForAcceptChannelStateSpec.scala | 12 ++++---- .../a/WaitForOpenChannelStateSpec.scala | 2 +- ...itForFundingCreatedInternalStateSpec.scala | 2 +- .../b/WaitForFundingCreatedStateSpec.scala | 2 +- .../b/WaitForFundingSignedStateSpec.scala | 2 +- .../c/WaitForFundingConfirmedStateSpec.scala | 2 +- .../c/WaitForFundingLockedStateSpec.scala | 2 +- .../interop/rustytests/RustyTestsSpec.scala | 2 +- 15 files changed, 46 insertions(+), 28 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index b36172785f..19f6817174 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -34,8 +34,8 @@ import fr.acinq.eclair.wire.{ChannelReestablish, _} import scodec.bits.ByteVector import scala.compat.Platform -import scala.concurrent.{Await, ExecutionContext} import scala.concurrent.duration._ +import scala.concurrent.{Await, ExecutionContext} import scala.util.{Failure, Success, Try} @@ -130,14 +130,12 @@ object Channel { ) // remove our invalid sig - val fundingNoSignatures = funding.copy( fundingTx = funding.fundingTx.copy( + funding.copy(fundingTx = funding.fundingTx.copy( txIn = funding.fundingTx.txIn.map(_.copy( signatureScript = ByteVector.empty, witness = ScriptWitness.empty )) )) - - fundingNoSignatures } } @@ -201,9 +199,9 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId // this LOCKS the outpoints that we'll use in the funding transaction, the resulting TX is NOT signed // later in the process we change the output scriptPubkey and ask the wallet to sign - val funding = createFundingWithNoSignatures(wallet, keyManager, Satoshi(fundingSatoshis), fundingTxFeeratePerKw) + val funding = createFundingWithNoSignatures(wallet, keyManager, Satoshi(fundingSatoshis), fundingTxFeeratePerKw) // FIXME blocking call! val defaultFinalScriptPubKey = Helpers.getFinalScriptPubKey(wallet, nodeParams.chainHash) - val localParams = makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder = false, fundingSatoshis, funding.fundingTx.txIn.head) + val localParams = makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder = true, fundingSatoshis, funding.fundingTx.txIn.head) log.info(s"using localParams=$localParams") val open = OpenChannel(nodeParams.chainHash, @@ -413,10 +411,17 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId val localFundingPubkey = keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey, remoteParams.fundingPubKey))) - // update the funding TX with the actual scriptPubkey - val finalFundingTx = funding.fundingTx.copy( - txOut = TxOut(Satoshi(fundingSatoshis), fundingPubkeyScript) :: Nil + // update the funding TX with the new scriptPubkey + val finalFundingTx = funding.fundingTx.copy ( + txOut = funding.fundingTx.txOut.zipWithIndex.map { + case (_, index) if index == funding.fundingTxOutputIndex => TxOut(Satoshi(fundingSatoshis), fundingPubkeyScript) + case (out, _) => out + } ) + + log.info(s"### PRE ### FINAL_FUNDING_TX=${finalFundingTx.bin.toHex}") + + // add our signature to the funding transaction wallet.signTransactionComplete(finalFundingTx).map(SignFundingTxResponse(_, funding.fundingTxOutputIndex, funding.fee)).pipeTo(self) @@ -453,6 +458,13 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId fundingOutputIndex = fundingTxOutputIndex, signature = localSigOfRemoteTx ) + + log.info(s"LOCAL_COMMIT_INPUT=${localCommitTx.input}") + log.info(s"REMOTE_COMMIT_INPUT=${remoteCommitTx.input}") + log.info(s"LOCAL_COMMIT_TX=${localCommitTx.tx.bin.toHex}") + log.info(s"REMOTE_COMMIT_TX=${remoteCommitTx.tx.bin.toHex}") + log.info(s"LOCAL_SIG_OF_REMOTE=$localSigOfRemoteTx") + val channelId = toLongId(fundingTx.hash, fundingTxOutputIndex) context.parent ! ChannelIdAssigned(self, remoteNodeId, temporaryChannelId, channelId) // we notify the peer asap so it knows how to route messages context.system.eventStream.publish(ChannelIdAssigned(self, remoteNodeId, temporaryChannelId, channelId)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala index 0a629ac936..729853775e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala @@ -50,6 +50,8 @@ case class Init(globalFeatures: ByteVector, case class Error(channelId: ByteVector32, data: ByteVector) extends SetupMessage with HasChannelId { def toAscii: String = if (fr.acinq.eclair.isAsciiPrintable(data)) new String(data.toArray, StandardCharsets.US_ASCII) else "n/a" + + override def toString: String = toAscii } object Error { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/TestWallet.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/TestWallet.scala index 62fbc39c8d..f845789bbe 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/TestWallet.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/TestWallet.scala @@ -16,7 +16,8 @@ package fr.acinq.eclair.blockchain -import fr.acinq.bitcoin.{ByteVector32, Crypto, OP_PUSHDATA, OutPoint, Satoshi, Script, Transaction, TxIn, TxOut} +import fr.acinq.bitcoin.{ByteVector32, Crypto, OP_PUSHDATA, OutPoint, Satoshi, Script, ScriptWitness, Transaction, TxIn, TxOut} +import fr.acinq.eclair.randomBytes import scodec.bits.ByteVector import scala.concurrent.Future @@ -36,6 +37,10 @@ class TestWallet extends EclairWallet { override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long, lockUnspent: Boolean = true): Future[MakeFundingTxResponse] = Future.successful(TestWallet.makeDummyFundingTx(pubkeyScript, amount, feeRatePerKw)) + override def signTransactionComplete(tx: Transaction): Future[Transaction] = Future.successful { + tx.updateWitness(0, ScriptWitness(Seq(randomBytes(73)))) + } + override def commit(tx: Transaction): Future[Boolean] = Future.successful(true) override def rollback(tx: Transaction): Future[Boolean] = { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSpec.scala index 3d53eb73e7..96a9bece53 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSpec.scala @@ -28,12 +28,12 @@ import fr.acinq.eclair.blockchain.bitcoind.BitcoinCoreWallet.{FundTransactionRes import fr.acinq.eclair.blockchain.bitcoind.{BitcoinCoreWallet, BitcoindService} import fr.acinq.eclair.blockchain.electrum.ElectrumClient.{BroadcastTransaction, BroadcastTransactionResponse, SSL} import fr.acinq.eclair.blockchain.electrum.ElectrumClientPool.ElectrumServerAddress +import fr.acinq.eclair.blockchain.electrum.ElectrumWallet._ import fr.acinq.eclair.blockchain.electrum.db.sqlite.SqliteWalletDb import grizzled.slf4j.Logging import org.json4s.JsonAST.{JDecimal, JString, JValue} import org.scalatest.{BeforeAndAfterAll, FunSuiteLike} import scodec.bits.ByteVector - import scala.concurrent.Await import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ @@ -41,7 +41,6 @@ import scala.concurrent.duration._ class ElectrumWalletSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with BitcoindService with ElectrumxService with BeforeAndAfterAll with Logging { - import ElectrumWallet._ val entropy = ByteVector32(ByteVector.fill(32)(1)) val mnemonics = MnemonicCode.toMnemonics(entropy) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala index 48a452e8ef..c06dc45b1e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala @@ -67,7 +67,7 @@ class FuzzySpec extends TestkitBaseClass with StateTestsHelperMethods with Loggi relayerA ! alice relayerB ! bob // no announcements - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, Alice.channelParams, pipe, bobInit, channelFlags = 0x00.toByte) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, pipe, bobInit, channelFlags = 0x00.toByte) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, pipe, aliceInit) pipe ! (alice, bob) alice2blockchain.expectMsgType[WatchSpent] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ThroughputSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ThroughputSpec.scala index 18aff515ef..981187c56a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ThroughputSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ThroughputSpec.scala @@ -71,7 +71,7 @@ class ThroughputSpec extends FunSuite { val bob = system.actorOf(Channel.props(Bob.nodeParams, wallet, Alice.nodeParams.nodeId, blockchain, ???, relayerB, None), "b") val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, Alice.channelParams, pipe, bobInit, ChannelFlags.Empty) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, pipe, bobInit, ChannelFlags.Empty) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, pipe, aliceInit) val latch = new CountDownLatch(2) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala index bcab510f4d..c6873b4dff 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala @@ -77,7 +77,7 @@ trait StateTestsHelperMethods extends TestKitBase { val bobInit = Init(bobParams.globalFeatures, bobParams.localFeatures) // reset global feerates (they may have been changed by previous tests) Globals.feeratesPerKw.set(FeeratesPerKw.single(TestConstants.feeratePerKw)) - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, channelFlags) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, alice2bob.ref, bobInit, channelFlags) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala index 17e9c8418b..a7b6298c6a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala @@ -40,19 +40,19 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp case class FixtureParam(alice: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe) override def withFixture(test: OneArgTest): Outcome = { - val noopWallet = new TestWallet { - override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long, lockUnspent: Boolean = true): Future[MakeFundingTxResponse] = Promise[MakeFundingTxResponse].future // will never be completed - } val setup = if (test.tags.contains("mainnet")) { - init(TestConstants.Alice.nodeParams.copy(chainHash = Block.LivenetGenesisBlock.hash), TestConstants.Bob.nodeParams.copy(chainHash = Block.LivenetGenesisBlock.hash), wallet = noopWallet) + val mainnetWallet = new TestWallet { + override def getFinalAddress: Future[String] = Future.successful("3LcWzTnuZGPkGkPyX7tfKsktdvMoz4VabR") + } + init(TestConstants.Alice.nodeParams.copy(chainHash = Block.LivenetGenesisBlock.hash), TestConstants.Bob.nodeParams.copy(chainHash = Block.LivenetGenesisBlock.hash), wallet = mainnetWallet) } else { - init(wallet = noopWallet) + init() } import setup._ val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) within(30 seconds) { - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, alice2bob.ref, bobInit, ChannelFlags.Empty) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala index 6b74dfd654..d7f89523fb 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala @@ -41,7 +41,7 @@ class WaitForOpenChannelStateSpec extends TestkitBaseClass with StateTestsHelper val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) within(30 seconds) { - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, alice2bob.ref, bobInit, ChannelFlags.Empty) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit) awaitCond(bob.stateName == WAIT_FOR_OPEN_CHANNEL) withFixture(test.toNoArgTest(FixtureParam(bob, alice2bob, bob2alice, bob2blockchain))) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala index f8e6d53127..20728b1b50 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala @@ -47,7 +47,7 @@ class WaitForFundingCreatedInternalStateSpec extends TestkitBaseClass with State val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) within(30 seconds) { - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, alice2bob.ref, bobInit, ChannelFlags.Empty) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala index 3bc38c87cf..d390fc9dfb 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala @@ -48,7 +48,7 @@ class WaitForFundingCreatedStateSpec extends TestkitBaseClass with StateTestsHel val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) within(30 seconds) { - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, alice2bob.ref, bobInit, ChannelFlags.Empty) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala index ef531d0f72..bf1b666f62 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala @@ -44,7 +44,7 @@ class WaitForFundingSignedStateSpec extends TestkitBaseClass with StateTestsHelp val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) within(30 seconds) { - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, alice2bob.ref, bobInit, ChannelFlags.Empty) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala index 694d9dc7ed..0dc77fcde0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala @@ -43,7 +43,7 @@ class WaitForFundingConfirmedStateSpec extends TestkitBaseClass with StateTestsH val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) within(30 seconds) { - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, alice2bob.ref, bobInit, ChannelFlags.Empty) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingLockedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingLockedStateSpec.scala index e8f71ae678..cf0d6bf2a7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingLockedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingLockedStateSpec.scala @@ -43,7 +43,7 @@ class WaitForFundingLockedStateSpec extends TestkitBaseClass with StateTestsHelp val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) within(30 seconds) { - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, alice2bob.ref, bobInit, ChannelFlags.Empty) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala index 94e940a5d8..cb209b926c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala @@ -59,7 +59,7 @@ class RustyTestsSpec extends TestKit(ActorSystem("test")) with Matchers with fix val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) // alice and bob will both have 1 000 000 sat Globals.feeratesPerKw.set(FeeratesPerKw.single(10000)) - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, 2000000, 1000000000, Globals.feeratesPerKw.get.blocks_2, Globals.feeratesPerKw.get.blocks_6, Alice.channelParams, pipe, bobInit, ChannelFlags.Empty) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, 2000000, 1000000000, Globals.feeratesPerKw.get.blocks_2, Globals.feeratesPerKw.get.blocks_6, pipe, bobInit, ChannelFlags.Empty) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, pipe, aliceInit) pipe ! (alice, bob) within(30 seconds) { From 99925815ebdae4a34d064ab3cef8d8694544c937 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 31 May 2019 17:44:36 +0200 Subject: [PATCH 05/43] Remove unnecessary logs --- .../scala/fr/acinq/eclair/channel/Channel.scala | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 19f6817174..d7936daea6 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -413,15 +413,9 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId // update the funding TX with the new scriptPubkey val finalFundingTx = funding.fundingTx.copy ( - txOut = funding.fundingTx.txOut.zipWithIndex.map { - case (_, index) if index == funding.fundingTxOutputIndex => TxOut(Satoshi(fundingSatoshis), fundingPubkeyScript) - case (out, _) => out - } + txOut = funding.fundingTx.txOut.updated(funding.fundingTxOutputIndex, TxOut(Satoshi(fundingSatoshis), fundingPubkeyScript)) ) - log.info(s"### PRE ### FINAL_FUNDING_TX=${finalFundingTx.bin.toHex}") - - // add our signature to the funding transaction wallet.signTransactionComplete(finalFundingTx).map(SignFundingTxResponse(_, funding.fundingTxOutputIndex, funding.fee)).pipeTo(self) @@ -459,12 +453,6 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId signature = localSigOfRemoteTx ) - log.info(s"LOCAL_COMMIT_INPUT=${localCommitTx.input}") - log.info(s"REMOTE_COMMIT_INPUT=${remoteCommitTx.input}") - log.info(s"LOCAL_COMMIT_TX=${localCommitTx.tx.bin.toHex}") - log.info(s"REMOTE_COMMIT_TX=${remoteCommitTx.tx.bin.toHex}") - log.info(s"LOCAL_SIG_OF_REMOTE=$localSigOfRemoteTx") - val channelId = toLongId(fundingTx.hash, fundingTxOutputIndex) context.parent ! ChannelIdAssigned(self, remoteNodeId, temporaryChannelId, channelId) // we notify the peer asap so it knows how to route messages context.system.eventStream.publish(ChannelIdAssigned(self, remoteNodeId, temporaryChannelId, channelId)) From 2235c6f6bb002d3bb38257d7fbb7fa510a263ce1 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 3 Jun 2019 16:56:24 +0200 Subject: [PATCH 06/43] Use different states/events to handle creation/signing of the funding tx --- .../fr/acinq/eclair/channel/Channel.scala | 117 +++++++++++------- .../acinq/eclair/channel/ChannelTypes.scala | 6 +- .../fr/acinq/eclair/channel/Helpers.scala | 3 +- .../a/WaitForAcceptChannelStateSpec.scala | 4 +- ...itForFundingCreatedInternalStateSpec.scala | 2 +- .../fr/acinq/eclair/gui/GUIUpdater.scala | 2 +- 6 files changed, 84 insertions(+), 50 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index d7936daea6..a58b83ab7f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -138,6 +138,13 @@ object Channel { )) } + def stripSignaturesFromTx(funding: MakeFundingTxResponse) = funding.copy(fundingTx = funding.fundingTx.copy( + txIn = funding.fundingTx.txIn.map(_.copy( + signatureScript = ByteVector.empty, + witness = ScriptWitness.empty + )) + )) + } class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId: PublicKey, blockchain: ActorRef, router: ActorRef, relayer: ActorRef, origin_opt: Option[ActorRef] = None)(implicit ec: ExecutionContext = ExecutionContext.Implicits.global) extends FSM[State, Data] with FSMDiagnosticActorLogging[State, Data] { @@ -193,49 +200,18 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId startWith(WAIT_FOR_INIT_INTERNAL, Nothing) when(WAIT_FOR_INIT_INTERNAL)(handleExceptions { - case Event(initFunder@INPUT_INIT_FUNDER(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, remote, _, channelFlags), Nothing) => + case Event(INPUT_INIT_FUNDER(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, remote, remoteInit, channelFlags), Nothing) => context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, true, temporaryChannelId)) forwarder ! remote - // this LOCKS the outpoints that we'll use in the funding transaction, the resulting TX is NOT signed - // later in the process we change the output scriptPubkey and ask the wallet to sign - val funding = createFundingWithNoSignatures(wallet, keyManager, Satoshi(fundingSatoshis), fundingTxFeeratePerKw) // FIXME blocking call! - val defaultFinalScriptPubKey = Helpers.getFinalScriptPubKey(wallet, nodeParams.chainHash) - val localParams = makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder = true, fundingSatoshis, funding.fundingTx.txIn.head) - log.info(s"using localParams=$localParams") - - val open = OpenChannel(nodeParams.chainHash, - temporaryChannelId = temporaryChannelId, - fundingSatoshis = fundingSatoshis, - pushMsat = pushMsat, - dustLimitSatoshis = localParams.dustLimitSatoshis, - maxHtlcValueInFlightMsat = localParams.maxHtlcValueInFlightMsat, - channelReserveSatoshis = localParams.channelReserveSatoshis, - htlcMinimumMsat = localParams.htlcMinimumMsat, - feeratePerKw = initialFeeratePerKw, - toSelfDelay = localParams.toSelfDelay, - maxAcceptedHtlcs = localParams.maxAcceptedHtlcs, - fundingPubkey = keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, - revocationBasepoint = keyManager.revocationPoint(localParams.channelKeyPath).publicKey, - paymentBasepoint = keyManager.paymentPoint(localParams.channelKeyPath).publicKey, - delayedPaymentBasepoint = keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, - htlcBasepoint = keyManager.htlcPoint(localParams.channelKeyPath).publicKey, - firstPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 0), - channelFlags = channelFlags) + // this LOCKS the outpoints that we'll use in the funding transaction, the resulting TX is NOT signed, later in the process we update the output scriptPubkey and ask the wallet to sign + val tempKeyPath = KeyPath(Seq(1, 2, 3, 4L)) + val scriptPubKey = Script.write(Script.pay2wsh(Scripts.multiSig2of2(keyManager.fundingPublicKey(tempKeyPath).publicKey, keyManager.fundingPublicKey(tempKeyPath).publicKey))) - val nextStateData = INPUT_INIT_FUNDER_WITH_PARAMS( - initFunder.temporaryChannelId, - initFunder.fundingSatoshis, - initFunder.pushMsat, - initFunder.initialFeeratePerKw, - initFunder.fundingTxFeeratePerKw, - localParams, - initFunder.remote, - initFunder.remoteInit, - initFunder.channelFlags - ) + // the resulting funding tx response will NOT have input script signature + wallet.makeFundingTx(pubkeyScript = scriptPubKey, Satoshi(fundingSatoshis), fundingTxFeeratePerKw).map(stripSignaturesFromTx).pipeTo(self) - goto(WAIT_FOR_ACCEPT_CHANNEL) using DATA_WAIT_FOR_ACCEPT_CHANNEL(nextStateData, open, funding) sending open + goto(WAIT_FOR_FUNDING_INTERNAL_CREATED) using DATA_WAIT_FOR_FUNDING_INTERNAL_CREATED(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, remote, remoteInit, channelFlags) case Event(inputFundee@INPUT_INIT_FUNDEE(_, localParams, remote, _), Nothing) if !localParams.isFunder => forwarder ! remote @@ -384,6 +360,61 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId case Event(INPUT_DISCONNECTED, _) => goto(CLOSED) }) + when(WAIT_FOR_FUNDING_INTERNAL_CREATED)(handleExceptions{ + case Event(funding:MakeFundingTxResponse, DATA_WAIT_FOR_FUNDING_INTERNAL_CREATED(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, remote, remoteInit, channelFlags)) => + + val defaultFinalScriptPubKey = Helpers.getFinalScriptPubKey(wallet, nodeParams.chainHash) + val localParams = makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder = true, fundingSatoshis, funding.fundingTx.txIn.head) + log.info(s"using localParams=$localParams") + + val open = OpenChannel(nodeParams.chainHash, + temporaryChannelId = temporaryChannelId, + fundingSatoshis = fundingSatoshis, + pushMsat = pushMsat, + dustLimitSatoshis = localParams.dustLimitSatoshis, + maxHtlcValueInFlightMsat = localParams.maxHtlcValueInFlightMsat, + channelReserveSatoshis = localParams.channelReserveSatoshis, + htlcMinimumMsat = localParams.htlcMinimumMsat, + feeratePerKw = initialFeeratePerKw, + toSelfDelay = localParams.toSelfDelay, + maxAcceptedHtlcs = localParams.maxAcceptedHtlcs, + fundingPubkey = keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, + revocationBasepoint = keyManager.revocationPoint(localParams.channelKeyPath).publicKey, + paymentBasepoint = keyManager.paymentPoint(localParams.channelKeyPath).publicKey, + delayedPaymentBasepoint = keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, + htlcBasepoint = keyManager.htlcPoint(localParams.channelKeyPath).publicKey, + firstPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 0), + channelFlags = channelFlags) + + val nextStateData = INPUT_INIT_FUNDER_WITH_PARAMS(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, localParams, remote, remoteInit, channelFlags) + + goto(WAIT_FOR_ACCEPT_CHANNEL) using DATA_WAIT_FOR_ACCEPT_CHANNEL(nextStateData, open, funding) sending open + + // TODO release outputs that were locked in the funding + case Event(Status.Failure(t), d: DATA_WAIT_FOR_FUNDING_INTERNAL_CREATED) => + log.error(t, s"wallet returned error: ") + replyToUser(Left(LocalError(t))) + handleLocalError(ChannelFundingError(d.temporaryChannelId), d, None) // we use a generic exception and don't send the internal error to the peer + + case Event(e: Error, d: DATA_WAIT_FOR_FUNDING_INTERNAL_CREATED) => + replyToUser(Left(RemoteError(e))) + handleRemoteError(e, d) + + case Event(CMD_CLOSE(_), _) => + replyToUser(Right("closed")) + goto(CLOSED) replying "ok" + + case Event(INPUT_DISCONNECTED, _) => + replyToUser(Left(LocalError(new RuntimeException("disconnected")))) + goto(CLOSED) + + case Event(TickChannelOpenTimeout, _) => + replyToUser(Left(LocalError(new RuntimeException("open channel cancelled, took too long")))) + goto(CLOSED) + + + }) + when(WAIT_FOR_ACCEPT_CHANNEL)(handleExceptions { case Event(accept: AcceptChannel, d@DATA_WAIT_FOR_ACCEPT_CHANNEL(INPUT_INIT_FUNDER_WITH_PARAMS(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, _, localParams, _, remoteInit, _), open, funding)) => log.info(s"received AcceptChannel=$accept") @@ -419,7 +450,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId // add our signature to the funding transaction wallet.signTransactionComplete(finalFundingTx).map(SignFundingTxResponse(_, funding.fundingTxOutputIndex, funding.fee)).pipeTo(self) - goto(WAIT_FOR_FUNDING_INTERNAL) using DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, accept.firstPerCommitmentPoint, open) + goto(WAIT_FOR_FUNDING_INTERNAL_SIGNED) using DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, accept.firstPerCommitmentPoint, open) } case Event(CMD_CLOSE(_), _) => @@ -439,8 +470,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId goto(CLOSED) }) - when(WAIT_FOR_FUNDING_INTERNAL)(handleExceptions { - case Event(SignFundingTxResponse(fundingTx, fundingTxOutputIndex, fundingTxFee), DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, remoteFirstPerCommitmentPoint, open)) => + when(WAIT_FOR_FUNDING_INTERNAL_SIGNED)(handleExceptions { + case Event(SignFundingTxResponse(fundingTx, fundingTxOutputIndex, fundingTxFee), DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, remoteFirstPerCommitmentPoint, open)) => // let's create the first commitment tx that spends the yet uncommitted funding tx val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.maxFeerateMismatch) require(fundingTx.txOut(fundingTxOutputIndex).publicKeyScript == localCommitTx.input.txOut.publicKeyScript, s"pubkey script mismatch!") @@ -459,7 +490,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId // NB: we don't send a ChannelSignatureSent for the first commit goto(WAIT_FOR_FUNDING_SIGNED) using DATA_WAIT_FOR_FUNDING_SIGNED(channelId, localParams, remoteParams, fundingTx, fundingTxFee, localSpec, localCommitTx, RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, remoteFirstPerCommitmentPoint), open.channelFlags, fundingCreated) sending fundingCreated - case Event(Status.Failure(t), d: DATA_WAIT_FOR_FUNDING_INTERNAL) => + case Event(Status.Failure(t), d: DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED) => log.error(t, s"wallet returned error: ") replyToUser(Left(LocalError(t))) handleLocalError(ChannelFundingError(d.temporaryChannelId), d, None) // we use a generic exception and don't send the internal error to the peer @@ -468,7 +499,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId replyToUser(Right("closed")) goto(CLOSED) replying "ok" - case Event(e: Error, d: DATA_WAIT_FOR_FUNDING_INTERNAL) => + case Event(e: Error, d: DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED) => replyToUser(Left(RemoteError(e))) handleRemoteError(e, d) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala index b13021dab7..cda6bbc482 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala @@ -50,7 +50,8 @@ sealed trait State case object WAIT_FOR_INIT_INTERNAL extends State case object WAIT_FOR_OPEN_CHANNEL extends State case object WAIT_FOR_ACCEPT_CHANNEL extends State -case object WAIT_FOR_FUNDING_INTERNAL extends State +case object WAIT_FOR_FUNDING_INTERNAL_SIGNED extends State +case object WAIT_FOR_FUNDING_INTERNAL_CREATED extends State case object WAIT_FOR_FUNDING_CREATED extends State case object WAIT_FOR_FUNDING_SIGNED extends State case object WAIT_FOR_FUNDING_CONFIRMED extends State @@ -152,7 +153,8 @@ case class RevokedCommitPublished(commitTx: Transaction, claimMainOutputTx: Opti final case class DATA_WAIT_FOR_OPEN_CHANNEL(initFundee: INPUT_INIT_FUNDEE) extends Data final case class DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder: INPUT_INIT_FUNDER_WITH_PARAMS, lastSent: OpenChannel, unsignedFundingTx: MakeFundingTxResponse) extends Data -final case class DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, remoteFirstPerCommitmentPoint: Point, lastSent: OpenChannel) extends Data +final case class DATA_WAIT_FOR_FUNDING_INTERNAL_CREATED(temporaryChannelId: ByteVector32, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, fundingTxFeeratePerKw: Long, remote: ActorRef, remoteInit: Init, channelFlags: Byte) extends Data +final case class DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED(temporaryChannelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, remoteFirstPerCommitmentPoint: Point, lastSent: OpenChannel) extends Data final case class DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, remoteFirstPerCommitmentPoint: Point, channelFlags: Byte, lastSent: AcceptChannel) extends Data final case class DATA_WAIT_FOR_FUNDING_SIGNED(channelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingTx: Transaction, fundingTxFee: Satoshi, localSpec: CommitmentSpec, localCommitTx: CommitTx, remoteCommit: RemoteCommit, channelFlags: Byte, lastSent: FundingCreated) extends Data final case class DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments: Commitments, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 835e1f67bc..b1072a4a96 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -53,7 +53,8 @@ object Helpers { case Nothing => ByteVector32.Zeroes case d: DATA_WAIT_FOR_OPEN_CHANNEL => d.initFundee.temporaryChannelId case d: DATA_WAIT_FOR_ACCEPT_CHANNEL => d.initFunder.temporaryChannelId - case d: DATA_WAIT_FOR_FUNDING_INTERNAL => d.temporaryChannelId + case d: DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED => d.temporaryChannelId + case d: DATA_WAIT_FOR_FUNDING_INTERNAL_CREATED => d.temporaryChannelId case d: DATA_WAIT_FOR_FUNDING_CREATED => d.temporaryChannelId case d: DATA_WAIT_FOR_FUNDING_SIGNED => d.channelId case d: HasCommitments => d.channelId diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala index a7b6298c6a..e87a91209a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala @@ -22,7 +22,7 @@ import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.blockchain.{MakeFundingTxResponse, TestWallet} import fr.acinq.eclair.channel.Channel.TickChannelOpenTimeout import fr.acinq.eclair.channel.states.StateTestsHelperMethods -import fr.acinq.eclair.channel.{WAIT_FOR_FUNDING_INTERNAL, _} +import fr.acinq.eclair.channel.{WAIT_FOR_FUNDING_INTERNAL_SIGNED, _} import fr.acinq.eclair.wire.{AcceptChannel, Error, Init, OpenChannel} import fr.acinq.eclair.{TestConstants, TestkitBaseClass} import org.scalatest.{Outcome, Tag} @@ -65,7 +65,7 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp import f._ bob2alice.expectMsgType[AcceptChannel] bob2alice.forward(alice) - awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL) + awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL_SIGNED) } test("recv AcceptChannel (invalid max accepted htlcs)") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala index 20728b1b50..b1f9bb4416 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala @@ -53,7 +53,7 @@ class WaitForFundingCreatedInternalStateSpec extends TestkitBaseClass with State alice2bob.forward(bob) bob2alice.expectMsgType[AcceptChannel] bob2alice.forward(alice) - awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL) + awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL_SIGNED) withFixture(test.toNoArgTest(FixtureParam(alice, alice2bob, bob2alice, alice2blockchain))) } } diff --git a/eclair-node-gui/src/main/scala/fr/acinq/eclair/gui/GUIUpdater.scala b/eclair-node-gui/src/main/scala/fr/acinq/eclair/gui/GUIUpdater.scala index d63969c9b0..d5916dfb4e 100644 --- a/eclair-node-gui/src/main/scala/fr/acinq/eclair/gui/GUIUpdater.scala +++ b/eclair-node-gui/src/main/scala/fr/acinq/eclair/gui/GUIUpdater.scala @@ -41,7 +41,7 @@ import scala.collection.JavaConversions._ */ class GUIUpdater(mainController: MainController) extends Actor with ActorLogging { - val STATE_MUTUAL_CLOSE = Set(WAIT_FOR_INIT_INTERNAL, WAIT_FOR_OPEN_CHANNEL, WAIT_FOR_ACCEPT_CHANNEL, WAIT_FOR_FUNDING_INTERNAL, WAIT_FOR_FUNDING_CREATED, WAIT_FOR_FUNDING_SIGNED, NORMAL) + val STATE_MUTUAL_CLOSE = Set(WAIT_FOR_INIT_INTERNAL, WAIT_FOR_OPEN_CHANNEL, WAIT_FOR_ACCEPT_CHANNEL, WAIT_FOR_FUNDING_INTERNAL_SIGNED, WAIT_FOR_FUNDING_CREATED, WAIT_FOR_FUNDING_SIGNED, NORMAL) val STATE_FORCE_CLOSE = Set(WAIT_FOR_FUNDING_CONFIRMED, WAIT_FOR_FUNDING_LOCKED, NORMAL, SHUTDOWN, NEGOTIATING, OFFLINE, SYNCING) /** From 0810f123ab452eee5c80ec891eb5a0407d955180 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 3 Jun 2019 17:40:18 +0200 Subject: [PATCH 07/43] Fix test for internal funding created state --- .../states/b/WaitForFundingCreatedInternalStateSpec.scala | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala index b1f9bb4416..ae5711789c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala @@ -17,7 +17,7 @@ package fr.acinq.eclair.channel.states.b import akka.testkit.{TestFSMRef, TestProbe} -import fr.acinq.bitcoin.{ByteVector32, Satoshi} +import fr.acinq.bitcoin.{ByteVector32, Satoshi, Transaction} import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.blockchain.{MakeFundingTxResponse, TestWallet} import fr.acinq.eclair.channel._ @@ -39,10 +39,7 @@ class WaitForFundingCreatedInternalStateSpec extends TestkitBaseClass with State case class FixtureParam(alice: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe) override def withFixture(test: OneArgTest): Outcome = { - val noopWallet = new TestWallet { - override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long, lockUnspent: Boolean = true): Future[MakeFundingTxResponse] = Promise[MakeFundingTxResponse].future // will never be completed - } - val setup = init(wallet = noopWallet) + val setup = init() import setup._ val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) From 54a0e599384a1a655435945234caf8e6db9c605d Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 3 Jun 2019 18:02:19 +0200 Subject: [PATCH 08/43] Revert changes to use old derivation path for REMOTE -> LOCAL --- .../main/scala/fr/acinq/eclair/io/Peer.scala | 42 +++++++++++++++---- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index 0db46ab291..0abfb6ba22 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -37,7 +37,6 @@ import scodec.Attempt import scodec.bits.ByteVector import scala.compat.Platform -import scala.concurrent.Await import scala.concurrent.duration._ import scala.util.Random @@ -272,10 +271,11 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: Actor d.transport ! TransportHandler.ReadAck(msg) d.channels.get(TemporaryChannelId(msg.temporaryChannelId)) match { case None => - val channel = spawnChannel(nodeParams, origin_opt = None) + val (channel, localParams) = createNewChannel(nodeParams, funder = false, fundingSatoshis = msg.fundingSatoshis, origin_opt = None) val temporaryChannelId = msg.temporaryChannelId log.info(s"accepting a new channel to $remoteNodeId temporaryChannelId=$temporaryChannelId") - channel ! INPUT_INIT_FUNDEE(temporaryChannelId, ???, d.transport, d.remoteInit) + + channel ! INPUT_INIT_FUNDEE(temporaryChannelId, localParams, d.transport, d.remoteInit) channel ! msg stay using d.copy(channels = d.channels + (TemporaryChannelId(temporaryChannelId) -> channel)) case Some(_) => @@ -486,12 +486,12 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: Actor case DISCONNECTED -> _ if nodeParams.autoReconnect && stateData.address_opt.isDefined => cancelTimer(RECONNECT_TIMER) } -// def createNewChannel(nodeParams: NodeParams, funder: Boolean, fundingSatoshis: Long, origin_opt: Option[ActorRef]): ActorRef = { -// val defaultFinalScriptPubKey = Helpers.getFinalScriptPubKey(wallet, nodeParams.chainHash) -// // val localParams = makeChannelParams(nodeParams, defaultFinalScriptPubKey, funder, fundingSatoshis, fundingInput) -// val channel = spawnChannel(nodeParams, origin_opt) -// channel -// } + def createNewChannel(nodeParams: NodeParams, funder: Boolean, fundingSatoshis: Long, origin_opt: Option[ActorRef]): (ActorRef, LocalParams) = { + val defaultFinalScriptPubKey = Helpers.getFinalScriptPubKey(wallet, nodeParams.chainHash) + val localParams = makeChannelParams(nodeParams, defaultFinalScriptPubKey, funder, fundingSatoshis) + val channel = spawnChannel(nodeParams, origin_opt) + (channel, localParams) + } def spawnChannel(nodeParams: NodeParams, origin_opt: Option[ActorRef]): ActorRef = { val channel = context.actorOf(Channel.props(nodeParams, wallet, remoteNodeId, watcher, router, relayer, origin_opt)) @@ -581,6 +581,30 @@ object Peer { // @formatter:on + def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long): LocalParams = { + val entropy = new Array[Byte](16) + secureRandom.nextBytes(entropy) + val bis = new ByteArrayInputStream(entropy) + val channelKeyPath = DeterministicWallet.KeyPath(Seq(Protocol.uint32(bis, ByteOrder.BIG_ENDIAN), Protocol.uint32(bis, ByteOrder.BIG_ENDIAN), Protocol.uint32(bis, ByteOrder.BIG_ENDIAN), Protocol.uint32(bis, ByteOrder.BIG_ENDIAN))) + makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder, fundingSatoshis, channelKeyPath) + } + + def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, channelKeyPath: DeterministicWallet.KeyPath): LocalParams = { + LocalParams( + nodeParams.nodeId, + channelKeyPath, + dustLimitSatoshis = nodeParams.dustLimitSatoshis, + maxHtlcValueInFlightMsat = nodeParams.maxHtlcValueInFlightMsat, + channelReserveSatoshis = Math.max((nodeParams.reserveToFundingRatio * fundingSatoshis).toLong, nodeParams.dustLimitSatoshis), // BOLT #2: make sure that our reserve is above our dust limit + htlcMinimumMsat = nodeParams.htlcMinimumMsat, + toSelfDelay = nodeParams.toRemoteDelayBlocks, // we choose their delay + maxAcceptedHtlcs = nodeParams.maxAcceptedHtlcs, + defaultFinalScriptPubKey = defaultFinalScriptPubKey, + isFunder = isFunder, + globalFeatures = nodeParams.globalFeatures, + localFeatures = nodeParams.localFeatures) + } + /** * Peer may want to filter announcements based on timestamp * From f77e1f877769f114c1c8d778a1275db423428df9 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 4 Jun 2019 15:34:54 +0200 Subject: [PATCH 09/43] Use bech32 test address --- .../src/test/scala/fr/acinq/eclair/blockchain/TestWallet.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/TestWallet.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/TestWallet.scala index f845789bbe..e4531e7854 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/TestWallet.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/TestWallet.scala @@ -32,7 +32,7 @@ class TestWallet extends EclairWallet { override def getBalance: Future[Satoshi] = ??? - override def getFinalAddress: Future[String] = Future.successful("2MsRZ1asG6k94m6GYUufDGaZJMoJ4EV5JKs") + override def getFinalAddress: Future[String] = Future.successful("bcrt1q82l6tngfd7stp2amhd8w2crn7dfy3qyelzywtn") override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long, lockUnspent: Boolean = true): Future[MakeFundingTxResponse] = Future.successful(TestWallet.makeDummyFundingTx(pubkeyScript, amount, feeRatePerKw)) From 132035b332d62917f6b97a1de02a4f9bc7c942bc Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 5 Jun 2019 14:46:11 +0200 Subject: [PATCH 10/43] Use deterministic derivation --- .../fr/acinq/eclair/channel/Channel.scala | 94 ++++++++----------- .../acinq/eclair/channel/ChannelTypes.scala | 10 +- .../fr/acinq/eclair/channel/Commitments.scala | 30 +++--- .../fr/acinq/eclair/channel/Helpers.scala | 52 +++++----- .../fr/acinq/eclair/crypto/KeyManager.scala | 23 +++++ .../acinq/eclair/crypto/LocalKeyManager.scala | 91 ++++++++++++++++++ .../main/scala/fr/acinq/eclair/io/Peer.scala | 2 +- .../fr/acinq/eclair/wire/ChannelCodecs.scala | 30 +++++- .../scala/fr/acinq/eclair/TestConstants.scala | 6 +- .../channel/states/e/NormalStateSpec.scala | 4 +- .../channel/states/e/OfflineStateSpec.scala | 8 +- .../fr/acinq/eclair/db/ChannelStateSpec.scala | 4 +- .../acinq/eclair/wire/ChannelCodecsSpec.scala | 6 +- 13 files changed, 248 insertions(+), 112 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index a58b83ab7f..a82ab4ced5 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -93,16 +93,19 @@ object Channel { // @formatter:on def makeChannelKeyPathFromInput(fundingInput: TxIn): KeyPath = { - val inputEntropy = Crypto.sha256(fundingInput.outPoint.hash).take(4).toLong(signed = false) - KeyPath(Seq(47, 2, inputEntropy, 0)) + // split the SHA into 8 groups of 4 bytes and convert to uint32 + val List(h0, h1, h2, h3, h4, h5, h6, h7) = Crypto.sha256(fundingInput.outPoint.hash).toArray.grouped(4).map(ByteVector(_).toLong(signed = false)).toList + KeyPath(Seq(47, 2, h0, h1, h2, h3, h4, h5, h6, h7, 0)) } - def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, fundingInput: TxIn): LocalParams = { + def makeFunderChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, fundingInput: TxIn): LocalParams = { + require(isFunder, s"Wrong params for isFunder=$isFunder") + val channelKeyPath = makeChannelKeyPathFromInput(fundingInput) - makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder, fundingSatoshis, channelKeyPath) + makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder, fundingSatoshis, Left(channelKeyPath)) } - def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, channelKeyPath: DeterministicWallet.KeyPath): LocalParams = { + def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, channelKeyPath: Either[DeterministicWallet.KeyPath, KeyPathFundee]): LocalParams = { LocalParams( nodeParams.nodeId, channelKeyPath, @@ -118,26 +121,6 @@ object Channel { localFeatures = nodeParams.localFeatures) } - // creates a funding transaction without signatures, this is used to lock an output and derive the channel keypath. - def createFundingWithNoSignatures(wallet: EclairWallet, keyManager: KeyManager, fundingSatoshis: Satoshi, fundingTxFeeratePerKw: Long): MakeFundingTxResponse = { - // this LOCKS the outpoints that we'll use in the funding transaction, the resulting TX is NOT signed - // later in the process we change the output scriptPubkey and ask the wallet to sign - val tempKeyPath = KeyPath(Seq(1, 2, 3, 4L)) - val scriptPubKey = Script.write(Script.pay2wsh(Scripts.multiSig2of2(keyManager.fundingPublicKey(tempKeyPath).publicKey, keyManager.fundingPublicKey(tempKeyPath).publicKey))) - val funding = Await.result( - wallet.makeFundingTx(pubkeyScript = scriptPubKey, fundingSatoshis, fundingTxFeeratePerKw), - 10 seconds // TODO change? - ) - - // remove our invalid sig - funding.copy(fundingTx = funding.fundingTx.copy( - txIn = funding.fundingTx.txIn.map(_.copy( - signatureScript = ByteVector.empty, - witness = ScriptWitness.empty - )) - )) - } - def stripSignaturesFromTx(funding: MakeFundingTxResponse) = funding.copy(fundingTx = funding.fundingTx.copy( txIn = funding.fundingTx.txIn.map(_.copy( signatureScript = ByteVector.empty, @@ -318,6 +301,11 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId case Failure(t) => handleLocalError(t, d, Some(open)) case Success(_) => context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, false, open.temporaryChannelId)) + + + + + // TODO: maybe also check uniqueness of temporary channel id val minimumDepth = nodeParams.minDepthBlocks val accept = AcceptChannel(temporaryChannelId = open.temporaryChannelId, @@ -328,12 +316,12 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId htlcMinimumMsat = localParams.htlcMinimumMsat, toSelfDelay = localParams.toSelfDelay, maxAcceptedHtlcs = localParams.maxAcceptedHtlcs, - fundingPubkey = keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, - revocationBasepoint = keyManager.revocationPoint(localParams.channelKeyPath).publicKey, - paymentBasepoint = keyManager.paymentPoint(localParams.channelKeyPath).publicKey, - delayedPaymentBasepoint = keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, - htlcBasepoint = keyManager.htlcPoint(localParams.channelKeyPath).publicKey, - firstPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 0)) + fundingPubkey = keyManager.deterministicFundingPublicKey(localParams).publicKey, + revocationBasepoint = keyManager.deterministicRevocationPoint(localParams).publicKey, + paymentBasepoint = keyManager.deterministicPaymentPoint(localParams).publicKey, + delayedPaymentBasepoint = keyManager.deterministicDelayedPaymentPoint(localParams).publicKey, + htlcBasepoint = keyManager.deterministicHtlcPoint(localParams).publicKey, + firstPerCommitmentPoint = keyManager.deterministicCommitmentPoint(localParams, 0)) val remoteParams = RemoteParams( nodeId = remoteNodeId, dustLimitSatoshis = open.dustLimitSatoshis, @@ -364,7 +352,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId case Event(funding:MakeFundingTxResponse, DATA_WAIT_FOR_FUNDING_INTERNAL_CREATED(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, remote, remoteInit, channelFlags)) => val defaultFinalScriptPubKey = Helpers.getFinalScriptPubKey(wallet, nodeParams.chainHash) - val localParams = makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder = true, fundingSatoshis, funding.fundingTx.txIn.head) + val localParams = makeFunderChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder = true, fundingSatoshis, funding.fundingTx.txIn.head) log.info(s"using localParams=$localParams") val open = OpenChannel(nodeParams.chainHash, @@ -378,12 +366,12 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId feeratePerKw = initialFeeratePerKw, toSelfDelay = localParams.toSelfDelay, maxAcceptedHtlcs = localParams.maxAcceptedHtlcs, - fundingPubkey = keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, - revocationBasepoint = keyManager.revocationPoint(localParams.channelKeyPath).publicKey, - paymentBasepoint = keyManager.paymentPoint(localParams.channelKeyPath).publicKey, - delayedPaymentBasepoint = keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, - htlcBasepoint = keyManager.htlcPoint(localParams.channelKeyPath).publicKey, - firstPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 0), + fundingPubkey = keyManager.deterministicFundingPublicKey(localParams).publicKey, + revocationBasepoint = keyManager.deterministicRevocationPoint(localParams).publicKey, + paymentBasepoint = keyManager.deterministicPaymentPoint(localParams).publicKey, + delayedPaymentBasepoint = keyManager.deterministicDelayedPaymentPoint(localParams).publicKey, + htlcBasepoint = keyManager.deterministicHtlcPoint(localParams).publicKey, + firstPerCommitmentPoint = keyManager.deterministicCommitmentPoint(localParams, 0), channelFlags = channelFlags) val nextStateData = INPUT_INIT_FUNDER_WITH_PARAMS(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, localParams, remote, remoteInit, channelFlags) @@ -439,7 +427,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId localFeatures = remoteInit.localFeatures) log.debug(s"remote params: $remoteParams") - val localFundingPubkey = keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey + val localFundingPubkey = keyManager.deterministicFundingPublicKey(localParams).publicKey val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey, remoteParams.fundingPubKey))) // update the funding TX with the new scriptPubkey @@ -475,7 +463,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId // let's create the first commitment tx that spends the yet uncommitted funding tx val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.maxFeerateMismatch) require(fundingTx.txOut(fundingTxOutputIndex).publicKeyScript == localCommitTx.input.txOut.publicKeyScript, s"pubkey script mismatch!") - val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) + val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.deterministicFundingPublicKey(localParams)) // signature of their initial commitment tx that pays remote pushMsat val fundingCreated = FundingCreated( temporaryChannelId = temporaryChannelId, @@ -518,12 +506,12 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, temporaryChannelId, localParams, remoteParams, fundingSatoshis: Long, pushMsat, initialFeeratePerKw, fundingTxHash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.maxFeerateMismatch) // check remote signature validity - val localSigOfLocalTx = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) - val signedLocalCommitTx = Transactions.addSigs(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig) + val localSigOfLocalTx = keyManager.sign(localCommitTx, keyManager.deterministicFundingPublicKey(localParams)) + val signedLocalCommitTx = Transactions.addSigs(localCommitTx, keyManager.deterministicFundingPublicKey(localParams).publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig) Transactions.checkSpendable(signedLocalCommitTx) match { case Failure(cause) => handleLocalError(InvalidCommitmentSignature(temporaryChannelId, signedLocalCommitTx.tx), d, None) case Success(_) => - val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) + val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.deterministicFundingPublicKey(localParams)) val channelId = toLongId(fundingTxHash, fundingTxOutputIndex) // watch the funding tx transaction val commitInput = localCommitTx.input @@ -560,8 +548,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId when(WAIT_FOR_FUNDING_SIGNED)(handleExceptions { case Event(msg@FundingSigned(_, remoteSig), d@DATA_WAIT_FOR_FUNDING_SIGNED(channelId, localParams, remoteParams, fundingTx, fundingTxFee, localSpec, localCommitTx, remoteCommit, channelFlags, fundingCreated)) => // we make sure that their sig checks out and that our first commit tx is spendable - val localSigOfLocalTx = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) - val signedLocalCommitTx = Transactions.addSigs(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig) + val localSigOfLocalTx = keyManager.sign(localCommitTx, keyManager.deterministicFundingPublicKey(localParams)) + val signedLocalCommitTx = Transactions.addSigs(localCommitTx, keyManager.deterministicFundingPublicKey(localParams).publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig) Transactions.checkSpendable(signedLocalCommitTx) match { case Failure(cause) => // we rollback the funding tx, it will never be published @@ -633,7 +621,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId case Event(WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK, blockHeight, txIndex), DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments, _, _, deferred, _)) => log.info(s"channelId=${commitments.channelId} was confirmed at blockHeight=$blockHeight txIndex=$txIndex") blockchain ! WatchLost(self, commitments.commitInput.outPoint.txid, nodeParams.minDepthBlocks, BITCOIN_FUNDING_LOST) - val nextPerCommitmentPoint = keyManager.commitmentPoint(commitments.localParams.channelKeyPath, 1) + val nextPerCommitmentPoint = keyManager.deterministicCommitmentPoint(commitments.localParams, 1) val fundingLocked = FundingLocked(commitments.channelId, nextPerCommitmentPoint) deferred.map(self ! _) // this is the temporary channel id that we will use in our channel_update message, the goal is to be able to use our channel @@ -988,7 +976,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId require(d.shortChannelId == remoteAnnSigs.shortChannelId, s"shortChannelId mismatch: local=${d.shortChannelId} remote=${remoteAnnSigs.shortChannelId}") log.info(s"announcing channelId=${d.channelId} on the network with shortId=${d.shortChannelId}") import d.commitments.{localParams, remoteParams} - val channelAnn = Announcements.makeChannelAnnouncement(nodeParams.chainHash, localAnnSigs.shortChannelId, nodeParams.nodeId, remoteParams.nodeId, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, localAnnSigs.nodeSignature, remoteAnnSigs.nodeSignature, localAnnSigs.bitcoinSignature, remoteAnnSigs.bitcoinSignature) + val channelAnn = Announcements.makeChannelAnnouncement(nodeParams.chainHash, localAnnSigs.shortChannelId, nodeParams.nodeId, remoteParams.nodeId, keyManager.deterministicFundingPublicKey(localParams).publicKey, remoteParams.fundingPubKey, localAnnSigs.nodeSignature, remoteAnnSigs.nodeSignature, localAnnSigs.bitcoinSignature, remoteAnnSigs.bitcoinSignature) // we use GOTO instead of stay because we want to fire transitions goto(NORMAL) using store(d.copy(channelAnnouncement = Some(channelAnn))) case Some(_) => @@ -1488,7 +1476,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId forwarder ! r val yourLastPerCommitmentSecret = d.commitments.remotePerCommitmentSecrets.lastIndex.flatMap(d.commitments.remotePerCommitmentSecrets.getHash).getOrElse(ByteVector32.Zeroes) - val myCurrentPerCommitmentPoint = keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, d.commitments.localCommit.index) + val myCurrentPerCommitmentPoint = keyManager.deterministicCommitmentPoint(d.commitments.localParams, d.commitments.localCommit.index) val channelReestablish = ChannelReestablish( channelId = d.channelId, @@ -1544,7 +1532,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId case Event(_: ChannelReestablish, d: DATA_WAIT_FOR_FUNDING_LOCKED) => log.debug(s"re-sending fundingLocked") - val nextPerCommitmentPoint = keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, 1) + val nextPerCommitmentPoint = keyManager.deterministicCommitmentPoint(d.commitments.localParams, 1) val fundingLocked = FundingLocked(d.commitments.channelId, nextPerCommitmentPoint) goto(WAIT_FOR_FUNDING_LOCKED) sending fundingLocked @@ -1553,7 +1541,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId case ChannelReestablish(_, _, nextRemoteRevocationNumber, Some(yourLastPerCommitmentSecret), _) if !Helpers.checkLocalCommit(d, nextRemoteRevocationNumber) => // if next_remote_revocation_number is greater than our local commitment index, it means that either we are using an outdated commitment, or they are lying // but first we need to make sure that the last per_commitment_secret that they claim to have received from us is correct for that next_remote_revocation_number minus 1 - if (keyManager.commitmentSecret(d.commitments.localParams.channelKeyPath, nextRemoteRevocationNumber - 1) == yourLastPerCommitmentSecret) { + if (keyManager.deterministicCommitmentSecret(d.commitments.localParams, nextRemoteRevocationNumber - 1) == yourLastPerCommitmentSecret) { log.warning(s"counterparty proved that we have an outdated (revoked) local commitment!!! ourCommitmentNumber=${d.commitments.localCommit.index} theirCommitmentNumber=${nextRemoteRevocationNumber}") // their data checks out, we indeed seem to be using an old revoked commitment, and must absolutely *NOT* publish it, because that would be a cheating attempt and they // would punish us by taking all the funds in the channel @@ -1578,7 +1566,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId if (channelReestablish.nextLocalCommitmentNumber == 1 && d.commitments.localCommit.index == 0) { // If next_local_commitment_number is 1 in both the channel_reestablish it sent and received, then the node MUST retransmit funding_locked, otherwise it MUST NOT log.debug(s"re-sending fundingLocked") - val nextPerCommitmentPoint = keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, 1) + val nextPerCommitmentPoint = keyManager.deterministicCommitmentPoint(d.commitments.localParams, 1) val fundingLocked = FundingLocked(d.commitments.channelId, nextPerCommitmentPoint) forwarder ! fundingLocked } @@ -2143,8 +2131,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } else if (commitments1.localCommit.index == channelReestablish.nextRemoteRevocationNumber + 1) { // our last revocation got lost, let's resend it log.debug(s"re-sending last revocation") - val localPerCommitmentSecret = keyManager.commitmentSecret(commitments1.localParams.channelKeyPath, d.commitments.localCommit.index - 1) - val localNextPerCommitmentPoint = keyManager.commitmentPoint(commitments1.localParams.channelKeyPath, d.commitments.localCommit.index + 1) + val localPerCommitmentSecret = keyManager.deterministicCommitmentSecret(commitments1.localParams, d.commitments.localCommit.index - 1) + val localNextPerCommitmentPoint = keyManager.deterministicCommitmentPoint(commitments1.localParams, d.commitments.localCommit.index + 1) val revocation = RevokeAndAck( channelId = commitments1.channelId, perCommitmentSecret = localPerCommitmentSecret, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala index cda6bbc482..2cb2a855ac 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala @@ -20,6 +20,7 @@ import java.util.UUID import akka.actor.ActorRef import fr.acinq.bitcoin.Crypto.{Point, PublicKey} +import fr.acinq.bitcoin.DeterministicWallet.KeyPath import fr.acinq.bitcoin.{ByteVector32, DeterministicWallet, OutPoint, Satoshi, Transaction} import fr.acinq.eclair.blockchain.MakeFundingTxResponse import fr.acinq.eclair.crypto.Sphinx @@ -194,7 +195,7 @@ final case class DATA_CLOSING(commitments: Commitments, final case class DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT(commitments: Commitments, remoteChannelReestablish: ChannelReestablish) extends Data with HasCommitments final case class LocalParams(nodeId: PublicKey, - channelKeyPath: DeterministicWallet.KeyPath, + channelKeyPath: Either[DeterministicWallet.KeyPath, KeyPathFundee], dustLimitSatoshis: Long, maxHtlcValueInFlightMsat: UInt64, channelReserveSatoshis: Long, @@ -204,7 +205,12 @@ final case class LocalParams(nodeId: PublicKey, isFunder: Boolean, defaultFinalScriptPubKey: ByteVector, globalFeatures: ByteVector, - localFeatures: ByteVector) + localFeatures: ByteVector) { + + require(isFunder && channelKeyPath.isLeft || !isFunder && channelKeyPath.isRight, s"Wrong keyPath derivation for isFunder=$isFunder") +} + +case class KeyPathFundee(publicKeyPath: KeyPath, pointsKeyPath: KeyPath) final case class RemoteParams(nodeId: PublicKey, dustLimitSatoshis: Long, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index ee0e47f09c..844efb39e5 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -359,10 +359,10 @@ object Commitments { // remote commitment will includes all local changes + remote acked changes val spec = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(keyManager, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, spec) - val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) + val sig = keyManager.sign(remoteCommitTx, keyManager.deterministicFundingPublicKey(localParams)) val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index) - val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), remoteNextPerCommitmentPoint)) + val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.deterministicHtlcPoint(localParams), remoteNextPerCommitmentPoint)) // NB: IN/OUT htlcs are inverted because this is the remote commit log.info(s"built remote commit number=${remoteCommit.index + 1} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${remoteCommitTx.tx.txid} tx={}", spec.htlcs.filter(_.direction == OUT).map(_.add.id).mkString(","), spec.htlcs.filter(_.direction == IN).map(_.add.id).mkString(","), remoteCommitTx.tx) @@ -406,16 +406,16 @@ object Commitments { // receiving money i.e its commit tx has one output for them val spec = CommitmentSpec.reduce(localCommit.spec, localChanges.acked, remoteChanges.proposed) - val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index + 1) + val localPerCommitmentPoint = keyManager.deterministicCommitmentPoint(localParams, commitments.localCommit.index + 1) val (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeLocalTxs(keyManager, localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, spec) - val sig = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) + val sig = keyManager.sign(localCommitTx, keyManager.deterministicFundingPublicKey(localParams)) log.info(s"built local commit number=${localCommit.index + 1} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${localCommitTx.tx.txid} tx={}", spec.htlcs.filter(_.direction == IN).map(_.add.id).mkString(","), spec.htlcs.filter(_.direction == OUT).map(_.add.id).mkString(","), localCommitTx.tx) // TODO: should we have optional sig? (original comment: this tx will NOT be signed if our output is empty) // no need to compute htlc sigs if commit sig doesn't check out - val signedCommitTx = Transactions.addSigs(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, sig, commit.signature) + val signedCommitTx = Transactions.addSigs(localCommitTx, keyManager.deterministicFundingPublicKey(localParams).publicKey, remoteParams.fundingPubKey, sig, commit.signature) if (Transactions.checkSpendable(signedCommitTx).isFailure) { throw InvalidCommitmentSignature(commitments.channelId, signedCommitTx.tx) } @@ -424,7 +424,7 @@ object Commitments { if (commit.htlcSignatures.size != sortedHtlcTxs.size) { throw new HtlcSigCountMismatch(commitments.channelId, sortedHtlcTxs.size, commit.htlcSignatures.size) } - val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), localPerCommitmentPoint)) + val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.deterministicHtlcPoint(localParams), localPerCommitmentPoint)) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint) // combine the sigs to make signed txes val htlcTxsAndSigs = (sortedHtlcTxs, htlcSigs, commit.htlcSignatures).zipped.toList.collect { @@ -442,8 +442,8 @@ object Commitments { } // we will send our revocation preimage + our next revocation hash - val localPerCommitmentSecret = keyManager.commitmentSecret(localParams.channelKeyPath, commitments.localCommit.index) - val localNextPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index + 2) + val localPerCommitmentSecret = keyManager.deterministicCommitmentSecret(localParams, commitments.localCommit.index) + val localNextPerCommitmentPoint = keyManager.deterministicCommitmentPoint(localParams, commitments.localCommit.index + 2) val revocation = RevokeAndAck( channelId = commitments.channelId, perCommitmentSecret = localPerCommitmentSecret, @@ -504,23 +504,23 @@ object Commitments { } def makeLocalTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: Point, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { - val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) - val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) + val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.deterministicDelayedPaymentPoint(localParams).publicKey, localPerCommitmentPoint) + val localHtlcPubkey = Generators.derivePubKey(keyManager.deterministicHtlcPoint(localParams).publicKey, localPerCommitmentPoint) val remotePaymentPubkey = Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint) val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) - val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remoteParams.paymentBasepoint, localParams.isFunder, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) + val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, keyManager.deterministicPaymentPoint(localParams).publicKey, remoteParams.paymentBasepoint, localParams.isFunder, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } def makeRemoteTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: Point, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { - val localPaymentPubkey = Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) - val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) + val localPaymentPubkey = Generators.derivePubKey(keyManager.deterministicPaymentPoint(localParams).publicKey, remotePerCommitmentPoint) + val localHtlcPubkey = Generators.derivePubKey(keyManager.deterministicHtlcPoint(localParams).publicKey, remotePerCommitmentPoint) val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint) - val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) - val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, !localParams.isFunder, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) + val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.deterministicRevocationPoint(localParams).publicKey, remotePerCommitmentPoint) + val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, keyManager.deterministicPaymentPoint(localParams).publicKey, !localParams.isFunder, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index b1072a4a96..8fc260b798 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -205,7 +205,7 @@ object Helpers { def makeAnnouncementSignatures(nodeParams: NodeParams, commitments: Commitments, shortChannelId: ShortChannelId) = { val features = ByteVector.empty // empty features for now - val (localNodeSig, localBitcoinSig) = nodeParams.keyManager.signChannelAnnouncement(commitments.localParams.channelKeyPath, nodeParams.chainHash, shortChannelId, commitments.remoteParams.nodeId, commitments.remoteParams.fundingPubKey, features) + val (localNodeSig, localBitcoinSig) = nodeParams.keyManager.deterministicSignChannelAnnouncement(commitments.localParams, nodeParams.chainHash, shortChannelId, commitments.remoteParams.nodeId, commitments.remoteParams.fundingPubKey, features) AnnouncementSignatures(commitments.channelId, shortChannelId, localNodeSig, localBitcoinSig) } @@ -269,8 +269,8 @@ object Helpers { } } - val commitmentInput = makeFundingInputInfo(fundingTxHash, fundingTxOutputIndex, Satoshi(fundingSatoshis), keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey) - val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 0) + val commitmentInput = makeFundingInputInfo(fundingTxHash, fundingTxOutputIndex, Satoshi(fundingSatoshis), keyManager.deterministicFundingPublicKey(localParams).publicKey, remoteParams.fundingPubKey) + val localPerCommitmentPoint = keyManager.deterministicCommitmentPoint(localParams, 0) val (localCommitTx, _, _) = Commitments.makeLocalTxs(keyManager, 0, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, localSpec) val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, 0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, remoteSpec) @@ -441,7 +441,7 @@ object Helpers { // TODO: check that val dustLimitSatoshis = Satoshi(Math.max(localParams.dustLimitSatoshis, remoteParams.dustLimitSatoshis)) val closingTx = Transactions.makeClosingTx(commitInput, localScriptPubkey, remoteScriptPubkey, localParams.isFunder, dustLimitSatoshis, closingFee, localCommit.spec) - val localClosingSig = keyManager.sign(closingTx, keyManager.fundingPublicKey(commitments.localParams.channelKeyPath)) + val localClosingSig = keyManager.sign(closingTx, keyManager.deterministicFundingPublicKey(commitments.localParams)) val closingSigned = ClosingSigned(channelId, closingFee.amount, localClosingSig) log.info(s"signed closing txid=${closingTx.tx.txid} with closingFeeSatoshis=${closingSigned.feeSatoshis}") log.debug(s"closingTxid=${closingTx.tx.txid} closingTx=${closingTx.tx}}") @@ -456,7 +456,7 @@ object Helpers { throw InvalidCloseFee(commitments.channelId, remoteClosingFee.amount) } val (closingTx, closingSigned) = makeClosingTx(keyManager, commitments, localScriptPubkey, remoteScriptPubkey, remoteClosingFee) - val signedClosingTx = Transactions.addSigs(closingTx, keyManager.fundingPublicKey(commitments.localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, closingSigned.signature, remoteClosingSig) + val signedClosingTx = Transactions.addSigs(closingTx, keyManager.deterministicFundingPublicKey(commitments.localParams).publicKey, remoteParams.fundingPubKey, closingSigned.signature, remoteClosingSig) Transactions.checkSpendable(signedClosingTx).map(x => signedClosingTx.tx).recover { case _ => throw InvalidCloseSignature(commitments.channelId, signedClosingTx.tx) } } @@ -486,9 +486,9 @@ object Helpers { import commitments._ require(localCommit.publishableTxs.commitTx.tx.txid == tx.txid, "txid mismatch, provided tx is not the current local commit tx") - val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index.toInt) + val localPerCommitmentPoint = keyManager.deterministicCommitmentPoint(localParams, commitments.localCommit.index.toInt) val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) - val localDelayedPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) + val localDelayedPubkey = Generators.derivePubKey(keyManager.deterministicDelayedPaymentPoint(localParams).publicKey, localPerCommitmentPoint) // no need to use a high fee rate for delayed transactions (we are the only one who can spend them) val feeratePerKwDelayed = Globals.feeratesPerKw.get.blocks_6 @@ -496,7 +496,7 @@ object Helpers { // first we will claim our main output as soon as the delay is over val mainDelayedTx = generateTx("main-delayed-output")(Try { val claimDelayed = Transactions.makeClaimDelayedOutputTx(tx, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwDelayed) - val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(localParams.channelKeyPath), localPerCommitmentPoint) + val sig = keyManager.sign(claimDelayed, keyManager.deterministicDelayedPaymentPoint(localParams), localPerCommitmentPoint) Transactions.addSigs(claimDelayed, sig) }) @@ -531,7 +531,7 @@ object Helpers { remoteParams.toSelfDelay, localDelayedPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwDelayed) - val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(localParams.channelKeyPath), localPerCommitmentPoint) + val sig = keyManager.sign(claimDelayed, keyManager.deterministicDelayedPaymentPoint(localParams), localPerCommitmentPoint) Transactions.addSigs(claimDelayed, sig) }) } @@ -560,10 +560,10 @@ object Helpers { val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec) require(remoteCommitTx.tx.txid == tx.txid, "txid mismatch, cannot recompute the current remote commit tx") - val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint) + val localHtlcPubkey = Generators.derivePubKey(keyManager.deterministicHtlcPoint(localParams).publicKey, remoteCommit.remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remoteCommit.remotePerCommitmentPoint) - val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index.toInt) - val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint) + val localPerCommitmentPoint = keyManager.deterministicCommitmentPoint(localParams, commitments.localCommit.index.toInt) + val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.deterministicRevocationPoint(localParams).publicKey, remoteCommit.remotePerCommitmentPoint) // we need to use a rather high fee for htlc-claim because we compete with the counterparty val feeratePerKwHtlc = Globals.feeratesPerKw.get.blocks_2 @@ -579,7 +579,7 @@ object Helpers { val preimage = preimages.find(r => sha256(r) == add.paymentHash).get val txinfo = Transactions.makeClaimHtlcSuccessTx(remoteCommitTx.tx, outputsAlreadyUsed, Satoshi(localParams.dustLimitSatoshis), localHtlcPubkey, remoteHtlcPubkey, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, add, feeratePerKwHtlc) outputsAlreadyUsed = outputsAlreadyUsed + txinfo.input.outPoint.index.toInt - val sig = keyManager.sign(txinfo, keyManager.htlcPoint(localParams.channelKeyPath), remoteCommit.remotePerCommitmentPoint) + val sig = keyManager.sign(txinfo, keyManager.deterministicHtlcPoint(localParams), remoteCommit.remotePerCommitmentPoint) Transactions.addSigs(txinfo, sig, preimage) }) @@ -589,7 +589,7 @@ object Helpers { case DirectedHtlc(IN, add: UpdateAddHtlc) => generateTx("claim-htlc-timeout")(Try { val txinfo = Transactions.makeClaimHtlcTimeoutTx(remoteCommitTx.tx, outputsAlreadyUsed, Satoshi(localParams.dustLimitSatoshis), localHtlcPubkey, remoteHtlcPubkey, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, add, feeratePerKwHtlc) outputsAlreadyUsed = outputsAlreadyUsed + txinfo.input.outPoint.index.toInt - val sig = keyManager.sign(txinfo, keyManager.htlcPoint(localParams.channelKeyPath), remoteCommit.remotePerCommitmentPoint) + val sig = keyManager.sign(txinfo, keyManager.deterministicHtlcPoint(localParams), remoteCommit.remotePerCommitmentPoint) Transactions.addSigs(txinfo, sig) }) }.toSeq.flatten @@ -612,7 +612,7 @@ object Helpers { * @return a list of transactions (one per HTLC that we can claim) */ def claimRemoteCommitMainOutput(keyManager: KeyManager, commitments: Commitments, remotePerCommitmentPoint: Point, tx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = { - val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(commitments.localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) + val localPubkey = Generators.derivePubKey(keyManager.deterministicPaymentPoint(commitments.localParams).publicKey, remotePerCommitmentPoint) // no need to use a high fee rate for our main output (we are the only one who can spend it) val feeratePerKwMain = Globals.feeratesPerKw.get.blocks_6 @@ -620,7 +620,7 @@ object Helpers { val mainTx = generateTx("claim-p2wpkh-output")(Try { val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, Satoshi(commitments.localParams.dustLimitSatoshis), localPubkey, commitments.localParams.defaultFinalScriptPubKey, feeratePerKwMain) - val sig = keyManager.sign(claimMain, keyManager.paymentPoint(commitments.localParams.channelKeyPath), remotePerCommitmentPoint) + val sig = keyManager.sign(claimMain, keyManager.deterministicPaymentPoint(commitments.localParams), remotePerCommitmentPoint) Transactions.addSigs(claimMain, localPubkey, sig) }) @@ -647,7 +647,7 @@ object Helpers { require(tx.txIn.size == 1, "commitment tx should have 1 input") val obscuredTxNumber = Transactions.decodeTxNumber(tx.txIn(0).sequence, tx.lockTime) // this tx has been published by remote, so we need to invert local/remote params - val txnumber = Transactions.obscuredCommitTxNumber(obscuredTxNumber, !localParams.isFunder, remoteParams.paymentBasepoint, keyManager.paymentPoint(localParams.channelKeyPath).publicKey) + val txnumber = Transactions.obscuredCommitTxNumber(obscuredTxNumber, !localParams.isFunder, remoteParams.paymentBasepoint, keyManager.deterministicPaymentPoint(localParams).publicKey) require(txnumber <= 0xffffffffffffL, "txnumber must be lesser than 48 bits long") log.warning(s"a revoked commit has been published with txnumber=$txnumber") // now we know what commit number this tx is referring to, we can derive the commitment point from the shachain @@ -656,9 +656,9 @@ object Helpers { .map { remotePerCommitmentSecret => val remotePerCommitmentPoint = remotePerCommitmentSecret.toPoint val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) - val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) - val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) - val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) + val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.deterministicRevocationPoint(localParams).publicKey, remotePerCommitmentPoint) + val localPubkey = Generators.derivePubKey(keyManager.deterministicPaymentPoint(localParams).publicKey, remotePerCommitmentPoint) + val localHtlcPubkey = Generators.derivePubKey(keyManager.deterministicHtlcPoint(localParams).publicKey, remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint) // no need to use a high fee rate for our main output (we are the only one who can spend it) @@ -669,14 +669,14 @@ object Helpers { // first we will claim our main output right away val mainTx = generateTx("claim-p2wpkh-output")(Try { val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, Satoshi(localParams.dustLimitSatoshis), localPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwMain) - val sig = keyManager.sign(claimMain, keyManager.paymentPoint(localParams.channelKeyPath), remotePerCommitmentPoint) + val sig = keyManager.sign(claimMain, keyManager.deterministicPaymentPoint(localParams), remotePerCommitmentPoint) Transactions.addSigs(claimMain, localPubkey, sig) }) // then we punish them by stealing their main output val mainPenaltyTx = generateTx("main-penalty")(Try { val txinfo = Transactions.makeMainPenaltyTx(tx, Satoshi(localParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, feeratePerKwPenalty) - val sig = keyManager.sign(txinfo, keyManager.revocationPoint(localParams.channelKeyPath), remotePerCommitmentSecret) + val sig = keyManager.sign(txinfo, keyManager.deterministicRevocationPoint(localParams), remotePerCommitmentSecret) Transactions.addSigs(txinfo, sig) }) @@ -697,7 +697,7 @@ object Helpers { generateTx("htlc-penalty")(Try { val htlcPenalty = Transactions.makeHtlcPenaltyTx(tx, outputsAlreadyUsed, htlcRedeemScript, Satoshi(localParams.dustLimitSatoshis), localParams.defaultFinalScriptPubKey, feeratePerKwPenalty) outputsAlreadyUsed = outputsAlreadyUsed + htlcPenalty.input.outPoint.index.toInt - val sig = keyManager.sign(htlcPenalty, keyManager.revocationPoint(localParams.channelKeyPath), remotePerCommitmentSecret) + val sig = keyManager.sign(htlcPenalty, keyManager.deterministicRevocationPoint(localParams), remotePerCommitmentSecret) Transactions.addSigs(htlcPenalty, sig, remoteRevocationPubkey) }) }.toList.flatten @@ -738,21 +738,21 @@ object Helpers { val tx = revokedCommitPublished.commitTx val obscuredTxNumber = Transactions.decodeTxNumber(tx.txIn(0).sequence, tx.lockTime) // this tx has been published by remote, so we need to invert local/remote params - val txnumber = Transactions.obscuredCommitTxNumber(obscuredTxNumber, !localParams.isFunder, remoteParams.paymentBasepoint, keyManager.paymentPoint(localParams.channelKeyPath).publicKey) + val txnumber = Transactions.obscuredCommitTxNumber(obscuredTxNumber, !localParams.isFunder, remoteParams.paymentBasepoint, keyManager.deterministicPaymentPoint(localParams).publicKey) // now we know what commit number this tx is referring to, we can derive the commitment point from the shachain remotePerCommitmentSecrets.getHash(0xFFFFFFFFFFFFL - txnumber) .map(d => Scalar(d)) .flatMap { remotePerCommitmentSecret => val remotePerCommitmentPoint = remotePerCommitmentSecret.toPoint val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) - val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) + val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.deterministicRevocationPoint(localParams).publicKey, remotePerCommitmentPoint) // we need to use a high fee here for punishment txes because after a delay they can be spent by the counterparty val feeratePerKwPenalty = Globals.feeratesPerKw.get.block_1 generateTx("claim-htlc-delayed-penalty")(Try { val htlcDelayedPenalty = Transactions.makeClaimDelayedOutputPenaltyTx(htlcTx, Satoshi(localParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwPenalty) - val sig = keyManager.sign(htlcDelayedPenalty, keyManager.revocationPoint(localParams.channelKeyPath), remotePerCommitmentSecret) + val sig = keyManager.sign(htlcDelayedPenalty, keyManager.deterministicRevocationPoint(localParams), remotePerCommitmentSecret) val signedTx = Transactions.addSigs(htlcDelayedPenalty, sig) // we need to make sure that the tx is indeed valid Transaction.correctlySpends(signedTx.tx, Seq(htlcTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/KeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/KeyManager.scala index 34f73f6156..f1ec5c4ce1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/KeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/KeyManager.scala @@ -20,6 +20,7 @@ import fr.acinq.bitcoin.Crypto.{Point, PublicKey, Scalar} import fr.acinq.bitcoin.DeterministicWallet.ExtendedPublicKey import fr.acinq.bitcoin.{ByteVector32, Crypto, DeterministicWallet} import fr.acinq.eclair.ShortChannelId +import fr.acinq.eclair.channel.LocalParams import fr.acinq.eclair.transactions.Transactions.TransactionWithInputInfo import scodec.bits.ByteVector @@ -42,6 +43,25 @@ trait KeyManager { def commitmentPoint(channelKeyPath: DeterministicWallet.KeyPath, index: Long): Crypto.Point + /** + * DETERMINISTIC + * + */ + + def deterministicFundingPublicKey(localParams: LocalParams): ExtendedPublicKey + + def deterministicRevocationPoint(localParams: LocalParams): ExtendedPublicKey + + def deterministicPaymentPoint(localParams: LocalParams): ExtendedPublicKey + + def deterministicDelayedPaymentPoint(localParams: LocalParams): ExtendedPublicKey + + def deterministicHtlcPoint(localParams: LocalParams): ExtendedPublicKey + + def deterministicCommitmentSecret(localParams: LocalParams, index: Long): Crypto.Scalar + + def deterministicCommitmentPoint(localParams: LocalParams, index: Long): Crypto.Point + /** * * @param tx input transaction @@ -74,4 +94,7 @@ trait KeyManager { def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remoteSecret: Scalar): ByteVector def signChannelAnnouncement(channelKeyPath: DeterministicWallet.KeyPath, chainHash: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: ByteVector): (ByteVector, ByteVector) + + def deterministicSignChannelAnnouncement(localParams: LocalParams, chainHash: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: ByteVector): (ByteVector, ByteVector) + } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala index 5583401922..f404c125b7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala @@ -21,6 +21,7 @@ import fr.acinq.bitcoin.Crypto.{Point, PublicKey, Scalar} import fr.acinq.bitcoin.DeterministicWallet.{derivePrivateKey, _} import fr.acinq.bitcoin.{Block, ByteVector32, Crypto, DeterministicWallet} import fr.acinq.eclair.ShortChannelId +import fr.acinq.eclair.channel.LocalParams import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.transactions.Transactions.TransactionWithInputInfo @@ -49,6 +50,13 @@ object LocalKeyManager { * @param seed seed from which keys will be derived */ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyManager { + + private def funderDerivationPath(entropy: ByteVector32) = Seq(47, 2, entropy.toLong(), 0) + + private def fundeeDerivationPath(entropy: ByteVector32) = Seq(47, 2, entropy.toLong(), 1) + + private def fundeePubkeyDerivationPath(entropy: ByteVector32) = Seq(47, 2, entropy.toLong(), 2) + private val master = DeterministicWallet.generate(seed) override val nodeKey = DeterministicWallet.derivePrivateKey(master, LocalKeyManager.nodeKeyBasePath(chainHash)) @@ -94,6 +102,76 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana override def commitmentPoint(channelKeyPath: DeterministicWallet.KeyPath, index: Long) = Generators.perCommitPoint(shaSeed(channelKeyPath), index) + /** + * DETERMINISTIC + */ + + override def deterministicFundingPublicKey(localParams: LocalParams): ExtendedPublicKey = { + val keyPath = localParams.channelKeyPath match { + case Left(kp) => kp + case Right(keyPathFundee) => keyPathFundee.publicKeyPath + } + + fundingPublicKey(keyPath) + } + + override def deterministicRevocationPoint(localParams: LocalParams): ExtendedPublicKey = { + val keyPath = localParams.channelKeyPath match { + case Left(kp) => kp + case Right(keyPathFundee) => keyPathFundee.pointsKeyPath + } + + revocationPoint(keyPath) + } + + override def deterministicPaymentPoint(localParams: LocalParams): ExtendedPublicKey = { + val keyPath = localParams.channelKeyPath match { + case Left(kp) => kp + case Right(keyPathFundee) => keyPathFundee.pointsKeyPath + } + + paymentPoint(keyPath) + + } + + override def deterministicDelayedPaymentPoint(localParams: LocalParams): ExtendedPublicKey = { + val keyPath = localParams.channelKeyPath match { + case Left(kp) => kp + case Right(keyPathFundee) => keyPathFundee.pointsKeyPath + } + + delayedPaymentPoint(keyPath) + + } + + override def deterministicHtlcPoint(localParams: LocalParams): ExtendedPublicKey = { + val keyPath = localParams.channelKeyPath match { + case Left(kp) => kp + case Right(keyPathFundee) => keyPathFundee.pointsKeyPath + } + + htlcPoint(keyPath) + + } + + override def deterministicCommitmentSecret(localParams: LocalParams, index: Long): Scalar = { + val keyPath = localParams.channelKeyPath match { + case Left(kp) => kp + case Right(keyPathFundee) => keyPathFundee.pointsKeyPath + } + + commitmentSecret(keyPath, index) + } + + override def deterministicCommitmentPoint(localParams: LocalParams, index: Long): Point = { + val keyPath = localParams.channelKeyPath match { + case Left(kp) => kp + case Right(keyPathFundee) => keyPathFundee.pointsKeyPath + } + + commitmentPoint(keyPath, index) + } + /** * * @param tx input transaction @@ -137,6 +215,8 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana } override def signChannelAnnouncement(channelKeyPath: DeterministicWallet.KeyPath, chainHash: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: ByteVector): (ByteVector, ByteVector) = { + + val witness = if (Announcements.isNode1(nodeId, remoteNodeId)) { Announcements.channelAnnouncementWitnessEncode(chainHash, shortChannelId, nodeId, remoteNodeId, fundingPublicKey(channelKeyPath).publicKey, remoteFundingKey, features) } else { @@ -146,4 +226,15 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana val bitcoinSig = Crypto.encodeSignature(Crypto.sign(witness, fundingPrivateKey(channelKeyPath).privateKey)) :+ 1.toByte (nodeSig, bitcoinSig) } + + override def deterministicSignChannelAnnouncement(localParams: LocalParams, chainHash: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: ByteVector): (ByteVector, ByteVector) = { + + val keyPath = localParams.channelKeyPath match { + case Left(kp) => kp + case Right(keyPathFundee) => keyPathFundee.publicKeyPath + } + + signChannelAnnouncement(keyPath, chainHash, shortChannelId, remoteNodeId, remoteFundingKey, features) + } + } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index 0abfb6ba22..c79faca327 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -592,7 +592,7 @@ object Peer { def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, channelKeyPath: DeterministicWallet.KeyPath): LocalParams = { LocalParams( nodeParams.nodeId, - channelKeyPath, + Left(channelKeyPath), dustLimitSatoshis = nodeParams.dustLimitSatoshis, maxHtlcValueInFlightMsat = nodeParams.maxHtlcValueInFlightMsat, channelReserveSatoshis = Math.max((nodeParams.reserveToFundingRatio * fundingSatoshis).toLong, nodeParams.dustLimitSatoshis), // BOLT #2: make sure that our reserve is above our dust limit diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala index 9f5c12b2c2..41fd7abc32 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala @@ -43,6 +43,12 @@ import scala.concurrent.duration._ object ChannelCodecs extends Logging { val keyPathCodec: Codec[KeyPath] = ("path" | listOfN(uint16, uint32)).xmap[KeyPath](l => new KeyPath(l), keyPath => keyPath.path.toList).as[KeyPath] + val keyPathFundee: Codec[KeyPathFundee] = ( + ("publicKeyPath" | keyPathCodec) :: + ("pointsKeyPath" | keyPathCodec) + ).as[KeyPathFundee] + + val newKeyPathCodec = either(bool, keyPathCodec, keyPathFundee) val extendedPrivateKeyCodec: Codec[ExtendedPrivateKey] = ( ("secretkeybytes" | bytes32) :: @@ -53,7 +59,7 @@ object ChannelCodecs extends Logging { val localParamsCodec: Codec[LocalParams] = ( ("nodeId" | publicKey) :: - ("channelPath" | keyPathCodec) :: + ("channelPath" | newKeyPathCodec) :: ("dustLimitSatoshis" | uint64) :: ("maxHtlcValueInFlightMsat" | uint64ex) :: ("channelReserveSatoshis" | uint64) :: @@ -315,4 +321,26 @@ object ChannelCodecs extends Logging { .typecase(0x06, DATA_CLOSING_Codec) .typecase(0x07, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec) + +// private def stateDataCodec(commitmentVersion: CommitmentVersion): Codec[HasCommitments] = discriminated[HasCommitments].by(uint16) +// .typecase(0x08, DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitmentVersion)) +// .typecase(0x01, DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec(commitmentVersion)) +// .typecase(0x02, DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitmentVersion)) +// .typecase(0x03, DATA_NORMAL_Codec(commitmentVersion)) +// .typecase(0x04, DATA_SHUTDOWN_Codec(commitmentVersion)) +// .typecase(0x05, DATA_NEGOTIATING_Codec(commitmentVersion)) +// .typecase(0x06, DATA_CLOSING_Codec(commitmentVersion)) +// .typecase(0x07, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitmentVersion)) +// +// val COMMITMENTv0_VERSION_BYTE = 0x00.toByte +// val COMMITMENTv1_VERSION_BYTE = 0x01.toByte +// +// val genericStateDataCodec = discriminated[HasCommitments].by(uint8) +// .\ (COMMITMENTv1_VERSION_BYTE) { case c if c.commitments.version == CommitmentV0 => c } (stateDataCodec(CommitmentV0)) +// .\ (COMMITMENTv0_VERSION_BYTE) { case c if c.commitments.version == CommitmentV1 => c } (stateDataCodec(CommitmentV1)) +// +// sealed trait CommitmentVersion +// case object CommitmentV0 extends CommitmentVersion +// case object CommitmentV1 extends CommitmentVersion + } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index 08f72962e9..eba92d5a21 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -22,7 +22,7 @@ import fr.acinq.bitcoin.Crypto.PrivateKey import fr.acinq.bitcoin.DeterministicWallet.KeyPath import fr.acinq.bitcoin.{Block, ByteVector32, Script} import fr.acinq.eclair.NodeParams.BITCOIND -import fr.acinq.eclair.channel.Channel +import fr.acinq.eclair.channel.{Channel, KeyPathFundee} import fr.acinq.eclair.crypto.LocalKeyManager import fr.acinq.eclair.db._ import fr.acinq.eclair.db.sqlite._ @@ -107,7 +107,7 @@ object TestConstants { defaultFinalScriptPubKey = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32, compressed = true).publicKey)), isFunder = true, fundingSatoshis, - KeyPath(Seq(1, 2, 3, 4L)) + Left(KeyPath(Seq(1, 2, 3, 4L))) ).copy( channelReserveSatoshis = 10000 // Bob will need to keep that much satoshis as direct payment ) @@ -173,7 +173,7 @@ object TestConstants { defaultFinalScriptPubKey = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32, compressed = true).publicKey)), isFunder = false, fundingSatoshis, - KeyPath(Seq(1, 2, 3, 4L)) + Right(KeyPathFundee(KeyPath(Seq(1, 2, 3, 4L)), KeyPath(Seq(1, 2, 3, 4L)))) ).copy( channelReserveSatoshis = 20000 // Alice will need to keep that much satoshis as direct payment ) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index a4cca3bad4..5ffa8e4005 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -2057,7 +2057,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42)) val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures] import initialState.commitments.{localParams, remoteParams} - val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) + val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.keyManager.deterministicFundingPublicKey(localParams).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) // actual test starts here bob2alice.forward(alice) awaitCond({ @@ -2076,7 +2076,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 42, 10)) val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures] import initialState.commitments.{localParams, remoteParams} - val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) + val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.keyManager.deterministicFundingPublicKey(localParams).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) bob2alice.forward(alice) awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement === Some(channelAnn)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala index f864e492f9..29b6a5f9fe 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala @@ -80,8 +80,8 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val bobCommitments = bob.stateData.asInstanceOf[HasCommitments].commitments val aliceCommitments = alice.stateData.asInstanceOf[HasCommitments].commitments - val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.commitmentPoint(bobCommitments.localParams.channelKeyPath, bobCommitments.localCommit.index) - val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.commitmentPoint(aliceCommitments.localParams.channelKeyPath, aliceCommitments.localCommit.index) + val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.deterministicCommitmentPoint(bobCommitments.localParams, bobCommitments.localCommit.index) + val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.deterministicCommitmentPoint(aliceCommitments.localParams, aliceCommitments.localCommit.index) // a didn't receive any update or sig @@ -164,8 +164,8 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val bobCommitments = bob.stateData.asInstanceOf[HasCommitments].commitments val aliceCommitments = alice.stateData.asInstanceOf[HasCommitments].commitments - val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.commitmentPoint(bobCommitments.localParams.channelKeyPath, bobCommitments.localCommit.index) - val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.commitmentPoint(aliceCommitments.localParams.channelKeyPath, aliceCommitments.localCommit.index) + val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.deterministicCommitmentPoint(bobCommitments.localParams, bobCommitments.localCommit.index) + val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.deterministicCommitmentPoint(aliceCommitments.localParams, aliceCommitments.localCommit.index) // a didn't receive the sig val ab_reestablish = alice2bob.expectMsg(ChannelReestablish(ab_add_0.channelId, 1, 0, Some(Scalar(ByteVector32.Zeroes)), Some(aliceCurrentPerCommitmentPoint))) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala index 5b1b53f837..25eabb8368 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala @@ -53,7 +53,7 @@ object ChannelStateSpec { val keyManager = new LocalKeyManager(ByteVector32(ByteVector.fill(32)(1)), Block.RegtestGenesisBlock.hash) val localParams = LocalParams( keyManager.nodeId, - channelKeyPath = DeterministicWallet.KeyPath(Seq(42L)), + channelKeyPath = Left(DeterministicWallet.KeyPath(Seq(42L))), dustLimitSatoshis = Satoshi(546).toLong, maxHtlcValueInFlightMsat = UInt64(50000000), channelReserveSatoshis = 10000, @@ -99,7 +99,7 @@ object ChannelStateSpec { val fundingTx = Transaction.read("0200000001adbb20ea41a8423ea937e76e8151636bf6093b70eaff942930d20576600521fd000000006b48304502210090587b6201e166ad6af0227d3036a9454223d49a1f11839c1a362184340ef0240220577f7cd5cca78719405cbf1de7414ac027f0239ef6e214c90fcaab0454d84b3b012103535b32d5eb0a6ed0982a0479bbadc9868d9836f6ba94dd5a63be16d875069184ffffffff028096980000000000220020c015c4a6be010e21657068fc2e6a9d02b27ebe4d490a25846f7237f104d1a3cd20256d29010000001600143ca33c2e4446f4a305f23c80df8ad1afdcf652f900000000") val fundingAmount = fundingTx.txOut(0).amount - val commitmentInput = Funding.makeFundingInputInfo(fundingTx.hash, 0, fundingAmount, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey) + val commitmentInput = Funding.makeFundingInputInfo(fundingTx.hash, 0, fundingAmount, keyManager.deterministicFundingPublicKey(localParams).publicKey, remoteParams.fundingPubKey) val localCommit = LocalCommit(0, CommitmentSpec(htlcs.toSet, 1500, 50000000, 70000000), PublishableTxs(CommitTx(commitmentInput, Transaction(2, Nil, Nil, 0)), Nil)) val remoteCommit = RemoteCommit(0, CommitmentSpec(htlcs.map(htlc => htlc.copy(direction = htlc.direction.opposite)).toSet, 1500, 50000, 700000), ByteVector32(hex"0303030303030303030303030303030303030303030303030303030303030303"), Scalar(ByteVector.fill(32)(4)).toPoint) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala index 93b5db5757..4a2cd7e5b2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala @@ -20,7 +20,7 @@ import java.util.UUID import akka.actor.ActorSystem import fr.acinq.bitcoin.DeterministicWallet.KeyPath -import fr.acinq.bitcoin.{DeterministicWallet, OutPoint} +import fr.acinq.bitcoin.{Crypto, DeterministicWallet, OutPoint} import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.Sphinx import fr.acinq.eclair.payment.{Local, Relayed} @@ -58,7 +58,7 @@ class ChannelCodecsSpec extends FunSuite { test("encode/decode localparams") { val o = LocalParams( nodeId = randomKey.publicKey, - channelKeyPath = DeterministicWallet.KeyPath(Seq(42L)), + channelKeyPath = Left(DeterministicWallet.KeyPath(Seq(42L))), dustLimitSatoshis = Random.nextInt(Int.MaxValue), maxHtlcValueInFlightMsat = UInt64(Random.nextInt(Int.MaxValue)), channelReserveSatoshis = Random.nextInt(Int.MaxValue), @@ -66,7 +66,7 @@ class ChannelCodecsSpec extends FunSuite { toSelfDelay = Random.nextInt(Short.MaxValue), maxAcceptedHtlcs = Random.nextInt(Short.MaxValue), defaultFinalScriptPubKey = randomBytes(10 + Random.nextInt(200)), - isFunder = Random.nextBoolean(), + isFunder = true, globalFeatures = randomBytes(256), localFeatures = randomBytes(256)) val encoded = localParamsCodec.encode(o).require From 3980fa02729b21f83507c926773a78348f7a82d5 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 5 Jun 2019 15:49:27 +0200 Subject: [PATCH 11/43] Bump commitment version for scodecs --- .../fr/acinq/eclair/wire/ChannelCodecs.scala | 109 +++++++++--------- .../fr/acinq/eclair/db/ChannelStateSpec.scala | 5 +- .../acinq/eclair/wire/ChannelCodecsSpec.scala | 29 ++++- 3 files changed, 80 insertions(+), 63 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala index 41fd7abc32..8fb27632c4 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala @@ -31,9 +31,8 @@ import grizzled.slf4j.Logging import scodec.bits.BitVector import scodec.codecs._ import scodec.{Attempt, Codec} -import scala.concurrent.duration._ -import scala.compat.Platform +import scala.compat.Platform import scala.concurrent.duration._ @@ -45,10 +44,10 @@ object ChannelCodecs extends Logging { val keyPathCodec: Codec[KeyPath] = ("path" | listOfN(uint16, uint32)).xmap[KeyPath](l => new KeyPath(l), keyPath => keyPath.path.toList).as[KeyPath] val keyPathFundee: Codec[KeyPathFundee] = ( ("publicKeyPath" | keyPathCodec) :: - ("pointsKeyPath" | keyPathCodec) - ).as[KeyPathFundee] + ("pointsKeyPath" | keyPathCodec) + ).as[KeyPathFundee] - val newKeyPathCodec = either(bool, keyPathCodec, keyPathFundee) + val channelKeyPathCodec = either(bool, keyPathCodec, keyPathFundee) val extendedPrivateKeyCodec: Codec[ExtendedPrivateKey] = ( ("secretkeybytes" | bytes32) :: @@ -57,9 +56,15 @@ object ChannelCodecs extends Logging { ("path" | keyPathCodec) :: ("parent" | int64)).as[ExtendedPrivateKey] - val localParamsCodec: Codec[LocalParams] = ( + def localParamsCodec(version: CommitmentVersion): Codec[LocalParams] = ( ("nodeId" | publicKey) :: - ("channelPath" | newKeyPathCodec) :: + (version match { + case CommitmentV0 => ("channelPath" | keyPathCodec.xmap[Either[KeyPath, KeyPathFundee]](Left(_), { + case Left(kp) => kp + case Right(_) => throw new IllegalArgumentException(s"Found unexpected value for version=$version") + })) + case CommitmentV1 => ("channelPath" | channelKeyPathCodec) + }) :: ("dustLimitSatoshis" | uint64) :: ("maxHtlcValueInFlightMsat" | uint64ex) :: ("channelReserveSatoshis" | uint64) :: @@ -201,8 +206,8 @@ object ChannelCodecs extends Logging { (wire: BitVector) => spentListCodec.decode(wire).map(_.map(_.toMap)) ) - val commitmentsCodec: Codec[Commitments] = ( - ("localParams" | localParamsCodec) :: + def commitmentsCodec(version: CommitmentVersion): Codec[Commitments] = ( + ("localParams" | localParamsCodec(version)) :: ("remoteParams" | remoteParamsCodec) :: ("channelFlags" | byte) :: ("localCommit" | localCommitCodec) :: @@ -245,27 +250,27 @@ object ChannelCodecs extends Logging { ("spent" | spentMapCodec)).as[RevokedCommitPublished] // this is a decode-only codec compatible with versions 997acee and below, with placeholders for new fields - val DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( - ("commitments" | commitmentsCodec) :: + def DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec(version: CommitmentVersion): Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( + ("commitments" | commitmentsCodec(version)) :: ("fundingTx" | provide[Option[Transaction]](None)) :: ("waitingSince" | provide(Platform.currentTime.milliseconds.toSeconds)) :: ("deferred" | optional(bool, fundingLockedCodec)) :: ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED].decodeOnly - val DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( - ("commitments" | commitmentsCodec) :: + def DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(version: CommitmentVersion): Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( + ("commitments" | commitmentsCodec(version)) :: ("fundingTx" | optional(bool, txCodec)) :: ("waitingSince" | int64) :: ("deferred" | optional(bool, fundingLockedCodec)) :: ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED] - val DATA_WAIT_FOR_FUNDING_LOCKED_Codec: Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = ( - ("commitments" | commitmentsCodec) :: + def DATA_WAIT_FOR_FUNDING_LOCKED_Codec(version: CommitmentVersion): Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = ( + ("commitments" | commitmentsCodec(version)) :: ("shortChannelId" | shortchannelid) :: ("lastSent" | fundingLockedCodec)).as[DATA_WAIT_FOR_FUNDING_LOCKED] - val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( - ("commitments" | commitmentsCodec) :: + def DATA_NORMAL_Codec(version: CommitmentVersion): Codec[DATA_NORMAL] = ( + ("commitments" | commitmentsCodec(version)) :: ("shortChannelId" | shortchannelid) :: ("buried" | bool) :: ("channelAnnouncement" | optional(bool, channelAnnouncementCodec)) :: @@ -273,20 +278,20 @@ object ChannelCodecs extends Logging { ("localShutdown" | optional(bool, shutdownCodec)) :: ("remoteShutdown" | optional(bool, shutdownCodec))).as[DATA_NORMAL] - val DATA_SHUTDOWN_Codec: Codec[DATA_SHUTDOWN] = ( - ("commitments" | commitmentsCodec) :: + def DATA_SHUTDOWN_Codec(version: CommitmentVersion): Codec[DATA_SHUTDOWN] = ( + ("commitments" | commitmentsCodec(version)) :: ("localShutdown" | shutdownCodec) :: ("remoteShutdown" | shutdownCodec)).as[DATA_SHUTDOWN] - val DATA_NEGOTIATING_Codec: Codec[DATA_NEGOTIATING] = ( - ("commitments" | commitmentsCodec) :: + def DATA_NEGOTIATING_Codec(version: CommitmentVersion): Codec[DATA_NEGOTIATING] = ( + ("commitments" | commitmentsCodec(version)) :: ("localShutdown" | shutdownCodec) :: ("remoteShutdown" | shutdownCodec) :: ("closingTxProposed" | listOfN(uint16, listOfN(uint16, closingTxProposedCodec))) :: ("bestUnpublishedClosingTx_opt" | optional(bool, txCodec))).as[DATA_NEGOTIATING] - val DATA_CLOSING_Codec: Codec[DATA_CLOSING] = ( - ("commitments" | commitmentsCodec) :: + def DATA_CLOSING_Codec(version: CommitmentVersion): Codec[DATA_CLOSING] = ( + ("commitments" | commitmentsCodec(version)) :: ("mutualCloseProposed" | listOfN(uint16, txCodec)) :: ("mutualClosePublished" | listOfN(uint16, txCodec)) :: ("localCommitPublished" | optional(bool, localCommitPublishedCodec)) :: @@ -295,8 +300,8 @@ object ChannelCodecs extends Logging { ("futureRemoteCommitPublished" | optional(bool, remoteCommitPublishedCodec)) :: ("revokedCommitPublished" | listOfN(uint16, revokedCommitPublishedCodec))).as[DATA_CLOSING] - val DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec: Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = ( - ("commitments" | commitmentsCodec) :: + def DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(version: CommitmentVersion): Codec[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] = ( + ("commitments" | commitmentsCodec(version)) :: ("remoteChannelReestablish" | channelReestablishCodec)).as[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] @@ -311,36 +316,28 @@ object ChannelCodecs extends Logging { * * More info here: https://github.com/scodec/scodec/issues/122 */ - val stateDataCodec: Codec[HasCommitments] = ("version" | constant(0x00)) ~> discriminated[HasCommitments].by(uint16) - .typecase(0x08, DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec) - .typecase(0x01, DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec) - .typecase(0x02, DATA_WAIT_FOR_FUNDING_LOCKED_Codec) - .typecase(0x03, DATA_NORMAL_Codec) - .typecase(0x04, DATA_SHUTDOWN_Codec) - .typecase(0x05, DATA_NEGOTIATING_Codec) - .typecase(0x06, DATA_CLOSING_Codec) - .typecase(0x07, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec) - - -// private def stateDataCodec(commitmentVersion: CommitmentVersion): Codec[HasCommitments] = discriminated[HasCommitments].by(uint16) -// .typecase(0x08, DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitmentVersion)) -// .typecase(0x01, DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec(commitmentVersion)) -// .typecase(0x02, DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitmentVersion)) -// .typecase(0x03, DATA_NORMAL_Codec(commitmentVersion)) -// .typecase(0x04, DATA_SHUTDOWN_Codec(commitmentVersion)) -// .typecase(0x05, DATA_NEGOTIATING_Codec(commitmentVersion)) -// .typecase(0x06, DATA_CLOSING_Codec(commitmentVersion)) -// .typecase(0x07, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitmentVersion)) -// -// val COMMITMENTv0_VERSION_BYTE = 0x00.toByte -// val COMMITMENTv1_VERSION_BYTE = 0x01.toByte -// -// val genericStateDataCodec = discriminated[HasCommitments].by(uint8) -// .\ (COMMITMENTv1_VERSION_BYTE) { case c if c.commitments.version == CommitmentV0 => c } (stateDataCodec(CommitmentV0)) -// .\ (COMMITMENTv0_VERSION_BYTE) { case c if c.commitments.version == CommitmentV1 => c } (stateDataCodec(CommitmentV1)) -// -// sealed trait CommitmentVersion -// case object CommitmentV0 extends CommitmentVersion -// case object CommitmentV1 extends CommitmentVersion + val COMMITMENTv0_VERSION_BYTE = 0x00.toByte + val COMMITMENTv1_VERSION_BYTE = 0x01.toByte + + val stateDataCodec = discriminated[HasCommitments].by(uint8) + .\(COMMITMENTv0_VERSION_BYTE) { case c => c }(stateDataCodecVersioned(CommitmentV0)) + .\(COMMITMENTv1_VERSION_BYTE) { case c => c }(stateDataCodecVersioned(CommitmentV1)) + + private def stateDataCodecVersioned(commitmentVersion: CommitmentVersion): Codec[HasCommitments] = discriminated[HasCommitments].by(uint16) + .typecase(0x08, DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitmentVersion)) + .typecase(0x01, DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec(commitmentVersion)) + .typecase(0x02, DATA_WAIT_FOR_FUNDING_LOCKED_Codec(commitmentVersion)) + .typecase(0x03, DATA_NORMAL_Codec(commitmentVersion)) + .typecase(0x04, DATA_SHUTDOWN_Codec(commitmentVersion)) + .typecase(0x05, DATA_NEGOTIATING_Codec(commitmentVersion)) + .typecase(0x06, DATA_CLOSING_Codec(commitmentVersion)) + .typecase(0x07, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitmentVersion)) + + + sealed trait CommitmentVersion + + case object CommitmentV0 extends CommitmentVersion + + case object CommitmentV1 extends CommitmentVersion } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala index 25eabb8368..1709e5fb5c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala @@ -27,6 +27,7 @@ import fr.acinq.eclair.payment.{Local, Relayed} import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.Transactions.CommitTx import fr.acinq.eclair.transactions._ +import fr.acinq.eclair.wire.ChannelCodecs.CommitmentV1 import fr.acinq.eclair.wire.{ChannelCodecs, UpdateAddHtlc} import fr.acinq.eclair.{ShortChannelId, UInt64, randomKey} import org.scalatest.FunSuite @@ -42,8 +43,8 @@ class ChannelStateSpec extends FunSuite { test("basic serialization test (NORMAL)") { val data = normal - val bin = ChannelCodecs.DATA_NORMAL_Codec.encode(data).require - val check = ChannelCodecs.DATA_NORMAL_Codec.decodeValue(bin).require + val bin = ChannelCodecs.DATA_NORMAL_Codec(CommitmentV1).encode(data).require + val check = ChannelCodecs.DATA_NORMAL_Codec(CommitmentV1).decodeValue(bin).require assert(data.commitments.localCommit.spec === check.commitments.localCommit.spec) assert(data === check) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala index 4a2cd7e5b2..30ca49fd51 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala @@ -56,9 +56,27 @@ class ChannelCodecsSpec extends FunSuite { } test("encode/decode localparams") { - val o = LocalParams( + val localParamFundee = LocalParams( nodeId = randomKey.publicKey, - channelKeyPath = Left(DeterministicWallet.KeyPath(Seq(42L))), + channelKeyPath = Right(KeyPathFundee(KeyPath(Seq(4, 3, 2, 1L)), KeyPath(Seq(1, 2, 3, 4L)))), + dustLimitSatoshis = Random.nextInt(Int.MaxValue), + maxHtlcValueInFlightMsat = UInt64(Random.nextInt(Int.MaxValue)), + channelReserveSatoshis = Random.nextInt(Int.MaxValue), + htlcMinimumMsat = Random.nextInt(Int.MaxValue), + toSelfDelay = Random.nextInt(Short.MaxValue), + maxAcceptedHtlcs = Random.nextInt(Short.MaxValue), + defaultFinalScriptPubKey = randomBytes(10 + Random.nextInt(200)), + isFunder = false, + globalFeatures = randomBytes(256), + localFeatures = randomBytes(256)) + + val encoded = localParamsCodec(CommitmentV1).encode(localParamFundee).require + val decoded = localParamsCodec(CommitmentV1).decode(encoded).require + assert(localParamFundee === decoded.value) + + val localParamFunder = LocalParams( + nodeId = randomKey.publicKey, + channelKeyPath = Left(KeyPath(Seq(4, 3, 2, 1L))), dustLimitSatoshis = Random.nextInt(Int.MaxValue), maxHtlcValueInFlightMsat = UInt64(Random.nextInt(Int.MaxValue)), channelReserveSatoshis = Random.nextInt(Int.MaxValue), @@ -69,9 +87,10 @@ class ChannelCodecsSpec extends FunSuite { isFunder = true, globalFeatures = randomBytes(256), localFeatures = randomBytes(256)) - val encoded = localParamsCodec.encode(o).require - val decoded = localParamsCodec.decode(encoded).require - assert(o === decoded.value) + + val encoded1 = localParamsCodec(CommitmentV1).encode(localParamFunder).require + val decoded1 = localParamsCodec(CommitmentV1).decode(encoded1).require + assert(localParamFunder === decoded1.value) } test("encode/decode remoteparams") { From 2a329d6e953ada594350b5bd7ad4b467d5d1bfa7 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 5 Jun 2019 16:14:48 +0200 Subject: [PATCH 12/43] Sort scodecs properly, update test --- .../src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala | 5 ++--- .../test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala index 8fb27632c4..0271a2209b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala @@ -319,9 +319,10 @@ object ChannelCodecs extends Logging { val COMMITMENTv0_VERSION_BYTE = 0x00.toByte val COMMITMENTv1_VERSION_BYTE = 0x01.toByte + // Order matters! val stateDataCodec = discriminated[HasCommitments].by(uint8) - .\(COMMITMENTv0_VERSION_BYTE) { case c => c }(stateDataCodecVersioned(CommitmentV0)) .\(COMMITMENTv1_VERSION_BYTE) { case c => c }(stateDataCodecVersioned(CommitmentV1)) + .\(COMMITMENTv0_VERSION_BYTE) { case c => c }(stateDataCodecVersioned(CommitmentV0)) private def stateDataCodecVersioned(commitmentVersion: CommitmentVersion): Codec[HasCommitments] = discriminated[HasCommitments].by(uint16) .typecase(0x08, DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec(commitmentVersion)) @@ -335,9 +336,7 @@ object ChannelCodecs extends Logging { sealed trait CommitmentVersion - case object CommitmentV0 extends CommitmentVersion - case object CommitmentV1 extends CommitmentVersion } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala index 30ca49fd51..fca6c7391f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala @@ -203,8 +203,8 @@ class ChannelCodecsSpec extends FunSuite { assert(Platform.currentTime.milliseconds.toSeconds - data_new.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].waitingSince < 3600) // we just set this timestamp to current time // and re-encode it with the new codec val bin_new = ByteVector(stateDataCodec.encode(data_new).require.toByteVector.toArray) - // data should now be encoded under the new format, with version=0 and type=8 - assert(bin_new.startsWith(hex"000008")) + // data should now be encoded under the new format, with version=1 and type=8 + assert(bin_new.startsWith(hex"010008")) // now let's decode it again val data_new2 = stateDataCodec.decode(bin_new.toBitVector).require.value // data should match perfectly From 3b25ea53775011145f67a1af7a0175f79f491432 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 5 Jun 2019 17:10:43 +0200 Subject: [PATCH 13/43] Use new derivation scheme for fundee scenario --- .../fr/acinq/eclair/channel/Channel.scala | 26 ++++++++++++++++--- .../main/scala/fr/acinq/eclair/io/Peer.scala | 12 +++------ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index a82ab4ced5..be4776b984 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -92,19 +92,37 @@ object Channel { case class RemoteError(e: Error) extends ChannelError // @formatter:on - def makeChannelKeyPathFromInput(fundingInput: TxIn): KeyPath = { + def fourByteGroupsFromSha(input: ByteVector): List[Long] = { // split the SHA into 8 groups of 4 bytes and convert to uint32 - val List(h0, h1, h2, h3, h4, h5, h6, h7) = Crypto.sha256(fundingInput.outPoint.hash).toArray.grouped(4).map(ByteVector(_).toLong(signed = false)).toList - KeyPath(Seq(47, 2, h0, h1, h2, h3, h4, h5, h6, h7, 0)) + Crypto.sha256(input).toArray.grouped(4).map(ByteVector(_).toLong(signed = false)).toList } def makeFunderChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, fundingInput: TxIn): LocalParams = { require(isFunder, s"Wrong params for isFunder=$isFunder") - val channelKeyPath = makeChannelKeyPathFromInput(fundingInput) + val List(h0, h1, h2, h3, h4, h5, h6, h7) = fourByteGroupsFromSha(fundingInput.outPoint.hash) + val channelKeyPath = KeyPath(Seq(47, 2, h0, h1, h2, h3, h4, h5, h6, h7, 0)) + makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder, fundingSatoshis, Left(channelKeyPath)) } + def makeFundeeChannelParams(nodeParams: NodeParams, open: OpenChannel, defaultFinalScriptPubKey: ByteVector, fundingSatoshis: Long): LocalParams = { + + val List(h0, h1, h2, h3, h4, h5, h6, h7) = fourByteGroupsFromSha(ByteVector.view(s"${Globals.blockCount} || 0".getBytes)) + val publicKeyPath = KeyPath(Seq(47, 2, h0, h1, h2, h3, h4, h5, h6, h7, 2)) + + val localFundingPubkey = nodeParams.keyManager.fundingPublicKey(publicKeyPath).publicKey + val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey, open.fundingPubkey))) + + val List(s0, s1, s2, s3, s4, s5, s6, s7) = fourByteGroupsFromSha(fundingPubkeyScript) + val pointsKeyPath = KeyPath(Seq(47, 2, s0, s1, s2, s3, s4, s5, s6, s7, 1)) + + val channelKeyPaths = KeyPathFundee(publicKeyPath, pointsKeyPath) + + makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder = false, fundingSatoshis, Right(channelKeyPaths)) + } + + def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, channelKeyPath: Either[DeterministicWallet.KeyPath, KeyPathFundee]): LocalParams = { LocalParams( nodeParams.nodeId, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index c79faca327..886d3a1a30 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -271,7 +271,10 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: Actor d.transport ! TransportHandler.ReadAck(msg) d.channels.get(TemporaryChannelId(msg.temporaryChannelId)) match { case None => - val (channel, localParams) = createNewChannel(nodeParams, funder = false, fundingSatoshis = msg.fundingSatoshis, origin_opt = None) + + val channel = spawnChannel(nodeParams, origin_opt = None) + val defaultFinalScriptPubkey = Helpers.getFinalScriptPubKey(wallet, nodeParams.chainHash) + val localParams = Channel.makeFundeeChannelParams(nodeParams, msg, defaultFinalScriptPubkey, msg.fundingSatoshis) val temporaryChannelId = msg.temporaryChannelId log.info(s"accepting a new channel to $remoteNodeId temporaryChannelId=$temporaryChannelId") @@ -486,13 +489,6 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: Actor case DISCONNECTED -> _ if nodeParams.autoReconnect && stateData.address_opt.isDefined => cancelTimer(RECONNECT_TIMER) } - def createNewChannel(nodeParams: NodeParams, funder: Boolean, fundingSatoshis: Long, origin_opt: Option[ActorRef]): (ActorRef, LocalParams) = { - val defaultFinalScriptPubKey = Helpers.getFinalScriptPubKey(wallet, nodeParams.chainHash) - val localParams = makeChannelParams(nodeParams, defaultFinalScriptPubKey, funder, fundingSatoshis) - val channel = spawnChannel(nodeParams, origin_opt) - (channel, localParams) - } - def spawnChannel(nodeParams: NodeParams, origin_opt: Option[ActorRef]): ActorRef = { val channel = context.actorOf(Channel.props(nodeParams, wallet, remoteNodeId, watcher, router, relayer, origin_opt)) context watch channel From b041d94f88aa181333e3576b040f3f9b00dd28ab Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 6 Jun 2019 16:29:27 +0200 Subject: [PATCH 14/43] Add test vectors for key derivation --- .../fr/acinq/eclair/channel/Channel.scala | 31 ++----- .../acinq/eclair/crypto/LocalKeyManager.scala | 48 ++++++----- .../eclair/crypto/LocalKeyManagerSpec.scala | 85 ++++++++++++++++++- 3 files changed, 119 insertions(+), 45 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index be4776b984..ab84380cba 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -25,7 +25,7 @@ import fr.acinq.bitcoin._ import fr.acinq.eclair._ import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.channel.Helpers.{Closing, Funding} -import fr.acinq.eclair.crypto.{KeyManager, ShaChain, Sphinx} +import fr.acinq.eclair.crypto.{KeyManager, LocalKeyManager, ShaChain, Sphinx} import fr.acinq.eclair.io.Peer import fr.acinq.eclair.payment._ import fr.acinq.eclair.router.Announcements @@ -92,37 +92,24 @@ object Channel { case class RemoteError(e: Error) extends ChannelError // @formatter:on - def fourByteGroupsFromSha(input: ByteVector): List[Long] = { - // split the SHA into 8 groups of 4 bytes and convert to uint32 - Crypto.sha256(input).toArray.grouped(4).map(ByteVector(_).toLong(signed = false)).toList - } - - def makeFunderChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, fundingInput: TxIn): LocalParams = { - require(isFunder, s"Wrong params for isFunder=$isFunder") - - val List(h0, h1, h2, h3, h4, h5, h6, h7) = fourByteGroupsFromSha(fundingInput.outPoint.hash) - val channelKeyPath = KeyPath(Seq(47, 2, h0, h1, h2, h3, h4, h5, h6, h7, 0)) - - makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder, fundingSatoshis, Left(channelKeyPath)) + def makeFunderChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, fundingSatoshis: Long, fundingInput: TxIn): LocalParams = { + val channelKeyPath = LocalKeyManager.makeChannelKeyPathFunder(fundingInput.outPoint.hash) + makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder = true, fundingSatoshis, Left(channelKeyPath)) } def makeFundeeChannelParams(nodeParams: NodeParams, open: OpenChannel, defaultFinalScriptPubKey: ByteVector, fundingSatoshis: Long): LocalParams = { - val List(h0, h1, h2, h3, h4, h5, h6, h7) = fourByteGroupsFromSha(ByteVector.view(s"${Globals.blockCount} || 0".getBytes)) - val publicKeyPath = KeyPath(Seq(47, 2, h0, h1, h2, h3, h4, h5, h6, h7, 2)) - + val publicKeyPath = LocalKeyManager.makeChannelKeyPathFundeePubkey(Globals.blockCount.get, 0) val localFundingPubkey = nodeParams.keyManager.fundingPublicKey(publicKeyPath).publicKey - val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey, open.fundingPubkey))) - val List(s0, s1, s2, s3, s4, s5, s6, s7) = fourByteGroupsFromSha(fundingPubkeyScript) - val pointsKeyPath = KeyPath(Seq(47, 2, s0, s1, s2, s3, s4, s5, s6, s7, 1)) + val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey, open.fundingPubkey))) + val channelKeyPath = LocalKeyManager.makeChannelKeyPathFundee(fundingPubkeyScript) - val channelKeyPaths = KeyPathFundee(publicKeyPath, pointsKeyPath) + val channelKeyPaths = KeyPathFundee(publicKeyPath, channelKeyPath) makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder = false, fundingSatoshis, Right(channelKeyPaths)) } - def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, channelKeyPath: Either[DeterministicWallet.KeyPath, KeyPathFundee]): LocalParams = { LocalParams( nodeParams.nodeId, @@ -370,7 +357,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId case Event(funding:MakeFundingTxResponse, DATA_WAIT_FOR_FUNDING_INTERNAL_CREATED(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, remote, remoteInit, channelFlags)) => val defaultFinalScriptPubKey = Helpers.getFinalScriptPubKey(wallet, nodeParams.chainHash) - val localParams = makeFunderChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder = true, fundingSatoshis, funding.fundingTx.txIn.head) + val localParams = makeFunderChannelParams(nodeParams, defaultFinalScriptPubKey, fundingSatoshis, funding.fundingTx.txIn.head) log.info(s"using localParams=$localParams") val open = OpenChannel(nodeParams.chainHash, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala index f404c125b7..ea343fb901 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala @@ -25,12 +25,12 @@ import fr.acinq.eclair.channel.LocalParams import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.transactions.Transactions.TransactionWithInputInfo -import scodec.bits.ByteVector +import scodec.bits.{ByteOrdering, ByteVector} object LocalKeyManager { def channelKeyBasePath(chainHash: ByteVector32) = chainHash match { - case Block.RegtestGenesisBlock.hash | Block.TestnetGenesisBlock.hash => DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(1) :: Nil - case Block.LivenetGenesisBlock.hash => DeterministicWallet.hardened(47) :: DeterministicWallet.hardened(1) :: Nil + case Block.RegtestGenesisBlock.hash | Block.TestnetGenesisBlock.hash => DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(2) :: Nil + case Block.LivenetGenesisBlock.hash => DeterministicWallet.hardened(47) :: DeterministicWallet.hardened(2) :: Nil } @@ -41,6 +41,21 @@ object LocalKeyManager { case Block.RegtestGenesisBlock.hash | Block.TestnetGenesisBlock.hash => DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(0) :: Nil case Block.LivenetGenesisBlock.hash => DeterministicWallet.hardened(47) :: DeterministicWallet.hardened(0) :: Nil } + + // split the SHA into 8 groups of 4 bytes and convert to uint32 + def fourByteGroupsFromSha(input: ByteVector): List[Long] = Crypto.sha256(input).toArray.grouped(4).map(ByteVector(_).toLong(signed = false)).toList + + def makeChannelKeyPathFunder(entropy: ByteVector) = KeyPath(fourByteGroupsFromSha(entropy) :+ 0L) + def makeChannelKeyPathFundee(entropy: ByteVector) = KeyPath(fourByteGroupsFromSha(entropy) :+ 1L) + def makeChannelKeyPathFundeePubkey(entropy: ByteVector) = KeyPath(fourByteGroupsFromSha(entropy) :+ 2L) + + def makeChannelKeyPathFundeePubkey(blockHeight: Long, counter: Int): KeyPath = { + val blockHeightBytes = ByteVector.fromLong(blockHeight, size = 4, ordering = ByteOrdering.LittleEndian) + val counterBytes = ByteVector.fromInt(counter, size = 4, ordering = ByteOrdering.LittleEndian) + + makeChannelKeyPathFundeePubkey(blockHeightBytes ++ counterBytes) + } + } /** @@ -51,12 +66,6 @@ object LocalKeyManager { */ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyManager { - private def funderDerivationPath(entropy: ByteVector32) = Seq(47, 2, entropy.toLong(), 0) - - private def fundeeDerivationPath(entropy: ByteVector32) = Seq(47, 2, entropy.toLong(), 1) - - private def fundeePubkeyDerivationPath(entropy: ByteVector32) = Seq(47, 2, entropy.toLong(), 2) - private val master = DeterministicWallet.generate(seed) override val nodeKey = DeterministicWallet.derivePrivateKey(master, LocalKeyManager.nodeKeyBasePath(chainHash)) @@ -76,27 +85,27 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana private def internalKeyPath(channelKeyPath: DeterministicWallet.KeyPath, index: Long): List[Long] = (LocalKeyManager.channelKeyBasePath(chainHash) ++ channelKeyPath.path) :+ index - private def fundingPrivateKey(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, hardened(0))) + private def fundingPrivateKey(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, 0)) - private def revocationSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, hardened(1))) + private def revocationSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, 1)) - private def paymentSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, hardened(2))) + private def paymentSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, 2)) - private def delayedPaymentSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, hardened(3))) + private def delayedPaymentSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, 3)) - private def htlcSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, hardened(4))) + private def htlcSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, 4)) private def shaSeed(channelKeyPath: DeterministicWallet.KeyPath) = Crypto.sha256(privateKeys.get(internalKeyPath(channelKeyPath, hardened(5))).privateKey.toBin) - override def fundingPublicKey(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(0))) + override def fundingPublicKey(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, 0)) - override def revocationPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(1))) + override def revocationPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, 1)) - override def paymentPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(2))) + override def paymentPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, 2)) - override def delayedPaymentPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(3))) + override def delayedPaymentPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, 3)) - override def htlcPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(4))) + override def htlcPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, 4)) override def commitmentSecret(channelKeyPath: DeterministicWallet.KeyPath, index: Long) = Generators.perCommitSecret(shaSeed(channelKeyPath), index) @@ -216,7 +225,6 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana override def signChannelAnnouncement(channelKeyPath: DeterministicWallet.KeyPath, chainHash: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: ByteVector): (ByteVector, ByteVector) = { - val witness = if (Announcements.isNode1(nodeId, remoteNodeId)) { Announcements.channelAnnouncementWitnessEncode(chainHash, shortChannelId, nodeId, remoteNodeId, fundingPublicKey(channelKeyPath).publicKey, remoteFundingKey, features) } else { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala index 2d59cc6cce..0f3548a977 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala @@ -16,13 +16,13 @@ package fr.acinq.eclair.crypto -import fr.acinq.bitcoin.Block +import fr.acinq.bitcoin.{Block, Crypto, DeterministicWallet, MnemonicCode, Script} import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.DeterministicWallet.KeyPath +import fr.acinq.eclair.transactions.Scripts import org.scalatest.FunSuite import scodec.bits._ - class LocalKeyManagerSpec extends FunSuite { test("generate the same node id from the same seed") { // if this test breaks it means that we will generate a different node id from @@ -40,4 +40,83 @@ class LocalKeyManagerSpec extends FunSuite { assert(keyManager1.fundingPublicKey(keyPath) != keyManager2.fundingPublicKey(keyPath)) assert(keyManager1.commitmentPoint(keyPath, 1) != keyManager2.commitmentPoint(keyPath, 1)) } -} + + /** + * TESTNET funder funding public key from extended public key: 03c69365c8ae813a45b9fead1056331c41f38ab3ab7d5d383d62c4c70cfd91f9ea + * TESTNET funder payment point from extended public key: 02efa31ae79a7c29faf23a21f00d5ca62ce14539c2f431db5dade3e919d2ff9050 + * MAINNET funder funding public key from extended public key: 02dd88103f4690fa5484f0c6a13f917fb00f03b5f2be1375cd08c64b11b19d730e + * MAINNET funder payment point from extended public key: 030a7898d98245666be451b55a63a5f1acd71ec1efec85fb8364f3fbe46327441b + * TESTNET fundee funding public key from extended public key #34273 #0: 029278489277ce1abf6a05463ec913f5fe32ee194588a13c5b7899215d4ee477da + * TESTNET fundee htlc public point from extended public key #34273 #0: 02563a8f3480b06f1e653c7a1a9a006236dfe503ec616ccef8656ab8e2cbe938d5 + * TESTNET fundee htlc public point from extended public key #34273 #4: 028ba2414d6fcc9ed4c26175dea6c63caaa595101c7a66810ea0653547ba8e0a07 + * MAINNET fundee funding public key from extended public key #34273 #0: 03fe59b3ac6c2f08172d634f0346194fbf3e4e90fe01422a030546a913f0a4b21e + * MAINNET fundee htlc public point from extended public key #34273 #0: 03a1c04e6281f9b1dcebb261ca6bbc6a260521ff011cbb617804ceac6fc0dd38bc + * MAINNET fundee htlc public point from extended public key #34273 #4: 0359fd05aed2daa09798b2d5e1a705d76664fcce740db2f88cdb6259d2bf055fb0 + */ + + test("test vectors derivation paths (funder scenario - TESTNET)") { + + val inputOutpoint = hex"1d12dcab62f3d509db16b8dcb69782ea6358a7060b579675561c4fc2e3294f41" + val seed = hex"0101010102020202AABBCCDD030303030404040405050505060606060707070701" + val keyManager = new LocalKeyManager(seed, Block.TestnetGenesisBlock.hash) + + val funderChannelKeyPath = LocalKeyManager.makeChannelKeyPathFunder(inputOutpoint) + + // TESTNET funder funding public key from extended public key + assert(keyManager.fundingPublicKey(funderChannelKeyPath).publicKey.toBin === hex"03c69365c8ae813a45b9fead1056331c41f38ab3ab7d5d383d62c4c70cfd91f9ea") + // TESTNET funder payment point from extended public key + assert(keyManager.paymentPoint(funderChannelKeyPath).publicKey.toBin === hex"02efa31ae79a7c29faf23a21f00d5ca62ce14539c2f431db5dade3e919d2ff9050") + } + + test("test vectors derivation paths (funder scenario - MAINNET)") { + + val inputOutpoint = hex"1d12dcab62f3d509db16b8dcb69782ea6358a7060b579675561c4fc2e3294f41" + val seed = hex"0101010102020202AABBCCDD030303030404040405050505060606060707070701" + val keyManager = new LocalKeyManager(seed, Block.LivenetGenesisBlock.hash) + + val funderChannelKeyPath = LocalKeyManager.makeChannelKeyPathFunder(inputOutpoint) + + // MAINNET funder funding public key from extended public key + assert(keyManager.fundingPublicKey(funderChannelKeyPath).publicKey.toBin === hex"02dd88103f4690fa5484f0c6a13f917fb00f03b5f2be1375cd08c64b11b19d730e") + // MAINNET funder payment point from extended public key + assert(keyManager.paymentPoint(funderChannelKeyPath).publicKey.toBin === hex"030a7898d98245666be451b55a63a5f1acd71ec1efec85fb8364f3fbe46327441b") + } + + test("test vectors derivation paths (fundee TESTNET)") { + + val remoteNodePubkey = PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f") + val seed = hex"0101010102020202AABBCCDD030303030404040405050505060606060707070701" + val keyManager = new LocalKeyManager(seed, Block.TestnetGenesisBlock.hash) + + val fundeePubkeyKeyPath = LocalKeyManager.makeChannelKeyPathFundeePubkey(34273, 0) + // TESTNET fundee funding public key from extended public key + assert(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey.toBin === hex"029278489277ce1abf6a05463ec913f5fe32ee194588a13c5b7899215d4ee477da") + + val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey, remoteNodePubkey))) + val fundeeChannelKeyPath = LocalKeyManager.makeChannelKeyPathFundee(fundingPubkeyScript) + + // TESTNET fundee htlc public point from extended public key + assert(keyManager.htlcPoint(fundeeChannelKeyPath).publicKey.toBin === hex"02563a8f3480b06f1e653c7a1a9a006236dfe503ec616ccef8656ab8e2cbe938d5") + // TESTNET fundee htlc public point from extended public key + assert(keyManager.paymentPoint(fundeeChannelKeyPath).publicKey.toBin === hex"0321047df59f000ba15f674c2eb6180c00edb55e5eae6e8ea22e82554c4213cfa4") + } + + test("test vectors derivation paths (fundee MAINNET)") { + + val remoteNodePubkey = PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f") + val seed = hex"0101010102020202AABBCCDD030303030404040405050505060606060707070701" + val keyManager = new LocalKeyManager(seed, Block.LivenetGenesisBlock.hash) + + val fundeePubkeyKeyPath = LocalKeyManager.makeChannelKeyPathFundeePubkey(34273, 0) + // MAINNET fundee funding public key from extended public key + assert(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey.toBin === hex"03fe59b3ac6c2f08172d634f0346194fbf3e4e90fe01422a030546a913f0a4b21e") + + val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey, remoteNodePubkey))) + val fundeeChannelKeyPath = LocalKeyManager.makeChannelKeyPathFundee(fundingPubkeyScript) + + println(keyManager.htlcPoint(fundeeChannelKeyPath).path.toString()) + // MAINNET fundee htlc public point from extended public key + assert(keyManager.htlcPoint(fundeeChannelKeyPath).publicKey.toBin === hex"03a1c04e6281f9b1dcebb261ca6bbc6a260521ff011cbb617804ceac6fc0dd38bc") + } + +} \ No newline at end of file From 596bc0cb4f989a16e6d6c83a7593062e1ac46700 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 7 Jun 2019 15:27:52 +0200 Subject: [PATCH 15/43] Store counter on channelDb --- .../fr/acinq/eclair/channel/Channel.scala | 4 ++- .../acinq/eclair/crypto/LocalKeyManager.scala | 4 +-- .../scala/fr/acinq/eclair/db/ChannelsDb.scala | 2 ++ .../eclair/db/sqlite/SqliteChannelsDb.scala | 33 +++++++++++++++++-- .../eclair/crypto/LocalKeyManagerSpec.scala | 11 +++---- .../eclair/db/SqliteChannelsDbSpec.scala | 14 ++++++++ 6 files changed, 56 insertions(+), 12 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index ab84380cba..e42948ab3a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -99,7 +99,9 @@ object Channel { def makeFundeeChannelParams(nodeParams: NodeParams, open: OpenChannel, defaultFinalScriptPubKey: ByteVector, fundingSatoshis: Long): LocalParams = { - val publicKeyPath = LocalKeyManager.makeChannelKeyPathFundeePubkey(Globals.blockCount.get, 0) + val blockHeight = Globals.blockCount.get + val counter = nodeParams.db.channels.getCounterFor(blockHeight) + val publicKeyPath = LocalKeyManager.makeChannelKeyPathFundeePubkey(blockHeight, counter) val localFundingPubkey = nodeParams.keyManager.fundingPublicKey(publicKeyPath).publicKey val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey, open.fundingPubkey))) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala index ea343fb901..dc69843779 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala @@ -49,9 +49,9 @@ object LocalKeyManager { def makeChannelKeyPathFundee(entropy: ByteVector) = KeyPath(fourByteGroupsFromSha(entropy) :+ 1L) def makeChannelKeyPathFundeePubkey(entropy: ByteVector) = KeyPath(fourByteGroupsFromSha(entropy) :+ 2L) - def makeChannelKeyPathFundeePubkey(blockHeight: Long, counter: Int): KeyPath = { + def makeChannelKeyPathFundeePubkey(blockHeight: Long, counter: Long): KeyPath = { val blockHeightBytes = ByteVector.fromLong(blockHeight, size = 4, ordering = ByteOrdering.LittleEndian) - val counterBytes = ByteVector.fromInt(counter, size = 4, ordering = ByteOrdering.LittleEndian) + val counterBytes = ByteVector.fromLong(counter, size = 4, ordering = ByteOrdering.LittleEndian) makeChannelKeyPathFundeePubkey(blockHeightBytes ++ counterBytes) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/ChannelsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/ChannelsDb.scala index dd9fb9e120..ea58fbdabb 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/ChannelsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/ChannelsDb.scala @@ -21,6 +21,8 @@ import fr.acinq.eclair.channel.HasCommitments trait ChannelsDb { + def getCounterFor(blockHeight: Long): Long + def addOrUpdateChannel(state: HasCommitments) def removeChannel(channelId: ByteVector32) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala index d747a3b9dd..a905a80395 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala @@ -16,7 +16,7 @@ package fr.acinq.eclair.db.sqlite -import java.sql.{Connection, Statement} +import java.sql.{Connection, SQLException, Statement} import fr.acinq.bitcoin.ByteVector32 import fr.acinq.eclair.channel.HasCommitments @@ -49,11 +49,40 @@ class SqliteChannelsDb(sqlite: Connection) extends ChannelsDb with Logging { statement.executeUpdate("CREATE TABLE IF NOT EXISTS local_channels (channel_id BLOB NOT NULL PRIMARY KEY, data BLOB NOT NULL, is_closed BOOLEAN NOT NULL DEFAULT 0)") statement.executeUpdate("CREATE TABLE IF NOT EXISTS htlc_infos (channel_id BLOB NOT NULL, commitment_number BLOB NOT NULL, payment_hash BLOB NOT NULL, cltv_expiry INTEGER NOT NULL, FOREIGN KEY(channel_id) REFERENCES local_channels(channel_id))") statement.executeUpdate("CREATE INDEX IF NOT EXISTS htlc_infos_idx ON htlc_infos(channel_id, commitment_number)") - + statement.executeUpdate("CREATE TABLE IF NOT EXISTS block_key_counter (block_height INTEGER NOT NULL PRIMARY KEY, counter INTEGER NOT NULL)") case unknownVersion => throw new RuntimeException(s"Unknown version of DB $DB_NAME found, version=$unknownVersion") } } + override def getCounterFor(blockHeight: Long): Long = synchronized { + val counterOld = getCounter(blockHeight) + setCounter(blockHeight, counterOld + 1) + counterOld + } + + private def getCounter(blockHeight: Long): Long = { + using(sqlite.prepareStatement("SELECT counter FROM block_key_counter WHERE block_height=?")) { statement => + statement.setLong(1, blockHeight) + val rs = statement.executeQuery() + if(rs.next()) rs.getLong("counter") else 0 + } + } + + private def setCounter(blockHeight: Long, counter: Long) = { + using(sqlite.prepareStatement("UPDATE block_key_counter SET counter=? WHERE block_height=?")) { statement => + statement.setLong(1, counter) + statement.setLong(2, blockHeight) + if(statement.executeUpdate() != 1){ + using(sqlite.prepareStatement("INSERT INTO block_key_counter VALUES(?, ?)")) { insert => + insert.setLong(1, blockHeight) + insert.setLong(2, counter) + insert.executeUpdate() + } + } + } + } + + override def addOrUpdateChannel(state: HasCommitments): Unit = { val data = stateDataCodec.encode(state).require.toByteArray using (sqlite.prepareStatement("UPDATE local_channels SET data=? WHERE channel_id=?")) { update => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala index 0f3548a977..a25058f888 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala @@ -48,13 +48,11 @@ class LocalKeyManagerSpec extends FunSuite { * MAINNET funder payment point from extended public key: 030a7898d98245666be451b55a63a5f1acd71ec1efec85fb8364f3fbe46327441b * TESTNET fundee funding public key from extended public key #34273 #0: 029278489277ce1abf6a05463ec913f5fe32ee194588a13c5b7899215d4ee477da * TESTNET fundee htlc public point from extended public key #34273 #0: 02563a8f3480b06f1e653c7a1a9a006236dfe503ec616ccef8656ab8e2cbe938d5 - * TESTNET fundee htlc public point from extended public key #34273 #4: 028ba2414d6fcc9ed4c26175dea6c63caaa595101c7a66810ea0653547ba8e0a07 * MAINNET fundee funding public key from extended public key #34273 #0: 03fe59b3ac6c2f08172d634f0346194fbf3e4e90fe01422a030546a913f0a4b21e - * MAINNET fundee htlc public point from extended public key #34273 #0: 03a1c04e6281f9b1dcebb261ca6bbc6a260521ff011cbb617804ceac6fc0dd38bc - * MAINNET fundee htlc public point from extended public key #34273 #4: 0359fd05aed2daa09798b2d5e1a705d76664fcce740db2f88cdb6259d2bf055fb0 + * MAINNET fundee htlc public point from extended public key #34273 #0: 03056cef064df0a65812c7c05fedfbdee978de5a9b4d986dc8b389e4c0ac42d9f1 */ - test("test vectors derivation paths (funder scenario - TESTNET)") { + test("test vectors derivation paths (funder TESTNET)") { val inputOutpoint = hex"1d12dcab62f3d509db16b8dcb69782ea6358a7060b579675561c4fc2e3294f41" val seed = hex"0101010102020202AABBCCDD030303030404040405050505060606060707070701" @@ -68,7 +66,7 @@ class LocalKeyManagerSpec extends FunSuite { assert(keyManager.paymentPoint(funderChannelKeyPath).publicKey.toBin === hex"02efa31ae79a7c29faf23a21f00d5ca62ce14539c2f431db5dade3e919d2ff9050") } - test("test vectors derivation paths (funder scenario - MAINNET)") { + test("test vectors derivation paths (funder MAINNET)") { val inputOutpoint = hex"1d12dcab62f3d509db16b8dcb69782ea6358a7060b579675561c4fc2e3294f41" val seed = hex"0101010102020202AABBCCDD030303030404040405050505060606060707070701" @@ -114,9 +112,8 @@ class LocalKeyManagerSpec extends FunSuite { val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey, remoteNodePubkey))) val fundeeChannelKeyPath = LocalKeyManager.makeChannelKeyPathFundee(fundingPubkeyScript) - println(keyManager.htlcPoint(fundeeChannelKeyPath).path.toString()) // MAINNET fundee htlc public point from extended public key - assert(keyManager.htlcPoint(fundeeChannelKeyPath).publicKey.toBin === hex"03a1c04e6281f9b1dcebb261ca6bbc6a260521ff011cbb617804ceac6fc0dd38bc") + assert(keyManager.htlcPoint(fundeeChannelKeyPath).publicKey.toBin === hex"03056cef064df0a65812c7c05fedfbdee978de5a9b4d986dc8b389e4c0ac42d9f1") } } \ No newline at end of file diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteChannelsDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteChannelsDbSpec.scala index e80273ecf5..002395483b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteChannelsDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteChannelsDbSpec.scala @@ -93,4 +93,18 @@ class SqliteChannelsDbSpec extends FunSuite { } assert(db.listLocalChannels() === List(channel)) } + + test("channel keypath counter should get and increment") { + + val sqlite = TestConstants.sqliteInMemory() + val channelDb = new SqliteChannelsDb(sqlite) + + assert(channelDb.getCounterFor(123) == 0) + assert(channelDb.getCounterFor(123) == 1) + assert(channelDb.getCounterFor(123) == 2) + assert(channelDb.getCounterFor(124) == 0) + assert(channelDb.getCounterFor(125) == 0) + assert(channelDb.getCounterFor(124) == 1) + } + } \ No newline at end of file From 004a2a64ce96a5bca2c998f239e6fa569f0d947f Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 7 Jun 2019 15:44:48 +0200 Subject: [PATCH 16/43] Update test vectors --- .../eclair/crypto/LocalKeyManagerSpec.scala | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala index a25058f888..80609c7bfa 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala @@ -16,7 +16,7 @@ package fr.acinq.eclair.crypto -import fr.acinq.bitcoin.{Block, Crypto, DeterministicWallet, MnemonicCode, Script} +import fr.acinq.bitcoin.{Block, Script} import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.DeterministicWallet.KeyPath import fr.acinq.eclair.transactions.Scripts @@ -44,12 +44,27 @@ class LocalKeyManagerSpec extends FunSuite { /** * TESTNET funder funding public key from extended public key: 03c69365c8ae813a45b9fead1056331c41f38ab3ab7d5d383d62c4c70cfd91f9ea * TESTNET funder payment point from extended public key: 02efa31ae79a7c29faf23a21f00d5ca62ce14539c2f431db5dade3e919d2ff9050 + * TESTNET funder revocation point from extended public key: 03f5cfec0e593e3d8fe965eb4ca066fe810cf8e87f7f2c0c8ababbd1a004662777 + * TESTNET funder payment point from extended public key: 02efa31ae79a7c29faf23a21f00d5ca62ce14539c2f431db5dade3e919d2ff9050 + * TESTNET funder delayed payment point from extended public key: 029b6dd179c16ea0952b7ff59ca59377ccd257b9ae8f0a6a88bd5409a07013b443 + * TESTNET funder htlc point from extended public key: 02e5afb68852863190c893d61f6244339ad368a14e0b761ab3cff5531c204e1efc + * TESTNET funder shachain point from extended public key: 02d46180dfd3a4bc388ac3dabe1240fc671d240e48c0f5652bce4363c22a878426 + * * MAINNET funder funding public key from extended public key: 02dd88103f4690fa5484f0c6a13f917fb00f03b5f2be1375cd08c64b11b19d730e * MAINNET funder payment point from extended public key: 030a7898d98245666be451b55a63a5f1acd71ec1efec85fb8364f3fbe46327441b + * * TESTNET fundee funding public key from extended public key #34273 #0: 029278489277ce1abf6a05463ec913f5fe32ee194588a13c5b7899215d4ee477da * TESTNET fundee htlc public point from extended public key #34273 #0: 02563a8f3480b06f1e653c7a1a9a006236dfe503ec616ccef8656ab8e2cbe938d5 + * * MAINNET fundee funding public key from extended public key #34273 #0: 03fe59b3ac6c2f08172d634f0346194fbf3e4e90fe01422a030546a913f0a4b21e * MAINNET fundee htlc public point from extended public key #34273 #0: 03056cef064df0a65812c7c05fedfbdee978de5a9b4d986dc8b389e4c0ac42d9f1 + * + * MAINNET fundee revocation point from extended public key #34273 #500: 022a714b39b36557b43146c37502d3a94f64fa5f651651b7749894cae64e6f42f7 + * MAINNET fundee payment point from extended public key #34273 #500: 022427779e5ea91e885a63413cae8e28c4d9bc451f4cdf7494fd9acc6a27828c1b + * MAINNET fundee delayed payment point from extended public key #34273 #500: 0256f501048527f6d67e155b4aa615490f675d57e99e879f0758114dfe8774f5fd + * MAINNET fundee htlc point from extended public key #34273 #500: 02d13757fd99c3e99985a263996d905d9a617fc2c17998630b3f5dd8cb79d88f79 + * MAINNET fundee shachain public point from extended public key #34273 #500: 037bc0457999d9ba6609250146fc3176f5cf382cfe5216e007267fd7fe047b2bf4 + * */ test("test vectors derivation paths (funder TESTNET)") { @@ -60,10 +75,16 @@ class LocalKeyManagerSpec extends FunSuite { val funderChannelKeyPath = LocalKeyManager.makeChannelKeyPathFunder(inputOutpoint) - // TESTNET funder funding public key from extended public key + // TESTNET funder funding public key assert(keyManager.fundingPublicKey(funderChannelKeyPath).publicKey.toBin === hex"03c69365c8ae813a45b9fead1056331c41f38ab3ab7d5d383d62c4c70cfd91f9ea") - // TESTNET funder payment point from extended public key + // TESTNET funder payment point assert(keyManager.paymentPoint(funderChannelKeyPath).publicKey.toBin === hex"02efa31ae79a7c29faf23a21f00d5ca62ce14539c2f431db5dade3e919d2ff9050") + // TESTNET funder revocation point + assert(keyManager.revocationPoint(funderChannelKeyPath).publicKey.toBin === hex"03f5cfec0e593e3d8fe965eb4ca066fe810cf8e87f7f2c0c8ababbd1a004662777") + // TESTNET funder delayed payment point + assert(keyManager.delayedPaymentPoint(funderChannelKeyPath).publicKey.toBin === hex"029b6dd179c16ea0952b7ff59ca59377ccd257b9ae8f0a6a88bd5409a07013b443") + // TESTNET funder htlc point + assert(keyManager.htlcPoint(funderChannelKeyPath).publicKey.toBin === hex"02e5afb68852863190c893d61f6244339ad368a14e0b761ab3cff5531c204e1efc") } test("test vectors derivation paths (funder MAINNET)") { @@ -114,6 +135,21 @@ class LocalKeyManagerSpec extends FunSuite { // MAINNET fundee htlc public point from extended public key assert(keyManager.htlcPoint(fundeeChannelKeyPath).publicKey.toBin === hex"03056cef064df0a65812c7c05fedfbdee978de5a9b4d986dc8b389e4c0ac42d9f1") + + + // with different counter + val fundeePubkeyKeyPath1 = LocalKeyManager.makeChannelKeyPathFundeePubkey(34273, 500) + val fundingPubkeyScript1 = Script.write(Script.pay2wsh(Scripts.multiSig2of2(keyManager.fundingPublicKey(fundeePubkeyKeyPath1).publicKey, remoteNodePubkey))) + val fundeeChannelKeyPath1 = LocalKeyManager.makeChannelKeyPathFundee(fundingPubkeyScript1) + + // MAINNET fundee revocation point + assert(keyManager.revocationPoint(fundeeChannelKeyPath1).publicKey.toBin === hex"022a714b39b36557b43146c37502d3a94f64fa5f651651b7749894cae64e6f42f7") + // MAINNET fundee payment point + assert(keyManager.paymentPoint(fundeeChannelKeyPath1).publicKey.toBin === hex"022427779e5ea91e885a63413cae8e28c4d9bc451f4cdf7494fd9acc6a27828c1b") + // MAINNET fundee delayed payment point + assert(keyManager.delayedPaymentPoint(fundeeChannelKeyPath1).publicKey.toBin === hex"0256f501048527f6d67e155b4aa615490f675d57e99e879f0758114dfe8774f5fd") + // MAINNET fundee htlc point + assert(keyManager.htlcPoint(fundeeChannelKeyPath1).publicKey.toBin === hex"02d13757fd99c3e99985a263996d905d9a617fc2c17998630b3f5dd8cb79d88f79") } } \ No newline at end of file From 92ee96cb3d9f9d53da1b701ccf0a926433631af8 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 7 Jun 2019 16:27:46 +0200 Subject: [PATCH 17/43] Do not use hardened index for shaSeed and test the shaSeed public point with test vectors --- .../main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala | 4 +++- .../scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala index dc69843779..4d7d738d15 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala @@ -95,7 +95,9 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana private def htlcSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, 4)) - private def shaSeed(channelKeyPath: DeterministicWallet.KeyPath) = Crypto.sha256(privateKeys.get(internalKeyPath(channelKeyPath, hardened(5))).privateKey.toBin) + private def shaSeed(channelKeyPath: DeterministicWallet.KeyPath) = Crypto.sha256(privateKeys.get(internalKeyPath(channelKeyPath, 5)).privateKey.toBin) + + def shaSeedPub(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, 5)) override def fundingPublicKey(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, 0)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala index 80609c7bfa..3a3fcffd8e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala @@ -16,9 +16,9 @@ package fr.acinq.eclair.crypto -import fr.acinq.bitcoin.{Block, Script} import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.DeterministicWallet.KeyPath +import fr.acinq.bitcoin.{Block, Script} import fr.acinq.eclair.transactions.Scripts import org.scalatest.FunSuite import scodec.bits._ @@ -136,7 +136,6 @@ class LocalKeyManagerSpec extends FunSuite { // MAINNET fundee htlc public point from extended public key assert(keyManager.htlcPoint(fundeeChannelKeyPath).publicKey.toBin === hex"03056cef064df0a65812c7c05fedfbdee978de5a9b4d986dc8b389e4c0ac42d9f1") - // with different counter val fundeePubkeyKeyPath1 = LocalKeyManager.makeChannelKeyPathFundeePubkey(34273, 500) val fundingPubkeyScript1 = Script.write(Script.pay2wsh(Scripts.multiSig2of2(keyManager.fundingPublicKey(fundeePubkeyKeyPath1).publicKey, remoteNodePubkey))) @@ -150,6 +149,8 @@ class LocalKeyManagerSpec extends FunSuite { assert(keyManager.delayedPaymentPoint(fundeeChannelKeyPath1).publicKey.toBin === hex"0256f501048527f6d67e155b4aa615490f675d57e99e879f0758114dfe8774f5fd") // MAINNET fundee htlc point assert(keyManager.htlcPoint(fundeeChannelKeyPath1).publicKey.toBin === hex"02d13757fd99c3e99985a263996d905d9a617fc2c17998630b3f5dd8cb79d88f79") + // MAINNED fundee shaSeed point + assert(keyManager.shaSeedPub(fundeeChannelKeyPath1).publicKey.toBin === hex"037bc0457999d9ba6609250146fc3176f5cf382cfe5216e007267fd7fe047b2bf4") } } \ No newline at end of file From 64f4b336d17a5fa3e8731b34d6f1cf37bfd21c2e Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 7 Jun 2019 16:41:49 +0200 Subject: [PATCH 18/43] Add test for fundee/funder keypath choice --- .../eclair/crypto/LocalKeyManagerSpec.scala | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala index 3a3fcffd8e..f227e7f181 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala @@ -19,6 +19,8 @@ package fr.acinq.eclair.crypto import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.DeterministicWallet.KeyPath import fr.acinq.bitcoin.{Block, Script} +import fr.acinq.eclair.TestConstants +import fr.acinq.eclair.channel.LocalParams import fr.acinq.eclair.transactions.Scripts import org.scalatest.FunSuite import scodec.bits._ @@ -153,4 +155,32 @@ class LocalKeyManagerSpec extends FunSuite { assert(keyManager.shaSeedPub(fundeeChannelKeyPath1).publicKey.toBin === hex"037bc0457999d9ba6609250146fc3176f5cf382cfe5216e007267fd7fe047b2bf4") } + test("use correct keypath to compute keys") { + val seed = hex"0101010102020202AABBCCDD030303030404040405050505060606060707070701" + val keyManager = new LocalKeyManager(seed, Block.LivenetGenesisBlock.hash) + + // FUNDER + val funderParams = TestConstants.Alice.channelParams + val funderKeyPath = funderParams.channelKeyPath.left.get + + assert(keyManager.deterministicFundingPublicKey(funderParams).publicKey == keyManager.fundingPublicKey(funderKeyPath).publicKey) + assert(keyManager.deterministicRevocationPoint(funderParams) == keyManager.revocationPoint(funderKeyPath)) + assert(keyManager.deterministicPaymentPoint(funderParams).publicKey == keyManager.paymentPoint(funderKeyPath).publicKey) + assert(keyManager.deterministicDelayedPaymentPoint(funderParams).publicKey == keyManager.delayedPaymentPoint(funderKeyPath).publicKey) + assert(keyManager.deterministicHtlcPoint(funderParams).publicKey == keyManager.htlcPoint(funderKeyPath).publicKey) + assert(keyManager.deterministicCommitmentPoint(funderParams, 0) == keyManager.commitmentPoint(funderKeyPath, 0)) + + // FUNDEE + val fundeeParams = TestConstants.Bob.channelParams + val fundeeFundingKeyPath = fundeeParams.channelKeyPath.right.get.publicKeyPath + val fundeeKeyPath = fundeeParams.channelKeyPath.right.get.pointsKeyPath + + assert(keyManager.deterministicFundingPublicKey(fundeeParams).publicKey == keyManager.fundingPublicKey(fundeeFundingKeyPath).publicKey) + assert(keyManager.deterministicRevocationPoint(fundeeParams) == keyManager.revocationPoint(fundeeKeyPath)) + assert(keyManager.deterministicPaymentPoint(fundeeParams).publicKey == keyManager.paymentPoint(fundeeKeyPath).publicKey) + assert(keyManager.deterministicDelayedPaymentPoint(fundeeParams).publicKey == keyManager.delayedPaymentPoint(fundeeKeyPath).publicKey) + assert(keyManager.deterministicHtlcPoint(fundeeParams).publicKey == keyManager.htlcPoint(fundeeKeyPath).publicKey) + assert(keyManager.deterministicCommitmentPoint(fundeeParams, 0) == keyManager.commitmentPoint(fundeeKeyPath, 0)) + } + } \ No newline at end of file From 3da0894904d05d14a6282387fd4cf9d148ac720c Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 11 Jun 2019 14:11:21 +0200 Subject: [PATCH 19/43] Add test to electrum wallet for SignTransactionResponse --- .../blockchain/electrum/ElectrumWallet.scala | 5 ++ .../electrum/ElectrumWalletSpec.scala | 87 +++++++++++++++++-- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWallet.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWallet.scala index 1541204a4c..1d835fc94e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWallet.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWallet.scala @@ -437,6 +437,11 @@ class ElectrumWallet(seed: ByteVector, client: ActorRef, params: ElectrumWallet. log.info(s"cancelling txid=${tx.txid}") stay using persistAndNotify(data.cancelTransaction(tx)) replying CancelTransactionResponse(tx) + case Event(SignTransaction(tx), data) => + log.info(s"signing txid=${tx.txid}") + val signed = data.signTransaction(tx) + stay replying SignTransactionResponse(signed) + case Event(bc@ElectrumClient.BroadcastTransaction(tx), _) => log.info(s"broadcasting txid=${tx.txid}") client forward bc diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSpec.scala index 96a9bece53..c9a0a5978c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSpec.scala @@ -24,24 +24,25 @@ import akka.actor.{ActorRef, ActorSystem, Props} import akka.testkit.{TestKit, TestProbe} import com.whisk.docker.DockerReadyChecker import fr.acinq.bitcoin.{Block, Btc, ByteVector32, DeterministicWallet, MnemonicCode, Satoshi, Transaction, TxOut} -import fr.acinq.eclair.blockchain.bitcoind.BitcoinCoreWallet.{FundTransactionResponse, SignTransactionResponse} +import fr.acinq.eclair.blockchain.MakeFundingTxResponse +import fr.acinq.eclair.blockchain.bitcoind.BitcoinCoreWallet.FundTransactionResponse import fr.acinq.eclair.blockchain.bitcoind.{BitcoinCoreWallet, BitcoindService} import fr.acinq.eclair.blockchain.electrum.ElectrumClient.{BroadcastTransaction, BroadcastTransactionResponse, SSL} import fr.acinq.eclair.blockchain.electrum.ElectrumClientPool.ElectrumServerAddress import fr.acinq.eclair.blockchain.electrum.ElectrumWallet._ import fr.acinq.eclair.blockchain.electrum.db.sqlite.SqliteWalletDb +import fr.acinq.eclair.channel.Channel import grizzled.slf4j.Logging -import org.json4s.JsonAST.{JDecimal, JString, JValue} +import org.json4s.JsonAST.{JArray, JDecimal, JString, JValue} import org.scalatest.{BeforeAndAfterAll, FunSuiteLike} import scodec.bits.ByteVector + import scala.concurrent.Await import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ - class ElectrumWalletSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with BitcoindService with ElectrumxService with BeforeAndAfterAll with Logging { - val entropy = ByteVector32(ByteVector.fill(32)(1)) val mnemonics = MnemonicCode.toMnemonics(entropy) val seed = MnemonicCode.toSeed(mnemonics, "") @@ -159,7 +160,7 @@ class ElectrumWalletSpec extends TestKit(ActorSystem("test")) with FunSuiteLike val btcWallet = new BitcoinCoreWallet(bitcoinrpcclient) val future = for { FundTransactionResponse(tx1, pos, fee) <- btcWallet.fundTransaction(tx, false, 10000) - SignTransactionResponse(tx2, true) <- btcWallet.signTransaction(tx1) + BitcoinCoreWallet.SignTransactionResponse(tx2, true) <- btcWallet.signTransaction(tx1) txid <- btcWallet.publishTransaction(tx2) } yield txid val txid = Await.result(future, 10 seconds) @@ -327,4 +328,80 @@ class ElectrumWalletSpec extends TestKit(ActorSystem("test")) with FunSuiteLike probe.send(wallet, IsDoubleSpent(tx2)) probe.expectMsg(IsDoubleSpentResponse(tx2, true)) } + + test("fund, sign and broadcast a transaction") { + val probe = TestProbe() + + val GetBalanceResponse(confirmed, unconfirmed) = getBalance(probe) + logger.info(s" balance: $confirmed $unconfirmed") + + // send money to our wallet + val GetCurrentReceiveAddressResponse(address) = getCurrentAddress(probe) + + logger.info(s"sending 1 btc to $address") + probe.send(bitcoincli, BitcoinReq("sendtoaddress", address, 1.0)) + probe.expectMsgType[JValue] + + awaitCond({ + val GetBalanceResponse(_, unconfirmed1) = getBalance(probe) + unconfirmed1 == unconfirmed + Satoshi(100000000L) + }, max = 30 seconds, interval = 1 second) + + // confirm our tx + probe.send(bitcoincli, BitcoinReq("generate", 1)) + probe.expectMsgType[JValue] + + awaitCond({ + val GetBalanceResponse(confirmed1, _) = getBalance(probe) + confirmed1 == confirmed + Satoshi(100000000L) + }, max = 30 seconds, interval = 1 second) + + val amountToSend = Satoshi(50000000L) // 0.5 BTC + + // create a tx that sends money to Bitcoin Core's address + probe.send(bitcoincli, BitcoinReq("getnewaddress")) + val JString(destinationAddress) = probe.expectMsgType[JValue] + + // raw transaction that sends the funds to a hardcoded address + val hardcodedOutput = TxOut(amountToSend, fr.acinq.eclair.addressToPublicKeyScript("2N2JczfZK7tDJ9yuH3eQ9S64fL3dMp5eNCr", Block.RegtestGenesisBlock.hash)) + val tx = Transaction(version = 2, txIn = Nil, txOut = hardcodedOutput :: Nil, lockTime = 0L) + + // this will ask the electrum wallet to attach an input (fund the tx) + probe.send(wallet, CompleteTransaction(tx, 20000)) + val CompleteTransactionResponse(tx1, _, None) = probe.expectMsgType[CompleteTransactionResponse] + + // we strip the signatures from the tx + val tx2 = Channel.stripSignaturesFromTx(MakeFundingTxResponse(tx1, 0, Satoshi(0))).fundingTx + + // assert there are no signatures + assert(tx2.txIn.forall(_.signatureScript.isEmpty)) + assert(tx2.txIn.forall(!_.hasWitness)) + + // update the transaction with the actual output we want + val tx3 = tx2.copy(txOut = tx2.txOut.updated( + tx2.txOut.indexOf(hardcodedOutput), + TxOut(amountToSend, fr.acinq.eclair.addressToPublicKeyScript(destinationAddress, Block.RegtestGenesisBlock.hash)) + )) + + // ask the electrum wallet to sign the transaction again + probe.send(wallet, SignTransaction(tx3)) + val ElectrumWallet.SignTransactionResponse(tx4) = probe.expectMsgType[SignTransactionResponse] + + // broadcast the transaction + logger.info(s"sending 0.5 btc to $address with tx ${tx4.txid}") + probe.send(wallet, BroadcastTransaction(tx4)) + val BroadcastTransactionResponse(_, None) = probe.expectMsgType[BroadcastTransactionResponse] + + // mine a block + probe.send(bitcoincli, BitcoinReq("generate", 1)) + val JArray(List(JString(blockHash))) = probe.expectMsgType[JValue] + + // get the block + probe.send(bitcoincli, BitcoinReq("getblock", blockHash, 0)) + val JString(serializedBlock) = probe.expectMsgType[JValue] + + // assert the block contains our transaction + assert(Block.read(serializedBlock).tx.exists(_.txid == tx4.txid)) + } + } From a3db46fee29aba0d394d53cd3c58b0f223d4ce03 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 11 Jun 2019 17:50:31 +0200 Subject: [PATCH 20/43] Use dummy script in MakeFundingTx --- .../scala/fr/acinq/eclair/channel/Channel.scala | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index e42948ab3a..c2f939897d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -32,7 +32,7 @@ import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire.{ChannelReestablish, _} import scodec.bits.ByteVector - +import scodec.bits._ import scala.compat.Platform import scala.concurrent.duration._ import scala.concurrent.{Await, ExecutionContext} @@ -135,6 +135,9 @@ object Channel { )) )) + // P2WSH of 2-2 multisig + val dummyMultisigScriptPubkey = hex"0020992b70c4600f066c3b63146c06e651cae903275761f1a2920d966bcb05a0c9ba" + } class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId: PublicKey, blockchain: ActorRef, router: ActorRef, relayer: ActorRef, origin_opt: Option[ActorRef] = None)(implicit ec: ExecutionContext = ExecutionContext.Implicits.global) extends FSM[State, Data] with FSMDiagnosticActorLogging[State, Data] { @@ -194,12 +197,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, true, temporaryChannelId)) forwarder ! remote - // this LOCKS the outpoints that we'll use in the funding transaction, the resulting TX is NOT signed, later in the process we update the output scriptPubkey and ask the wallet to sign - val tempKeyPath = KeyPath(Seq(1, 2, 3, 4L)) - val scriptPubKey = Script.write(Script.pay2wsh(Scripts.multiSig2of2(keyManager.fundingPublicKey(tempKeyPath).publicKey, keyManager.fundingPublicKey(tempKeyPath).publicKey))) - // the resulting funding tx response will NOT have input script signature - wallet.makeFundingTx(pubkeyScript = scriptPubKey, Satoshi(fundingSatoshis), fundingTxFeeratePerKw).map(stripSignaturesFromTx).pipeTo(self) + wallet.makeFundingTx(pubkeyScript = dummyMultisigScriptPubkey, Satoshi(fundingSatoshis), fundingTxFeeratePerKw).map(stripSignaturesFromTx).pipeTo(self) goto(WAIT_FOR_FUNDING_INTERNAL_CREATED) using DATA_WAIT_FOR_FUNDING_INTERNAL_CREATED(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, remote, remoteInit, channelFlags) @@ -309,10 +308,6 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId case Success(_) => context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, false, open.temporaryChannelId)) - - - - // TODO: maybe also check uniqueness of temporary channel id val minimumDepth = nodeParams.minDepthBlocks val accept = AcceptChannel(temporaryChannelId = open.temporaryChannelId, From 169c1b315ba5541dd64b80647ba8ccc6c7b36ca6 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 12 Jun 2019 11:25:33 +0200 Subject: [PATCH 21/43] Add test for WAIT_FOR_FUNDING_CREATED_INTERNAL --- .../fr/acinq/eclair/channel/Channel.scala | 2 - ...itForFundingCreatedInternalStateSpec.scala | 7 +-- ...aitForFundingSignedInternalStateSpec.scala | 62 +++++++++++++++++++ 3 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index c2f939897d..dce787734f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -401,8 +401,6 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId case Event(TickChannelOpenTimeout, _) => replyToUser(Left(LocalError(new RuntimeException("open channel cancelled, took too long")))) goto(CLOSED) - - }) when(WAIT_FOR_ACCEPT_CHANNEL)(handleExceptions { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala index ae5711789c..5602a5055b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala @@ -19,15 +19,11 @@ package fr.acinq.eclair.channel.states.b import akka.testkit.{TestFSMRef, TestProbe} import fr.acinq.bitcoin.{ByteVector32, Satoshi, Transaction} import fr.acinq.eclair.TestConstants.{Alice, Bob} -import fr.acinq.eclair.blockchain.{MakeFundingTxResponse, TestWallet} import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.wire._ import fr.acinq.eclair.{TestConstants, TestkitBaseClass} import org.scalatest.Outcome -import scodec.bits.ByteVector - -import scala.concurrent.{Future, Promise} import scala.concurrent.duration._ /** @@ -46,11 +42,11 @@ class WaitForFundingCreatedInternalStateSpec extends TestkitBaseClass with State within(30 seconds) { alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, alice2bob.ref, bobInit, ChannelFlags.Empty) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit) + awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL_CREATED) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) bob2alice.expectMsgType[AcceptChannel] bob2alice.forward(alice) - awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL_SIGNED) withFixture(test.toNoArgTest(FixtureParam(alice, alice2bob, bob2alice, alice2blockchain))) } } @@ -63,6 +59,7 @@ class WaitForFundingCreatedInternalStateSpec extends TestkitBaseClass with State test("recv CMD_CLOSE") { f => import f._ + alice ! CMD_CLOSE(None) awaitCond(alice.stateName == CLOSED) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala new file mode 100644 index 0000000000..b430511b70 --- /dev/null +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala @@ -0,0 +1,62 @@ +/* + * Copyright 2019 ACINQ SAS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package fr.acinq.eclair.channel.states.b + +import scala.concurrent.duration._ +import akka.testkit.{TestFSMRef, TestProbe} +import fr.acinq.bitcoin.{Block, ByteVector32, Script} +import fr.acinq.eclair.TestConstants.{Alice, Bob} +import fr.acinq.eclair.{TestConstants, TestkitBaseClass} +import fr.acinq.eclair.channel._ +import fr.acinq.eclair.channel.states.StateTestsHelperMethods +import fr.acinq.eclair.wire.{AcceptChannel, Error, Init, OpenChannel} +import org.scalatest.Outcome + +class WaitForFundingSignedInternalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { + + case class FixtureParam(alice: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe) + + override def withFixture(test: OneArgTest): Outcome = { + val setup = init() + import setup._ + val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) + val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) + within(30 seconds) { + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, alice2bob.ref, bobInit, ChannelFlags.Empty) + bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit) + alice2bob.expectMsgType[OpenChannel] + alice2bob.forward(bob) + bob2alice.expectMsgType[AcceptChannel] + bob2alice.forward(alice) + awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL_SIGNED) + withFixture(test.toNoArgTest(FixtureParam(alice, alice2bob, bob2alice, alice2blockchain))) + } + } + + test("recv Error") { f => + import f._ + alice ! Error(ByteVector32.Zeroes, "oops") + awaitCond(alice.stateName == CLOSED) + } + + test("recv CMD_CLOSE") { f => + import f._ + alice ! CMD_CLOSE(None) + awaitCond(alice.stateName == CLOSED) + } + +} From a4f32f9e780c66b4bb9d56a3a6d5cf11dd31efa6 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 12 Jun 2019 11:27:40 +0200 Subject: [PATCH 22/43] Remove empty lines --- .../main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala | 5 ----- 1 file changed, 5 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala index 4d7d738d15..dbdb8c1946 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala @@ -142,7 +142,6 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana } paymentPoint(keyPath) - } override def deterministicDelayedPaymentPoint(localParams: LocalParams): ExtendedPublicKey = { @@ -152,7 +151,6 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana } delayedPaymentPoint(keyPath) - } override def deterministicHtlcPoint(localParams: LocalParams): ExtendedPublicKey = { @@ -162,7 +160,6 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana } htlcPoint(keyPath) - } override def deterministicCommitmentSecret(localParams: LocalParams, index: Long): Scalar = { @@ -226,7 +223,6 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana } override def signChannelAnnouncement(channelKeyPath: DeterministicWallet.KeyPath, chainHash: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: ByteVector): (ByteVector, ByteVector) = { - val witness = if (Announcements.isNode1(nodeId, remoteNodeId)) { Announcements.channelAnnouncementWitnessEncode(chainHash, shortChannelId, nodeId, remoteNodeId, fundingPublicKey(channelKeyPath).publicKey, remoteFundingKey, features) } else { @@ -238,7 +234,6 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana } override def deterministicSignChannelAnnouncement(localParams: LocalParams, chainHash: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: ByteVector): (ByteVector, ByteVector) = { - val keyPath = localParams.channelKeyPath match { case Left(kp) => kp case Right(keyPathFundee) => keyPathFundee.publicKeyPath From 726c5d991e6204cb86ec31577f33cdaf9d466c9d Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 12 Jun 2019 16:13:16 +0200 Subject: [PATCH 23/43] Rollback funding tx if the flow is aborted in state WAIT_FOR_ACCEPT_CHANNEL --- .../scala/fr/acinq/eclair/channel/Channel.scala | 15 +++++++-------- .../states/a/WaitForAcceptChannelStateSpec.scala | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index dce787734f..4ca3b5c9ca 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -20,7 +20,6 @@ import akka.actor.{ActorRef, FSM, OneForOneStrategy, Props, Status, SupervisorSt import akka.event.Logging.MDC import akka.pattern.pipe import fr.acinq.bitcoin.Crypto.{PublicKey, Scalar, sha256} -import fr.acinq.bitcoin.DeterministicWallet.KeyPath import fr.acinq.bitcoin._ import fr.acinq.eclair._ import fr.acinq.eclair.blockchain._ @@ -98,17 +97,13 @@ object Channel { } def makeFundeeChannelParams(nodeParams: NodeParams, open: OpenChannel, defaultFinalScriptPubKey: ByteVector, fundingSatoshis: Long): LocalParams = { - val blockHeight = Globals.blockCount.get val counter = nodeParams.db.channels.getCounterFor(blockHeight) val publicKeyPath = LocalKeyManager.makeChannelKeyPathFundeePubkey(blockHeight, counter) val localFundingPubkey = nodeParams.keyManager.fundingPublicKey(publicKeyPath).publicKey - val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey, open.fundingPubkey))) val channelKeyPath = LocalKeyManager.makeChannelKeyPathFundee(fundingPubkeyScript) - val channelKeyPaths = KeyPathFundee(publicKeyPath, channelKeyPath) - makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder = false, fundingSatoshis, Right(channelKeyPaths)) } @@ -441,20 +436,24 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId goto(WAIT_FOR_FUNDING_INTERNAL_SIGNED) using DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, accept.firstPerCommitmentPoint, open) } - case Event(CMD_CLOSE(_), _) => + case Event(CMD_CLOSE(_) | CMD_FORCECLOSE, d: DATA_WAIT_FOR_ACCEPT_CHANNEL) => replyToUser(Right("closed")) + wallet.rollback(d.unsignedFundingTx.fundingTx) goto(CLOSED) replying "ok" case Event(e: Error, d: DATA_WAIT_FOR_ACCEPT_CHANNEL) => replyToUser(Left(RemoteError(e))) + wallet.rollback(d.unsignedFundingTx.fundingTx) handleRemoteError(e, d) - case Event(INPUT_DISCONNECTED, _) => + case Event(INPUT_DISCONNECTED, d: DATA_WAIT_FOR_ACCEPT_CHANNEL) => replyToUser(Left(LocalError(new RuntimeException("disconnected")))) + wallet.rollback(d.unsignedFundingTx.fundingTx) goto(CLOSED) - case Event(TickChannelOpenTimeout, _) => + case Event(TickChannelOpenTimeout, d: DATA_WAIT_FOR_ACCEPT_CHANNEL) => replyToUser(Left(LocalError(new RuntimeException("open channel cancelled, took too long")))) + wallet.rollback(d.unsignedFundingTx.fundingTx) goto(CLOSED) }) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala index e87a91209a..6b2e9d50ec 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala @@ -145,19 +145,34 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp test("recv Error") { f => import f._ + val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_ACCEPT_CHANNEL].unsignedFundingTx.fundingTx + assert(alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.isEmpty) + alice ! Error(ByteVector32.Zeroes, "oops") + + assert(alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.contains(fundingTx)) awaitCond(alice.stateName == CLOSED) } test("recv CMD_CLOSE") { f => import f._ + val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_ACCEPT_CHANNEL].unsignedFundingTx.fundingTx + assert(alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.isEmpty) + alice ! CMD_CLOSE(None) + + assert(alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.contains(fundingTx)) awaitCond(alice.stateName == CLOSED) } test("recv TickChannelOpenTimeout") { f => import f._ + val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_ACCEPT_CHANNEL].unsignedFundingTx.fundingTx + assert(alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.isEmpty) + alice ! TickChannelOpenTimeout + + assert(alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.contains(fundingTx)) awaitCond(alice.stateName == CLOSED) } From 5ded959a411b41d53a36754dca7c2aa2a6a38b6f Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 12 Jun 2019 17:22:23 +0200 Subject: [PATCH 24/43] Remove unused functions --- .../main/scala/fr/acinq/eclair/io/Peer.scala | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index 886d3a1a30..84a0b37875 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -577,30 +577,6 @@ object Peer { // @formatter:on - def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long): LocalParams = { - val entropy = new Array[Byte](16) - secureRandom.nextBytes(entropy) - val bis = new ByteArrayInputStream(entropy) - val channelKeyPath = DeterministicWallet.KeyPath(Seq(Protocol.uint32(bis, ByteOrder.BIG_ENDIAN), Protocol.uint32(bis, ByteOrder.BIG_ENDIAN), Protocol.uint32(bis, ByteOrder.BIG_ENDIAN), Protocol.uint32(bis, ByteOrder.BIG_ENDIAN))) - makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder, fundingSatoshis, channelKeyPath) - } - - def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, channelKeyPath: DeterministicWallet.KeyPath): LocalParams = { - LocalParams( - nodeParams.nodeId, - Left(channelKeyPath), - dustLimitSatoshis = nodeParams.dustLimitSatoshis, - maxHtlcValueInFlightMsat = nodeParams.maxHtlcValueInFlightMsat, - channelReserveSatoshis = Math.max((nodeParams.reserveToFundingRatio * fundingSatoshis).toLong, nodeParams.dustLimitSatoshis), // BOLT #2: make sure that our reserve is above our dust limit - htlcMinimumMsat = nodeParams.htlcMinimumMsat, - toSelfDelay = nodeParams.toRemoteDelayBlocks, // we choose their delay - maxAcceptedHtlcs = nodeParams.maxAcceptedHtlcs, - defaultFinalScriptPubKey = defaultFinalScriptPubKey, - isFunder = isFunder, - globalFeatures = nodeParams.globalFeatures, - localFeatures = nodeParams.localFeatures) - } - /** * Peer may want to filter announcements based on timestamp * From 9e4db87ce29e6c45c6629159dd8ccdde6262b11f Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 12 Jun 2019 17:53:13 +0200 Subject: [PATCH 25/43] Fix race condition in flaky test WaitForFundingCreatedInternalStateSpec --- .../scala/fr/acinq/eclair/crypto/LocalKeyManager.scala | 1 - .../b/WaitForFundingCreatedInternalStateSpec.scala | 10 +++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala index dbdb8c1946..75b237a441 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala @@ -55,7 +55,6 @@ object LocalKeyManager { makeChannelKeyPathFundeePubkey(blockHeightBytes ++ counterBytes) } - } /** diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala index 5602a5055b..b71bb94306 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala @@ -16,14 +16,17 @@ package fr.acinq.eclair.channel.states.b +import akka.actor.FSM.{CurrentState, SubscribeTransitionCallBack, Transition} import akka.testkit.{TestFSMRef, TestProbe} import fr.acinq.bitcoin.{ByteVector32, Satoshi, Transaction} import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.states.StateTestsHelperMethods +import fr.acinq.eclair.payment.PaymentLifecycle.WAITING_FOR_REQUEST import fr.acinq.eclair.wire._ import fr.acinq.eclair.{TestConstants, TestkitBaseClass} import org.scalatest.Outcome + import scala.concurrent.duration._ /** @@ -40,9 +43,14 @@ class WaitForFundingCreatedInternalStateSpec extends TestkitBaseClass with State val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) within(30 seconds) { + val monitor = TestProbe() + alice ! SubscribeTransitionCallBack(monitor.ref) + val CurrentState(_, WAIT_FOR_INIT_INTERNAL) = monitor.expectMsgClass(classOf[CurrentState[_]]) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, alice2bob.ref, bobInit, ChannelFlags.Empty) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit) - awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL_CREATED) + + val Transition(_, WAIT_FOR_INIT_INTERNAL, WAIT_FOR_FUNDING_INTERNAL_CREATED) = monitor.expectMsgClass(classOf[Transition[_]]) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) bob2alice.expectMsgType[AcceptChannel] From 12caee2f91aaef16c8dfae4d0382b97062da01a4 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 14 Jun 2019 10:58:40 +0200 Subject: [PATCH 26/43] KeyManager cleanup from deterministic* methods --- .../fr/acinq/eclair/channel/Channel.scala | 64 ++++++++------- .../acinq/eclair/channel/ChannelTypes.scala | 2 +- .../fr/acinq/eclair/channel/Commitments.scala | 31 ++++---- .../fr/acinq/eclair/channel/Helpers.scala | 54 ++++++------- .../fr/acinq/eclair/crypto/KeyManager.scala | 23 ------ .../acinq/eclair/crypto/LocalKeyManager.scala | 78 ------------------- .../channel/states/e/NormalStateSpec.scala | 6 +- .../channel/states/e/OfflineStateSpec.scala | 10 +-- .../eclair/crypto/LocalKeyManagerSpec.scala | 27 +++---- .../fr/acinq/eclair/db/ChannelStateSpec.scala | 3 +- 10 files changed, 105 insertions(+), 193 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 4ca3b5c9ca..ba435b9615 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -133,6 +133,16 @@ object Channel { // P2WSH of 2-2 multisig val dummyMultisigScriptPubkey = hex"0020992b70c4600f066c3b63146c06e651cae903275761f1a2920d966bcb05a0c9ba" + def fundingKeyPath(localParams: LocalParams) = localParams.channelKeyPath match { + case Left(funderKeyPath) => funderKeyPath + case Right(fundeeKeyPath) => fundeeKeyPath.fundingKeyPath + } + + def keyPath(localParams: LocalParams) = localParams.channelKeyPath match { + case Left(funderKeyPath) => funderKeyPath + case Right(fundeeKeyPath) => fundeeKeyPath.pointsKeyPath + } + } class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId: PublicKey, blockchain: ActorRef, router: ActorRef, relayer: ActorRef, origin_opt: Option[ActorRef] = None)(implicit ec: ExecutionContext = ExecutionContext.Implicits.global) extends FSM[State, Data] with FSMDiagnosticActorLogging[State, Data] { @@ -313,12 +323,12 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId htlcMinimumMsat = localParams.htlcMinimumMsat, toSelfDelay = localParams.toSelfDelay, maxAcceptedHtlcs = localParams.maxAcceptedHtlcs, - fundingPubkey = keyManager.deterministicFundingPublicKey(localParams).publicKey, - revocationBasepoint = keyManager.deterministicRevocationPoint(localParams).publicKey, - paymentBasepoint = keyManager.deterministicPaymentPoint(localParams).publicKey, - delayedPaymentBasepoint = keyManager.deterministicDelayedPaymentPoint(localParams).publicKey, - htlcBasepoint = keyManager.deterministicHtlcPoint(localParams).publicKey, - firstPerCommitmentPoint = keyManager.deterministicCommitmentPoint(localParams, 0)) + fundingPubkey = keyManager.fundingPublicKey(fundingKeyPath(localParams)).publicKey, + revocationBasepoint = keyManager.revocationPoint(keyPath(localParams)).publicKey, + paymentBasepoint = keyManager.paymentPoint(keyPath(localParams)).publicKey, + delayedPaymentBasepoint = keyManager.delayedPaymentPoint(keyPath(localParams)).publicKey, + htlcBasepoint = keyManager.htlcPoint(keyPath(localParams)).publicKey, + firstPerCommitmentPoint = keyManager.commitmentPoint(keyPath(localParams), 0)) val remoteParams = RemoteParams( nodeId = remoteNodeId, dustLimitSatoshis = open.dustLimitSatoshis, @@ -363,12 +373,12 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId feeratePerKw = initialFeeratePerKw, toSelfDelay = localParams.toSelfDelay, maxAcceptedHtlcs = localParams.maxAcceptedHtlcs, - fundingPubkey = keyManager.deterministicFundingPublicKey(localParams).publicKey, - revocationBasepoint = keyManager.deterministicRevocationPoint(localParams).publicKey, - paymentBasepoint = keyManager.deterministicPaymentPoint(localParams).publicKey, - delayedPaymentBasepoint = keyManager.deterministicDelayedPaymentPoint(localParams).publicKey, - htlcBasepoint = keyManager.deterministicHtlcPoint(localParams).publicKey, - firstPerCommitmentPoint = keyManager.deterministicCommitmentPoint(localParams, 0), + fundingPubkey = keyManager.fundingPublicKey(fundingKeyPath(localParams)).publicKey, + revocationBasepoint = keyManager.revocationPoint(keyPath(localParams)).publicKey, + paymentBasepoint = keyManager.paymentPoint(keyPath(localParams)).publicKey, + delayedPaymentBasepoint = keyManager.delayedPaymentPoint(keyPath(localParams)).publicKey, + htlcBasepoint = keyManager.htlcPoint(keyPath(localParams)).publicKey, + firstPerCommitmentPoint = keyManager.commitmentPoint(keyPath(localParams), 0), channelFlags = channelFlags) val nextStateData = INPUT_INIT_FUNDER_WITH_PARAMS(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, localParams, remote, remoteInit, channelFlags) @@ -422,7 +432,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId localFeatures = remoteInit.localFeatures) log.debug(s"remote params: $remoteParams") - val localFundingPubkey = keyManager.deterministicFundingPublicKey(localParams).publicKey + val localFundingPubkey = keyManager.fundingPublicKey(fundingKeyPath(localParams)).publicKey val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey, remoteParams.fundingPubKey))) // update the funding TX with the new scriptPubkey @@ -462,7 +472,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId // let's create the first commitment tx that spends the yet uncommitted funding tx val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.maxFeerateMismatch) require(fundingTx.txOut(fundingTxOutputIndex).publicKeyScript == localCommitTx.input.txOut.publicKeyScript, s"pubkey script mismatch!") - val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.deterministicFundingPublicKey(localParams)) + val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(fundingKeyPath(localParams))) // signature of their initial commitment tx that pays remote pushMsat val fundingCreated = FundingCreated( temporaryChannelId = temporaryChannelId, @@ -505,12 +515,12 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, temporaryChannelId, localParams, remoteParams, fundingSatoshis: Long, pushMsat, initialFeeratePerKw, fundingTxHash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.maxFeerateMismatch) // check remote signature validity - val localSigOfLocalTx = keyManager.sign(localCommitTx, keyManager.deterministicFundingPublicKey(localParams)) - val signedLocalCommitTx = Transactions.addSigs(localCommitTx, keyManager.deterministicFundingPublicKey(localParams).publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig) + val localSigOfLocalTx = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(fundingKeyPath(localParams))) + val signedLocalCommitTx = Transactions.addSigs(localCommitTx, keyManager.fundingPublicKey(fundingKeyPath(localParams))publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig) Transactions.checkSpendable(signedLocalCommitTx) match { case Failure(cause) => handleLocalError(InvalidCommitmentSignature(temporaryChannelId, signedLocalCommitTx.tx), d, None) case Success(_) => - val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.deterministicFundingPublicKey(localParams)) + val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(fundingKeyPath(localParams))) val channelId = toLongId(fundingTxHash, fundingTxOutputIndex) // watch the funding tx transaction val commitInput = localCommitTx.input @@ -547,8 +557,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId when(WAIT_FOR_FUNDING_SIGNED)(handleExceptions { case Event(msg@FundingSigned(_, remoteSig), d@DATA_WAIT_FOR_FUNDING_SIGNED(channelId, localParams, remoteParams, fundingTx, fundingTxFee, localSpec, localCommitTx, remoteCommit, channelFlags, fundingCreated)) => // we make sure that their sig checks out and that our first commit tx is spendable - val localSigOfLocalTx = keyManager.sign(localCommitTx, keyManager.deterministicFundingPublicKey(localParams)) - val signedLocalCommitTx = Transactions.addSigs(localCommitTx, keyManager.deterministicFundingPublicKey(localParams).publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig) + val localSigOfLocalTx = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(fundingKeyPath(localParams))) + val signedLocalCommitTx = Transactions.addSigs(localCommitTx, keyManager.fundingPublicKey(fundingKeyPath(localParams)).publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig) Transactions.checkSpendable(signedLocalCommitTx) match { case Failure(cause) => // we rollback the funding tx, it will never be published @@ -620,7 +630,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId case Event(WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK, blockHeight, txIndex), DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments, _, _, deferred, _)) => log.info(s"channelId=${commitments.channelId} was confirmed at blockHeight=$blockHeight txIndex=$txIndex") blockchain ! WatchLost(self, commitments.commitInput.outPoint.txid, nodeParams.minDepthBlocks, BITCOIN_FUNDING_LOST) - val nextPerCommitmentPoint = keyManager.deterministicCommitmentPoint(commitments.localParams, 1) + val nextPerCommitmentPoint = keyManager.commitmentPoint(keyPath(commitments.localParams), 1) val fundingLocked = FundingLocked(commitments.channelId, nextPerCommitmentPoint) deferred.map(self ! _) // this is the temporary channel id that we will use in our channel_update message, the goal is to be able to use our channel @@ -975,7 +985,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId require(d.shortChannelId == remoteAnnSigs.shortChannelId, s"shortChannelId mismatch: local=${d.shortChannelId} remote=${remoteAnnSigs.shortChannelId}") log.info(s"announcing channelId=${d.channelId} on the network with shortId=${d.shortChannelId}") import d.commitments.{localParams, remoteParams} - val channelAnn = Announcements.makeChannelAnnouncement(nodeParams.chainHash, localAnnSigs.shortChannelId, nodeParams.nodeId, remoteParams.nodeId, keyManager.deterministicFundingPublicKey(localParams).publicKey, remoteParams.fundingPubKey, localAnnSigs.nodeSignature, remoteAnnSigs.nodeSignature, localAnnSigs.bitcoinSignature, remoteAnnSigs.bitcoinSignature) + val channelAnn = Announcements.makeChannelAnnouncement(nodeParams.chainHash, localAnnSigs.shortChannelId, nodeParams.nodeId, remoteParams.nodeId, keyManager.fundingPublicKey(fundingKeyPath(localParams)).publicKey, remoteParams.fundingPubKey, localAnnSigs.nodeSignature, remoteAnnSigs.nodeSignature, localAnnSigs.bitcoinSignature, remoteAnnSigs.bitcoinSignature) // we use GOTO instead of stay because we want to fire transitions goto(NORMAL) using store(d.copy(channelAnnouncement = Some(channelAnn))) case Some(_) => @@ -1475,7 +1485,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId forwarder ! r val yourLastPerCommitmentSecret = d.commitments.remotePerCommitmentSecrets.lastIndex.flatMap(d.commitments.remotePerCommitmentSecrets.getHash).getOrElse(ByteVector32.Zeroes) - val myCurrentPerCommitmentPoint = keyManager.deterministicCommitmentPoint(d.commitments.localParams, d.commitments.localCommit.index) + val myCurrentPerCommitmentPoint = keyManager.commitmentPoint(keyPath(d.commitments.localParams), d.commitments.localCommit.index) val channelReestablish = ChannelReestablish( channelId = d.channelId, @@ -1531,7 +1541,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId case Event(_: ChannelReestablish, d: DATA_WAIT_FOR_FUNDING_LOCKED) => log.debug(s"re-sending fundingLocked") - val nextPerCommitmentPoint = keyManager.deterministicCommitmentPoint(d.commitments.localParams, 1) + val nextPerCommitmentPoint = keyManager.commitmentPoint(keyPath(d.commitments.localParams), 1) val fundingLocked = FundingLocked(d.commitments.channelId, nextPerCommitmentPoint) goto(WAIT_FOR_FUNDING_LOCKED) sending fundingLocked @@ -1540,7 +1550,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId case ChannelReestablish(_, _, nextRemoteRevocationNumber, Some(yourLastPerCommitmentSecret), _) if !Helpers.checkLocalCommit(d, nextRemoteRevocationNumber) => // if next_remote_revocation_number is greater than our local commitment index, it means that either we are using an outdated commitment, or they are lying // but first we need to make sure that the last per_commitment_secret that they claim to have received from us is correct for that next_remote_revocation_number minus 1 - if (keyManager.deterministicCommitmentSecret(d.commitments.localParams, nextRemoteRevocationNumber - 1) == yourLastPerCommitmentSecret) { + if (keyManager.commitmentSecret(keyPath(d.commitments.localParams), nextRemoteRevocationNumber - 1) == yourLastPerCommitmentSecret) { log.warning(s"counterparty proved that we have an outdated (revoked) local commitment!!! ourCommitmentNumber=${d.commitments.localCommit.index} theirCommitmentNumber=${nextRemoteRevocationNumber}") // their data checks out, we indeed seem to be using an old revoked commitment, and must absolutely *NOT* publish it, because that would be a cheating attempt and they // would punish us by taking all the funds in the channel @@ -1565,7 +1575,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId if (channelReestablish.nextLocalCommitmentNumber == 1 && d.commitments.localCommit.index == 0) { // If next_local_commitment_number is 1 in both the channel_reestablish it sent and received, then the node MUST retransmit funding_locked, otherwise it MUST NOT log.debug(s"re-sending fundingLocked") - val nextPerCommitmentPoint = keyManager.deterministicCommitmentPoint(d.commitments.localParams, 1) + val nextPerCommitmentPoint = keyManager.commitmentPoint(keyPath(d.commitments.localParams), 1) val fundingLocked = FundingLocked(d.commitments.channelId, nextPerCommitmentPoint) forwarder ! fundingLocked } @@ -2130,8 +2140,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId } else if (commitments1.localCommit.index == channelReestablish.nextRemoteRevocationNumber + 1) { // our last revocation got lost, let's resend it log.debug(s"re-sending last revocation") - val localPerCommitmentSecret = keyManager.deterministicCommitmentSecret(commitments1.localParams, d.commitments.localCommit.index - 1) - val localNextPerCommitmentPoint = keyManager.deterministicCommitmentPoint(commitments1.localParams, d.commitments.localCommit.index + 1) + val localPerCommitmentSecret = keyManager.commitmentSecret(keyPath(commitments1.localParams), d.commitments.localCommit.index - 1) + val localNextPerCommitmentPoint = keyManager.commitmentPoint(keyPath(commitments1.localParams), d.commitments.localCommit.index + 1) val revocation = RevokeAndAck( channelId = commitments1.channelId, perCommitmentSecret = localPerCommitmentSecret, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala index 2cb2a855ac..08600ef245 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala @@ -210,7 +210,7 @@ final case class LocalParams(nodeId: PublicKey, require(isFunder && channelKeyPath.isLeft || !isFunder && channelKeyPath.isRight, s"Wrong keyPath derivation for isFunder=$isFunder") } -case class KeyPathFundee(publicKeyPath: KeyPath, pointsKeyPath: KeyPath) +case class KeyPathFundee(fundingKeyPath: KeyPath, pointsKeyPath: KeyPath) final case class RemoteParams(nodeId: PublicKey, dustLimitSatoshis: Long, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 844efb39e5..3b8516c3b6 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -19,6 +19,7 @@ package fr.acinq.eclair.channel import akka.event.LoggingAdapter import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, sha256} import fr.acinq.bitcoin.{ByteVector32, Crypto, Satoshi} +import fr.acinq.eclair.channel.Channel._ import fr.acinq.eclair.crypto.{Generators, KeyManager, ShaChain, Sphinx} import fr.acinq.eclair.payment._ import fr.acinq.eclair.transactions.Transactions._ @@ -359,10 +360,10 @@ object Commitments { // remote commitment will includes all local changes + remote acked changes val spec = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed) val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(keyManager, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, spec) - val sig = keyManager.sign(remoteCommitTx, keyManager.deterministicFundingPublicKey(localParams)) + val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(fundingKeyPath(localParams))) val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index) - val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.deterministicHtlcPoint(localParams), remoteNextPerCommitmentPoint)) + val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(keyPath(localParams)), remoteNextPerCommitmentPoint)) // NB: IN/OUT htlcs are inverted because this is the remote commit log.info(s"built remote commit number=${remoteCommit.index + 1} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${remoteCommitTx.tx.txid} tx={}", spec.htlcs.filter(_.direction == OUT).map(_.add.id).mkString(","), spec.htlcs.filter(_.direction == IN).map(_.add.id).mkString(","), remoteCommitTx.tx) @@ -406,16 +407,16 @@ object Commitments { // receiving money i.e its commit tx has one output for them val spec = CommitmentSpec.reduce(localCommit.spec, localChanges.acked, remoteChanges.proposed) - val localPerCommitmentPoint = keyManager.deterministicCommitmentPoint(localParams, commitments.localCommit.index + 1) + val localPerCommitmentPoint = keyManager.commitmentPoint(keyPath(localParams), commitments.localCommit.index + 1) val (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeLocalTxs(keyManager, localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, spec) - val sig = keyManager.sign(localCommitTx, keyManager.deterministicFundingPublicKey(localParams)) + val sig = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(fundingKeyPath(localParams))) log.info(s"built local commit number=${localCommit.index + 1} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${localCommitTx.tx.txid} tx={}", spec.htlcs.filter(_.direction == IN).map(_.add.id).mkString(","), spec.htlcs.filter(_.direction == OUT).map(_.add.id).mkString(","), localCommitTx.tx) // TODO: should we have optional sig? (original comment: this tx will NOT be signed if our output is empty) // no need to compute htlc sigs if commit sig doesn't check out - val signedCommitTx = Transactions.addSigs(localCommitTx, keyManager.deterministicFundingPublicKey(localParams).publicKey, remoteParams.fundingPubKey, sig, commit.signature) + val signedCommitTx = Transactions.addSigs(localCommitTx, keyManager.fundingPublicKey(fundingKeyPath(localParams)).publicKey, remoteParams.fundingPubKey, sig, commit.signature) if (Transactions.checkSpendable(signedCommitTx).isFailure) { throw InvalidCommitmentSignature(commitments.channelId, signedCommitTx.tx) } @@ -424,7 +425,7 @@ object Commitments { if (commit.htlcSignatures.size != sortedHtlcTxs.size) { throw new HtlcSigCountMismatch(commitments.channelId, sortedHtlcTxs.size, commit.htlcSignatures.size) } - val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.deterministicHtlcPoint(localParams), localPerCommitmentPoint)) + val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(keyPath(localParams)), localPerCommitmentPoint)) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint) // combine the sigs to make signed txes val htlcTxsAndSigs = (sortedHtlcTxs, htlcSigs, commit.htlcSignatures).zipped.toList.collect { @@ -442,8 +443,8 @@ object Commitments { } // we will send our revocation preimage + our next revocation hash - val localPerCommitmentSecret = keyManager.deterministicCommitmentSecret(localParams, commitments.localCommit.index) - val localNextPerCommitmentPoint = keyManager.deterministicCommitmentPoint(localParams, commitments.localCommit.index + 2) + val localPerCommitmentSecret = keyManager.commitmentSecret(keyPath(localParams), commitments.localCommit.index) + val localNextPerCommitmentPoint = keyManager.commitmentPoint(keyPath(localParams), commitments.localCommit.index + 2) val revocation = RevokeAndAck( channelId = commitments.channelId, perCommitmentSecret = localPerCommitmentSecret, @@ -504,23 +505,23 @@ object Commitments { } def makeLocalTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: Point, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { - val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.deterministicDelayedPaymentPoint(localParams).publicKey, localPerCommitmentPoint) - val localHtlcPubkey = Generators.derivePubKey(keyManager.deterministicHtlcPoint(localParams).publicKey, localPerCommitmentPoint) + val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(keyPath(localParams)).publicKey, localPerCommitmentPoint) + val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(keyPath(localParams)).publicKey, localPerCommitmentPoint) val remotePaymentPubkey = Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint) val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) - val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, keyManager.deterministicPaymentPoint(localParams).publicKey, remoteParams.paymentBasepoint, localParams.isFunder, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) + val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, keyManager.paymentPoint(keyPath(localParams)).publicKey, remoteParams.paymentBasepoint, localParams.isFunder, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } def makeRemoteTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: Point, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { - val localPaymentPubkey = Generators.derivePubKey(keyManager.deterministicPaymentPoint(localParams).publicKey, remotePerCommitmentPoint) - val localHtlcPubkey = Generators.derivePubKey(keyManager.deterministicHtlcPoint(localParams).publicKey, remotePerCommitmentPoint) + val localPaymentPubkey = Generators.derivePubKey(keyManager.paymentPoint(keyPath(localParams)).publicKey, remotePerCommitmentPoint) + val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(keyPath(localParams)).publicKey, remotePerCommitmentPoint) val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint) - val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.deterministicRevocationPoint(localParams).publicKey, remotePerCommitmentPoint) - val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, keyManager.deterministicPaymentPoint(localParams).publicKey, !localParams.isFunder, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) + val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(keyPath(localParams)).publicKey, remotePerCommitmentPoint) + val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, keyManager.paymentPoint(keyPath(localParams)).publicKey, !localParams.isFunder, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 8fc260b798..1a94a4ac60 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -21,7 +21,7 @@ import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, Scalar, ripemd160, import fr.acinq.bitcoin.Script._ import fr.acinq.bitcoin.{OutPoint, _} import fr.acinq.eclair.blockchain.EclairWallet -import fr.acinq.eclair.channel.Channel.REFRESH_CHANNEL_UPDATE_INTERVAL +import fr.acinq.eclair.channel.Channel.{REFRESH_CHANNEL_UPDATE_INTERVAL, fundingKeyPath, keyPath} import fr.acinq.eclair.crypto.{Generators, KeyManager} import fr.acinq.eclair.db.ChannelsDb import fr.acinq.eclair.payment.{Local, Origin} @@ -205,7 +205,7 @@ object Helpers { def makeAnnouncementSignatures(nodeParams: NodeParams, commitments: Commitments, shortChannelId: ShortChannelId) = { val features = ByteVector.empty // empty features for now - val (localNodeSig, localBitcoinSig) = nodeParams.keyManager.deterministicSignChannelAnnouncement(commitments.localParams, nodeParams.chainHash, shortChannelId, commitments.remoteParams.nodeId, commitments.remoteParams.fundingPubKey, features) + val (localNodeSig, localBitcoinSig) = nodeParams.keyManager.signChannelAnnouncement(fundingKeyPath(commitments.localParams), nodeParams.chainHash, shortChannelId, commitments.remoteParams.nodeId, commitments.remoteParams.fundingPubKey, features) AnnouncementSignatures(commitments.channelId, shortChannelId, localNodeSig, localBitcoinSig) } @@ -269,8 +269,8 @@ object Helpers { } } - val commitmentInput = makeFundingInputInfo(fundingTxHash, fundingTxOutputIndex, Satoshi(fundingSatoshis), keyManager.deterministicFundingPublicKey(localParams).publicKey, remoteParams.fundingPubKey) - val localPerCommitmentPoint = keyManager.deterministicCommitmentPoint(localParams, 0) + val commitmentInput = makeFundingInputInfo(fundingTxHash, fundingTxOutputIndex, Satoshi(fundingSatoshis), keyManager.fundingPublicKey(fundingKeyPath(localParams)).publicKey, remoteParams.fundingPubKey) + val localPerCommitmentPoint = keyManager.commitmentPoint(keyPath(localParams), 0) val (localCommitTx, _, _) = Commitments.makeLocalTxs(keyManager, 0, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, localSpec) val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, 0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, remoteSpec) @@ -441,7 +441,7 @@ object Helpers { // TODO: check that val dustLimitSatoshis = Satoshi(Math.max(localParams.dustLimitSatoshis, remoteParams.dustLimitSatoshis)) val closingTx = Transactions.makeClosingTx(commitInput, localScriptPubkey, remoteScriptPubkey, localParams.isFunder, dustLimitSatoshis, closingFee, localCommit.spec) - val localClosingSig = keyManager.sign(closingTx, keyManager.deterministicFundingPublicKey(commitments.localParams)) + val localClosingSig = keyManager.sign(closingTx, keyManager.fundingPublicKey(fundingKeyPath(commitments.localParams))) val closingSigned = ClosingSigned(channelId, closingFee.amount, localClosingSig) log.info(s"signed closing txid=${closingTx.tx.txid} with closingFeeSatoshis=${closingSigned.feeSatoshis}") log.debug(s"closingTxid=${closingTx.tx.txid} closingTx=${closingTx.tx}}") @@ -456,7 +456,7 @@ object Helpers { throw InvalidCloseFee(commitments.channelId, remoteClosingFee.amount) } val (closingTx, closingSigned) = makeClosingTx(keyManager, commitments, localScriptPubkey, remoteScriptPubkey, remoteClosingFee) - val signedClosingTx = Transactions.addSigs(closingTx, keyManager.deterministicFundingPublicKey(commitments.localParams).publicKey, remoteParams.fundingPubKey, closingSigned.signature, remoteClosingSig) + val signedClosingTx = Transactions.addSigs(closingTx, keyManager.fundingPublicKey(fundingKeyPath(commitments.localParams)).publicKey, remoteParams.fundingPubKey, closingSigned.signature, remoteClosingSig) Transactions.checkSpendable(signedClosingTx).map(x => signedClosingTx.tx).recover { case _ => throw InvalidCloseSignature(commitments.channelId, signedClosingTx.tx) } } @@ -486,9 +486,9 @@ object Helpers { import commitments._ require(localCommit.publishableTxs.commitTx.tx.txid == tx.txid, "txid mismatch, provided tx is not the current local commit tx") - val localPerCommitmentPoint = keyManager.deterministicCommitmentPoint(localParams, commitments.localCommit.index.toInt) + val localPerCommitmentPoint = keyManager.commitmentPoint(keyPath(localParams), commitments.localCommit.index.toInt) val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) - val localDelayedPubkey = Generators.derivePubKey(keyManager.deterministicDelayedPaymentPoint(localParams).publicKey, localPerCommitmentPoint) + val localDelayedPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(keyPath(localParams)).publicKey, localPerCommitmentPoint) // no need to use a high fee rate for delayed transactions (we are the only one who can spend them) val feeratePerKwDelayed = Globals.feeratesPerKw.get.blocks_6 @@ -496,7 +496,7 @@ object Helpers { // first we will claim our main output as soon as the delay is over val mainDelayedTx = generateTx("main-delayed-output")(Try { val claimDelayed = Transactions.makeClaimDelayedOutputTx(tx, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwDelayed) - val sig = keyManager.sign(claimDelayed, keyManager.deterministicDelayedPaymentPoint(localParams), localPerCommitmentPoint) + val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(keyPath(localParams)), localPerCommitmentPoint) Transactions.addSigs(claimDelayed, sig) }) @@ -531,7 +531,7 @@ object Helpers { remoteParams.toSelfDelay, localDelayedPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwDelayed) - val sig = keyManager.sign(claimDelayed, keyManager.deterministicDelayedPaymentPoint(localParams), localPerCommitmentPoint) + val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(keyPath(localParams)), localPerCommitmentPoint) Transactions.addSigs(claimDelayed, sig) }) } @@ -560,10 +560,10 @@ object Helpers { val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec) require(remoteCommitTx.tx.txid == tx.txid, "txid mismatch, cannot recompute the current remote commit tx") - val localHtlcPubkey = Generators.derivePubKey(keyManager.deterministicHtlcPoint(localParams).publicKey, remoteCommit.remotePerCommitmentPoint) + val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(keyPath(localParams)).publicKey, remoteCommit.remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remoteCommit.remotePerCommitmentPoint) - val localPerCommitmentPoint = keyManager.deterministicCommitmentPoint(localParams, commitments.localCommit.index.toInt) - val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.deterministicRevocationPoint(localParams).publicKey, remoteCommit.remotePerCommitmentPoint) + val localPerCommitmentPoint = keyManager.commitmentPoint(keyPath(localParams), commitments.localCommit.index.toInt) + val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(keyPath(localParams)).publicKey, remoteCommit.remotePerCommitmentPoint) // we need to use a rather high fee for htlc-claim because we compete with the counterparty val feeratePerKwHtlc = Globals.feeratesPerKw.get.blocks_2 @@ -579,7 +579,7 @@ object Helpers { val preimage = preimages.find(r => sha256(r) == add.paymentHash).get val txinfo = Transactions.makeClaimHtlcSuccessTx(remoteCommitTx.tx, outputsAlreadyUsed, Satoshi(localParams.dustLimitSatoshis), localHtlcPubkey, remoteHtlcPubkey, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, add, feeratePerKwHtlc) outputsAlreadyUsed = outputsAlreadyUsed + txinfo.input.outPoint.index.toInt - val sig = keyManager.sign(txinfo, keyManager.deterministicHtlcPoint(localParams), remoteCommit.remotePerCommitmentPoint) + val sig = keyManager.sign(txinfo, keyManager.htlcPoint(keyPath(localParams)), remoteCommit.remotePerCommitmentPoint) Transactions.addSigs(txinfo, sig, preimage) }) @@ -589,7 +589,7 @@ object Helpers { case DirectedHtlc(IN, add: UpdateAddHtlc) => generateTx("claim-htlc-timeout")(Try { val txinfo = Transactions.makeClaimHtlcTimeoutTx(remoteCommitTx.tx, outputsAlreadyUsed, Satoshi(localParams.dustLimitSatoshis), localHtlcPubkey, remoteHtlcPubkey, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, add, feeratePerKwHtlc) outputsAlreadyUsed = outputsAlreadyUsed + txinfo.input.outPoint.index.toInt - val sig = keyManager.sign(txinfo, keyManager.deterministicHtlcPoint(localParams), remoteCommit.remotePerCommitmentPoint) + val sig = keyManager.sign(txinfo, keyManager.htlcPoint(keyPath(localParams)), remoteCommit.remotePerCommitmentPoint) Transactions.addSigs(txinfo, sig) }) }.toSeq.flatten @@ -612,7 +612,7 @@ object Helpers { * @return a list of transactions (one per HTLC that we can claim) */ def claimRemoteCommitMainOutput(keyManager: KeyManager, commitments: Commitments, remotePerCommitmentPoint: Point, tx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = { - val localPubkey = Generators.derivePubKey(keyManager.deterministicPaymentPoint(commitments.localParams).publicKey, remotePerCommitmentPoint) + val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(keyPath(commitments.localParams)).publicKey, remotePerCommitmentPoint) // no need to use a high fee rate for our main output (we are the only one who can spend it) val feeratePerKwMain = Globals.feeratesPerKw.get.blocks_6 @@ -620,7 +620,7 @@ object Helpers { val mainTx = generateTx("claim-p2wpkh-output")(Try { val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, Satoshi(commitments.localParams.dustLimitSatoshis), localPubkey, commitments.localParams.defaultFinalScriptPubKey, feeratePerKwMain) - val sig = keyManager.sign(claimMain, keyManager.deterministicPaymentPoint(commitments.localParams), remotePerCommitmentPoint) + val sig = keyManager.sign(claimMain, keyManager.paymentPoint(keyPath(commitments.localParams)), remotePerCommitmentPoint) Transactions.addSigs(claimMain, localPubkey, sig) }) @@ -647,7 +647,7 @@ object Helpers { require(tx.txIn.size == 1, "commitment tx should have 1 input") val obscuredTxNumber = Transactions.decodeTxNumber(tx.txIn(0).sequence, tx.lockTime) // this tx has been published by remote, so we need to invert local/remote params - val txnumber = Transactions.obscuredCommitTxNumber(obscuredTxNumber, !localParams.isFunder, remoteParams.paymentBasepoint, keyManager.deterministicPaymentPoint(localParams).publicKey) + val txnumber = Transactions.obscuredCommitTxNumber(obscuredTxNumber, !localParams.isFunder, remoteParams.paymentBasepoint, keyManager.paymentPoint(keyPath(localParams)).publicKey) require(txnumber <= 0xffffffffffffL, "txnumber must be lesser than 48 bits long") log.warning(s"a revoked commit has been published with txnumber=$txnumber") // now we know what commit number this tx is referring to, we can derive the commitment point from the shachain @@ -656,9 +656,9 @@ object Helpers { .map { remotePerCommitmentSecret => val remotePerCommitmentPoint = remotePerCommitmentSecret.toPoint val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) - val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.deterministicRevocationPoint(localParams).publicKey, remotePerCommitmentPoint) - val localPubkey = Generators.derivePubKey(keyManager.deterministicPaymentPoint(localParams).publicKey, remotePerCommitmentPoint) - val localHtlcPubkey = Generators.derivePubKey(keyManager.deterministicHtlcPoint(localParams).publicKey, remotePerCommitmentPoint) + val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(keyPath(localParams)).publicKey, remotePerCommitmentPoint) + val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(keyPath(localParams)).publicKey, remotePerCommitmentPoint) + val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(keyPath(localParams)).publicKey, remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint) // no need to use a high fee rate for our main output (we are the only one who can spend it) @@ -669,14 +669,14 @@ object Helpers { // first we will claim our main output right away val mainTx = generateTx("claim-p2wpkh-output")(Try { val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, Satoshi(localParams.dustLimitSatoshis), localPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwMain) - val sig = keyManager.sign(claimMain, keyManager.deterministicPaymentPoint(localParams), remotePerCommitmentPoint) + val sig = keyManager.sign(claimMain, keyManager.paymentPoint(keyPath(localParams)), remotePerCommitmentPoint) Transactions.addSigs(claimMain, localPubkey, sig) }) // then we punish them by stealing their main output val mainPenaltyTx = generateTx("main-penalty")(Try { val txinfo = Transactions.makeMainPenaltyTx(tx, Satoshi(localParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, feeratePerKwPenalty) - val sig = keyManager.sign(txinfo, keyManager.deterministicRevocationPoint(localParams), remotePerCommitmentSecret) + val sig = keyManager.sign(txinfo, keyManager.revocationPoint(keyPath(localParams)), remotePerCommitmentSecret) Transactions.addSigs(txinfo, sig) }) @@ -697,7 +697,7 @@ object Helpers { generateTx("htlc-penalty")(Try { val htlcPenalty = Transactions.makeHtlcPenaltyTx(tx, outputsAlreadyUsed, htlcRedeemScript, Satoshi(localParams.dustLimitSatoshis), localParams.defaultFinalScriptPubKey, feeratePerKwPenalty) outputsAlreadyUsed = outputsAlreadyUsed + htlcPenalty.input.outPoint.index.toInt - val sig = keyManager.sign(htlcPenalty, keyManager.deterministicRevocationPoint(localParams), remotePerCommitmentSecret) + val sig = keyManager.sign(htlcPenalty, keyManager.revocationPoint(keyPath(localParams)), remotePerCommitmentSecret) Transactions.addSigs(htlcPenalty, sig, remoteRevocationPubkey) }) }.toList.flatten @@ -738,21 +738,21 @@ object Helpers { val tx = revokedCommitPublished.commitTx val obscuredTxNumber = Transactions.decodeTxNumber(tx.txIn(0).sequence, tx.lockTime) // this tx has been published by remote, so we need to invert local/remote params - val txnumber = Transactions.obscuredCommitTxNumber(obscuredTxNumber, !localParams.isFunder, remoteParams.paymentBasepoint, keyManager.deterministicPaymentPoint(localParams).publicKey) + val txnumber = Transactions.obscuredCommitTxNumber(obscuredTxNumber, !localParams.isFunder, remoteParams.paymentBasepoint, keyManager.paymentPoint(keyPath(localParams)).publicKey) // now we know what commit number this tx is referring to, we can derive the commitment point from the shachain remotePerCommitmentSecrets.getHash(0xFFFFFFFFFFFFL - txnumber) .map(d => Scalar(d)) .flatMap { remotePerCommitmentSecret => val remotePerCommitmentPoint = remotePerCommitmentSecret.toPoint val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) - val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.deterministicRevocationPoint(localParams).publicKey, remotePerCommitmentPoint) + val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(keyPath(localParams)).publicKey, remotePerCommitmentPoint) // we need to use a high fee here for punishment txes because after a delay they can be spent by the counterparty val feeratePerKwPenalty = Globals.feeratesPerKw.get.block_1 generateTx("claim-htlc-delayed-penalty")(Try { val htlcDelayedPenalty = Transactions.makeClaimDelayedOutputPenaltyTx(htlcTx, Satoshi(localParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwPenalty) - val sig = keyManager.sign(htlcDelayedPenalty, keyManager.deterministicRevocationPoint(localParams), remotePerCommitmentSecret) + val sig = keyManager.sign(htlcDelayedPenalty, keyManager.revocationPoint(keyPath(localParams)), remotePerCommitmentSecret) val signedTx = Transactions.addSigs(htlcDelayedPenalty, sig) // we need to make sure that the tx is indeed valid Transaction.correctlySpends(signedTx.tx, Seq(htlcTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/KeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/KeyManager.scala index f1ec5c4ce1..34f73f6156 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/KeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/KeyManager.scala @@ -20,7 +20,6 @@ import fr.acinq.bitcoin.Crypto.{Point, PublicKey, Scalar} import fr.acinq.bitcoin.DeterministicWallet.ExtendedPublicKey import fr.acinq.bitcoin.{ByteVector32, Crypto, DeterministicWallet} import fr.acinq.eclair.ShortChannelId -import fr.acinq.eclair.channel.LocalParams import fr.acinq.eclair.transactions.Transactions.TransactionWithInputInfo import scodec.bits.ByteVector @@ -43,25 +42,6 @@ trait KeyManager { def commitmentPoint(channelKeyPath: DeterministicWallet.KeyPath, index: Long): Crypto.Point - /** - * DETERMINISTIC - * - */ - - def deterministicFundingPublicKey(localParams: LocalParams): ExtendedPublicKey - - def deterministicRevocationPoint(localParams: LocalParams): ExtendedPublicKey - - def deterministicPaymentPoint(localParams: LocalParams): ExtendedPublicKey - - def deterministicDelayedPaymentPoint(localParams: LocalParams): ExtendedPublicKey - - def deterministicHtlcPoint(localParams: LocalParams): ExtendedPublicKey - - def deterministicCommitmentSecret(localParams: LocalParams, index: Long): Crypto.Scalar - - def deterministicCommitmentPoint(localParams: LocalParams, index: Long): Crypto.Point - /** * * @param tx input transaction @@ -94,7 +74,4 @@ trait KeyManager { def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remoteSecret: Scalar): ByteVector def signChannelAnnouncement(channelKeyPath: DeterministicWallet.KeyPath, chainHash: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: ByteVector): (ByteVector, ByteVector) - - def deterministicSignChannelAnnouncement(localParams: LocalParams, chainHash: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: ByteVector): (ByteVector, ByteVector) - } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala index 75b237a441..038f3b00d2 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala @@ -21,7 +21,6 @@ import fr.acinq.bitcoin.Crypto.{Point, PublicKey, Scalar} import fr.acinq.bitcoin.DeterministicWallet.{derivePrivateKey, _} import fr.acinq.bitcoin.{Block, ByteVector32, Crypto, DeterministicWallet} import fr.acinq.eclair.ShortChannelId -import fr.acinq.eclair.channel.LocalParams import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.transactions.Transactions.TransactionWithInputInfo @@ -112,73 +111,6 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana override def commitmentPoint(channelKeyPath: DeterministicWallet.KeyPath, index: Long) = Generators.perCommitPoint(shaSeed(channelKeyPath), index) - /** - * DETERMINISTIC - */ - - override def deterministicFundingPublicKey(localParams: LocalParams): ExtendedPublicKey = { - val keyPath = localParams.channelKeyPath match { - case Left(kp) => kp - case Right(keyPathFundee) => keyPathFundee.publicKeyPath - } - - fundingPublicKey(keyPath) - } - - override def deterministicRevocationPoint(localParams: LocalParams): ExtendedPublicKey = { - val keyPath = localParams.channelKeyPath match { - case Left(kp) => kp - case Right(keyPathFundee) => keyPathFundee.pointsKeyPath - } - - revocationPoint(keyPath) - } - - override def deterministicPaymentPoint(localParams: LocalParams): ExtendedPublicKey = { - val keyPath = localParams.channelKeyPath match { - case Left(kp) => kp - case Right(keyPathFundee) => keyPathFundee.pointsKeyPath - } - - paymentPoint(keyPath) - } - - override def deterministicDelayedPaymentPoint(localParams: LocalParams): ExtendedPublicKey = { - val keyPath = localParams.channelKeyPath match { - case Left(kp) => kp - case Right(keyPathFundee) => keyPathFundee.pointsKeyPath - } - - delayedPaymentPoint(keyPath) - } - - override def deterministicHtlcPoint(localParams: LocalParams): ExtendedPublicKey = { - val keyPath = localParams.channelKeyPath match { - case Left(kp) => kp - case Right(keyPathFundee) => keyPathFundee.pointsKeyPath - } - - htlcPoint(keyPath) - } - - override def deterministicCommitmentSecret(localParams: LocalParams, index: Long): Scalar = { - val keyPath = localParams.channelKeyPath match { - case Left(kp) => kp - case Right(keyPathFundee) => keyPathFundee.pointsKeyPath - } - - commitmentSecret(keyPath, index) - } - - override def deterministicCommitmentPoint(localParams: LocalParams, index: Long): Point = { - val keyPath = localParams.channelKeyPath match { - case Left(kp) => kp - case Right(keyPathFundee) => keyPathFundee.pointsKeyPath - } - - commitmentPoint(keyPath, index) - } - /** * * @param tx input transaction @@ -231,14 +163,4 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana val bitcoinSig = Crypto.encodeSignature(Crypto.sign(witness, fundingPrivateKey(channelKeyPath).privateKey)) :+ 1.toByte (nodeSig, bitcoinSig) } - - override def deterministicSignChannelAnnouncement(localParams: LocalParams, chainHash: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: ByteVector): (ByteVector, ByteVector) = { - val keyPath = localParams.channelKeyPath match { - case Left(kp) => kp - case Right(keyPathFundee) => keyPathFundee.publicKeyPath - } - - signChannelAnnouncement(keyPath, chainHash, shortChannelId, remoteNodeId, remoteFundingKey, features) - } - } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 5ffa8e4005..af451339ee 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -27,7 +27,7 @@ import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.UInt64.Conversions._ import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.blockchain.fee.FeeratesPerKw -import fr.acinq.eclair.channel.Channel.{BroadcastChannelUpdate, PeriodicRefresh, Reconnected, RevocationTimeout} +import fr.acinq.eclair.channel.Channel._ import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.io.Peer @@ -2057,7 +2057,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42)) val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures] import initialState.commitments.{localParams, remoteParams} - val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.keyManager.deterministicFundingPublicKey(localParams).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) + val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.keyManager.fundingPublicKey(fundingKeyPath(localParams)).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) // actual test starts here bob2alice.forward(alice) awaitCond({ @@ -2076,7 +2076,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 42, 10)) val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures] import initialState.commitments.{localParams, remoteParams} - val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.keyManager.deterministicFundingPublicKey(localParams).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) + val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.keyManager.fundingPublicKey(fundingKeyPath(localParams)).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) bob2alice.forward(alice) awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement === Some(channelAnn)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala index 29b6a5f9fe..d2eeed9b02 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala @@ -18,7 +18,7 @@ package fr.acinq.eclair.channel.states.e import akka.actor.Status import java.util.UUID - +import fr.acinq.eclair.channel.Channel._ import akka.testkit.TestProbe import fr.acinq.bitcoin.Crypto.Scalar import fr.acinq.bitcoin.{ByteVector32, ScriptFlags, Transaction} @@ -80,8 +80,8 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val bobCommitments = bob.stateData.asInstanceOf[HasCommitments].commitments val aliceCommitments = alice.stateData.asInstanceOf[HasCommitments].commitments - val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.deterministicCommitmentPoint(bobCommitments.localParams, bobCommitments.localCommit.index) - val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.deterministicCommitmentPoint(aliceCommitments.localParams, aliceCommitments.localCommit.index) + val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.commitmentPoint(keyPath(bobCommitments.localParams), bobCommitments.localCommit.index) + val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.commitmentPoint(keyPath(aliceCommitments.localParams), aliceCommitments.localCommit.index) // a didn't receive any update or sig @@ -164,8 +164,8 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val bobCommitments = bob.stateData.asInstanceOf[HasCommitments].commitments val aliceCommitments = alice.stateData.asInstanceOf[HasCommitments].commitments - val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.deterministicCommitmentPoint(bobCommitments.localParams, bobCommitments.localCommit.index) - val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.deterministicCommitmentPoint(aliceCommitments.localParams, aliceCommitments.localCommit.index) + val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.commitmentPoint(keyPath(bobCommitments.localParams), bobCommitments.localCommit.index) + val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.commitmentPoint(keyPath(aliceCommitments.localParams), aliceCommitments.localCommit.index) // a didn't receive the sig val ab_reestablish = alice2bob.expectMsg(ChannelReestablish(ab_add_0.channelId, 1, 0, Some(Scalar(ByteVector32.Zeroes)), Some(aliceCurrentPerCommitmentPoint))) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala index f227e7f181..93c5d7864e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala @@ -20,6 +20,7 @@ import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.DeterministicWallet.KeyPath import fr.acinq.bitcoin.{Block, Script} import fr.acinq.eclair.TestConstants +import fr.acinq.eclair.channel.Channel.{fundingKeyPath, keyPath} import fr.acinq.eclair.channel.LocalParams import fr.acinq.eclair.transactions.Scripts import org.scalatest.FunSuite @@ -163,24 +164,24 @@ class LocalKeyManagerSpec extends FunSuite { val funderParams = TestConstants.Alice.channelParams val funderKeyPath = funderParams.channelKeyPath.left.get - assert(keyManager.deterministicFundingPublicKey(funderParams).publicKey == keyManager.fundingPublicKey(funderKeyPath).publicKey) - assert(keyManager.deterministicRevocationPoint(funderParams) == keyManager.revocationPoint(funderKeyPath)) - assert(keyManager.deterministicPaymentPoint(funderParams).publicKey == keyManager.paymentPoint(funderKeyPath).publicKey) - assert(keyManager.deterministicDelayedPaymentPoint(funderParams).publicKey == keyManager.delayedPaymentPoint(funderKeyPath).publicKey) - assert(keyManager.deterministicHtlcPoint(funderParams).publicKey == keyManager.htlcPoint(funderKeyPath).publicKey) - assert(keyManager.deterministicCommitmentPoint(funderParams, 0) == keyManager.commitmentPoint(funderKeyPath, 0)) + assert(keyManager.fundingPublicKey(fundingKeyPath(funderParams)).publicKey == keyManager.fundingPublicKey(funderKeyPath).publicKey) + assert(keyManager.revocationPoint(keyPath(funderParams)) == keyManager.revocationPoint(funderKeyPath)) + assert(keyManager.paymentPoint(keyPath(funderParams)).publicKey == keyManager.paymentPoint(funderKeyPath).publicKey) + assert(keyManager.delayedPaymentPoint(keyPath(funderParams)).publicKey == keyManager.delayedPaymentPoint(funderKeyPath).publicKey) + assert(keyManager.htlcPoint(keyPath(funderParams)).publicKey == keyManager.htlcPoint(funderKeyPath).publicKey) + assert(keyManager.commitmentPoint(keyPath(funderParams), 0) == keyManager.commitmentPoint(funderKeyPath, 0)) // FUNDEE val fundeeParams = TestConstants.Bob.channelParams - val fundeeFundingKeyPath = fundeeParams.channelKeyPath.right.get.publicKeyPath + val fundeeFundingKeyPath = fundeeParams.channelKeyPath.right.get.fundingKeyPath val fundeeKeyPath = fundeeParams.channelKeyPath.right.get.pointsKeyPath - assert(keyManager.deterministicFundingPublicKey(fundeeParams).publicKey == keyManager.fundingPublicKey(fundeeFundingKeyPath).publicKey) - assert(keyManager.deterministicRevocationPoint(fundeeParams) == keyManager.revocationPoint(fundeeKeyPath)) - assert(keyManager.deterministicPaymentPoint(fundeeParams).publicKey == keyManager.paymentPoint(fundeeKeyPath).publicKey) - assert(keyManager.deterministicDelayedPaymentPoint(fundeeParams).publicKey == keyManager.delayedPaymentPoint(fundeeKeyPath).publicKey) - assert(keyManager.deterministicHtlcPoint(fundeeParams).publicKey == keyManager.htlcPoint(fundeeKeyPath).publicKey) - assert(keyManager.deterministicCommitmentPoint(fundeeParams, 0) == keyManager.commitmentPoint(fundeeKeyPath, 0)) + assert(keyManager.fundingPublicKey(fundingKeyPath(fundeeParams)).publicKey == keyManager.fundingPublicKey(fundeeFundingKeyPath).publicKey) + assert(keyManager.revocationPoint(keyPath(fundeeParams)) == keyManager.revocationPoint(fundeeKeyPath)) + assert(keyManager.paymentPoint(keyPath(fundeeParams)).publicKey == keyManager.paymentPoint(fundeeKeyPath).publicKey) + assert(keyManager.delayedPaymentPoint(keyPath(fundeeParams)).publicKey == keyManager.delayedPaymentPoint(fundeeKeyPath).publicKey) + assert(keyManager.htlcPoint(keyPath(fundeeParams)).publicKey == keyManager.htlcPoint(fundeeKeyPath).publicKey) + assert(keyManager.commitmentPoint(keyPath(fundeeParams), 0) == keyManager.commitmentPoint(fundeeKeyPath, 0)) } } \ No newline at end of file diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala index 1709e5fb5c..671b4a52c2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala @@ -20,6 +20,7 @@ import java.util.UUID import fr.acinq.bitcoin.Crypto.{PrivateKey, Scalar} import fr.acinq.bitcoin.{Block, ByteVector32, Crypto, DeterministicWallet, MilliSatoshi, Satoshi, Transaction} +import fr.acinq.eclair.channel.Channel.fundingKeyPath import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.{LocalKeyManager, ShaChain, Sphinx} @@ -100,7 +101,7 @@ object ChannelStateSpec { val fundingTx = Transaction.read("0200000001adbb20ea41a8423ea937e76e8151636bf6093b70eaff942930d20576600521fd000000006b48304502210090587b6201e166ad6af0227d3036a9454223d49a1f11839c1a362184340ef0240220577f7cd5cca78719405cbf1de7414ac027f0239ef6e214c90fcaab0454d84b3b012103535b32d5eb0a6ed0982a0479bbadc9868d9836f6ba94dd5a63be16d875069184ffffffff028096980000000000220020c015c4a6be010e21657068fc2e6a9d02b27ebe4d490a25846f7237f104d1a3cd20256d29010000001600143ca33c2e4446f4a305f23c80df8ad1afdcf652f900000000") val fundingAmount = fundingTx.txOut(0).amount - val commitmentInput = Funding.makeFundingInputInfo(fundingTx.hash, 0, fundingAmount, keyManager.deterministicFundingPublicKey(localParams).publicKey, remoteParams.fundingPubKey) + val commitmentInput = Funding.makeFundingInputInfo(fundingTx.hash, 0, fundingAmount, keyManager.fundingPublicKey(fundingKeyPath(localParams)).publicKey, remoteParams.fundingPubKey) val localCommit = LocalCommit(0, CommitmentSpec(htlcs.toSet, 1500, 50000000, 70000000), PublishableTxs(CommitTx(commitmentInput, Transaction(2, Nil, Nil, 0)), Nil)) val remoteCommit = RemoteCommit(0, CommitmentSpec(htlcs.map(htlc => htlc.copy(direction = htlc.direction.opposite)).toSet, 1500, 50000, 700000), ByteVector32(hex"0303030303030303030303030303030303030303030303030303030303030303"), Scalar(ByteVector.fill(32)(4)).toPoint) From 847eb06f1f836dec27e473792c0ff15a1415c407 Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 14 Jun 2019 14:57:17 +0200 Subject: [PATCH 27/43] Upgrade localParams scodec to handle previous version fundee scenario, Remove "isFunder" from LocalParams, Add test for backward compatibility --- .../fr/acinq/eclair/channel/Channel.scala | 7 +- .../acinq/eclair/channel/ChannelTypes.scala | 3 +- .../fr/acinq/eclair/wire/ChannelCodecs.scala | 79 ++++++++++++++++--- .../scala/fr/acinq/eclair/TestConstants.scala | 2 - .../fr/acinq/eclair/db/ChannelStateSpec.scala | 1 - .../acinq/eclair/wire/ChannelCodecsSpec.scala | 21 ++++- 6 files changed, 90 insertions(+), 23 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index ba435b9615..8603aa7ca4 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -93,7 +93,7 @@ object Channel { def makeFunderChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, fundingSatoshis: Long, fundingInput: TxIn): LocalParams = { val channelKeyPath = LocalKeyManager.makeChannelKeyPathFunder(fundingInput.outPoint.hash) - makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder = true, fundingSatoshis, Left(channelKeyPath)) + makeChannelParams(nodeParams, defaultFinalScriptPubKey, fundingSatoshis, Left(channelKeyPath)) } def makeFundeeChannelParams(nodeParams: NodeParams, open: OpenChannel, defaultFinalScriptPubKey: ByteVector, fundingSatoshis: Long): LocalParams = { @@ -104,10 +104,10 @@ object Channel { val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey, open.fundingPubkey))) val channelKeyPath = LocalKeyManager.makeChannelKeyPathFundee(fundingPubkeyScript) val channelKeyPaths = KeyPathFundee(publicKeyPath, channelKeyPath) - makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder = false, fundingSatoshis, Right(channelKeyPaths)) + makeChannelParams(nodeParams, defaultFinalScriptPubKey, fundingSatoshis, Right(channelKeyPaths)) } - def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, channelKeyPath: Either[DeterministicWallet.KeyPath, KeyPathFundee]): LocalParams = { + def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, fundingSatoshis: Long, channelKeyPath: Either[DeterministicWallet.KeyPath, KeyPathFundee]): LocalParams = { LocalParams( nodeParams.nodeId, channelKeyPath, @@ -118,7 +118,6 @@ object Channel { toSelfDelay = nodeParams.toRemoteDelayBlocks, // we choose their delay maxAcceptedHtlcs = nodeParams.maxAcceptedHtlcs, defaultFinalScriptPubKey = defaultFinalScriptPubKey, - isFunder = isFunder, globalFeatures = nodeParams.globalFeatures, localFeatures = nodeParams.localFeatures) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala index 08600ef245..c574fc476b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala @@ -202,12 +202,11 @@ final case class LocalParams(nodeId: PublicKey, htlcMinimumMsat: Long, toSelfDelay: Int, maxAcceptedHtlcs: Int, - isFunder: Boolean, defaultFinalScriptPubKey: ByteVector, globalFeatures: ByteVector, localFeatures: ByteVector) { - require(isFunder && channelKeyPath.isLeft || !isFunder && channelKeyPath.isRight, s"Wrong keyPath derivation for isFunder=$isFunder") + def isFunder = channelKeyPath.isLeft } case class KeyPathFundee(fundingKeyPath: KeyPath, pointsKeyPath: KeyPath) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala index 0271a2209b..6cd8fd8dda 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala @@ -19,8 +19,10 @@ package fr.acinq.eclair.wire import java.util.UUID import akka.actor.ActorRef +import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, KeyPath} import fr.acinq.bitcoin.{ByteVector32, OutPoint, Transaction, TxOut} +import fr.acinq.eclair.UInt64 import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.payment.{Local, Origin, Relayed} @@ -28,9 +30,10 @@ import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire.LightningMessageCodecs._ import grizzled.slf4j.Logging -import scodec.bits.BitVector +import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ import scodec.{Attempt, Codec} +import shapeless.{HList, HNil} import scala.compat.Platform import scala.concurrent.duration._ @@ -42,12 +45,12 @@ import scala.concurrent.duration._ object ChannelCodecs extends Logging { val keyPathCodec: Codec[KeyPath] = ("path" | listOfN(uint16, uint32)).xmap[KeyPath](l => new KeyPath(l), keyPath => keyPath.path.toList).as[KeyPath] - val keyPathFundee: Codec[KeyPathFundee] = ( - ("publicKeyPath" | keyPathCodec) :: + val keyPathFundeeCodec: Codec[KeyPathFundee] = ( + ("fundingKeyPath" | keyPathCodec) :: ("pointsKeyPath" | keyPathCodec) ).as[KeyPathFundee] - val channelKeyPathCodec = either(bool, keyPathCodec, keyPathFundee) + val channelKeyPathCodec = either(bool, keyPathCodec, keyPathFundeeCodec) val extendedPrivateKeyCodec: Codec[ExtendedPrivateKey] = ( ("secretkeybytes" | bytes32) :: @@ -56,15 +59,10 @@ object ChannelCodecs extends Logging { ("path" | keyPathCodec) :: ("parent" | int64)).as[ExtendedPrivateKey] - def localParamsCodec(version: CommitmentVersion): Codec[LocalParams] = ( + import shapeless._ + val localParamsCodecV0: Codec[LocalParams] = ( ("nodeId" | publicKey) :: - (version match { - case CommitmentV0 => ("channelPath" | keyPathCodec.xmap[Either[KeyPath, KeyPathFundee]](Left(_), { - case Left(kp) => kp - case Right(_) => throw new IllegalArgumentException(s"Found unexpected value for version=$version") - })) - case CommitmentV1 => ("channelPath" | channelKeyPathCodec) - }) :: + ("channelPath" | keyPathCodec) :: ("dustLimitSatoshis" | uint64) :: ("maxHtlcValueInFlightMsat" | uint64ex) :: ("channelReserveSatoshis" | uint64) :: @@ -74,8 +72,65 @@ object ChannelCodecs extends Logging { ("isFunder" | bool) :: ("defaultFinalScriptPubKey" | varsizebinarydata) :: ("globalFeatures" | varsizebinarydata) :: + ("localFeatures" | varsizebinarydata)).xmap({ + case nodeId :: + keyPath :: + dustLimitSatoshis :: + maxHtlcValueInFlightMsat :: + channelReserveSatoshis :: + htlcMinimumMsat :: + toSelfDelay :: + maxAcceptedHtlcs :: + isFunder :: + defaultFinalScriptPubKey :: + globalFeatures :: localFeatures :: HNil => + + LocalParams( + nodeId = nodeId, + // old versions of "LocalParams" use a single keypath + channelKeyPath = if(isFunder) Left(keyPath) else Right(KeyPathFundee(keyPath, keyPath)), + dustLimitSatoshis = dustLimitSatoshis, + maxHtlcValueInFlightMsat = maxHtlcValueInFlightMsat, + channelReserveSatoshis = channelReserveSatoshis, + htlcMinimumMsat = htlcMinimumMsat, + toSelfDelay = toSelfDelay, + maxAcceptedHtlcs = maxAcceptedHtlcs, + defaultFinalScriptPubKey = defaultFinalScriptPubKey, + globalFeatures = globalFeatures, + localFeatures = localFeatures) + },{ localParams => + + localParams.nodeId :: + localParams.channelKeyPath.left.get :: + localParams.dustLimitSatoshis :: + localParams.maxHtlcValueInFlightMsat :: + localParams.channelReserveSatoshis :: + localParams.htlcMinimumMsat :: + localParams.toSelfDelay :: + localParams.maxAcceptedHtlcs :: + localParams.isFunder :: + localParams.defaultFinalScriptPubKey :: + localParams.globalFeatures :: localParams.localFeatures :: HNil + }) + + val localParamsCodecV1: Codec[LocalParams] = ( + ("nodeId" | publicKey) :: + ("channelPath" | channelKeyPathCodec) :: + ("dustLimitSatoshis" | uint64) :: + ("maxHtlcValueInFlightMsat" | uint64ex) :: + ("channelReserveSatoshis" | uint64) :: + ("htlcMinimumMsat" | uint64) :: + ("toSelfDelay" | uint16) :: + ("maxAcceptedHtlcs" | uint16) :: + ("defaultFinalScriptPubKey" | varsizebinarydata) :: + ("globalFeatures" | varsizebinarydata) :: ("localFeatures" | varsizebinarydata)).as[LocalParams] + def localParamsCodec(version: CommitmentVersion): Codec[LocalParams] = version match { + case CommitmentV1 => localParamsCodecV1 + case CommitmentV0 => localParamsCodecV0 + } + val remoteParamsCodec: Codec[RemoteParams] = ( ("nodeId" | publicKey) :: ("dustLimitSatoshis" | uint64) :: diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index eba92d5a21..765ca90dce 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -105,7 +105,6 @@ object TestConstants { def channelParams = Channel.makeChannelParams( nodeParams = nodeParams, defaultFinalScriptPubKey = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32, compressed = true).publicKey)), - isFunder = true, fundingSatoshis, Left(KeyPath(Seq(1, 2, 3, 4L))) ).copy( @@ -171,7 +170,6 @@ object TestConstants { def channelParams = Channel.makeChannelParams( nodeParams = nodeParams, defaultFinalScriptPubKey = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32, compressed = true).publicKey)), - isFunder = false, fundingSatoshis, Right(KeyPathFundee(KeyPath(Seq(1, 2, 3, 4L)), KeyPath(Seq(1, 2, 3, 4L)))) ).copy( diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala index 671b4a52c2..6030ed8c9d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala @@ -63,7 +63,6 @@ object ChannelStateSpec { toSelfDelay = 144, maxAcceptedHtlcs = 50, defaultFinalScriptPubKey = ByteVector.empty, - isFunder = true, globalFeatures = hex"dead", localFeatures = hex"beef") diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala index fca6c7391f..d882f3e9c1 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala @@ -66,7 +66,6 @@ class ChannelCodecsSpec extends FunSuite { toSelfDelay = Random.nextInt(Short.MaxValue), maxAcceptedHtlcs = Random.nextInt(Short.MaxValue), defaultFinalScriptPubKey = randomBytes(10 + Random.nextInt(200)), - isFunder = false, globalFeatures = randomBytes(256), localFeatures = randomBytes(256)) @@ -84,7 +83,6 @@ class ChannelCodecsSpec extends FunSuite { toSelfDelay = Random.nextInt(Short.MaxValue), maxAcceptedHtlcs = Random.nextInt(Short.MaxValue), defaultFinalScriptPubKey = randomBytes(10 + Random.nextInt(200)), - isFunder = true, globalFeatures = randomBytes(256), localFeatures = randomBytes(256)) @@ -211,4 +209,23 @@ class ChannelCodecsSpec extends FunSuite { assert(data_new === data_new2) } + test("backwards compatibility DATA_NORMAL for fundee scenario") { + // this is a DATA_NORMAL encoded with the previous version of the codec (at commit 849b6bd22be6a7550c7e915e3824201ba530743d) + val bin_old = hex"00000302A9D7232139779A93C72EDA7D9519957F7BC6C802D8618CA5A75C0B87EBFBC76F0004AA5FAE018E7726D9074CAB0245C7FB470000000000000222000000012A05F20000000000000186A0000000000000000102D0001E000B000A3A1B8E03A82FE05383331ED4E197CC58AB1710F680000000C101BC107808C08DE5B23D000090742B42EE4B837857EAB0A6B9043694B822007B1F8000000000000111000000009502F900000000000000C35000000000000000008168000F015A8ACA042BCAB0F9CD055A0FE603764A6A416D3329A70818FC239C243F3E619481596A18360D46F3CEEF9AE150F2C0569561CFDC2A758800C1D209FD4D8B89B11001DB791A4573BD56E416F0BD4EA61D670C8F3CB417414B261117806D3C28857421819FAD914FF84A7FDAF242B3D6FBBA0B87B9283A7B132BDFE0FEDA5D3D773B73E1814CAEC057CB90CA0E5B83011DB0951BC2235C942D4BA32881FE785DAC4684692900000000C10080000000000000000000000057E40000000000000000000000012A05F2000012111CACBC8C568791785028DDFB2FCA4E9CA05245563BABF3757CB8080C0122BA800000000015C04B4C00000000001100104FDEC3872BA5D67B7380F99ADA446FAB7248F007A575338A1CB14DA5B807E20E0023A910811F9D8A833659E851E19BA22B9B6CCE3EEF65CD999424658E6852B6921B2DE36990815A8ACA042BCAB0F9CD055A0FE603764A6A416D3329A70818FC239C243F3E6194A957009801000000000080911CACBC8C568791785028DDFB2FCA4E9CA05245563BABF3757CB8080C0122BA80000000000E3359C0009E0BCC00000000000B000A17856E8874244BFF977A80228A098DF977384EEE02002418228110804A81F0F66B3D1E90E7410F900E3B767690E64CAB3C4312BB00EAB69DF32EEFD1011016A5F92AFD49F8E92AE5B1533A453AE4F0495EBA595416DF47F5A25A327E4CBE00A4182281108057167EFC9876DD5ACB23966777EAE4909345FB0B385FEC48A92ABCAD720594A6011035DED61D19D80AEE2405DA6A1AFC449D5F92B05F34A861EBDB8C42AACB38C7C480A3A910811F9D8A833659E851E19BA22B9B6CCE3EEF65CD999424658E6852B6921B2DE36990815A8ACA042BCAB0F9CD055A0FE603764A6A416D3329A70818FC239C243F3E6194A95762CFE410000000000000000000000000000057E4000000012A05F20000000000000000004A0C0E9BB8C3F6CCABC0974A7EFF83215DBF7F95C03E4F4294E8BC3BA119037101DB4160554EC9F3F65EB6747990098A3B6E31037823E935FDA668BBB7BDBDF511000000000000000000000000000000000000000000000000000000000000408D6E453F3F302236BF1543948883FDAD07BE54339851A7EF79081322BA11C4118009088E565E462B43C8BC28146EFD97E5274E502922AB1DD5F9BABE5C040600915D40000000000AE025A6000000000008800827EF61C395D2EB3DB9C07CCD6D2237D5B9247803D2BA99C50E58A6D2DC03F1070011D488408FCEC5419B2CF428F0CDD115CDB6671F77B2E6CCCA1232C734295B490D96F1B4C840AD45650215E5587CE682AD07F301BB253520B69994D3840C7E11CE121F9F30CA54AB800004472B2F2315A1E45E140A377ECBF293A7281491558EEAFCDD5F2E02030048AEA0011CC00000200019D45EF1751408B9C2BA601951D8445B9BA0A205FCFE911C73CAC093080FE315B0451A5C6DA9B9C0280CEEE6BF34392FB2F9AE7450CB38D5851BAEFA6FB1AB68BA477A4F0318E6BBEF8BA89828DCDC585CC50D6EB848D1D3F29E944AE854272E5ACC602FFCA39A291F1CD1CA9D9BC0EB1D7EB087BE6B15BBD5C4081602C513399836201BEB92FC7912229F53FB7B671052E4EC9654D02CA41D6D86A094F1A83B0BD0FB34400443CDF9D88DA54247B9B2F0E9B107F12C4EC040EE0B92BC2C31761EE735ADBC0E763B21B4DA08870533F2C0058404FD248EA3306CE4F6AB3B6ABA320C077D223A1B165527612C2F97400DE578842A112E2220ED4C5CF299BD23ECB800003113723088D05ACE557893021F5ADDF9461A79D2F19950FE3D95B9E78C4488780047300000080000154EB91909CBBCD49E3976D3ECA8CCABFBDE364016C30C652D3AE05C3F5FDE3B781BC107808C08DE5B23D000090742B42EE4B837857EAB0A6B9043694B822007B1F811F9D8A833659E851E19BA22B9B6CCE3EEF65CD999424658E6852B6921B2DE369815A8ACA042BCAB0F9CD055A0FE603764A6A416D3329A70818FC239C243F3E6194D1F27730DEB0C49576CDDDBC06C61139AEF86B7AAEAC797EE3619F3DC74690DF01588F42CDF60665152590AC94EC01F3C9E2248C7CE0E8BF12CFBF4B7BAF1B8103113723088D05ACE557893021F5ADDF9461A79D2F19950FE3D95B9E78C4488780047300000080002E81CB30808100480000000000000000800001F400000032000000012A05F2000" + // currently version=0 and discriminator type=3 + assert(bin_old.startsWith(hex"000003")) + // let's decode the old data (this will use the old codec that provides default values for new fields) + val data_new = stateDataCodec.decode(bin_old.toBitVector).require.value + assert(data_new.asInstanceOf[DATA_NORMAL].commitments.localParams.isFunder == false) + // and re-encode it with the new codec + val bin_new = ByteVector(stateDataCodec.encode(data_new).require.toByteVector.toArray) + // data should now be encoded under the new format, with version=1 and type=3 + assert(bin_new.startsWith(hex"010003")) + // now let's decode it again + val data_new2 = stateDataCodec.decode(bin_new.toBitVector).require.value + // data should match perfectly + assert(data_new === data_new2) + } + + } From debd6a1e26f29d1bdd657834a0f94a72f327b5af Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 14 Jun 2019 16:57:14 +0200 Subject: [PATCH 28/43] Rollback fundingTx if there is an error when waiting for the signature from the wallet --- .../scala/fr/acinq/eclair/channel/Channel.scala | 11 ++++++++--- .../fr/acinq/eclair/channel/ChannelTypes.scala | 2 +- .../fr/acinq/eclair/crypto/LocalKeyManager.scala | 3 ++- .../WaitForFundingSignedInternalStateSpec.scala | 16 ++++++++++++++++ 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 8603aa7ca4..fade03cb6e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -384,7 +384,6 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId goto(WAIT_FOR_ACCEPT_CHANNEL) using DATA_WAIT_FOR_ACCEPT_CHANNEL(nextStateData, open, funding) sending open - // TODO release outputs that were locked in the funding case Event(Status.Failure(t), d: DATA_WAIT_FOR_FUNDING_INTERNAL_CREATED) => log.error(t, s"wallet returned error: ") replyToUser(Left(LocalError(t))) @@ -442,7 +441,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId // add our signature to the funding transaction wallet.signTransactionComplete(finalFundingTx).map(SignFundingTxResponse(_, funding.fundingTxOutputIndex, funding.fee)).pipeTo(self) - goto(WAIT_FOR_FUNDING_INTERNAL_SIGNED) using DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, accept.firstPerCommitmentPoint, open) + goto(WAIT_FOR_FUNDING_INTERNAL_SIGNED) using DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, accept.firstPerCommitmentPoint, open, finalFundingTx) } case Event(CMD_CLOSE(_) | CMD_FORCECLOSE, d: DATA_WAIT_FOR_ACCEPT_CHANNEL) => @@ -467,7 +466,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId }) when(WAIT_FOR_FUNDING_INTERNAL_SIGNED)(handleExceptions { - case Event(SignFundingTxResponse(fundingTx, fundingTxOutputIndex, fundingTxFee), DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, remoteFirstPerCommitmentPoint, open)) => + case Event(SignFundingTxResponse(fundingTx, fundingTxOutputIndex, fundingTxFee), DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, remoteFirstPerCommitmentPoint, open, fundingTxResponse)) => // let's create the first commitment tx that spends the yet uncommitted funding tx val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.maxFeerateMismatch) require(fundingTx.txOut(fundingTxOutputIndex).publicKeyScript == localCommitTx.input.txOut.publicKeyScript, s"pubkey script mismatch!") @@ -1461,6 +1460,12 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId log.info("shutting down") stop(FSM.Normal) + // this may happen if connection is lost, or remote sends an error while we were waiting for the funding tx to be created by our wallet + // in that case we rollback the tx + case Event(SignFundingTxResponse(fundingTx, _, _), _) => + wallet.rollback(fundingTx) + stay + case Event(MakeFundingTxResponse(fundingTx, _, _), _) => // this may happen if connection is lost, or remote sends an error while we were waiting for the funding tx to be created by our wallet // in that case we rollback the tx diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala index c574fc476b..0d84d4af51 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala @@ -155,7 +155,7 @@ case class RevokedCommitPublished(commitTx: Transaction, claimMainOutputTx: Opti final case class DATA_WAIT_FOR_OPEN_CHANNEL(initFundee: INPUT_INIT_FUNDEE) extends Data final case class DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder: INPUT_INIT_FUNDER_WITH_PARAMS, lastSent: OpenChannel, unsignedFundingTx: MakeFundingTxResponse) extends Data final case class DATA_WAIT_FOR_FUNDING_INTERNAL_CREATED(temporaryChannelId: ByteVector32, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, fundingTxFeeratePerKw: Long, remote: ActorRef, remoteInit: Init, channelFlags: Byte) extends Data -final case class DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED(temporaryChannelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, remoteFirstPerCommitmentPoint: Point, lastSent: OpenChannel) extends Data +final case class DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED(temporaryChannelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, remoteFirstPerCommitmentPoint: Point, lastSent: OpenChannel, unsignedFundingTx: Transaction) extends Data final case class DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, remoteFirstPerCommitmentPoint: Point, channelFlags: Byte, lastSent: AcceptChannel) extends Data final case class DATA_WAIT_FOR_FUNDING_SIGNED(channelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingTx: Transaction, fundingTxFee: Satoshi, localSpec: CommitmentSpec, localCommitTx: CommitTx, remoteCommit: RemoteCommit, channelFlags: Byte, lastSent: FundingCreated) extends Data final case class DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments: Commitments, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala index 038f3b00d2..606d3dd5fd 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala @@ -41,7 +41,7 @@ object LocalKeyManager { case Block.LivenetGenesisBlock.hash => DeterministicWallet.hardened(47) :: DeterministicWallet.hardened(0) :: Nil } - // split the SHA into 8 groups of 4 bytes and convert to uint32 + // split the SHA(input) into 8 groups of 4 bytes and convert to uint32 def fourByteGroupsFromSha(input: ByteVector): List[Long] = Crypto.sha256(input).toArray.grouped(4).map(ByteVector(_).toLong(signed = false)).toList def makeChannelKeyPathFunder(entropy: ByteVector) = KeyPath(fourByteGroupsFromSha(entropy) :+ 0L) @@ -95,6 +95,7 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana private def shaSeed(channelKeyPath: DeterministicWallet.KeyPath) = Crypto.sha256(privateKeys.get(internalKeyPath(channelKeyPath, 5)).privateKey.toBin) + // used only in test def shaSeedPub(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, 5)) override def fundingPublicKey(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, 0)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala index b430511b70..1e1982deeb 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala @@ -20,6 +20,7 @@ import scala.concurrent.duration._ import akka.testkit.{TestFSMRef, TestProbe} import fr.acinq.bitcoin.{Block, ByteVector32, Script} import fr.acinq.eclair.TestConstants.{Alice, Bob} +import fr.acinq.eclair.blockchain.TestWallet import fr.acinq.eclair.{TestConstants, TestkitBaseClass} import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.states.StateTestsHelperMethods @@ -49,13 +50,28 @@ class WaitForFundingSignedInternalStateSpec extends TestkitBaseClass with StateT test("recv Error") { f => import f._ + + val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED].unsignedFundingTx + assert(alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.isEmpty) + alice ! Error(ByteVector32.Zeroes, "oops") + awaitCond(alice.stateName == CLOSED) + val rolledBackTx = alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.head + assert(rolledBackTx.txOut == fundingTx.txOut) + assert(rolledBackTx.txIn.map(_.outPoint) == fundingTx.txIn.map(_.outPoint)) } test("recv CMD_CLOSE") { f => import f._ + val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED].unsignedFundingTx + assert(alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.isEmpty) + alice ! CMD_CLOSE(None) + + val rolledBackTx = alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.head + assert(rolledBackTx.txOut == fundingTx.txOut) + assert(rolledBackTx.txIn.map(_.outPoint) == fundingTx.txIn.map(_.outPoint)) awaitCond(alice.stateName == CLOSED) } From 7770a58f8aa30bb6af50b878fe8ab698a26cbb3a Mon Sep 17 00:00:00 2001 From: Andrea Date: Fri, 14 Jun 2019 17:19:19 +0200 Subject: [PATCH 29/43] Use SubscribeTransitionCallBack to correctly wait for states during test --- .../states/b/WaitForFundingSignedInternalStateSpec.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala index 1e1982deeb..2ab06d51f5 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala @@ -16,6 +16,7 @@ package fr.acinq.eclair.channel.states.b +import akka.actor.FSM.{CurrentState, SubscribeTransitionCallBack, Transition} import scala.concurrent.duration._ import akka.testkit.{TestFSMRef, TestProbe} import fr.acinq.bitcoin.{Block, ByteVector32, Script} @@ -37,13 +38,18 @@ class WaitForFundingSignedInternalStateSpec extends TestkitBaseClass with StateT val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) within(30 seconds) { + val monitor = TestProbe() + alice ! SubscribeTransitionCallBack(monitor.ref) + val CurrentState(_, WAIT_FOR_INIT_INTERNAL) = monitor.expectMsgClass(classOf[CurrentState[_]]) alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, alice2bob.ref, bobInit, ChannelFlags.Empty) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit) + val Transition(_, WAIT_FOR_INIT_INTERNAL, WAIT_FOR_FUNDING_INTERNAL_CREATED) = monitor.expectMsgClass(classOf[Transition[_]]) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) bob2alice.expectMsgType[AcceptChannel] bob2alice.forward(alice) - awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL_SIGNED) + val Transition(_, WAIT_FOR_FUNDING_INTERNAL_CREATED, WAIT_FOR_ACCEPT_CHANNEL) = monitor.expectMsgClass(classOf[Transition[_]]) + val Transition(_, WAIT_FOR_ACCEPT_CHANNEL, WAIT_FOR_FUNDING_INTERNAL_SIGNED) = monitor.expectMsgClass(classOf[Transition[_]]) withFixture(test.toNoArgTest(FixtureParam(alice, alice2bob, bob2alice, alice2blockchain))) } } From f78f2f35659f15f58f1c67d0d7e50a2c6693ef5d Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 17 Jun 2019 14:39:44 +0200 Subject: [PATCH 30/43] Finish merging master --- .../acinq/eclair/channel/ChannelTypes.scala | 3 +- .../eclair/crypto/LocalKeyManagerSpec.scala | 34 +++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala index 820ba72f6c..f86e189a17 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala @@ -19,8 +19,7 @@ package fr.acinq.eclair.channel import java.util.UUID import akka.actor.ActorRef -import fr.acinq.bitcoin.Crypto.{PublicKey} -import fr.acinq.bitcoin.Crypto.{Point, PublicKey} +import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.DeterministicWallet.KeyPath import fr.acinq.bitcoin.{ByteVector32, DeterministicWallet, OutPoint, Satoshi, Transaction} import fr.acinq.eclair.blockchain.MakeFundingTxResponse diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala index 739af57a31..c2c61bd063 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala @@ -97,15 +97,15 @@ class LocalKeyManagerSpec extends FunSuite { val funderChannelKeyPath = LocalKeyManager.makeChannelKeyPathFunder(inputOutpoint) // TESTNET funder funding public key - assert(keyManager.fundingPublicKey(funderChannelKeyPath).publicKey.toBin === hex"03c69365c8ae813a45b9fead1056331c41f38ab3ab7d5d383d62c4c70cfd91f9ea") + assert(keyManager.fundingPublicKey(funderChannelKeyPath).publicKey.value === hex"03c69365c8ae813a45b9fead1056331c41f38ab3ab7d5d383d62c4c70cfd91f9ea") // TESTNET funder payment point - assert(keyManager.paymentPoint(funderChannelKeyPath).publicKey.toBin === hex"02efa31ae79a7c29faf23a21f00d5ca62ce14539c2f431db5dade3e919d2ff9050") + assert(keyManager.paymentPoint(funderChannelKeyPath).publicKey.value === hex"02efa31ae79a7c29faf23a21f00d5ca62ce14539c2f431db5dade3e919d2ff9050") // TESTNET funder revocation point - assert(keyManager.revocationPoint(funderChannelKeyPath).publicKey.toBin === hex"03f5cfec0e593e3d8fe965eb4ca066fe810cf8e87f7f2c0c8ababbd1a004662777") + assert(keyManager.revocationPoint(funderChannelKeyPath).publicKey.value === hex"03f5cfec0e593e3d8fe965eb4ca066fe810cf8e87f7f2c0c8ababbd1a004662777") // TESTNET funder delayed payment point - assert(keyManager.delayedPaymentPoint(funderChannelKeyPath).publicKey.toBin === hex"029b6dd179c16ea0952b7ff59ca59377ccd257b9ae8f0a6a88bd5409a07013b443") + assert(keyManager.delayedPaymentPoint(funderChannelKeyPath).publicKey.value === hex"029b6dd179c16ea0952b7ff59ca59377ccd257b9ae8f0a6a88bd5409a07013b443") // TESTNET funder htlc point - assert(keyManager.htlcPoint(funderChannelKeyPath).publicKey.toBin === hex"02e5afb68852863190c893d61f6244339ad368a14e0b761ab3cff5531c204e1efc") + assert(keyManager.htlcPoint(funderChannelKeyPath).publicKey.value === hex"02e5afb68852863190c893d61f6244339ad368a14e0b761ab3cff5531c204e1efc") } test("test vectors derivation paths (funder MAINNET)") { @@ -117,9 +117,9 @@ class LocalKeyManagerSpec extends FunSuite { val funderChannelKeyPath = LocalKeyManager.makeChannelKeyPathFunder(inputOutpoint) // MAINNET funder funding public key from extended public key - assert(keyManager.fundingPublicKey(funderChannelKeyPath).publicKey.toBin === hex"02dd88103f4690fa5484f0c6a13f917fb00f03b5f2be1375cd08c64b11b19d730e") + assert(keyManager.fundingPublicKey(funderChannelKeyPath).publicKey.value === hex"02dd88103f4690fa5484f0c6a13f917fb00f03b5f2be1375cd08c64b11b19d730e") // MAINNET funder payment point from extended public key - assert(keyManager.paymentPoint(funderChannelKeyPath).publicKey.toBin === hex"030a7898d98245666be451b55a63a5f1acd71ec1efec85fb8364f3fbe46327441b") + assert(keyManager.paymentPoint(funderChannelKeyPath).publicKey.value === hex"030a7898d98245666be451b55a63a5f1acd71ec1efec85fb8364f3fbe46327441b") } test("test vectors derivation paths (fundee TESTNET)") { @@ -130,15 +130,15 @@ class LocalKeyManagerSpec extends FunSuite { val fundeePubkeyKeyPath = LocalKeyManager.makeChannelKeyPathFundeePubkey(34273, 0) // TESTNET fundee funding public key from extended public key - assert(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey.toBin === hex"029278489277ce1abf6a05463ec913f5fe32ee194588a13c5b7899215d4ee477da") + assert(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey.value === hex"029278489277ce1abf6a05463ec913f5fe32ee194588a13c5b7899215d4ee477da") val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey, remoteNodePubkey))) val fundeeChannelKeyPath = LocalKeyManager.makeChannelKeyPathFundee(fundingPubkeyScript) // TESTNET fundee htlc public point from extended public key - assert(keyManager.htlcPoint(fundeeChannelKeyPath).publicKey.toBin === hex"02563a8f3480b06f1e653c7a1a9a006236dfe503ec616ccef8656ab8e2cbe938d5") + assert(keyManager.htlcPoint(fundeeChannelKeyPath).publicKey.value === hex"02563a8f3480b06f1e653c7a1a9a006236dfe503ec616ccef8656ab8e2cbe938d5") // TESTNET fundee htlc public point from extended public key - assert(keyManager.paymentPoint(fundeeChannelKeyPath).publicKey.toBin === hex"0321047df59f000ba15f674c2eb6180c00edb55e5eae6e8ea22e82554c4213cfa4") + assert(keyManager.paymentPoint(fundeeChannelKeyPath).publicKey.value === hex"0321047df59f000ba15f674c2eb6180c00edb55e5eae6e8ea22e82554c4213cfa4") } test("test vectors derivation paths (fundee MAINNET)") { @@ -149,13 +149,13 @@ class LocalKeyManagerSpec extends FunSuite { val fundeePubkeyKeyPath = LocalKeyManager.makeChannelKeyPathFundeePubkey(34273, 0) // MAINNET fundee funding public key from extended public key - assert(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey.toBin === hex"03fe59b3ac6c2f08172d634f0346194fbf3e4e90fe01422a030546a913f0a4b21e") + assert(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey.value === hex"03fe59b3ac6c2f08172d634f0346194fbf3e4e90fe01422a030546a913f0a4b21e") val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey, remoteNodePubkey))) val fundeeChannelKeyPath = LocalKeyManager.makeChannelKeyPathFundee(fundingPubkeyScript) // MAINNET fundee htlc public point from extended public key - assert(keyManager.htlcPoint(fundeeChannelKeyPath).publicKey.toBin === hex"03056cef064df0a65812c7c05fedfbdee978de5a9b4d986dc8b389e4c0ac42d9f1") + assert(keyManager.htlcPoint(fundeeChannelKeyPath).publicKey.value === hex"03056cef064df0a65812c7c05fedfbdee978de5a9b4d986dc8b389e4c0ac42d9f1") // with different counter val fundeePubkeyKeyPath1 = LocalKeyManager.makeChannelKeyPathFundeePubkey(34273, 500) @@ -163,15 +163,15 @@ class LocalKeyManagerSpec extends FunSuite { val fundeeChannelKeyPath1 = LocalKeyManager.makeChannelKeyPathFundee(fundingPubkeyScript1) // MAINNET fundee revocation point - assert(keyManager.revocationPoint(fundeeChannelKeyPath1).publicKey.toBin === hex"022a714b39b36557b43146c37502d3a94f64fa5f651651b7749894cae64e6f42f7") + assert(keyManager.revocationPoint(fundeeChannelKeyPath1).publicKey.value === hex"022a714b39b36557b43146c37502d3a94f64fa5f651651b7749894cae64e6f42f7") // MAINNET fundee payment point - assert(keyManager.paymentPoint(fundeeChannelKeyPath1).publicKey.toBin === hex"022427779e5ea91e885a63413cae8e28c4d9bc451f4cdf7494fd9acc6a27828c1b") + assert(keyManager.paymentPoint(fundeeChannelKeyPath1).publicKey.value === hex"022427779e5ea91e885a63413cae8e28c4d9bc451f4cdf7494fd9acc6a27828c1b") // MAINNET fundee delayed payment point - assert(keyManager.delayedPaymentPoint(fundeeChannelKeyPath1).publicKey.toBin === hex"0256f501048527f6d67e155b4aa615490f675d57e99e879f0758114dfe8774f5fd") + assert(keyManager.delayedPaymentPoint(fundeeChannelKeyPath1).publicKey.value === hex"0256f501048527f6d67e155b4aa615490f675d57e99e879f0758114dfe8774f5fd") // MAINNET fundee htlc point - assert(keyManager.htlcPoint(fundeeChannelKeyPath1).publicKey.toBin === hex"02d13757fd99c3e99985a263996d905d9a617fc2c17998630b3f5dd8cb79d88f79") + assert(keyManager.htlcPoint(fundeeChannelKeyPath1).publicKey.value === hex"02d13757fd99c3e99985a263996d905d9a617fc2c17998630b3f5dd8cb79d88f79") // MAINNED fundee shaSeed point - assert(keyManager.shaSeedPub(fundeeChannelKeyPath1).publicKey.toBin === hex"037bc0457999d9ba6609250146fc3176f5cf382cfe5216e007267fd7fe047b2bf4") + assert(keyManager.shaSeedPub(fundeeChannelKeyPath1).publicKey.value === hex"037bc0457999d9ba6609250146fc3176f5cf382cfe5216e007267fd7fe047b2bf4") } test("use correct keypath to compute keys") { From 0d5d15dfb2d97abdc1306ffa15a584930b933380 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 17 Jun 2019 16:16:32 +0200 Subject: [PATCH 31/43] Do not alter root derivation paths from previous eclair's version, update test vectors --- .../acinq/eclair/crypto/LocalKeyManager.scala | 28 +++---- .../eclair/crypto/LocalKeyManagerSpec.scala | 84 +++++++++---------- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala index f947db2bd2..de4ea1e83e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala @@ -28,8 +28,8 @@ import scodec.bits.{ByteOrdering, ByteVector} object LocalKeyManager { def channelKeyBasePath(chainHash: ByteVector32) = chainHash match { - case Block.RegtestGenesisBlock.hash | Block.TestnetGenesisBlock.hash => DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(2) :: Nil - case Block.LivenetGenesisBlock.hash => DeterministicWallet.hardened(47) :: DeterministicWallet.hardened(2) :: Nil + case Block.RegtestGenesisBlock.hash | Block.TestnetGenesisBlock.hash => DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(1) :: Nil + case Block.LivenetGenesisBlock.hash => DeterministicWallet.hardened(47) :: DeterministicWallet.hardened(1) :: Nil } @@ -83,30 +83,30 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana private def internalKeyPath(channelKeyPath: DeterministicWallet.KeyPath, index: Long): List[Long] = (LocalKeyManager.channelKeyBasePath(chainHash) ++ channelKeyPath.path) :+ index - private def fundingPrivateKey(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, 0)) + private def fundingPrivateKey(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, hardened(0))) - private def revocationSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, 1)) + private def revocationSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, hardened(1))) - private def paymentSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, 2)) + private def paymentSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, hardened(2))) - private def delayedPaymentSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, 3)) + private def delayedPaymentSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, hardened(3))) - private def htlcSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, 4)) + private def htlcSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, hardened(4))) - private def shaSeed(channelKeyPath: DeterministicWallet.KeyPath) = Crypto.sha256(privateKeys.get(internalKeyPath(channelKeyPath, 5)).privateKey.value :+ 1.toByte) + private def shaSeed(channelKeyPath: DeterministicWallet.KeyPath) = Crypto.sha256(privateKeys.get(internalKeyPath(channelKeyPath, hardened(5))).privateKey.value :+ 1.toByte) // used only in test - def shaSeedPub(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, 5)) + def shaSeedPub(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(5))) - override def fundingPublicKey(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, 0)) + override def fundingPublicKey(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(0))) - override def revocationPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, 1)) + override def revocationPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(1))) - override def paymentPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, 2)) + override def paymentPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(2))) - override def delayedPaymentPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, 3)) + override def delayedPaymentPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(3))) - override def htlcPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, 4)) + override def htlcPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(4))) override def commitmentSecret(channelKeyPath: DeterministicWallet.KeyPath, index: Long) = Generators.perCommitSecret(shaSeed(channelKeyPath), index) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala index c2c61bd063..4e77b89507 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala @@ -63,28 +63,28 @@ class LocalKeyManagerSpec extends FunSuite { } /** - * TESTNET funder funding public key from extended public key: 03c69365c8ae813a45b9fead1056331c41f38ab3ab7d5d383d62c4c70cfd91f9ea - * TESTNET funder payment point from extended public key: 02efa31ae79a7c29faf23a21f00d5ca62ce14539c2f431db5dade3e919d2ff9050 - * TESTNET funder revocation point from extended public key: 03f5cfec0e593e3d8fe965eb4ca066fe810cf8e87f7f2c0c8ababbd1a004662777 - * TESTNET funder payment point from extended public key: 02efa31ae79a7c29faf23a21f00d5ca62ce14539c2f431db5dade3e919d2ff9050 - * TESTNET funder delayed payment point from extended public key: 029b6dd179c16ea0952b7ff59ca59377ccd257b9ae8f0a6a88bd5409a07013b443 - * TESTNET funder htlc point from extended public key: 02e5afb68852863190c893d61f6244339ad368a14e0b761ab3cff5531c204e1efc - * TESTNET funder shachain point from extended public key: 02d46180dfd3a4bc388ac3dabe1240fc671d240e48c0f5652bce4363c22a878426 + * TESTNET funder funding public key from extended private key: 025677d1885ec346adadfb788739c455fec6d789702fedd578744a580d49224e1c + * TESTNET funder payment point from extended private key: 035af8e011c0ca6fe95585802cbdb563211033434f14738e99f9196894fded8306 + * TESTNET funder revocation point from extended private key: 029d6adad640a74826a9a683439cfdf76845dc52bb9bd418416bf486239f90ba4f + * TESTNET funder delayed payment point from extended private key: 02cf755cd01d7f48e0e373fbcd8d78eb8efe142ff2c2c108950bceaf88147945a6 + * TESTNET funder htlc point from extended private key: 03648c46cde1e8bbfe73cec3890cfcbcc0bfd5a08011b1e7a9d940ee11ae49f2a6 + * TESTNET funder shachain point from extended private key: * - * MAINNET funder funding public key from extended public key: 02dd88103f4690fa5484f0c6a13f917fb00f03b5f2be1375cd08c64b11b19d730e - * MAINNET funder payment point from extended public key: 030a7898d98245666be451b55a63a5f1acd71ec1efec85fb8364f3fbe46327441b + * MAINNET funder funding public key from extended private key: 029984abba4283bfc6f1b8fddf792fe8bf7592309cd01a5a99211cdaab61f6f701 + * MAINNET funder payment point from extended private key: 039c668597b0c0b547aefd315abf8abb3e5eb6fc905b97b65653b0fb86a9755a3a * - * TESTNET fundee funding public key from extended public key #34273 #0: 029278489277ce1abf6a05463ec913f5fe32ee194588a13c5b7899215d4ee477da - * TESTNET fundee htlc public point from extended public key #34273 #0: 02563a8f3480b06f1e653c7a1a9a006236dfe503ec616ccef8656ab8e2cbe938d5 + * TESTNET fundee funding public key from extended private key #34273 #0: 02e4c59d2e4736da12c5692ec7f540da33defb5709af7122e53e48ae27ce1be386 + * TESTNET fundee htlc public point from extended private key #34273 #0: 0390a904e032ec3b1970acdd0f00565b08b9e062e61985468fc7d0718c2ec5699f + * TESTNET fundee payment point from extended private key #34273 #0: 024d46d97bbd563aaa9f834632864cc2ef0c418d62ec6d4169fdbb3cd10c6e379c * - * MAINNET fundee funding public key from extended public key #34273 #0: 03fe59b3ac6c2f08172d634f0346194fbf3e4e90fe01422a030546a913f0a4b21e - * MAINNET fundee htlc public point from extended public key #34273 #0: 03056cef064df0a65812c7c05fedfbdee978de5a9b4d986dc8b389e4c0ac42d9f1 + * MAINNET fundee funding public key from extended private key #34273 #0: 034b39ac3dc425783453413875aaa9dc55f6d832867897f1e46727a07256766b92 + * MAINNET fundee htlc public point from extended private key #34273 #0: 03dd09a0fb2907b2e107e73686edde8e59dabcdae763aaec37c2bf60a3b04380c3 * - * MAINNET fundee revocation point from extended public key #34273 #500: 022a714b39b36557b43146c37502d3a94f64fa5f651651b7749894cae64e6f42f7 - * MAINNET fundee payment point from extended public key #34273 #500: 022427779e5ea91e885a63413cae8e28c4d9bc451f4cdf7494fd9acc6a27828c1b - * MAINNET fundee delayed payment point from extended public key #34273 #500: 0256f501048527f6d67e155b4aa615490f675d57e99e879f0758114dfe8774f5fd - * MAINNET fundee htlc point from extended public key #34273 #500: 02d13757fd99c3e99985a263996d905d9a617fc2c17998630b3f5dd8cb79d88f79 - * MAINNET fundee shachain public point from extended public key #34273 #500: 037bc0457999d9ba6609250146fc3176f5cf382cfe5216e007267fd7fe047b2bf4 + * MAINNET fundee revocation point from extended private key #34273 #500: 028f8416ff67a17e9b3d21269027fc50c08df65e690a2eb2c455630f4a6542ea2d + * MAINNET fundee payment point from extended private key #34273 #500: 037c01af4e8af469f4810a70497f4e4f61ce31ff8a7bed2fb1d9a6cc90d4c57c3e + * MAINNET fundee delayed payment point from extended private key #34273 #500: 0368d295e7e74c7de9a0972c56272130e7dbb47b25d44b1f127b9c29636c0bf2a0 + * MAINNET fundee htlc point from extended private key #34273 #500: 038c9ea3b9263f9ad85f751c5a2a1deb0864892327fe00133b55de21f7759a9c70 + * MAINNET fundee shachain public point from extended private key #34273 #500: 03885c5995154134b5570aa8c71814c0c5c5401b413459fb684b83817e9499aba7 * */ @@ -97,15 +97,15 @@ class LocalKeyManagerSpec extends FunSuite { val funderChannelKeyPath = LocalKeyManager.makeChannelKeyPathFunder(inputOutpoint) // TESTNET funder funding public key - assert(keyManager.fundingPublicKey(funderChannelKeyPath).publicKey.value === hex"03c69365c8ae813a45b9fead1056331c41f38ab3ab7d5d383d62c4c70cfd91f9ea") + assert(keyManager.fundingPublicKey(funderChannelKeyPath).publicKey.value === hex"025677d1885ec346adadfb788739c455fec6d789702fedd578744a580d49224e1c") // TESTNET funder payment point - assert(keyManager.paymentPoint(funderChannelKeyPath).publicKey.value === hex"02efa31ae79a7c29faf23a21f00d5ca62ce14539c2f431db5dade3e919d2ff9050") + assert(keyManager.paymentPoint(funderChannelKeyPath).publicKey.value === hex"035af8e011c0ca6fe95585802cbdb563211033434f14738e99f9196894fded8306") // TESTNET funder revocation point - assert(keyManager.revocationPoint(funderChannelKeyPath).publicKey.value === hex"03f5cfec0e593e3d8fe965eb4ca066fe810cf8e87f7f2c0c8ababbd1a004662777") + assert(keyManager.revocationPoint(funderChannelKeyPath).publicKey.value === hex"029d6adad640a74826a9a683439cfdf76845dc52bb9bd418416bf486239f90ba4f") // TESTNET funder delayed payment point - assert(keyManager.delayedPaymentPoint(funderChannelKeyPath).publicKey.value === hex"029b6dd179c16ea0952b7ff59ca59377ccd257b9ae8f0a6a88bd5409a07013b443") + assert(keyManager.delayedPaymentPoint(funderChannelKeyPath).publicKey.value === hex"02cf755cd01d7f48e0e373fbcd8d78eb8efe142ff2c2c108950bceaf88147945a6") // TESTNET funder htlc point - assert(keyManager.htlcPoint(funderChannelKeyPath).publicKey.value === hex"02e5afb68852863190c893d61f6244339ad368a14e0b761ab3cff5531c204e1efc") + assert(keyManager.htlcPoint(funderChannelKeyPath).publicKey.value === hex"03648c46cde1e8bbfe73cec3890cfcbcc0bfd5a08011b1e7a9d940ee11ae49f2a6") } test("test vectors derivation paths (funder MAINNET)") { @@ -116,10 +116,10 @@ class LocalKeyManagerSpec extends FunSuite { val funderChannelKeyPath = LocalKeyManager.makeChannelKeyPathFunder(inputOutpoint) - // MAINNET funder funding public key from extended public key - assert(keyManager.fundingPublicKey(funderChannelKeyPath).publicKey.value === hex"02dd88103f4690fa5484f0c6a13f917fb00f03b5f2be1375cd08c64b11b19d730e") - // MAINNET funder payment point from extended public key - assert(keyManager.paymentPoint(funderChannelKeyPath).publicKey.value === hex"030a7898d98245666be451b55a63a5f1acd71ec1efec85fb8364f3fbe46327441b") + // MAINNET funder funding public key from extended private key + assert(keyManager.fundingPublicKey(funderChannelKeyPath).publicKey.value === hex"029984abba4283bfc6f1b8fddf792fe8bf7592309cd01a5a99211cdaab61f6f701") + // MAINNET funder payment point from extended private key + assert(keyManager.paymentPoint(funderChannelKeyPath).publicKey.value === hex"039c668597b0c0b547aefd315abf8abb3e5eb6fc905b97b65653b0fb86a9755a3a") } test("test vectors derivation paths (fundee TESTNET)") { @@ -129,16 +129,16 @@ class LocalKeyManagerSpec extends FunSuite { val keyManager = new LocalKeyManager(seed, Block.TestnetGenesisBlock.hash) val fundeePubkeyKeyPath = LocalKeyManager.makeChannelKeyPathFundeePubkey(34273, 0) - // TESTNET fundee funding public key from extended public key - assert(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey.value === hex"029278489277ce1abf6a05463ec913f5fe32ee194588a13c5b7899215d4ee477da") + // TESTNET fundee funding public key from extended private key + assert(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey.value === hex"02e4c59d2e4736da12c5692ec7f540da33defb5709af7122e53e48ae27ce1be386") val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey, remoteNodePubkey))) val fundeeChannelKeyPath = LocalKeyManager.makeChannelKeyPathFundee(fundingPubkeyScript) - // TESTNET fundee htlc public point from extended public key - assert(keyManager.htlcPoint(fundeeChannelKeyPath).publicKey.value === hex"02563a8f3480b06f1e653c7a1a9a006236dfe503ec616ccef8656ab8e2cbe938d5") - // TESTNET fundee htlc public point from extended public key - assert(keyManager.paymentPoint(fundeeChannelKeyPath).publicKey.value === hex"0321047df59f000ba15f674c2eb6180c00edb55e5eae6e8ea22e82554c4213cfa4") + // TESTNET fundee htlc public point from extended private key + assert(keyManager.htlcPoint(fundeeChannelKeyPath).publicKey.value === hex"0390a904e032ec3b1970acdd0f00565b08b9e062e61985468fc7d0718c2ec5699f") + // TESTNET fundee htlc public point from extended private key + assert(keyManager.paymentPoint(fundeeChannelKeyPath).publicKey.value === hex"024d46d97bbd563aaa9f834632864cc2ef0c418d62ec6d4169fdbb3cd10c6e379c") } test("test vectors derivation paths (fundee MAINNET)") { @@ -148,14 +148,14 @@ class LocalKeyManagerSpec extends FunSuite { val keyManager = new LocalKeyManager(seed, Block.LivenetGenesisBlock.hash) val fundeePubkeyKeyPath = LocalKeyManager.makeChannelKeyPathFundeePubkey(34273, 0) - // MAINNET fundee funding public key from extended public key - assert(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey.value === hex"03fe59b3ac6c2f08172d634f0346194fbf3e4e90fe01422a030546a913f0a4b21e") + // MAINNET fundee funding public key from extended private key + assert(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey.value === hex"034b39ac3dc425783453413875aaa9dc55f6d832867897f1e46727a07256766b92") val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey, remoteNodePubkey))) val fundeeChannelKeyPath = LocalKeyManager.makeChannelKeyPathFundee(fundingPubkeyScript) - // MAINNET fundee htlc public point from extended public key - assert(keyManager.htlcPoint(fundeeChannelKeyPath).publicKey.value === hex"03056cef064df0a65812c7c05fedfbdee978de5a9b4d986dc8b389e4c0ac42d9f1") + // MAINNET fundee htlc public point from extended private key + assert(keyManager.htlcPoint(fundeeChannelKeyPath).publicKey.value === hex"03dd09a0fb2907b2e107e73686edde8e59dabcdae763aaec37c2bf60a3b04380c3") // with different counter val fundeePubkeyKeyPath1 = LocalKeyManager.makeChannelKeyPathFundeePubkey(34273, 500) @@ -163,15 +163,15 @@ class LocalKeyManagerSpec extends FunSuite { val fundeeChannelKeyPath1 = LocalKeyManager.makeChannelKeyPathFundee(fundingPubkeyScript1) // MAINNET fundee revocation point - assert(keyManager.revocationPoint(fundeeChannelKeyPath1).publicKey.value === hex"022a714b39b36557b43146c37502d3a94f64fa5f651651b7749894cae64e6f42f7") + assert(keyManager.revocationPoint(fundeeChannelKeyPath1).publicKey.value === hex"028f8416ff67a17e9b3d21269027fc50c08df65e690a2eb2c455630f4a6542ea2d") // MAINNET fundee payment point - assert(keyManager.paymentPoint(fundeeChannelKeyPath1).publicKey.value === hex"022427779e5ea91e885a63413cae8e28c4d9bc451f4cdf7494fd9acc6a27828c1b") + assert(keyManager.paymentPoint(fundeeChannelKeyPath1).publicKey.value === hex"037c01af4e8af469f4810a70497f4e4f61ce31ff8a7bed2fb1d9a6cc90d4c57c3e") // MAINNET fundee delayed payment point - assert(keyManager.delayedPaymentPoint(fundeeChannelKeyPath1).publicKey.value === hex"0256f501048527f6d67e155b4aa615490f675d57e99e879f0758114dfe8774f5fd") + assert(keyManager.delayedPaymentPoint(fundeeChannelKeyPath1).publicKey.value === hex"0368d295e7e74c7de9a0972c56272130e7dbb47b25d44b1f127b9c29636c0bf2a0") // MAINNET fundee htlc point - assert(keyManager.htlcPoint(fundeeChannelKeyPath1).publicKey.value === hex"02d13757fd99c3e99985a263996d905d9a617fc2c17998630b3f5dd8cb79d88f79") + assert(keyManager.htlcPoint(fundeeChannelKeyPath1).publicKey.value === hex"038c9ea3b9263f9ad85f751c5a2a1deb0864892327fe00133b55de21f7759a9c70") // MAINNED fundee shaSeed point - assert(keyManager.shaSeedPub(fundeeChannelKeyPath1).publicKey.value === hex"037bc0457999d9ba6609250146fc3176f5cf382cfe5216e007267fd7fe047b2bf4") + assert(keyManager.shaSeedPub(fundeeChannelKeyPath1).publicKey.value === hex"03885c5995154134b5570aa8c71814c0c5c5401b413459fb684b83817e9499aba7") } test("use correct keypath to compute keys") { From 602e73ba74c442714f733e1af2ba535b2c034578 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 17 Jun 2019 16:58:07 +0200 Subject: [PATCH 32/43] Use hardened BIP32 indexes also for the the internal keypaths --- .../acinq/eclair/crypto/LocalKeyManager.scala | 7 +- .../eclair/crypto/LocalKeyManagerSpec.scala | 70 +++++++++---------- 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala index de4ea1e83e..bb3ea55426 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala @@ -44,9 +44,9 @@ object LocalKeyManager { // split the SHA(input) into 8 groups of 4 bytes and convert to uint32 def fourByteGroupsFromSha(input: ByteVector): List[Long] = Crypto.sha256(input).toArray.grouped(4).map(ByteVector(_).toLong(signed = false)).toList - def makeChannelKeyPathFunder(entropy: ByteVector) = KeyPath(fourByteGroupsFromSha(entropy) :+ 0L) - def makeChannelKeyPathFundee(entropy: ByteVector) = KeyPath(fourByteGroupsFromSha(entropy) :+ 1L) - def makeChannelKeyPathFundeePubkey(entropy: ByteVector) = KeyPath(fourByteGroupsFromSha(entropy) :+ 2L) + def makeChannelKeyPathFunder(entropy: ByteVector) = KeyPath(fourByteGroupsFromSha(entropy) :+ hardened(0)) + def makeChannelKeyPathFundee(entropy: ByteVector) = KeyPath(fourByteGroupsFromSha(entropy) :+ hardened(1)) + def makeChannelKeyPathFundeePubkey(entropy: ByteVector) = KeyPath(fourByteGroupsFromSha(entropy) :+ hardened(2)) def makeChannelKeyPathFundeePubkey(blockHeight: Long, counter: Long): KeyPath = { val blockHeightBytes = ByteVector.fromLong(blockHeight, size = 4, ordering = ByteOrdering.LittleEndian) @@ -63,7 +63,6 @@ object LocalKeyManager { * @param seed seed from which keys will be derived */ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyManager { - private val master = DeterministicWallet.generate(seed) override val nodeKey = DeterministicWallet.derivePrivateKey(master, LocalKeyManager.nodeKeyBasePath(chainHash)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala index 4e77b89507..710f68bf91 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala @@ -63,28 +63,27 @@ class LocalKeyManagerSpec extends FunSuite { } /** - * TESTNET funder funding public key from extended private key: 025677d1885ec346adadfb788739c455fec6d789702fedd578744a580d49224e1c - * TESTNET funder payment point from extended private key: 035af8e011c0ca6fe95585802cbdb563211033434f14738e99f9196894fded8306 - * TESTNET funder revocation point from extended private key: 029d6adad640a74826a9a683439cfdf76845dc52bb9bd418416bf486239f90ba4f - * TESTNET funder delayed payment point from extended private key: 02cf755cd01d7f48e0e373fbcd8d78eb8efe142ff2c2c108950bceaf88147945a6 - * TESTNET funder htlc point from extended private key: 03648c46cde1e8bbfe73cec3890cfcbcc0bfd5a08011b1e7a9d940ee11ae49f2a6 - * TESTNET funder shachain point from extended private key: + * TESTNET funder funding public key from extended private key: 039abec2238484d9a0680a8ffce6ad920930285fea1cbc30b8f53fad862f6e9157 + * TESTNET funder payment point from extended private key: 03edbfb2cd187e5738f3d4bb5b4d14dad16ee50e3deb1043155d6832d7816aa5f7 + * TESTNET funder revocation point from extended private key: 023c73ac122df051f8cabbefd9d1a06845aab01f0e4fed85475530ff99b73a4bfd + * TESTNET funder delayed payment point from extended private key: 02168b4c51ba573a7cf2158da5338c6e852f5a62960ae2561dcc6f9e5ed6932ba5 + * TESTNET funder htlc point from extended private key: 03d82c30596db587d9e32ebcc68e451727c841ed27ffed441c7eb05e00bb16305d * - * MAINNET funder funding public key from extended private key: 029984abba4283bfc6f1b8fddf792fe8bf7592309cd01a5a99211cdaab61f6f701 - * MAINNET funder payment point from extended private key: 039c668597b0c0b547aefd315abf8abb3e5eb6fc905b97b65653b0fb86a9755a3a + * MAINNET funder funding public key from extended private key: 0353b7dfdb0cbae349146795bd6406ff6ac5cd93bfb31bbdfc5df01b0d4da171b4 + * MAINNET funder payment point from extended private key: 0254036505ad9aa8d8e03ed3727825eaa72ce49f5ff9f2fd286e6d20a2b88caa12 * - * TESTNET fundee funding public key from extended private key #34273 #0: 02e4c59d2e4736da12c5692ec7f540da33defb5709af7122e53e48ae27ce1be386 - * TESTNET fundee htlc public point from extended private key #34273 #0: 0390a904e032ec3b1970acdd0f00565b08b9e062e61985468fc7d0718c2ec5699f - * TESTNET fundee payment point from extended private key #34273 #0: 024d46d97bbd563aaa9f834632864cc2ef0c418d62ec6d4169fdbb3cd10c6e379c + * TESTNET fundee funding public key from extended private key #34273 #0: 02bfe2d6e9ec07b68f588c1bcbf6afdb1e1e068ab11e82b3648eb5032007e9bb48 + * TESTNET fundee htlc public point from extended private key #34273 #0: 037223be02f02a117c9ad3ee243c1664d5763a57911317257536e6f355acb32418 + * TESTNET fundee payment point from extended private key #34273 #0: 02ce4468352b0b722e7e066a3603ed9081dcc6a93137b45e1d9acbe35caad08c2e * - * MAINNET fundee funding public key from extended private key #34273 #0: 034b39ac3dc425783453413875aaa9dc55f6d832867897f1e46727a07256766b92 - * MAINNET fundee htlc public point from extended private key #34273 #0: 03dd09a0fb2907b2e107e73686edde8e59dabcdae763aaec37c2bf60a3b04380c3 + * MAINNET fundee funding public key from extended private key #34273 #0: 03b8c35b3171dcec2d1e4a2b08244b4b4ce284bd8a7dbef6da467cb6db5abdc30d + * MAINNET fundee htlc public point from extended private key #34273 #0: 0258173e7db0f833691e72b259a9bf5349d9548dd9dcfccf4556a238d9d76193bf * - * MAINNET fundee revocation point from extended private key #34273 #500: 028f8416ff67a17e9b3d21269027fc50c08df65e690a2eb2c455630f4a6542ea2d - * MAINNET fundee payment point from extended private key #34273 #500: 037c01af4e8af469f4810a70497f4e4f61ce31ff8a7bed2fb1d9a6cc90d4c57c3e - * MAINNET fundee delayed payment point from extended private key #34273 #500: 0368d295e7e74c7de9a0972c56272130e7dbb47b25d44b1f127b9c29636c0bf2a0 - * MAINNET fundee htlc point from extended private key #34273 #500: 038c9ea3b9263f9ad85f751c5a2a1deb0864892327fe00133b55de21f7759a9c70 - * MAINNET fundee shachain public point from extended private key #34273 #500: 03885c5995154134b5570aa8c71814c0c5c5401b413459fb684b83817e9499aba7 + * MAINNET fundee revocation point from extended private key #34273 #500: 0213718574b4169ef093286fbc6459c81c7a672cfae62391e405aa04f82fa318e2 + * MAINNET fundee payment point from extended private key #34273 #500: 026a5d2792fb7fec674e75e34a981c74bb07b7439aca8db72d800a5b7f106648be + * MAINNET fundee delayed payment point from extended private key #34273 #500: 03b289f71d00e6b363adc08cdd7eec113441ab1c6c8208621d71e70789874f48d0 + * MAINNET fundee htlc point from extended private key #34273 #500: 039fd510225bd7c70a5f8f1661247c19cab9942088d7c868bad1c47bf0fad9b73d + * MAINNET fundee shachain public point from extended private key #34273 #500: 0200742b2176552d4e29d01455f7de2c16094c77ba644310d5d58385306c09ddc9 * */ @@ -97,15 +96,15 @@ class LocalKeyManagerSpec extends FunSuite { val funderChannelKeyPath = LocalKeyManager.makeChannelKeyPathFunder(inputOutpoint) // TESTNET funder funding public key - assert(keyManager.fundingPublicKey(funderChannelKeyPath).publicKey.value === hex"025677d1885ec346adadfb788739c455fec6d789702fedd578744a580d49224e1c") + assert(keyManager.fundingPublicKey(funderChannelKeyPath).publicKey.value === hex"039abec2238484d9a0680a8ffce6ad920930285fea1cbc30b8f53fad862f6e9157") // TESTNET funder payment point - assert(keyManager.paymentPoint(funderChannelKeyPath).publicKey.value === hex"035af8e011c0ca6fe95585802cbdb563211033434f14738e99f9196894fded8306") + assert(keyManager.paymentPoint(funderChannelKeyPath).publicKey.value === hex"03edbfb2cd187e5738f3d4bb5b4d14dad16ee50e3deb1043155d6832d7816aa5f7") // TESTNET funder revocation point - assert(keyManager.revocationPoint(funderChannelKeyPath).publicKey.value === hex"029d6adad640a74826a9a683439cfdf76845dc52bb9bd418416bf486239f90ba4f") + assert(keyManager.revocationPoint(funderChannelKeyPath).publicKey.value === hex"023c73ac122df051f8cabbefd9d1a06845aab01f0e4fed85475530ff99b73a4bfd") // TESTNET funder delayed payment point - assert(keyManager.delayedPaymentPoint(funderChannelKeyPath).publicKey.value === hex"02cf755cd01d7f48e0e373fbcd8d78eb8efe142ff2c2c108950bceaf88147945a6") + assert(keyManager.delayedPaymentPoint(funderChannelKeyPath).publicKey.value === hex"02168b4c51ba573a7cf2158da5338c6e852f5a62960ae2561dcc6f9e5ed6932ba5") // TESTNET funder htlc point - assert(keyManager.htlcPoint(funderChannelKeyPath).publicKey.value === hex"03648c46cde1e8bbfe73cec3890cfcbcc0bfd5a08011b1e7a9d940ee11ae49f2a6") + assert(keyManager.htlcPoint(funderChannelKeyPath).publicKey.value === hex"03d82c30596db587d9e32ebcc68e451727c841ed27ffed441c7eb05e00bb16305d") } test("test vectors derivation paths (funder MAINNET)") { @@ -117,9 +116,10 @@ class LocalKeyManagerSpec extends FunSuite { val funderChannelKeyPath = LocalKeyManager.makeChannelKeyPathFunder(inputOutpoint) // MAINNET funder funding public key from extended private key - assert(keyManager.fundingPublicKey(funderChannelKeyPath).publicKey.value === hex"029984abba4283bfc6f1b8fddf792fe8bf7592309cd01a5a99211cdaab61f6f701") + println(s"keyManager.fundingPublicKey(funderChannelKeyPath) = ${keyManager.fundingPublicKey(funderChannelKeyPath).path}") + assert(keyManager.fundingPublicKey(funderChannelKeyPath).publicKey.value === hex"0353b7dfdb0cbae349146795bd6406ff6ac5cd93bfb31bbdfc5df01b0d4da171b4") // MAINNET funder payment point from extended private key - assert(keyManager.paymentPoint(funderChannelKeyPath).publicKey.value === hex"039c668597b0c0b547aefd315abf8abb3e5eb6fc905b97b65653b0fb86a9755a3a") + assert(keyManager.paymentPoint(funderChannelKeyPath).publicKey.value === hex"0254036505ad9aa8d8e03ed3727825eaa72ce49f5ff9f2fd286e6d20a2b88caa12") } test("test vectors derivation paths (fundee TESTNET)") { @@ -130,15 +130,15 @@ class LocalKeyManagerSpec extends FunSuite { val fundeePubkeyKeyPath = LocalKeyManager.makeChannelKeyPathFundeePubkey(34273, 0) // TESTNET fundee funding public key from extended private key - assert(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey.value === hex"02e4c59d2e4736da12c5692ec7f540da33defb5709af7122e53e48ae27ce1be386") + assert(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey.value === hex"02bfe2d6e9ec07b68f588c1bcbf6afdb1e1e068ab11e82b3648eb5032007e9bb48") val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey, remoteNodePubkey))) val fundeeChannelKeyPath = LocalKeyManager.makeChannelKeyPathFundee(fundingPubkeyScript) // TESTNET fundee htlc public point from extended private key - assert(keyManager.htlcPoint(fundeeChannelKeyPath).publicKey.value === hex"0390a904e032ec3b1970acdd0f00565b08b9e062e61985468fc7d0718c2ec5699f") + assert(keyManager.htlcPoint(fundeeChannelKeyPath).publicKey.value === hex"037223be02f02a117c9ad3ee243c1664d5763a57911317257536e6f355acb32418") // TESTNET fundee htlc public point from extended private key - assert(keyManager.paymentPoint(fundeeChannelKeyPath).publicKey.value === hex"024d46d97bbd563aaa9f834632864cc2ef0c418d62ec6d4169fdbb3cd10c6e379c") + assert(keyManager.paymentPoint(fundeeChannelKeyPath).publicKey.value === hex"02ce4468352b0b722e7e066a3603ed9081dcc6a93137b45e1d9acbe35caad08c2e") } test("test vectors derivation paths (fundee MAINNET)") { @@ -149,13 +149,13 @@ class LocalKeyManagerSpec extends FunSuite { val fundeePubkeyKeyPath = LocalKeyManager.makeChannelKeyPathFundeePubkey(34273, 0) // MAINNET fundee funding public key from extended private key - assert(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey.value === hex"034b39ac3dc425783453413875aaa9dc55f6d832867897f1e46727a07256766b92") + assert(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey.value === hex"03b8c35b3171dcec2d1e4a2b08244b4b4ce284bd8a7dbef6da467cb6db5abdc30d") val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey, remoteNodePubkey))) val fundeeChannelKeyPath = LocalKeyManager.makeChannelKeyPathFundee(fundingPubkeyScript) // MAINNET fundee htlc public point from extended private key - assert(keyManager.htlcPoint(fundeeChannelKeyPath).publicKey.value === hex"03dd09a0fb2907b2e107e73686edde8e59dabcdae763aaec37c2bf60a3b04380c3") + assert(keyManager.htlcPoint(fundeeChannelKeyPath).publicKey.value === hex"0258173e7db0f833691e72b259a9bf5349d9548dd9dcfccf4556a238d9d76193bf") // with different counter val fundeePubkeyKeyPath1 = LocalKeyManager.makeChannelKeyPathFundeePubkey(34273, 500) @@ -163,15 +163,15 @@ class LocalKeyManagerSpec extends FunSuite { val fundeeChannelKeyPath1 = LocalKeyManager.makeChannelKeyPathFundee(fundingPubkeyScript1) // MAINNET fundee revocation point - assert(keyManager.revocationPoint(fundeeChannelKeyPath1).publicKey.value === hex"028f8416ff67a17e9b3d21269027fc50c08df65e690a2eb2c455630f4a6542ea2d") + assert(keyManager.revocationPoint(fundeeChannelKeyPath1).publicKey.value === hex"0213718574b4169ef093286fbc6459c81c7a672cfae62391e405aa04f82fa318e2") // MAINNET fundee payment point - assert(keyManager.paymentPoint(fundeeChannelKeyPath1).publicKey.value === hex"037c01af4e8af469f4810a70497f4e4f61ce31ff8a7bed2fb1d9a6cc90d4c57c3e") + assert(keyManager.paymentPoint(fundeeChannelKeyPath1).publicKey.value === hex"026a5d2792fb7fec674e75e34a981c74bb07b7439aca8db72d800a5b7f106648be") // MAINNET fundee delayed payment point - assert(keyManager.delayedPaymentPoint(fundeeChannelKeyPath1).publicKey.value === hex"0368d295e7e74c7de9a0972c56272130e7dbb47b25d44b1f127b9c29636c0bf2a0") + assert(keyManager.delayedPaymentPoint(fundeeChannelKeyPath1).publicKey.value === hex"03b289f71d00e6b363adc08cdd7eec113441ab1c6c8208621d71e70789874f48d0") // MAINNET fundee htlc point - assert(keyManager.htlcPoint(fundeeChannelKeyPath1).publicKey.value === hex"038c9ea3b9263f9ad85f751c5a2a1deb0864892327fe00133b55de21f7759a9c70") + assert(keyManager.htlcPoint(fundeeChannelKeyPath1).publicKey.value === hex"039fd510225bd7c70a5f8f1661247c19cab9942088d7c868bad1c47bf0fad9b73d") // MAINNED fundee shaSeed point - assert(keyManager.shaSeedPub(fundeeChannelKeyPath1).publicKey.value === hex"03885c5995154134b5570aa8c71814c0c5c5401b413459fb684b83817e9499aba7") + assert(keyManager.shaSeedPub(fundeeChannelKeyPath1).publicKey.value === hex"0200742b2176552d4e29d01455f7de2c16094c77ba644310d5d58385306c09ddc9") } test("use correct keypath to compute keys") { From 922072fceafc571eb80add936b40fe852c3d915f Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 18 Jun 2019 10:26:12 +0200 Subject: [PATCH 33/43] Remove unnecessary println, merge master, improve handling of rollback while wait_for_funding_signed_internal --- .../blockchain/electrum/ElectrumClient.scala | 10 +++++----- .../scala/fr/acinq/eclair/channel/Channel.scala | 10 +++++++--- .../blockchain/electrum/ElectrumClientSpec.scala | 8 ++------ .../b/WaitForFundingSignedInternalStateSpec.scala | 14 ++++++++------ .../acinq/eclair/crypto/LocalKeyManagerSpec.scala | 1 - 5 files changed, 22 insertions(+), 21 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumClient.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumClient.scala index e1e1869c93..271d8039df 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumClient.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/electrum/ElectrumClient.scala @@ -381,7 +381,7 @@ object ElectrumClient { case class BroadcastTransactionResponse(tx: Transaction, error: Option[Error]) extends Response case class GetTransactionIdFromPosition(height: Int, tx_pos: Int, merkle: Boolean = false) extends Request - case class GetTransactionIdFromPositionResponse(txid: ByteVector32, merkle: Seq[ByteVector32]) extends Response + case class GetTransactionIdFromPositionResponse(txid: ByteVector32, height: Int, tx_pos: Int, merkle: Seq[ByteVector32]) extends Response case class GetTransaction(txid: ByteVector32) extends Request case class GetTransactionResponse(tx: Transaction) extends Response @@ -593,14 +593,14 @@ object ElectrumClient { UnspentItem(ByteVector32.fromValidHex(tx_hash), tx_pos, value, height) }) ScriptHashListUnspentResponse(scripthash, items) - case GetTransactionIdFromPosition(_, _, false) => + case GetTransactionIdFromPosition(height, tx_pos, false) => val JString(tx_hash) = json.result - GetTransactionIdFromPositionResponse(ByteVector32.fromValidHex(tx_hash), Nil) - case GetTransactionIdFromPosition(_, _, true) => + GetTransactionIdFromPositionResponse(ByteVector32.fromValidHex(tx_hash), height, tx_pos, Nil) + case GetTransactionIdFromPosition(height, tx_pos, true) => val JString(tx_hash) = json.result \ "tx_hash" val JArray(hashes) = json.result \ "merkle" val leaves = hashes collect { case JString(value) => ByteVector32.fromValidHex(value) } - GetTransactionIdFromPositionResponse(ByteVector32.fromValidHex(tx_hash), leaves) + GetTransactionIdFromPositionResponse(ByteVector32.fromValidHex(tx_hash), height, tx_pos, leaves) case GetTransaction(_) => val JString(hex) = json.result GetTransactionResponse(Transaction.read(hex)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index d2da5fd66f..980f6ef7c1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -501,20 +501,24 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId replyToUser(Left(LocalError(t))) handleLocalError(ChannelFundingError(d.temporaryChannelId), d, None) // we use a generic exception and don't send the internal error to the peer - case Event(CMD_CLOSE(_), _) => + case Event(CMD_CLOSE(_) | CMD_FORCECLOSE, d: DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED) => replyToUser(Right("closed")) + wallet.rollback(d.unsignedFundingTx) goto(CLOSED) replying "ok" case Event(e: Error, d: DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED) => replyToUser(Left(RemoteError(e))) + wallet.rollback(d.unsignedFundingTx) handleRemoteError(e, d) - case Event(INPUT_DISCONNECTED, _) => + case Event(INPUT_DISCONNECTED, d: DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED) => replyToUser(Left(LocalError(new RuntimeException("disconnected")))) + wallet.rollback(d.unsignedFundingTx) goto(CLOSED) - case Event(TickChannelOpenTimeout, _) => + case Event(TickChannelOpenTimeout, d: DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED) => replyToUser(Left(LocalError(new RuntimeException("open channel cancelled, took too long")))) + wallet.rollback(d.unsignedFundingTx) goto(CLOSED) }) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumClientSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumClientSpec.scala index fb936f955a..ebe8e90906 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumClientSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumClientSpec.scala @@ -69,16 +69,12 @@ class ElectrumClientSpec extends TestKit(ActorSystem("test")) with FunSuiteLike test("get transaction id from position") { probe.send(client, GetTransactionIdFromPosition(height, position)) - val GetTransactionIdFromPositionResponse(txid, merkle) = probe.expectMsgType[GetTransactionIdFromPositionResponse] - assert(txid === referenceTx.txid) - assert(merkle === Nil) + probe.expectMsg(GetTransactionIdFromPositionResponse(referenceTx.txid, height, position, Nil)) } test("get transaction id from position with merkle proof") { probe.send(client, GetTransactionIdFromPosition(height, position, merkle = true)) - val GetTransactionIdFromPositionResponse(txid, merkle) = probe.expectMsgType[GetTransactionIdFromPositionResponse] - assert(txid === referenceTx.txid) - assert(merkle === merkleProof) + probe.expectMsg(GetTransactionIdFromPositionResponse(referenceTx.txid, height, position, merkleProof)) } test("get transaction") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala index 2ab06d51f5..d1081fc74c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala @@ -62,10 +62,11 @@ class WaitForFundingSignedInternalStateSpec extends TestkitBaseClass with StateT alice ! Error(ByteVector32.Zeroes, "oops") + awaitCond({ + val rolledBackTx = alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.head + rolledBackTx.txOut == fundingTx.txOut && rolledBackTx.txIn.map(_.outPoint) == fundingTx.txIn.map(_.outPoint) + }) awaitCond(alice.stateName == CLOSED) - val rolledBackTx = alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.head - assert(rolledBackTx.txOut == fundingTx.txOut) - assert(rolledBackTx.txIn.map(_.outPoint) == fundingTx.txIn.map(_.outPoint)) } test("recv CMD_CLOSE") { f => @@ -75,9 +76,10 @@ class WaitForFundingSignedInternalStateSpec extends TestkitBaseClass with StateT alice ! CMD_CLOSE(None) - val rolledBackTx = alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.head - assert(rolledBackTx.txOut == fundingTx.txOut) - assert(rolledBackTx.txIn.map(_.outPoint) == fundingTx.txIn.map(_.outPoint)) + awaitCond({ + val rolledBackTx = alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.head + rolledBackTx.txOut == fundingTx.txOut && rolledBackTx.txIn.map(_.outPoint) == fundingTx.txIn.map(_.outPoint) + }) awaitCond(alice.stateName == CLOSED) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala index 710f68bf91..4336cc530e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala @@ -116,7 +116,6 @@ class LocalKeyManagerSpec extends FunSuite { val funderChannelKeyPath = LocalKeyManager.makeChannelKeyPathFunder(inputOutpoint) // MAINNET funder funding public key from extended private key - println(s"keyManager.fundingPublicKey(funderChannelKeyPath) = ${keyManager.fundingPublicKey(funderChannelKeyPath).path}") assert(keyManager.fundingPublicKey(funderChannelKeyPath).publicKey.value === hex"0353b7dfdb0cbae349146795bd6406ff6ac5cd93bfb31bbdfc5df01b0d4da171b4") // MAINNET funder payment point from extended private key assert(keyManager.paymentPoint(funderChannelKeyPath).publicKey.value === hex"0254036505ad9aa8d8e03ed3727825eaa72ce49f5ff9f2fd286e6d20a2b88caa12") From 79a8284bf13b0b078ea45138a821af93c62d5ca4 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 18 Jun 2019 11:29:10 +0200 Subject: [PATCH 34/43] Wait for correct state in test --- .../states/b/WaitForFundingSignedInternalStateSpec.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala index d1081fc74c..11a44a53ab 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala @@ -57,6 +57,7 @@ class WaitForFundingSignedInternalStateSpec extends TestkitBaseClass with StateT test("recv Error") { f => import f._ + awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL_SIGNED) val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED].unsignedFundingTx assert(alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.isEmpty) @@ -71,6 +72,8 @@ class WaitForFundingSignedInternalStateSpec extends TestkitBaseClass with StateT test("recv CMD_CLOSE") { f => import f._ + + awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL_SIGNED) val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED].unsignedFundingTx assert(alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.isEmpty) From a693df52bf8a435d26911890644f7a5e791fe8d1 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 18 Jun 2019 12:10:00 +0200 Subject: [PATCH 35/43] Use a non returing call to avoid quick transition during test --- .../WaitForFundingSignedInternalStateSpec.scala | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala index 11a44a53ab..a3552f344e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedInternalStateSpec.scala @@ -17,9 +17,10 @@ package fr.acinq.eclair.channel.states.b import akka.actor.FSM.{CurrentState, SubscribeTransitionCallBack, Transition} + import scala.concurrent.duration._ import akka.testkit.{TestFSMRef, TestProbe} -import fr.acinq.bitcoin.{Block, ByteVector32, Script} +import fr.acinq.bitcoin.{Block, ByteVector32, Script, Transaction} import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.blockchain.TestWallet import fr.acinq.eclair.{TestConstants, TestkitBaseClass} @@ -28,12 +29,19 @@ import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.wire.{AcceptChannel, Error, Init, OpenChannel} import org.scalatest.Outcome +import scala.concurrent.{Await, ExecutionContext, Future, Promise} + class WaitForFundingSignedInternalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { case class FixtureParam(alice: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe) override def withFixture(test: OneArgTest): Outcome = { - val setup = init() + + val nonReturningWallet = new TestWallet { + override def signTransactionComplete(tx: Transaction): Future[Transaction] = Promise[Transaction]().future + } + + val setup = init(wallet = nonReturningWallet) import setup._ val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) @@ -57,7 +65,7 @@ class WaitForFundingSignedInternalStateSpec extends TestkitBaseClass with StateT test("recv Error") { f => import f._ - awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL_SIGNED) + assert(alice.stateName == WAIT_FOR_FUNDING_INTERNAL_SIGNED) val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED].unsignedFundingTx assert(alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.isEmpty) @@ -73,7 +81,7 @@ class WaitForFundingSignedInternalStateSpec extends TestkitBaseClass with StateT test("recv CMD_CLOSE") { f => import f._ - awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL_SIGNED) + assert(alice.stateName == WAIT_FOR_FUNDING_INTERNAL_SIGNED) val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL_SIGNED].unsignedFundingTx assert(alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.isEmpty) From 4b24e0332bb5e9e12ec2a0707ecea6f85ea460b7 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 18 Jun 2019 13:25:36 +0200 Subject: [PATCH 36/43] Add counter table to migration v1 -> v2 --- .../main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala | 1 + .../src/test/scala/fr/acinq/eclair/db/SqliteChannelsDbSpec.scala | 1 + 2 files changed, 2 insertions(+) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala index a905a80395..be1d21eb55 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala @@ -36,6 +36,7 @@ class SqliteChannelsDb(sqlite: Connection) extends ChannelsDb with Logging { private def migration12(statement: Statement) = { statement.executeUpdate("ALTER TABLE local_channels ADD COLUMN is_closed BOOLEAN NOT NULL DEFAULT 0") + statement.executeUpdate("CREATE TABLE IF NOT EXISTS block_key_counter (block_height INTEGER NOT NULL PRIMARY KEY, counter INTEGER NOT NULL)") } using(sqlite.createStatement()) { statement => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteChannelsDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteChannelsDbSpec.scala index 002395483b..b1c33b3d76 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteChannelsDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteChannelsDbSpec.scala @@ -92,6 +92,7 @@ class SqliteChannelsDbSpec extends FunSuite { assert(getVersion(statement, "channels", 1) == 2) // version changed from 1 -> 2 } assert(db.listLocalChannels() === List(channel)) + assert(db.getCounterFor(123) === 0) } test("channel keypath counter should get and increment") { From f95bd004916ec39201a1445ac090d049a7e0f887 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 24 Jun 2019 15:39:03 +0200 Subject: [PATCH 37/43] Test backward compatibility for state transition WAIT_FOR_FUNDING_CONFIRMED -> WAIT_FOR_FUNDING_LOCKED in funder scenario --- .../c/WaitForFundingConfirmedStateSpec.scala | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala index 0a9e53a233..5d1c1e4f6f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala @@ -23,9 +23,10 @@ import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.states.StateTestsHelperMethods -import fr.acinq.eclair.wire.{AcceptChannel, Error, FundingCreated, FundingLocked, FundingSigned, Init, OpenChannel} +import fr.acinq.eclair.wire.{AcceptChannel, ChannelCodecs, Error, FundingCreated, FundingLocked, FundingSigned, Init, OpenChannel} import fr.acinq.eclair.{TestConstants, TestkitBaseClass} import org.scalatest.Outcome +import scodec.bits._ import scala.concurrent.duration._ @@ -78,6 +79,19 @@ class WaitForFundingConfirmedStateSpec extends TestkitBaseClass with StateTestsH alice2bob.expectMsgType[FundingLocked] } + // this asserts we can move WAIT_FOR_FUNDING_CONFIRMED -> WAIT_FOR_FUNDING_LOCKED after upgrading from master 316ba02f + test("recv BITCOIN_FUNDING_DEPTHOK using a legacy DATA_WAIT_FOR_FUNDING_CONFIRMED") { f => + import f._ + + val legacyStateData = ChannelCodecs.stateDataCodec.decode(hex"00000803af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d00004087224bcb3e8633fe2886e6f3c18b703000000000000044c0000000008f0d1800000000000002710000000000000000000900064800b000a3c62de7dc8e62bd93b66cf47420e5790edef339a800000008001cee07058e92c82f227efb7c744baad2f10cb42c20685e0e94699847cb1451ac280000000000001f47fffffffffffffff800000000000271000000000000001f40048000f01ed9187296e4c54b479ae345140aa1391ccc9faf267f6c5944dfe664caa63bc87819a259d51b0799ae40ffe378da636a9295f0f1ca729fe7288e3fb726321ddacb9012a9a22a5fe0f279fd7a14a2470128271d76ef0d4f7e2e6a89d40372966b3c60d81d57eefaafa9c0d29da3771e8bfb57df89bf9222a6201a512dffb3002070c2bb101e68a0233838a37442cc5ac9367c5e33036d86c59da6504bf4b640c513dcc8f44000000000000000000000000000000000013880000000017d784000000000005f5e100001256455401b07c5bf408c59267c5c7f9ba4a3d95e328450deb5a7a3296d83ecab2800000000015a021078000000000110010646319c0c17e834063062d25ad955159489aea968e1b14d6cc58cb99be89c7838023a910813d7b72631b3de37925f4d91934a35e9f5178ff78c7deaba3b49ba5a94cff776b1081ed9187296e4c54b479ae345140aa1391ccc9faf267f6c5944dfe664caa63bc87a95700ad01000000000080d6455401b07c5bf408c59267c5c7f9ba4a3d95e328450deb5a7a3296d83ecab28000000000316633400120068180000000000b000a08f63f057773606da741e58e2f9d7a536d9d91dd5c0c0600000000001100105be873e3d5ba9979d72da3bbd5720a416b1d970bf2bc00702f1676655c18d65b82002418228110805db5dae6348bc53e64f001e567412275e64af83e5b66d71e6c0d693d833d8c3181102f27d53bd13e6e7581e9bc55cc101cb844a50f2094ef24a4b0f65fe6fbe6f67880a39822011020340f177c84953a0321b6c2cde893fa4d3629bd1f9b5164308fb5766196850d811010d91e80ba0f02a3ed8a31a84f897338f4549e6dab14b109b591d25a44da8b4000a3a910813d7b72631b3de37925f4d91934a35e9f5178ff78c7deaba3b49ba5a94cff776b1081ed9187296e4c54b479ae345140aa1391ccc9faf267f6c5944dfe664caa63bc87a9570382a890000000000000000000000000000013880000000005f5e1000000000017d784000eb072f0b13aeae10a66802ce65c477f1eb2a44be91c4f178a044479bf442060016197621911deea00ecdd7d789e91690661608651f7b5e7c6b7f30582b4dd6d2100000000000000000000000000000000000000000000000000000000000040c0c42642c86958a4e78c48f1078e03ccf00165e9483691069a1e70f59a15c1b580092b22aa00d83e2dfa0462c933e2e3fcdd251ecaf1942286f5ad3d194b6c1f655940000000000ad01083c00000000008800832318ce060bf41a031831692d6caa8aca44d754b470d8a6b662c65ccdf44e3c1c011d488409ebdb9318d9ef1bc92fa6c8c9a51af4fa8bc7fbc63ef55d1da4dd2d4a67fbbb58840f6c8c394b7262a5a3cd71a28a05509c8e664fd7933fb62ca26ff33265531de43d4ab8000159155006c1f16fd02316499f171fe6e928f6578ca11437ad69e8ca5b60fb2acb005e020000000101010101010101010101010101010101010101010101010101010101010101012a00000000ffffffff0140420f0000000000220020c8c6338182fd0680c60c5a4b5b2aa2b29135d52d1c3629ad98b197337d138f0700000000000000005d10c71400000000000000000000000000000000000000000000000000000000000000002b22aa00d83e2dfa0462c933e2e3fcdd251ecaf1942286f5ad3d194b6c1f655940003726337f1c37ac74d004a8836ac4fc05eb3dbb6f4c99301a2a4d079bc6fa9391d00e30f4a4a1aa0bb119b1528ca822195da27202992398f79c820cb0569ba4910".bits).require.value + alice.setState(WAIT_FOR_FUNDING_CONFIRMED, stateData = legacyStateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED]) + alice ! WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK, 42000, 42) + awaitCond(alice.stateName == WAIT_FOR_FUNDING_LOCKED) + alice2blockchain.expectMsgType[WatchLost] + alice2bob.expectMsgType[FundingLocked] + } + + test("recv BITCOIN_FUNDING_PUBLISH_FAILED") { f => import f._ alice ! BITCOIN_FUNDING_PUBLISH_FAILED From 08395e82247be17c3ce0c48280518487084cbd06 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 24 Jun 2019 15:47:56 +0200 Subject: [PATCH 38/43] Test backward compatibility for state transition WAIT_FOR_FUNDING_LOCKED -> NORMAL in funder scenario --- .../states/c/WaitForFundingLockedStateSpec.scala | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingLockedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingLockedStateSpec.scala index cf0d6bf2a7..9b8c49db06 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingLockedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingLockedStateSpec.scala @@ -26,7 +26,7 @@ import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.wire._ import fr.acinq.eclair.{TestConstants, TestkitBaseClass} import org.scalatest.Outcome - +import scodec.bits._ import scala.concurrent.duration._ /** @@ -76,6 +76,18 @@ class WaitForFundingLockedStateSpec extends TestkitBaseClass with StateTestsHelp bob2alice.expectNoMsg(200 millis) } + // this asserts we can move WAIT_FOR_FUNDING_LOCKED -> NORMAL after upgrading from master 316ba02f + test("recv FundingLocked using a legacy DATA_WAIT_FOR_FUNDING_LOCKED") { f => + import f._ + + val legacyStateData = ChannelCodecs.stateDataCodec.decode(hex"00000203af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d0000419bcfa4263dd8d3687392dafc9912ee2000000000000044c0000000008f0d1800000000000002710000000000000000000900064800b000a011bd50be544b3e72531e4bdcda249a7035bfe8a800000008001cee07058e92c82f227efb7c744baad2f10cb42c20685e0e94699847cb1451ac280000000000001f47fffffffffffffff800000000000271000000000000001f40048000f011b7b3af58e62bd1afb758ec699a9de5c024bb9ffa3a13eedff8cab2f42fadf8e811c3a0116ed5f9b6e70131b4a563f14887ad2c74da13b0153b7eeb674d6cc408c01536569e85f9f905ffa5517ddc1f95e88d0350df9ccf58760fa315521c3917e0b81ebd3e53a68001ba7bbe2e387b4de858147f0f3facbc7460251438d931177e89981e15184fa6a7d0750233dbfdfdf023c5ef72ae88d67c8e64f499e020fe4d0723f800000000000000000000000000000000013880000000017d784000000000005f5e10000121b6b4371be5edfc7bc25e0c9201d867b1869a30a84928bcb30f3aee423b28f14800000000015a0210780000000001100102b64496b535cc694f07c8ca04832eabc04df4910a9707df0d1d5478d5d3862bd0023a910811b7b3af58e62bd1afb758ec699a9de5c024bb9ffa3a13eedff8cab2f42fadf8e9081fc58d19c6c0c664fb329dd212a43e043a7094202c5f4410b04e964353b1770c8a95700ac810000000000809b6b4371be5edfc7bc25e0c9201d867b1869a30a84928bcb30f3aee423b28f1480000000000cbf3dc00120068180000000000b000a1ddff3dac3bc63f50b7f548c71c3c67189fac725dc0c06000000000011001012b70e548015620a96a8b89ff50d877830f87a49edfb7baf3cba2944eb6a3c58020023982201100be011964fa2b937fc410107f1196e12fc512be2f7495f09a954f8afe53d2a0a011019210ba8ef98698a7fb84c0598757e9c38fc3d81cf919bfb19d3b6676baff85500a39822011003ebbeb478fa8233d82d86402dbf5e5e3ab92a79c5093fb3b1a3792c1e51dc78811026e1122d3fd2ed2a14d61f3496679838c57cedcdb9e35a02145f34db5da701b880a3a910811b7b3af58e62bd1afb758ec699a9de5c024bb9ffa3a13eedff8cab2f42fadf8e9081fc58d19c6c0c664fb329dd212a43e043a7094202c5f4410b04e964353b1770c8a95702ba2d90000000000000000000000000000013880000000005f5e1000000000017d7840060a5b5bbab14ce247fdbfcef08972f3c4cbecb63bb5c7832b24db0150ab37da8013fb54c36f195bef6fd3034d4105a75b04d688b8e08d035e19df55b3a06623e2000000000000000000000000000000000000000000000000000000000000040e3559e01ef0db325dae812ad6a4f85fdfc4539ae02d2ced0e06cacc8b4cbf0f800090db5a1b8df2f6fe3de12f064900ec33d8c34d185424945e59879d77211d9478a40000000000ad01083c00000000008800815b224b5a9ae634a783e46502419755e026fa48854b83ef868eaa3c6ae9c315e8011d488408dbd9d7ac7315e8d7dbac7634cd4ef2e0125dcffd1d09f76ffc65597a17d6fc74840fe2c68ce36063327d994ee909521f021d384a10162fa20858274b21a9d8bb86454ab800006dad0dc6f97b7f1ef0978324807619ec61a68c2a124a2f2cc3cebb908eca3c520c350000005400006dad0dc6f97b7f1ef0978324807619ec61a68c2a124a2f2cc3cebb908eca3c52044526ddfd95ae01708239a4c3f48a04a762a4586607066e782569afa115590a840".bits).require.value + alice.setState(WAIT_FOR_FUNDING_LOCKED, stateData = legacyStateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED]) + bob2alice.expectMsgType[FundingLocked] + bob2alice.forward(alice) + awaitCond(alice.stateName == NORMAL) + bob2alice.expectNoMsg(200 millis) + } + test("recv BITCOIN_FUNDING_SPENT (remote commit)") { f => import f._ // bob publishes his commitment tx From bdcd0aba7e6c2f6eb6acc5e665aa4d3a57cfda20 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 4 Jul 2019 11:16:39 +0200 Subject: [PATCH 39/43] Finish merging master --- .../scala/fr/acinq/eclair/wire/ChannelCodecs.scala | 10 +++++----- .../states/c/WaitForFundingConfirmedStateSpec.scala | 4 ++-- .../eclair/channel/states/h/ClosingStateSpec.scala | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala index 28494a407c..1e72753f5d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala @@ -114,10 +114,10 @@ object ChannelCodecs extends Logging { val localParamsCodecV1: Codec[LocalParams] = ( ("nodeId" | publicKey) :: ("channelPath" | channelKeyPathCodec) :: - ("dustLimitSatoshis" | uint64) :: - ("maxHtlcValueInFlightMsat" | uint64ex) :: - ("channelReserveSatoshis" | uint64) :: - ("htlcMinimumMsat" | uint64) :: + ("dustLimitSatoshis" | uint64overflow) :: + ("maxHtlcValueInFlightMsat" | uint64) :: + ("channelReserveSatoshis" | uint64overflow) :: + ("htlcMinimumMsat" | uint64overflow) :: ("toSelfDelay" | uint16) :: ("maxAcceptedHtlcs" | uint16) :: ("defaultFinalScriptPubKey" | varsizebinarydata) :: @@ -409,7 +409,7 @@ object ChannelCodecs extends Logging { .typecase(0x03, DATA_NORMAL_Codec(commitmentVersion)) .typecase(0x04, DATA_SHUTDOWN_Codec(commitmentVersion)) .typecase(0x05, DATA_NEGOTIATING_Codec(commitmentVersion)) - .typecase(0x06, DATA_CLOSING_Codec(commitmentVersion)) + .typecase(0x06, DATA_CLOSING_COMPAT_06_Codec(commitmentVersion)) .typecase(0x07, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec(commitmentVersion)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala index 1cf17f5340..6c7c6b2c89 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala @@ -22,7 +22,7 @@ import fr.acinq.bitcoin.{ByteVector32, Satoshi, Script, Transaction} import fr.acinq.eclair.randomKey import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.blockchain._ -import fr.acinq.eclair.channel._ +import fr.acinq.eclair.channel.{WAIT_FOR_FUNDING_CONFIRMED, _} import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.transactions.Scripts.multiSig2of2 import fr.acinq.eclair.wire.{AcceptChannel, ChannelCodecs, Error, FundingCreated, FundingLocked, FundingSigned, Init, OpenChannel} @@ -89,7 +89,7 @@ class WaitForFundingConfirmedStateSpec extends TestkitBaseClass with StateTestsH val legacyStateData = ChannelCodecs.stateDataCodec.decode(hex"00000803af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d00004087224bcb3e8633fe2886e6f3c18b703000000000000044c0000000008f0d1800000000000002710000000000000000000900064800b000a3c62de7dc8e62bd93b66cf47420e5790edef339a800000008001cee07058e92c82f227efb7c744baad2f10cb42c20685e0e94699847cb1451ac280000000000001f47fffffffffffffff800000000000271000000000000001f40048000f01ed9187296e4c54b479ae345140aa1391ccc9faf267f6c5944dfe664caa63bc87819a259d51b0799ae40ffe378da636a9295f0f1ca729fe7288e3fb726321ddacb9012a9a22a5fe0f279fd7a14a2470128271d76ef0d4f7e2e6a89d40372966b3c60d81d57eefaafa9c0d29da3771e8bfb57df89bf9222a6201a512dffb3002070c2bb101e68a0233838a37442cc5ac9367c5e33036d86c59da6504bf4b640c513dcc8f44000000000000000000000000000000000013880000000017d784000000000005f5e100001256455401b07c5bf408c59267c5c7f9ba4a3d95e328450deb5a7a3296d83ecab2800000000015a021078000000000110010646319c0c17e834063062d25ad955159489aea968e1b14d6cc58cb99be89c7838023a910813d7b72631b3de37925f4d91934a35e9f5178ff78c7deaba3b49ba5a94cff776b1081ed9187296e4c54b479ae345140aa1391ccc9faf267f6c5944dfe664caa63bc87a95700ad01000000000080d6455401b07c5bf408c59267c5c7f9ba4a3d95e328450deb5a7a3296d83ecab28000000000316633400120068180000000000b000a08f63f057773606da741e58e2f9d7a536d9d91dd5c0c0600000000001100105be873e3d5ba9979d72da3bbd5720a416b1d970bf2bc00702f1676655c18d65b82002418228110805db5dae6348bc53e64f001e567412275e64af83e5b66d71e6c0d693d833d8c3181102f27d53bd13e6e7581e9bc55cc101cb844a50f2094ef24a4b0f65fe6fbe6f67880a39822011020340f177c84953a0321b6c2cde893fa4d3629bd1f9b5164308fb5766196850d811010d91e80ba0f02a3ed8a31a84f897338f4549e6dab14b109b591d25a44da8b4000a3a910813d7b72631b3de37925f4d91934a35e9f5178ff78c7deaba3b49ba5a94cff776b1081ed9187296e4c54b479ae345140aa1391ccc9faf267f6c5944dfe664caa63bc87a9570382a890000000000000000000000000000013880000000005f5e1000000000017d784000eb072f0b13aeae10a66802ce65c477f1eb2a44be91c4f178a044479bf442060016197621911deea00ecdd7d789e91690661608651f7b5e7c6b7f30582b4dd6d2100000000000000000000000000000000000000000000000000000000000040c0c42642c86958a4e78c48f1078e03ccf00165e9483691069a1e70f59a15c1b580092b22aa00d83e2dfa0462c933e2e3fcdd251ecaf1942286f5ad3d194b6c1f655940000000000ad01083c00000000008800832318ce060bf41a031831692d6caa8aca44d754b470d8a6b662c65ccdf44e3c1c011d488409ebdb9318d9ef1bc92fa6c8c9a51af4fa8bc7fbc63ef55d1da4dd2d4a67fbbb58840f6c8c394b7262a5a3cd71a28a05509c8e664fd7933fb62ca26ff33265531de43d4ab8000159155006c1f16fd02316499f171fe6e928f6578ca11437ad69e8ca5b60fb2acb005e020000000101010101010101010101010101010101010101010101010101010101010101012a00000000ffffffff0140420f0000000000220020c8c6338182fd0680c60c5a4b5b2aa2b29135d52d1c3629ad98b197337d138f0700000000000000005d10c71400000000000000000000000000000000000000000000000000000000000000002b22aa00d83e2dfa0462c933e2e3fcdd251ecaf1942286f5ad3d194b6c1f655940003726337f1c37ac74d004a8836ac4fc05eb3dbb6f4c99301a2a4d079bc6fa9391d00e30f4a4a1aa0bb119b1528ca822195da27202992398f79c820cb0569ba4910".bits).require.value alice.setState(WAIT_FOR_FUNDING_CONFIRMED, stateData = legacyStateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED]) - alice ! WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK, 42000, 42) + alice ! WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK, 42000, 42, legacyStateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get) awaitCond(alice.stateName == WAIT_FOR_FUNDING_LOCKED) alice2blockchain.expectMsgType[WatchLost] alice2bob.expectMsgType[FundingLocked] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala index ec19d818e7..c23393d212 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala @@ -64,7 +64,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods { within(30 seconds) { val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, alice2bob.ref, bobInit, ChannelFlags.Empty) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) From 259748bab819329fc7c4e7698b5a0813ce534176 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 4 Jul 2019 13:24:40 +0200 Subject: [PATCH 40/43] Renaming --- .../src/main/scala/fr/acinq/eclair/channel/Channel.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 5ed7462f8a..e50fc0045d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -99,11 +99,11 @@ object Channel { def makeFundeeChannelParams(nodeParams: NodeParams, open: OpenChannel, defaultFinalScriptPubKey: ByteVector, fundingSatoshis: Long): LocalParams = { val blockHeight = Globals.blockCount.get val counter = nodeParams.db.channels.getCounterFor(blockHeight) - val publicKeyPath = LocalKeyManager.makeChannelKeyPathFundeePubkey(blockHeight, counter) - val localFundingPubkey = nodeParams.keyManager.fundingPublicKey(publicKeyPath).publicKey + val fundingKeyPath = LocalKeyManager.makeChannelKeyPathFundeePubkey(blockHeight, counter) + val localFundingPubkey = nodeParams.keyManager.fundingPublicKey(fundingKeyPath).publicKey val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey, open.fundingPubkey))) val channelKeyPath = LocalKeyManager.makeChannelKeyPathFundee(fundingPubkeyScript) - val channelKeyPaths = KeyPathFundee(publicKeyPath, channelKeyPath) + val channelKeyPaths = KeyPathFundee(fundingKeyPath, channelKeyPath) makeChannelParams(nodeParams, defaultFinalScriptPubKey, fundingSatoshis, Right(channelKeyPaths)) } From 5c1698e5f3d568a588ea95a3d5deba2eb9f5158a Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 4 Jul 2019 13:39:31 +0200 Subject: [PATCH 41/43] Move the publishing of `ChannelCreated` close to the sending of `open_channel` --- .../src/main/scala/fr/acinq/eclair/channel/Channel.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index e50fc0045d..dd62be69fb 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -198,7 +198,6 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId when(WAIT_FOR_INIT_INTERNAL)(handleExceptions { case Event(INPUT_INIT_FUNDER(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, remote, remoteInit, channelFlags), Nothing) => - context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, true, temporaryChannelId)) forwarder ! remote // the resulting funding tx response will NOT have input script signature @@ -354,6 +353,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId when(WAIT_FOR_FUNDING_INTERNAL_CREATED)(handleExceptions{ case Event(funding:MakeFundingTxResponse, DATA_WAIT_FOR_FUNDING_INTERNAL_CREATED(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, remote, remoteInit, channelFlags)) => + context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, true, temporaryChannelId)) val defaultFinalScriptPubKey = Helpers.getFinalScriptPubKey(wallet, nodeParams.chainHash) val localParams = makeFunderChannelParams(nodeParams, defaultFinalScriptPubKey, fundingSatoshis, funding.fundingTx.txIn.head) From 6136adfe6414f0f55fc3695bf742a815ccfd1f6d Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 16 Jul 2019 10:49:25 +0200 Subject: [PATCH 42/43] Use transitions callbacks to avoid race condition in test --- .../states/a/WaitForAcceptChannelStateSpec.scala | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala index 6b2e9d50ec..b150019211 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala @@ -16,6 +16,7 @@ package fr.acinq.eclair.channel.states.a +import akka.actor.FSM.{CurrentState, SubscribeTransitionCallBack, Transition} import akka.testkit.{TestFSMRef, TestProbe} import fr.acinq.bitcoin.{Block, ByteVector32, Satoshi} import fr.acinq.eclair.TestConstants.{Alice, Bob} @@ -52,11 +53,24 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) within(30 seconds) { + val aliceMonitor = TestProbe() + val bobMonitor = TestProbe() + alice ! SubscribeTransitionCallBack(aliceMonitor.ref) + bob ! SubscribeTransitionCallBack(bobMonitor.ref) + + val CurrentState(_, WAIT_FOR_INIT_INTERNAL) = aliceMonitor.expectMsgClass(classOf[CurrentState[_]]) + val CurrentState(_, WAIT_FOR_INIT_INTERNAL) = bobMonitor.expectMsgClass(classOf[CurrentState[_]]) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, alice2bob.ref, bobInit, ChannelFlags.Empty) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit) + + val Transition(_, WAIT_FOR_INIT_INTERNAL, WAIT_FOR_FUNDING_INTERNAL_CREATED) = aliceMonitor.expectMsgClass(classOf[Transition[_]]) + val Transition(_, WAIT_FOR_INIT_INTERNAL, WAIT_FOR_OPEN_CHANNEL) = bobMonitor.expectMsgClass(classOf[Transition[_]]) + alice2bob.expectMsgType[OpenChannel] - alice2bob.forward(bob) awaitCond(alice.stateName == WAIT_FOR_ACCEPT_CHANNEL) + alice2bob.forward(bob) + val Transition(_, WAIT_FOR_OPEN_CHANNEL, WAIT_FOR_FUNDING_CREATED) = bobMonitor.expectMsgClass(classOf[Transition[_]]) withFixture(test.toNoArgTest(FixtureParam(alice, alice2bob, bob2alice, alice2blockchain))) } } From 2b00b1463ec339161cc5de278955624f48f9165f Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 16 Jul 2019 11:37:58 +0200 Subject: [PATCH 43/43] Do not send message to remote (bob) during fixture setup --- .../a/WaitForAcceptChannelStateSpec.scala | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala index b150019211..6e966acc31 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala @@ -16,7 +16,6 @@ package fr.acinq.eclair.channel.states.a -import akka.actor.FSM.{CurrentState, SubscribeTransitionCallBack, Transition} import akka.testkit.{TestFSMRef, TestProbe} import fr.acinq.bitcoin.{Block, ByteVector32, Satoshi} import fr.acinq.eclair.TestConstants.{Alice, Bob} @@ -27,8 +26,6 @@ import fr.acinq.eclair.channel.{WAIT_FOR_FUNDING_INTERNAL_SIGNED, _} import fr.acinq.eclair.wire.{AcceptChannel, Error, Init, OpenChannel} import fr.acinq.eclair.{TestConstants, TestkitBaseClass} import org.scalatest.{Outcome, Tag} -import scodec.bits.ByteVector - import scala.concurrent.{Future, Promise} import scala.concurrent.duration._ @@ -38,7 +35,7 @@ import scala.concurrent.duration._ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelperMethods { - case class FixtureParam(alice: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe) + case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe) override def withFixture(test: OneArgTest): Outcome = { val setup = if (test.tags.contains("mainnet")) { @@ -53,30 +50,17 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) within(30 seconds) { - val aliceMonitor = TestProbe() - val bobMonitor = TestProbe() - alice ! SubscribeTransitionCallBack(aliceMonitor.ref) - bob ! SubscribeTransitionCallBack(bobMonitor.ref) - - val CurrentState(_, WAIT_FOR_INIT_INTERNAL) = aliceMonitor.expectMsgClass(classOf[CurrentState[_]]) - val CurrentState(_, WAIT_FOR_INIT_INTERNAL) = bobMonitor.expectMsgClass(classOf[CurrentState[_]]) - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, alice2bob.ref, bobInit, ChannelFlags.Empty) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, Bob.channelParams, bob2alice.ref, aliceInit) - - val Transition(_, WAIT_FOR_INIT_INTERNAL, WAIT_FOR_FUNDING_INTERNAL_CREATED) = aliceMonitor.expectMsgClass(classOf[Transition[_]]) - val Transition(_, WAIT_FOR_INIT_INTERNAL, WAIT_FOR_OPEN_CHANNEL) = bobMonitor.expectMsgClass(classOf[Transition[_]]) - alice2bob.expectMsgType[OpenChannel] awaitCond(alice.stateName == WAIT_FOR_ACCEPT_CHANNEL) - alice2bob.forward(bob) - val Transition(_, WAIT_FOR_OPEN_CHANNEL, WAIT_FOR_FUNDING_CREATED) = bobMonitor.expectMsgClass(classOf[Transition[_]]) - withFixture(test.toNoArgTest(FixtureParam(alice, alice2bob, bob2alice, alice2blockchain))) + withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain))) } } test("recv AcceptChannel") { f => import f._ + alice2bob.forward(bob) bob2alice.expectMsgType[AcceptChannel] bob2alice.forward(alice) awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL_SIGNED) @@ -84,6 +68,7 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp test("recv AcceptChannel (invalid max accepted htlcs)") { f => import f._ + alice2bob.forward(bob) val accept = bob2alice.expectMsgType[AcceptChannel] // spec says max = 483 val invalidMaxAcceptedHtlcs = 484 @@ -95,6 +80,7 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp test("recv AcceptChannel (invalid dust limit)", Tag("mainnet")) { f => import f._ + alice2bob.forward(bob) val accept = bob2alice.expectMsgType[AcceptChannel] // we don't want their dust limit to be below 546 val lowDustLimitSatoshis = 545 @@ -106,6 +92,7 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp test("recv AcceptChannel (to_self_delay too high)") { f => import f._ + alice2bob.forward(bob) val accept = bob2alice.expectMsgType[AcceptChannel] val delayTooHigh = 10000 alice ! accept.copy(toSelfDelay = delayTooHigh) @@ -116,6 +103,7 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp test("recv AcceptChannel (reserve too high)") { f => import f._ + alice2bob.forward(bob) val accept = bob2alice.expectMsgType[AcceptChannel] // 30% is huge, recommended ratio is 1% val reserveTooHigh = (0.3 * TestConstants.fundingSatoshis).toLong @@ -127,6 +115,7 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp test("recv AcceptChannel (reserve below dust limit)") { f => import f._ + alice2bob.forward(bob) val accept = bob2alice.expectMsgType[AcceptChannel] val reserveTooSmall = accept.dustLimitSatoshis - 1 alice ! accept.copy(channelReserveSatoshis = reserveTooSmall) @@ -137,6 +126,7 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp test("recv AcceptChannel (reserve below our dust limit)") { f => import f._ + alice2bob.forward(bob) val accept = bob2alice.expectMsgType[AcceptChannel] val open = alice.stateData.asInstanceOf[DATA_WAIT_FOR_ACCEPT_CHANNEL].lastSent val reserveTooSmall = open.dustLimitSatoshis - 1 @@ -148,6 +138,7 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp test("recv AcceptChannel (dust limit above our reserve)") { f => import f._ + alice2bob.forward(bob) val accept = bob2alice.expectMsgType[AcceptChannel] val open = alice.stateData.asInstanceOf[DATA_WAIT_FOR_ACCEPT_CHANNEL].lastSent val dustTooBig = open.channelReserveSatoshis + 1 @@ -159,6 +150,7 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp test("recv Error") { f => import f._ + alice2bob.forward(bob) val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_ACCEPT_CHANNEL].unsignedFundingTx.fundingTx assert(alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.isEmpty) @@ -170,6 +162,7 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp test("recv CMD_CLOSE") { f => import f._ + alice2bob.forward(bob) val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_ACCEPT_CHANNEL].unsignedFundingTx.fundingTx assert(alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.isEmpty) @@ -181,6 +174,7 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp test("recv TickChannelOpenTimeout") { f => import f._ + alice2bob.forward(bob) val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_ACCEPT_CHANNEL].unsignedFundingTx.fundingTx assert(alice.underlyingActor.wallet.asInstanceOf[TestWallet].rolledback.isEmpty)