mirror of
https://github.com/ACINQ/eclair.git
synced 2025-02-23 06:35:11 +01:00
Improve key management (#372)
* generate all channel keys and secrets from the node key and channel number * use a key manager the key manager does not export private keys or secrets. It exports public keys, and points, and provide methods to sign transaction. There is just one exception: it does export revocation secrets, since we need to send them back when we receive a commitment signature. * key management: cache private keys and public keys * add key manager to node parameters * create an interface for key manager and an implementation which keeps private keys locally * generate a new BIP32 key path for each new channel When we create a new channel we generate a new random BIP32 key path with 128 bits of entropy
This commit is contained in:
parent
42fb9a90c1
commit
24dadff625
18 changed files with 435 additions and 250 deletions
|
@ -7,11 +7,10 @@ import java.sql.DriverManager
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
import com.typesafe.config.{Config, ConfigFactory}
|
import com.typesafe.config.{Config, ConfigFactory}
|
||||||
import fr.acinq.bitcoin.Crypto.PrivateKey
|
import fr.acinq.bitcoin.{BinaryData, Block}
|
||||||
import fr.acinq.bitcoin.DeterministicWallet.ExtendedPrivateKey
|
|
||||||
import fr.acinq.bitcoin.{BinaryData, Block, DeterministicWallet}
|
|
||||||
import fr.acinq.eclair.NodeParams.WatcherType
|
import fr.acinq.eclair.NodeParams.WatcherType
|
||||||
import fr.acinq.eclair.channel.Channel
|
import fr.acinq.eclair.channel.Channel
|
||||||
|
import fr.acinq.eclair.crypto.KeyManager
|
||||||
import fr.acinq.eclair.db._
|
import fr.acinq.eclair.db._
|
||||||
import fr.acinq.eclair.db.sqlite._
|
import fr.acinq.eclair.db.sqlite._
|
||||||
import fr.acinq.eclair.wire.Color
|
import fr.acinq.eclair.wire.Color
|
||||||
|
@ -22,8 +21,7 @@ import scala.concurrent.duration.FiniteDuration
|
||||||
/**
|
/**
|
||||||
* Created by PM on 26/02/2017.
|
* Created by PM on 26/02/2017.
|
||||||
*/
|
*/
|
||||||
case class NodeParams(extendedPrivateKey: ExtendedPrivateKey,
|
case class NodeParams(keyManager: KeyManager,
|
||||||
privateKey: PrivateKey,
|
|
||||||
alias: String,
|
alias: String,
|
||||||
color: Color,
|
color: Color,
|
||||||
publicAddresses: List[InetSocketAddress],
|
publicAddresses: List[InetSocketAddress],
|
||||||
|
@ -58,7 +56,8 @@ case class NodeParams(extendedPrivateKey: ExtendedPrivateKey,
|
||||||
watcherType: WatcherType,
|
watcherType: WatcherType,
|
||||||
paymentRequestExpiry: FiniteDuration,
|
paymentRequestExpiry: FiniteDuration,
|
||||||
maxPendingPaymentRequests: Int) {
|
maxPendingPaymentRequests: Int) {
|
||||||
val nodeId = privateKey.publicKey
|
val privateKey = keyManager.nodeKey.privateKey
|
||||||
|
val nodeId = keyManager.nodeId
|
||||||
}
|
}
|
||||||
|
|
||||||
object NodeParams {
|
object NodeParams {
|
||||||
|
@ -82,25 +81,22 @@ object NodeParams {
|
||||||
.withFallback(overrideDefaults)
|
.withFallback(overrideDefaults)
|
||||||
.withFallback(ConfigFactory.load()).getConfig("eclair")
|
.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()
|
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 chain = config.getString("chain")
|
||||||
val chainHash = chain match {
|
val chainHash = chain match {
|
||||||
case "test" => Block.TestnetGenesisBlock.hash
|
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}")
|
require(maxAcceptedHtlcs <= Channel.MAX_ACCEPTED_HTLCS, s"max-accepted-htlcs must be lower than ${Channel.MAX_ACCEPTED_HTLCS}")
|
||||||
|
|
||||||
NodeParams(
|
NodeParams(
|
||||||
extendedPrivateKey = extendedPrivateKey,
|
keyManager = keyManager,
|
||||||
privateKey = extendedPrivateKey.privateKey,
|
|
||||||
alias = config.getString("node-alias").take(32),
|
alias = config.getString("node-alias").take(32),
|
||||||
color = Color(color.data(0), color.data(1), color.data(2)),
|
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"))),
|
publicAddresses = config.getStringList("server.public-ips").toList.map(ip => new InetSocketAddress(ip, config.getInt("server.port"))),
|
||||||
|
|
|
@ -19,6 +19,7 @@ import fr.acinq.eclair.blockchain.electrum.{ElectrumClient, ElectrumEclairWallet
|
||||||
import fr.acinq.eclair.blockchain.fee.{ConstantFeeProvider, _}
|
import fr.acinq.eclair.blockchain.fee.{ConstantFeeProvider, _}
|
||||||
import fr.acinq.eclair.blockchain.{EclairWallet, _}
|
import fr.acinq.eclair.blockchain.{EclairWallet, _}
|
||||||
import fr.acinq.eclair.channel.Register
|
import fr.acinq.eclair.channel.Register
|
||||||
|
import fr.acinq.eclair.crypto.LocalKeyManager
|
||||||
import fr.acinq.eclair.io.{Authenticator, Server, Switchboard}
|
import fr.acinq.eclair.io.{Authenticator, Server, Switchboard}
|
||||||
import fr.acinq.eclair.payment._
|
import fr.acinq.eclair.payment._
|
||||||
import fr.acinq.eclair.router._
|
import fr.acinq.eclair.router._
|
||||||
|
@ -30,7 +31,7 @@ import scala.concurrent.{Await, ExecutionContext, Future, Promise}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup eclair from a datadir.
|
* Setup eclair from a datadir.
|
||||||
* <p>
|
*
|
||||||
* Created by PM on 25/01/2016.
|
* Created by PM on 25/01/2016.
|
||||||
*
|
*
|
||||||
* @param datadir directory where eclair-core will write/read its data
|
* @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"version=${getClass.getPackage.getImplementationVersion} commit=${getClass.getPackage.getSpecificationVersion}")
|
||||||
logger.info(s"datadir=${datadir.getCanonicalPath}")
|
logger.info(s"datadir=${datadir.getCanonicalPath}")
|
||||||
|
|
||||||
val config: Config = NodeParams.loadConfiguration(datadir, overrideDefaults)
|
val config = NodeParams.loadConfiguration(datadir, overrideDefaults)
|
||||||
val nodeParams: NodeParams = NodeParams.makeNodeParams(datadir, config, seed_opt)
|
val seed = seed_opt.getOrElse(NodeParams.getSeed(datadir))
|
||||||
val chain: String = config.getString("chain")
|
val keyManager = new LocalKeyManager(seed)
|
||||||
|
val nodeParams = NodeParams.makeNodeParams(datadir, config, keyManager)
|
||||||
|
val chain = config.getString("chain")
|
||||||
|
|
||||||
// early checks
|
// early checks
|
||||||
DBCompatChecker.checkDBCompatibility(nodeParams)
|
DBCompatChecker.checkDBCompatibility(nodeParams)
|
||||||
|
|
|
@ -10,7 +10,7 @@ import fr.acinq.bitcoin._
|
||||||
import fr.acinq.eclair._
|
import fr.acinq.eclair._
|
||||||
import fr.acinq.eclair.blockchain._
|
import fr.acinq.eclair.blockchain._
|
||||||
import fr.acinq.eclair.channel.Helpers.{Closing, Funding}
|
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.payment._
|
||||||
import fr.acinq.eclair.router.Announcements
|
import fr.acinq.eclair.router.Announcements
|
||||||
import fr.acinq.eclair.transactions._
|
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] {
|
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 Channel._
|
||||||
|
import nodeParams.keyManager
|
||||||
|
|
||||||
// we pass these to helpers classes so that they have the logging context
|
// we pass these to helpers classes so that they have the logging context
|
||||||
implicit def implicitLog = log
|
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) =>
|
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))
|
context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, true, temporaryChannelId))
|
||||||
forwarder ! remote
|
forwarder ! remote
|
||||||
val firstPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 0)
|
|
||||||
val open = OpenChannel(nodeParams.chainHash,
|
val open = OpenChannel(nodeParams.chainHash,
|
||||||
temporaryChannelId = temporaryChannelId,
|
temporaryChannelId = temporaryChannelId,
|
||||||
fundingSatoshis = fundingSatoshis,
|
fundingSatoshis = fundingSatoshis,
|
||||||
|
@ -118,12 +118,12 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
||||||
feeratePerKw = initialFeeratePerKw,
|
feeratePerKw = initialFeeratePerKw,
|
||||||
toSelfDelay = localParams.toSelfDelay,
|
toSelfDelay = localParams.toSelfDelay,
|
||||||
maxAcceptedHtlcs = localParams.maxAcceptedHtlcs,
|
maxAcceptedHtlcs = localParams.maxAcceptedHtlcs,
|
||||||
fundingPubkey = localParams.fundingPrivKey.publicKey,
|
fundingPubkey = keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey,
|
||||||
revocationBasepoint = localParams.revocationBasepoint,
|
revocationBasepoint = keyManager.revocationPoint(localParams.channelKeyPath).publicKey,
|
||||||
paymentBasepoint = localParams.paymentBasepoint,
|
paymentBasepoint = keyManager.paymentPoint(localParams.channelKeyPath).publicKey,
|
||||||
delayedPaymentBasepoint = localParams.delayedPaymentBasepoint,
|
delayedPaymentBasepoint = keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey,
|
||||||
htlcBasepoint = localParams.htlcBasepoint,
|
htlcBasepoint = keyManager.htlcPoint(localParams.channelKeyPath).publicKey,
|
||||||
firstPerCommitmentPoint = firstPerCommitmentPoint,
|
firstPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 0),
|
||||||
channelFlags = channelFlags)
|
channelFlags = channelFlags)
|
||||||
goto(WAIT_FOR_ACCEPT_CHANNEL) using DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder, open) sending open
|
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))
|
context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, false, open.temporaryChannelId))
|
||||||
// TODO: maybe also check uniqueness of temporary channel id
|
// TODO: maybe also check uniqueness of temporary channel id
|
||||||
val minimumDepth = nodeParams.minDepthBlocks
|
val minimumDepth = nodeParams.minDepthBlocks
|
||||||
val firstPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 0)
|
|
||||||
val accept = AcceptChannel(temporaryChannelId = open.temporaryChannelId,
|
val accept = AcceptChannel(temporaryChannelId = open.temporaryChannelId,
|
||||||
dustLimitSatoshis = localParams.dustLimitSatoshis,
|
dustLimitSatoshis = localParams.dustLimitSatoshis,
|
||||||
maxHtlcValueInFlightMsat = localParams.maxHtlcValueInFlightMsat,
|
maxHtlcValueInFlightMsat = localParams.maxHtlcValueInFlightMsat,
|
||||||
|
@ -196,12 +195,12 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
||||||
htlcMinimumMsat = localParams.htlcMinimumMsat,
|
htlcMinimumMsat = localParams.htlcMinimumMsat,
|
||||||
toSelfDelay = localParams.toSelfDelay,
|
toSelfDelay = localParams.toSelfDelay,
|
||||||
maxAcceptedHtlcs = localParams.maxAcceptedHtlcs,
|
maxAcceptedHtlcs = localParams.maxAcceptedHtlcs,
|
||||||
fundingPubkey = localParams.fundingPrivKey.publicKey,
|
fundingPubkey = keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey,
|
||||||
revocationBasepoint = localParams.revocationBasepoint,
|
revocationBasepoint = keyManager.revocationPoint(localParams.channelKeyPath).publicKey,
|
||||||
paymentBasepoint = localParams.paymentBasepoint,
|
paymentBasepoint = keyManager.paymentPoint(localParams.channelKeyPath).publicKey,
|
||||||
delayedPaymentBasepoint = localParams.delayedPaymentBasepoint,
|
delayedPaymentBasepoint = keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey,
|
||||||
htlcBasepoint = localParams.htlcBasepoint,
|
htlcBasepoint = keyManager.htlcPoint(localParams.channelKeyPath).publicKey,
|
||||||
firstPerCommitmentPoint = firstPerCommitmentPoint)
|
firstPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 0))
|
||||||
val remoteParams = RemoteParams(
|
val remoteParams = RemoteParams(
|
||||||
nodeId = remoteNodeId,
|
nodeId = remoteNodeId,
|
||||||
dustLimitSatoshis = open.dustLimitSatoshis,
|
dustLimitSatoshis = open.dustLimitSatoshis,
|
||||||
|
@ -254,7 +253,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
||||||
globalFeatures = remoteInit.globalFeatures,
|
globalFeatures = remoteInit.globalFeatures,
|
||||||
localFeatures = remoteInit.localFeatures)
|
localFeatures = remoteInit.localFeatures)
|
||||||
log.debug(s"remote params: $remoteParams")
|
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)))
|
val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey, remoteParams.fundingPubKey)))
|
||||||
wallet.makeFundingTx(fundingPubkeyScript, Satoshi(fundingSatoshis), fundingTxFeeratePerKw).pipeTo(self)
|
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)
|
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 {
|
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)) =>
|
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
|
// 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!")
|
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
|
// signature of their initial commitment tx that pays remote pushMsat
|
||||||
val fundingCreated = FundingCreated(
|
val fundingCreated = FundingCreated(
|
||||||
temporaryChannelId = temporaryChannelId,
|
temporaryChannelId = temporaryChannelId,
|
||||||
|
@ -315,11 +314,11 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
||||||
when(WAIT_FOR_FUNDING_CREATED)(handleExceptions {
|
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, _)) =>
|
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)
|
// 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
|
// check remote signature validity
|
||||||
val localSigOfLocalTx = Transactions.sign(localCommitTx, localParams.fundingPrivKey)
|
val localSigOfLocalTx = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath))
|
||||||
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig)
|
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig)
|
||||||
Transactions.checkSpendable(signedLocalCommitTx) match {
|
Transactions.checkSpendable(signedLocalCommitTx) match {
|
||||||
case Failure(cause) =>
|
case Failure(cause) =>
|
||||||
log.error(cause, "their FundingCreated message contains an invalid signature")
|
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
|
// we haven't anything at stake yet, we can just stop
|
||||||
goto(CLOSED) sending error
|
goto(CLOSED) sending error
|
||||||
case Success(_) =>
|
case Success(_) =>
|
||||||
val localSigOfRemoteTx = Transactions.sign(remoteCommitTx, localParams.fundingPrivKey)
|
val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath))
|
||||||
val channelId = toLongId(fundingTxHash, fundingTxOutputIndex)
|
val channelId = toLongId(fundingTxHash, fundingTxOutputIndex)
|
||||||
// watch the funding tx transaction
|
// watch the funding tx transaction
|
||||||
val commitInput = localCommitTx.input
|
val commitInput = localCommitTx.input
|
||||||
|
@ -363,8 +362,8 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
||||||
when(WAIT_FOR_FUNDING_SIGNED)(handleExceptions {
|
when(WAIT_FOR_FUNDING_SIGNED)(handleExceptions {
|
||||||
case Event(FundingSigned(_, remoteSig), DATA_WAIT_FOR_FUNDING_SIGNED(channelId, localParams, remoteParams, fundingTx, localSpec, localCommitTx, remoteCommit, channelFlags, fundingCreated)) =>
|
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
|
// we make sure that their sig checks out and that our first commit tx is spendable
|
||||||
val localSigOfLocalTx = Transactions.sign(localCommitTx, localParams.fundingPrivKey)
|
val localSigOfLocalTx = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath))
|
||||||
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig)
|
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig)
|
||||||
Transactions.checkSpendable(signedLocalCommitTx) match {
|
Transactions.checkSpendable(signedLocalCommitTx) match {
|
||||||
case Failure(cause) =>
|
case Failure(cause) =>
|
||||||
log.error(cause, "their FundingSigned message contains an invalid signature")
|
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, _)) =>
|
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")
|
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)
|
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)
|
val fundingLocked = FundingLocked(commitments.channelId, nextPerCommitmentPoint)
|
||||||
deferred.map(self ! _)
|
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
|
// 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)")
|
log.debug("ignoring CMD_SIGN (nothing to sign)")
|
||||||
stay
|
stay
|
||||||
case Right(_) =>
|
case Right(_) =>
|
||||||
Try(Commitments.sendCommit(d.commitments)) match {
|
Try(Commitments.sendCommit(d.commitments, keyManager)) match {
|
||||||
case Success((commitments1, commit)) =>
|
case Success((commitments1, commit)) =>
|
||||||
log.debug(s"sending a new sig, spec:\n${Commitments.specs2String(commitments1)}")
|
log.debug(s"sending a new sig, spec:\n${Commitments.specs2String(commitments1)}")
|
||||||
commitments1.localChanges.signed.collect {
|
commitments1.localChanges.signed.collect {
|
||||||
|
@ -609,7 +608,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
||||||
}
|
}
|
||||||
|
|
||||||
case Event(commit: CommitSig, d: DATA_NORMAL) =>
|
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)) =>
|
case Success((commitments1, revocation)) =>
|
||||||
log.debug(s"received a new sig, spec:\n${Commitments.specs2String(commitments1)}")
|
log.debug(s"received a new sig, spec:\n${Commitments.specs2String(commitments1)}")
|
||||||
if (Commitments.localHasChanges(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
|
// there are no pending signed htlcs, let's go directly to NEGOTIATING
|
||||||
if (d.commitments.localParams.isFunder) {
|
if (d.commitments.localParams.isFunder) {
|
||||||
// we are funder, need to initiate the negotiation by sending the first closing_signed
|
// 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
|
goto(NEGOTIATING) using store(DATA_NEGOTIATING(d.commitments, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending sendList :+ closingSigned
|
||||||
} else {
|
} else {
|
||||||
// we are fundee, will wait for their closing_signed
|
// 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}")
|
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}")
|
log.info(s"announcing channelId=${d.channelId} on the network with shortId=${d.shortChannelId.toHexString}")
|
||||||
import d.commitments.{localParams, remoteParams}
|
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
|
// we use GOTO instead of stay because we want to fire transitions
|
||||||
goto(NORMAL) using store(d.copy(channelAnnouncement = Some(channelAnn)))
|
goto(NORMAL) using store(d.copy(channelAnnouncement = Some(channelAnn)))
|
||||||
case Some(_) =>
|
case Some(_) =>
|
||||||
|
@ -899,7 +898,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
||||||
log.debug("ignoring CMD_SIGN (nothing to sign)")
|
log.debug("ignoring CMD_SIGN (nothing to sign)")
|
||||||
stay
|
stay
|
||||||
case Right(_) =>
|
case Right(_) =>
|
||||||
Try(Commitments.sendCommit(d.commitments)) match {
|
Try(Commitments.sendCommit(d.commitments, keyManager)) match {
|
||||||
case Success((commitments1, commit)) =>
|
case Success((commitments1, commit)) =>
|
||||||
log.debug(s"sending a new sig, spec:\n${Commitments.specs2String(commitments1)}")
|
log.debug(s"sending a new sig, spec:\n${Commitments.specs2String(commitments1)}")
|
||||||
commitments1.localChanges.signed.collect {
|
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)) =>
|
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) =>
|
case (commitments1, revocation) =>
|
||||||
// we always reply with a revocation
|
// we always reply with a revocation
|
||||||
log.debug(s"received a new sig:\n${Commitments.specs2String(commitments1)}")
|
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 =>
|
case Success((commitments1, revocation)) if commitments1.hasNoPendingHtlcs =>
|
||||||
if (d.commitments.localParams.isFunder) {
|
if (d.commitments.localParams.isFunder) {
|
||||||
// we are funder, need to initiate the negotiation by sending the first closing_signed
|
// 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
|
goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending revocation :: closingSigned :: Nil
|
||||||
} else {
|
} else {
|
||||||
// we are fundee, will wait for their closing_signed
|
// 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)}")
|
log.debug(s"received a new rev, switching to NEGOTIATING spec:\n${Commitments.specs2String(commitments1)}")
|
||||||
if (d.commitments.localParams.isFunder) {
|
if (d.commitments.localParams.isFunder) {
|
||||||
// we are funder, need to initiate the negotiation by sending the first closing_signed
|
// 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
|
goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending closingSigned
|
||||||
} else {
|
} else {
|
||||||
// we are fundee, will wait for their closing_signed
|
// 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 {
|
when(NEGOTIATING)(handleExceptions {
|
||||||
case Event(c@ClosingSigned(_, remoteClosingFee, remoteSig), d: DATA_NEGOTIATING) =>
|
case Event(c@ClosingSigned(_, remoteClosingFee, remoteSig), d: DATA_NEGOTIATING) =>
|
||||||
log.info(s"received closingFeeSatoshis=$remoteClosingFee")
|
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 =>
|
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
|
// we close when we converge or when there were too many iterations
|
||||||
handleMutualClose(signedClosingTx, Left(d.copy(bestUnpublishedClosingTx_opt = Some(signedClosingTx))))
|
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(
|
val nextClosingFee = Closing.nextClosingFee(
|
||||||
localClosingFee = lastLocalClosingFee.getOrElse(Closing.firstClosingFee(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey)),
|
localClosingFee = lastLocalClosingFee.getOrElse(Closing.firstClosingFee(d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey)),
|
||||||
remoteClosingFee = Satoshi(remoteClosingFee))
|
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) {
|
if (Some(nextClosingFee) == lastLocalClosingFee) {
|
||||||
// next computed fee is the same than the one we previously sent (probably because of rounding), let's close now
|
// 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))))
|
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")
|
log.info(s"got valid payment preimage, recalculating transactions to redeem the corresponding htlc on-chain")
|
||||||
val localCommitPublished1 = d.localCommitPublished.map {
|
val localCommitPublished1 = d.localCommitPublished.map {
|
||||||
case localCommitPublished =>
|
case localCommitPublished =>
|
||||||
val localCommitPublished1 = Helpers.Closing.claimCurrentLocalCommitTxOutputs(commitments1, localCommitPublished.commitTx)
|
val localCommitPublished1 = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, commitments1, localCommitPublished.commitTx)
|
||||||
doPublish(localCommitPublished1)
|
doPublish(localCommitPublished1)
|
||||||
localCommitPublished1
|
localCommitPublished1
|
||||||
}
|
}
|
||||||
val remoteCommitPublished1 = d.remoteCommitPublished.map {
|
val remoteCommitPublished1 = d.remoteCommitPublished.map {
|
||||||
case remoteCommitPublished =>
|
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)
|
doPublish(remoteCommitPublished1)
|
||||||
remoteCommitPublished1
|
remoteCommitPublished1
|
||||||
}
|
}
|
||||||
val nextRemoteCommitPublished1 = d.nextRemoteCommitPublished.map {
|
val nextRemoteCommitPublished1 = d.nextRemoteCommitPublished.map {
|
||||||
case remoteCommitPublished =>
|
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)
|
doPublish(remoteCommitPublished1)
|
||||||
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
|
// 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 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 remoteCommitDone = remoteCommitPublished1.map(Closing.isRemoteCommitDone(_)).getOrElse(false)
|
||||||
val nextRemoteCommitDone = nextRemoteCommitPublished1.map(Closing.isRemoteCommitDone(_)).getOrElse(false)
|
val nextRemoteCommitDone = nextRemoteCommitPublished1.map(Closing.isRemoteCommitDone(_)).getOrElse(false)
|
||||||
val futureRemoteCommitDone = futureRemoteCommitPublished1.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
|
forwarder ! r
|
||||||
|
|
||||||
val yourLastPerCommitmentSecret = d.commitments.remotePerCommitmentSecrets.lastIndex.flatMap(d.commitments.remotePerCommitmentSecrets.getHash).getOrElse(Sphinx zeroes 32)
|
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(
|
val channelReestablish = ChannelReestablish(
|
||||||
channelId = d.channelId,
|
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) =>
|
case Event(_: ChannelReestablish, d: DATA_WAIT_FOR_FUNDING_LOCKED) =>
|
||||||
log.debug(s"re-sending fundingLocked")
|
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)
|
val fundingLocked = FundingLocked(d.commitments.channelId, nextPerCommitmentPoint)
|
||||||
goto(WAIT_FOR_FUNDING_LOCKED) sending fundingLocked
|
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 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
|
// 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
|
// 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}")
|
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
|
// 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
|
// 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 (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
|
// 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")
|
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)
|
val fundingLocked = FundingLocked(d.commitments.channelId, nextPerCommitmentPoint)
|
||||||
forwarder ! fundingLocked
|
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
|
// 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) {
|
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
|
// 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))
|
val closingTxProposed1 = d.closingTxProposed :+ List(ClosingTxProposed(closingTx.tx, closingSigned))
|
||||||
goto(NEGOTIATING) using store(d.copy(closingTxProposed = closingTxProposed1)) sending d.localShutdown :: closingSigned :: Nil
|
goto(NEGOTIATING) using store(d.copy(closingTxProposed = closingTxProposed1)) sending d.localShutdown :: closingSigned :: Nil
|
||||||
} else {
|
} else {
|
||||||
|
@ -1543,7 +1542,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
||||||
} else {
|
} else {
|
||||||
val commitTx = d.commitments.localCommit.publishableTxs.commitTx.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)
|
doPublish(localCommitPublished)
|
||||||
|
|
||||||
val nextData = d match {
|
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}")
|
log.warning(s"they published their current commit in txid=${commitTx.txid}")
|
||||||
require(commitTx.txid == d.commitments.remoteCommit.txid, "txid mismatch")
|
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)
|
doPublish(remoteCommitPublished)
|
||||||
|
|
||||||
val nextData = d match {
|
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}")
|
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
|
// if we are in this state, then this field is defined
|
||||||
val remotePerCommitmentPoint = d.remoteChannelReestablish.myCurrentPerCommitmentPoint.get
|
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))
|
val nextData = DATA_CLOSING(d.commitments, Nil, futureRemoteCommitPublished = Some(remoteCommitPublished))
|
||||||
|
|
||||||
doPublish(remoteCommitPublished)
|
doPublish(remoteCommitPublished)
|
||||||
|
@ -1643,7 +1642,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
||||||
val remoteCommit = d.commitments.remoteNextCommitInfo.left.get.nextRemoteCommit
|
val remoteCommit = d.commitments.remoteNextCommitInfo.left.get.nextRemoteCommit
|
||||||
require(commitTx.txid == remoteCommit.txid, "txid mismatch")
|
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)
|
doPublish(remoteCommitPublished)
|
||||||
|
|
||||||
val nextData = d match {
|
val nextData = d match {
|
||||||
|
@ -1675,7 +1674,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
||||||
def handleRemoteSpentOther(tx: Transaction, d: HasCommitments) = {
|
def handleRemoteSpentOther(tx: Transaction, d: HasCommitments) = {
|
||||||
log.warning(s"funding tx spent in txid=${tx.txid}")
|
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) =>
|
case Some(revokedCommitPublished) =>
|
||||||
log.warning(s"txid=${tx.txid} was a revoked commitment, publishing the penalty tx")
|
log.warning(s"txid=${tx.txid} was a revoked commitment, publishing the penalty tx")
|
||||||
val exc = FundingTxSpent(d.channelId, 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
|
// let's try to spend our current local tx
|
||||||
val commitTx = d.commitments.localCommit.publishableTxs.commitTx.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)
|
doPublish(localCommitPublished)
|
||||||
|
|
||||||
goto(ERR_INFORMATION_LEAK) sending error
|
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) {
|
} else if (commitments1.localCommit.index == channelReestablish.nextRemoteRevocationNumber + 1) {
|
||||||
// our last revocation got lost, let's resend it
|
// our last revocation got lost, let's resend it
|
||||||
log.debug(s"re-sending last revocation")
|
log.debug(s"re-sending last revocation")
|
||||||
val localPerCommitmentSecret = Generators.perCommitSecret(commitments1.localParams.shaSeed, d.commitments.localCommit.index - 1)
|
val localPerCommitmentSecret = keyManager.commitmentSecret(commitments1.localParams.channelKeyPath, d.commitments.localCommit.index - 1)
|
||||||
val localNextPerCommitmentPoint = Generators.perCommitPoint(commitments1.localParams.shaSeed, d.commitments.localCommit.index + 1)
|
val localNextPerCommitmentPoint = keyManager.commitmentPoint(commitments1.localParams.channelKeyPath, d.commitments.localCommit.index + 1)
|
||||||
val revocation = RevokeAndAck(
|
val revocation = RevokeAndAck(
|
||||||
channelId = commitments1.channelId,
|
channelId = commitments1.channelId,
|
||||||
perCommitmentSecret = localPerCommitmentSecret,
|
perCommitmentSecret = localPerCommitmentSecret,
|
||||||
|
|
|
@ -2,10 +2,10 @@ package fr.acinq.eclair.channel
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
import akka.actor.ActorRef
|
||||||
import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, Scalar}
|
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.UInt64
|
||||||
import fr.acinq.eclair.crypto.Sphinx
|
import fr.acinq.eclair.crypto.{Sphinx}
|
||||||
import fr.acinq.eclair.transactions.CommitmentSpec
|
import fr.acinq.eclair.transactions.{CommitmentSpec, Transactions}
|
||||||
import fr.acinq.eclair.transactions.Transactions.CommitTx
|
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}
|
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 DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT(commitments: Commitments, remoteChannelReestablish: ChannelReestablish) extends Data with HasCommitments
|
||||||
|
|
||||||
final case class LocalParams(nodeId: PublicKey,
|
final case class LocalParams(nodeId: PublicKey,
|
||||||
|
channelKeyPath: DeterministicWallet.KeyPath,
|
||||||
dustLimitSatoshis: Long,
|
dustLimitSatoshis: Long,
|
||||||
maxHtlcValueInFlightMsat: UInt64,
|
maxHtlcValueInFlightMsat: UInt64,
|
||||||
channelReserveSatoshis: Long,
|
channelReserveSatoshis: Long,
|
||||||
htlcMinimumMsat: Long,
|
htlcMinimumMsat: Long,
|
||||||
toSelfDelay: Int,
|
toSelfDelay: Int,
|
||||||
maxAcceptedHtlcs: Int,
|
maxAcceptedHtlcs: Int,
|
||||||
fundingPrivKey: PrivateKey,
|
|
||||||
revocationSecret: Scalar,
|
|
||||||
paymentKey: Scalar,
|
|
||||||
delayedPaymentKey: Scalar,
|
|
||||||
htlcKey: Scalar,
|
|
||||||
defaultFinalScriptPubKey: BinaryData,
|
|
||||||
shaSeed: BinaryData,
|
|
||||||
isFunder: Boolean,
|
isFunder: Boolean,
|
||||||
|
defaultFinalScriptPubKey: BinaryData,
|
||||||
globalFeatures: BinaryData,
|
globalFeatures: BinaryData,
|
||||||
localFeatures: BinaryData) {
|
localFeatures: BinaryData)
|
||||||
// precomputed for performance reasons
|
|
||||||
val paymentBasepoint = paymentKey.toPoint
|
|
||||||
val delayedPaymentBasepoint = delayedPaymentKey.toPoint
|
|
||||||
val revocationBasepoint = revocationSecret.toPoint
|
|
||||||
val htlcBasepoint = htlcKey.toPoint
|
|
||||||
}
|
|
||||||
|
|
||||||
final case class RemoteParams(nodeId: PublicKey,
|
final case class RemoteParams(nodeId: PublicKey,
|
||||||
dustLimitSatoshis: Long,
|
dustLimitSatoshis: Long,
|
||||||
|
|
|
@ -3,8 +3,8 @@ package fr.acinq.eclair.channel
|
||||||
import akka.event.LoggingAdapter
|
import akka.event.LoggingAdapter
|
||||||
import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, sha256}
|
import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, sha256}
|
||||||
import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi, Transaction}
|
import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi, Transaction}
|
||||||
import fr.acinq.eclair.crypto.{Generators, ShaChain, Sphinx}
|
import fr.acinq.eclair.crypto.{Generators, KeyManager, ShaChain, Sphinx}
|
||||||
import fr.acinq.eclair.payment.{Origin, PaymentLifecycle}
|
import fr.acinq.eclair.payment.Origin
|
||||||
import fr.acinq.eclair.transactions.Transactions._
|
import fr.acinq.eclair.transactions.Transactions._
|
||||||
import fr.acinq.eclair.transactions._
|
import fr.acinq.eclair.transactions._
|
||||||
import fr.acinq.eclair.wire._
|
import fr.acinq.eclair.wire._
|
||||||
|
@ -346,7 +346,7 @@ object Commitments {
|
||||||
|
|
||||||
def revocationHash(seed: BinaryData, index: Long): BinaryData = Crypto.sha256(revocationPreimage(seed, index))
|
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._
|
import commitments._
|
||||||
commitments.remoteNextCommitInfo match {
|
commitments.remoteNextCommitInfo match {
|
||||||
case Right(_) if !localHasChanges(commitments) =>
|
case Right(_) if !localHasChanges(commitments) =>
|
||||||
|
@ -354,12 +354,11 @@ object Commitments {
|
||||||
case Right(remoteNextPerCommitmentPoint) =>
|
case Right(remoteNextPerCommitmentPoint) =>
|
||||||
// remote commitment will includes all local changes + remote acked changes
|
// remote commitment will includes all local changes + remote acked changes
|
||||||
val spec = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed)
|
val spec = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed)
|
||||||
val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, spec)
|
val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(keyManager, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, spec)
|
||||||
val sig = Transactions.sign(remoteCommitTx, localParams.fundingPrivKey)
|
val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath))
|
||||||
|
|
||||||
val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index)
|
val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index)
|
||||||
val htlcKey = Generators.derivePrivKey(localParams.htlcKey, remoteNextPerCommitmentPoint)
|
val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), remoteNextPerCommitmentPoint))
|
||||||
val htlcSigs = sortedHtlcTxs.map(Transactions.sign(_, htlcKey))
|
|
||||||
|
|
||||||
// don't sign if they don't get paid
|
// don't sign if they don't get paid
|
||||||
val commitSig = CommitSig(
|
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._
|
import commitments._
|
||||||
// they sent us a signature for *their* view of *our* next commit tx
|
// they sent us a signature for *their* view of *our* next commit tx
|
||||||
// so in terms of rev.hashes and indexes we have:
|
// 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
|
// receiving money i.e its commit tx has one output for them
|
||||||
|
|
||||||
val spec = CommitmentSpec.reduce(localCommit.spec, localChanges.acked, remoteChanges.proposed)
|
val spec = CommitmentSpec.reduce(localCommit.spec, localChanges.acked, remoteChanges.proposed)
|
||||||
val localPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, commitments.localCommit.index + 1)
|
val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index + 1)
|
||||||
val (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeLocalTxs(localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, spec)
|
val (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeLocalTxs(keyManager, localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, spec)
|
||||||
val sig = Transactions.sign(localCommitTx, localParams.fundingPrivKey)
|
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)
|
// 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
|
// 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) {
|
if (Transactions.checkSpendable(signedCommitTx).isFailure) {
|
||||||
throw InvalidCommitmentSignature(commitments.channelId, signedCommitTx.tx)
|
throw InvalidCommitmentSignature(commitments.channelId, signedCommitTx.tx)
|
||||||
}
|
}
|
||||||
|
@ -413,8 +412,7 @@ object Commitments {
|
||||||
if (commit.htlcSignatures.size != sortedHtlcTxs.size) {
|
if (commit.htlcSignatures.size != sortedHtlcTxs.size) {
|
||||||
throw new HtlcSigCountMismatch(commitments.channelId, sortedHtlcTxs.size, commit.htlcSignatures.size)
|
throw new HtlcSigCountMismatch(commitments.channelId, sortedHtlcTxs.size, commit.htlcSignatures.size)
|
||||||
}
|
}
|
||||||
val localHtlcKey = Generators.derivePrivKey(localParams.htlcKey, localPerCommitmentPoint)
|
val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), localPerCommitmentPoint))
|
||||||
val htlcSigs = sortedHtlcTxs.map(Transactions.sign(_, localHtlcKey))
|
|
||||||
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint)
|
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint)
|
||||||
// combine the sigs to make signed txes
|
// combine the sigs to make signed txes
|
||||||
val htlcTxsAndSigs = (sortedHtlcTxs, htlcSigs, commit.htlcSignatures).zipped.toList.collect {
|
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
|
// we will send our revocation preimage + our next revocation hash
|
||||||
val localPerCommitmentSecret = Generators.perCommitSecret(localParams.shaSeed, commitments.localCommit.index)
|
val localPerCommitmentSecret = keyManager.commitmentSecret(localParams.channelKeyPath, commitments.localCommit.index)
|
||||||
val localNextPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, commitments.localCommit.index + 2)
|
val localNextPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index + 2)
|
||||||
val revocation = RevokeAndAck(
|
val revocation = RevokeAndAck(
|
||||||
channelId = commitments.channelId,
|
channelId = commitments.channelId,
|
||||||
perCommitmentSecret = localPerCommitmentSecret,
|
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]) = {
|
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(localParams.paymentBasepoint, localPerCommitmentPoint)
|
val localPaymentPubkey = Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint)
|
||||||
val localDelayedPaymentPubkey = Generators.derivePubKey(localParams.delayedPaymentBasepoint, localPerCommitmentPoint)
|
val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint)
|
||||||
val localHtlcPubkey = Generators.derivePubKey(localParams.htlcBasepoint, localPerCommitmentPoint)
|
val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint)
|
||||||
val remotePaymentPubkey = Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint)
|
val remotePaymentPubkey = Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint)
|
||||||
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint)
|
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint)
|
||||||
val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, 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)
|
val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec)
|
||||||
(commitTx, htlcTimeoutTxs, htlcSuccessTxs)
|
(commitTx, htlcTimeoutTxs, htlcSuccessTxs)
|
||||||
}
|
}
|
||||||
|
|
||||||
def makeRemoteTxs(commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: Point, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = {
|
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(localParams.paymentBasepoint, remotePerCommitmentPoint)
|
val localPaymentPubkey = Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint)
|
||||||
val localHtlcPubkey = Generators.derivePubKey(localParams.htlcBasepoint, remotePerCommitmentPoint)
|
val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint)
|
||||||
val remotePaymentPubkey = Generators.derivePubKey(remoteParams.paymentBasepoint, remotePerCommitmentPoint)
|
val remotePaymentPubkey = Generators.derivePubKey(remoteParams.paymentBasepoint, remotePerCommitmentPoint)
|
||||||
val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint)
|
val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint)
|
||||||
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint)
|
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint)
|
||||||
val remoteRevocationPubkey = Generators.revocationPubKey(localParams.revocationBasepoint, remotePerCommitmentPoint)
|
val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, 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 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)
|
val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, Satoshi(remoteParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec)
|
||||||
(commitTx, htlcTimeoutTxs, htlcSuccessTxs)
|
(commitTx, htlcTimeoutTxs, htlcSuccessTxs)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
package fr.acinq.eclair.channel
|
package fr.acinq.eclair.channel
|
||||||
|
|
||||||
import akka.event.LoggingAdapter
|
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.Script._
|
||||||
import fr.acinq.bitcoin.{OutPoint, _}
|
import fr.acinq.bitcoin.{OutPoint, _}
|
||||||
import fr.acinq.eclair.blockchain.EclairWallet
|
import fr.acinq.eclair.blockchain.EclairWallet
|
||||||
import fr.acinq.eclair.crypto.Generators
|
import fr.acinq.eclair.crypto.{Generators, KeyManager}
|
||||||
import fr.acinq.eclair.router.Announcements
|
|
||||||
import fr.acinq.eclair.transactions.Scripts._
|
import fr.acinq.eclair.transactions.Scripts._
|
||||||
import fr.acinq.eclair.transactions.Transactions._
|
import fr.acinq.eclair.transactions.Transactions._
|
||||||
import fr.acinq.eclair.transactions._
|
import fr.acinq.eclair.transactions._
|
||||||
|
@ -97,7 +96,7 @@ object Helpers {
|
||||||
def makeAnnouncementSignatures(nodeParams: NodeParams, commitments: Commitments, shortChannelId: Long) = {
|
def makeAnnouncementSignatures(nodeParams: NodeParams, commitments: Commitments, shortChannelId: Long) = {
|
||||||
// TODO: empty features
|
// TODO: empty features
|
||||||
val features = BinaryData("")
|
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)
|
AnnouncementSignatures(commitments.channelId, shortChannelId, localNodeSig, localBitcoinSig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,7 +129,7 @@ object Helpers {
|
||||||
* @param remoteFirstPerCommitmentPoint
|
* @param remoteFirstPerCommitmentPoint
|
||||||
* @return (localSpec, localTx, remoteSpec, remoteTx, fundingTxOutput)
|
* @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 toLocalMsat = if (localParams.isFunder) fundingSatoshis * 1000 - pushMsat else pushMsat
|
||||||
val toRemoteMsat = if (localParams.isFunder) pushMsat else fundingSatoshis * 1000 - 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 commitmentInput = makeFundingInputInfo(fundingTxHash, fundingTxOutputIndex, Satoshi(fundingSatoshis), keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey)
|
||||||
val localPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 0)
|
val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 0)
|
||||||
val (localCommitTx, _, _) = Commitments.makeLocalTxs(0, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, localSpec)
|
val (localCommitTx, _, _) = Commitments.makeLocalTxs(keyManager, 0, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, localSpec)
|
||||||
val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, remoteSpec)
|
val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, 0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, remoteSpec)
|
||||||
|
|
||||||
(localSpec, localCommitTx, remoteSpec, remoteCommitTx)
|
(localSpec, localCommitTx, remoteSpec, remoteCommitTx)
|
||||||
}
|
}
|
||||||
|
@ -159,6 +158,9 @@ object Helpers {
|
||||||
|
|
||||||
object Closing {
|
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 = {
|
def isValidFinalScriptPubkey(scriptPubKey: BinaryData): Boolean = {
|
||||||
Try(Script.parse(scriptPubKey)) match {
|
Try(Script.parse(scriptPubKey)) match {
|
||||||
case Success(OP_DUP :: OP_HASH160 :: OP_PUSHDATA(pubkeyHash, _) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) if pubkeyHash.size == 20 => true
|
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._
|
import commitments._
|
||||||
// this is just to estimate the weight, it depends on size of the pubkey scripts
|
// 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 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"
|
// 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)
|
val feeratePerKw = Math.min(Globals.feeratesPerKw.get.blocks_6, commitments.localCommit.spec.feeratePerKw)
|
||||||
log.info(s"using feeratePerKw=$feeratePerKw for initial closing tx")
|
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 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)
|
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._
|
import commitments._
|
||||||
require(isValidFinalScriptPubkey(localScriptPubkey), "invalid localScriptPubkey")
|
require(isValidFinalScriptPubkey(localScriptPubkey), "invalid localScriptPubkey")
|
||||||
require(isValidFinalScriptPubkey(remoteScriptPubkey), "invalid remoteScriptPubkey")
|
require(isValidFinalScriptPubkey(remoteScriptPubkey), "invalid remoteScriptPubkey")
|
||||||
|
@ -195,22 +197,22 @@ object Helpers {
|
||||||
// TODO: check that
|
// TODO: check that
|
||||||
val dustLimitSatoshis = Satoshi(Math.max(localParams.dustLimitSatoshis, remoteParams.dustLimitSatoshis))
|
val dustLimitSatoshis = Satoshi(Math.max(localParams.dustLimitSatoshis, remoteParams.dustLimitSatoshis))
|
||||||
val closingTx = Transactions.makeClosingTx(commitInput, localScriptPubkey, remoteScriptPubkey, localParams.isFunder, dustLimitSatoshis, closingFee, localCommit.spec)
|
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)
|
val closingSigned = ClosingSigned(channelId, closingFee.amount, localClosingSig)
|
||||||
log.info(s"signed closing txid=${closingTx.tx.txid} with closingFeeSatoshis=${closingSigned.feeSatoshis}")
|
log.info(s"signed closing txid=${closingTx.tx.txid} with closingFeeSatoshis=${closingSigned.feeSatoshis}")
|
||||||
log.debug(s"closingTxid=${closingTx.tx.txid} closingTx=${closingTx.tx}}")
|
log.debug(s"closingTxid=${closingTx.tx.txid} closingTx=${closingTx.tx}}")
|
||||||
(closingTx, closingSigned)
|
(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._
|
import commitments._
|
||||||
val lastCommitFeeSatoshi = commitments.commitInput.txOut.amount.amount - commitments.localCommit.publishableTxs.commitTx.tx.txOut.map(_.amount.amount).sum
|
val lastCommitFeeSatoshi = commitments.commitInput.txOut.amount.amount - commitments.localCommit.publishableTxs.commitTx.tx.txOut.map(_.amount.amount).sum
|
||||||
if (remoteClosingFee.amount > lastCommitFeeSatoshi) {
|
if (remoteClosingFee.amount > lastCommitFeeSatoshi) {
|
||||||
log.error(s"remote proposed a commit fee higher than the last commitment fee: remoteClosingFeeSatoshi=${remoteClosingFee.amount} lastCommitFeeSatoshi=$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)
|
throw new InvalidCloseFee(commitments.channelId, remoteClosingFee.amount)
|
||||||
}
|
}
|
||||||
val (closingTx, closingSigned) = makeClosingTx(commitments, localScriptPubkey, remoteScriptPubkey, remoteClosingFee)
|
val (closingTx, closingSigned) = makeClosingTx(keyManager, commitments, localScriptPubkey, remoteScriptPubkey, remoteClosingFee)
|
||||||
val signedClosingTx = Transactions.addSigs(closingTx, localParams.fundingPrivKey.publicKey, remoteParams.fundingPubKey, closingSigned.signature, remoteClosingSig)
|
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) }
|
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
|
* @param commitments our commitment data, which include payment preimages
|
||||||
* @return a list of transactions (one per HTLC that we can claim)
|
* @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._
|
import commitments._
|
||||||
require(localCommit.publishableTxs.commitTx.tx.txid == tx.txid, "txid mismatch, provided tx is not the current local commit tx")
|
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 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)
|
// 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
|
val feeratePerKwDelayed = Globals.feeratesPerKw.get.blocks_6
|
||||||
|
|
||||||
// first we will claim our main output as soon as the delay is over
|
// first we will claim our main output as soon as the delay is over
|
||||||
val mainDelayedTx = generateTx("main-delayed-output")(Try {
|
val mainDelayedTx = generateTx("main-delayed-output")(Try {
|
||||||
val claimDelayed = Transactions.makeClaimDelayedOutputTx(tx, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPrivkey.publicKey, localParams.defaultFinalScriptPubKey, feeratePerKwDelayed)
|
val claimDelayed = Transactions.makeClaimDelayedOutputTx(tx, Satoshi(localParams.dustLimitSatoshis), localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwDelayed)
|
||||||
val sig = Transactions.sign(claimDelayed, localDelayedPrivkey)
|
val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(localParams.channelKeyPath), localPerCommitmentPoint)
|
||||||
Transactions.addSigs(claimDelayed, sig)
|
Transactions.addSigs(claimDelayed, sig)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -278,8 +280,14 @@ object Helpers {
|
||||||
val htlcDelayedTxes = htlcTxes.flatMap {
|
val htlcDelayedTxes = htlcTxes.flatMap {
|
||||||
txinfo: TransactionWithInputInfo => generateTx("claim-delayed-output")(Try {
|
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
|
// 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 claimDelayed = Transactions.makeClaimDelayedOutputTx(
|
||||||
val sig = Transactions.sign(claimDelayed, localDelayedPrivkey)
|
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)
|
Transactions.addSigs(claimDelayed, sig)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -306,15 +314,18 @@ object Helpers {
|
||||||
* @param tx the remote commitment transaction that has just been published
|
* @param tx the remote commitment transaction that has just been published
|
||||||
* @return a list of transactions (one per HTLC that we can claim)
|
* @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}
|
import commitments.{commitInput, localParams, remoteParams}
|
||||||
require(remoteCommit.txid == tx.txid, "txid mismatch, provided tx is not the current remote commit tx")
|
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")
|
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 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
|
// we need to use a rather high fee for htlc-claim because we compete with the counterparty
|
||||||
val feeratePerKwHtlc = Globals.feeratesPerKw.get.block_1
|
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
|
// 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 {
|
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 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 tx = Transactions.makeClaimHtlcSuccessTx(remoteCommitTx.tx, Satoshi(localParams.dustLimitSatoshis), localHtlcPubkey, remoteHtlcPubkey, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, add, feeratePerKwHtlc)
|
||||||
val sig = Transactions.sign(tx, localHtlcPrivkey)
|
val sig = keyManager.sign(tx, keyManager.htlcPoint(localParams.channelKeyPath), remoteCommit.remotePerCommitmentPoint)
|
||||||
Transactions.addSigs(tx, sig, preimage)
|
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
|
// 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 {
|
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 tx = Transactions.makeClaimHtlcTimeoutTx(remoteCommitTx.tx, Satoshi(localParams.dustLimitSatoshis), localHtlcPubkey, remoteHtlcPubkey, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, add, feeratePerKwHtlc)
|
||||||
val sig = Transactions.sign(tx, localHtlcPrivkey)
|
val sig = keyManager.sign(tx, keyManager.htlcPoint(localParams.channelKeyPath), remoteCommit.remotePerCommitmentPoint)
|
||||||
Transactions.addSigs(tx, sig)
|
Transactions.addSigs(tx, sig)
|
||||||
})
|
})
|
||||||
}.toSeq.flatten
|
}.toSeq.flatten
|
||||||
|
@ -345,7 +356,7 @@ object Helpers {
|
||||||
// OPTIONAL: let's check transactions are actually spendable
|
// OPTIONAL: let's check transactions are actually spendable
|
||||||
//require(txes.forall(Transactions.checkSpendable(_).isSuccess), "the tx we produced are not 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 },
|
claimHtlcSuccessTxs = txes.toList.collect { case c: ClaimHtlcSuccessTx => c.tx },
|
||||||
claimHtlcTimeoutTxs = txes.toList.collect { case c: ClaimHtlcTimeoutTx => 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
|
* @param tx the remote commitment transaction that has just been published
|
||||||
* @return a list of transactions (one per HTLC that we can claim)
|
* @return a list of transactions (one per HTLC that we can claim)
|
||||||
*/
|
*/
|
||||||
def claimRemoteCommitMainOutput(commitments: Commitments, remotePerCommitmentPoint: Point, tx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = {
|
def claimRemoteCommitMainOutput(keyManager: KeyManager, commitments: Commitments, remotePerCommitmentPoint: Point, tx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = {
|
||||||
val localPaymentPrivkey = Generators.derivePrivKey(commitments.localParams.paymentKey, remotePerCommitmentPoint)
|
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)
|
// 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 feeratePerKwMain = Globals.feeratesPerKw.get.blocks_6
|
||||||
|
|
||||||
val mainTx = generateTx("claim-p2wpkh-output")(Try {
|
val mainTx = generateTx("claim-p2wpkh-output")(Try {
|
||||||
val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, Satoshi(commitments.localParams.dustLimitSatoshis),
|
val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, Satoshi(commitments.localParams.dustLimitSatoshis),
|
||||||
localPaymentPrivkey.publicKey, commitments.localParams.defaultFinalScriptPubKey, feeratePerKwMain)
|
localPubkey, commitments.localParams.defaultFinalScriptPubKey, feeratePerKwMain)
|
||||||
val sig = Transactions.sign(claimMain, localPaymentPrivkey)
|
val sig = keyManager.sign(claimMain, keyManager.paymentPoint(commitments.localParams.channelKeyPath), remotePerCommitmentPoint)
|
||||||
Transactions.addSigs(claimMain, localPaymentPrivkey.publicKey, sig)
|
Transactions.addSigs(claimMain, localPubkey, sig)
|
||||||
})
|
})
|
||||||
|
|
||||||
RemoteCommitPublished(
|
RemoteCommitPublished(
|
||||||
|
@ -393,12 +404,12 @@ object Helpers {
|
||||||
*
|
*
|
||||||
* @return a [[RevokedCommitPublished]] object containing penalty transactions if the tx is a revoked commitment
|
* @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._
|
import commitments._
|
||||||
require(tx.txIn.size == 1, "commitment tx should have 1 input")
|
require(tx.txIn.size == 1, "commitment tx should have 1 input")
|
||||||
val obscuredTxNumber = Transactions.decodeTxNumber(tx.txIn(0).sequence, tx.lockTime)
|
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
|
// 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")
|
require(txnumber <= 0xffffffffffffL, "txnumber must be lesser than 48 bits long")
|
||||||
log.warning(s"counterparty has published revoked commit txnumber=$txnumber")
|
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
|
// 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 remotePerCommitmentPoint = remotePerCommitmentSecret.toPoint
|
||||||
|
|
||||||
val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint)
|
val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint)
|
||||||
val remoteRevocationPrivkey = Generators.revocationPrivKey(localParams.revocationSecret, remotePerCommitmentSecret)
|
val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentSecret.toPoint)
|
||||||
val localPrivkey = Generators.derivePrivKey(localParams.paymentKey, remotePerCommitmentPoint)
|
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)
|
// 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 feeratePerKwMain = Globals.feeratesPerKw.get.blocks_6
|
||||||
|
@ -418,16 +429,16 @@ object Helpers {
|
||||||
|
|
||||||
// first we will claim our main output right away
|
// first we will claim our main output right away
|
||||||
val mainTx = generateTx("claim-p2wpkh-output")(Try {
|
val mainTx = generateTx("claim-p2wpkh-output")(Try {
|
||||||
val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, Satoshi(localParams.dustLimitSatoshis), localPrivkey.publicKey, localParams.defaultFinalScriptPubKey, feeratePerKwMain)
|
val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, Satoshi(localParams.dustLimitSatoshis), localPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwMain)
|
||||||
val sig = Transactions.sign(claimMain, localPrivkey)
|
val sig = keyManager.sign(claimMain, keyManager.paymentPoint(localParams.channelKeyPath), remotePerCommitmentPoint)
|
||||||
Transactions.addSigs(claimMain, localPrivkey.publicKey, sig)
|
Transactions.addSigs(claimMain, localPubkey, sig)
|
||||||
})
|
})
|
||||||
|
|
||||||
// then we punish them by stealing their main output
|
// then we punish them by stealing their main output
|
||||||
val mainPenaltyTx = generateTx("main-penalty")(Try {
|
val mainPenaltyTx = generateTx("main-penalty")(Try {
|
||||||
// TODO: we should use the current fee rate, not the initial fee rate that we get from localParams
|
// 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 txinfo = Transactions.makeMainPenaltyTx(tx, Satoshi(localParams.dustLimitSatoshis), remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, feeratePerKwPenalty)
|
||||||
val sig = Transactions.sign(txinfo, remoteRevocationPrivkey)
|
val sig = keyManager.sign(txinfo, keyManager.revocationPoint(localParams.channelKeyPath), remotePerCommitmentSecret)
|
||||||
Transactions.addSigs(txinfo, sig)
|
Transactions.addSigs(txinfo, sig)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,19 @@
|
||||||
package fr.acinq.eclair.io
|
package fr.acinq.eclair.io
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
|
import java.nio.ByteOrder
|
||||||
|
|
||||||
import akka.actor.{ActorRef, FSM, OneForOneStrategy, PoisonPill, Props, Status, SupervisorStrategy, Terminated}
|
import akka.actor.{ActorRef, FSM, OneForOneStrategy, PoisonPill, Props, Status, SupervisorStrategy, Terminated}
|
||||||
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
|
import fr.acinq.bitcoin.Crypto.PublicKey
|
||||||
import fr.acinq.bitcoin.{BinaryData, Crypto, DeterministicWallet, MilliSatoshi, Satoshi}
|
import fr.acinq.bitcoin.{BinaryData, DeterministicWallet, MilliSatoshi, Protocol, Satoshi}
|
||||||
import fr.acinq.eclair._
|
|
||||||
import fr.acinq.eclair.blockchain.EclairWallet
|
import fr.acinq.eclair.blockchain.EclairWallet
|
||||||
import fr.acinq.eclair.channel._
|
import fr.acinq.eclair.channel._
|
||||||
import fr.acinq.eclair.crypto.TransportHandler
|
import fr.acinq.eclair.crypto.TransportHandler
|
||||||
import fr.acinq.eclair.crypto.TransportHandler.Listener
|
import fr.acinq.eclair.crypto.TransportHandler.Listener
|
||||||
import fr.acinq.eclair.router._
|
import fr.acinq.eclair.router._
|
||||||
import fr.acinq.eclair.wire
|
import fr.acinq.eclair.{wire, _}
|
||||||
import fr.acinq.eclair.wire.LightningMessage
|
import fr.acinq.eclair.wire._
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.util.Random
|
import scala.util.Random
|
||||||
|
@ -358,31 +359,27 @@ object Peer {
|
||||||
|
|
||||||
// @formatter:on
|
// @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 = {
|
def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: BinaryData, isFunder: Boolean, fundingSatoshis: Long): LocalParams = {
|
||||||
// all secrets are generated from the main seed
|
val entropy = new Array[Byte](16)
|
||||||
// TODO: check this
|
secureRandom.nextBytes(entropy)
|
||||||
val keyIndex = secureRandom.nextInt(1000).toLong
|
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(
|
LocalParams(
|
||||||
nodeId = nodeParams.privateKey.publicKey,
|
nodeParams.nodeId,
|
||||||
|
channelKeyPath,
|
||||||
dustLimitSatoshis = nodeParams.dustLimitSatoshis,
|
dustLimitSatoshis = nodeParams.dustLimitSatoshis,
|
||||||
maxHtlcValueInFlightMsat = nodeParams.maxHtlcValueInFlightMsat,
|
maxHtlcValueInFlightMsat = nodeParams.maxHtlcValueInFlightMsat,
|
||||||
channelReserveSatoshis = (nodeParams.reserveToFundingRatio * fundingSatoshis).toLong,
|
channelReserveSatoshis = (nodeParams.reserveToFundingRatio * fundingSatoshis).toLong,
|
||||||
htlcMinimumMsat = nodeParams.htlcMinimumMsat,
|
htlcMinimumMsat = nodeParams.htlcMinimumMsat,
|
||||||
toSelfDelay = nodeParams.delayBlocks,
|
toSelfDelay = nodeParams.delayBlocks,
|
||||||
maxAcceptedHtlcs = nodeParams.maxAcceptedHtlcs,
|
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,
|
defaultFinalScriptPubKey = defaultFinalScriptPubKey,
|
||||||
shaSeed = Crypto.sha256(generateKey(nodeParams, keyIndex :: 5L :: Nil).toBin), // TODO: check that
|
|
||||||
isFunder = isFunder,
|
isFunder = isFunder,
|
||||||
globalFeatures = nodeParams.globalFeatures,
|
globalFeatures = nodeParams.globalFeatures,
|
||||||
localFeatures = nodeParams.localFeatures)
|
localFeatures = nodeParams.localFeatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
package fr.acinq.eclair.wire
|
package fr.acinq.eclair.wire
|
||||||
|
|
||||||
|
import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, KeyPath}
|
||||||
import fr.acinq.bitcoin.{BinaryData, OutPoint, Transaction, TxOut}
|
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.crypto.ShaChain
|
||||||
import fr.acinq.eclair.payment.{Local, Origin, Relayed}
|
import fr.acinq.eclair.payment.{Local, Origin, Relayed}
|
||||||
import fr.acinq.eclair.transactions.Transactions._
|
import fr.acinq.eclair.transactions.Transactions._
|
||||||
|
@ -10,29 +11,33 @@ import fr.acinq.eclair.wire.LightningMessageCodecs._
|
||||||
import grizzled.slf4j.Logging
|
import grizzled.slf4j.Logging
|
||||||
import scodec.bits.{BitVector, ByteVector}
|
import scodec.bits.{BitVector, ByteVector}
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
import scodec.{Attempt, Codec, DecodeResult}
|
import scodec.{Attempt, Codec}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 02/06/2017.
|
* Created by PM on 02/06/2017.
|
||||||
*/
|
*/
|
||||||
object ChannelCodecs extends Logging {
|
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] = (
|
val localParamsCodec: Codec[LocalParams] = (
|
||||||
("nodeId" | publicKey) ::
|
("nodeId" | publicKey) ::
|
||||||
|
("channelPath" | keyPathCodec) ::
|
||||||
("dustLimitSatoshis" | uint64) ::
|
("dustLimitSatoshis" | uint64) ::
|
||||||
("maxHtlcValueInFlightMsat" | uint64ex) ::
|
("maxHtlcValueInFlightMsat" | uint64ex) ::
|
||||||
("channelReserveSatoshis" | uint64) ::
|
("channelReserveSatoshis" | uint64) ::
|
||||||
("htlcMinimumMsat" | uint64) ::
|
("htlcMinimumMsat" | uint64) ::
|
||||||
("toSelfDelay" | uint16) ::
|
("toSelfDelay" | uint16) ::
|
||||||
("maxAcceptedHtlcs" | uint16) ::
|
("maxAcceptedHtlcs" | uint16) ::
|
||||||
("fundingPrivKey" | privateKey) ::
|
|
||||||
("revocationSecret" | scalar) ::
|
|
||||||
("paymentKey" | scalar) ::
|
|
||||||
("delayedPaymentKey" | scalar) ::
|
|
||||||
("htlcKey" | scalar) ::
|
|
||||||
("defaultFinalScriptPubKey" | varsizebinarydata) ::
|
|
||||||
("shaSeed" | varsizebinarydata) ::
|
|
||||||
("isFunder" | bool) ::
|
("isFunder" | bool) ::
|
||||||
|
("defaultFinalScriptPubKey" | varsizebinarydata) ::
|
||||||
("globalFeatures" | varsizebinarydata) ::
|
("globalFeatures" | varsizebinarydata) ::
|
||||||
("localFeatures" | varsizebinarydata)).as[LocalParams]
|
("localFeatures" | varsizebinarydata)).as[LocalParams]
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,9 @@ import java.net.InetSocketAddress
|
||||||
import java.sql.DriverManager
|
import java.sql.DriverManager
|
||||||
|
|
||||||
import fr.acinq.bitcoin.Crypto.PrivateKey
|
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.NodeParams.BITCOIND
|
||||||
|
import fr.acinq.eclair.crypto.LocalKeyManager
|
||||||
import fr.acinq.eclair.db.sqlite._
|
import fr.acinq.eclair.db.sqlite._
|
||||||
import fr.acinq.eclair.io.Peer
|
import fr.acinq.eclair.io.Peer
|
||||||
import fr.acinq.eclair.wire.Color
|
import fr.acinq.eclair.wire.Color
|
||||||
|
@ -22,15 +23,13 @@ object TestConstants {
|
||||||
|
|
||||||
object Alice {
|
object Alice {
|
||||||
val seed = BinaryData("01" * 32)
|
val seed = BinaryData("01" * 32)
|
||||||
val master = DeterministicWallet.generate(seed)
|
val keyManager = new LocalKeyManager(seed)
|
||||||
val extendedPrivateKey = DeterministicWallet.derivePrivateKey(master, DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(0) :: Nil)
|
|
||||||
|
|
||||||
def sqlite = DriverManager.getConnection("jdbc:sqlite::memory:")
|
def sqlite = DriverManager.getConnection("jdbc:sqlite::memory:")
|
||||||
|
|
||||||
// This is a function, and not a val! When called will return a new NodeParams
|
// This is a function, and not a val! When called will return a new NodeParams
|
||||||
def nodeParams = NodeParams(
|
def nodeParams = NodeParams(
|
||||||
extendedPrivateKey = extendedPrivateKey,
|
keyManager = keyManager,
|
||||||
privateKey = extendedPrivateKey.privateKey,
|
|
||||||
alias = "alice",
|
alias = "alice",
|
||||||
color = Color(1, 2, 3),
|
color = Color(1, 2, 3),
|
||||||
publicAddresses = new InetSocketAddress("localhost", 9731) :: Nil,
|
publicAddresses = new InetSocketAddress("localhost", 9731) :: Nil,
|
||||||
|
@ -79,14 +78,12 @@ object TestConstants {
|
||||||
|
|
||||||
object Bob {
|
object Bob {
|
||||||
val seed = BinaryData("02" * 32)
|
val seed = BinaryData("02" * 32)
|
||||||
val master = DeterministicWallet.generate(seed)
|
val keyManager = new LocalKeyManager(seed)
|
||||||
val extendedPrivateKey = DeterministicWallet.derivePrivateKey(master, DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(0) :: Nil)
|
|
||||||
|
|
||||||
def sqlite = DriverManager.getConnection("jdbc:sqlite::memory:")
|
def sqlite = DriverManager.getConnection("jdbc:sqlite::memory:")
|
||||||
|
|
||||||
def nodeParams = NodeParams(
|
def nodeParams = NodeParams(
|
||||||
extendedPrivateKey = extendedPrivateKey,
|
keyManager = keyManager,
|
||||||
privateKey = extendedPrivateKey.privateKey,
|
|
||||||
alias = "bob",
|
alias = "bob",
|
||||||
color = Color(4, 5, 6),
|
color = Color(4, 5, 6),
|
||||||
publicAddresses = new InetSocketAddress("localhost", 9732) :: Nil,
|
publicAddresses = new InetSocketAddress("localhost", 9732) :: Nil,
|
||||||
|
|
|
@ -1871,7 +1871,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures]
|
val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures]
|
||||||
import initialState.commitments.localParams
|
import initialState.commitments.localParams
|
||||||
import initialState.commitments.remoteParams
|
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)
|
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
|
// actual test starts here
|
||||||
bob2alice.forward(alice)
|
bob2alice.forward(alice)
|
||||||
|
@ -1890,7 +1890,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures]
|
val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures]
|
||||||
import initialState.commitments.localParams
|
import initialState.commitments.localParams
|
||||||
import initialState.commitments.remoteParams
|
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)
|
bob2alice.forward(alice)
|
||||||
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement === Some(channelAnn))
|
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement === Some(channelAnn))
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
package fr.acinq.eclair.channel.states.e
|
package fr.acinq.eclair.channel.states.e
|
||||||
|
|
||||||
import akka.testkit.{TestFSMRef, TestProbe}
|
import akka.testkit.{TestFSMRef, TestProbe}
|
||||||
import fr.acinq.bitcoin.{BinaryData, ScriptFlags, Transaction}
|
|
||||||
import fr.acinq.bitcoin.Crypto.Scalar
|
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.blockchain.{PublishAsap, WatchEventSpent}
|
||||||
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||||
import fr.acinq.eclair.channel.{Data, State, _}
|
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.wire._
|
||||||
|
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.scalatest.junit.JUnitRunner
|
import org.scalatest.junit.JUnitRunner
|
||||||
|
|
||||||
|
@ -58,8 +58,8 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
val bobCommitments = bob.stateData.asInstanceOf[HasCommitments].commitments
|
val bobCommitments = bob.stateData.asInstanceOf[HasCommitments].commitments
|
||||||
val aliceCommitments = alice.stateData.asInstanceOf[HasCommitments].commitments
|
val aliceCommitments = alice.stateData.asInstanceOf[HasCommitments].commitments
|
||||||
|
|
||||||
val bobCurrentPerCommitmentPoint = Generators.perCommitPoint(bobCommitments.localParams.shaSeed, bobCommitments.localCommit.index)
|
val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.commitmentPoint(bobCommitments.localParams.channelKeyPath, bobCommitments.localCommit.index)
|
||||||
val aliceCurrentPerCommitmentPoint = Generators.perCommitPoint(aliceCommitments.localParams.shaSeed, aliceCommitments.localCommit.index)
|
val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.commitmentPoint(aliceCommitments.localParams.channelKeyPath, aliceCommitments.localCommit.index)
|
||||||
|
|
||||||
|
|
||||||
// a didn't receive any update or sig
|
// 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 bobCommitments = bob.stateData.asInstanceOf[HasCommitments].commitments
|
||||||
val aliceCommitments = alice.stateData.asInstanceOf[HasCommitments].commitments
|
val aliceCommitments = alice.stateData.asInstanceOf[HasCommitments].commitments
|
||||||
|
|
||||||
val bobCurrentPerCommitmentPoint = Generators.perCommitPoint(bobCommitments.localParams.shaSeed, bobCommitments.localCommit.index)
|
val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.commitmentPoint(bobCommitments.localParams.channelKeyPath, bobCommitments.localCommit.index)
|
||||||
val aliceCurrentPerCommitmentPoint = Generators.perCommitPoint(aliceCommitments.localParams.shaSeed, aliceCommitments.localCommit.index)
|
val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.commitmentPoint(aliceCommitments.localParams.channelKeyPath, aliceCommitments.localCommit.index)
|
||||||
|
|
||||||
// a didn't receive the sig
|
// 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)))
|
val ab_reestablish = alice2bob.expectMsg(ChannelReestablish(ab_add_0.channelId, 1, 0, Some(Scalar(Sphinx zeroes 32)), Some(aliceCurrentPerCommitmentPoint)))
|
||||||
|
|
|
@ -3,6 +3,7 @@ package fr.acinq.eclair.channel.states.g
|
||||||
import akka.actor.Status.Failure
|
import akka.actor.Status.Failure
|
||||||
import akka.testkit.{TestFSMRef, TestProbe}
|
import akka.testkit.{TestFSMRef, TestProbe}
|
||||||
import fr.acinq.bitcoin.Satoshi
|
import fr.acinq.bitcoin.Satoshi
|
||||||
|
import fr.acinq.eclair.TestConstants.Bob
|
||||||
import fr.acinq.eclair.blockchain._
|
import fr.acinq.eclair.blockchain._
|
||||||
import fr.acinq.eclair.blockchain.fee.FeeratesPerKw
|
import fr.acinq.eclair.blockchain.fee.FeeratesPerKw
|
||||||
import fr.acinq.eclair.channel.Helpers.Closing
|
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
|
// 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]
|
val d = bob.stateData.asInstanceOf[DATA_NEGOTIATING]
|
||||||
implicit val log = bob.underlyingActor.implicitLog
|
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)
|
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobClosingTx)
|
||||||
alice2blockchain.expectMsgType[PublishAsap]
|
alice2blockchain.expectMsgType[PublishAsap]
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
package fr.acinq.eclair.db
|
package fr.acinq.eclair.db
|
||||||
|
|
||||||
import fr.acinq.bitcoin.Crypto.{PrivateKey, Scalar}
|
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.Helpers.Funding
|
||||||
import fr.acinq.eclair.channel._
|
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.payment.{Local, Relayed}
|
||||||
|
import fr.acinq.eclair.{UInt64, randomKey}
|
||||||
import fr.acinq.eclair.router.Announcements
|
import fr.acinq.eclair.router.Announcements
|
||||||
import fr.acinq.eclair.transactions.Transactions.CommitTx
|
import fr.acinq.eclair.transactions.Transactions.CommitTx
|
||||||
import fr.acinq.eclair.transactions._
|
import fr.acinq.eclair.transactions._
|
||||||
import fr.acinq.eclair.wire.{ChannelCodecs, ChannelUpdate, UpdateAddHtlc}
|
import fr.acinq.eclair.wire.{ChannelCodecs, UpdateAddHtlc}
|
||||||
import fr.acinq.eclair.{UInt64, randomKey}
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
import org.scalatest.junit.JUnitRunner
|
||||||
|
@ -33,21 +33,17 @@ class ChannelStateSpec extends FunSuite {
|
||||||
}
|
}
|
||||||
|
|
||||||
object ChannelStateSpec {
|
object ChannelStateSpec {
|
||||||
|
val keyManager = new LocalKeyManager("01" * 32)
|
||||||
val localParams = LocalParams(
|
val localParams = LocalParams(
|
||||||
nodeId = randomKey.publicKey,
|
keyManager.nodeId,
|
||||||
|
channelKeyPath = DeterministicWallet.KeyPath(Seq(42)),
|
||||||
dustLimitSatoshis = Satoshi(546).toLong,
|
dustLimitSatoshis = Satoshi(546).toLong,
|
||||||
maxHtlcValueInFlightMsat = UInt64(50),
|
maxHtlcValueInFlightMsat = UInt64(50),
|
||||||
channelReserveSatoshis = 10000,
|
channelReserveSatoshis = 10000,
|
||||||
htlcMinimumMsat = 50000,
|
htlcMinimumMsat = 50000,
|
||||||
toSelfDelay = 144,
|
toSelfDelay = 144,
|
||||||
maxAcceptedHtlcs = 50,
|
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,
|
defaultFinalScriptPubKey = Nil,
|
||||||
shaSeed = BinaryData("05" * 32),
|
|
||||||
isFunder = true,
|
isFunder = true,
|
||||||
globalFeatures = "foo".getBytes(),
|
globalFeatures = "foo".getBytes(),
|
||||||
localFeatures = "bar".getBytes())
|
localFeatures = "bar".getBytes())
|
||||||
|
@ -86,7 +82,7 @@ object ChannelStateSpec {
|
||||||
|
|
||||||
val fundingTx = Transaction.read("0200000001adbb20ea41a8423ea937e76e8151636bf6093b70eaff942930d20576600521fd000000006b48304502210090587b6201e166ad6af0227d3036a9454223d49a1f11839c1a362184340ef0240220577f7cd5cca78719405cbf1de7414ac027f0239ef6e214c90fcaab0454d84b3b012103535b32d5eb0a6ed0982a0479bbadc9868d9836f6ba94dd5a63be16d875069184ffffffff028096980000000000220020c015c4a6be010e21657068fc2e6a9d02b27ebe4d490a25846f7237f104d1a3cd20256d29010000001600143ca33c2e4446f4a305f23c80df8ad1afdcf652f900000000")
|
val fundingTx = Transaction.read("0200000001adbb20ea41a8423ea937e76e8151636bf6093b70eaff942930d20576600521fd000000006b48304502210090587b6201e166ad6af0227d3036a9454223d49a1f11839c1a362184340ef0240220577f7cd5cca78719405cbf1de7414ac027f0239ef6e214c90fcaab0454d84b3b012103535b32d5eb0a6ed0982a0479bbadc9868d9836f6ba94dd5a63be16d875069184ffffffff028096980000000000220020c015c4a6be010e21657068fc2e6a9d02b27ebe4d490a25846f7237f104d1a3cd20256d29010000001600143ca33c2e4446f4a305f23c80df8ad1afdcf652f900000000")
|
||||||
val fundingAmount = fundingTx.txOut(0).amount
|
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 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)
|
val remoteCommit = RemoteCommit(0, CommitmentSpec(htlcs.toSet, 1500, 50000, 700000), BinaryData("0303030303030303030303030303030303030303030303030303030303030303"), Scalar(BinaryData("04" * 32)).toPoint)
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
package fr.acinq.eclair.payment
|
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.channel.Channel
|
||||||
import fr.acinq.eclair.crypto.Sphinx
|
import fr.acinq.eclair.crypto.Sphinx
|
||||||
import fr.acinq.eclair.crypto.Sphinx.{PacketAndSecrets, ParsedPacket}
|
import fr.acinq.eclair.crypto.Sphinx.{PacketAndSecrets, ParsedPacket}
|
||||||
import fr.acinq.eclair.payment.PaymentLifecycle._
|
import fr.acinq.eclair.payment.PaymentLifecycle._
|
||||||
import fr.acinq.eclair.{randomKey, nodeFee}
|
|
||||||
import fr.acinq.eclair.router.Hop
|
import fr.acinq.eclair.router.Hop
|
||||||
import fr.acinq.eclair.wire.{ChannelUpdate, LightningMessageCodecs, PerHopPayload}
|
import fr.acinq.eclair.wire.{ChannelUpdate, LightningMessageCodecs, PerHopPayload}
|
||||||
|
import fr.acinq.eclair.{TestConstants, nodeFee, randomBytes}
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
import org.scalatest.junit.JUnitRunner
|
||||||
|
@ -54,25 +55,25 @@ class HtlcGenerationSpec extends FunSuite {
|
||||||
assert(packet_b.serialize.size === Sphinx.PacketLength)
|
assert(packet_b.serialize.size === Sphinx.PacketLength)
|
||||||
|
|
||||||
// let's peel the onion
|
// 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
|
val payload_b = LightningMessageCodecs.perHopPayloadCodec.decode(BitVector(bin_b.data)).require.value
|
||||||
assert(packet_c.serialize.size === Sphinx.PacketLength)
|
assert(packet_c.serialize.size === Sphinx.PacketLength)
|
||||||
assert(payload_b.amtToForward === amount_bc)
|
assert(payload_b.amtToForward === amount_bc)
|
||||||
assert(payload_b.outgoingCltvValue === expiry_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
|
val payload_c = LightningMessageCodecs.perHopPayloadCodec.decode(BitVector(bin_c.data)).require.value
|
||||||
assert(packet_d.serialize.size === Sphinx.PacketLength)
|
assert(packet_d.serialize.size === Sphinx.PacketLength)
|
||||||
assert(payload_c.amtToForward === amount_cd)
|
assert(payload_c.amtToForward === amount_cd)
|
||||||
assert(payload_c.outgoingCltvValue === expiry_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
|
val payload_d = LightningMessageCodecs.perHopPayloadCodec.decode(BitVector(bin_d.data)).require.value
|
||||||
assert(packet_e.serialize.size === Sphinx.PacketLength)
|
assert(packet_e.serialize.size === Sphinx.PacketLength)
|
||||||
assert(payload_d.amtToForward === amount_de)
|
assert(payload_d.amtToForward === amount_de)
|
||||||
assert(payload_d.outgoingCltvValue === expiry_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
|
val payload_e = LightningMessageCodecs.perHopPayloadCodec.decode(BitVector(bin_e.data)).require.value
|
||||||
assert(packet_random.serialize.size === Sphinx.PacketLength)
|
assert(packet_random.serialize.size === Sphinx.PacketLength)
|
||||||
assert(payload_e.amtToForward === finalAmountMsat)
|
assert(payload_e.amtToForward === finalAmountMsat)
|
||||||
|
@ -89,25 +90,25 @@ class HtlcGenerationSpec extends FunSuite {
|
||||||
assert(add.onion.length === Sphinx.PacketLength)
|
assert(add.onion.length === Sphinx.PacketLength)
|
||||||
|
|
||||||
// let's peel the onion
|
// 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
|
val payload_b = LightningMessageCodecs.perHopPayloadCodec.decode(BitVector(bin_b.data)).require.value
|
||||||
assert(packet_c.serialize.size === Sphinx.PacketLength)
|
assert(packet_c.serialize.size === Sphinx.PacketLength)
|
||||||
assert(payload_b.amtToForward === amount_bc)
|
assert(payload_b.amtToForward === amount_bc)
|
||||||
assert(payload_b.outgoingCltvValue === expiry_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
|
val payload_c = LightningMessageCodecs.perHopPayloadCodec.decode(BitVector(bin_c.data)).require.value
|
||||||
assert(packet_d.serialize.size === Sphinx.PacketLength)
|
assert(packet_d.serialize.size === Sphinx.PacketLength)
|
||||||
assert(payload_c.amtToForward === amount_cd)
|
assert(payload_c.amtToForward === amount_cd)
|
||||||
assert(payload_c.outgoingCltvValue === expiry_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
|
val payload_d = LightningMessageCodecs.perHopPayloadCodec.decode(BitVector(bin_d.data)).require.value
|
||||||
assert(packet_e.serialize.size === Sphinx.PacketLength)
|
assert(packet_e.serialize.size === Sphinx.PacketLength)
|
||||||
assert(payload_d.amtToForward === amount_de)
|
assert(payload_d.amtToForward === amount_de)
|
||||||
assert(payload_d.outgoingCltvValue === expiry_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
|
val payload_e = LightningMessageCodecs.perHopPayloadCodec.decode(BitVector(bin_e.data)).require.value
|
||||||
assert(packet_random.serialize.size === Sphinx.PacketLength)
|
assert(packet_random.serialize.size === Sphinx.PacketLength)
|
||||||
assert(payload_e.amtToForward === finalAmountMsat)
|
assert(payload_e.amtToForward === finalAmountMsat)
|
||||||
|
@ -123,7 +124,7 @@ class HtlcGenerationSpec extends FunSuite {
|
||||||
assert(add.onion.size === Sphinx.PacketLength)
|
assert(add.onion.size === Sphinx.PacketLength)
|
||||||
|
|
||||||
// let's peel the onion
|
// 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
|
val payload_b = LightningMessageCodecs.perHopPayloadCodec.decode(BitVector(bin_b.data)).require.value
|
||||||
assert(packet_random.serialize.size === Sphinx.PacketLength)
|
assert(packet_random.serialize.size === Sphinx.PacketLength)
|
||||||
assert(payload_b.amtToForward === finalAmountMsat)
|
assert(payload_b.amtToForward === finalAmountMsat)
|
||||||
|
@ -133,9 +134,12 @@ class HtlcGenerationSpec extends FunSuite {
|
||||||
}
|
}
|
||||||
|
|
||||||
object HtlcGenerationSpec {
|
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 (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 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_ab = defaultChannelUpdate.copy(shortChannelId = 1, cltvExpiryDelta = 4, feeBaseMsat = 642000, feeProportionalMillionths = 7)
|
||||||
val channelUpdate_bc = defaultChannelUpdate.copy(shortChannelId = 2, cltvExpiryDelta = 5, feeBaseMsat = 153000, feeProportionalMillionths = 4)
|
val channelUpdate_bc = defaultChannelUpdate.copy(shortChannelId = 2, cltvExpiryDelta = 5, feeBaseMsat = 153000, feeProportionalMillionths = 4)
|
||||||
|
|
|
@ -34,7 +34,8 @@ class RelayerSpec extends TestkitBaseClass {
|
||||||
val register = TestProbe()
|
val register = TestProbe()
|
||||||
val paymentHandler = TestProbe()
|
val paymentHandler = TestProbe()
|
||||||
// we are node B in the route A -> B -> C -> ....
|
// 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))
|
test((relayer, register, paymentHandler))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package fr.acinq.eclair.wire
|
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.channel.{LocalParams, RemoteParams}
|
||||||
import fr.acinq.eclair.crypto.Sphinx
|
import fr.acinq.eclair.crypto.Sphinx
|
||||||
import fr.acinq.eclair.payment.{Local, Relayed}
|
import fr.acinq.eclair.payment.{Local, Relayed}
|
||||||
|
@ -25,22 +26,31 @@ class ChannelCodecsSpec extends FunSuite {
|
||||||
bin
|
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") {
|
test("encode/decode localparams") {
|
||||||
val o = LocalParams(
|
val o = LocalParams(
|
||||||
nodeId = randomKey.publicKey,
|
nodeId = randomKey.publicKey,
|
||||||
|
channelKeyPath = DeterministicWallet.KeyPath(Seq(42)),
|
||||||
dustLimitSatoshis = Random.nextInt(Int.MaxValue),
|
dustLimitSatoshis = Random.nextInt(Int.MaxValue),
|
||||||
maxHtlcValueInFlightMsat = UInt64(Random.nextInt(Int.MaxValue)),
|
maxHtlcValueInFlightMsat = UInt64(Random.nextInt(Int.MaxValue)),
|
||||||
channelReserveSatoshis = Random.nextInt(Int.MaxValue),
|
channelReserveSatoshis = Random.nextInt(Int.MaxValue),
|
||||||
htlcMinimumMsat = Random.nextInt(Int.MaxValue),
|
htlcMinimumMsat = Random.nextInt(Int.MaxValue),
|
||||||
toSelfDelay = Random.nextInt(Short.MaxValue),
|
toSelfDelay = Random.nextInt(Short.MaxValue),
|
||||||
maxAcceptedHtlcs = 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)),
|
defaultFinalScriptPubKey = randomBytes(10 + Random.nextInt(200)),
|
||||||
shaSeed = randomBytes(32),
|
|
||||||
isFunder = Random.nextBoolean(),
|
isFunder = Random.nextBoolean(),
|
||||||
globalFeatures = randomBytes(256),
|
globalFeatures = randomBytes(256),
|
||||||
localFeatures = randomBytes(256))
|
localFeatures = randomBytes(256))
|
||||||
|
|
Loading…
Add table
Reference in a new issue