@@ -2,21 +2,23 @@ package fr.acinq.eclair
22
33import fr .acinq .bitcoin .scalacompat .Crypto .PublicKey
44import fr .acinq .bitcoin .scalacompat .Musig2 .{IndividualNonce , LocalNonce }
5- import fr .acinq .bitcoin .scalacompat .{ByteVector32 , ByteVector64 , DeterministicWallet , Musig2 , OutPoint , Satoshi , SatoshiLong , Script , ScriptWitness , Transaction , TxIn , TxOut , addressToPublicKeyScript }
5+ import fr .acinq .bitcoin .scalacompat .{DeterministicWallet , Musig2 , OutPoint , Satoshi , SatoshiLong , Script , ScriptWitness , Transaction , TxIn , TxOut , addressToPublicKeyScript }
66import fr .acinq .eclair .blockchain .fee .FeeratePerKw
7- import fr .acinq .eclair .channel .ChannelSpendSignature .PartialSignatureWithNonce
87import fr .acinq .eclair .channel .{ChannelConfig , ChannelSpendSignature }
9- import fr .acinq .eclair .transactions .Scripts .Taproot
108import fr .acinq .eclair .transactions .Transactions ._
119import fr .acinq .eclair .transactions .{Scripts , Transactions }
1210import scodec .bits .ByteVector
1311
1412import scala .concurrent .Future
13+ import scala .jdk .CollectionConverters .ConcurrentMapHasAsScala
1514
1615trait SpendFromChannelAddress {
1716
1817 this : EclairImpl =>
1918
19+ import java .util .concurrent .ConcurrentHashMap
20+ private val nonces = new ConcurrentHashMap [IndividualNonce , LocalNonce ]().asScala
21+
2022 private def buildTx (outPoint : OutPoint , outputAmount : Satoshi , pubKeyScript : ByteVector , witness : ScriptWitness ) = Transaction (2 ,
2123 txIn = Seq (TxIn (outPoint, ByteVector .empty, 0 , witness)),
2224 txOut = Seq (TxOut (outputAmount, pubKeyScript)),
@@ -29,71 +31,42 @@ trait SpendFromChannelAddress {
2931 Right (pubKeyScript) = addressToPublicKeyScript(appKit.nodeParams.chainHash, address).map(Script .write)
3032 channelKeys = appKit.nodeParams.channelKeyManager.channelKeys(ChannelConfig .standard, fundingKeyPath)
3133 localFundingPubkey = channelKeys.fundingKey(fundingTxIndex).publicKey
34+ isTaproot = Script .isPay2tr(Script .parse(pubKeyScript))
35+ localNonce_opt = if (isTaproot) {
36+ val serverNonce = Musig2 .generateNonce(randomBytes32(), Right (localFundingPubkey), Seq (localFundingPubkey), None , None )
37+ nonces.put(serverNonce.publicNonce, serverNonce)
38+ Some (serverNonce.publicNonce)
39+ } else {
40+ None
41+ }
3242 // build the tx a first time with a zero amount to compute the weight
3343 dummyWitness = Scripts .witness2of2(PlaceHolderSig , PlaceHolderSig , localFundingPubkey, localFundingPubkey)
3444 fee = Transactions .weight2fee(feerate, buildTx(outPoint, 0 .sat, pubKeyScript, dummyWitness).weight())
3545 _ = assert(inputAmount - fee > Scripts .dustLimit(pubKeyScript), s " amount insufficient (fee= $fee) " )
3646 unsignedTx = buildTx(outPoint, inputAmount - fee, pubKeyScript, dummyWitness)
37- } yield SpendFromChannelPrep (fundingTxIndex, localFundingPubkey, inputAmount, unsignedTx)
47+ } yield SpendFromChannelPrep (fundingTxIndex, localFundingPubkey, localNonce_opt, inputAmount, unsignedTx)
3848 }
3949
40- override def spendFromChannelAddress (fundingKeyPath : DeterministicWallet .KeyPath , fundingTxIndex : Long , remoteFundingPubkey : PublicKey , remoteSig : ByteVector64 , unsignedTx : Transaction ): Future [SpendFromChannelResult ] = {
50+ override def spendFromChannelAddress (fundingKeyPath : DeterministicWallet .KeyPath , fundingTxIndex : Long , remoteFundingPubkey : PublicKey , localNonce_opt : Option [ IndividualNonce ], remoteSig : ChannelSpendSignature , unsignedTx : Transaction ): Future [SpendFromChannelResult ] = {
4151 for {
4252 _ <- Future .successful(())
4353 outPoint = unsignedTx.txIn.head.outPoint
4454 inputTx <- appKit.wallet.getTransaction(outPoint.txid)
45- channelKeys = appKit.nodeParams.channelKeyManager.channelKeys(ChannelConfig .standard, fundingKeyPath)
46- localFundingKey = channelKeys.fundingKey(fundingTxIndex)
4755 inputInfo = InputInfo (outPoint, inputTx.txOut(outPoint.index.toInt))
4856 // classify as splice, doesn't really matter
4957 tx = Transactions .SpliceTx (inputInfo, unsignedTx)
50- localSig = tx.sign(localFundingKey, remoteFundingPubkey, extraUtxos = Map .empty)
51- signedTx = tx.aggregateSigs(localFundingKey.publicKey, remoteFundingPubkey, localSig, ChannelSpendSignature .IndividualSignature (remoteSig))
52- } yield SpendFromChannelResult (signedTx)
53- }
54-
55- override def spendFromTaprootChannelAddressPrep (outPoint : OutPoint , fundingKeyPath : DeterministicWallet .KeyPath , fundingTxIndex : Long , address : String , feerate : FeeratePerKw , sessionId : ByteVector32 ): Future [SpendFromTaprootChannelPrep ] = {
56- for {
57- inputTx <- appKit.wallet.getTransaction(outPoint.txid)
58- inputAmount = inputTx.txOut(outPoint.index.toInt).amount
59- Right (pubKeyScript) = addressToPublicKeyScript(appKit.nodeParams.chainHash, address).map(Script .write)
60- channelKeys = appKit.nodeParams.channelKeyManager.channelKeys(ChannelConfig .standard, fundingKeyPath)
61- localFundingPubkey = channelKeys.fundingKey(fundingTxIndex).publicKey
62- // build the tx a first time with a zero amount to compute the weight
63- dummyWitness = Script .witnessKeyPathPay2tr(PlaceHolderSig )
64- fee = Transactions .weight2fee(feerate, buildTx(outPoint, 0 .sat, pubKeyScript, dummyWitness).weight())
65- _ = assert(inputAmount - fee > Scripts .dustLimit(pubKeyScript), s " amount insufficient (fee= $fee) " )
66- unsignedTx = buildTx(outPoint, inputAmount - fee, pubKeyScript, dummyWitness)
67- nonce = Musig2 .generateNonce(sessionId, Right (localFundingPubkey), Seq (localFundingPubkey), None , None )
68- } yield SpendFromTaprootChannelPrep (fundingTxIndex, localFundingPubkey, inputAmount, unsignedTx, nonce)
69- }
70-
71- override def spendFromTaprootChannelAddressPartialSign (fundingKeyPath : DeterministicWallet .KeyPath , fundingTxIndex : Long , unsignedTx : Transaction , amount : Satoshi , remoteFundingPubkey : PublicKey , remoteNonce : IndividualNonce ): Future [SpendFromTaprootChannelPartialSign ] = {
72- for {
73- _ <- Future .successful(())
74- channelKeys = appKit.nodeParams.channelKeyManager.channelKeys(ChannelConfig .standard, fundingKeyPath)
75- localFundingKey = channelKeys.fundingKey(fundingTxIndex)
76- outputScript = Script .pay2tr(Taproot .musig2Aggregate(localFundingKey.publicKey, remoteFundingPubkey), None )
77- inputInfo = InputInfo (unsignedTx.txIn(0 ).outPoint, TxOut (amount, outputScript))
78- tx = Transactions .SpliceTx (inputInfo, unsignedTx)
79- localNonce = Musig2 .generateNonce(randomBytes32(), Left (localFundingKey), Seq (localFundingKey.publicKey), None , None )
80- Right (localSig) = tx.partialSign(localFundingKey, remoteFundingPubkey, extraUtxos = Map .empty, localNonce = localNonce, publicNonces = Seq (localNonce.publicNonce, remoteNonce))
81- } yield SpendFromTaprootChannelPartialSign (localFundingKey.publicKey, localSig)
82- }
83-
84- override def spendFromTaprootChannelAddress (fundingKeyPath : DeterministicWallet .KeyPath , fundingTxIndex : Long , remoteFundingPubkey : PublicKey , sessionId : ByteVector32 , remotePartialSig : PartialSignatureWithNonce , unsignedTx : Transaction ): Future [SpendFromChannelResult ] = {
85- for {
86- _ <- Future .successful(())
87- outPoint = unsignedTx.txIn.head.outPoint
88- inputTx <- appKit.wallet.getTransaction(outPoint.txid)
8958 channelKeys = appKit.nodeParams.channelKeyManager.channelKeys(ChannelConfig .standard, fundingKeyPath)
9059 localFundingKey = channelKeys.fundingKey(fundingTxIndex)
91- inputInfo = InputInfo (outPoint, inputTx.txOut(outPoint.index.toInt))
92- // classify as splice, doesn't really matter
93- tx = Transactions .SpliceTx (inputInfo, unsignedTx)
94- localNonce = Musig2 .generateNonce(sessionId, Right (localFundingKey.publicKey), Seq (localFundingKey.publicKey), None , None )
95- Right (localSig) = tx.partialSign(localFundingKey, remoteFundingPubkey, extraUtxos = Map .empty, localNonce = localNonce, publicNonces = Seq (localNonce.publicNonce, remotePartialSig.nonce))
96- Right (signedTx) = tx.aggregateSigs(localFundingKey.publicKey, remoteFundingPubkey, localSig, remotePartialSig, extraUtxos = Map .empty)
60+ signedTx = remoteSig match {
61+ case individualRemoteSig : ChannelSpendSignature .IndividualSignature =>
62+ val localSig = tx.sign(localFundingKey, remoteFundingPubkey, extraUtxos = Map .empty)
63+ tx.aggregateSigs(localFundingKey.publicKey, remoteFundingPubkey, localSig, individualRemoteSig)
64+ case remotePartialSig : ChannelSpendSignature .PartialSignatureWithNonce =>
65+ val localPrivateNonce = nonces(localNonce_opt.get)
66+ val Right (localSig) = tx.partialSign(localFundingKey, remoteFundingPubkey, extraUtxos = Map .empty, localNonce = localPrivateNonce, publicNonces = Seq (localPrivateNonce.publicNonce, remotePartialSig.nonce))
67+ val Right (signedTx) = tx.aggregateSigs(localFundingKey.publicKey, remoteFundingPubkey, localSig, remotePartialSig, extraUtxos = Map .empty)
68+ signedTx
69+ }
9770 } yield SpendFromChannelResult (signedTx)
9871 }
9972}
0 commit comments