Skip to content

Commit

Permalink
Add a "funding pubkey path" option to the channel version field
Browse files Browse the repository at this point in the history
This option is checked when we need to compute channel keys. For old channels it won't be set, and we always
set it for new ones.
  • Loading branch information
sstone committed Sep 16, 2019
1 parent ea3551b commit 823aaed
Show file tree
Hide file tree
Showing 19 changed files with 73 additions and 119 deletions.
30 changes: 15 additions & 15 deletions eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,11 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
startWith(WAIT_FOR_INIT_INTERNAL, Nothing)

when(WAIT_FOR_INIT_INTERNAL)(handleExceptions {
case Event(initFunder@INPUT_INIT_FUNDER(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, localParams, remote, _, channelFlags, _), Nothing) =>
case Event(initFunder@INPUT_INIT_FUNDER(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, localParams, remote, _, channelFlags, channelVersion), Nothing) =>
context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, isFunder = true, temporaryChannelId, initialFeeratePerKw, Some(fundingTxFeeratePerKw)))
forwarder ! remote
val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey
val channelKeyPath = localParams.channelKeyPath(nodeParams.keyManager)
val channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
val open = OpenChannel(nodeParams.chainHash,
temporaryChannelId = temporaryChannelId,
fundingSatoshis = fundingSatoshis,
Expand All @@ -161,7 +161,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
feeratePerKw = initialFeeratePerKw,
toSelfDelay = localParams.toSelfDelay,
maxAcceptedHtlcs = localParams.maxAcceptedHtlcs,
fundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey,
fundingPubkey = fundingPubKey,
revocationBasepoint = keyManager.revocationPoint(channelKeyPath).publicKey,
paymentBasepoint = keyManager.paymentPoint(channelKeyPath).publicKey,
delayedPaymentBasepoint = keyManager.delayedPaymentPoint(channelKeyPath).publicKey,
Expand Down Expand Up @@ -273,7 +273,9 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
case Failure(t) => handleLocalError(t, d, Some(open))
case Success(_) =>
context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, isFunder = false, open.temporaryChannelId, open.feeratePerKw, None))
val channelKeyPath = localParams.channelKeyPath(nodeParams.keyManager)
val fundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey
val channelVersion = ChannelVersion.STANDARD | ChannelVersion.USE_PUBKEY_KEYPATH
val channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
// TODO: maybe also check uniqueness of temporary channel id
val minimumDepth = nodeParams.minDepthBlocks
val accept = AcceptChannel(temporaryChannelId = open.temporaryChannelId,
Expand All @@ -284,7 +286,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
htlcMinimumMsat = localParams.htlcMinimum,
toSelfDelay = localParams.toSelfDelay,
maxAcceptedHtlcs = localParams.maxAcceptedHtlcs,
fundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey,
fundingPubkey = fundingPubkey,
revocationBasepoint = keyManager.revocationPoint(channelKeyPath).publicKey,
paymentBasepoint = keyManager.paymentPoint(channelKeyPath).publicKey,
delayedPaymentBasepoint = keyManager.delayedPaymentPoint(channelKeyPath).publicKey,
Expand All @@ -306,7 +308,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
globalFeatures = remoteInit.globalFeatures,
localFeatures = remoteInit.localFeatures)
log.debug(s"remote params: $remoteParams")
goto(WAIT_FOR_FUNDING_CREATED) using DATA_WAIT_FOR_FUNDING_CREATED(open.temporaryChannelId, localParams, remoteParams, open.fundingSatoshis, open.pushMsat, open.feeratePerKw, open.firstPerCommitmentPoint, open.channelFlags, ChannelVersion.STANDARD, accept) sending accept
goto(WAIT_FOR_FUNDING_CREATED) using DATA_WAIT_FOR_FUNDING_CREATED(open.temporaryChannelId, localParams, remoteParams, open.fundingSatoshis, open.pushMsat, open.feeratePerKw, open.firstPerCommitmentPoint, open.channelFlags, channelVersion, accept) sending accept
}

case Event(CMD_CLOSE(_), _) => goto(CLOSED) replying "ok"
Expand Down Expand Up @@ -365,7 +367,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
when(WAIT_FOR_FUNDING_INTERNAL)(handleExceptions {
case Event(MakeFundingTxResponse(fundingTx, fundingTxOutputIndex, fundingTxFee), DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, remoteFirstPerCommitmentPoint, channelVersion, open)) =>
// let's create the first commitment tx that spends the yet uncommitted funding tx
val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.onChainFeeConf.maxFeerateMismatch)
val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, channelVersion, temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.onChainFeeConf.maxFeerateMismatch)
require(fundingTx.txOut(fundingTxOutputIndex).publicKeyScript == localCommitTx.input.txOut.publicKeyScript, s"pubkey script mismatch!")
val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.fundingKeyPath))
// signature of their initial commitment tx that pays remote pushMsat
Expand Down Expand Up @@ -406,7 +408,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
when(WAIT_FOR_FUNDING_CREATED)(handleExceptions {
case Event(FundingCreated(_, fundingTxHash, fundingTxOutputIndex, remoteSig), d@DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, remoteFirstPerCommitmentPoint, channelFlags, channelVersion, _)) =>
// 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(keyManager, temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, fundingTxHash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.onChainFeeConf.maxFeerateMismatch)
val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, channelVersion, temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, fundingTxHash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.onChainFeeConf.maxFeerateMismatch)

