1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-03-13 11:35:47 +01:00

Derive per-user swap-in keys

We derive keys based on the server xpub and our node ID.
This commit is contained in:
t-bast 2023-06-15 12:12:45 +02:00
parent 98d588ab03
commit 72f0993a31
No known key found for this signature in database
GPG key ID: 34F377B0100ED6BB
2 changed files with 27 additions and 10 deletions

View file

@ -25,6 +25,7 @@ import fr.acinq.eclair.transactions.Transactions
import fr.acinq.eclair.transactions.Transactions.TransactionWithInputInfo import fr.acinq.eclair.transactions.Transactions.TransactionWithInputInfo
import fr.acinq.eclair.{Features, ShortChannelId, secureRandom} import fr.acinq.eclair.{Features, ShortChannelId, secureRandom}
import scodec.bits.ByteVector import scodec.bits.ByteVector
import scodec.codecs.uint16
object LocalKeyManager { object LocalKeyManager {
def channelKeyBasePath(chainHash: ByteVector32) = (chainHash: @unchecked) match { def channelKeyBasePath(chainHash: ByteVector32) = (chainHash: @unchecked) match {
@ -158,7 +159,8 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana
Announcements.signChannelAnnouncement(chainHash, shortChannelId, localNodeSecret, remoteNodeId, localFundingPrivKey, remoteFundingKey, features) Announcements.signChannelAnnouncement(chainHash, shortChannelId, localNodeSecret, remoteNodeId, localFundingPrivKey, remoteFundingKey, features)
} }
def multisigSwapInAddress(serverPublicKey: Crypto.PublicKey, refundDelay: Int): String = { def multisigSwapInAddress(localNodeId: PublicKey, serverExtendedPublicKey: String, refundDelay: Int): String = {
val serverPublicKey = deriveSwapInServerPublicKey(localNodeId, serverExtendedPublicKey)
val userKeyPath = chainHash match { val userKeyPath = chainHash match {
case Block.RegtestGenesisBlock.hash | Block.TestnetGenesisBlock.hash => case Block.RegtestGenesisBlock.hash | Block.TestnetGenesisBlock.hash =>
DeterministicWallet.hardened(51) :: DeterministicWallet.hardened(0) :: DeterministicWallet.hardened(0) :: Nil DeterministicWallet.hardened(51) :: DeterministicWallet.hardened(0) :: DeterministicWallet.hardened(0) :: Nil
@ -179,4 +181,11 @@ class LocalKeyManager(seed: ByteVector, chainHash: ByteVector32) extends KeyMana
val address = Bech32.encodeWitnessAddress(hrp, 0, witnessScript) val address = Bech32.encodeWitnessAddress(hrp, 0, witnessScript)
address address
} }
private def deriveSwapInServerPublicKey(localNodeId: PublicKey, serverExtendedPublicKey: String): PublicKey = {
val (_, xpub) = DeterministicWallet.ExtendedPublicKey.decode(serverExtendedPublicKey)
val h = Crypto.sha256(localNodeId.value)
val path = h.bits.grouped(16).toSeq.map(uint16.decode(_).require.value.toLong)
DeterministicWallet.derivePublicKey(xpub, path).publicKey
}
} }

View file

@ -77,7 +77,7 @@ class LocalKeyManagerSpec extends AnyFunSuite {
} }
def makefundingKeyPath(entropy: ByteVector, isFunder: Boolean) = { 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 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) val last = DeterministicWallet.hardened(if (isFunder) 1L else 0L)
KeyPath(items :+ last) KeyPath(items :+ last)
} }
@ -151,14 +151,22 @@ class LocalKeyManagerSpec extends AnyFunSuite {
} }
test("generate multisig swap-in address") { test("generate multisig swap-in address") {
val entropy = ByteVector32.fromValidHex("0101010101010101010101010101010101010101010101010101010101010101") val aliceKeyManager = {
val seed = MnemonicCode.toSeed(MnemonicCode.toMnemonics(entropy), "").take(32) val entropy = ByteVector32.fromValidHex("0101010101010101010101010101010101010101010101010101010101010101")
val keyManager = new LocalKeyManager(seed, Block.RegtestGenesisBlock.hash) val seed = MnemonicCode.toSeed(MnemonicCode.toMnemonics(entropy), "").take(32)
new LocalKeyManager(seed, Block.RegtestGenesisBlock.hash)
val serverPublicKey = PublicKey(ByteVector.fromValidHex("02cd0e2ed9c42af42e0b30e2a0b339c8335bbdc1f895fe552d8e224aedc82d6c88")) }
val bobKeyManager = {
val entropy = ByteVector32.fromValidHex("0202020202020202020202020202020202020202020202020202020202020202")
val seed = MnemonicCode.toSeed(MnemonicCode.toMnemonics(entropy), "").take(32)
new LocalKeyManager(seed, Block.RegtestGenesisBlock.hash)
}
val swapInRefundDelay = 144 * 30 * 6 val swapInRefundDelay = 144 * 30 * 6
val swapInAddress = keyManager.multisigSwapInAddress(serverPublicKey, swapInRefundDelay) val bobSwapInServerXpub = "tpubDDt5vQap1awkyDXx1z1cP7QFKSZHDCCpbU8nSq9jy7X2grTjUVZDePexf6gc6AHtRRzkgfPW87K6EKUVV6t3Hu2hg7YkHkmMeLSfrP85x41"
val swapInAddressAlice = aliceKeyManager.multisigSwapInAddress(aliceKeyManager.kmpNodeKey.publicKey, bobSwapInServerXpub, swapInRefundDelay)
assert(swapInAddress == "bcrt1qvwc4zcelvlj3pcy97pj09dz2hgq0ptav25nrjm54dt3ch09plxnq6pmjje") assert(swapInAddressAlice == "bcrt1qw78cdcsn55vwsvmwe9qgwnx0fwffzqej7keuqfjnwj5xm0f5u6js2hp66f")
val aliceSwapInServerXpub = "tpubDCvYeHUZisCMVTSfWDa1yevTf89NeF6TWxXUQwqkcmFrNvNdNvZQh1j4m4uTA4QcmPEwcrKVF8bJih1v16zDZacRr4j9MCAFQoSydKKy66q"
val swapInAddressBob = bobKeyManager.multisigSwapInAddress(bobKeyManager.kmpNodeKey.publicKey, aliceSwapInServerXpub, swapInRefundDelay)
assert(swapInAddressBob == "bcrt1qjs2l2ey9rk742hvv25kjghqvqdyhvf7vshwesgflzch9kcq2c8lqh60h44")
} }
} }