1
0
mirror of https://github.com/ACINQ/eclair.git synced 2024-11-20 10:39:19 +01:00

Derive channel keys from the channel funding pubkey (#1097)

* Derive channel keys from funding pubkey

We now generate a random funding key for each new channel, and use its public key to deterministically derive all channel keys and secrets. This will let us easily recover funds using DLP even if we've lost everything but our seed: we just need to connect to the node we had a channel with, ask them to publish their commit tx, and once we see it on the blockchain we can extract our funding pubkey, recompute channel keys and spend our output.

* Add rationale for new channel derivation scheme

* Add a "funding pubkey path" option to the channel version field

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.

* ChannelVersion: make sure that all bits are set to 0 for legacy channels

* ChannelVersion: USE_PUBKEY_KEYPATH is set by default

* Move recovery test out of OfflineStateSpec
This commit is contained in:
Fabrice Drouin 2019-09-23 11:44:58 +02:00 committed by GitHub
parent abf3907d4d
commit ea773425c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 345 additions and 122 deletions

View File

@ -145,9 +145,11 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
startWith(WAIT_FOR_INIT_INTERNAL, Nothing) startWith(WAIT_FOR_INIT_INTERNAL, Nothing)
when(WAIT_FOR_INIT_INTERNAL)(handleExceptions { 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))) context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, isFunder = true, temporaryChannelId, initialFeeratePerKw, Some(fundingTxFeeratePerKw)))
forwarder ! remote forwarder ! remote
val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey
val channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
val open = OpenChannel(nodeParams.chainHash, val open = OpenChannel(nodeParams.chainHash,
temporaryChannelId = temporaryChannelId, temporaryChannelId = temporaryChannelId,
fundingSatoshis = fundingSatoshis, fundingSatoshis = fundingSatoshis,
@ -159,12 +161,12 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
feeratePerKw = initialFeeratePerKw, feeratePerKw = initialFeeratePerKw,
toSelfDelay = localParams.toSelfDelay, toSelfDelay = localParams.toSelfDelay,
maxAcceptedHtlcs = localParams.maxAcceptedHtlcs, maxAcceptedHtlcs = localParams.maxAcceptedHtlcs,
fundingPubkey = keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, fundingPubkey = fundingPubKey,
revocationBasepoint = keyManager.revocationPoint(localParams.channelKeyPath).publicKey, revocationBasepoint = keyManager.revocationPoint(channelKeyPath).publicKey,
paymentBasepoint = keyManager.paymentPoint(localParams.channelKeyPath).publicKey, paymentBasepoint = keyManager.paymentPoint(channelKeyPath).publicKey,
delayedPaymentBasepoint = keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, delayedPaymentBasepoint = keyManager.delayedPaymentPoint(channelKeyPath).publicKey,
htlcBasepoint = keyManager.htlcPoint(localParams.channelKeyPath).publicKey, htlcBasepoint = keyManager.htlcPoint(channelKeyPath).publicKey,
firstPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 0), firstPerCommitmentPoint = keyManager.commitmentPoint(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
@ -271,6 +273,9 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
case Failure(t) => handleLocalError(t, d, Some(open)) case Failure(t) => handleLocalError(t, d, Some(open))
case Success(_) => case Success(_) =>
context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, isFunder = false, open.temporaryChannelId, open.feeratePerKw, None)) context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, isFunder = false, open.temporaryChannelId, open.feeratePerKw, None))
val fundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey
val channelVersion = ChannelVersion.STANDARD
val channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
// 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 accept = AcceptChannel(temporaryChannelId = open.temporaryChannelId, val accept = AcceptChannel(temporaryChannelId = open.temporaryChannelId,
@ -281,12 +286,12 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
htlcMinimumMsat = localParams.htlcMinimum, htlcMinimumMsat = localParams.htlcMinimum,
toSelfDelay = localParams.toSelfDelay, toSelfDelay = localParams.toSelfDelay,
maxAcceptedHtlcs = localParams.maxAcceptedHtlcs, maxAcceptedHtlcs = localParams.maxAcceptedHtlcs,
fundingPubkey = keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, fundingPubkey = fundingPubkey,
revocationBasepoint = keyManager.revocationPoint(localParams.channelKeyPath).publicKey, revocationBasepoint = keyManager.revocationPoint(channelKeyPath).publicKey,
paymentBasepoint = keyManager.paymentPoint(localParams.channelKeyPath).publicKey, paymentBasepoint = keyManager.paymentPoint(channelKeyPath).publicKey,
delayedPaymentBasepoint = keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, delayedPaymentBasepoint = keyManager.delayedPaymentPoint(channelKeyPath).publicKey,
htlcBasepoint = keyManager.htlcPoint(localParams.channelKeyPath).publicKey, htlcBasepoint = keyManager.htlcPoint(channelKeyPath).publicKey,
firstPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 0)) firstPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 0))
val remoteParams = RemoteParams( val remoteParams = RemoteParams(
nodeId = remoteNodeId, nodeId = remoteNodeId,
dustLimit = open.dustLimitSatoshis, dustLimit = open.dustLimitSatoshis,
@ -303,7 +308,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
globalFeatures = remoteInit.globalFeatures, globalFeatures = remoteInit.globalFeatures,
localFeatures = remoteInit.localFeatures) localFeatures = remoteInit.localFeatures)
log.debug(s"remote params: $remoteParams") 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" case Event(CMD_CLOSE(_), _) => goto(CLOSED) replying "ok"
@ -336,8 +341,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
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 = keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey val localFundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath)
val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey, remoteParams.fundingPubKey))) val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey.publicKey, remoteParams.fundingPubKey)))
wallet.makeFundingTx(fundingPubkeyScript, fundingSatoshis, fundingTxFeeratePerKw).pipeTo(self) wallet.makeFundingTx(fundingPubkeyScript, fundingSatoshis, fundingTxFeeratePerKw).pipeTo(self)
goto(WAIT_FOR_FUNDING_INTERNAL) using DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, accept.firstPerCommitmentPoint, channelVersion, open) goto(WAIT_FOR_FUNDING_INTERNAL) using DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingSatoshis, pushMsat, initialFeeratePerKw, accept.firstPerCommitmentPoint, channelVersion, open)
} }
@ -362,9 +367,9 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
when(WAIT_FOR_FUNDING_INTERNAL)(handleExceptions { 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)) => 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 // 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!") require(fundingTx.txOut(fundingTxOutputIndex).publicKeyScript == localCommitTx.input.txOut.publicKeyScript, s"pubkey script mismatch!")
val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.fundingKeyPath))
// 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,
@ -403,15 +408,16 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
when(WAIT_FOR_FUNDING_CREATED)(handleExceptions { 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, _)) => 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) // 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 // check remote signature validity
val localSigOfLocalTx = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath)
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig) val localSigOfLocalTx = keyManager.sign(localCommitTx, fundingPubKey)
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, fundingPubKey.publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig)
Transactions.checkSpendable(signedLocalCommitTx) match { Transactions.checkSpendable(signedLocalCommitTx) match {
case Failure(cause) => handleLocalError(InvalidCommitmentSignature(temporaryChannelId, signedLocalCommitTx.tx), d, None) case Failure(cause) => handleLocalError(InvalidCommitmentSignature(temporaryChannelId, signedLocalCommitTx.tx), d, None)
case Success(_) => case Success(_) =>
val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, fundingPubKey)
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
@ -447,8 +453,9 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
when(WAIT_FOR_FUNDING_SIGNED)(handleExceptions { when(WAIT_FOR_FUNDING_SIGNED)(handleExceptions {
case Event(msg@FundingSigned(_, remoteSig), d@DATA_WAIT_FOR_FUNDING_SIGNED(channelId, localParams, remoteParams, fundingTx, fundingTxFee, localSpec, localCommitTx, remoteCommit, channelFlags, channelVersion, fundingCreated)) => case Event(msg@FundingSigned(_, remoteSig), d@DATA_WAIT_FOR_FUNDING_SIGNED(channelId, localParams, remoteParams, fundingTx, fundingTxFee, localSpec, localCommitTx, remoteCommit, channelFlags, channelVersion, 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 = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath)
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig) val localSigOfLocalTx = keyManager.sign(localCommitTx, fundingPubKey)
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, fundingPubKey.publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig)
Transactions.checkSpendable(signedLocalCommitTx) match { Transactions.checkSpendable(signedLocalCommitTx) match {
case Failure(cause) => case Failure(cause) =>
// we rollback the funding tx, it will never be published // we rollback the funding tx, it will never be published
@ -524,7 +531,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
case Success(_) => case Success(_) =>
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 = keyManager.commitmentPoint(commitments.localParams.channelKeyPath, 1) val channelKeyPath = keyManager.channelKeyPath(d.commitments.localParams, commitments.channelVersion)
val nextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 1)
val fundingLocked = FundingLocked(commitments.channelId, nextPerCommitmentPoint) val fundingLocked = FundingLocked(commitments.channelId, nextPerCommitmentPoint)
deferred.foreach(self ! _) deferred.foreach(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
@ -885,7 +893,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
require(d.shortChannelId == remoteAnnSigs.shortChannelId, s"shortChannelId mismatch: local=${d.shortChannelId} remote=${remoteAnnSigs.shortChannelId}") require(d.shortChannelId == remoteAnnSigs.shortChannelId, s"shortChannelId mismatch: local=${d.shortChannelId} remote=${remoteAnnSigs.shortChannelId}")
log.info(s"announcing channelId=${d.channelId} on the network with shortId=${d.shortChannelId}") log.info(s"announcing channelId=${d.channelId} on the network with shortId=${d.shortChannelId}")
import d.commitments.{localParams, remoteParams} import d.commitments.{localParams, remoteParams}
val channelAnn = Announcements.makeChannelAnnouncement(nodeParams.chainHash, localAnnSigs.shortChannelId, nodeParams.nodeId, remoteParams.nodeId, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, localAnnSigs.nodeSignature, remoteAnnSigs.nodeSignature, localAnnSigs.bitcoinSignature, remoteAnnSigs.bitcoinSignature) val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath)
val channelAnn = Announcements.makeChannelAnnouncement(nodeParams.chainHash, localAnnSigs.shortChannelId, nodeParams.nodeId, remoteParams.nodeId, fundingPubKey.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 d.copy(channelAnnouncement = Some(channelAnn)) storing() goto(NORMAL) using d.copy(channelAnnouncement = Some(channelAnn)) storing()
case Some(_) => case Some(_) =>
@ -1383,7 +1392,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
forwarder ! r forwarder ! r
val yourLastPerCommitmentSecret = d.commitments.remotePerCommitmentSecrets.lastIndex.flatMap(d.commitments.remotePerCommitmentSecrets.getHash).getOrElse(ByteVector32.Zeroes) val yourLastPerCommitmentSecret = d.commitments.remotePerCommitmentSecrets.lastIndex.flatMap(d.commitments.remotePerCommitmentSecrets.getHash).getOrElse(ByteVector32.Zeroes)
val myCurrentPerCommitmentPoint = keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, d.commitments.localCommit.index) val channelKeyPath = keyManager.channelKeyPath(d.commitments.localParams, d.commitments.channelVersion)
val myCurrentPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, d.commitments.localCommit.index)
val channelReestablish = ChannelReestablish( val channelReestablish = ChannelReestablish(
channelId = d.channelId, channelId = d.channelId,
@ -1443,16 +1453,18 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
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 = keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, 1) val channelKeyPath = keyManager.channelKeyPath(d.commitments.localParams, d.commitments.channelVersion)
val nextPerCommitmentPoint = keyManager.commitmentPoint(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
case Event(channelReestablish: ChannelReestablish, d: DATA_NORMAL) => case Event(channelReestablish: ChannelReestablish, d: DATA_NORMAL) =>
val channelKeyPath = keyManager.channelKeyPath(d.commitments.localParams, d.commitments.channelVersion)
channelReestablish match { channelReestablish match {
case ChannelReestablish(_, _, nextRemoteRevocationNumber, Some(yourLastPerCommitmentSecret), _) if !Helpers.checkLocalCommit(d, nextRemoteRevocationNumber) => 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 // 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 (keyManager.commitmentSecret(d.commitments.localParams.channelKeyPath, nextRemoteRevocationNumber - 1) == yourLastPerCommitmentSecret) { if (keyManager.commitmentSecret(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
@ -1477,7 +1489,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
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 = keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, 1) val nextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 1)
val fundingLocked = FundingLocked(d.commitments.channelId, nextPerCommitmentPoint) val fundingLocked = FundingLocked(d.commitments.channelId, nextPerCommitmentPoint)
forwarder ! fundingLocked forwarder ! fundingLocked
} }
@ -2140,8 +2152,9 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
} 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 = keyManager.commitmentSecret(commitments1.localParams.channelKeyPath, d.commitments.localCommit.index - 1) val channelKeyPath = keyManager.channelKeyPath(d.commitments.localParams, d.commitments.channelVersion)
val localNextPerCommitmentPoint = keyManager.commitmentPoint(commitments1.localParams.channelKeyPath, d.commitments.localCommit.index + 1) val localPerCommitmentSecret = keyManager.commitmentSecret(channelKeyPath, d.commitments.localCommit.index - 1)
val localNextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, d.commitments.localCommit.index + 1)
val revocation = RevokeAndAck( val revocation = RevokeAndAck(
channelId = commitments1.channelId, channelId = commitments1.channelId,
perCommitmentSecret = localPerCommitmentSecret, perCommitmentSecret = localPerCommitmentSecret,

View File

@ -190,7 +190,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 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, fundingKeyPath: DeterministicWallet.KeyPath,
dustLimit: Satoshi, dustLimit: Satoshi,
maxHtlcValueInFlightMsat: UInt64, // this is not MilliSatoshi because it can exceed the total amount of MilliSatoshi maxHtlcValueInFlightMsat: UInt64, // this is not MilliSatoshi because it can exceed the total amount of MilliSatoshi
channelReserve: Satoshi, channelReserve: Satoshi,
@ -224,9 +224,23 @@ object ChannelFlags {
case class ChannelVersion(bits: BitVector) { case class ChannelVersion(bits: BitVector) {
require(bits.size == ChannelVersion.LENGTH_BITS, "channel version takes 4 bytes") 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 { object ChannelVersion {
import scodec.bits._
val LENGTH_BITS = 4 * 8 val LENGTH_BITS = 4 * 8
val STANDARD = ChannelVersion(BitVector.fill(LENGTH_BITS)(false)) val ZEROES = ChannelVersion(bin"00000000000000000000000000000000")
val USE_PUBKEY_KEYPATH_BIT = 0 // bit numbers start at 0
def fromBit(bit: Int) = ChannelVersion(BitVector.low(LENGTH_BITS).set(bit).reverse)
val USE_PUBKEY_KEYPATH = fromBit(USE_PUBKEY_KEYPATH_BIT)
val STANDARD = ZEROES | USE_PUBKEY_KEYPATH
} }
// @formatter:on // @formatter:on

View File

@ -376,11 +376,12 @@ 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(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(localParams.channelKeyPath)) val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(commitments.localParams.fundingKeyPath))
val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index) val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index)
val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), remoteNextPerCommitmentPoint)) 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 // NB: IN/OUT htlcs are inverted because this is the remote commit
log.info(s"built remote commit number=${remoteCommit.index + 1} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${remoteCommitTx.tx.txid} tx={}", spec.htlcs.filter(_.direction == OUT).map(_.add.id).mkString(","), spec.htlcs.filter(_.direction == IN).map(_.add.id).mkString(","), remoteCommitTx.tx) log.info(s"built remote commit number=${remoteCommit.index + 1} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${remoteCommitTx.tx.txid} tx={}", spec.htlcs.filter(_.direction == OUT).map(_.add.id).mkString(","), spec.htlcs.filter(_.direction == IN).map(_.add.id).mkString(","), remoteCommitTx.tx)
@ -424,16 +425,17 @@ 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 = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index + 1) val channelKeyPath = keyManager.channelKeyPath(commitments.localParams, commitments.channelVersion)
val (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeLocalTxs(keyManager, localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, spec) val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitments.localCommit.index + 1)
val sig = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath)) 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) log.info(s"built local commit number=${localCommit.index + 1} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${localCommitTx.tx.txid} tx={}", spec.htlcs.filter(_.direction == IN).map(_.add.id).mkString(","), spec.htlcs.filter(_.direction == OUT).map(_.add.id).mkString(","), localCommitTx.tx)
// TODO: should we have optional sig? (original comment: this tx will NOT be signed if our output is empty) // 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, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, sig, commit.signature) val signedCommitTx = Transactions.addSigs(localCommitTx, keyManager.fundingPublicKey(commitments.localParams.fundingKeyPath).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)
} }
@ -442,7 +444,7 @@ object Commitments {
if (commit.htlcSignatures.size != sortedHtlcTxs.size) { if (commit.htlcSignatures.size != sortedHtlcTxs.size) {
throw HtlcSigCountMismatch(commitments.channelId, sortedHtlcTxs.size, commit.htlcSignatures.size) throw HtlcSigCountMismatch(commitments.channelId, sortedHtlcTxs.size, commit.htlcSignatures.size)
} }
val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(localParams.channelKeyPath), localPerCommitmentPoint)) val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(channelKeyPath), localPerCommitmentPoint))
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 {
@ -460,8 +462,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 = keyManager.commitmentSecret(localParams.channelKeyPath, commitments.localCommit.index) val localPerCommitmentSecret = keyManager.commitmentSecret(channelKeyPath, commitments.localCommit.index)
val localNextPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index + 2) val localNextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitments.localCommit.index + 2)
val revocation = RevokeAndAck( val revocation = RevokeAndAck(
channelId = commitments.channelId, channelId = commitments.channelId,
perCommitmentSecret = localPerCommitmentSecret, perCommitmentSecret = localPerCommitmentSecret,
@ -521,24 +523,26 @@ object Commitments {
} }
} }
def makeLocalTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: PublicKey, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { def makeLocalTxs(keyManager: KeyManager, channelVersion: ChannelVersion, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: PublicKey, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = {
val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) val channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) 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) 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, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remoteParams.paymentBasepoint, localParams.isFunder, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, keyManager.paymentPoint(channelKeyPath).publicKey, remoteParams.paymentBasepoint, localParams.isFunder, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec)
val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec) val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec)
(commitTx, htlcTimeoutTxs, htlcSuccessTxs) (commitTx, htlcTimeoutTxs, htlcSuccessTxs)
} }
def makeRemoteTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: PublicKey, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = { def makeRemoteTxs(keyManager: KeyManager, channelVersion: ChannelVersion, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: PublicKey, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = {
val localPaymentPubkey = Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) val channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) 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) 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(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, !localParams.isFunder, remoteParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, keyManager.paymentPoint(channelKeyPath).publicKey, !localParams.isFunder, remoteParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec)
val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, remoteParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec) val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, remoteParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec)
(commitTx, htlcTimeoutTxs, htlcSuccessTxs) (commitTx, htlcTimeoutTxs, htlcSuccessTxs)
} }

