Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static key refactoring #1463

Merged
merged 3 commits into from
Jun 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
maxAcceptedHtlcs = localParams.maxAcceptedHtlcs,
fundingPubkey = fundingPubKey,
revocationBasepoint = keyManager.revocationPoint(channelKeyPath).publicKey,
paymentBasepoint = channelVersion match {
case v if v.isSet(USE_STATIC_REMOTEKEY_BIT) => localParams.localPaymentBasepoint.get
case _ => keyManager.paymentPoint(channelKeyPath).publicKey
},
paymentBasepoint = localParams.staticPaymentBasepoint.getOrElse(keyManager.paymentPoint(channelKeyPath).publicKey),
delayedPaymentBasepoint = keyManager.delayedPaymentPoint(channelKeyPath).publicKey,
htlcBasepoint = keyManager.htlcPoint(channelKeyPath).publicKey,
firstPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 0),
Expand Down Expand Up @@ -319,10 +316,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
maxAcceptedHtlcs = localParams.maxAcceptedHtlcs,
fundingPubkey = fundingPubkey,
revocationBasepoint = keyManager.revocationPoint(channelKeyPath).publicKey,
paymentBasepoint = channelVersion match {
case v if v.isSet(USE_STATIC_REMOTEKEY_BIT) => localParams.localPaymentBasepoint.get
case _ => keyManager.paymentPoint(channelKeyPath).publicKey
},
paymentBasepoint = localParams.staticPaymentBasepoint.getOrElse(keyManager.paymentPoint(channelKeyPath).publicKey),
delayedPaymentBasepoint = keyManager.delayedPaymentPoint(channelKeyPath).publicKey,
htlcBasepoint = keyManager.htlcPoint(channelKeyPath).publicKey,
firstPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 0),
Expand Down Expand Up @@ -2105,7 +2099,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
def handleRemoteSpentFuture(commitTx: Transaction, d: DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT) = {
log.warning(s"they published their future commit (because we asked them to) in txid=${commitTx.txid}")
d.commitments.channelVersion match {
case v if v.isSet(USE_STATIC_REMOTEKEY_BIT) =>
case v if v.hasStaticRemotekey =>
val remoteCommitPublished = RemoteCommitPublished(commitTx, None, List.empty, List.empty, Map.empty)
val nextData = DATA_CLOSING(d.commitments, fundingTx = None, waitingSince = now, Nil, futureRemoteCommitPublished = Some(remoteCommitPublished))
goto(CLOSING) using nextData storing() // we don't need to claim our main output in the remote commit because it already spends to our wallet address
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ final case class LocalParams(nodeId: PublicKey,
maxAcceptedHtlcs: Int,
isFunder: Boolean,
defaultFinalScriptPubKey: ByteVector,
localPaymentBasepoint: Option[PublicKey],
staticPaymentBasepoint: Option[PublicKey],
features: Features)

final case class RemoteParams(nodeId: PublicKey,
Expand All @@ -252,27 +252,33 @@ object ChannelFlags {
}

case class ChannelVersion(bits: BitVector) {
import ChannelVersion._

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)

private def isSet(bit: Int) = bits.reverse.get(bit)

// formatter:off
def hasPubkeyKeyPath: Boolean = isSet(USE_PUBKEY_KEYPATH_BIT)
def hasStaticRemotekey: Boolean = isSet(USE_STATIC_REMOTEKEY_BIT)
// formatter:on
}

object ChannelVersion {
import scodec.bits._
val LENGTH_BITS = 4 * 8
val ZEROES = ChannelVersion(bin"00000000000000000000000000000000")
val USE_PUBKEY_KEYPATH_BIT = 0 // bit numbers start at 0
val USE_STATIC_REMOTEKEY_BIT = 1
val LENGTH_BITS: Int = 4 * 8

def fromBit(bit: Int) = ChannelVersion(BitVector.low(LENGTH_BITS).set(bit).reverse)
private val USE_PUBKEY_KEYPATH_BIT = 0 // bit numbers start at 0
private val USE_STATIC_REMOTEKEY_BIT = 1

val USE_PUBKEY_KEYPATH = fromBit(USE_PUBKEY_KEYPATH_BIT)
val USE_STATIC_REMOTEKEY = fromBit(USE_STATIC_REMOTEKEY_BIT)
private def setBit(bit: Int) = ChannelVersion(BitVector.low(LENGTH_BITS).set(bit).reverse)

val STANDARD = ZEROES | USE_PUBKEY_KEYPATH
val STATIC_REMOTEKEY = STANDARD | USE_STATIC_REMOTEKEY // USE_PUBKEY_KEYPATH + USE_STATIC_REMOTEKEY
val ZEROES = ChannelVersion(bin"00000000000000000000000000000000")
val STANDARD = ZEROES | setBit(USE_PUBKEY_KEYPATH_BIT)
val STATIC_REMOTEKEY = STANDARD | setBit(USE_STATIC_REMOTEKEY_BIT) // PUBKEY_KEYPATH + STATIC_REMOTEKEY
}
// @formatter:on
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ case class Commitments(channelVersion: ChannelVersion,
commitInput: InputInfo,
remotePerCommitmentSecrets: ShaChain, channelId: ByteVector32) {

require(!channelVersion.isSet(USE_STATIC_REMOTEKEY_BIT) || (channelVersion.isSet(USE_STATIC_REMOTEKEY_BIT) && localParams.localPaymentBasepoint.isDefined), s"localParams.localPaymentBasepoint must be defined for commitments with version=$channelVersion")
require(!channelVersion.hasStaticRemotekey || (channelVersion.hasStaticRemotekey && localParams.staticPaymentBasepoint.isDefined), s"localParams.localPaymentBasepoint must be defined for commitments with version=$channelVersion")

def hasNoPendingHtlcs: Boolean = localCommit.spec.htlcs.isEmpty && remoteCommit.spec.htlcs.isEmpty && remoteNextCommitInfo.isRight

Expand Down Expand Up @@ -604,16 +604,10 @@ object Commitments {
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 = channelVersion match {
case v if v.isSet(USE_STATIC_REMOTEKEY_BIT) => remoteParams.paymentBasepoint
case _ => Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint)
}
val remotePaymentPubkey = if (channelVersion.hasStaticRemotekey) remoteParams.paymentBasepoint else Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint)
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint)
val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint)
val localPaymentBasepoint = channelVersion match {
case v if v.isSet(USE_STATIC_REMOTEKEY_BIT) => localParams.localPaymentBasepoint.get
case _ => keyManager.paymentPoint(channelKeyPath).publicKey
}
val localPaymentBasepoint = localParams.staticPaymentBasepoint.getOrElse(keyManager.paymentPoint(channelKeyPath).publicKey)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks better, but now it does not check that localParams.staticPaymentBasepoint is defined iff channelVersion activates option_static_remote_key

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It kind of does, here:

