1
0
mirror of https://github.com/ACINQ/eclair.git synced 2024-11-20 10:39:19 +01:00

Extend funding key path to 256 bits (#1154)

Our random funding key path is now 8 * 32 bits plus a 1' (funder) or 0' (fundee).
Channel key paths are computed from the sha256 of the funding public key (we take all 256 bits).
This commit is contained in:
Fabrice Drouin 2019-10-02 16:43:58 +02:00 committed by GitHub
parent 332216b6ec
commit 320af437d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 99 additions and 8 deletions

View File

@ -54,6 +54,15 @@ trait KeyManager {
localParams.fundingKeyPath
}
/**
*
* @param isFunder true if we're funding this channel
* @return a partial key path for a new funding public key. This key path will be extended:
* - with a specific "chain" prefix
* - with a specific "funding pubkey" suffix
*/
def newFundingKeyPath(isFunder: Boolean) : DeterministicWallet.KeyPath
/**
*
* @param tx input transaction
@ -112,9 +121,10 @@ object KeyManager {
* @return a BIP32 path
*/
def channelKeyPath(fundingPubKey: PublicKey) : DeterministicWallet.KeyPath = {
val buffer = fundingPubKey.hash160.take(16)
val buffer = Crypto.sha256(fundingPubKey.value)
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 next() = Protocol.uint32(bis, ByteOrder.BIG_ENDIAN)
DeterministicWallet.KeyPath(Seq(next(), next(), next(), next(), next(), next(), next(), next()))
}
def channelKeyPath(fundingPubKey: DeterministicWallet.ExtendedPublicKey) : DeterministicWallet.KeyPath = channelKeyPath(fundingPubKey.publicKey)

View File

@ -17,13 +17,13 @@
package fr.acinq.eclair.crypto
import com.google.common.cache.{CacheBuilder, CacheLoader, LoadingCache}
import fr.acinq.bitcoin.Crypto.{PublicKey, PrivateKey}
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.DeterministicWallet.{derivePrivateKey, _}
import fr.acinq.bitcoin.{Block, ByteVector32, ByteVector64, Crypto, DeterministicWallet}
import fr.acinq.eclair.ShortChannelId
import fr.acinq.eclair.router.Announcements
import fr.acinq.eclair.transactions.Transactions
import fr.acinq.eclair.transactions.Transactions.TransactionWithInputInfo
import fr.acinq.eclair.{ShortChannelId, secureRandom}
import scodec.bits.ByteVector
object LocalKeyManager {
@ -80,6 +80,12 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana
private def shaSeed(channelKeyPath: DeterministicWallet.KeyPath) = Crypto.sha256(privateKeys.get(internalKeyPath(channelKeyPath, hardened(5))).privateKey.value :+ 1.toByte)
override def newFundingKeyPath(isFunder: Boolean): KeyPath = {
val last = DeterministicWallet.hardened(if (isFunder) 1 else 0)
def next() = secureRandom.nextInt() & 0xFFFFFFFFL
DeterministicWallet.KeyPath(Seq(next(), next(), next(), next(), next(), next(), next(), next(), last))
}
override def fundingPublicKey(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(0)))
override def revocationPoint(channelKeyPath: DeterministicWallet.KeyPath) = publicKeys.get(internalKeyPath(channelKeyPath, hardened(1)))

View File

@ -29,7 +29,7 @@ import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.TransportHandler
import fr.acinq.eclair.router._
import fr.acinq.eclair.wire._
import fr.acinq.eclair.{secureRandom, wire, _}
import fr.acinq.eclair.{wire, _}
import kamon.Kamon
import scodec.Attempt
import scodec.bits.ByteVector
@ -663,8 +663,7 @@ object Peer {
def makeChannelParams(nodeParams: NodeParams, defaultFinalScriptPubKey: ByteVector, isFunder: Boolean, fundingAmount: Satoshi): LocalParams = {
// 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))
val fundingKeyPath = nodeParams.keyManager.newFundingKeyPath(isFunder)
makeChannelParams(nodeParams, defaultFinalScriptPubKey, isFunder, fundingAmount, fundingKeyPath)
}

View File

@ -19,6 +19,8 @@ package fr.acinq.eclair.crypto
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.DeterministicWallet.KeyPath
import fr.acinq.bitcoin.{Block, ByteVector32, DeterministicWallet}
import fr.acinq.eclair.TestConstants
import fr.acinq.eclair.channel.ChannelVersion
import org.scalatest.FunSuite
import scodec.bits._
@ -63,6 +65,80 @@ class LocalKeyManagerSpec extends FunSuite {
// will break existing channels !
val pub = PrivateKey(ByteVector32.fromValidHex("01" * 32)).publicKey
val keyPath = KeyManager.channelKeyPath(pub)
assert(keyPath.toString() == "m/2041577608/1982247572/689197082'/1288840885")
assert(keyPath.toString() == "m/1909530642'/1080788911/847211985'/1791010671/1303008749'/34154019'/723973395/767609665")
}
def makefundingKeyPath(entropy: ByteVector, isFunder: Boolean) = {
val items = for(i <- 0 to 7) yield entropy.drop(i * 4).take(4).toInt(signed = false) & 0xFFFFFFFFL
val last = DeterministicWallet.hardened(if (isFunder) 1L else 0L)
KeyPath(items :+ last)
}
test("test vectors (testnet, funder)") {
val seed = ByteVector.fromValidHex("17b086b228025fa8f4416324b6ba2ec36e68570ae2fc3d392520969f2a9d0c1501")
val keyManager = new LocalKeyManager(seed, Block.TestnetGenesisBlock.hash)
val fundingKeyPath = makefundingKeyPath(hex"be4fa97c62b9f88437a3be577b31eb48f2165c7bc252194a15ff92d995778cfb", isFunder = true)
val fundingPub = keyManager.fundingPublicKey(fundingKeyPath)
val localParams = TestConstants.Alice.channelParams.copy(fundingKeyPath = fundingKeyPath)
val channelKeyPath = keyManager.channelKeyPath(localParams, ChannelVersion.STANDARD)
assert(fundingPub.publicKey == PrivateKey(hex"216414970b4216b197a1040367419ad6922f80e8b73ced083e9afe5e6ddd8e4c").publicKey)
assert(keyManager.revocationPoint(channelKeyPath).publicKey == PrivateKey(hex"a4e7ab3c54752a3487b3c474467843843f28d3bb9113e65e92056ad45d1e318e").publicKey)
assert(keyManager.paymentPoint(channelKeyPath).publicKey == PrivateKey(hex"de24c43d24b8d6bc66b020ac81164206bb577c7924511d4e99431c0d60505012").publicKey)
assert(keyManager.delayedPaymentPoint(channelKeyPath).publicKey == PrivateKey(hex"8aa7b8b14a7035540c331c030be0dd73e8806fb0c97a2519d63775c2f579a950").publicKey)
assert(keyManager.htlcPoint(channelKeyPath).publicKey == PrivateKey(hex"94eca6eade204d6e753344c347b46bb09067c92b2fe371cf4f8362c1594c8c59").publicKey)
assert(keyManager.commitmentSecret(channelKeyPath, 0).value == ShaChain.shaChainFromSeed(ByteVector32.fromValidHex("64e9d1e9840add3bb02c1525995edd28feea67f1df7a9ee075179e8541adc7a2"), 0xFFFFFFFFFFFFL))
}
test("test vectors (testnet, fundee)") {
val seed = ByteVector.fromValidHex("aeb3e9b5642cd4523e9e09164047f60adb413633549c3c6189192921311894d501")
val keyManager = new LocalKeyManager(seed, Block.TestnetGenesisBlock.hash)
val fundingKeyPath = makefundingKeyPath(hex"06535806c1aa73971ec4877a5e2e684fa636136c073810f190b63eefc58ca488", isFunder = false)
val fundingPub = keyManager.fundingPublicKey(fundingKeyPath)
val localParams = TestConstants.Alice.channelParams.copy(fundingKeyPath = fundingKeyPath)
val channelKeyPath = keyManager.channelKeyPath(localParams, ChannelVersion.STANDARD)
assert(fundingPub.publicKey == PrivateKey(hex"7bb8019c99fcba1c6bd0cc7f3c635c14c658d26751232d6a6350d8b6127d53c3").publicKey)
assert(keyManager.revocationPoint(channelKeyPath).publicKey == PrivateKey(hex"26510db99546c9b08418fe9df2da710a92afa6cc4e5681141610dfb8019052e6").publicKey)
assert(keyManager.paymentPoint(channelKeyPath).publicKey == PrivateKey(hex"0766c93fd06f69287fcc7b343916e678b83942345d4080e83f4c8a061b1a9f4b").publicKey)
assert(keyManager.delayedPaymentPoint(channelKeyPath).publicKey == PrivateKey(hex"094aa052a9647228fd80e42461cae26c04f6cdd1665b816d4660df686915319a").publicKey)
assert(keyManager.htlcPoint(channelKeyPath).publicKey == PrivateKey(hex"8ec62bd03b241a2e522477ae1a9861a668429ab3e443abd2aa0f2f10e2dc2206").publicKey)
assert(keyManager.commitmentSecret(channelKeyPath, 0).value == ShaChain.shaChainFromSeed(ByteVector32.fromValidHex("c49e98202b0fee19f28fd3af60691aaacdd2c09e20896f5fa3ad1b9b70e4879f"), 0xFFFFFFFFFFFFL))
}
test("test vectors (mainnet, funder)") {
val seed = ByteVector.fromValidHex("d8d5431487c2b19ee6486aad6c3bdfb99d10b727bade7fa848e2ab7901c15bff01")
val keyManager = new LocalKeyManager(seed, Block.LivenetGenesisBlock.hash)
val fundingKeyPath = makefundingKeyPath(hex"ec1c41cd6be2b6e4ef46c1107f6c51fbb2066d7e1f7720bde4715af233ae1322", isFunder = true)
val fundingPub = keyManager.fundingPublicKey(fundingKeyPath)
val localParams = TestConstants.Alice.channelParams.copy(fundingKeyPath = fundingKeyPath)
val channelKeyPath = keyManager.channelKeyPath(localParams, ChannelVersion.STANDARD)
assert(fundingPub.publicKey == PrivateKey(hex"b97c04796850e9d74a06c9d7230d85e2ecca3598b162ddf902895ece820c8f09").publicKey)
assert(keyManager.revocationPoint(channelKeyPath).publicKey == PrivateKey(hex"ee13db7f2d7e672f21395111ee169af8462c6e8d1a6a78d808f7447b27155ffb").publicKey)
assert(keyManager.paymentPoint(channelKeyPath).publicKey == PrivateKey(hex"7fc18e4c925bf3c5a83411eac7f234f0c5eaef9a8022b22ec6e3272ae329e17e").publicKey)
assert(keyManager.delayedPaymentPoint(channelKeyPath).publicKey == PrivateKey(hex"c0d9a3e3601d79b11b948db9d672fcddafcb9a3c0873c6a738bb09087ea2bfc6").publicKey)
assert(keyManager.htlcPoint(channelKeyPath).publicKey == PrivateKey(hex"bd3ba7068d131a9ab47f33202d532c5824cc5fc35a9adada3644ac2994372228").publicKey)
assert(keyManager.commitmentSecret(channelKeyPath, 0).value == ShaChain.shaChainFromSeed(ByteVector32.fromValidHex("7799de34239f97837a12191f5b60e766e32e9704bb84b0f12b539e9bf6a0dc2a"), 0xFFFFFFFFFFFFL))
}
test("test vectors (mainnet, fundee)") {
val seed = ByteVector.fromValidHex("4b809dd593b36131c454d60c2f7bdfd49d12ec455e5b657c47a9ca0f5dfc5eef01")
val keyManager = new LocalKeyManager(seed, Block.LivenetGenesisBlock.hash)
val fundingKeyPath = makefundingKeyPath(hex"2b4f045be5303d53f9d3a84a1e70c12251168dc29f300cf9cece0ec85cd8182b", isFunder = false)
val fundingPub = keyManager.fundingPublicKey(fundingKeyPath)
val localParams = TestConstants.Alice.channelParams.copy(fundingKeyPath = fundingKeyPath)
val channelKeyPath = keyManager.channelKeyPath(localParams, ChannelVersion.STANDARD)
assert(fundingPub.publicKey == PrivateKey(hex"46a4e818615a48a99ce9f6bd73eea07d5822dcfcdff18081ea781d4e5e6c036c").publicKey)
assert(keyManager.revocationPoint(channelKeyPath).publicKey == PrivateKey(hex"c2cd9e2f9f8203f16b1751bd252285bb2e7fc4688857d620467b99645ebdfbe6").publicKey)
assert(keyManager.paymentPoint(channelKeyPath).publicKey == PrivateKey(hex"1e4d3527788b39dc8ebc0ae6368a67e92eff55a43bea8e93054338ca850fa340").publicKey)
assert(keyManager.delayedPaymentPoint(channelKeyPath).publicKey == PrivateKey(hex"6bc30b0852fbc653451662a1ff6ad530f311d58b5e5661b541eb57dba8206937").publicKey)
assert(keyManager.htlcPoint(channelKeyPath).publicKey == PrivateKey(hex"b1be27b5232e3bc5d6a261949b4ee68d96fa61f481998d36342e2ad99444cf8a").publicKey)
assert(keyManager.commitmentSecret(channelKeyPath, 0).value == ShaChain.shaChainFromSeed(ByteVector32.fromValidHex("eeb3bad6808e8bb5f1774581ccf64aa265fef38eca80a1463d6310bb801b3ba7"), 0xFFFFFFFFFFFFL))
}
}