diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala index ad2b71016..80f73fa98 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -7,11 +7,10 @@ import java.sql.DriverManager import java.util.concurrent.TimeUnit import com.typesafe.config.{Config, ConfigFactory} -import fr.acinq.bitcoin.Crypto.PrivateKey -import fr.acinq.bitcoin.DeterministicWallet.ExtendedPrivateKey -import fr.acinq.bitcoin.{BinaryData, Block, DeterministicWallet} +import fr.acinq.bitcoin.{BinaryData, Block} import fr.acinq.eclair.NodeParams.WatcherType import fr.acinq.eclair.channel.Channel +import fr.acinq.eclair.crypto.KeyManager import fr.acinq.eclair.db._ import fr.acinq.eclair.db.sqlite._ import fr.acinq.eclair.wire.Color @@ -22,8 +21,7 @@ import scala.concurrent.duration.FiniteDuration /** * Created by PM on 26/02/2017. */ -case class NodeParams(extendedPrivateKey: ExtendedPrivateKey, - privateKey: PrivateKey, +case class NodeParams(keyManager: KeyManager, alias: String, color: Color, publicAddresses: List[InetSocketAddress], @@ -58,7 +56,8 @@ case class NodeParams(extendedPrivateKey: ExtendedPrivateKey, watcherType: WatcherType, paymentRequestExpiry: FiniteDuration, maxPendingPaymentRequests: Int) { - val nodeId = privateKey.publicKey + val privateKey = keyManager.nodeKey.privateKey + val nodeId = keyManager.nodeId } object NodeParams { @@ -82,25 +81,22 @@ object NodeParams { .withFallback(overrideDefaults) .withFallback(ConfigFactory.load()).getConfig("eclair") - def makeNodeParams(datadir: File, config: Config, seed_opt: Option[BinaryData] = None): NodeParams = { + def getSeed(datadir: File): BinaryData = { + val seedPath = new File(datadir, "seed.dat") + seedPath.exists() match { + case true => Files.readAllBytes(seedPath.toPath) + case false => + datadir.mkdirs() + val seed = randomKey.toBin + Files.write(seedPath.toPath, seed) + seed + } + } + + def makeNodeParams(datadir: File, config: Config, keyManager: KeyManager): NodeParams = { datadir.mkdirs() - val seed: BinaryData = seed_opt match { - case Some(s) => s - case None => - val seedPath = new File(datadir, "seed.dat") - seedPath.exists() match { - case true => Files.readAllBytes(seedPath.toPath) - case false => - val seed = randomKey.toBin - Files.write(seedPath.toPath, seed) - seed - } - } - val master = DeterministicWallet.generate(seed) - val extendedPrivateKey = DeterministicWallet.derivePrivateKey(master, DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(0) :: Nil) - val chain = config.getString("chain") val chainHash = chain match { case "test" => Block.TestnetGenesisBlock.hash @@ -134,8 +130,7 @@ object NodeParams { require(maxAcceptedHtlcs <= Channel.MAX_ACCEPTED_HTLCS, s"max-accepted-htlcs must be lower than ${Channel.MAX_ACCEPTED_HTLCS}") NodeParams( - extendedPrivateKey = extendedPrivateKey, - privateKey = extendedPrivateKey.privateKey, + keyManager = keyManager, alias = config.getString("node-alias").take(32), color = Color(color.data(0), color.data(1), color.data(2)), publicAddresses = config.getStringList("server.public-ips").toList.map(ip => new InetSocketAddress(ip, config.getInt("server.port"))), diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala index 189d48782..37256937b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Setup.scala @@ -19,6 +19,7 @@ import fr.acinq.eclair.blockchain.electrum.{ElectrumClient, ElectrumEclairWallet import fr.acinq.eclair.blockchain.fee.{ConstantFeeProvider, _} import fr.acinq.eclair.blockchain.{EclairWallet, _} import fr.acinq.eclair.channel.Register +import fr.acinq.eclair.crypto.LocalKeyManager import fr.acinq.eclair.io.{Authenticator, Server, Switchboard} import fr.acinq.eclair.payment._ import fr.acinq.eclair.router._ @@ -30,7 +31,7 @@ import scala.concurrent.{Await, ExecutionContext, Future, Promise} /** * Setup eclair from a datadir. - *
+ * * Created by PM on 25/01/2016. * * @param datadir directory where eclair-core will write/read its data @@ -44,9 +45,11 @@ class Setup(datadir: File, overrideDefaults: Config = ConfigFactory.empty(), act logger.info(s"version=${getClass.getPackage.getImplementationVersion} commit=${getClass.getPackage.getSpecificationVersion}") logger.info(s"datadir=${datadir.getCanonicalPath}") - val config: Config = NodeParams.loadConfiguration(datadir, overrideDefaults) - val nodeParams: NodeParams = NodeParams.makeNodeParams(datadir, config, seed_opt) - val chain: String = config.getString("chain") + val config = NodeParams.loadConfiguration(datadir, overrideDefaults) + val seed = seed_opt.getOrElse(NodeParams.getSeed(datadir)) + val keyManager = new LocalKeyManager(seed) + val nodeParams = NodeParams.makeNodeParams(datadir, config, keyManager) + val chain = config.getString("chain") // early checks DBCompatChecker.checkDBCompatibility(nodeParams) 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 b47375200..e82391c94 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 @@ -10,7 +10,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.{Generators, ShaChain, Sphinx} +import fr.acinq.eclair.crypto.{Generators, LocalKeyManager, ShaChain, Sphinx} import fr.acinq.eclair.payment._ import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions._ @@ -52,6 +52,7 @@ object Channel { class Channel(val nodeParams: NodeParams, 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] { import Channel._ + import nodeParams.keyManager // we pass these to helpers classes so that they have the logging context implicit def implicitLog = log @@ -106,7 +107,6 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Event(initFunder@INPUT_INIT_FUNDER(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, localParams, remote, remoteInit, channelFlags), Nothing) => context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, true, temporaryChannelId)) forwarder ! remote - val firstPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 0) val open = OpenChannel(nodeParams.chainHash, temporaryChannelId = temporaryChannelId, fundingSatoshis = fundingSatoshis, @@ -118,12 +118,12 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu feeratePerKw = initialFeeratePerKw, toSelfDelay = localParams.toSelfDelay, maxAcceptedHtlcs = localParams.maxAcceptedHtlcs, - fundingPubkey = localParams.fundingPrivKey.publicKey, - revocationBasepoint = localParams.revocationBasepoint, - paymentBasepoint = localParams.paymentBasepoint, - delayedPaymentBasepoint = localParams.delayedPaymentBasepoint, - htlcBasepoint = localParams.htlcBasepoint, - firstPerCommitmentPoint = firstPerCommitmentPoint, + 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) goto(WAIT_FOR_ACCEPT_CHANNEL) using DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder, open) sending open @@ -187,7 +187,6 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu 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 firstPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 0) val accept = AcceptChannel(temporaryChannelId = open.temporaryChannelId, dustLimitSatoshis = localParams.dustLimitSatoshis, maxHtlcValueInFlightMsat = localParams.maxHtlcValueInFlightMsat, @@ -196,12 +195,12 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu htlcMinimumMsat = localParams.htlcMinimumMsat, toSelfDelay = localParams.toSelfDelay, maxAcceptedHtlcs = localParams.maxAcceptedHtlcs, - fundingPubkey = localParams.fundingPrivKey.publicKey, - revocationBasepoint = localParams.revocationBasepoint, - paymentBasepoint = localParams.paymentBasepoint, - delayedPaymentBasepoint = localParams.delayedPaymentBasepoint, - htlcBasepoint = localParams.htlcBasepoint, - firstPerCommitmentPoint = firstPerCommitmentPoint) + 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)) val remoteParams = RemoteParams( nodeId = remoteNodeId, dustLimitSatoshis = open.dustLimitSatoshis, @@ -254,7 +253,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu globalFeatures = remoteInit.globalFeatures, localFeatures = remoteInit.localFeatures) log.debug(s"remote params: $remoteParams") - val localFundingPubkey = localParams.fundingPrivKey.publicKey + 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) goto(WAIT_FOR_FUNDING_INTERNAL) using DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, accept.firstPerCommitmentPoint, open) @@ -276,9 +275,9 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu when(WAIT_FOR_FUNDING_INTERNAL)(handleExceptions { case Event(MakeFundingTxResponse(fundingTx, fundingTxOutputIndex), data@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(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.maxFeerateMismatch) + 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 = Transactions.sign(remoteCommitTx, localParams.fundingPrivKey) + val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) // signature of their initial commitment tx that pays remote pushMsat val fundingCreated = FundingCreated( temporaryChannelId = temporaryChannelId, @@ -315,11 +314,11 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu when(WAIT_FOR_FUNDING_CREATED)(handleExceptions { case Event(FundingCreated(_, fundingTxHash, fundingTxOutputIndex, remoteSig), DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, remoteFirstPerCommitmentPoint, channelFlags, _)) => // they fund the channel with their funding tx, so the money is theirs (but we are paid pushMsat) - val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(temporaryChannelId, localParams, remoteParams, fundingSatoshis: Long, pushMsat, initialFeeratePerKw, fundingTxHash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.maxFeerateMismatch) + 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 = Transactions.sign(localCommitTx, localParams.fundingPrivKey) - val signedLocalCommitTx = Transactions.addSigs(localCommitTx, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig) + val localSigOfLocalTx = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) + val signedLocalCommitTx = Transactions.addSigs(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig) Transactions.checkSpendable(signedLocalCommitTx) match { case Failure(cause) => log.error(cause, "their FundingCreated message contains an invalid signature") @@ -328,7 +327,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu // we haven't anything at stake yet, we can just stop goto(CLOSED) sending error case Success(_) => - val localSigOfRemoteTx = Transactions.sign(remoteCommitTx, localParams.fundingPrivKey) + val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) val channelId = toLongId(fundingTxHash, fundingTxOutputIndex) // watch the funding tx transaction val commitInput = localCommitTx.input @@ -363,8 +362,8 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu when(WAIT_FOR_FUNDING_SIGNED)(handleExceptions { case Event(FundingSigned(_, remoteSig), DATA_WAIT_FOR_FUNDING_SIGNED(channelId, localParams, remoteParams, fundingTx, localSpec, localCommitTx, remoteCommit, channelFlags, fundingCreated)) => // we make sure that their sig checks out and that our first commit tx is spendable - val localSigOfLocalTx = Transactions.sign(localCommitTx, localParams.fundingPrivKey) - val signedLocalCommitTx = Transactions.addSigs(localCommitTx, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig) + val localSigOfLocalTx = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) + val signedLocalCommitTx = Transactions.addSigs(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig) Transactions.checkSpendable(signedLocalCommitTx) match { case Failure(cause) => log.error(cause, "their FundingSigned message contains an invalid signature") @@ -425,7 +424,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu 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 = Generators.perCommitPoint(commitments.localParams.shaSeed, 1) + val nextPerCommitmentPoint = keyManager.commitmentPoint(commitments.localParams.channelKeyPath, 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 @@ -590,7 +589,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu log.debug("ignoring CMD_SIGN (nothing to sign)") stay case Right(_) => - Try(Commitments.sendCommit(d.commitments)) match { + Try(Commitments.sendCommit(d.commitments, keyManager)) match { case Success((commitments1, commit)) => log.debug(s"sending a new sig, spec:\n${Commitments.specs2String(commitments1)}") commitments1.localChanges.signed.collect { @@ -609,7 +608,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu } case Event(commit: CommitSig, d: DATA_NORMAL) => - Try(Commitments.receiveCommit(d.commitments, commit)) match { + Try(Commitments.receiveCommit(d.commitments, commit, keyManager)) match { case Success((commitments1, revocation)) => log.debug(s"received a new sig, spec:\n${Commitments.specs2String(commitments1)}") if (Commitments.localHasChanges(commitments1)) { @@ -713,7 +712,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu // there are no pending signed htlcs, let's go directly to NEGOTIATING if (d.commitments.localParams.isFunder) { // we are funder, need to initiate the negotiation by sending the first closing_signed - val (closingTx, closingSigned) = Closing.makeFirstClosingTx(d.commitments, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey) + val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey) goto(NEGOTIATING) using store(DATA_NEGOTIATING(d.commitments, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending sendList :+ closingSigned } else { // we are fundee, will wait for their closing_signed @@ -769,7 +768,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu require(d.shortChannelId == remoteAnnSigs.shortChannelId, s"shortChannelId mismatch: local=${d.shortChannelId.toHexString} remote=${remoteAnnSigs.shortChannelId.toHexString}") log.info(s"announcing channelId=${d.channelId} on the network with shortId=${d.shortChannelId.toHexString}") import d.commitments.{localParams, remoteParams} - val channelAnn = Announcements.makeChannelAnnouncement(nodeParams.chainHash, localAnnSigs.shortChannelId, localParams.nodeId, remoteParams.nodeId, localParams.fundingPrivKey.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(localParams.channelKeyPath).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(_) => @@ -899,7 +898,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu log.debug("ignoring CMD_SIGN (nothing to sign)") stay case Right(_) => - Try(Commitments.sendCommit(d.commitments)) match { + Try(Commitments.sendCommit(d.commitments, keyManager)) match { case Success((commitments1, commit)) => log.debug(s"sending a new sig, spec:\n${Commitments.specs2String(commitments1)}") commitments1.localChanges.signed.collect { @@ -917,7 +916,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu } case Event(commit: CommitSig, d@DATA_SHUTDOWN(_, localShutdown, remoteShutdown)) => - Try(Commitments.receiveCommit(d.commitments, commit)) map { + Try(Commitments.receiveCommit(d.commitments, commit, keyManager)) map { case (commitments1, revocation) => // we always reply with a revocation log.debug(s"received a new sig:\n${Commitments.specs2String(commitments1)}") @@ -927,7 +926,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Success((commitments1, revocation)) if commitments1.hasNoPendingHtlcs => if (d.commitments.localParams.isFunder) { // we are funder, need to initiate the negotiation by sending the first closing_signed - val (closingTx, closingSigned) = Closing.makeFirstClosingTx(commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey) + val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey) goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending revocation :: closingSigned :: Nil } else { // we are fundee, will wait for their closing_signed @@ -950,7 +949,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu log.debug(s"received a new rev, switching to NEGOTIATING spec:\n${Commitments.specs2String(commitments1)}") if (d.commitments.localParams.isFunder) { // we are funder, need to initiate the negotiation by sending the first closing_signed - val (closingTx, closingSigned) = Closing.makeFirstClosingTx(commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey) + val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey) goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending closingSigned } else { // we are fundee, will wait for their closing_signed @@ -1000,7 +999,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu when(NEGOTIATING)(handleExceptions { case Event(c@ClosingSigned(_, remoteClosingFee, remoteSig), d: DATA_NEGOTIATING) => log.info(s"received closingFeeSatoshis=$remoteClosingFee") - Closing.checkClosingSignature(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, Satoshi(remoteClosingFee), remoteSig) match { + Closing.checkClosingSignature(keyManager, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, Satoshi(remoteClosingFee), remoteSig) match { case Success(signedClosingTx) if Some(remoteClosingFee) == d.closingTxProposed.last.lastOption.map(_.localClosingSigned.feeSatoshis) || d.closingTxProposed.flatten.size >= MAX_NEGOTIATION_ITERATIONS => // we close when we converge or when there were too many iterations handleMutualClose(signedClosingTx, Left(d.copy(bestUnpublishedClosingTx_opt = Some(signedClosingTx)))) @@ -1010,7 +1009,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu val nextClosingFee = Closing.nextClosingFee( localClosingFee = lastLocalClosingFee.getOrElse(Closing.firstClosingFee(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey)), remoteClosingFee = Satoshi(remoteClosingFee)) - val (closingTx, closingSigned) = Closing.makeClosingTx(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nextClosingFee) + val (closingTx, closingSigned) = Closing.makeClosingTx(keyManager, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, nextClosingFee) if (Some(nextClosingFee) == lastLocalClosingFee) { // next computed fee is the same than the one we previously sent (probably because of rounding), let's close now handleMutualClose(signedClosingTx, Left(d.copy(bestUnpublishedClosingTx_opt = Some(signedClosingTx)))) @@ -1053,19 +1052,19 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu log.info(s"got valid payment preimage, recalculating transactions to redeem the corresponding htlc on-chain") val localCommitPublished1 = d.localCommitPublished.map { case localCommitPublished => - val localCommitPublished1 = Helpers.Closing.claimCurrentLocalCommitTxOutputs(commitments1, localCommitPublished.commitTx) + val localCommitPublished1 = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, commitments1, localCommitPublished.commitTx) doPublish(localCommitPublished1) localCommitPublished1 } val remoteCommitPublished1 = d.remoteCommitPublished.map { case remoteCommitPublished => - val remoteCommitPublished1 = Helpers.Closing.claimRemoteCommitTxOutputs(commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx) + val remoteCommitPublished1 = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx) doPublish(remoteCommitPublished1) remoteCommitPublished1 } val nextRemoteCommitPublished1 = d.nextRemoteCommitPublished.map { case remoteCommitPublished => - val remoteCommitPublished1 = Helpers.Closing.claimRemoteCommitTxOutputs(commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx) + val remoteCommitPublished1 = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx) doPublish(remoteCommitPublished1) remoteCommitPublished1 } @@ -1136,7 +1135,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu } // then let's see if any of the possible close scenarii can be considered done val mutualCloseDone = d.mutualClosePublished.exists(_.txid == tx.txid) // this case is trivial, in a mutual close scenario we only need to make sure that one of the closing txes is confirmed - val localCommitDone = localCommitPublished1.map(Closing.isLocalCommitDone(_)).getOrElse(false) + val localCommitDone = localCommitPublished1.map(Closing.isLocalCommitDone(_)).getOrElse(false) val remoteCommitDone = remoteCommitPublished1.map(Closing.isRemoteCommitDone(_)).getOrElse(false) val nextRemoteCommitDone = nextRemoteCommitPublished1.map(Closing.isRemoteCommitDone(_)).getOrElse(false) val futureRemoteCommitDone = futureRemoteCommitPublished1.map(Closing.isRemoteCommitDone(_)).getOrElse(false) @@ -1212,7 +1211,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu forwarder ! r val yourLastPerCommitmentSecret = d.commitments.remotePerCommitmentSecrets.lastIndex.flatMap(d.commitments.remotePerCommitmentSecrets.getHash).getOrElse(Sphinx zeroes 32) - val myCurrentPerCommitmentPoint = Generators.perCommitPoint(d.commitments.localParams.shaSeed, d.commitments.localCommit.index) + val myCurrentPerCommitmentPoint = keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, d.commitments.localCommit.index) val channelReestablish = ChannelReestablish( channelId = d.channelId, @@ -1252,7 +1251,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu case Event(_: ChannelReestablish, d: DATA_WAIT_FOR_FUNDING_LOCKED) => log.debug(s"re-sending fundingLocked") - val nextPerCommitmentPoint = Generators.perCommitPoint(d.commitments.localParams.shaSeed, 1) + val nextPerCommitmentPoint = keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, 1) val fundingLocked = FundingLocked(d.commitments.channelId, nextPerCommitmentPoint) goto(WAIT_FOR_FUNDING_LOCKED) sending fundingLocked @@ -1260,7 +1259,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu if d.commitments.localCommit.index < 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 (Generators.perCommitSecret(d.commitments.localParams.shaSeed, nextRemoteRevocationNumber - 1) == yourLastPerCommitmentSecret) { + if (keyManager.commitmentSecret(d.commitments.localParams.channelKeyPath, 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 @@ -1276,7 +1275,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu 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 = Generators.perCommitPoint(d.commitments.localParams.shaSeed, 1) + val nextPerCommitmentPoint = keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, 1) val fundingLocked = FundingLocked(d.commitments.channelId, nextPerCommitmentPoint) forwarder ! fundingLocked } @@ -1325,7 +1324,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu // note: in any case we still need to keep all previously sent closing_signed, because they may publish one of them if (d.commitments.localParams.isFunder) { // we could use the last closing_signed we sent, but network fees may have changed while we were offline so it is better to restart from scratch - val (closingTx, closingSigned) = Closing.makeFirstClosingTx(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey) + val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey) val closingTxProposed1 = d.closingTxProposed :+ List(ClosingTxProposed(closingTx.tx, closingSigned)) goto(NEGOTIATING) using store(d.copy(closingTxProposed = closingTxProposed1)) sending d.localShutdown :: closingSigned :: Nil } else { @@ -1543,7 +1542,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu } else { val commitTx = d.commitments.localCommit.publishableTxs.commitTx.tx - val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(d.commitments, commitTx) + val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, d.commitments, commitTx) doPublish(localCommitPublished) val nextData = d match { @@ -1614,7 +1613,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu log.warning(s"they published their current commit in txid=${commitTx.txid}") require(commitTx.txid == d.commitments.remoteCommit.txid, "txid mismatch") - val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(d.commitments, d.commitments.remoteCommit, commitTx) + val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, d.commitments, d.commitments.remoteCommit, commitTx) doPublish(remoteCommitPublished) val nextData = d match { @@ -1630,7 +1629,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu log.warning(s"they published their future commit (because we asked them to) in txid=${commitTx.txid}") // if we are in this state, then this field is defined val remotePerCommitmentPoint = d.remoteChannelReestablish.myCurrentPerCommitmentPoint.get - val remoteCommitPublished = Helpers.Closing.claimRemoteCommitMainOutput(d.commitments, remotePerCommitmentPoint, commitTx) + val remoteCommitPublished = Helpers.Closing.claimRemoteCommitMainOutput(keyManager, d.commitments, remotePerCommitmentPoint, commitTx) val nextData = DATA_CLOSING(d.commitments, Nil, futureRemoteCommitPublished = Some(remoteCommitPublished)) doPublish(remoteCommitPublished) @@ -1643,7 +1642,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu val remoteCommit = d.commitments.remoteNextCommitInfo.left.get.nextRemoteCommit require(commitTx.txid == remoteCommit.txid, "txid mismatch") - val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(d.commitments, remoteCommit, commitTx) + val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, d.commitments, remoteCommit, commitTx) doPublish(remoteCommitPublished) val nextData = d match { @@ -1675,7 +1674,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu def handleRemoteSpentOther(tx: Transaction, d: HasCommitments) = { log.warning(s"funding tx spent in txid=${tx.txid}") - Helpers.Closing.claimRevokedRemoteCommitTxOutputs(d.commitments, tx) match { + Helpers.Closing.claimRevokedRemoteCommitTxOutputs(keyManager, d.commitments, tx) match { case Some(revokedCommitPublished) => log.warning(s"txid=${tx.txid} was a revoked commitment, publishing the penalty tx") val exc = FundingTxSpent(d.channelId, tx) @@ -1721,7 +1720,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu // let's try to spend our current local tx val commitTx = d.commitments.localCommit.publishableTxs.commitTx.tx - val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(d.commitments, commitTx) + val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, d.commitments, commitTx) doPublish(localCommitPublished) goto(ERR_INFORMATION_LEAK) sending error @@ -1746,8 +1745,8 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu } 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 = Generators.perCommitSecret(commitments1.localParams.shaSeed, d.commitments.localCommit.index - 1) - val localNextPerCommitmentPoint = Generators.perCommitPoint(commitments1.localParams.shaSeed, d.commitments.localCommit.index + 1) + 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 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 a29d15166..e361a0ea8 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 @@ -2,10 +2,10 @@ package fr.acinq.eclair.channel import akka.actor.ActorRef import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, Scalar} -import fr.acinq.bitcoin.{BinaryData, OutPoint, Transaction} +import fr.acinq.bitcoin.{BinaryData, DeterministicWallet, OutPoint, Transaction} import fr.acinq.eclair.UInt64 -import fr.acinq.eclair.crypto.Sphinx -import fr.acinq.eclair.transactions.CommitmentSpec +import fr.acinq.eclair.crypto.{Sphinx} +import fr.acinq.eclair.transactions.{CommitmentSpec, Transactions} import fr.acinq.eclair.transactions.Transactions.CommitTx import fr.acinq.eclair.wire.{AcceptChannel, ChannelAnnouncement, ChannelReestablish, ChannelUpdate, ClosingSigned, FailureMessage, FundingCreated, FundingLocked, FundingSigned, Init, OpenChannel, Shutdown, UpdateAddHtlc} @@ -168,28 +168,17 @@ 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, dustLimitSatoshis: Long, maxHtlcValueInFlightMsat: UInt64, channelReserveSatoshis: Long, htlcMinimumMsat: Long, toSelfDelay: Int, maxAcceptedHtlcs: Int, - fundingPrivKey: PrivateKey, - revocationSecret: Scalar, - paymentKey: Scalar, - delayedPaymentKey: Scalar, - htlcKey: Scalar, - defaultFinalScriptPubKey: BinaryData, - shaSeed: BinaryData, isFunder: Boolean, + defaultFinalScriptPubKey: BinaryData, globalFeatures: BinaryData, - localFeatures: BinaryData) { - // precomputed for performance reasons - val paymentBasepoint = paymentKey.toPoint - val delayedPaymentBasepoint = delayedPaymentKey.toPoint - val revocationBasepoint = revocationSecret.toPoint - val htlcBasepoint = htlcKey.toPoint -} + localFeatures: BinaryData) 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 9a5c1ed05..8973c255e 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 @@ -3,8 +3,8 @@ package fr.acinq.eclair.channel import akka.event.LoggingAdapter import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, sha256} import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi, Transaction} -import fr.acinq.eclair.crypto.{Generators, ShaChain, Sphinx} -import fr.acinq.eclair.payment.{Origin, PaymentLifecycle} +import fr.acinq.eclair.crypto.{Generators, KeyManager, ShaChain, Sphinx} +import fr.acinq.eclair.payment.Origin import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire._ @@ -346,7 +346,7 @@ object Commitments { def revocationHash(seed: BinaryData, index: Long): BinaryData = Crypto.sha256(revocationPreimage(seed, index)) - def sendCommit(commitments: Commitments): (Commitments, CommitSig) = { + def sendCommit(commitments: Commitments, keyManager: KeyManager): (Commitments, CommitSig) = { import commitments._ commitments.remoteNextCommitInfo match { case Right(_) if !localHasChanges(commitments) => @@ -354,12 +354,11 @@ object Commitments { case Right(remoteNextPerCommitmentPoint) => // 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(remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, spec) - val sig = Transactions.sign(remoteCommitTx, localParams.fundingPrivKey) + val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(keyManager, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, spec) + val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index) - val htlcKey = Generators.derivePrivKey(localParams.htlcKey, remoteNextPerCommitmentPoint) - val htlcSigs = sortedHtlcTxs.map(Transactions.sign(_, htlcKey)) + val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), remoteNextPerCommitmentPoint)) // don't sign if they don't get paid val commitSig = CommitSig( @@ -378,7 +377,7 @@ object Commitments { } } - def receiveCommit(commitments: Commitments, commit: CommitSig)(implicit log: LoggingAdapter): (Commitments, RevokeAndAck) = { + def receiveCommit(commitments: Commitments, commit: CommitSig, keyManager: KeyManager)(implicit log: LoggingAdapter): (Commitments, RevokeAndAck) = { import commitments._ // they sent us a signature for *their* view of *our* next commit tx // so in terms of rev.hashes and indexes we have: @@ -397,14 +396,14 @@ 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 = Generators.perCommitPoint(localParams.shaSeed, commitments.localCommit.index + 1) - val (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeLocalTxs(localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, spec) - val sig = Transactions.sign(localCommitTx, localParams.fundingPrivKey) + val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 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)) // 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, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, sig, commit.signature) + val signedCommitTx = Transactions.addSigs(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, sig, commit.signature) if (Transactions.checkSpendable(signedCommitTx).isFailure) { throw InvalidCommitmentSignature(commitments.channelId, signedCommitTx.tx) } @@ -413,8 +412,7 @@ object Commitments { if (commit.htlcSignatures.size != sortedHtlcTxs.size) { throw new HtlcSigCountMismatch(commitments.channelId, sortedHtlcTxs.size, commit.htlcSignatures.size) } - val localHtlcKey = Generators.derivePrivKey(localParams.htlcKey, localPerCommitmentPoint) - val htlcSigs = sortedHtlcTxs.map(Transactions.sign(_, localHtlcKey)) + val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), localPerCommitmentPoint)) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint) // combine the sigs to make signed txes val htlcTxsAndSigs = (sortedHtlcTxs, htlcSigs, commit.htlcSignatures).zipped.toList.collect { @@ -432,8 +430,8 @@ object Commitments { } // we will send our revocation preimage + our next revocation hash - val localPerCommitmentSecret = Generators.perCommitSecret(localParams.shaSeed, commitments.localCommit.index) - val localNextPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, commitments.localCommit.index + 2) + val localPerCommitmentSecret = keyManager.commitmentSecret(localParams.channelKeyPath, commitments.localCommit.index) + val localNextPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index + 2) val revocation = RevokeAndAck( channelId = commitments.channelId, perCommitmentSecret = localPerCommitmentSecret, @@ -478,26 +476,26 @@ object Commitments { } } - def makeLocalTxs(commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: Point, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { - val localPaymentPubkey = Generators.derivePubKey(localParams.paymentBasepoint, localPerCommitmentPoint) - val localDelayedPaymentPubkey = Generators.derivePubKey(localParams.delayedPaymentBasepoint, localPerCommitmentPoint) - val localHtlcPubkey = Generators.derivePubKey(localParams.htlcBasepoint, localPerCommitmentPoint) + def makeLocalTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: Point, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { + val localPaymentPubkey = Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) + val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) + val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).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, localParams.paymentBasepoint, remoteParams.paymentBasepoint, localParams.isFunder, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) + 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 (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) (commitTx, htlcTimeoutTxs, htlcSuccessTxs) } - def makeRemoteTxs(commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: Point, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { - val localPaymentPubkey = Generators.derivePubKey(localParams.paymentBasepoint, remotePerCommitmentPoint) - val localHtlcPubkey = Generators.derivePubKey(localParams.htlcBasepoint, remotePerCommitmentPoint) + 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 remotePaymentPubkey = Generators.derivePubKey(remoteParams.paymentBasepoint, remotePerCommitmentPoint) val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint) - val remoteRevocationPubkey = Generators.revocationPubKey(localParams.revocationBasepoint, remotePerCommitmentPoint) - val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, localParams.paymentBasepoint, !localParams.isFunder, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) + 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 (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 988a5164f..6018d165b 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 @@ -1,12 +1,11 @@ package fr.acinq.eclair.channel import akka.event.LoggingAdapter -import fr.acinq.bitcoin.Crypto.{Point, PublicKey, Scalar, ripemd160, sha256} +import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, Scalar, ripemd160, sha256} import fr.acinq.bitcoin.Script._ import fr.acinq.bitcoin.{OutPoint, _} import fr.acinq.eclair.blockchain.EclairWallet -import fr.acinq.eclair.crypto.Generators -import fr.acinq.eclair.router.Announcements +import fr.acinq.eclair.crypto.{Generators, KeyManager} import fr.acinq.eclair.transactions.Scripts._ import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions._ @@ -97,7 +96,7 @@ object Helpers { def makeAnnouncementSignatures(nodeParams: NodeParams, commitments: Commitments, shortChannelId: Long) = { // TODO: empty features val features = BinaryData("") - val (localNodeSig, localBitcoinSig) = Announcements.signChannelAnnouncement(nodeParams.chainHash, shortChannelId, nodeParams.privateKey, commitments.remoteParams.nodeId, commitments.localParams.fundingPrivKey, commitments.remoteParams.fundingPubKey, features) + val (localNodeSig, localBitcoinSig) = nodeParams.keyManager.signChannelAnnouncement(commitments.localParams.channelKeyPath, nodeParams.chainHash, shortChannelId, commitments.remoteParams.nodeId, commitments.remoteParams.fundingPubKey, features) AnnouncementSignatures(commitments.channelId, shortChannelId, localNodeSig, localBitcoinSig) } @@ -130,7 +129,7 @@ object Helpers { * @param remoteFirstPerCommitmentPoint * @return (localSpec, localTx, remoteSpec, remoteTx, fundingTxOutput) */ - def makeFirstCommitTxs(temporaryChannelId: BinaryData, localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, fundingTxHash: BinaryData, fundingTxOutputIndex: Int, remoteFirstPerCommitmentPoint: Point, maxFeerateMismatch: Double): (CommitmentSpec, CommitTx, CommitmentSpec, CommitTx) = { + def makeFirstCommitTxs(keyManager: KeyManager, temporaryChannelId: BinaryData, localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Long, pushMsat: Long, initialFeeratePerKw: Long, fundingTxHash: BinaryData, fundingTxOutputIndex: Int, remoteFirstPerCommitmentPoint: Point, maxFeerateMismatch: Double): (CommitmentSpec, CommitTx, CommitmentSpec, CommitTx) = { val toLocalMsat = if (localParams.isFunder) fundingSatoshis * 1000 - pushMsat else pushMsat val toRemoteMsat = if (localParams.isFunder) pushMsat else fundingSatoshis * 1000 - pushMsat @@ -147,10 +146,10 @@ object Helpers { } } - val commitmentInput = makeFundingInputInfo(fundingTxHash, fundingTxOutputIndex, Satoshi(fundingSatoshis), localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey) - val localPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 0) - val (localCommitTx, _, _) = Commitments.makeLocalTxs(0, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, localSpec) - val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, remoteSpec) + val commitmentInput = makeFundingInputInfo(fundingTxHash, fundingTxOutputIndex, Satoshi(fundingSatoshis), keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey) + val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 0) + val (localCommitTx, _, _) = Commitments.makeLocalTxs(keyManager, 0, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, localSpec) + val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, 0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, remoteSpec) (localSpec, localCommitTx, remoteSpec, remoteCommitTx) } @@ -159,6 +158,9 @@ object Helpers { object Closing { + // used only to compute tx weights and estimate fees + lazy val dummyPublicKey = PrivateKey(BinaryData("01" * 32), true).publicKey + def isValidFinalScriptPubkey(scriptPubKey: BinaryData): Boolean = { Try(Script.parse(scriptPubKey)) match { case Success(OP_DUP :: OP_HASH160 :: OP_PUSHDATA(pubkeyHash, _) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) if pubkeyHash.size == 20 => true @@ -173,7 +175,7 @@ object Helpers { import commitments._ // this is just to estimate the weight, it depends on size of the pubkey scripts val dummyClosingTx = Transactions.makeClosingTx(commitInput, localScriptPubkey, remoteScriptPubkey, localParams.isFunder, Satoshi(0), Satoshi(0), localCommit.spec) - val closingWeight = Transaction.weight(Transactions.addSigs(dummyClosingTx, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, "aa" * 71, "bb" * 71).tx) + val closingWeight = Transaction.weight(Transactions.addSigs(dummyClosingTx, dummyPublicKey, remoteParams.fundingPubKey, "aa" * 71, "bb" * 71).tx) // no need to use a very high fee here, so we target 6 blocks; also, we "MUST set fee_satoshis less than or equal to the base fee of the final commitment transaction" val feeratePerKw = Math.min(Globals.feeratesPerKw.get.blocks_6, commitments.localCommit.spec.feeratePerKw) log.info(s"using feeratePerKw=$feeratePerKw for initial closing tx") @@ -182,12 +184,12 @@ object Helpers { def nextClosingFee(localClosingFee: Satoshi, remoteClosingFee: Satoshi): Satoshi = ((localClosingFee + remoteClosingFee) / 4) * 2 - def makeFirstClosingTx(commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData)(implicit log: LoggingAdapter): (ClosingTx, ClosingSigned) = { + def makeFirstClosingTx(keyManager: KeyManager, commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData)(implicit log: LoggingAdapter): (ClosingTx, ClosingSigned) = { val closingFee = firstClosingFee(commitments, localScriptPubkey, remoteScriptPubkey) - makeClosingTx(commitments, localScriptPubkey, remoteScriptPubkey, closingFee) + makeClosingTx(keyManager, commitments, localScriptPubkey, remoteScriptPubkey, closingFee) } - def makeClosingTx(commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData, closingFee: Satoshi)(implicit log: LoggingAdapter): (ClosingTx, ClosingSigned) = { + def makeClosingTx(keyManager: KeyManager, commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData, closingFee: Satoshi)(implicit log: LoggingAdapter): (ClosingTx, ClosingSigned) = { import commitments._ require(isValidFinalScriptPubkey(localScriptPubkey), "invalid localScriptPubkey") require(isValidFinalScriptPubkey(remoteScriptPubkey), "invalid remoteScriptPubkey") @@ -195,22 +197,22 @@ 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 = Transactions.sign(closingTx, commitments.localParams.fundingPrivKey) + val localClosingSig = keyManager.sign(closingTx, keyManager.fundingPublicKey(commitments.localParams.channelKeyPath)) 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}}") (closingTx, closingSigned) } - def checkClosingSignature(commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData, remoteClosingFee: Satoshi, remoteClosingSig: BinaryData)(implicit log: LoggingAdapter): Try[Transaction] = { + def checkClosingSignature(keyManager: KeyManager, commitments: Commitments, localScriptPubkey: BinaryData, remoteScriptPubkey: BinaryData, remoteClosingFee: Satoshi, remoteClosingSig: BinaryData)(implicit log: LoggingAdapter): Try[Transaction] = { import commitments._ val lastCommitFeeSatoshi = commitments.commitInput.txOut.amount.amount - commitments.localCommit.publishableTxs.commitTx.tx.txOut.map(_.amount.amount).sum if (remoteClosingFee.amount > lastCommitFeeSatoshi) { log.error(s"remote proposed a commit fee higher than the last commitment fee: remoteClosingFeeSatoshi=${remoteClosingFee.amount} lastCommitFeeSatoshi=$lastCommitFeeSatoshi") throw new InvalidCloseFee(commitments.channelId, remoteClosingFee.amount) } - val (closingTx, closingSigned) = makeClosingTx(commitments, localScriptPubkey, remoteScriptPubkey, remoteClosingFee) - val signedClosingTx = Transactions.addSigs(closingTx, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, closingSigned.signature, remoteClosingSig) + 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) Transactions.checkSpendable(signedClosingTx).map(x => signedClosingTx.tx).recover { case _ => throw InvalidCloseSignature(commitments.channelId, signedClosingTx.tx) } } @@ -236,21 +238,21 @@ object Helpers { * @param commitments our commitment data, which include payment preimages * @return a list of transactions (one per HTLC that we can claim) */ - def claimCurrentLocalCommitTxOutputs(commitments: Commitments, tx: Transaction)(implicit log: LoggingAdapter): LocalCommitPublished = { + def claimCurrentLocalCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction)(implicit log: LoggingAdapter): LocalCommitPublished = { import commitments._ require(localCommit.publishableTxs.commitTx.tx.txid == tx.txid, "txid mismatch, provided tx is not the current local commit tx") - val localPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, commitments.localCommit.index.toInt) + val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index.toInt) val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) - val localDelayedPrivkey = Generators.derivePrivKey(localParams.delayedPaymentKey, localPerCommitmentPoint) + val localDelayedPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(localParams.channelKeyPath).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 // 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, localDelayedPrivkey.publicKey, localParams.defaultFinalScriptPubKey, feeratePerKwDelayed) - val sig = Transactions.sign(claimDelayed, localDelayedPrivkey) + 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) Transactions.addSigs(claimDelayed, sig) }) @@ -278,8 +280,14 @@ object Helpers { val htlcDelayedTxes = htlcTxes.flatMap { txinfo: TransactionWithInputInfo => generateTx("claim-delayed-output")(Try { // TODO: we should use the current fee rate, not the initial fee rate that we get from localParams - val claimDelayed = Transactions.makeClaimDelayedOutputTx(txinfo.tx, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPrivkey.publicKey, localParams.defaultFinalScriptPubKey, feeratePerKwDelayed) - val sig = Transactions.sign(claimDelayed, localDelayedPrivkey) + val claimDelayed = Transactions.makeClaimDelayedOutputTx( + txinfo.tx, + Satoshi(localParams.dustLimitSatoshis), + localRevocationPubkey, + remoteParams.toSelfDelay, + localDelayedPubkey, + localParams.defaultFinalScriptPubKey, feeratePerKwDelayed) + val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(localParams.channelKeyPath), localPerCommitmentPoint) Transactions.addSigs(claimDelayed, sig) }) } @@ -306,15 +314,18 @@ object Helpers { * @param tx the remote commitment transaction that has just been published * @return a list of transactions (one per HTLC that we can claim) */ - def claimRemoteCommitTxOutputs(commitments: Commitments, remoteCommit: RemoteCommit, tx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = { + def claimRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, remoteCommit: RemoteCommit, tx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = { import commitments.{commitInput, localParams, remoteParams} require(remoteCommit.txid == tx.txid, "txid mismatch, provided tx is not the current remote commit tx") - val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec) + 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 localHtlcPrivkey = Generators.derivePrivKey(localParams.htlcKey, remoteCommit.remotePerCommitmentPoint) + val localPaymentPubkey = Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint) + val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remoteCommit.remotePerCommitmentPoint) - val remoteRevocationPubkey = Generators.revocationPubKey(localParams.revocationBasepoint, remoteCommit.remotePerCommitmentPoint) + val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index.toInt) + val localRevocationPubKey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) + val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).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.block_1 @@ -327,8 +338,8 @@ object Helpers { // incoming htlc for which we have the preimage: we spend it directly case DirectedHtlc(OUT, add: UpdateAddHtlc) if preimages.exists(r => sha256(r) == add.paymentHash) => generateTx("claim-htlc-success")(Try { val preimage = preimages.find(r => sha256(r) == add.paymentHash).get - val tx = Transactions.makeClaimHtlcSuccessTx(remoteCommitTx.tx, Satoshi(localParams.dustLimitSatoshis), localHtlcPrivkey.publicKey, remoteHtlcPubkey, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, add, feeratePerKwHtlc) - val sig = Transactions.sign(tx, localHtlcPrivkey) + val tx = Transactions.makeClaimHtlcSuccessTx(remoteCommitTx.tx, Satoshi(localParams.dustLimitSatoshis), localHtlcPubkey, remoteHtlcPubkey, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, add, feeratePerKwHtlc) + val sig = keyManager.sign(tx, keyManager.htlcPoint(localParams.channelKeyPath), remoteCommit.remotePerCommitmentPoint) Transactions.addSigs(tx, sig, preimage) }) @@ -336,8 +347,8 @@ object Helpers { // outgoing htlc: they may or may not have the preimage, the only thing to do is try to get back our funds after timeout case DirectedHtlc(IN, add: UpdateAddHtlc) => generateTx("claim-htlc-timeout")(Try { - val tx = Transactions.makeClaimHtlcTimeoutTx(remoteCommitTx.tx, Satoshi(localParams.dustLimitSatoshis), localHtlcPrivkey.publicKey, remoteHtlcPubkey, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, add, feeratePerKwHtlc) - val sig = Transactions.sign(tx, localHtlcPrivkey) + val tx = Transactions.makeClaimHtlcTimeoutTx(remoteCommitTx.tx, Satoshi(localParams.dustLimitSatoshis), localHtlcPubkey, remoteHtlcPubkey, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, add, feeratePerKwHtlc) + val sig = keyManager.sign(tx, keyManager.htlcPoint(localParams.channelKeyPath), remoteCommit.remotePerCommitmentPoint) Transactions.addSigs(tx, sig) }) }.toSeq.flatten @@ -345,7 +356,7 @@ object Helpers { // OPTIONAL: let's check transactions are actually spendable //require(txes.forall(Transactions.checkSpendable(_).isSuccess), "the tx we produced are not spendable!") - claimRemoteCommitMainOutput(commitments, remoteCommit.remotePerCommitmentPoint, tx).copy( + claimRemoteCommitMainOutput(keyManager, commitments, remoteCommit.remotePerCommitmentPoint, tx).copy( claimHtlcSuccessTxs = txes.toList.collect { case c: ClaimHtlcSuccessTx => c.tx }, claimHtlcTimeoutTxs = txes.toList.collect { case c: ClaimHtlcTimeoutTx => c.tx } ) @@ -362,17 +373,17 @@ object Helpers { * @param tx the remote commitment transaction that has just been published * @return a list of transactions (one per HTLC that we can claim) */ - def claimRemoteCommitMainOutput(commitments: Commitments, remotePerCommitmentPoint: Point, tx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = { - val localPaymentPrivkey = Generators.derivePrivKey(commitments.localParams.paymentKey, remotePerCommitmentPoint) + 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) // 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 val mainTx = generateTx("claim-p2wpkh-output")(Try { val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, Satoshi(commitments.localParams.dustLimitSatoshis), - localPaymentPrivkey.publicKey, commitments.localParams.defaultFinalScriptPubKey, feeratePerKwMain) - val sig = Transactions.sign(claimMain, localPaymentPrivkey) - Transactions.addSigs(claimMain, localPaymentPrivkey.publicKey, sig) + localPubkey, commitments.localParams.defaultFinalScriptPubKey, feeratePerKwMain) + val sig = keyManager.sign(claimMain, keyManager.paymentPoint(commitments.localParams.channelKeyPath), remotePerCommitmentPoint) + Transactions.addSigs(claimMain, localPubkey, sig) }) RemoteCommitPublished( @@ -393,12 +404,12 @@ object Helpers { * * @return a [[RevokedCommitPublished]] object containing penalty transactions if the tx is a revoked commitment */ - def claimRevokedRemoteCommitTxOutputs(commitments: Commitments, tx: Transaction)(implicit log: LoggingAdapter): Option[RevokedCommitPublished] = { + def claimRevokedRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction)(implicit log: LoggingAdapter): Option[RevokedCommitPublished] = { import commitments._ 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, localParams.paymentBasepoint) + val txnumber = Transactions.obscuredCommitTxNumber(obscuredTxNumber, !localParams.isFunder, remoteParams.paymentBasepoint, keyManager.paymentPoint(localParams.channelKeyPath).publicKey) require(txnumber <= 0xffffffffffffL, "txnumber must be lesser than 48 bits long") log.warning(s"counterparty has published revoked commit txnumber=$txnumber") // now we know what commit number this tx is referring to, we can derive the commitment point from the shachain @@ -408,8 +419,8 @@ object Helpers { val remotePerCommitmentPoint = remotePerCommitmentSecret.toPoint val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) - val remoteRevocationPrivkey = Generators.revocationPrivKey(localParams.revocationSecret, remotePerCommitmentSecret) - val localPrivkey = Generators.derivePrivKey(localParams.paymentKey, remotePerCommitmentPoint) + val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentSecret.toPoint) + val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).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 @@ -418,16 +429,16 @@ 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), localPrivkey.publicKey, localParams.defaultFinalScriptPubKey, feeratePerKwMain) - val sig = Transactions.sign(claimMain, localPrivkey) - Transactions.addSigs(claimMain, localPrivkey.publicKey, sig) + val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, Satoshi(localParams.dustLimitSatoshis), localPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwMain) + val sig = keyManager.sign(claimMain, keyManager.paymentPoint(localParams.channelKeyPath), remotePerCommitmentPoint) + Transactions.addSigs(claimMain, localPubkey, sig) }) // then we punish them by stealing their main output val mainPenaltyTx = generateTx("main-penalty")(Try { // TODO: we should use the current fee rate, not the initial fee rate that we get from localParams - val txinfo = Transactions.makeMainPenaltyTx(tx, Satoshi(localParams.dustLimitSatoshis), remoteRevocationPrivkey.publicKey, localParams.defaultFinalScriptPubKey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, feeratePerKwPenalty) - val sig = Transactions.sign(txinfo, remoteRevocationPrivkey) + 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) Transactions.addSigs(txinfo, sig) }) 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 new file mode 100644 index 000000000..5328c1a81 --- /dev/null +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/KeyManager.scala @@ -0,0 +1,59 @@ +package fr.acinq.eclair.crypto + +import fr.acinq.bitcoin.{BinaryData, Crypto, DeterministicWallet} +import fr.acinq.bitcoin.Crypto.{Point, PublicKey, Scalar} +import fr.acinq.bitcoin.DeterministicWallet.ExtendedPublicKey +import fr.acinq.eclair.transactions.Transactions.TransactionWithInputInfo + +trait KeyManager { + def nodeKey: DeterministicWallet.ExtendedPrivateKey + + def nodeId: PublicKey + + def fundingPublicKey(channelKeyPath: DeterministicWallet.KeyPath): ExtendedPublicKey + + def revocationPoint(channelKeyPath: DeterministicWallet.KeyPath): ExtendedPublicKey + + def paymentPoint(channelKeyPath: DeterministicWallet.KeyPath): ExtendedPublicKey + + def delayedPaymentPoint(channelKeyPath: DeterministicWallet.KeyPath): ExtendedPublicKey + + def htlcPoint(channelKeyPath: DeterministicWallet.KeyPath): ExtendedPublicKey + + def commitmentSecret(channelKeyPath: DeterministicWallet.KeyPath, index: Long): Crypto.Scalar + + def commitmentPoint(channelKeyPath: DeterministicWallet.KeyPath, index: Long): Crypto.Point + + /** + * + * @param tx input transaction + * @param publicKey extended public key + * @return a signature generated with the private key that matches the input + * extended public key + */ + def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey): BinaryData + + /** + * This method is used to spend funds send to htlc keys/delayed keys + * + * @param tx input transaction + * @param publicKey extended public key + * @param remotePoint remote point + * @return a signature generated with a private key generated from the input keys's matching + * private key and the remote point. + */ + def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remotePoint: Point): BinaryData + + /** + * Ths method is used to spend revoked transactions, with the corresponding revocation key + * + * @param tx input transaction + * @param publicKey extended public key + * @param remoteSecret remote secret + * @return a signature generated with a private key generated from the input keys's matching + * private key and the remote secret. + */ + def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remoteSecret: Scalar): BinaryData + + def signChannelAnnouncement(channelKeyPath: DeterministicWallet.KeyPath, chainHash: BinaryData, shortChannelId: Long, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: BinaryData): (BinaryData, BinaryData) +} 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 new file mode 100644 index 000000000..63fe4dafd --- /dev/null +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala @@ -0,0 +1,120 @@ +package fr.acinq.eclair.crypto + +import com.google.common.cache.{CacheBuilder, CacheLoader, LoadingCache} +import fr.acinq.bitcoin.Crypto.{Point, PublicKey, Scalar} +import fr.acinq.bitcoin.DeterministicWallet.{derivePrivateKey, _} +import fr.acinq.bitcoin.{BinaryData, Crypto, DeterministicWallet} +import fr.acinq.eclair.router.Announcements +import fr.acinq.eclair.transactions.Transactions +import fr.acinq.eclair.transactions.Transactions.TransactionWithInputInfo + +object LocalKeyManager { + val channelKeyBasePath = DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(0) :: Nil + val nodeKeyBasePath = DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(1) :: Nil +} + +/** + * This class manages secrets and private keys. + * It exports points and public keys, and provides signing methods + * + * @param seed seed from which keys will be derived + */ +class LocalKeyManager(seed: BinaryData) extends KeyManager { + private val master = DeterministicWallet.generate(seed) + + override val nodeKey = DeterministicWallet.derivePrivateKey(master, LocalKeyManager.nodeKeyBasePath) + override val nodeId = nodeKey.publicKey + + private val privateKeys: LoadingCache[KeyPath, ExtendedPrivateKey] = CacheBuilder.newBuilder() + .maximumSize(6 * 200) // 6 keys per channel * 200 channels + .build[KeyPath, ExtendedPrivateKey](new CacheLoader[KeyPath, ExtendedPrivateKey] { + override def load(keyPath: KeyPath): ExtendedPrivateKey = derivePrivateKey(master, keyPath) + }) + + private val publicKeys: LoadingCache[KeyPath, ExtendedPublicKey] = CacheBuilder.newBuilder() + .maximumSize(6 * 200) // 6 keys per channel * 200 channels + .build[KeyPath, ExtendedPublicKey](new CacheLoader[KeyPath, ExtendedPublicKey] { + override def load(keyPath: KeyPath): ExtendedPublicKey = publicKey(privateKeys.get(keyPath)) + }) + + private def internalKeyPath(channelKeyPath: DeterministicWallet.KeyPath, index: Long): List[Long] = (LocalKeyManager.channelKeyBasePath ++ channelKeyPath.path) :+ index + + private def fundingPrivateKey(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, hardened(0))) + + private def revocationSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, hardened(1))) + + private def paymentSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, hardened(2))) + + private def delayedPaymentSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, hardened(3))) + + private def htlcSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, hardened(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 revocationPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(1))) + + override def paymentPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(2))) + + override def delayedPaymentPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(3))) + + 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) + + override def commitmentPoint(channelKeyPath: DeterministicWallet.KeyPath, index: Long) = Generators.perCommitPoint(shaSeed(channelKeyPath), index) + + /** + * + * @param tx input transaction + * @param publicKey extended public key + * @return a signature generated with the private key that matches the input + * extended public key + */ + def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey): BinaryData = { + val privateKey = privateKeys.get(publicKey.path) + Transactions.sign(tx, privateKey.privateKey) + } + + /** + * This method is used to spend funds send to htlc keys/delayed keys + * + * @param tx input transaction + * @param publicKey extended public key + * @param remotePoint remote point + * @return a signature generated with a private key generated from the input keys's matching + * private key and the remote point. + */ + def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remotePoint: Point): BinaryData = { + val privateKey = privateKeys.get(publicKey.path) + val currentKey = Generators.derivePrivKey(privateKey.privateKey, remotePoint) + Transactions.sign(tx, currentKey) + } + + /** + * Ths method is used to spend revoked transactions, with the corresponding revocation key + * + * @param tx input transaction + * @param publicKey extended public key + * @param remoteSecret remote secret + * @return a signature generated with a private key generated from the input keys's matching + * private key and the remote secret. + */ + def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remoteSecret: Scalar): BinaryData = { + val privateKey = privateKeys.get(publicKey.path) + val currentKey = Generators.revocationPrivKey(privateKey.privateKey, remoteSecret) + Transactions.sign(tx, currentKey) + } + + override def signChannelAnnouncement(channelKeyPath: DeterministicWallet.KeyPath, chainHash: BinaryData, shortChannelId: Long, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: BinaryData): (BinaryData, BinaryData) = { + val witness = if (Announcements.isNode1(nodeId.toBin, remoteNodeId.toBin)) { + Announcements.channelAnnouncementWitnessEncode(chainHash, shortChannelId, nodeId, remoteNodeId, fundingPublicKey(channelKeyPath).publicKey, remoteFundingKey, features) + } else { + Announcements.channelAnnouncementWitnessEncode(chainHash, shortChannelId, remoteNodeId, nodeId, remoteFundingKey, fundingPublicKey(channelKeyPath).publicKey, features) + } + val nodeSig = Crypto.encodeSignature(Crypto.sign(witness, nodeKey.privateKey)) :+ 1.toByte + val bitcoinSig = Crypto.encodeSignature(Crypto.sign(witness, fundingPrivateKey(channelKeyPath).privateKey)) :+ 1.toByte + (nodeSig, bitcoinSig) + } +} 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 717ac9a49..b231692a4 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 @@ -1,18 +1,19 @@ package fr.acinq.eclair.io +import java.io.ByteArrayInputStream import java.net.InetSocketAddress +import java.nio.ByteOrder import akka.actor.{ActorRef, FSM, OneForOneStrategy, PoisonPill, Props, Status, SupervisorStrategy, Terminated} -import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} -import fr.acinq.bitcoin.{BinaryData, Crypto, DeterministicWallet, MilliSatoshi, Satoshi} -import fr.acinq.eclair._ +import fr.acinq.bitcoin.Crypto.PublicKey +import fr.acinq.bitcoin.{BinaryData, DeterministicWallet, MilliSatoshi, Protocol, Satoshi} import fr.acinq.eclair.blockchain.EclairWallet import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.TransportHandler import fr.acinq.eclair.crypto.TransportHandler.Listener import fr.acinq.eclair.router._ -import fr.acinq.eclair.wire -import fr.acinq.eclair.wire.LightningMessage +import fr.acinq.eclair.{wire, _} +import fr.acinq.eclair.wire._ import scala.concurrent.duration._ import scala.util.Random @@ -358,31 +359,27 @@ object Peer { // @formatter:on - - def generateKey(nodeParams: NodeParams, keyPath: Seq[Long]): PrivateKey = DeterministicWallet.derivePrivateKey(nodeParams.extendedPrivateKey, keyPath).privateKey - def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: BinaryData, isFunder: Boolean, fundingSatoshis: Long): LocalParams = { - // all secrets are generated from the main seed - // TODO: check this - val keyIndex = secureRandom.nextInt(1000).toLong + 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: BinaryData, isFunder: Boolean, fundingSatoshis: Long, channelKeyPath: DeterministicWallet.KeyPath): LocalParams = { LocalParams( - nodeId = nodeParams.privateKey.publicKey, + nodeParams.nodeId, + channelKeyPath, dustLimitSatoshis = nodeParams.dustLimitSatoshis, maxHtlcValueInFlightMsat = nodeParams.maxHtlcValueInFlightMsat, channelReserveSatoshis = (nodeParams.reserveToFundingRatio * fundingSatoshis).toLong, htlcMinimumMsat = nodeParams.htlcMinimumMsat, toSelfDelay = nodeParams.delayBlocks, maxAcceptedHtlcs = nodeParams.maxAcceptedHtlcs, - fundingPrivKey = generateKey(nodeParams, keyIndex :: 0L :: Nil), - revocationSecret = generateKey(nodeParams, keyIndex :: 1L :: Nil), - paymentKey = generateKey(nodeParams, keyIndex :: 2L :: Nil), - delayedPaymentKey = generateKey(nodeParams, keyIndex :: 3L :: Nil), - htlcKey = generateKey(nodeParams, keyIndex :: 4L :: Nil), defaultFinalScriptPubKey = defaultFinalScriptPubKey, - shaSeed = Crypto.sha256(generateKey(nodeParams, keyIndex :: 5L :: Nil).toBin), // TODO: check that isFunder = isFunder, globalFeatures = nodeParams.globalFeatures, localFeatures = nodeParams.localFeatures) } - } 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 07c9649b6..8e64272c6 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 @@ -1,7 +1,8 @@ package fr.acinq.eclair.wire +import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, KeyPath} import fr.acinq.bitcoin.{BinaryData, OutPoint, Transaction, TxOut} -import fr.acinq.eclair.channel.{DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT, _} +import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.payment.{Local, Origin, Relayed} import fr.acinq.eclair.transactions.Transactions._ @@ -10,29 +11,33 @@ import fr.acinq.eclair.wire.LightningMessageCodecs._ import grizzled.slf4j.Logging import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ -import scodec.{Attempt, Codec, DecodeResult} +import scodec.{Attempt, Codec} /** * Created by PM on 02/06/2017. */ 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 extendedPrivateKeyCodec: Codec[ExtendedPrivateKey] = ( + ("secretkeybytes" | binarydata(32)) :: + ("chaincode" | binarydata(32)) :: + ("depth" | uint16) :: + ("path" | keyPathCodec) :: + ("parent" | int64)).as[ExtendedPrivateKey] + val localParamsCodec: Codec[LocalParams] = ( ("nodeId" | publicKey) :: + ("channelPath" | keyPathCodec) :: ("dustLimitSatoshis" | uint64) :: ("maxHtlcValueInFlightMsat" | uint64ex) :: ("channelReserveSatoshis" | uint64) :: ("htlcMinimumMsat" | uint64) :: ("toSelfDelay" | uint16) :: ("maxAcceptedHtlcs" | uint16) :: - ("fundingPrivKey" | privateKey) :: - ("revocationSecret" | scalar) :: - ("paymentKey" | scalar) :: - ("delayedPaymentKey" | scalar) :: - ("htlcKey" | scalar) :: - ("defaultFinalScriptPubKey" | varsizebinarydata) :: - ("shaSeed" | varsizebinarydata) :: ("isFunder" | bool) :: + ("defaultFinalScriptPubKey" | varsizebinarydata) :: ("globalFeatures" | varsizebinarydata) :: ("localFeatures" | varsizebinarydata)).as[LocalParams] 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 b780db721..9cbc9606d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -4,8 +4,9 @@ import java.net.InetSocketAddress import java.sql.DriverManager import fr.acinq.bitcoin.Crypto.PrivateKey -import fr.acinq.bitcoin.{BinaryData, Block, DeterministicWallet, Script} +import fr.acinq.bitcoin.{BinaryData, Block, Script} import fr.acinq.eclair.NodeParams.BITCOIND +import fr.acinq.eclair.crypto.LocalKeyManager import fr.acinq.eclair.db.sqlite._ import fr.acinq.eclair.io.Peer import fr.acinq.eclair.wire.Color @@ -22,15 +23,13 @@ object TestConstants { object Alice { val seed = BinaryData("01" * 32) - val master = DeterministicWallet.generate(seed) - val extendedPrivateKey = DeterministicWallet.derivePrivateKey(master, DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(0) :: Nil) + val keyManager = new LocalKeyManager(seed) def sqlite = DriverManager.getConnection("jdbc:sqlite::memory:") // This is a function, and not a val! When called will return a new NodeParams def nodeParams = NodeParams( - extendedPrivateKey = extendedPrivateKey, - privateKey = extendedPrivateKey.privateKey, + keyManager = keyManager, alias = "alice", color = Color(1, 2, 3), publicAddresses = new InetSocketAddress("localhost", 9731) :: Nil, @@ -79,14 +78,12 @@ object TestConstants { object Bob { val seed = BinaryData("02" * 32) - val master = DeterministicWallet.generate(seed) - val extendedPrivateKey = DeterministicWallet.derivePrivateKey(master, DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(0) :: Nil) + val keyManager = new LocalKeyManager(seed) def sqlite = DriverManager.getConnection("jdbc:sqlite::memory:") def nodeParams = NodeParams( - extendedPrivateKey = extendedPrivateKey, - privateKey = extendedPrivateKey.privateKey, + keyManager = keyManager, alias = "bob", color = Color(4, 5, 6), publicAddresses = new InetSocketAddress("localhost", 9732) :: Nil, 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 ba4cd2fdf..e960ec655 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 @@ -1871,7 +1871,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures] import initialState.commitments.localParams import initialState.commitments.remoteParams - val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, localParams.nodeId, remoteParams.nodeId, localParams.fundingPrivKey.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(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) val channelUpdate = Announcements.makeChannelUpdate(Alice.nodeParams.chainHash, Alice.nodeParams.privateKey, remoteParams.nodeId, annSigsA.shortChannelId, Alice.nodeParams.expiryDeltaBlocks, Bob.nodeParams.htlcMinimumMsat, Alice.nodeParams.feeBaseMsat, Alice.nodeParams.feeProportionalMillionth) // actual test starts here bob2alice.forward(alice) @@ -1890,7 +1890,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures] import initialState.commitments.localParams import initialState.commitments.remoteParams - val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, localParams.nodeId, remoteParams.nodeId, localParams.fundingPrivKey.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(localParams.channelKeyPath).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 c564570fc..ae731c677 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 @@ -1,14 +1,14 @@ package fr.acinq.eclair.channel.states.e import akka.testkit.{TestFSMRef, TestProbe} -import fr.acinq.bitcoin.{BinaryData, ScriptFlags, Transaction} import fr.acinq.bitcoin.Crypto.Scalar -import fr.acinq.eclair.TestkitBaseClass +import fr.acinq.bitcoin.{BinaryData, ScriptFlags, Transaction} import fr.acinq.eclair.blockchain.{PublishAsap, WatchEventSpent} import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.channel.{Data, State, _} -import fr.acinq.eclair.crypto.{Generators, Sphinx} +import fr.acinq.eclair.crypto.Sphinx import fr.acinq.eclair.wire._ +import fr.acinq.eclair.{TestConstants, TestkitBaseClass} import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner @@ -58,8 +58,8 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val bobCommitments = bob.stateData.asInstanceOf[HasCommitments].commitments val aliceCommitments = alice.stateData.asInstanceOf[HasCommitments].commitments - val bobCurrentPerCommitmentPoint = Generators.perCommitPoint(bobCommitments.localParams.shaSeed, bobCommitments.localCommit.index) - val aliceCurrentPerCommitmentPoint = Generators.perCommitPoint(aliceCommitments.localParams.shaSeed, aliceCommitments.localCommit.index) + val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.commitmentPoint(bobCommitments.localParams.channelKeyPath, bobCommitments.localCommit.index) + val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.commitmentPoint(aliceCommitments.localParams.channelKeyPath, aliceCommitments.localCommit.index) // a didn't receive any update or sig @@ -141,8 +141,8 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods { val bobCommitments = bob.stateData.asInstanceOf[HasCommitments].commitments val aliceCommitments = alice.stateData.asInstanceOf[HasCommitments].commitments - val bobCurrentPerCommitmentPoint = Generators.perCommitPoint(bobCommitments.localParams.shaSeed, bobCommitments.localCommit.index) - val aliceCurrentPerCommitmentPoint = Generators.perCommitPoint(aliceCommitments.localParams.shaSeed, aliceCommitments.localCommit.index) + val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.commitmentPoint(bobCommitments.localParams.channelKeyPath, bobCommitments.localCommit.index) + val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.commitmentPoint(aliceCommitments.localParams.channelKeyPath, aliceCommitments.localCommit.index) // a didn't receive the sig val ab_reestablish = alice2bob.expectMsg(ChannelReestablish(ab_add_0.channelId, 1, 0, Some(Scalar(Sphinx zeroes 32)), Some(aliceCurrentPerCommitmentPoint))) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala index cdc44244a..8b5ac5073 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala @@ -3,6 +3,7 @@ package fr.acinq.eclair.channel.states.g import akka.actor.Status.Failure import akka.testkit.{TestFSMRef, TestProbe} import fr.acinq.bitcoin.Satoshi +import fr.acinq.eclair.TestConstants.Bob import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.blockchain.fee.FeeratesPerKw import fr.acinq.eclair.channel.Helpers.Closing @@ -172,7 +173,7 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods // at this point alice and bob have not yet converged on closing fees, but bob decides to publish a mutual close with one of the previous sigs val d = bob.stateData.asInstanceOf[DATA_NEGOTIATING] implicit val log = bob.underlyingActor.implicitLog - val Success(bobClosingTx) = Closing.checkClosingSignature(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, Satoshi(aliceClose1.feeSatoshis), aliceClose1.signature) + val Success(bobClosingTx) = Closing.checkClosingSignature(Bob.keyManager, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, Satoshi(aliceClose1.feeSatoshis), aliceClose1.signature) alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobClosingTx) alice2blockchain.expectMsgType[PublishAsap] 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 a5d6352d4..ab44f399a 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 @@ -1,16 +1,16 @@ package fr.acinq.eclair.db import fr.acinq.bitcoin.Crypto.{PrivateKey, Scalar} -import fr.acinq.bitcoin.{BinaryData, Crypto, MilliSatoshi, Satoshi, Transaction} +import fr.acinq.bitcoin.{BinaryData, Crypto, DeterministicWallet, MilliSatoshi, Satoshi, Transaction} import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.channel._ -import fr.acinq.eclair.crypto.{ShaChain, Sphinx} +import fr.acinq.eclair.crypto.{LocalKeyManager, ShaChain, Sphinx} import fr.acinq.eclair.payment.{Local, Relayed} +import fr.acinq.eclair.{UInt64, randomKey} import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.Transactions.CommitTx import fr.acinq.eclair.transactions._ -import fr.acinq.eclair.wire.{ChannelCodecs, ChannelUpdate, UpdateAddHtlc} -import fr.acinq.eclair.{UInt64, randomKey} +import fr.acinq.eclair.wire.{ChannelCodecs, UpdateAddHtlc} import org.junit.runner.RunWith import org.scalatest.FunSuite import org.scalatest.junit.JUnitRunner @@ -33,21 +33,17 @@ class ChannelStateSpec extends FunSuite { } object ChannelStateSpec { + val keyManager = new LocalKeyManager("01" * 32) val localParams = LocalParams( - nodeId = randomKey.publicKey, + keyManager.nodeId, + channelKeyPath = DeterministicWallet.KeyPath(Seq(42)), dustLimitSatoshis = Satoshi(546).toLong, maxHtlcValueInFlightMsat = UInt64(50), channelReserveSatoshis = 10000, htlcMinimumMsat = 50000, toSelfDelay = 144, maxAcceptedHtlcs = 50, - fundingPrivKey = PrivateKey(BinaryData("01" * 32) :+ 1.toByte), - revocationSecret = Scalar(BinaryData("02" * 32)), - paymentKey = PrivateKey(BinaryData("03" * 32) :+ 1.toByte), - delayedPaymentKey = Scalar(BinaryData("04" * 32)), - htlcKey = PrivateKey(BinaryData("06" * 32) :+ 1.toByte), defaultFinalScriptPubKey = Nil, - shaSeed = BinaryData("05" * 32), isFunder = true, globalFeatures = "foo".getBytes(), localFeatures = "bar".getBytes()) @@ -86,7 +82,7 @@ object ChannelStateSpec { val fundingTx = Transaction.read("0200000001adbb20ea41a8423ea937e76e8151636bf6093b70eaff942930d20576600521fd000000006b48304502210090587b6201e166ad6af0227d3036a9454223d49a1f11839c1a362184340ef0240220577f7cd5cca78719405cbf1de7414ac027f0239ef6e214c90fcaab0454d84b3b012103535b32d5eb0a6ed0982a0479bbadc9868d9836f6ba94dd5a63be16d875069184ffffffff028096980000000000220020c015c4a6be010e21657068fc2e6a9d02b27ebe4d490a25846f7237f104d1a3cd20256d29010000001600143ca33c2e4446f4a305f23c80df8ad1afdcf652f900000000") val fundingAmount = fundingTx.txOut(0).amount - val commitmentInput = Funding.makeFundingInputInfo(fundingTx.hash, 0, fundingAmount, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey) + val commitmentInput = Funding.makeFundingInputInfo(fundingTx.hash, 0, fundingAmount, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey) val localCommit = LocalCommit(0, CommitmentSpec(htlcs.toSet, 1500, 50000, 700000), PublishableTxs(CommitTx(commitmentInput, Transaction(2, Nil, Nil, 0)), Nil)) val remoteCommit = RemoteCommit(0, CommitmentSpec(htlcs.toSet, 1500, 50000, 700000), BinaryData("0303030303030303030303030303030303030303030303030303030303030303"), Scalar(BinaryData("04" * 32)).toPoint) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/HtlcGenerationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/HtlcGenerationSpec.scala index edc0c9224..2b079f1c6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/HtlcGenerationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/HtlcGenerationSpec.scala @@ -1,13 +1,14 @@ package fr.acinq.eclair.payment -import fr.acinq.bitcoin.{BinaryData, Block, Crypto} +import fr.acinq.bitcoin.DeterministicWallet.ExtendedPrivateKey +import fr.acinq.bitcoin.{BinaryData, Block, Crypto, DeterministicWallet} import fr.acinq.eclair.channel.Channel import fr.acinq.eclair.crypto.Sphinx import fr.acinq.eclair.crypto.Sphinx.{PacketAndSecrets, ParsedPacket} import fr.acinq.eclair.payment.PaymentLifecycle._ -import fr.acinq.eclair.{randomKey, nodeFee} import fr.acinq.eclair.router.Hop import fr.acinq.eclair.wire.{ChannelUpdate, LightningMessageCodecs, PerHopPayload} +import fr.acinq.eclair.{TestConstants, nodeFee, randomBytes} import org.junit.runner.RunWith import org.scalatest.FunSuite import org.scalatest.junit.JUnitRunner @@ -54,25 +55,25 @@ class HtlcGenerationSpec extends FunSuite { assert(packet_b.serialize.size === Sphinx.PacketLength) // let's peel the onion - val Success(ParsedPacket(bin_b, packet_c, _)) = Sphinx.parsePacket(priv_b, paymentHash, packet_b.serialize) + val Success(ParsedPacket(bin_b, packet_c, _)) = Sphinx.parsePacket(priv_b.privateKey, paymentHash, packet_b.serialize) val payload_b = LightningMessageCodecs.perHopPayloadCodec.decode(BitVector(bin_b.data)).require.value assert(packet_c.serialize.size === Sphinx.PacketLength) assert(payload_b.amtToForward === amount_bc) assert(payload_b.outgoingCltvValue === expiry_bc) - val Success(ParsedPacket(bin_c, packet_d, _)) = Sphinx.parsePacket(priv_c, paymentHash, packet_c.serialize) + val Success(ParsedPacket(bin_c, packet_d, _)) = Sphinx.parsePacket(priv_c.privateKey, paymentHash, packet_c.serialize) val payload_c = LightningMessageCodecs.perHopPayloadCodec.decode(BitVector(bin_c.data)).require.value assert(packet_d.serialize.size === Sphinx.PacketLength) assert(payload_c.amtToForward === amount_cd) assert(payload_c.outgoingCltvValue === expiry_cd) - val Success(ParsedPacket(bin_d, packet_e, _)) = Sphinx.parsePacket(priv_d, paymentHash, packet_d.serialize) + val Success(ParsedPacket(bin_d, packet_e, _)) = Sphinx.parsePacket(priv_d.privateKey, paymentHash, packet_d.serialize) val payload_d = LightningMessageCodecs.perHopPayloadCodec.decode(BitVector(bin_d.data)).require.value assert(packet_e.serialize.size === Sphinx.PacketLength) assert(payload_d.amtToForward === amount_de) assert(payload_d.outgoingCltvValue === expiry_de) - val Success(ParsedPacket(bin_e, packet_random, _)) = Sphinx.parsePacket(priv_e, paymentHash, packet_e.serialize) + val Success(ParsedPacket(bin_e, packet_random, _)) = Sphinx.parsePacket(priv_e.privateKey, paymentHash, packet_e.serialize) val payload_e = LightningMessageCodecs.perHopPayloadCodec.decode(BitVector(bin_e.data)).require.value assert(packet_random.serialize.size === Sphinx.PacketLength) assert(payload_e.amtToForward === finalAmountMsat) @@ -89,25 +90,25 @@ class HtlcGenerationSpec extends FunSuite { assert(add.onion.length === Sphinx.PacketLength) // let's peel the onion - val Success(ParsedPacket(bin_b, packet_c, _)) = Sphinx.parsePacket(priv_b, paymentHash, add.onion) + val Success(ParsedPacket(bin_b, packet_c, _)) = Sphinx.parsePacket(priv_b.privateKey, paymentHash, add.onion) val payload_b = LightningMessageCodecs.perHopPayloadCodec.decode(BitVector(bin_b.data)).require.value assert(packet_c.serialize.size === Sphinx.PacketLength) assert(payload_b.amtToForward === amount_bc) assert(payload_b.outgoingCltvValue === expiry_bc) - val Success(ParsedPacket(bin_c, packet_d, _)) = Sphinx.parsePacket(priv_c, paymentHash, packet_c.serialize) + val Success(ParsedPacket(bin_c, packet_d, _)) = Sphinx.parsePacket(priv_c.privateKey, paymentHash, packet_c.serialize) val payload_c = LightningMessageCodecs.perHopPayloadCodec.decode(BitVector(bin_c.data)).require.value assert(packet_d.serialize.size === Sphinx.PacketLength) assert(payload_c.amtToForward === amount_cd) assert(payload_c.outgoingCltvValue === expiry_cd) - val Success(ParsedPacket(bin_d, packet_e, _)) = Sphinx.parsePacket(priv_d, paymentHash, packet_d.serialize) + val Success(ParsedPacket(bin_d, packet_e, _)) = Sphinx.parsePacket(priv_d.privateKey, paymentHash, packet_d.serialize) val payload_d = LightningMessageCodecs.perHopPayloadCodec.decode(BitVector(bin_d.data)).require.value assert(packet_e.serialize.size === Sphinx.PacketLength) assert(payload_d.amtToForward === amount_de) assert(payload_d.outgoingCltvValue === expiry_de) - val Success(ParsedPacket(bin_e, packet_random, _)) = Sphinx.parsePacket(priv_e, paymentHash, packet_e.serialize) + val Success(ParsedPacket(bin_e, packet_random, _)) = Sphinx.parsePacket(priv_e.privateKey, paymentHash, packet_e.serialize) val payload_e = LightningMessageCodecs.perHopPayloadCodec.decode(BitVector(bin_e.data)).require.value assert(packet_random.serialize.size === Sphinx.PacketLength) assert(payload_e.amtToForward === finalAmountMsat) @@ -123,7 +124,7 @@ class HtlcGenerationSpec extends FunSuite { assert(add.onion.size === Sphinx.PacketLength) // let's peel the onion - val Success(ParsedPacket(bin_b, packet_random, _)) = Sphinx.parsePacket(priv_b, paymentHash, add.onion) + val Success(ParsedPacket(bin_b, packet_random, _)) = Sphinx.parsePacket(priv_b.privateKey, paymentHash, add.onion) val payload_b = LightningMessageCodecs.perHopPayloadCodec.decode(BitVector(bin_b.data)).require.value assert(packet_random.serialize.size === Sphinx.PacketLength) assert(payload_b.amtToForward === finalAmountMsat) @@ -133,9 +134,12 @@ class HtlcGenerationSpec extends FunSuite { } object HtlcGenerationSpec { - val (priv_a, priv_b, priv_c, priv_d, priv_e) = (randomKey, randomKey, randomKey, randomKey, randomKey) + + def randomExtendedPrivateKey: ExtendedPrivateKey = DeterministicWallet.generate(randomBytes(32)) + + val (priv_a, priv_b, priv_c, priv_d, priv_e) = (TestConstants.Alice.keyManager.nodeKey, TestConstants.Bob.keyManager.nodeKey, randomExtendedPrivateKey, randomExtendedPrivateKey, randomExtendedPrivateKey) val (a, b, c, d, e) = (priv_a.publicKey, priv_b.publicKey, priv_c.publicKey, priv_d.publicKey, priv_e.publicKey) - val sig = Crypto.encodeSignature(Crypto.sign(Crypto.sha256(BinaryData.empty), priv_a)) :+ 1.toByte + val sig = Crypto.encodeSignature(Crypto.sign(Crypto.sha256(BinaryData.empty), priv_a.privateKey)) :+ 1.toByte val defaultChannelUpdate = ChannelUpdate(sig, Block.RegtestGenesisBlock.hash, 0, 0, "0000", 0, 42000, 0, 0) val channelUpdate_ab = defaultChannelUpdate.copy(shortChannelId = 1, cltvExpiryDelta = 4, feeBaseMsat = 642000, feeProportionalMillionths = 7) val channelUpdate_bc = defaultChannelUpdate.copy(shortChannelId = 2, cltvExpiryDelta = 5, feeBaseMsat = 153000, feeProportionalMillionths = 4) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/RelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/RelayerSpec.scala index 5ad40cc5d..e6229fc06 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/RelayerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/RelayerSpec.scala @@ -34,7 +34,8 @@ class RelayerSpec extends TestkitBaseClass { val register = TestProbe() val paymentHandler = TestProbe() // we are node B in the route A -> B -> C -> .... - val relayer = system.actorOf(Relayer.props(TestConstants.Bob.nodeParams.copy(privateKey = priv_b), register.ref, paymentHandler.ref)) + //val relayer = system.actorOf(Relayer.props(TestConstants.Bob.nodeParams.copy(nodeKey = priv_b), register.ref, paymentHandler.ref)) + val relayer = system.actorOf(Relayer.props(TestConstants.Bob.nodeParams, register.ref, paymentHandler.ref)) test((relayer, register, paymentHandler)) } } 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 256fd8cca..679880ccb 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 @@ -1,6 +1,7 @@ package fr.acinq.eclair.wire -import fr.acinq.bitcoin.{BinaryData, OutPoint} +import fr.acinq.bitcoin.DeterministicWallet.KeyPath +import fr.acinq.bitcoin.{BinaryData, DeterministicWallet, OutPoint} import fr.acinq.eclair.channel.{LocalParams, RemoteParams} import fr.acinq.eclair.crypto.Sphinx import fr.acinq.eclair.payment.{Local, Relayed} @@ -25,22 +26,31 @@ class ChannelCodecsSpec extends FunSuite { bin } + test("encode/decode key paths (all 0s)") { + val keyPath = KeyPath(Seq(0, 0, 0, 0)) + val encoded = keyPathCodec.encode(keyPath).require + val decoded = keyPathCodec.decode(encoded).require + assert(keyPath === decoded.value) + } + + test("encode/decode key paths (all 1s)") { + val keyPath = KeyPath(Seq(0xffffffffL, 0xffffffffL, 0xffffffffL, 0xffffffffL)) + val encoded = keyPathCodec.encode(keyPath).require + val decoded = keyPathCodec.decode(encoded).require + assert(keyPath === decoded.value) + } + test("encode/decode localparams") { val o = LocalParams( nodeId = randomKey.publicKey, + channelKeyPath = DeterministicWallet.KeyPath(Seq(42)), 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), - fundingPrivKey = randomKey, - revocationSecret = randomKey.value, - paymentKey = randomKey, - delayedPaymentKey = randomKey.value, - htlcKey = randomKey, defaultFinalScriptPubKey = randomBytes(10 + Random.nextInt(200)), - shaSeed = randomBytes(32), isFunder = Random.nextBoolean(), globalFeatures = randomBytes(256), localFeatures = randomBytes(256))