View File

@ -204,7 +204,8 @@ object Helpers {
def makeAnnouncementSignatures(nodeParams: NodeParams, commitments: Commitments, shortChannelId: ShortChannelId) = { def makeAnnouncementSignatures(nodeParams: NodeParams, commitments: Commitments, shortChannelId: ShortChannelId) = {
val features = ByteVector.empty // empty features for now val features = ByteVector.empty // empty features for now
val (localNodeSig, localBitcoinSig) = nodeParams.keyManager.signChannelAnnouncement(commitments.localParams.channelKeyPath, nodeParams.chainHash, shortChannelId, commitments.remoteParams.nodeId, commitments.remoteParams.fundingPubKey, features) val fundingPubKey = nodeParams.keyManager.fundingPublicKey(commitments.localParams.fundingKeyPath)
val (localNodeSig, localBitcoinSig) = nodeParams.keyManager.signChannelAnnouncement(fundingPubKey.path, nodeParams.chainHash, shortChannelId, commitments.remoteParams.nodeId, commitments.remoteParams.fundingPubKey, features)
AnnouncementSignatures(commitments.channelId, shortChannelId, localNodeSig, localBitcoinSig) AnnouncementSignatures(commitments.channelId, shortChannelId, localNodeSig, localBitcoinSig)
} }
@ -251,7 +252,7 @@ object Helpers {
* @param remoteFirstPerCommitmentPoint * @param remoteFirstPerCommitmentPoint
* @return (localSpec, localTx, remoteSpec, remoteTx, fundingTxOutput) * @return (localSpec, localTx, remoteSpec, remoteTx, fundingTxOutput)
*/ */
def makeFirstCommitTxs(keyManager: KeyManager, temporaryChannelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingAmount: Satoshi, pushMsat: MilliSatoshi, initialFeeratePerKw: Long, fundingTxHash: ByteVector32, fundingTxOutputIndex: Int, remoteFirstPerCommitmentPoint: PublicKey, maxFeerateMismatch: Double): (CommitmentSpec, CommitTx, CommitmentSpec, CommitTx) = { def makeFirstCommitTxs(keyManager: KeyManager, channelVersion: ChannelVersion, temporaryChannelId: ByteVector32, localParams: LocalParams, remoteParams: RemoteParams, fundingAmount: Satoshi, pushMsat: MilliSatoshi, initialFeeratePerKw: Long, fundingTxHash: ByteVector32, fundingTxOutputIndex: Int, remoteFirstPerCommitmentPoint: PublicKey, maxFeerateMismatch: Double): (CommitmentSpec, CommitTx, CommitmentSpec, CommitTx) = {
val toLocalMsat = if (localParams.isFunder) fundingAmount.toMilliSatoshi - pushMsat else pushMsat val toLocalMsat = if (localParams.isFunder) fundingAmount.toMilliSatoshi - pushMsat else pushMsat
val toRemoteMsat = if (localParams.isFunder) pushMsat else fundingAmount.toMilliSatoshi - pushMsat val toRemoteMsat = if (localParams.isFunder) pushMsat else fundingAmount.toMilliSatoshi - pushMsat
@ -268,10 +269,12 @@ object Helpers {
} }
} }
val commitmentInput = makeFundingInputInfo(fundingTxHash, fundingTxOutputIndex, fundingAmount, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey) val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath)
val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 0) val channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
val (localCommitTx, _, _) = Commitments.makeLocalTxs(keyManager, 0, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, localSpec) val commitmentInput = makeFundingInputInfo(fundingTxHash, fundingTxOutputIndex, fundingAmount, fundingPubKey.publicKey, remoteParams.fundingPubKey)
val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, 0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, remoteSpec) val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 0)
val (localCommitTx, _, _) = Commitments.makeLocalTxs(keyManager, channelVersion, 0, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, localSpec)
val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, channelVersion, 0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, remoteSpec)
(localSpec, localCommitTx, remoteSpec, remoteCommitTx) (localSpec, localCommitTx, remoteSpec, remoteCommitTx)
} }
@ -454,7 +457,7 @@ object Helpers {
// TODO: check that // TODO: check that
val dustLimitSatoshis = localParams.dustLimit.max(remoteParams.dustLimit) val dustLimitSatoshis = localParams.dustLimit.max(remoteParams.dustLimit)
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 = keyManager.sign(closingTx, keyManager.fundingPublicKey(commitments.localParams.channelKeyPath)) val localClosingSig = keyManager.sign(closingTx, keyManager.fundingPublicKey(commitments.localParams.fundingKeyPath))
val closingSigned = ClosingSigned(channelId, closingFee, localClosingSig) val closingSigned = ClosingSigned(channelId, closingFee, 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}}")
@ -469,7 +472,7 @@ object Helpers {
throw InvalidCloseFee(commitments.channelId, remoteClosingFee) throw InvalidCloseFee(commitments.channelId, remoteClosingFee)
} }
val (closingTx, closingSigned) = makeClosingTx(keyManager, commitments, localScriptPubkey, remoteScriptPubkey, remoteClosingFee) val (closingTx, closingSigned) = makeClosingTx(keyManager, commitments, localScriptPubkey, remoteScriptPubkey, remoteClosingFee)
val signedClosingTx = Transactions.addSigs(closingTx, keyManager.fundingPublicKey(commitments.localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, closingSigned.signature, remoteClosingSig) val signedClosingTx = Transactions.addSigs(closingTx, keyManager.fundingPublicKey(commitments.localParams.fundingKeyPath).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) }
} }
@ -498,17 +501,17 @@ object Helpers {
def claimCurrentLocalCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): LocalCommitPublished = { def claimCurrentLocalCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(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 channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index.toInt) val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitments.localCommit.index.toInt)
val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint)
val localDelayedPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint) val localDelayedPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(channelKeyPath).publicKey, localPerCommitmentPoint)
val feeratePerKwDelayed = feeEstimator.getFeeratePerKw(feeTargets.claimMainBlockTarget) val feeratePerKwDelayed = feeEstimator.getFeeratePerKw(feeTargets.claimMainBlockTarget)
// 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, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwDelayed) val claimDelayed = Transactions.makeClaimDelayedOutputTx(tx, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwDelayed)
val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(localParams.channelKeyPath), localPerCommitmentPoint) val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(channelKeyPath), localPerCommitmentPoint)
Transactions.addSigs(claimDelayed, sig) Transactions.addSigs(claimDelayed, sig)
}) })
@ -543,7 +546,7 @@ object Helpers {
remoteParams.toSelfDelay, remoteParams.toSelfDelay,
localDelayedPubkey, localDelayedPubkey,
localParams.defaultFinalScriptPubKey, feeratePerKwDelayed) localParams.defaultFinalScriptPubKey, feeratePerKwDelayed)
val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(localParams.channelKeyPath), localPerCommitmentPoint) val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(channelKeyPath), localPerCommitmentPoint)
Transactions.addSigs(claimDelayed, sig) Transactions.addSigs(claimDelayed, sig)
}) })
} }
@ -567,15 +570,15 @@ object Helpers {
* @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(keyManager: KeyManager, commitments: Commitments, remoteCommit: RemoteCommit, tx: Transaction, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): RemoteCommitPublished = { def claimRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, remoteCommit: RemoteCommit, tx: Transaction, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): RemoteCommitPublished = {
import commitments.{commitInput, localParams, remoteParams} import commitments.{channelVersion, 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(keyManager, remoteCommit.index, localParams, remoteParams, commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec) val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, channelVersion, 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 channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint) val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint)
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remoteCommit.remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remoteCommit.remotePerCommitmentPoint)
val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index.toInt) val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitments.localCommit.index.toInt)
val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint) val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(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 = feeEstimator.getFeeratePerKw(target = 2) val feeratePerKwHtlc = feeEstimator.getFeeratePerKw(target = 2)
@ -591,7 +594,7 @@ object Helpers {
val preimage = preimages.find(r => sha256(r) == add.paymentHash).get val preimage = preimages.find(r => sha256(r) == add.paymentHash).get
val txinfo = Transactions.makeClaimHtlcSuccessTx(remoteCommitTx.tx, outputsAlreadyUsed, localParams.dustLimit, localHtlcPubkey, remoteHtlcPubkey, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, add, feeratePerKwHtlc) val txinfo = Transactions.makeClaimHtlcSuccessTx(remoteCommitTx.tx, outputsAlreadyUsed, localParams.dustLimit, localHtlcPubkey, remoteHtlcPubkey, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, add, feeratePerKwHtlc)
outputsAlreadyUsed = outputsAlreadyUsed + txinfo.input.outPoint.index.toInt outputsAlreadyUsed = outputsAlreadyUsed + txinfo.input.outPoint.index.toInt
val sig = keyManager.sign(txinfo, keyManager.htlcPoint(localParams.channelKeyPath), remoteCommit.remotePerCommitmentPoint) val sig = keyManager.sign(txinfo, keyManager.htlcPoint(channelKeyPath), remoteCommit.remotePerCommitmentPoint)
Transactions.addSigs(txinfo, sig, preimage) Transactions.addSigs(txinfo, sig, preimage)
}) })
@ -601,7 +604,7 @@ object Helpers {
case DirectedHtlc(IN, add: UpdateAddHtlc) => generateTx("claim-htlc-timeout")(Try { case DirectedHtlc(IN, add: UpdateAddHtlc) => generateTx("claim-htlc-timeout")(Try {
val txinfo = Transactions.makeClaimHtlcTimeoutTx(remoteCommitTx.tx, outputsAlreadyUsed, localParams.dustLimit, localHtlcPubkey, remoteHtlcPubkey, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, add, feeratePerKwHtlc) val txinfo = Transactions.makeClaimHtlcTimeoutTx(remoteCommitTx.tx, outputsAlreadyUsed, localParams.dustLimit, localHtlcPubkey, remoteHtlcPubkey, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, add, feeratePerKwHtlc)
outputsAlreadyUsed = outputsAlreadyUsed + txinfo.input.outPoint.index.toInt outputsAlreadyUsed = outputsAlreadyUsed + txinfo.input.outPoint.index.toInt
val sig = keyManager.sign(txinfo, keyManager.htlcPoint(localParams.channelKeyPath), remoteCommit.remotePerCommitmentPoint) val sig = keyManager.sign(txinfo, keyManager.htlcPoint(channelKeyPath), remoteCommit.remotePerCommitmentPoint)
Transactions.addSigs(txinfo, sig) Transactions.addSigs(txinfo, sig)
}) })
}.toSeq.flatten }.toSeq.flatten
@ -624,14 +627,15 @@ object Helpers {
* @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(keyManager: KeyManager, commitments: Commitments, remotePerCommitmentPoint: PublicKey, tx: Transaction, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): RemoteCommitPublished = { def claimRemoteCommitMainOutput(keyManager: KeyManager, commitments: Commitments, remotePerCommitmentPoint: PublicKey, tx: Transaction, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): RemoteCommitPublished = {
val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(commitments.localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) val channelKeyPath = keyManager.channelKeyPath(commitments.localParams, commitments.channelVersion)
val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
val feeratePerKwMain = feeEstimator.getFeeratePerKw(feeTargets.claimMainBlockTarget) val feeratePerKwMain = feeEstimator.getFeeratePerKw(feeTargets.claimMainBlockTarget)
val mainTx = generateTx("claim-p2wpkh-output")(Try { val mainTx = generateTx("claim-p2wpkh-output")(Try {
val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, commitments.localParams.dustLimit, val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, commitments.localParams.dustLimit,
localPubkey, commitments.localParams.defaultFinalScriptPubKey, feeratePerKwMain) localPubkey, commitments.localParams.defaultFinalScriptPubKey, feeratePerKwMain)
val sig = keyManager.sign(claimMain, keyManager.paymentPoint(commitments.localParams.channelKeyPath), remotePerCommitmentPoint) val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), remotePerCommitmentPoint)
Transactions.addSigs(claimMain, localPubkey, sig) Transactions.addSigs(claimMain, localPubkey, sig)
}) })
@ -656,9 +660,11 @@ object Helpers {
def claimRevokedRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction, db: ChannelsDb, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): Option[RevokedCommitPublished] = { def claimRevokedRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction, db: ChannelsDb, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(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 fundingPubKey = commitments.localParams.fundingPubKey(keyManager)
val channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
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, keyManager.paymentPoint(localParams.channelKeyPath).publicKey) val txnumber = Transactions.obscuredCommitTxNumber(obscuredTxNumber, !localParams.isFunder, remoteParams.paymentBasepoint, keyManager.paymentPoint(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"a revoked commit has been published with txnumber=$txnumber") log.warning(s"a revoked commit has been published with txnumber=$txnumber")
// now we know what commit number this tx is referring to, we can derive the commitment point from the shachain // now we know what commit number this tx is referring to, we can derive the commitment point from the shachain
@ -667,9 +673,9 @@ object Helpers {
.map { remotePerCommitmentSecret => .map { remotePerCommitmentSecret =>
val remotePerCommitmentPoint = remotePerCommitmentSecret.publicKey val remotePerCommitmentPoint = remotePerCommitmentSecret.publicKey
val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint)
val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint)
val feeratePerKwMain = feeEstimator.getFeeratePerKw(feeTargets.claimMainBlockTarget) val feeratePerKwMain = feeEstimator.getFeeratePerKw(feeTargets.claimMainBlockTarget)
@ -679,14 +685,14 @@ 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, localParams.dustLimit, localPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwMain) val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, localParams.dustLimit, localPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwMain)
val sig = keyManager.sign(claimMain, keyManager.paymentPoint(localParams.channelKeyPath), remotePerCommitmentPoint) val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), remotePerCommitmentPoint)
Transactions.addSigs(claimMain, localPubkey, 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 {
val txinfo = Transactions.makeMainPenaltyTx(tx, localParams.dustLimit, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, feeratePerKwPenalty) val txinfo = Transactions.makeMainPenaltyTx(tx, localParams.dustLimit, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, feeratePerKwPenalty)
val sig = keyManager.sign(txinfo, keyManager.revocationPoint(localParams.channelKeyPath), remotePerCommitmentSecret) val sig = keyManager.sign(txinfo, keyManager.revocationPoint(channelKeyPath), remotePerCommitmentSecret)
Transactions.addSigs(txinfo, sig) Transactions.addSigs(txinfo, sig)
}) })
@ -707,7 +713,7 @@ object Helpers {
generateTx("htlc-penalty")(Try { generateTx("htlc-penalty")(Try {
val htlcPenalty = Transactions.makeHtlcPenaltyTx(tx, outputsAlreadyUsed, htlcRedeemScript, localParams.dustLimit, localParams.defaultFinalScriptPubKey, feeratePerKwPenalty) val htlcPenalty = Transactions.makeHtlcPenaltyTx(tx, outputsAlreadyUsed, htlcRedeemScript, localParams.dustLimit, localParams.defaultFinalScriptPubKey, feeratePerKwPenalty)
outputsAlreadyUsed = outputsAlreadyUsed + htlcPenalty.input.outPoint.index.toInt outputsAlreadyUsed = outputsAlreadyUsed + htlcPenalty.input.outPoint.index.toInt
val sig = keyManager.sign(htlcPenalty, keyManager.revocationPoint(localParams.channelKeyPath), remotePerCommitmentSecret) val sig = keyManager.sign(htlcPenalty, keyManager.revocationPoint(channelKeyPath), remotePerCommitmentSecret)
Transactions.addSigs(htlcPenalty, sig, remoteRevocationPubkey) Transactions.addSigs(htlcPenalty, sig, remoteRevocationPubkey)
}) })
}.toList.flatten }.toList.flatten
@ -747,22 +753,23 @@ object Helpers {
import commitments._ import commitments._
val tx = revokedCommitPublished.commitTx val tx = revokedCommitPublished.commitTx
val obscuredTxNumber = Transactions.decodeTxNumber(tx.txIn(0).sequence, tx.lockTime) val obscuredTxNumber = Transactions.decodeTxNumber(tx.txIn(0).sequence, tx.lockTime)
val channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
// 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, keyManager.paymentPoint(localParams.channelKeyPath).publicKey) val txnumber = Transactions.obscuredCommitTxNumber(obscuredTxNumber, !localParams.isFunder, remoteParams.paymentBasepoint, keyManager.paymentPoint(channelKeyPath).publicKey)
// 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
remotePerCommitmentSecrets.getHash(0xFFFFFFFFFFFFL - txnumber) remotePerCommitmentSecrets.getHash(0xFFFFFFFFFFFFL - txnumber)
.map(d => PrivateKey(d)) .map(d => PrivateKey(d))
.flatMap { remotePerCommitmentSecret => .flatMap { remotePerCommitmentSecret =>
val remotePerCommitmentPoint = remotePerCommitmentSecret.publicKey val remotePerCommitmentPoint = remotePerCommitmentSecret.publicKey
val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint)
val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint) val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
// we need to use a high fee here for punishment txes because after a delay they can be spent by the counterparty // we need to use a high fee here for punishment txes because after a delay they can be spent by the counterparty
val feeratePerKwPenalty = feeEstimator.getFeeratePerKw(target = 1) val feeratePerKwPenalty = feeEstimator.getFeeratePerKw(target = 1)
generateTx("claim-htlc-delayed-penalty")(Try { generateTx("claim-htlc-delayed-penalty")(Try {
val htlcDelayedPenalty = Transactions.makeClaimDelayedOutputPenaltyTx(htlcTx, localParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwPenalty) val htlcDelayedPenalty = Transactions.makeClaimDelayedOutputPenaltyTx(htlcTx, localParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwPenalty)
val sig = keyManager.sign(htlcDelayedPenalty, keyManager.revocationPoint(localParams.channelKeyPath), remotePerCommitmentSecret) val sig = keyManager.sign(htlcDelayedPenalty, keyManager.revocationPoint(channelKeyPath), remotePerCommitmentSecret)
val signedTx = Transactions.addSigs(htlcDelayedPenalty, sig) val signedTx = Transactions.addSigs(htlcDelayedPenalty, sig)
// we need to make sure that the tx is indeed valid // we need to make sure that the tx is indeed valid
Transaction.correctlySpends(signedTx.tx, Seq(htlcTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) Transaction.correctlySpends(signedTx.tx, Seq(htlcTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)

View File

@ -16,10 +16,14 @@
package fr.acinq.eclair.crypto package fr.acinq.eclair.crypto
import java.io.ByteArrayInputStream
import java.nio.ByteOrder
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.DeterministicWallet.ExtendedPublicKey import fr.acinq.bitcoin.DeterministicWallet.ExtendedPublicKey
import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, DeterministicWallet} import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, DeterministicWallet, Protocol}
import fr.acinq.eclair.ShortChannelId import fr.acinq.eclair.ShortChannelId
import fr.acinq.eclair.channel.{ChannelVersion, LocalParams}
import fr.acinq.eclair.transactions.Transactions.TransactionWithInputInfo import fr.acinq.eclair.transactions.Transactions.TransactionWithInputInfo
import scodec.bits.ByteVector import scodec.bits.ByteVector
@ -28,7 +32,7 @@ trait KeyManager {
def nodeId: PublicKey def nodeId: PublicKey
def fundingPublicKey(channelKeyPath: DeterministicWallet.KeyPath): ExtendedPublicKey def fundingPublicKey(keyPath: DeterministicWallet.KeyPath): ExtendedPublicKey
def revocationPoint(channelKeyPath: DeterministicWallet.KeyPath): ExtendedPublicKey def revocationPoint(channelKeyPath: DeterministicWallet.KeyPath): ExtendedPublicKey
@ -42,6 +46,14 @@ trait KeyManager {
def commitmentPoint(channelKeyPath: DeterministicWallet.KeyPath, index: Long): Crypto.PublicKey def commitmentPoint(channelKeyPath: DeterministicWallet.KeyPath, index: Long): Crypto.PublicKey
def channelKeyPath(localParams: LocalParams, channelVersion: ChannelVersion): DeterministicWallet.KeyPath = if (channelVersion.isSet(ChannelVersion.USE_PUBKEY_KEYPATH_BIT)) {
// deterministic mode: use the funding pubkey to compute the channel key path
KeyManager.channelKeyPath(fundingPublicKey(localParams.fundingKeyPath))
} else {
// legacy mode: we reuse the funding key path as our channel key path
localParams.fundingKeyPath
}
/** /**
* *
* @param tx input transaction * @param tx input transaction
@ -73,5 +85,37 @@ trait KeyManager {
*/ */
def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remoteSecret: PrivateKey): ByteVector64 def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remoteSecret: PrivateKey): ByteVector64
def signChannelAnnouncement(channelKeyPath: DeterministicWallet.KeyPath, chainHash: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: ByteVector): (ByteVector64, ByteVector64) /**
* Sign a channel announcement message
*
* @param fundingKeyPath BIP32 path of the funding public key
* @param chainHash chain hash
* @param shortChannelId short channel id
* @param remoteNodeId remote node id
* @param remoteFundingKey remote funding pubkey
* @param features channel features
* @return a (nodeSig, bitcoinSig) pair. nodeSig is the signature of the channel announcement with our node's
* private key, bitcoinSig is the signature of the channel announcement with our funding private key
*/
def signChannelAnnouncement(fundingKeyPath: DeterministicWallet.KeyPath, chainHash: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: ByteVector): (ByteVector64, ByteVector64)
}
object KeyManager {
/**
* Create a BIP32 path from a public key. This path will be used to derive channel keys.
* Having channel keys derived from the funding public keys makes it very easy to retrieve your funds when've you've lost your data:
* - connect to your peer and use DLP to get them to publish their remote commit tx
* - retrieve the commit tx from the bitcoin network, extract your funding pubkey from its witness data
* - recompute your channel keys and spend your output
*
* @param fundingPubKey funding public key
* @return a BIP32 path
*/
def channelKeyPath(fundingPubKey: PublicKey) : DeterministicWallet.KeyPath = {
val buffer = fundingPubKey.hash160.take(8)
val bis = new ByteArrayInputStream(buffer.toArray)
DeterministicWallet.KeyPath(Seq(Protocol.uint32(bis, ByteOrder.BIG_ENDIAN), Protocol.uint32(bis, ByteOrder.BIG_ENDIAN), Protocol.uint32(bis, ByteOrder.BIG_ENDIAN), Protocol.uint32(bis, ByteOrder.BIG_ENDIAN)))
}
def channelKeyPath(fundingPubKey: DeterministicWallet.ExtendedPublicKey) : DeterministicWallet.KeyPath = channelKeyPath(fundingPubKey.publicKey)
} }

View File

@ -137,9 +137,9 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana
Transactions.sign(tx, currentKey) Transactions.sign(tx, currentKey)
} }
override def signChannelAnnouncement(channelKeyPath: DeterministicWallet.KeyPath, chainHash: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: ByteVector): (ByteVector64, ByteVector64) = { override def signChannelAnnouncement(fundingKeyPath: DeterministicWallet.KeyPath, chainHash: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: ByteVector): (ByteVector64, ByteVector64) = {
val localNodeSecret = nodeKey.privateKey val localNodeSecret = nodeKey.privateKey
val localFundingPrivKey = fundingPrivateKey(channelKeyPath).privateKey val localFundingPrivKey = privateKeys.get(fundingKeyPath).privateKey
Announcements.signChannelAnnouncement(chainHash, shortChannelId, localNodeSecret, remoteNodeId, localFundingPrivKey, remoteFundingKey, features) Announcements.signChannelAnnouncement(chainHash, shortChannelId, localNodeSecret, remoteNodeId, localFundingPrivKey, remoteFundingKey, features)
} }
} }