require(!channelVersion.isSet(USE_STATIC_REMOTEKEY_BIT) || (channelVersion.isSet(USE_STATIC_REMOTEKEY_BIT) && localParams.localPaymentBasepoint.isDefined), s"localParams.localPaymentBasepoint must be defined for commitments with version=$channelVersion")

I think that's consistent with how remote works: we just use the value they give us and assume it follows the static feature bit.

val outputs = makeCommitTxOutputs(localParams.isFunder, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, spec)
val commitTx = Transactions.makeCommitTx(commitmentInput, commitTxNumber, localPaymentBasepoint, remoteParams.paymentBasepoint, localParams.isFunder, outputs)
val (htlcTimeoutTxs, htlcSuccessTxs) = Transactions.makeHtlcTxs(commitTx.tx, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, spec.feeratePerKw, outputs)
Expand All @@ -627,14 +621,8 @@ object Commitments {
remotePerCommitmentPoint: PublicKey,
spec: CommitmentSpec): (CommitTx, Seq[HtlcTimeoutTx], Seq[HtlcSuccessTx]) = {
val channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
val localPaymentBasepoint = channelVersion match {
case v if v.isSet(USE_STATIC_REMOTEKEY_BIT) => localParams.localPaymentBasepoint.get
case _ => keyManager.paymentPoint(channelKeyPath).publicKey
}
val localPaymentPubkey = channelVersion match {
case v if v.isSet(USE_STATIC_REMOTEKEY_BIT) => localPaymentBasepoint
case _ => Generators.derivePubKey(localPaymentBasepoint, remotePerCommitmentPoint)
}
val localPaymentBasepoint = localParams.staticPaymentBasepoint.getOrElse(keyManager.paymentPoint(channelKeyPath).publicKey)
val localPaymentPubkey = if (channelVersion.hasStaticRemotekey) localPaymentBasepoint else Generators.derivePubKey(localPaymentBasepoint, remotePerCommitmentPoint)
val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(channelKeyPath).publicKey, remotePerCommitmentPoint)
val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint)
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,7 @@ object Helpers {
}.toSeq.flatten