// check remote signature validity
val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath)
Expand Down Expand Up @@ -529,8 +531,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
case Success(_) =>
log.info(s"channelId=${commitments.channelId} was confirmed at blockHeight=$blockHeight txIndex=$txIndex")
blockchain ! WatchLost(self, commitments.commitInput.outPoint.txid, nodeParams.minDepthBlocks, BITCOIN_FUNDING_LOST)
val fundingPubKey = keyManager.fundingPublicKey(d.commitments.localParams.fundingKeyPath).publicKey
val channelKeyPath = d.commitments.localParams.channelKeyPath(keyManager)
val channelKeyPath = keyManager.channelKeyPath(d.commitments.localParams, commitments.channelVersion)
val nextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 1)
val fundingLocked = FundingLocked(commitments.channelId, nextPerCommitmentPoint)
deferred.foreach(self ! _)
Expand Down Expand Up @@ -1407,8 +1408,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
forwarder ! r

val yourLastPerCommitmentSecret = d.commitments.remotePerCommitmentSecrets.lastIndex.flatMap(d.commitments.remotePerCommitmentSecrets.getHash).getOrElse(ByteVector32.Zeroes)
val fundingPubKey = keyManager.fundingPublicKey(d.commitments.localParams.fundingKeyPath)
val channelKeyPath = d.commitments.localParams.channelKeyPath(keyManager)
val channelKeyPath = keyManager.channelKeyPath(d.commitments.localParams, d.commitments.channelVersion)
val myCurrentPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, d.commitments.localCommit.index)

val channelReestablish = ChannelReestablish(
Expand Down Expand Up @@ -1466,13 +1466,13 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId

case Event(_: ChannelReestablish, d: DATA_WAIT_FOR_FUNDING_LOCKED) =>
log.debug(s"re-sending fundingLocked")
val channelKeyPath = d.commitments.localParams.channelKeyPath(keyManager)
val channelKeyPath = keyManager.channelKeyPath(d.commitments.localParams, d.commitments.channelVersion)
val nextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 1)
val fundingLocked = FundingLocked(d.commitments.channelId, nextPerCommitmentPoint)
goto(WAIT_FOR_FUNDING_LOCKED) sending fundingLocked

