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:
parent
abf3907d4d
commit
ea773425c2
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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] = (
|
||||||
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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._
|
||||||
|
@ -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.
|
||||||
|
@ -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._
|
||||||
|
|
||||||
|
@ -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))
|
||||||
|
|
||||||
|
@ -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)))
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user