< Summary - Combined Code Coverage

Information
Class: NLightning.Infrastructure.Bitcoin.Signers.LocalLightningSigner
Assembly: NLightning.Infrastructure.Bitcoin
File(s): /home/runner/work/NLightning/NLightning/src/NLightning.Infrastructure.Bitcoin/Signers/LocalLightningSigner.cs
Tag: 57_24045730253
Line coverage
15%
Covered lines: 52
Uncovered lines: 292
Coverable lines: 344
Total lines: 567
Line coverage: 15.1%
Branch coverage
12%
Covered branches: 9
Total branches: 74
Branch coverage: 12.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/NLightning/NLightning/src/NLightning.Infrastructure.Bitcoin/Signers/LocalLightningSigner.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2using Microsoft.Extensions.Logging;
 3using NBitcoin;
 4using NBitcoin.Crypto;
 5
 6namespace NLightning.Infrastructure.Bitcoin.Signers;
 7
 8using Builders;
 9using Domain.Bitcoin.Enums;
 10using Domain.Bitcoin.Interfaces;
 11using Domain.Bitcoin.Transactions.Outputs;
 12using Domain.Bitcoin.ValueObjects;
 13using Domain.Bitcoin.Wallet.Models;
 14using Domain.Channels.ValueObjects;
 15using Domain.Crypto.Constants;
 16using Domain.Crypto.ValueObjects;
 17using Domain.Exceptions;
 18using Domain.Node.Options;
 19using Domain.Protocol.Interfaces;
 20
 21public class LocalLightningSigner : ILightningSigner
 22{
 23    private const int FundingDerivationIndex = 0; // m/0' is the funding key
 24    private const int RevocationDerivationIndex = 1; // m/1' is the revocation key
 25    private const int PaymentDerivationIndex = 2; // m/2' is the payment key
 26    private const int DelayedPaymentDerivationIndex = 3; // m/3' is the delayed payment key
 27    private const int HtlcDerivationIndex = 4; // m/4' is the HTLC key
 28    private const int PerCommitmentSeedDerivationIndex = 5; // m/5' is the per-commitment seed
 29
 30    private readonly ISecureKeyManager _secureKeyManager;
 031    private readonly IUtxoMemoryRepository _utxoMemoryRepository;
 32    private readonly IFundingOutputBuilder _fundingOutputBuilder;
 33    private readonly IKeyDerivationService _keyDerivationService;
 7234    private readonly ConcurrentDictionary<ChannelId, ChannelSigningInfo> _channelSigningInfo = new();
 035    private readonly ILogger<LocalLightningSigner> _logger;
 036    private readonly Network _network;
 037
 7238    public LocalLightningSigner(IFundingOutputBuilder fundingOutputBuilder,
 7239                                IKeyDerivationService keyDerivationService, ILogger<LocalLightningSigner> logger,
 7240                                NodeOptions nodeOptions, ISecureKeyManager secureKeyManager,
 7241                                IUtxoMemoryRepository utxoMemoryRepository)
 042    {
 7243        _fundingOutputBuilder = fundingOutputBuilder;
 7244        _keyDerivationService = keyDerivationService;
 7245        _logger = logger;
 7246        _secureKeyManager = secureKeyManager;
 7247        _utxoMemoryRepository = utxoMemoryRepository;
 048
 7249        _network = Network.GetNetwork(nodeOptions.BitcoinNetwork) ??
 7250                   throw new ArgumentException("Invalid Bitcoin network specified", nameof(nodeOptions));
 51
 52        // TODO: Load channel key data from database
 7253    }
 054
 055    /// <inheritdoc />
 56    public uint CreateNewChannel(out ChannelBasepoints basepoints, out CompactPubKey firstPerCommitmentPoint)
 57    {
 058        // Generate a new key for this channel
 059        var channelPrivExtKey = _secureKeyManager.GetNextChannelKey(out var index);
 060        var channelKey = ExtKey.CreateFromBytes(channelPrivExtKey);
 061
 062        // Generate Lightning basepoints using proper BIP32 derivation paths
 063        using var localFundingSecret = GenerateFundingPrivateKey(channelKey);
 064        using var localRevocationSecret = channelKey.Derive(RevocationDerivationIndex, true).PrivateKey;
 065        using var localPaymentSecret = channelKey.Derive(PaymentDerivationIndex, true).PrivateKey;
 066        using var localDelayedPaymentSecret = channelKey.Derive(DelayedPaymentDerivationIndex, true).PrivateKey;
 067        using var localHtlcSecret = channelKey.Derive(HtlcDerivationIndex, true).PrivateKey;
 068        using var perCommitmentSeed = channelKey.Derive(PerCommitmentSeedDerivationIndex, true).PrivateKey;
 069
 070        // Generate static basepoints (these don't change per commitment)
 071        basepoints = new ChannelBasepoints(
 072            localFundingSecret.PubKey.ToBytes(),
 073            localRevocationSecret.PubKey.ToBytes(),
 074            localPaymentSecret.PubKey.ToBytes(),
 075            localDelayedPaymentSecret.PubKey.ToBytes(),
 076            localHtlcSecret.PubKey.ToBytes()
 077        );
 078
 79        // Generate the first per-commitment point
 080        var firstPerCommitmentSecretBytes = _keyDerivationService
 081           .GeneratePerCommitmentSecret(perCommitmentSeed.ToBytes(), CryptoConstants.FirstPerCommitmentIndex);
 082        using var firstPerCommitmentSecret = new Key(firstPerCommitmentSecretBytes);
 083        firstPerCommitmentPoint = firstPerCommitmentSecret.PubKey.ToBytes();
 84
 085        return index;
 086    }
 87
 88    /// <inheritdoc />
 089    public ChannelBasepoints GetChannelBasepoints(uint channelKeyIndex)
 090    {
 091        _logger.LogTrace("Generating channel basepoints for key index {ChannelKeyIndex}", channelKeyIndex);
 092
 093        // Recreate the basepoints from the channel key index
 094        var channelExtKey = _secureKeyManager.GetChannelKeyAtIndex(channelKeyIndex);
 095        var channelKey = ExtKey.CreateFromBytes(channelExtKey);
 096
 097        using var localFundingSecret = channelKey.Derive(FundingDerivationIndex, true).PrivateKey;
 098        using var localRevocationSecret = channelKey.Derive(RevocationDerivationIndex, true).PrivateKey;
 099        using var localPaymentSecret = channelKey.Derive(PaymentDerivationIndex, true).PrivateKey;
 0100        using var localDelayedPaymentSecret = channelKey.Derive(DelayedPaymentDerivationIndex, true).PrivateKey;
 0101        using var localHtlcSecret = channelKey.Derive(HtlcDerivationIndex, true).PrivateKey;
 0102
 0103        return new ChannelBasepoints(
 0104            localFundingSecret.PubKey.ToBytes(),
 0105            localRevocationSecret.PubKey.ToBytes(),
 0106            localPaymentSecret.PubKey.ToBytes(),
 0107            localDelayedPaymentSecret.PubKey.ToBytes(),
 0108            localHtlcSecret.PubKey.ToBytes()
 0109        );
 0110    }
 111
 0112    /// <inheritdoc />
 0113    public ChannelBasepoints GetChannelBasepoints(ChannelId channelId)
 114    {
 0115        _logger.LogTrace("Retrieving channel basepoints for channel {ChannelId}", channelId);
 116
 0117        if (!_channelSigningInfo.TryGetValue(channelId, out var signingInfo))
 0118            throw new SignerException($"Channel {channelId} not registered", channelId);
 0119
 0120        return GetChannelBasepoints(signingInfo.ChannelKeyIndex);
 121    }
 122
 123    /// <inheritdoc />
 0124    public CompactPubKey GetNodePublicKey() => _secureKeyManager.GetNodeKeyPair().CompactPubKey;
 0125
 0126    /// <inheritdoc />
 127    public CompactPubKey GetPerCommitmentPoint(uint channelKeyIndex, ulong commitmentNumber)
 128    {
 0129        _logger.LogTrace(
 0130            "Generating per-commitment point for channel key index {ChannelKeyIndex} and commitment number {CommitmentNu
 0131            channelKeyIndex, commitmentNumber);
 132
 0133        // Derive the per-commitment seed from the channel key
 0134        var channelExtKey = _secureKeyManager.GetChannelKeyAtIndex(channelKeyIndex);
 0135        var channelKey = ExtKey.CreateFromBytes(channelExtKey);
 0136        using var perCommitmentSeed = channelKey.Derive(PerCommitmentSeedDerivationIndex, true).PrivateKey;
 0137
 0138        var perCommitmentSecret =
 0139            _keyDerivationService.GeneratePerCommitmentSecret(perCommitmentSeed.ToBytes(), commitmentNumber);
 140
 0141        var perCommitmentPoint = new Key(perCommitmentSecret).PubKey;
 0142        return perCommitmentPoint.ToBytes();
 0143    }
 0144
 145    /// <inheritdoc />
 0146    public CompactPubKey GetPerCommitmentPoint(ChannelId channelId, ulong commitmentNumber)
 147    {
 0148        if (!_channelSigningInfo.TryGetValue(channelId, out var signingInfo))
 0149            throw new SignerException($"Channel {channelId} not registered", channelId);
 150
 0151        return GetPerCommitmentPoint(signingInfo.ChannelKeyIndex, commitmentNumber);
 0152    }
 153
 0154    /// <inheritdoc />
 0155    public void RegisterChannel(ChannelId channelId, ChannelSigningInfo signingInfo)
 156    {
 68157        _logger.LogTrace("Registering channel {ChannelId} with signing info", channelId);
 158
 68159        _channelSigningInfo.TryAdd(channelId, signingInfo);
 68160    }
 0161
 0162    /// <inheritdoc />
 163    public Secret ReleasePerCommitmentSecret(uint channelKeyIndex, ulong commitmentNumber)
 164    {
 0165        _logger.LogTrace(
 0166            "Releasing per-commitment secret for channel key index {ChannelKeyIndex} and commitment number {CommitmentNu
 0167            channelKeyIndex, commitmentNumber);
 168
 0169        // Derive the per-commitment seed from the channel key
 0170        var channelExtKey = _secureKeyManager.GetChannelKeyAtIndex(channelKeyIndex);
 0171        var channelKey = ExtKey.CreateFromBytes(channelExtKey);
 0172        using var perCommitmentSeed = channelKey.Derive(PerCommitmentSeedDerivationIndex, true).PrivateKey;
 173
 0174        return _keyDerivationService.GeneratePerCommitmentSecret(
 0175            perCommitmentSeed.ToBytes(), commitmentNumber);
 0176    }
 0177
 178    /// <inheritdoc />
 0179    public Secret ReleasePerCommitmentSecret(ChannelId channelId, ulong commitmentNumber)
 180    {
 0181        if (!_channelSigningInfo.TryGetValue(channelId, out var signingInfo))
 0182            throw new SignerException($"Channel {channelId} not registered", channelId);
 183
 0184        return ReleasePerCommitmentSecret(signingInfo.ChannelKeyIndex, commitmentNumber);
 0185    }
 0186
 187    public bool SignWalletTransaction(SignedTransaction unsignedTransaction)
 0188    {
 0189        throw new NotImplementedException();
 190    }
 191
 192    public bool SignFundingTransaction(ChannelId channelId, SignedTransaction unsignedTransaction)
 193    {
 0194        _logger.LogTrace("Signing funding transaction for channel {ChannelId} with TxId {TxId}", channelId,
 0195                         unsignedTransaction.TxId);
 0196
 0197        if (!_channelSigningInfo.TryGetValue(channelId, out var signingInfo))
 0198            throw new SignerException($"Channel {channelId} not registered with signer", channelId);
 0199
 200        Transaction nBitcoinTx;
 201        try
 202        {
 0203            nBitcoinTx = Transaction.Load(unsignedTransaction.RawTxBytes, _network);
 0204        }
 0205        catch (Exception ex)
 0206        {
 0207            throw new ArgumentException(
 0208                $"Failed to load transaction from RawTxBytes. TxId hint: {unsignedTransaction.TxId}", ex);
 0209        }
 0210
 211        try
 212        {
 0213            // Verify the funding output exists and is correct
 0214            if (signingInfo.FundingOutputIndex >= nBitcoinTx.Outputs.Count)
 0215                throw new SignerException($"Funding output index {signingInfo.FundingOutputIndex} is out of range",
 0216                                          channelId);
 217
 0218            // Build the funding output using the channel's signing info
 0219            var fundingOutputInfo = new FundingOutputInfo(signingInfo.FundingSatoshis, signingInfo.LocalFundingPubKey,
 0220                                                          signingInfo.RemoteFundingPubKey, signingInfo.FundingTxId,
 0221                                                          signingInfo.FundingOutputIndex);
 0222
 0223            var expectedFundingOutput = _fundingOutputBuilder.Build(fundingOutputInfo);
 0224            var expectedTxOut = expectedFundingOutput.ToTxOut();
 225
 0226            // Validate the transaction output matches what we expect
 0227            var actualTxOut = nBitcoinTx.Outputs[signingInfo.FundingOutputIndex];
 0228            if (!actualTxOut.ToBytes().SequenceEqual(expectedTxOut.ToBytes()))
 0229                throw new SignerException("Funding output script does not match expected script", channelId);
 230
 0231            if (actualTxOut.Value != expectedTxOut.Value)
 0232                throw new SignerException(
 0233                    $"Funding output amount {actualTxOut.Value} does not match expected amount {expectedTxOut.Value}",
 0234                    channelId);
 0235
 0236            _logger.LogDebug("Funding output validation passed for channel {ChannelId}", channelId);
 237
 0238            // Check transaction structure
 0239            if (nBitcoinTx.Inputs.Count == 0)
 0240                throw new SignerException("Funding transaction has no inputs", channelId);
 241
 242            // Get the utxoSet for the channel
 0243            var utxoModels = _utxoMemoryRepository.GetLockedUtxosForChannel(channelId);
 0244
 0245            var signedInputCount = 0;
 0246            var prevOuts = new TxOut[nBitcoinTx.Inputs.Count];
 0247            var signingKeys = new Key?[nBitcoinTx.Inputs.Count];
 0248            var taprootKeyPairs = new TaprootKeyPair?[nBitcoinTx.Inputs.Count];
 0249            var utxos = new UtxoModel[nBitcoinTx.Inputs.Count];
 250
 251            // Sign each input
 0252            for (var i = 0; i < nBitcoinTx.Inputs.Count; i++)
 253            {
 0254                var input = nBitcoinTx.Inputs[i];
 0255
 0256                // Try to get the address being spent
 0257                var utxo = utxoModels.FirstOrDefault(x => x.TxId.Equals(new TxId(input.PrevOut.Hash.ToBytes()))
 0258                                                       && x.Index.Equals(input.PrevOut.N));
 0259                if (utxo is null)
 260                {
 0261                    _logger.LogWarning("Could not find UTXO for input {InputIndex} in funding transaction", i);
 0262                    continue;
 263                }
 0264
 0265                if (utxo.WalletAddress is null)
 266                {
 0267                    _logger.LogWarning(
 0268                        "UTXO did not have a WalletAddress for input {InputIndex} in funding transaction", i);
 0269                    continue;
 0270                }
 0271
 0272                utxos[i] = utxo;
 0273
 0274                try
 275                {
 276                    // Create the scriptPubKey and previous output based on the address type
 277                    Script scriptPubKey;
 278                    ExtPrivKey signingExtKey;
 0279                    Key? signingKey = null;
 0280                    TaprootKeyPair? taprootKeyPair = null;
 0281
 0282                    switch (utxo.AddressType)
 0283                    {
 0284                        case AddressType.P2Wpkh:
 0285                            // Derive the key for this specific UTXO
 0286                            signingExtKey =
 0287                                _secureKeyManager.GetDepositP2WpkhKeyAtIndex(
 0288                                    utxo.WalletAddress.Index, utxo.WalletAddress.IsChange);
 0289                            signingKey = ExtKey.CreateFromBytes(signingExtKey).PrivateKey;
 0290                            // For P2WPKH: OP_0 <20-byte-pubkey-hash>
 0291                            scriptPubKey = signingKey.PubKey.WitHash.ScriptPubKey;
 0292                            break;
 293
 0294                        case AddressType.P2Tr:
 0295                            // Derive the key for this specific UTXO
 0296                            signingExtKey =
 0297                                _secureKeyManager.GetDepositP2TrKeyAtIndex(
 0298                                    utxo.WalletAddress.Index, utxo.WalletAddress.IsChange);
 0299                            var rootKey = ExtKey.CreateFromBytes(signingExtKey).PrivateKey;
 0300                            // For P2TR (Taproot): OP_1 <32-byte-taproot-output>
 0301                            taprootKeyPair = rootKey.CreateTaprootKeyPair();
 0302                            scriptPubKey = taprootKeyPair.PubKey.ScriptPubKey;
 0303                            break;
 304
 305                        default:
 0306                            throw new SignerException($"Unsupported address type {utxo.AddressType} for input {i}",
 0307                                                      channelId);
 308                    }
 0309
 0310                    signingKeys[i] = signingKey;
 0311                    taprootKeyPairs[i] = taprootKeyPair;
 0312                    prevOuts[i] = new TxOut(new Money(utxo.Amount.Satoshi), scriptPubKey);
 0313                }
 0314                catch (Exception ex)
 315                {
 0316                    _logger.LogError(ex, "Failed to sign input {InputIndex} in funding transaction", i);
 0317                    throw new SignerException(
 0318                        $"Failed to sign input {i}",
 0319                        channelId, ex, "Signing error");
 320                }
 321            }
 322
 0323            for (var i = 0; i < nBitcoinTx.Inputs.Count; i++)
 324            {
 325                try
 326                {
 0327                    var utxo = utxos[i];
 0328                    var signingKey = signingKeys[i];
 0329                    var taprootKeyPair = taprootKeyPairs[i];
 0330                    var prevOut = prevOuts[i];
 331
 0332                    switch (utxo.AddressType)
 333                    {
 334                        // Sign based on the address type
 335                        case AddressType.P2Wpkh:
 0336                            if (signingKey is null)
 0337                                throw new SignerException($"Missing signing key for P2WPKH input {i}", channelId);
 338
 339                            // Sign P2WPKH input
 0340                            SignP2WpkhInput(nBitcoinTx, i, signingKey, prevOut);
 0341                            break;
 342                        case AddressType.P2Tr:
 0343                            if (taprootKeyPair is null)
 0344                                throw new SignerException($"Missing taproot key pair for P2TR input {i}", channelId);
 345
 346                            // Sign P2TR (Taproot) input - key path spend
 0347                            SignP2TrInput(nBitcoinTx, i, taprootKeyPair, prevOuts);
 0348                            break;
 349                        default:
 0350                            throw new SignerException($"Unsupported address type {utxo.AddressType} for input {i}",
 0351                                                      channelId);
 352                    }
 353
 0354                    signedInputCount++;
 355
 0356                    _logger.LogTrace("Signed input {InputIndex} for funding transaction", i);
 0357                }
 0358                catch (Exception ex)
 359                {
 0360                    _logger.LogError(ex, "Failed to sign input {InputIndex} in funding transaction", i);
 0361                    throw new SignerException(
 0362                        $"Failed to sign input {i}",
 0363                        channelId, ex, "Signing error");
 364                }
 365            }
 366
 0367            if (signedInputCount == 0)
 0368                throw new SignerException("No inputs were successfully signed", channelId, "Signing failed");
 369
 370            // Update the transaction bytes in the SignedTransaction
 0371            unsignedTransaction.RawTxBytes = nBitcoinTx.ToBytes();
 372
 0373            _logger.LogInformation(
 0374                "Successfully signed {SignedCount}/{TotalCount} inputs for funding transaction {TxId}",
 0375                signedInputCount, nBitcoinTx.Inputs.Count, nBitcoinTx.GetHash());
 376
 0377            return signedInputCount == nBitcoinTx.Inputs.Count;
 378        }
 0379        catch (SignerException)
 380        {
 0381            throw;
 382        }
 0383        catch (Exception e)
 384        {
 0385            throw new SignerException($"Exception during funding transaction signing for TxId {nBitcoinTx.GetHash()}",
 0386                                      channelId, e);
 387        }
 0388    }
 389
 390    /// <inheritdoc />
 391    public CompactSignature SignChannelTransaction(ChannelId channelId, SignedTransaction unsignedTransaction)
 392    {
 64393        if (_logger.IsEnabled(LogLevel.Trace))
 0394            _logger.LogTrace("Signing transaction for channel {ChannelId} with TxId {TxId}", channelId,
 0395                             unsignedTransaction.TxId);
 396
 64397        if (!_channelSigningInfo.TryGetValue(channelId, out var signingInfo))
 0398            throw new InvalidOperationException($"Channel {channelId} not registered with signer");
 399
 400        Transaction nBitcoinTx;
 401        try
 402        {
 64403            nBitcoinTx = Transaction.Load(unsignedTransaction.RawTxBytes, _network);
 64404        }
 0405        catch (Exception ex)
 406        {
 0407            throw new ArgumentException(
 0408                $"Failed to load transaction from RawTxBytes. TxId hint: {unsignedTransaction.TxId}", ex);
 409        }
 410
 411        try
 412        {
 413            // Build the funding output using the channel's signing info
 64414            var fundingOutputInfo = new FundingOutputInfo(signingInfo.FundingSatoshis, signingInfo.LocalFundingPubKey,
 64415                                                          signingInfo.RemoteFundingPubKey, signingInfo.FundingTxId,
 64416                                                          signingInfo.FundingOutputIndex);
 417
 64418            var fundingOutput = _fundingOutputBuilder.Build(fundingOutputInfo);
 64419            var spentOutput = fundingOutput.ToTxOut();
 420
 421            // Get the signature hash for SegWit
 64422            var signatureHash = nBitcoinTx.GetSignatureHash(fundingOutput.RedeemScript, 0, SigHash.All, spentOutput,
 64423                                                            HashVersion.WitnessV0);
 424
 425            // Get the funding private key
 64426            using var fundingPrivateKey = GenerateFundingPrivateKey(signingInfo.ChannelKeyIndex);
 427
 64428            var signature = fundingPrivateKey.Sign(signatureHash, new SigningOptions(SigHash.All, false));
 429
 64430            return signature.Signature.MakeCanonical().ToCompact();
 431        }
 0432        catch (Exception ex)
 433        {
 0434            throw new InvalidOperationException(
 0435                $"Exception during signature verification for TxId {nBitcoinTx.GetHash()}", ex);
 436        }
 64437    }
 438
 439    /// <inheritdoc />
 440    public void ValidateSignature(ChannelId channelId, CompactSignature signature,
 441                                  SignedTransaction unsignedTransaction)
 442    {
 72443        if (_logger.IsEnabled(LogLevel.Trace))
 0444            _logger.LogTrace("Validating signature for channel {ChannelId} with TxId {TxId}", channelId,
 0445                             unsignedTransaction.TxId);
 446
 72447        if (!_channelSigningInfo.TryGetValue(channelId, out var signingInfo))
 4448            throw new SignerException("Channel not registered with signer", channelId, "Internal error");
 449
 450        Transaction nBitcoinTx;
 451        try
 452        {
 68453            nBitcoinTx = Transaction.Load(unsignedTransaction.RawTxBytes, _network);
 68454        }
 0455        catch (Exception e)
 456        {
 0457            throw new SignerException("Failed to load transaction from RawTxBytes", channelId, e, "Internal error");
 458        }
 459
 460        PubKey pubKey;
 461        try
 462        {
 68463            pubKey = new PubKey(signingInfo.RemoteFundingPubKey);
 68464        }
 0465        catch (Exception e)
 466        {
 0467            throw new SignerException("Failed to parse public key from CompactPubKey", channelId, e, "Internal error");
 468        }
 469
 470        ECDSASignature txSignature;
 471        try
 472        {
 68473            if (!ECDSASignature.TryParseFromCompact(signature, out txSignature))
 0474                throw new SignerException("Failed to parse compact signature", channelId, "Signature format error");
 475
 68476            if (!txSignature.IsLowS)
 0477                throw new SignerException("Signature is not low S", channelId,
 0478                                          "Signature is malleable");
 68479        }
 0480        catch (Exception e)
 481        {
 0482            throw new SignerException("Failed to parse DER signature", channelId, e,
 0483                                      "Signature format error");
 484        }
 485
 486        try
 487        {
 488            // Build the funding output using the channel's signing info
 68489            var fundingOutputInfo = new FundingOutputInfo(signingInfo.FundingSatoshis, signingInfo.LocalFundingPubKey,
 68490                                                          signingInfo.RemoteFundingPubKey, signingInfo.FundingTxId,
 68491                                                          signingInfo.FundingOutputIndex);
 492
 68493            var fundingOutput = _fundingOutputBuilder.Build(fundingOutputInfo);
 68494            var spentOutput = fundingOutput.ToTxOut();
 495
 68496            var signatureHash =
 68497                nBitcoinTx.GetSignatureHash(fundingOutput.RedeemScript, 0, SigHash.All, spentOutput,
 68498                                            HashVersion.WitnessV0);
 499
 68500            if (!pubKey.Verify(signatureHash, txSignature))
 0501                throw new SignerException("Peer signature is invalid", channelId, "Invalid signature provided");
 68502        }
 0503        catch (Exception e)
 504        {
 0505            throw new SignerException("Exception during signature verification", channelId, e,
 0506                                      "Signature verification error");
 507        }
 68508    }
 509
 510    protected virtual Key GenerateFundingPrivateKey(uint channelKeyIndex)
 511    {
 0512        var channelExtKey = _secureKeyManager.GetChannelKeyAtIndex(channelKeyIndex);
 0513        var channelKey = ExtKey.CreateFromBytes(channelExtKey);
 514
 0515        return GenerateFundingPrivateKey(channelKey);
 516    }
 517
 518    private static Key GenerateFundingPrivateKey(ExtKey extKey)
 519    {
 0520        return extKey.Derive(FundingDerivationIndex, true).PrivateKey;
 521    }
 522
 523    /// <summary>
 524    /// Sign a P2WPKH (Pay-to-Witness-PubKey-Hash) input
 525    /// </summary>
 526    private static void SignP2WpkhInput(Transaction tx, int inputIndex, Key signingKey, TxOut prevOut)
 527    {
 528        // For P2WPKH, the scriptCode is the P2PKH script: OP_DUP OP_HASH160 <pubkeyhash> OP_EQUALVERIFY OP_CHECKSIG
 0529        var scriptCode = signingKey.PubKey.Hash.ScriptPubKey;
 530
 531        // Get the signature hash for SegWit v0
 0532        var sigHash =
 0533            tx.GetSignatureHash(scriptCode, inputIndex, SigHash.All, prevOut, HashVersion.WitnessV0);
 534
 535        // Sign the hash
 0536        var transactionSignature = signingKey.Sign(sigHash, new SigningOptions(SigHash.All, false));
 537
 538        // For P2WPKH, witness is: <signature> <pubkey>
 0539        var witness = new WitScript(
 0540            Op.GetPushOp(transactionSignature.ToBytes()),
 0541            Op.GetPushOp(signingKey.PubKey.ToBytes()));
 542
 0543        tx.Inputs[inputIndex].WitScript = witness;
 0544    }
 545
 546    /// <summary>
 547    /// Sign a P2TR (Pay-to-Taproot) input using the key path spend
 548    /// </summary>
 549    /// <remarks>For Taproot, we use BIP341 signing</remarks>
 550    private static void SignP2TrInput(Transaction tx, int inputIndex, TaprootKeyPair taprootKeyPair, TxOut[] prevOuts)
 551    {
 552        // Create the TaprootExecutionData
 0553        var taprootExecutionData = new TaprootExecutionData(inputIndex)
 0554        {
 0555            SigHash = TaprootSigHash.All
 0556        };
 557
 558        // Calculate the signature hash using Taproot rules (BIP341)
 0559        var sigHash = tx.GetSignatureHashTaproot(prevOuts.ToArray(), taprootExecutionData);
 560
 561        // Sign with Schnorr signature (BIP340)
 0562        var taprootSignature = taprootKeyPair.SignTaprootKeySpend(sigHash, TaprootSigHash.All);
 563
 564        // For key path spend, witness is just: <signature>
 0565        tx.Inputs[inputIndex].WitScript = new WitScript(Op.GetPushOp(taprootSignature.ToBytes()));
 0566    }
 567}

