diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index be4776b984..ab84380cba 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -25,7 +25,7 @@ import fr.acinq.bitcoin._ import fr.acinq.eclair._ import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.channel.Helpers.{Closing, Funding} -import fr.acinq.eclair.crypto.{KeyManager, ShaChain, Sphinx} +import fr.acinq.eclair.crypto.{KeyManager, LocalKeyManager, ShaChain, Sphinx} import fr.acinq.eclair.io.Peer import fr.acinq.eclair.payment._ import fr.acinq.eclair.router.Announcements @@ -92,37 +92,24 @@ object Channel { case class RemoteError(e: Error) extends ChannelError // @formatter:on - def fourByteGroupsFromSha(input: ByteVector): List[Long] = { - // split the SHA into 8 groups of 4 bytes and convert to uint32 - Crypto.sha256(input).toArray.grouped(4).map(ByteVector(_).toLong(signed = false)).toList - } - - def makeFunderChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, fundingInput: TxIn): LocalParams = { - require(isFunder, s"Wrong params for isFunder=$isFunder") - - val List(h0, h1, h2, h3, h4, h5, h6, h7) = fourByteGroupsFromSha(fundingInput.outPoint.hash) - val channelKeyPath = KeyPath(Seq(47, 2, h0, h1, h2, h3, h4, h5, h6, h7, 0)) - - makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder, fundingSatoshis, Left(channelKeyPath)) + def makeFunderChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, fundingSatoshis: Long, fundingInput: TxIn): LocalParams = { + val channelKeyPath = LocalKeyManager.makeChannelKeyPathFunder(fundingInput.outPoint.hash) + makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder = true, fundingSatoshis, Left(channelKeyPath)) } def makeFundeeChannelParams(nodeParams: NodeParams, open: OpenChannel, defaultFinalScriptPubKey: ByteVector, fundingSatoshis: Long): LocalParams = { - val List(h0, h1, h2, h3, h4, h5, h6, h7) = fourByteGroupsFromSha(ByteVector.view(s"${Globals.blockCount} || 0".getBytes)) - val publicKeyPath = KeyPath(Seq(47, 2, h0, h1, h2, h3, h4, h5, h6, h7, 2)) - + val publicKeyPath = LocalKeyManager.makeChannelKeyPathFundeePubkey(Globals.blockCount.get, 0) val localFundingPubkey = nodeParams.keyManager.fundingPublicKey(publicKeyPath).publicKey - val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey, open.fundingPubkey))) - val List(s0, s1, s2, s3, s4, s5, s6, s7) = fourByteGroupsFromSha(fundingPubkeyScript) - val pointsKeyPath = KeyPath(Seq(47, 2, s0, s1, s2, s3, s4, s5, s6, s7, 1)) + val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey, open.fundingPubkey))) + val channelKeyPath = LocalKeyManager.makeChannelKeyPathFundee(fundingPubkeyScript) - val channelKeyPaths = KeyPathFundee(publicKeyPath, pointsKeyPath) + val channelKeyPaths = KeyPathFundee(publicKeyPath, channelKeyPath) makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder = false, fundingSatoshis, Right(channelKeyPaths)) } - def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingSatoshis: Long, channelKeyPath: Either[DeterministicWallet.KeyPath, KeyPathFundee]): LocalParams = { LocalParams( nodeParams.nodeId, @@ -370,7 +357,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId case Event(funding:MakeFundingTxResponse, DATA_WAIT_FOR_FUNDING_INTERNAL_CREATED(temporaryChannelId, fundingSatoshis, pushMsat, initialFeeratePerKw, fundingTxFeeratePerKw, remote, remoteInit, channelFlags)) => val defaultFinalScriptPubKey = Helpers.getFinalScriptPubKey(wallet, nodeParams.chainHash) - val localParams = makeFunderChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder = true, fundingSatoshis, funding.fundingTx.txIn.head) + val localParams = makeFunderChannelParams(nodeParams, defaultFinalScriptPubKey, fundingSatoshis, funding.fundingTx.txIn.head) log.info(s"using localParams=$localParams") val open = OpenChannel(nodeParams.chainHash, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala index f404c125b7..ea343fb901 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/LocalKeyManager.scala @@ -25,12 +25,12 @@ import fr.acinq.eclair.channel.LocalParams import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.transactions.Transactions.TransactionWithInputInfo -import scodec.bits.ByteVector +import scodec.bits.{ByteOrdering, ByteVector} object LocalKeyManager { def channelKeyBasePath(chainHash: ByteVector32) = chainHash match { - case Block.RegtestGenesisBlock.hash | Block.TestnetGenesisBlock.hash => DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(1) :: Nil - case Block.LivenetGenesisBlock.hash => DeterministicWallet.hardened(47) :: DeterministicWallet.hardened(1) :: Nil + case Block.RegtestGenesisBlock.hash | Block.TestnetGenesisBlock.hash => DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(2) :: Nil + case Block.LivenetGenesisBlock.hash => DeterministicWallet.hardened(47) :: DeterministicWallet.hardened(2) :: Nil } @@ -41,6 +41,21 @@ object LocalKeyManager { case Block.RegtestGenesisBlock.hash | Block.TestnetGenesisBlock.hash => DeterministicWallet.hardened(46) :: DeterministicWallet.hardened(0) :: Nil case Block.LivenetGenesisBlock.hash => DeterministicWallet.hardened(47) :: DeterministicWallet.hardened(0) :: Nil } + + // split the SHA into 8 groups of 4 bytes and convert to uint32 + def fourByteGroupsFromSha(input: ByteVector): List[Long] = Crypto.sha256(input).toArray.grouped(4).map(ByteVector(_).toLong(signed = false)).toList + + def makeChannelKeyPathFunder(entropy: ByteVector) = KeyPath(fourByteGroupsFromSha(entropy) :+ 0L) + def makeChannelKeyPathFundee(entropy: ByteVector) = KeyPath(fourByteGroupsFromSha(entropy) :+ 1L) + def makeChannelKeyPathFundeePubkey(entropy: ByteVector) = KeyPath(fourByteGroupsFromSha(entropy) :+ 2L) + + def makeChannelKeyPathFundeePubkey(blockHeight: Long, counter: Int): KeyPath = { + val blockHeightBytes = ByteVector.fromLong(blockHeight, size = 4, ordering = ByteOrdering.LittleEndian) + val counterBytes = ByteVector.fromInt(counter, size = 4, ordering = ByteOrdering.LittleEndian) + + makeChannelKeyPathFundeePubkey(blockHeightBytes ++ counterBytes) + } + } /** @@ -51,12 +66,6 @@ object LocalKeyManager { */ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyManager { - private def funderDerivationPath(entropy: ByteVector32) = Seq(47, 2, entropy.toLong(), 0) - - private def fundeeDerivationPath(entropy: ByteVector32) = Seq(47, 2, entropy.toLong(), 1) - - private def fundeePubkeyDerivationPath(entropy: ByteVector32) = Seq(47, 2, entropy.toLong(), 2) - private val master = DeterministicWallet.generate(seed) override val nodeKey = DeterministicWallet.derivePrivateKey(master, LocalKeyManager.nodeKeyBasePath(chainHash)) @@ -76,27 +85,27 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana private def internalKeyPath(channelKeyPath: DeterministicWallet.KeyPath, index: Long): List[Long] = (LocalKeyManager.channelKeyBasePath(chainHash) ++ channelKeyPath.path) :+ index - private def fundingPrivateKey(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, hardened(0))) + private def fundingPrivateKey(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, 0)) - private def revocationSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, hardened(1))) + private def revocationSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, 1)) - private def paymentSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, hardened(2))) + private def paymentSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, 2)) - private def delayedPaymentSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, hardened(3))) + private def delayedPaymentSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, 3)) - private def htlcSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, hardened(4))) + private def htlcSecret(channelKeyPath: DeterministicWallet.KeyPath) = privateKeys.get(internalKeyPath(channelKeyPath, 4)) private def shaSeed(channelKeyPath: DeterministicWallet.KeyPath) = Crypto.sha256(privateKeys.get(internalKeyPath(channelKeyPath, hardened(5))).privateKey.toBin) - override def fundingPublicKey(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(0))) + override def fundingPublicKey(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, 0)) - override def revocationPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(1))) + override def revocationPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, 1)) - override def paymentPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(2))) + override def paymentPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, 2)) - override def delayedPaymentPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(3))) + override def delayedPaymentPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, 3)) - override def htlcPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(4))) + override def htlcPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, 4)) override def commitmentSecret(channelKeyPath: DeterministicWallet.KeyPath, index: Long) = Generators.perCommitSecret(shaSeed(channelKeyPath), index) @@ -216,7 +225,6 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana override def signChannelAnnouncement(channelKeyPath: DeterministicWallet.KeyPath, chainHash: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, remoteFundingKey: PublicKey, features: ByteVector): (ByteVector, ByteVector) = { - val witness = if (Announcements.isNode1(nodeId, remoteNodeId)) { Announcements.channelAnnouncementWitnessEncode(chainHash, shortChannelId, nodeId, remoteNodeId, fundingPublicKey(channelKeyPath).publicKey, remoteFundingKey, features) } else { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala index 2d59cc6cce..0f3548a977 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala @@ -16,13 +16,13 @@ package fr.acinq.eclair.crypto -import fr.acinq.bitcoin.Block +import fr.acinq.bitcoin.{Block, Crypto, DeterministicWallet, MnemonicCode, Script} import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.DeterministicWallet.KeyPath +import fr.acinq.eclair.transactions.Scripts import org.scalatest.FunSuite import scodec.bits._ - class LocalKeyManagerSpec extends FunSuite { test("generate the same node id from the same seed") { // if this test breaks it means that we will generate a different node id from @@ -40,4 +40,83 @@ class LocalKeyManagerSpec extends FunSuite { assert(keyManager1.fundingPublicKey(keyPath) != keyManager2.fundingPublicKey(keyPath)) assert(keyManager1.commitmentPoint(keyPath, 1) != keyManager2.commitmentPoint(keyPath, 1)) } -} + + /** + * TESTNET funder funding public key from extended public key: 03c69365c8ae813a45b9fead1056331c41f38ab3ab7d5d383d62c4c70cfd91f9ea + * TESTNET funder payment point from extended public key: 02efa31ae79a7c29faf23a21f00d5ca62ce14539c2f431db5dade3e919d2ff9050 + * MAINNET funder funding public key from extended public key: 02dd88103f4690fa5484f0c6a13f917fb00f03b5f2be1375cd08c64b11b19d730e + * MAINNET funder payment point from extended public key: 030a7898d98245666be451b55a63a5f1acd71ec1efec85fb8364f3fbe46327441b + * TESTNET fundee funding public key from extended public key #34273 #0: 029278489277ce1abf6a05463ec913f5fe32ee194588a13c5b7899215d4ee477da + * TESTNET fundee htlc public point from extended public key #34273 #0: 02563a8f3480b06f1e653c7a1a9a006236dfe503ec616ccef8656ab8e2cbe938d5 + * TESTNET fundee htlc public point from extended public key #34273 #4: 028ba2414d6fcc9ed4c26175dea6c63caaa595101c7a66810ea0653547ba8e0a07 + * MAINNET fundee funding public key from extended public key #34273 #0: 03fe59b3ac6c2f08172d634f0346194fbf3e4e90fe01422a030546a913f0a4b21e + * MAINNET fundee htlc public point from extended public key #34273 #0: 03a1c04e6281f9b1dcebb261ca6bbc6a260521ff011cbb617804ceac6fc0dd38bc + * MAINNET fundee htlc public point from extended public key #34273 #4: 0359fd05aed2daa09798b2d5e1a705d76664fcce740db2f88cdb6259d2bf055fb0 + */ + + test("test vectors derivation paths (funder scenario - TESTNET)") { + + val inputOutpoint = hex"1d12dcab62f3d509db16b8dcb69782ea6358a7060b579675561c4fc2e3294f41" + val seed = hex"0101010102020202AABBCCDD030303030404040405050505060606060707070701" + val keyManager = new LocalKeyManager(seed, Block.TestnetGenesisBlock.hash) + + val funderChannelKeyPath = LocalKeyManager.makeChannelKeyPathFunder(inputOutpoint) + + // TESTNET funder funding public key from extended public key + assert(keyManager.fundingPublicKey(funderChannelKeyPath).publicKey.toBin === hex"03c69365c8ae813a45b9fead1056331c41f38ab3ab7d5d383d62c4c70cfd91f9ea") + // TESTNET funder payment point from extended public key + assert(keyManager.paymentPoint(funderChannelKeyPath).publicKey.toBin === hex"02efa31ae79a7c29faf23a21f00d5ca62ce14539c2f431db5dade3e919d2ff9050") + } + + test("test vectors derivation paths (funder scenario - MAINNET)") { + + val inputOutpoint = hex"1d12dcab62f3d509db16b8dcb69782ea6358a7060b579675561c4fc2e3294f41" + val seed = hex"0101010102020202AABBCCDD030303030404040405050505060606060707070701" + val keyManager = new LocalKeyManager(seed, Block.LivenetGenesisBlock.hash) + + val funderChannelKeyPath = LocalKeyManager.makeChannelKeyPathFunder(inputOutpoint) + + // MAINNET funder funding public key from extended public key + assert(keyManager.fundingPublicKey(funderChannelKeyPath).publicKey.toBin === hex"02dd88103f4690fa5484f0c6a13f917fb00f03b5f2be1375cd08c64b11b19d730e") + // MAINNET funder payment point from extended public key + assert(keyManager.paymentPoint(funderChannelKeyPath).publicKey.toBin === hex"030a7898d98245666be451b55a63a5f1acd71ec1efec85fb8364f3fbe46327441b") + } + + test("test vectors derivation paths (fundee TESTNET)") { + + val remoteNodePubkey = PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f") + val seed = hex"0101010102020202AABBCCDD030303030404040405050505060606060707070701" + val keyManager = new LocalKeyManager(seed, Block.TestnetGenesisBlock.hash) + + val fundeePubkeyKeyPath = LocalKeyManager.makeChannelKeyPathFundeePubkey(34273, 0) + // TESTNET fundee funding public key from extended public key + assert(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey.toBin === hex"029278489277ce1abf6a05463ec913f5fe32ee194588a13c5b7899215d4ee477da") + + val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey, remoteNodePubkey))) + val fundeeChannelKeyPath = LocalKeyManager.makeChannelKeyPathFundee(fundingPubkeyScript) + + // TESTNET fundee htlc public point from extended public key + assert(keyManager.htlcPoint(fundeeChannelKeyPath).publicKey.toBin === hex"02563a8f3480b06f1e653c7a1a9a006236dfe503ec616ccef8656ab8e2cbe938d5") + // TESTNET fundee htlc public point from extended public key + assert(keyManager.paymentPoint(fundeeChannelKeyPath).publicKey.toBin === hex"0321047df59f000ba15f674c2eb6180c00edb55e5eae6e8ea22e82554c4213cfa4") + } + + test("test vectors derivation paths (fundee MAINNET)") { + + val remoteNodePubkey = PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f") + val seed = hex"0101010102020202AABBCCDD030303030404040405050505060606060707070701" + val keyManager = new LocalKeyManager(seed, Block.LivenetGenesisBlock.hash) + + val fundeePubkeyKeyPath = LocalKeyManager.makeChannelKeyPathFundeePubkey(34273, 0) + // MAINNET fundee funding public key from extended public key + assert(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey.toBin === hex"03fe59b3ac6c2f08172d634f0346194fbf3e4e90fe01422a030546a913f0a4b21e") + + val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(keyManager.fundingPublicKey(fundeePubkeyKeyPath).publicKey, remoteNodePubkey))) + val fundeeChannelKeyPath = LocalKeyManager.makeChannelKeyPathFundee(fundingPubkeyScript) + + println(keyManager.htlcPoint(fundeeChannelKeyPath).path.toString()) + // MAINNET fundee htlc public point from extended public key + assert(keyManager.htlcPoint(fundeeChannelKeyPath).publicKey.toBin === hex"03a1c04e6281f9b1dcebb261ca6bbc6a260521ff011cbb617804ceac6fc0dd38bc") + } + +} \ No newline at end of file