View File

@ -16,16 +16,14 @@
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 akka.event.Logging.MDC import akka.event.Logging.MDC
import akka.util.Timeout import akka.util.Timeout
import com.google.common.net.HostAndPort import com.google.common.net.HostAndPort
import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.{Block, ByteVector32, DeterministicWallet, Protocol, Satoshi} import fr.acinq.bitcoin.{Block, ByteVector32, DeterministicWallet, Satoshi}
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
@ -664,17 +662,16 @@ object Peer {
} }
def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingAmount: Satoshi): LocalParams = { def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingAmount: Satoshi): LocalParams = {
val entropy = new Array[Byte](16) // we make sure that funder and fundee key path end differently
secureRandom.nextBytes(entropy) val last = DeterministicWallet.hardened(if (isFunder) 1 else 0)
val bis = new ByteArrayInputStream(entropy) val fundingKeyPath = DeterministicWallet.KeyPath(Seq(secureRandom.nextInt() & 0xFFFFFFFFL, secureRandom.nextInt() & 0xFFFFFFFFL, last))
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, fundingAmount, fundingKeyPath)
makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder, fundingAmount, channelKeyPath)
} }
def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingAmount: Satoshi, channelKeyPath: DeterministicWallet.KeyPath): LocalParams = { def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingAmount: Satoshi, fundingKeyPath: DeterministicWallet.KeyPath): LocalParams = {
LocalParams( LocalParams(
nodeParams.nodeId, nodeParams.nodeId,
channelKeyPath, fundingKeyPath,
dustLimit = nodeParams.dustLimit, dustLimit = nodeParams.dustLimit,
maxHtlcValueInFlightMsat = nodeParams.maxHtlcValueInFlightMsat, maxHtlcValueInFlightMsat = nodeParams.maxHtlcValueInFlightMsat,
channelReserve = (fundingAmount * nodeParams.reserveToFundingRatio).max(nodeParams.dustLimit), // BOLT #2: make sure that our reserve is above our dust limit channelReserve = (fundingAmount * nodeParams.reserveToFundingRatio).max(nodeParams.dustLimit), // BOLT #2: make sure that our reserve is above our dust limit

View File

@ -55,7 +55,8 @@ object ChannelCodecs extends Logging {
.typecase(0x01, bits(ChannelVersion.LENGTH_BITS).as[ChannelVersion]) .typecase(0x01, bits(ChannelVersion.LENGTH_BITS).as[ChannelVersion])
// NB: 0x02 and 0x03 are *reserved* for backward compatibility reasons // NB: 0x02 and 0x03 are *reserved* for backward compatibility reasons
, ,
fallback = provide(ChannelVersion.STANDARD) fallback = provide(ChannelVersion.ZEROES) // README: DO NOT CHANGE THIS !! old channels don't have a channel version
// field and don't support additional features which is why all bits are set to 0.
) )
val localParamsCodec: Codec[LocalParams] = ( val localParamsCodec: Codec[LocalParams] = (

View File

@ -0,0 +1,10 @@
package fr.acinq.eclair.channel
import org.scalatest.FunSuite
class ChannelTypesSpec extends FunSuite {
test("standard channel features include deterministic channel key path") {
assert(ChannelVersion.STANDARD.isSet(ChannelVersion.USE_PUBKEY_KEYPATH_BIT))
assert(!ChannelVersion.ZEROES.isSet(ChannelVersion.USE_PUBKEY_KEYPATH_BIT))
}
}

View File

@ -0,0 +1,122 @@
package fr.acinq.eclair.channel
import akka.testkit.TestProbe
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin._
import fr.acinq.eclair.TestConstants.Alice
import fr.acinq.eclair.blockchain.WatchEventSpent
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.crypto.{Generators, KeyManager}
import fr.acinq.eclair.transactions.Scripts
import fr.acinq.eclair.transactions.Transactions.{ClaimP2WPKHOutputTx, InputInfo}
import fr.acinq.eclair.wire.{ChannelReestablish, CommitSig, Error, Init, RevokeAndAck}
import fr.acinq.eclair.{TestConstants, TestkitBaseClass, _}
import org.scalatest.Outcome
import scala.concurrent.duration._
class RecoverySpec extends TestkitBaseClass with StateTestsHelperMethods {
type FixtureParam = SetupFixture
override def withFixture(test: OneArgTest): Outcome = {
val setup = test.tags.contains("disable-offline-mismatch") match {
case false => init()
case true => init(nodeParamsA = Alice.nodeParams.copy(onChainFeeConf = Alice.nodeParams.onChainFeeConf.copy(closeOnOfflineMismatch = false)))
}
import setup._
within(30 seconds) {
reachNormal(setup)
awaitCond(alice.stateName == NORMAL)
awaitCond(bob.stateName == NORMAL)
withFixture(test.toNoArgTest(setup))
}
}
def aliceInit = Init(TestConstants.Alice.nodeParams.globalFeatures, TestConstants.Alice.nodeParams.localFeatures)
def bobInit = Init(TestConstants.Bob.nodeParams.globalFeatures, TestConstants.Bob.nodeParams.localFeatures)
test("use funding pubkeys from publish commitment to spend our output") { f =>
import f._
val sender = TestProbe()
// we start by storing the current state
val oldStateData = alice.stateData
// then we add an htlc and sign it
addHtlc(250000000 msat, alice, bob, alice2bob, bob2alice)
sender.send(alice, CMD_SIGN)
sender.expectMsg("ok")
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
// alice will receive neither the revocation nor the commit sig
bob2alice.expectMsgType[RevokeAndAck]
bob2alice.expectMsgType[CommitSig]
// we simulate a disconnection
sender.send(alice, INPUT_DISCONNECTED)
sender.send(bob, INPUT_DISCONNECTED)
awaitCond(alice.stateName == OFFLINE)
awaitCond(bob.stateName == OFFLINE)
// then we manually replace alice's state with an older one
alice.setState(OFFLINE, oldStateData)
// then we reconnect them
sender.send(alice, INPUT_RECONNECTED(alice2bob.ref, aliceInit, bobInit))
sender.send(bob, INPUT_RECONNECTED(bob2alice.ref, bobInit, aliceInit))
// peers exchange channel_reestablish messages
alice2bob.expectMsgType[ChannelReestablish]
val ce = bob2alice.expectMsgType[ChannelReestablish]
// alice then realizes it has an old state...
bob2alice.forward(alice)
// ... and ask bob to publish its current commitment
val error = alice2bob.expectMsgType[Error]
assert(new String(error.data.toArray) === PleasePublishYourCommitment(channelId(alice)).getMessage)
// alice now waits for bob to publish its commitment
awaitCond(alice.stateName == WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT)
// bob is nice and publishes its commitment
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
// actual tests starts here: let's see what we can do with Bob's commit tx
sender.send(alice, WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx))
// from Bob's commit tx we can extract both funding public keys
val OP_2 :: OP_PUSHDATA(pub1, _) :: OP_PUSHDATA(pub2, _) :: OP_2 :: OP_CHECKMULTISIG :: Nil = Script.parse(bobCommitTx.txIn(0).witness.stack.last)
// from Bob's commit tx we can also extract our p2wpkh output
val ourOutput = bobCommitTx.txOut.find(_.publicKeyScript.length == 22).get
val OP_0 :: OP_PUSHDATA(pubKeyHash, _) :: Nil = Script.parse(ourOutput.publicKeyScript)
val keyManager = TestConstants.Alice.nodeParams.keyManager
// find our funding pub key
val fundingPubKey = Seq(PublicKey(pub1), PublicKey(pub2)).find {
pub =>
val channelKeyPath = KeyManager.channelKeyPath(pub)
val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(channelKeyPath).publicKey, ce.myCurrentPerCommitmentPoint.get)
localPubkey.hash160 == pubKeyHash
} get
// compute our to-remote pubkey
val channelKeyPath = KeyManager.channelKeyPath(fundingPubKey)
val ourToRemotePubKey = Generators.derivePubKey(keyManager.paymentPoint(channelKeyPath).publicKey, ce.myCurrentPerCommitmentPoint.get)
// spend our output
val tx = Transaction(version = 2,
txIn = TxIn(OutPoint(bobCommitTx, bobCommitTx.txOut.indexOf(ourOutput)), sequence = TxIn.SEQUENCE_FINAL, signatureScript = Nil) :: Nil,
txOut = TxOut(Satoshi(1000), Script.pay2pkh(fr.acinq.eclair.randomKey.publicKey)) :: Nil,
lockTime = 0)
val sig = keyManager.sign(
ClaimP2WPKHOutputTx(InputInfo(OutPoint(bobCommitTx, bobCommitTx.txOut.indexOf(ourOutput)), ourOutput, Script.pay2pkh(ourToRemotePubKey)), tx),
keyManager.paymentPoint(channelKeyPath),
ce.myCurrentPerCommitmentPoint.get)
val tx1 = tx.updateWitness(0, ScriptWitness(Scripts.der(sig) :: ourToRemotePubKey.value :: Nil))
Transaction.correctlySpends(tx1, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
}
}

View File

@ -22,7 +22,7 @@ import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.wire.{Error, Init, OpenChannel} import fr.acinq.eclair.wire.{Error, Init, OpenChannel}
import fr.acinq.eclair.{CltvExpiryDelta, LongToBtcAmount, MilliSatoshi, TestConstants, TestkitBaseClass, ToMilliSatoshiConversion} import fr.acinq.eclair.{CltvExpiryDelta, LongToBtcAmount, TestConstants, TestkitBaseClass, ToMilliSatoshiConversion}
import org.scalatest.Outcome import org.scalatest.Outcome
import scala.concurrent.duration._ import scala.concurrent.duration._

View File

@ -27,8 +27,8 @@ import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
import org.scalatest.Outcome import org.scalatest.Outcome
import scodec.bits.ByteVector import scodec.bits.ByteVector
import scala.concurrent.{Future, Promise}
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.concurrent.{Future, Promise}
/** /**
* Created by PM on 05/07/2016. * Created by PM on 05/07/2016.

View File

@ -26,7 +26,6 @@ import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.wire.{AcceptChannel, Error, FundingCreated, FundingSigned, Init, OpenChannel} import fr.acinq.eclair.wire.{AcceptChannel, Error, FundingCreated, FundingSigned, Init, OpenChannel}
import fr.acinq.eclair.{TestConstants, TestkitBaseClass} import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
import org.scalatest.Outcome import org.scalatest.Outcome
import scodec.bits.ByteVector
import scala.concurrent.duration._ import scala.concurrent.duration._

View File

@ -2240,7 +2240,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42, null)) sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42, null))
val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures] val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures]
import initialState.commitments.{localParams, remoteParams} import initialState.commitments.{localParams, remoteParams}
val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.keyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature)
// actual test starts here // actual test starts here
bob2alice.forward(alice) bob2alice.forward(alice)
awaitCond({ awaitCond({
@ -2259,7 +2259,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 42, 10, null)) sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 42, 10, null))
val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures] val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures]
import initialState.commitments.{localParams, remoteParams} import initialState.commitments.{localParams, remoteParams}
val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.keyManager.fundingPublicKey(localParams.fundingKeyPath).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))

View File

@ -24,7 +24,7 @@ import fr.acinq.bitcoin.Crypto.PrivateKey
import fr.acinq.bitcoin.{ByteVector32, ScriptFlags, Transaction} import fr.acinq.bitcoin.{ByteVector32, ScriptFlags, Transaction}
import fr.acinq.eclair.TestConstants.Alice import fr.acinq.eclair.TestConstants.Alice
import fr.acinq.eclair.blockchain.fee.FeeratesPerKw import fr.acinq.eclair.blockchain.fee.FeeratesPerKw
import fr.acinq.eclair.blockchain.{CurrentBlockCount, CurrentFeerates, PublishAsap, WatchConfirmed, WatchEventSpent} import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel.Channel.LocalError import fr.acinq.eclair.channel.Channel.LocalError
import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.channel.states.StateTestsHelperMethods
@ -90,8 +90,12 @@ 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 = TestConstants.Bob.keyManager.commitmentPoint(bobCommitments.localParams.channelKeyPath, bobCommitments.localCommit.index) val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.commitmentPoint(
val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.commitmentPoint(aliceCommitments.localParams.channelKeyPath, aliceCommitments.localCommit.index) TestConstants.Bob.keyManager.channelKeyPath(bobCommitments.localParams, bobCommitments.channelVersion),
bobCommitments.localCommit.index)
val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.commitmentPoint(
TestConstants.Alice.keyManager.channelKeyPath(aliceCommitments.localParams, aliceCommitments.channelVersion),
aliceCommitments.localCommit.index)
// a didn't receive any update or sig // a didn't receive any update or sig
@ -174,8 +178,12 @@ 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 = TestConstants.Bob.keyManager.commitmentPoint(bobCommitments.localParams.channelKeyPath, bobCommitments.localCommit.index) val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.commitmentPoint(
val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.commitmentPoint(aliceCommitments.localParams.channelKeyPath, aliceCommitments.localCommit.index) TestConstants.Bob.keyManager.channelKeyPath(bobCommitments.localParams, bobCommitments.channelVersion),
bobCommitments.localCommit.index)
val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.commitmentPoint(
TestConstants.Alice.keyManager.channelKeyPath(aliceCommitments.localParams, aliceCommitments.channelVersion),
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(PrivateKey(ByteVector32.Zeroes)), Some(aliceCurrentPerCommitmentPoint))) val ab_reestablish = alice2bob.expectMsg(ChannelReestablish(ab_add_0.channelId, 1, 0, Some(PrivateKey(ByteVector32.Zeroes)), Some(aliceCurrentPerCommitmentPoint)))

View File

@ -33,9 +33,9 @@ import fr.acinq.eclair.transactions.Transactions.{CommitTx, InputInfo, Transacti
import fr.acinq.eclair.transactions._ import fr.acinq.eclair.transactions._
import fr.acinq.eclair.wire.ChannelCodecs._ import fr.acinq.eclair.wire.ChannelCodecs._
import fr.acinq.eclair.{TestConstants, UInt64, randomBytes, randomBytes32, randomKey, _} import fr.acinq.eclair.{TestConstants, UInt64, randomBytes, randomBytes32, randomKey, _}
import org.json4s.{CustomKeySerializer, CustomSerializer}
import org.json4s.JsonAST._ import org.json4s.JsonAST._
import org.json4s.jackson.Serialization import org.json4s.jackson.Serialization
import org.json4s.{CustomKeySerializer, CustomSerializer}
import org.scalatest.FunSuite import org.scalatest.FunSuite
import scodec.bits._ import scodec.bits._
import scodec.{Attempt, DecodeResult} import scodec.{Attempt, DecodeResult}
@ -71,21 +71,21 @@ class ChannelCodecsSpec extends FunSuite {
// before we had commitment version, public keys were stored first (they started with 0x02 and 0x03) // before we had commitment version, public keys were stored first (they started with 0x02 and 0x03)
val legacy02 = hex"02a06ea3081f0f7a8ce31eb4f0822d10d2da120d5a1b1451f0727f51c7372f0f9b" val legacy02 = hex"02a06ea3081f0f7a8ce31eb4f0822d10d2da120d5a1b1451f0727f51c7372f0f9b"
val legacy03 = hex"03d5c030835d6a6248b2d1d4cac60813838011b995a66b6f78dcc9fb8b5c40c3f3" val legacy03 = hex"03d5c030835d6a6248b2d1d4cac60813838011b995a66b6f78dcc9fb8b5c40c3f3"
val current02 = hex"010000000002a06ea3081f0f7a8ce31eb4f0822d10d2da120d5a1b1451f0727f51c7372f0f9b" val current02 = hex"010000000102a06ea3081f0f7a8ce31eb4f0822d10d2da120d5a1b1451f0727f51c7372f0f9b"
val current03 = hex"010000000003d5c030835d6a6248b2d1d4cac60813838011b995a66b6f78dcc9fb8b5c40c3f3" val current03 = hex"010000000103d5c030835d6a6248b2d1d4cac60813838011b995a66b6f78dcc9fb8b5c40c3f3"
assert(channelVersionCodec.decode(legacy02.bits) === Attempt.successful(DecodeResult(ChannelVersion.STANDARD, legacy02.bits))) assert(channelVersionCodec.decode(legacy02.bits) === Attempt.successful(DecodeResult(ChannelVersion.ZEROES, legacy02.bits)))
assert(channelVersionCodec.decode(legacy03.bits) === Attempt.successful(DecodeResult(ChannelVersion.STANDARD, legacy03.bits))) assert(channelVersionCodec.decode(legacy03.bits) === Attempt.successful(DecodeResult(ChannelVersion.ZEROES, legacy03.bits)))
assert(channelVersionCodec.decode(current02.bits) === Attempt.successful(DecodeResult(ChannelVersion.STANDARD, current02.drop(5).bits))) assert(channelVersionCodec.decode(current02.bits) === Attempt.successful(DecodeResult(ChannelVersion.STANDARD, current02.drop(5).bits)))
assert(channelVersionCodec.decode(current03.bits) === Attempt.successful(DecodeResult(ChannelVersion.STANDARD, current03.drop(5).bits))) assert(channelVersionCodec.decode(current03.bits) === Attempt.successful(DecodeResult(ChannelVersion.STANDARD, current03.drop(5).bits)))
assert(channelVersionCodec.encode(ChannelVersion.STANDARD) === Attempt.successful(hex"0100000000".bits)) assert(channelVersionCodec.encode(ChannelVersion.STANDARD) === Attempt.successful(hex"0100000001".bits))
} }
test("encode/decode localparams") { test("encode/decode localparams") {
val o = LocalParams( val o = LocalParams(
nodeId = randomKey.publicKey, nodeId = randomKey.publicKey,
channelKeyPath = DeterministicWallet.KeyPath(Seq(42L)), fundingKeyPath = DeterministicWallet.KeyPath(Seq(42L)),
dustLimit = Satoshi(Random.nextInt(Int.MaxValue)), dustLimit = Satoshi(Random.nextInt(Int.MaxValue)),
maxHtlcValueInFlightMsat = UInt64(Random.nextInt(Int.MaxValue)), maxHtlcValueInFlightMsat = UInt64(Random.nextInt(Int.MaxValue)),
channelReserve = Satoshi(Random.nextInt(Int.MaxValue)), channelReserve = Satoshi(Random.nextInt(Int.MaxValue)),
@ -323,6 +323,8 @@ class ChannelCodecsSpec extends FunSuite {
.replace(""""htlcMinimum"""", """"htlcMinimumMsat"""") .replace(""""htlcMinimum"""", """"htlcMinimumMsat"""")
.replace(""""toLocal"""", """"toLocalMsat"""") .replace(""""toLocal"""", """"toLocalMsat"""")
.replace(""""toRemote"""", """"toRemoteMsat"""") .replace(""""toRemote"""", """"toRemoteMsat"""")
.replace("fundingKeyPath", "channelKeyPath")
.replace(""""version":0,""", "")
val newjson = Serialization.write(newnormal)(JsonSupport.formats) val newjson = Serialization.write(newnormal)(JsonSupport.formats)
.replace(""","unknownFields":""""", "") .replace(""","unknownFields":""""", "")
@ -332,6 +334,8 @@ class ChannelCodecsSpec extends FunSuite {
.replace(""""htlcMinimum"""", """"htlcMinimumMsat"""") .replace(""""htlcMinimum"""", """"htlcMinimumMsat"""")
.replace(""""toLocal"""", """"toLocalMsat"""") .replace(""""toLocal"""", """"toLocalMsat"""")
.replace(""""toRemote"""", """"toRemoteMsat"""") .replace(""""toRemote"""", """"toRemoteMsat"""")
.replace("fundingKeyPath", "channelKeyPath")
.replace(""""version":0,""", "")
assert(oldjson === refjson) assert(oldjson === refjson)
assert(newjson === refjson) assert(newjson === refjson)
@ -343,8 +347,8 @@ object ChannelCodecsSpec {
val keyManager = new LocalKeyManager(ByteVector32(ByteVector.fill(32)(1)), Block.RegtestGenesisBlock.hash) val keyManager = new LocalKeyManager(ByteVector32(ByteVector.fill(32)(1)), Block.RegtestGenesisBlock.hash)
val localParams = LocalParams( val localParams = LocalParams(
keyManager.nodeId, keyManager.nodeId,
channelKeyPath = DeterministicWallet.KeyPath(Seq(42L)), fundingKeyPath = DeterministicWallet.KeyPath(Seq(42L)),
dustLimit = 546 sat, dustLimit = Satoshi(546),
maxHtlcValueInFlightMsat = UInt64(50000000), maxHtlcValueInFlightMsat = UInt64(50000000),
channelReserve = 10000 sat, channelReserve = 10000 sat,
htlcMinimum = 10000 msat, htlcMinimum = 10000 msat,
@ -389,7 +393,7 @@ object ChannelCodecsSpec {
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, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey) val commitmentInput = Funding.makeFundingInputInfo(fundingTx.hash, 0, fundingAmount, keyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey, remoteParams.fundingPubKey)
val localCommit = LocalCommit(0, CommitmentSpec(htlcs.toSet, 1500, 50000000 msat, 70000000 msat), PublishableTxs(CommitTx(commitmentInput, Transaction(2, Nil, Nil, 0)), Nil)) val localCommit = LocalCommit(0, CommitmentSpec(htlcs.toSet, 1500, 50000000 msat, 70000000 msat), PublishableTxs(CommitTx(commitmentInput, Transaction(2, Nil, Nil, 0)), Nil))
val remoteCommit = RemoteCommit(0, CommitmentSpec(htlcs.map(htlc => htlc.copy(direction = htlc.direction.opposite)).toSet, 1500, 50000 msat, 700000 msat), ByteVector32(hex"0303030303030303030303030303030303030303030303030303030303030303"), PrivateKey(ByteVector.fill(32)(4)).publicKey) val remoteCommit = RemoteCommit(0, CommitmentSpec(htlcs.map(htlc => htlc.copy(direction = htlc.direction.opposite)).toSet, 1500, 50000 msat, 700000 msat), ByteVector32(hex"0303030303030303030303030303030303030303030303030303030303030303"), PrivateKey(ByteVector.fill(32)(4)).publicKey)