Methods/Properties

.ctor(NLightning.Infrastructure.Bitcoin.Builders.IFundingOutputBuilder,NLightning.Domain.Protocol.Interfaces.IKeyDerivationService,Microsoft.Extensions.Logging.ILogger`1<NLightning.Infrastructure.Bitcoin.Signers.LocalLightningSigner>,NLightning.Domain.Node.Options.NodeOptions,NLightning.Domain.Protocol.Interfaces.ISecureKeyManager)
.ctor(NLightning.Infrastructure.Bitcoin.Builders.IFundingOutputBuilder,NLightning.Domain.Protocol.Interfaces.IKeyDerivationService,Microsoft.Extensions.Logging.ILogger`1<NLightning.Infrastructure.Bitcoin.Signers.LocalLightningSigner>,NLightning.Domain.Node.Options.NodeOptions,NLightning.Domain.Protocol.Interfaces.ISecureKeyManager,NLightning.Domain.Bitcoin.Interfaces.IUtxoMemoryRepository)
CreateNewChannel(NLightning.Domain.Channels.ValueObjects.ChannelBasepoints&,NLightning.Domain.Crypto.ValueObjects.CompactPubKey&)
CreateNewChannel(NLightning.Domain.Channels.ValueObjects.ChannelBasepoints&,NLightning.Domain.Crypto.ValueObjects.CompactPubKey&)
GetChannelBasepoints(System.UInt32)
GetChannelBasepoints(System.UInt32)
GetChannelBasepoints(NLightning.Domain.Channels.ValueObjects.ChannelId)
GetChannelBasepoints(NLightning.Domain.Channels.ValueObjects.ChannelId)
GetNodePublicKey()
GetPerCommitmentPoint(System.UInt32,System.UInt64)
GetNodePublicKey()
GetPerCommitmentPoint(System.UInt32,System.UInt64)
GetPerCommitmentPoint(NLightning.Domain.Channels.ValueObjects.ChannelId,System.UInt64)
GetPerCommitmentPoint(NLightning.Domain.Channels.ValueObjects.ChannelId,System.UInt64)
RegisterChannel(NLightning.Domain.Channels.ValueObjects.ChannelId,NLightning.Domain.Channels.ValueObjects.ChannelSigningInfo)
RegisterChannel(NLightning.Domain.Channels.ValueObjects.ChannelId,NLightning.Domain.Channels.ValueObjects.ChannelSigningInfo)
ReleasePerCommitmentSecret(System.UInt32,System.UInt64)
ReleasePerCommitmentSecret(System.UInt32,System.UInt64)
ReleasePerCommitmentSecret(NLightning.Domain.Channels.ValueObjects.ChannelId,System.UInt64)
ReleasePerCommitmentSecret(NLightning.Domain.Channels.ValueObjects.ChannelId,System.UInt64)
SignTransaction(NLightning.Domain.Channels.ValueObjects.ChannelId,NLightning.Domain.Bitcoin.ValueObjects.SignedTransaction)
SignWalletTransaction(NLightning.Domain.Bitcoin.ValueObjects.SignedTransaction)
SignFundingTransaction(NLightning.Domain.Channels.ValueObjects.ChannelId,NLightning.Domain.Bitcoin.ValueObjects.SignedTransaction)
ValidateSignature(NLightning.Domain.Channels.ValueObjects.ChannelId,NLightning.Domain.Crypto.ValueObjects.CompactSignature,NLightning.Domain.Bitcoin.ValueObjects.SignedTransaction)
GenerateFundingPrivateKey(System.UInt32)
GenerateFundingPrivateKey(NBitcoin.ExtKey)
SignChannelTransaction(NLightning.Domain.Channels.ValueObjects.ChannelId,NLightning.Domain.Bitcoin.ValueObjects.SignedTransaction)
ValidateSignature(NLightning.Domain.Channels.ValueObjects.ChannelId,NLightning.Domain.Crypto.ValueObjects.CompactSignature,NLightning.Domain.Bitcoin.ValueObjects.SignedTransaction)
GenerateFundingPrivateKey(System.UInt32)
GenerateFundingPrivateKey(NBitcoin.ExtKey)
SignP2WpkhInput(NBitcoin.Transaction,System.Int32,NBitcoin.Key,NBitcoin.TxOut)
SignP2TrInput(NBitcoin.Transaction,System.Int32,NBitcoin.TaprootKeyPair,NBitcoin.TxOut[])