channelVersion match {
case v if v.isSet(USE_STATIC_REMOTEKEY_BIT) =>
case v if v.hasStaticRemotekey =>
RemoteCommitPublished(
commitTx = tx,
claimMainOutputTx = None,
Expand Down Expand Up @@ -696,10 +696,7 @@ object Helpers {
require(tx.txIn.size == 1, "commitment tx should have 1 input")
val channelKeyPath = keyManager.channelKeyPath(localParams, channelVersion)
val obscuredTxNumber = Transactions.decodeTxNumber(tx.txIn.head.sequence, tx.lockTime)
val localPaymentPoint = channelVersion match {
case v if v.isSet(USE_STATIC_REMOTEKEY_BIT) => localParams.localPaymentBasepoint.get
case _ => keyManager.paymentPoint(channelKeyPath).publicKey
}
val localPaymentPoint = localParams.staticPaymentBasepoint.getOrElse(keyManager.paymentPoint(channelKeyPath).publicKey)
// this tx has been published by remote, so we need to invert local/remote params
val txnumber = Transactions.obscuredCommitTxNumber(obscuredTxNumber, !localParams.isFunder, remoteParams.paymentBasepoint, localPaymentPoint)
require(txnumber <= 0xffffffffffffL, "txnumber must be lesser than 48 bits long")
Expand All @@ -721,7 +718,7 @@ object Helpers {

// first we will claim our main output right away
val mainTx = channelVersion match {
case v if v.isSet(USE_STATIC_REMOTEKEY_BIT) =>
case v if v.hasStaticRemotekey =>
log.info(s"channel uses option_static_remotekey, not claiming our p2wpkh output")
None
case _ => generateTx("claim-p2wpkh-output") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ 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)) {
def channelKeyPath(localParams: LocalParams, channelVersion: ChannelVersion): DeterministicWallet.KeyPath = if (channelVersion.hasPubkeyKeyPath) {
// deterministic mode: use the funding pubkey to compute the channel key path
KeyManager.channelKeyPath(fundingPublicKey(localParams.fundingKeyPath))
} else {
Expand Down
6 changes: 3 additions & 3 deletions eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, watcher: ActorRe

def createNewChannel(nodeParams: NodeParams, funder: Boolean, fundingAmount: Satoshi, origin_opt: Option[ActorRef], channelVersion: ChannelVersion): (ActorRef, LocalParams) = {
val (finalScript, localPaymentBasepoint) = channelVersion match {
case v if v.isSet(ChannelVersion.USE_STATIC_REMOTEKEY_BIT) =>
case v if v.hasStaticRemotekey =>
val walletKey = Helpers.getWalletPaymentBasepoint(wallet)
(Script.write(Script.pay2wpkh(walletKey)), Some(walletKey))
case _ =>
Expand Down Expand Up @@ -404,7 +404,7 @@ object Peer {
makeChannelParams(nodeParams, defaultFinalScriptPubkey, localPaymentBasepoint, isFunder, fundingAmount, fundingKeyPath)
}

def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubkey: ByteVector, localPaymentBasepoint: Option[PublicKey], isFunder: Boolean, fundingAmount: Satoshi, fundingKeyPath: DeterministicWallet.KeyPath): LocalParams = {
def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubkey: ByteVector, staticPaymentBasepoint: Option[PublicKey], isFunder: Boolean, fundingAmount: Satoshi, fundingKeyPath: DeterministicWallet.KeyPath): LocalParams = {
LocalParams(
nodeParams.nodeId,
fundingKeyPath,
Expand All @@ -416,7 +416,7 @@ object Peer {
maxAcceptedHtlcs = nodeParams.maxAcceptedHtlcs,
isFunder = isFunder,
defaultFinalScriptPubKey = defaultFinalScriptPubkey,
localPaymentBasepoint = localPaymentBasepoint,
staticPaymentBasepoint = staticPaymentBasepoint,
features = nodeParams.features)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ object ChannelCodecs extends Logging {
("maxAcceptedHtlcs" | uint16) ::
("isFunder" | bool) ::
("defaultFinalScriptPubKey" | varsizebinarydata) ::
("localPaymentBasepoint" | optional(provide(channelVersion.isSet(ChannelVersion.USE_STATIC_REMOTEKEY_BIT)), publicKey)) ::
("localPaymentBasepoint" | optional(provide(channelVersion.hasStaticRemotekey), publicKey)) ::
("features" | combinedFeaturesCodec)).as[LocalParams]

val remoteParamsCodec: Codec[RemoteParams] = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import org.scalatest.funsuite.AnyFunSuite

class ChannelTypesSpec extends AnyFunSuite {
test("standard channel features include deterministic channel key path") {
assert(!ChannelVersion.ZEROES.isSet(ChannelVersion.USE_PUBKEY_KEYPATH_BIT))
assert(ChannelVersion.STANDARD.isSet(ChannelVersion.USE_PUBKEY_KEYPATH_BIT))
assert(ChannelVersion.STATIC_REMOTEKEY.isSet(ChannelVersion.USE_PUBKEY_KEYPATH_BIT))
assert(!ChannelVersion.ZEROES.hasPubkeyKeyPath)
assert(ChannelVersion.STANDARD.hasPubkeyKeyPath)
assert(ChannelVersion.STATIC_REMOTEKEY.hasStaticRemotekey)
assert(ChannelVersion.STATIC_REMOTEKEY.hasPubkeyKeyPath)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ trait StateTestsHelperMethods extends TestKitBase with FixtureTestSuite with Par
val channelFlags = if (tags.contains("channels_public")) ChannelFlags.AnnounceChannel else ChannelFlags.Empty
val pushMsat = if (tags.contains("no_push_msat")) 0.msat else TestConstants.pushMsat
val (aliceParams, bobParams) = if(tags.contains("static_remotekey")) {
(Alice.channelParams.copy(features = Features(Set(ActivatedFeature(StaticRemoteKey, Optional))), localPaymentBasepoint = Some(Helpers.getWalletPaymentBasepoint(wallet))),
Bob.channelParams.copy(features = Features(Set(ActivatedFeature(StaticRemoteKey, Optional))), localPaymentBasepoint = Some(Helpers.getWalletPaymentBasepoint(wallet))))
(Alice.channelParams.copy(features = Features(Set(ActivatedFeature(StaticRemoteKey, Optional))), staticPaymentBasepoint = Some(Helpers.getWalletPaymentBasepoint(wallet))),
Bob.channelParams.copy(features = Features(Set(ActivatedFeature(StaticRemoteKey, Optional))), staticPaymentBasepoint = Some(Helpers.getWalletPaymentBasepoint(wallet))))
} else {
(Alice.channelParams, Bob.channelParams)
}
Expand Down
6 changes: 3 additions & 3 deletions eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -297,9 +297,9 @@ class PeerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with StateTe
val info = probe.expectMsgType[RES_GETINFO]
assert(info.state == WAIT_FOR_ACCEPT_CHANNEL)
val inputInit = info.data.asInstanceOf[DATA_WAIT_FOR_ACCEPT_CHANNEL].initFunder
assert(inputInit.channelVersion.isSet(ChannelVersion.USE_STATIC_REMOTEKEY_BIT))
assert(inputInit.localParams.localPaymentBasepoint.isDefined)
assert(inputInit.localParams.defaultFinalScriptPubKey === Script.write(Script.pay2wpkh(inputInit.localParams.localPaymentBasepoint.get)))
assert(inputInit.channelVersion.hasStaticRemotekey)
assert(inputInit.localParams.staticPaymentBasepoint.isDefined)
assert(inputInit.localParams.defaultFinalScriptPubKey === Script.write(Script.pay2wpkh(inputInit.localParams.staticPaymentBasepoint.get)))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,7 @@ trait TestVectorsSpec extends AnyFunSuite with Logging {
val funding_pubkey = funding_privkey.publicKey
val per_commitment_point = PublicKey(hex"025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486")
val htlc_privkey = Generators.derivePrivKey(payment_basepoint_secret, per_commitment_point)
val payment_privkey = channelVersion.isSet(USE_STATIC_REMOTEKEY_BIT) match {
case true => payment_basepoint_secret
case false => htlc_privkey
}
val payment_privkey = if (channelVersion.hasStaticRemotekey) payment_basepoint_secret else htlc_privkey
val delayed_payment_privkey = Generators.derivePrivKey(delayed_payment_basepoint_secret, per_commitment_point)
val revocation_pubkey = PublicKey(hex"0212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b19")
val feerate_per_kw = 15000
Expand All @@ -101,10 +98,7 @@ trait TestVectorsSpec extends AnyFunSuite with Logging {
val funding_privkey = PrivateKey(hex"1552dfba4f6cf29a62a0af13c8d6981d36d0ef8d61ba10fb0fe90da7634d7e1301")
val funding_pubkey = funding_privkey.publicKey
val htlc_privkey = Generators.derivePrivKey(payment_basepoint_secret, Local.per_commitment_point)
val payment_privkey = channelVersion.isSet(USE_STATIC_REMOTEKEY_BIT) match {
case true => payment_basepoint_secret
case false => htlc_privkey
}
val payment_privkey = if (channelVersion.hasStaticRemotekey) payment_basepoint_secret else htlc_privkey
}

val coinbaseTx = Transaction.read("01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0100f2052a010000001976a9143ca33c2e4446f4a305f23c80df8ad1afdcf652f988ac00000000")
Expand Down Expand Up @@ -512,5 +506,5 @@ class DefaultCommitmentTestVectorSpec extends TestVectorsSpec {

class StaticRemoteKeyTestVectorSpec extends TestVectorsSpec {
override def filename: String = "/bolt3-tx-test-vectors-static-remotekey-format.txt"
override def channelVersion: ChannelVersion = ChannelVersion.USE_STATIC_REMOTEKEY
override def channelVersion: ChannelVersion = ChannelVersion.STATIC_REMOTEKEY
}
Loading