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:
parent
98d588ab03
commit
72f0993a31
2 changed files with 27 additions and 10 deletions
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue