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)
|
||||
|
||||
when(WAIT_FOR_INIT_INTERNAL)(handleExceptions {
|
||||
case Event(initFunder@INPUT_INIT_FUNDER(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, localParams, remote, _, channelFlags, _), Nothing) =>
|
||||
case Event(initFunder@INPUT_INIT_FUNDER(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, localParams, remote, _, channelFlags, channelVersion), Nothing) =>
|
||||
context.system.eventStream.publish(ChannelCreated(self, context.parent, remoteNodeId, isFunder = true, temporaryChannelId, initialFeeratePerKw, Some(fundingTxFeeratePerKw)))
|
||||
forwarder ! remote
|
||||
val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey
|
||||
val channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
|
||||
val open = OpenChannel(nodeParams.chainHash,
|
||||
temporaryChannelId = temporaryChannelId,
|
||||
fundingSatoshis = fundingSatoshis,
|
||||
@ -159,12 +161,12 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
|
||||
feeratePerKw = initialFeeratePerKw,
|
||||
toSelfDelay = localParams.toSelfDelay,
|
||||
maxAcceptedHtlcs = localParams.maxAcceptedHtlcs,
|
||||
fundingPubkey = keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey,
|
||||
revocationBasepoint = keyManager.revocationPoint(localParams.channelKeyPath).publicKey,
|
||||
paymentBasepoint = keyManager.paymentPoint(localParams.channelKeyPath).publicKey,
|
||||
delayedPaymentBasepoint = keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey,
|
||||
htlcBasepoint = keyManager.htlcPoint(localParams.channelKeyPath).publicKey,
|
||||
firstPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 0),
|
||||
fundingPubkey = fundingPubKey,
|
||||
revocationBasepoint = keyManager.revocationPoint(channelKeyPath).publicKey,
|
||||
paymentBasepoint = keyManager.paymentPoint(channelKeyPath).publicKey,
|
||||
delayedPaymentBasepoint = keyManager.delayedPaymentPoint(channelKeyPath).publicKey,
|
||||
htlcBasepoint = keyManager.htlcPoint(channelKeyPath).publicKey,
|
||||
firstPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 0),
|
||||
channelFlags = channelFlags)
|
||||
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 Success(_) =>
|
||||
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
|
||||
val minimumDepth = nodeParams.minDepthBlocks
|
||||
val accept = AcceptChannel(temporaryChannelId = open.temporaryChannelId,
|
||||
@ -281,12 +286,12 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
|
||||
htlcMinimumMsat = localParams.htlcMinimum,
|
||||
toSelfDelay = localParams.toSelfDelay,
|
||||
maxAcceptedHtlcs = localParams.maxAcceptedHtlcs,
|
||||
fundingPubkey = keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey,
|
||||
revocationBasepoint = keyManager.revocationPoint(localParams.channelKeyPath).publicKey,
|
||||
paymentBasepoint = keyManager.paymentPoint(localParams.channelKeyPath).publicKey,
|
||||
delayedPaymentBasepoint = keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey,
|
||||
htlcBasepoint = keyManager.htlcPoint(localParams.channelKeyPath).publicKey,
|
||||
firstPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 0))
|
||||
fundingPubkey = fundingPubkey,
|
||||
revocationBasepoint = keyManager.revocationPoint(channelKeyPath).publicKey,
|
||||
paymentBasepoint = keyManager.paymentPoint(channelKeyPath).publicKey,
|
||||
delayedPaymentBasepoint = keyManager.delayedPaymentPoint(channelKeyPath).publicKey,
|
||||
htlcBasepoint = keyManager.htlcPoint(channelKeyPath).publicKey,
|
||||
firstPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 0))
|
||||
val remoteParams = RemoteParams(
|
||||
nodeId = remoteNodeId,
|
||||
dustLimit = open.dustLimitSatoshis,
|
||||
@ -303,7 +308,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
|
||||
globalFeatures = remoteInit.globalFeatures,
|
||||
localFeatures = remoteInit.localFeatures)
|
||||
log.debug(s"remote params: $remoteParams")
|
||||
goto(WAIT_FOR_FUNDING_CREATED) using DATA_WAIT_FOR_FUNDING_CREATED(open.temporaryChannelId, localParams, remoteParams, open.fundingSatoshis, open.pushMsat, open.feeratePerKw, open.firstPerCommitmentPoint, open.channelFlags, ChannelVersion.STANDARD, accept) sending accept
|
||||
goto(WAIT_FOR_FUNDING_CREATED) using DATA_WAIT_FOR_FUNDING_CREATED(open.temporaryChannelId, localParams, remoteParams, open.fundingSatoshis, open.pushMsat, open.feeratePerKw, open.firstPerCommitmentPoint, open.channelFlags, channelVersion, accept) sending accept
|
||||
}
|
||||
|
||||
case Event(CMD_CLOSE(_), _) => goto(CLOSED) replying "ok"
|
||||
@ -336,8 +341,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
|
||||
globalFeatures = remoteInit.globalFeatures,
|
||||
localFeatures = remoteInit.localFeatures)
|
||||
log.debug(s"remote params: $remoteParams")
|
||||
val localFundingPubkey = keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey
|
||||
val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey, remoteParams.fundingPubKey)))
|
||||
val localFundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath)
|
||||
val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey.publicKey, remoteParams.fundingPubKey)))
|
||||
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)
|
||||
}
|
||||
@ -362,9 +367,9 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
|
||||
when(WAIT_FOR_FUNDING_INTERNAL)(handleExceptions {
|
||||
case Event(MakeFundingTxResponse(fundingTx, fundingTxOutputIndex, fundingTxFee), DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, remoteFirstPerCommitmentPoint, channelVersion, open)) =>
|
||||
// let's create the first commitment tx that spends the yet uncommitted funding tx
|
||||
val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.onChainFeeConf.maxFeerateMismatch)
|
||||
val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, channelVersion, temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.onChainFeeConf.maxFeerateMismatch)
|
||||
require(fundingTx.txOut(fundingTxOutputIndex).publicKeyScript == localCommitTx.input.txOut.publicKeyScript, s"pubkey script mismatch!")
|
||||
val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath))
|
||||
val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.fundingKeyPath))
|
||||
// signature of their initial commitment tx that pays remote pushMsat
|
||||
val fundingCreated = FundingCreated(
|
||||
temporaryChannelId = temporaryChannelId,
|
||||
@ -403,15 +408,16 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
|
||||
when(WAIT_FOR_FUNDING_CREATED)(handleExceptions {
|
||||
case Event(FundingCreated(_, fundingTxHash, fundingTxOutputIndex, remoteSig), d@DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, remoteFirstPerCommitmentPoint, channelFlags, channelVersion, _)) =>
|
||||
// they fund the channel with their funding tx, so the money is theirs (but we are paid pushMsat)
|
||||
val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, fundingTxHash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.onChainFeeConf.maxFeerateMismatch)
|
||||
val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, channelVersion, temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, fundingTxHash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, nodeParams.onChainFeeConf.maxFeerateMismatch)
|
||||
|
||||
// check remote signature validity
|
||||
val localSigOfLocalTx = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath))
|
||||
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig)
|
||||
val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath)
|
||||
val localSigOfLocalTx = keyManager.sign(localCommitTx, fundingPubKey)
|
||||
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, fundingPubKey.publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig)
|
||||
Transactions.checkSpendable(signedLocalCommitTx) match {
|
||||
case Failure(cause) => handleLocalError(InvalidCommitmentSignature(temporaryChannelId, signedLocalCommitTx.tx), d, None)
|
||||
case Success(_) =>
|
||||
val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath))
|
||||
val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, fundingPubKey)
|
||||
val channelId = toLongId(fundingTxHash, fundingTxOutputIndex)
|
||||
// watch the funding tx transaction
|
||||
val commitInput = localCommitTx.input
|
||||
@ -447,8 +453,9 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
|
||||
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)) =>
|
||||
// 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 signedLocalCommitTx = Transactions.addSigs(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig)
|
||||
val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath)
|
||||
val localSigOfLocalTx = keyManager.sign(localCommitTx, fundingPubKey)
|
||||
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, fundingPubKey.publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig)
|
||||
Transactions.checkSpendable(signedLocalCommitTx) match {
|
||||
case Failure(cause) =>
|
||||
// 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(_) =>
|
||||
log.info(s"channelId=${commitments.channelId} was confirmed at blockHeight=$blockHeight txIndex=$txIndex")
|
||||
blockchain ! WatchLost(self, commitments.commitInput.outPoint.txid, nodeParams.minDepthBlocks, BITCOIN_FUNDING_LOST)
|
||||
val nextPerCommitmentPoint = 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)
|
||||
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
|
||||
@ -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}")
|
||||
log.info(s"announcing channelId=${d.channelId} on the network with shortId=${d.shortChannelId}")
|
||||
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
|
||||
goto(NORMAL) using d.copy(channelAnnouncement = Some(channelAnn)) storing()
|
||||
case Some(_) =>
|
||||
@ -1383,7 +1392,8 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
|
||||
forwarder ! r
|
||||
|
||||
val yourLastPerCommitmentSecret = d.commitments.remotePerCommitmentSecrets.lastIndex.flatMap(d.commitments.remotePerCommitmentSecrets.getHash).getOrElse(ByteVector32.Zeroes)
|
||||
val 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(
|
||||
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) =>
|
||||
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)
|
||||
goto(WAIT_FOR_FUNDING_LOCKED) sending fundingLocked
|
||||
|
||||
case Event(channelReestablish: ChannelReestablish, d: DATA_NORMAL) =>
|
||||
val channelKeyPath = keyManager.channelKeyPath(d.commitments.localParams, d.commitments.channelVersion)
|
||||
channelReestablish match {
|
||||
case ChannelReestablish(_, _, nextRemoteRevocationNumber, Some(yourLastPerCommitmentSecret), _) if !Helpers.checkLocalCommit(d, nextRemoteRevocationNumber) =>
|
||||
// if next_remote_revocation_number is greater than our local commitment index, it means that either we are using an outdated commitment, or they are lying
|
||||
// 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")
|
||||
// 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
|
||||
@ -1477,7 +1489,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
|
||||
if (channelReestablish.nextLocalCommitmentNumber == 1 && d.commitments.localCommit.index == 0) {
|
||||
// If next_local_commitment_number is 1 in both the channel_reestablish it sent and received, then the node MUST retransmit funding_locked, otherwise it MUST NOT
|
||||
log.debug(s"re-sending fundingLocked")
|
||||
val nextPerCommitmentPoint = keyManager.commitmentPoint(d.commitments.localParams.channelKeyPath, 1)
|
||||
val nextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 1)
|
||||
val fundingLocked = FundingLocked(d.commitments.channelId, nextPerCommitmentPoint)
|
||||
forwarder ! fundingLocked
|
||||
}
|
||||
@ -2140,8 +2152,9 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
|
||||
} else if (commitments1.localCommit.index == channelReestablish.nextRemoteRevocationNumber + 1) {
|
||||
// our last revocation got lost, let's resend it
|
||||
log.debug(s"re-sending last revocation")
|
||||
val localPerCommitmentSecret = keyManager.commitmentSecret(commitments1.localParams.channelKeyPath, d.commitments.localCommit.index - 1)
|
||||
val localNextPerCommitmentPoint = keyManager.commitmentPoint(commitments1.localParams.channelKeyPath, d.commitments.localCommit.index + 1)
|
||||
val channelKeyPath = keyManager.channelKeyPath(d.commitments.localParams, d.commitments.channelVersion)
|
||||
val localPerCommitmentSecret = keyManager.commitmentSecret(channelKeyPath, d.commitments.localCommit.index - 1)
|
||||
val localNextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, d.commitments.localCommit.index + 1)
|
||||
val revocation = RevokeAndAck(
|
||||
channelId = commitments1.channelId,
|
||||
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 LocalParams(nodeId: PublicKey,
|
||||
channelKeyPath: DeterministicWallet.KeyPath,
|
||||
fundingKeyPath: DeterministicWallet.KeyPath,
|
||||
dustLimit: Satoshi,
|
||||
maxHtlcValueInFlightMsat: UInt64, // this is not MilliSatoshi because it can exceed the total amount of MilliSatoshi
|
||||
channelReserve: Satoshi,
|
||||
@ -224,9 +224,23 @@ object ChannelFlags {
|
||||
|
||||
case class ChannelVersion(bits: BitVector) {
|
||||
require(bits.size == ChannelVersion.LENGTH_BITS, "channel version takes 4 bytes")
|
||||
|
||||
def |(other: ChannelVersion) = ChannelVersion(bits | other.bits)
|
||||
def &(other: ChannelVersion) = ChannelVersion(bits & other.bits)
|
||||
def ^(other: ChannelVersion) = ChannelVersion(bits ^ other.bits)
|
||||
def isSet(bit: Int) = bits.reverse.get(bit)
|
||||
}
|
||||
|
||||
object ChannelVersion {
|
||||
import scodec.bits._
|
||||
val LENGTH_BITS = 4 * 8
|
||||
val STANDARD = ChannelVersion(BitVector.fill(LENGTH_BITS)(false))
|
||||
val ZEROES = ChannelVersion(bin"00000000000000000000000000000000")
|
||||
val 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
|
||||
|
@ -376,11 +376,12 @@ object Commitments {
|
||||
case Right(remoteNextPerCommitmentPoint) =>
|
||||
// remote commitment will includes all local changes + remote acked changes
|
||||
val spec = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed)
|
||||
val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(keyManager, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, spec)
|
||||
val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath))
|
||||
val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(keyManager, channelVersion, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, spec)
|
||||
val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(commitments.localParams.fundingKeyPath))
|
||||
|
||||
val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index)
|
||||
val 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
|
||||
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
|
||||
|
||||
val spec = CommitmentSpec.reduce(localCommit.spec, localChanges.acked, remoteChanges.proposed)
|
||||
val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index + 1)
|
||||
val (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeLocalTxs(keyManager, localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, spec)
|
||||
val sig = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(localParams.channelKeyPath))
|
||||
val channelKeyPath = keyManager.channelKeyPath(commitments.localParams, commitments.channelVersion)
|
||||
val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitments.localCommit.index + 1)
|
||||
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)
|
||||
|
||||
// TODO: should we have optional sig? (original comment: this tx will NOT be signed if our output is empty)
|
||||
|
||||
// no need to compute htlc sigs if commit sig doesn't check out
|
||||
val signedCommitTx = Transactions.addSigs(localCommitTx, 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) {
|
||||
throw InvalidCommitmentSignature(commitments.channelId, signedCommitTx.tx)
|
||||
}
|
||||
@ -442,7 +444,7 @@ object Commitments {
|
||||
if (commit.htlcSignatures.size != sortedHtlcTxs.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)
|
||||
// combine the sigs to make signed txes
|
||||
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
|
||||
val localPerCommitmentSecret = keyManager.commitmentSecret(localParams.channelKeyPath, commitments.localCommit.index)
|
||||
val localNextPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index + 2)
|
||||
val localPerCommitmentSecret = keyManager.commitmentSecret(channelKeyPath, commitments.localCommit.index)
|
||||
val localNextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitments.localCommit.index + 2)
|
||||
val revocation = RevokeAndAck(
|
||||
channelId = commitments.channelId,
|
||||
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]) = {
|
||||
val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint)
|
||||
val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, localPerCommitmentPoint)
|
||||
def makeLocalTxs(keyManager: KeyManager, channelVersion: ChannelVersion, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, localPerCommitmentPoint: PublicKey, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = {
|
||||
val channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
|
||||
val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(channelKeyPath).publicKey, localPerCommitmentPoint)
|
||||
val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(channelKeyPath).publicKey, localPerCommitmentPoint)
|
||||
val remotePaymentPubkey = Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint)
|
||||
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, 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)
|
||||
(commitTx, htlcTimeoutTxs, htlcSuccessTxs)
|
||||
}
|
||||
|
||||
def makeRemoteTxs(keyManager: KeyManager, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: PublicKey, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = {
|
||||
val localPaymentPubkey = Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint)
|
||||
val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint)
|
||||
def makeRemoteTxs(keyManager: KeyManager, channelVersion: ChannelVersion, commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, commitmentInput: InputInfo, remotePerCommitmentPoint: PublicKey, spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = {
|
||||
val channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
|
||||
val localPaymentPubkey = Generators.derivePubKey(keyManager.paymentPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
|
||||
val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
|
||||
val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint)
|
||||
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint)
|
||||
val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint)
|
||||
val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, keyManager.paymentPoint(localParams.channelKeyPath).publicKey, !localParams.isFunder, remoteParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, spec)
|
||||
val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
|
||||
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)
|
||||
(commitTx, htlcTimeoutTxs, htlcSuccessTxs)
|
||||
}
|
||||
|
@ -204,7 +204,8 @@ object Helpers {
|
||||
|
||||
def makeAnnouncementSignatures(nodeParams: NodeParams, commitments: Commitments, shortChannelId: ShortChannelId) = {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -251,7 +252,7 @@ object Helpers {
|
||||
* @param remoteFirstPerCommitmentPoint
|
||||
* @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 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 localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, 0)
|
||||
val (localCommitTx, _, _) = Commitments.makeLocalTxs(keyManager, 0, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, localSpec)
|
||||
val (remoteCommitTx, _, _) = Commitments.makeRemoteTxs(keyManager, 0, localParams, remoteParams, commitmentInput, remoteFirstPerCommitmentPoint, remoteSpec)
|
||||
val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath)
|
||||
val channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
|
||||
val commitmentInput = makeFundingInputInfo(fundingTxHash, fundingTxOutputIndex, fundingAmount, fundingPubKey.publicKey, remoteParams.fundingPubKey)
|
||||
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)
|
||||
}
|
||||
@ -454,7 +457,7 @@ object Helpers {
|
||||
// TODO: check that
|
||||
val dustLimitSatoshis = localParams.dustLimit.max(remoteParams.dustLimit)
|
||||
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)
|
||||
log.info(s"signed closing txid=${closingTx.tx.txid} with closingFeeSatoshis=${closingSigned.feeSatoshis}")
|
||||
log.debug(s"closingTxid=${closingTx.tx.txid} closingTx=${closingTx.tx}}")
|
||||
@ -469,7 +472,7 @@ object Helpers {
|
||||
throw InvalidCloseFee(commitments.channelId, 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) }
|
||||
}
|
||||
|
||||
@ -498,17 +501,17 @@ object Helpers {
|
||||
def claimCurrentLocalCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, tx: Transaction, feeEstimator: FeeEstimator, feeTargets: FeeTargets)(implicit log: LoggingAdapter): LocalCommitPublished = {
|
||||
import commitments._
|
||||
require(localCommit.publishableTxs.commitTx.tx.txid == tx.txid, "txid mismatch, provided tx is not the current local commit tx")
|
||||
|
||||
val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index.toInt)
|
||||
val channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
|
||||
val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitments.localCommit.index.toInt)
|
||||
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)
|
||||
|
||||
// first we will claim our main output as soon as the delay is over
|
||||
val mainDelayedTx = generateTx("main-delayed-output")(Try {
|
||||
val claimDelayed = Transactions.makeClaimDelayedOutputTx(tx, 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)
|
||||
})
|
||||
|
||||
@ -543,7 +546,7 @@ object Helpers {
|
||||
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)
|
||||
})
|
||||
}
|
||||
@ -567,15 +570,15 @@ object Helpers {
|
||||
* @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 = {
|
||||
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")
|
||||
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")
|
||||
|
||||
val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint)
|
||||
val channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
|
||||
val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint)
|
||||
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remoteCommit.remotePerCommitmentPoint)
|
||||
val localPerCommitmentPoint = keyManager.commitmentPoint(localParams.channelKeyPath, commitments.localCommit.index.toInt)
|
||||
val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint)
|
||||
val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitments.localCommit.index.toInt)
|
||||
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
|
||||
val feeratePerKwHtlc = feeEstimator.getFeeratePerKw(target = 2)
|
||||
@ -591,7 +594,7 @@ object Helpers {
|
||||
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)
|
||||
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)
|
||||
})
|
||||
|
||||
@ -601,7 +604,7 @@ object Helpers {
|
||||
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)
|
||||
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)
|
||||
})
|
||||
}.toSeq.flatten
|
||||
@ -624,14 +627,15 @@ object Helpers {
|
||||
* @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 = {
|
||||
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 mainTx = generateTx("claim-p2wpkh-output")(Try {
|
||||
val claimMain = Transactions.makeClaimP2WPKHOutputTx(tx, commitments.localParams.dustLimit,
|
||||
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)
|
||||
})
|
||||
|
||||
@ -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] = {
|
||||
import commitments._
|
||||
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)
|
||||
// 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")
|
||||
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
|
||||
@ -667,9 +673,9 @@ object Helpers {
|
||||
.map { remotePerCommitmentSecret =>
|
||||
val remotePerCommitmentPoint = remotePerCommitmentSecret.publicKey
|
||||
val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint)
|
||||
val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint)
|
||||
val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint)
|
||||
val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(localParams.channelKeyPath).publicKey, remotePerCommitmentPoint)
|
||||
val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
|
||||
val localPubkey = Generators.derivePubKey(keyManager.paymentPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
|
||||
val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
|
||||
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint)
|
||||
|
||||
val feeratePerKwMain = feeEstimator.getFeeratePerKw(feeTargets.claimMainBlockTarget)
|
||||
@ -679,14 +685,14 @@ object Helpers {
|
||||
// first we will claim our main output right away
|
||||
val mainTx = generateTx("claim-p2wpkh-output")(Try {
|
||||
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)
|
||||
})
|
||||
|
||||
// then we punish them by stealing their main output
|
||||
val mainPenaltyTx = generateTx("main-penalty")(Try {
|
||||
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)
|
||||
})
|
||||
|
||||
@ -707,7 +713,7 @@ object Helpers {
|
||||
generateTx("htlc-penalty")(Try {
|
||||
val htlcPenalty = Transactions.makeHtlcPenaltyTx(tx, outputsAlreadyUsed, htlcRedeemScript, localParams.dustLimit, localParams.defaultFinalScriptPubKey, feeratePerKwPenalty)
|
||||
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)
|
||||
})
|
||||
}.toList.flatten
|
||||
@ -747,22 +753,23 @@ object Helpers {
|
||||
import commitments._
|
||||
val tx = revokedCommitPublished.commitTx
|
||||
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
|
||||
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
|
||||
remotePerCommitmentSecrets.getHash(0xFFFFFFFFFFFFL - txnumber)
|
||||
.map(d => PrivateKey(d))
|
||||
.flatMap { remotePerCommitmentSecret =>
|
||||
val remotePerCommitmentPoint = remotePerCommitmentSecret.publicKey
|
||||
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
|
||||
val feeratePerKwPenalty = feeEstimator.getFeeratePerKw(target = 1)
|
||||
|
||||
generateTx("claim-htlc-delayed-penalty")(Try {
|
||||
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)
|
||||
// we need to make sure that the tx is indeed valid
|
||||
Transaction.correctlySpends(signedTx.tx, Seq(htlcTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
|
@ -16,10 +16,14 @@
|
||||
|
||||
package fr.acinq.eclair.crypto
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.nio.ByteOrder
|
||||
|
||||
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
|
||||
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.channel.{ChannelVersion, LocalParams}
|
||||
import fr.acinq.eclair.transactions.Transactions.TransactionWithInputInfo
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
@ -28,7 +32,7 @@ trait KeyManager {
|
||||
|
||||
def nodeId: PublicKey
|
||||
|
||||
def fundingPublicKey(channelKeyPath: DeterministicWallet.KeyPath): ExtendedPublicKey
|
||||
def fundingPublicKey(keyPath: 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 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
|
||||
@ -73,5 +85,37 @@ trait KeyManager {
|
||||
*/
|
||||
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)
|
||||
}
|
||||
|
||||
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 localFundingPrivKey = fundingPrivateKey(channelKeyPath).privateKey
|
||||
val localFundingPrivKey = privateKeys.get(fundingKeyPath).privateKey
|
||||
Announcements.signChannelAnnouncement(chainHash, shortChannelId, localNodeSecret, remoteNodeId, localFundingPrivKey, remoteFundingKey, features)
|
||||
}
|
||||
}
|
||||
|
@ -16,16 +16,14 @@
|
||||
|
||||
package fr.acinq.eclair.io
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.net.InetSocketAddress
|
||||
import java.nio.ByteOrder
|
||||
|
||||
import akka.actor.{ActorRef, FSM, OneForOneStrategy, PoisonPill, Props, Status, SupervisorStrategy, Terminated}
|
||||
import akka.event.Logging.MDC
|
||||
import akka.util.Timeout
|
||||
import com.google.common.net.HostAndPort
|
||||
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.channel._
|
||||
import fr.acinq.eclair.crypto.TransportHandler
|
||||
@ -664,17 +662,16 @@ object Peer {
|
||||
}
|
||||
|
||||
def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingAmount: Satoshi): LocalParams = {
|
||||
val entropy = new Array[Byte](16)
|
||||
secureRandom.nextBytes(entropy)
|
||||
val bis = new ByteArrayInputStream(entropy)
|
||||
val channelKeyPath = DeterministicWallet.KeyPath(Seq(Protocol.uint32(bis, ByteOrder.BIG_ENDIAN), Protocol.uint32(bis, ByteOrder.BIG_ENDIAN), Protocol.uint32(bis, ByteOrder.BIG_ENDIAN), Protocol.uint32(bis, ByteOrder.BIG_ENDIAN)))
|
||||
makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder, fundingAmount, channelKeyPath)
|
||||
// we make sure that funder and fundee key path end differently
|
||||
val last = DeterministicWallet.hardened(if (isFunder) 1 else 0)
|
||||
val fundingKeyPath = DeterministicWallet.KeyPath(Seq(secureRandom.nextInt() & 0xFFFFFFFFL, secureRandom.nextInt() & 0xFFFFFFFFL, last))
|
||||
makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder, fundingAmount, fundingKeyPath)
|
||||
}
|
||||
|
||||
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(
|
||||
nodeParams.nodeId,
|
||||
channelKeyPath,
|
||||
fundingKeyPath,
|
||||
dustLimit = nodeParams.dustLimit,
|
||||
maxHtlcValueInFlightMsat = nodeParams.maxHtlcValueInFlightMsat,
|
||||
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])
|
||||
// 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] = (
|
||||
|
@ -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.states.StateTestsHelperMethods
|
||||
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 scala.concurrent.duration._
|
||||
|
@ -27,8 +27,8 @@ import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
|
||||
import org.scalatest.Outcome
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.concurrent.{Future, Promise}
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.{Future, Promise}
|
||||
|
||||
/**
|
||||
* 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.{TestConstants, TestkitBaseClass}
|
||||
import org.scalatest.Outcome
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
|
@ -2240,7 +2240,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||
sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42, null))
|
||||
val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures]
|
||||
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
|
||||
bob2alice.forward(alice)
|
||||
awaitCond({
|
||||
@ -2259,7 +2259,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||
sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 42, 10, null))
|
||||
val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures]
|
||||
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)
|
||||
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.eclair.TestConstants.Alice
|
||||
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._
|
||||
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 aliceCommitments = alice.stateData.asInstanceOf[HasCommitments].commitments
|
||||
|
||||
val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.commitmentPoint(bobCommitments.localParams.channelKeyPath, bobCommitments.localCommit.index)
|
||||
val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.commitmentPoint(aliceCommitments.localParams.channelKeyPath, aliceCommitments.localCommit.index)
|
||||
val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.commitmentPoint(
|
||||
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
|
||||
@ -174,8 +178,12 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||
val bobCommitments = bob.stateData.asInstanceOf[HasCommitments].commitments
|
||||
val aliceCommitments = alice.stateData.asInstanceOf[HasCommitments].commitments
|
||||
|
||||
val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.commitmentPoint(bobCommitments.localParams.channelKeyPath, bobCommitments.localCommit.index)
|
||||
val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.commitmentPoint(aliceCommitments.localParams.channelKeyPath, aliceCommitments.localCommit.index)
|
||||
val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.commitmentPoint(
|
||||
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
|
||||
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.wire.ChannelCodecs._
|
||||
import fr.acinq.eclair.{TestConstants, UInt64, randomBytes, randomBytes32, randomKey, _}
|
||||
import org.json4s.{CustomKeySerializer, CustomSerializer}
|
||||
import org.json4s.JsonAST._
|
||||
import org.json4s.jackson.Serialization
|
||||
import org.json4s.{CustomKeySerializer, CustomSerializer}
|
||||
import org.scalatest.FunSuite
|
||||
import scodec.bits._
|
||||
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)
|
||||
val legacy02 = hex"02a06ea3081f0f7a8ce31eb4f0822d10d2da120d5a1b1451f0727f51c7372f0f9b"
|
||||
val legacy03 = hex"03d5c030835d6a6248b2d1d4cac60813838011b995a66b6f78dcc9fb8b5c40c3f3"
|
||||
val current02 = hex"010000000002a06ea3081f0f7a8ce31eb4f0822d10d2da120d5a1b1451f0727f51c7372f0f9b"
|
||||
val current03 = hex"010000000003d5c030835d6a6248b2d1d4cac60813838011b995a66b6f78dcc9fb8b5c40c3f3"
|
||||
val current02 = hex"010000000102a06ea3081f0f7a8ce31eb4f0822d10d2da120d5a1b1451f0727f51c7372f0f9b"
|
||||
val current03 = hex"010000000103d5c030835d6a6248b2d1d4cac60813838011b995a66b6f78dcc9fb8b5c40c3f3"
|
||||
|
||||
assert(channelVersionCodec.decode(legacy02.bits) === Attempt.successful(DecodeResult(ChannelVersion.STANDARD, legacy02.bits)))
|
||||
assert(channelVersionCodec.decode(legacy03.bits) === Attempt.successful(DecodeResult(ChannelVersion.STANDARD, legacy03.bits)))
|
||||
assert(channelVersionCodec.decode(legacy02.bits) === Attempt.successful(DecodeResult(ChannelVersion.ZEROES, legacy02.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(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") {
|
||||
val o = LocalParams(
|
||||
nodeId = randomKey.publicKey,
|
||||
channelKeyPath = DeterministicWallet.KeyPath(Seq(42L)),
|
||||
fundingKeyPath = DeterministicWallet.KeyPath(Seq(42L)),
|
||||
dustLimit = Satoshi(Random.nextInt(Int.MaxValue)),
|
||||
maxHtlcValueInFlightMsat = UInt64(Random.nextInt(Int.MaxValue)),
|
||||
channelReserve = Satoshi(Random.nextInt(Int.MaxValue)),
|
||||
@ -323,6 +323,8 @@ class ChannelCodecsSpec extends FunSuite {
|
||||
.replace(""""htlcMinimum"""", """"htlcMinimumMsat"""")
|
||||
.replace(""""toLocal"""", """"toLocalMsat"""")
|
||||
.replace(""""toRemote"""", """"toRemoteMsat"""")
|
||||
.replace("fundingKeyPath", "channelKeyPath")
|
||||
.replace(""""version":0,""", "")
|
||||
|
||||
val newjson = Serialization.write(newnormal)(JsonSupport.formats)
|
||||
.replace(""","unknownFields":""""", "")
|
||||
@ -332,6 +334,8 @@ class ChannelCodecsSpec extends FunSuite {
|
||||
.replace(""""htlcMinimum"""", """"htlcMinimumMsat"""")
|
||||
.replace(""""toLocal"""", """"toLocalMsat"""")
|
||||
.replace(""""toRemote"""", """"toRemoteMsat"""")
|
||||
.replace("fundingKeyPath", "channelKeyPath")
|
||||
.replace(""""version":0,""", "")
|
||||
|
||||
assert(oldjson === refjson)
|
||||
assert(newjson === refjson)
|
||||
@ -343,8 +347,8 @@ object ChannelCodecsSpec {
|
||||
val keyManager = new LocalKeyManager(ByteVector32(ByteVector.fill(32)(1)), Block.RegtestGenesisBlock.hash)
|
||||
val localParams = LocalParams(
|
||||
keyManager.nodeId,
|
||||
channelKeyPath = DeterministicWallet.KeyPath(Seq(42L)),
|
||||
dustLimit = 546 sat,
|
||||
fundingKeyPath = DeterministicWallet.KeyPath(Seq(42L)),
|
||||
dustLimit = Satoshi(546),
|
||||
maxHtlcValueInFlightMsat = UInt64(50000000),
|
||||
channelReserve = 10000 sat,
|
||||
htlcMinimum = 10000 msat,
|
||||
@ -389,7 +393,7 @@ object ChannelCodecsSpec {
|
||||
|
||||
val fundingTx = Transaction.read("0200000001adbb20ea41a8423ea937e76e8151636bf6093b70eaff942930d20576600521fd000000006b48304502210090587b6201e166ad6af0227d3036a9454223d49a1f11839c1a362184340ef0240220577f7cd5cca78719405cbf1de7414ac027f0239ef6e214c90fcaab0454d84b3b012103535b32d5eb0a6ed0982a0479bbadc9868d9836f6ba94dd5a63be16d875069184ffffffff028096980000000000220020c015c4a6be010e21657068fc2e6a9d02b27ebe4d490a25846f7237f104d1a3cd20256d29010000001600143ca33c2e4446f4a305f23c80df8ad1afdcf652f900000000")
|
||||
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 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