Skip to content

Commit

Permalink
Add test vectors for key derivation
Browse files Browse the repository at this point in the history
  • Loading branch information
araspitzu committed Jun 6, 2019
1 parent 3b25ea5 commit b041d94
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 45 deletions.
31 changes: 9 additions & 22 deletions eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}


Expand All @@ -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)
}

}

/**
Expand All @@ -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))
Expand All @@ -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)

Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
}

}

0 comments on commit b041d94

Please sign in to comment.