case Event(channelReestablish: ChannelReestablish, d: DATA_NORMAL) =>
val channelKeyPath = d.commitments.localParams.channelKeyPath(keyManager)
val channelKeyPath = keyManager.channelKeyPath(d.commitments.localParams, d.commitments.channelVersion)
channelReestablish match {
case ChannelReestablish(_, _, nextRemoteRevocationNumber, Some(yourLastPerCommitmentSecret), _) if !Helpers.checkLocalCommit(d, nextRemoteRevocationNumber) =>
// if next_remote_revocation_number is greater than our local commitment index, it means that either we are using an outdated commitment, or they are lying
Expand Down Expand Up @@ -2126,7 +2126,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
} else if (commitments1.localCommit.index == channelReestablish.nextRemoteRevocationNumber + 1) {
// our last revocation got lost, let's resend it
log.debug(s"re-sending last revocation")
val channelKeyPath = d.commitments.localParams.channelKeyPath(keyManager)
val channelKeyPath = keyManager.channelKeyPath(d.commitments.localParams, d.commitments.channelVersion)
val localPerCommitmentSecret = keyManager.commitmentSecret(channelKeyPath, d.commitments.localCommit.index - 1)
val localNextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, d.commitments.localCommit.index + 1)
val revocation = RevokeAndAck(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import java.util.UUID
import akka.actor.ActorRef
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.{ByteVector32, DeterministicWallet, OutPoint, Satoshi, Transaction}
import fr.acinq.eclair.crypto.KeyManager
import fr.acinq.eclair.transactions.CommitmentSpec
import fr.acinq.eclair.transactions.Transactions.CommitTx
import fr.acinq.eclair.wire.{AcceptChannel, ChannelAnnouncement, ChannelReestablish, ChannelUpdate, ClosingSigned, FailureMessage, FundingCreated, FundingLocked, FundingSigned, Init, OnionRoutingPacket, OpenChannel, Shutdown, UpdateAddHtlc}
Expand Down Expand Up @@ -190,8 +189,7 @@ final case class DATA_CLOSING(commitments: Commitments,

final case class DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT(commitments: Commitments, remoteChannelReestablish: ChannelReestablish) extends Data with HasCommitments

final case class LocalParams(version: Int,
nodeId: PublicKey,
final case class LocalParams(nodeId: PublicKey,
fundingKeyPath: DeterministicWallet.KeyPath,
dustLimit: Satoshi,
maxHtlcValueInFlightMsat: UInt64, // this is not MilliSatoshi because it can exceed the total amount of MilliSatoshi
Expand All @@ -202,18 +200,7 @@ final case class LocalParams(version: Int,
isFunder: Boolean,
defaultFinalScriptPubKey: ByteVector,
globalFeatures: ByteVector,
localFeatures: ByteVector) {

def channelKeyPath(keyManager: KeyManager) = version match {
case 0 =>
// legacy mode: we reuse the funding key path as our channel key path
fundingKeyPath
case 1 =>
// deterministic mode: use the funding pubkey to compute the channel key path
KeyManager.channelKeyPath(keyManager.fundingPublicKey(fundingKeyPath))

}
}
localFeatures: ByteVector)

final case class RemoteParams(nodeId: PublicKey,
dustLimit: Satoshi,
Expand All @@ -237,9 +224,19 @@ object ChannelFlags {

case class ChannelVersion(bits: BitVector) {
require(bits.size == ChannelVersion.LENGTH_BITS, "channel version takes 4 bytes")

def |(other: ChannelVersion) = ChannelVersion(bits | other.bits)
def &(other: ChannelVersion) = ChannelVersion(bits & other.bits)
def ^(other: ChannelVersion) = ChannelVersion(bits ^ other.bits)
def isSet(bit: Int) = bits.reverse.get(bit)
}

object ChannelVersion {
import scodec.bits._
val LENGTH_BITS = 4 * 8
val STANDARD = ChannelVersion(BitVector.fill(LENGTH_BITS)(false))
val ZEROES = ChannelVersion(bin"00000000000000000000000000000000")
val STANDARD = ZEROES
val USE_PUBKEY_KEYPATH_BIT = 0 // bit numbers start at 0
val USE_PUBKEY_KEYPATH = STANDARD | ChannelVersion(bin"00000000000000000000000000000001")
}
// @formatter:on
Original file line number Diff line number Diff line change
Expand Up @@ -376,11 +376,11 @@ object Commitments {
case Right(remoteNextPerCommitmentPoint) =>
// remote commitment will includes all local changes + remote acked changes
val spec = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed)
val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(keyManager, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, spec)
val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(keyManager, channelVersion, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, spec)
val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(commitments.localParams.fundingKeyPath))

val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index)
val channelKeyPath = localParams.channelKeyPath(keyManager)
val channelKeyPath = keyManager.channelKeyPath(commitments.localParams, commitments.channelVersion)
val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(channelKeyPath), remoteNextPerCommitmentPoint))

// NB: IN/OUT htlcs are inverted because this is the remote commit
Expand Down Expand Up @@ -425,9 +425,9 @@ object Commitments {
// receiving money i.e its commit tx has one output for them

val spec = CommitmentSpec.reduce(localCommit.spec, localChanges.acked, remoteChanges.proposed)
val channelKeyPath = localParams.channelKeyPath(keyManager)
val channelKeyPath = keyManager.channelKeyPath(commitments.localParams, commitments.channelVersion)
val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitments.localCommit.index + 1)
val (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeLocalTxs(keyManager, localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, spec)
val (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeLocalTxs(keyManager, channelVersion, localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, spec)
val sig = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(commitments.localParams.fundingKeyPath))

log.info(s"built local commit number=${localCommit.index + 1} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${localCommitTx.tx.txid} tx={}", spec.htlcs.filter(_.direction == IN).map(_.add.id).mkString(","), spec.htlcs.filter(_.direction == OUT).map(_.add.id).mkString(","), localCommitTx.tx)
Expand Down Expand Up @@ -523,8 +523,8 @@ object Commitments {
}
}

def makeLocalTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: PublicKey, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = {
val channelKeyPath = localParams.channelKeyPath(keyManager)
def makeLocalTxs(keyManager: KeyManager, channelVersion: ChannelVersion, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: PublicKey, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = {
val channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(channelKeyPath).publicKey, localPerCommitmentPoint)
val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(channelKeyPath).publicKey, localPerCommitmentPoint)
val remotePaymentPubkey = Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint)
Expand All @@ -535,8 +535,8 @@ object Commitments {
(commitTx, htlcTimeoutTxs, htlcSuccessTxs)
}

def makeRemoteTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: PublicKey, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = {
val channelKeyPath = localParams.channelKeyPath(keyManager)
def makeRemoteTxs(keyManager: KeyManager, channelVersion: ChannelVersion, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: PublicKey, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = {
val channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
val localPaymentPubkey = Generators.derivePubKey(keyManager.paymentPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint)
Expand Down
Loading

0 comments on commit 823aaed

Please sign in to comment.