< Summary - Combined Code Coverage

Information
Class: NLightning.Infrastructure.Protocol.Services.KeyDerivationService
Assembly: NLightning.Infrastructure
File(s): /home/runner/work/nlightning/nlightning/src/NLightning.Infrastructure/Protocol/Services/KeyDerivationService.cs
Tag: 30_15166811759
Line coverage
88%
Covered lines: 63
Uncovered lines: 8
Coverable lines: 71
Total lines: 212
Line coverage: 88.7%
Branch coverage
58%
Covered branches: 14
Total branches: 24
Branch coverage: 58.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
DerivePublicKey(...)100%11100%
DerivePrivateKey(...)100%11100%
DeriveRevocationPubKey(...)100%11100%
DeriveRevocationPrivKey(...)100%11100%
GeneratePerCommitmentSecret(...)100%44100%
ComputeSha256(...)100%11100%
AddPubKeys(...)50%8.83657.14%
MultiplyPubKey(...)50%6.84671.43%
AddPrivateKeys(...)50%2.01285.71%
MultiplyPrivateKey(...)50%6.84671.43%

File(s)

/home/runner/work/nlightning/nlightning/src/NLightning.Infrastructure/Protocol/Services/KeyDerivationService.cs

#LineLine coverage
 1using NBitcoin;
 2using NBitcoin.Secp256k1;
 3
 4namespace NLightning.Infrastructure.Protocol.Services;
 5
 6using Crypto.Contexts;
 7using Crypto.Hashes;
 8using Domain.Crypto.Constants;
 9using Domain.Protocol.Services;
 10
 11public class KeyDerivationService : IKeyDerivationService
 12{
 13    /// <summary>
 14    /// Derives a public key using the formula: basepoint + SHA256(per_commitment_point || basepoint) * G
 15    /// </summary>
 16    public PubKey DerivePublicKey(PubKey basepoint, PubKey perCommitmentPoint)
 17    {
 18        // Calculate SHA256(per_commitment_point || basepoint)
 419        Span<byte> hashBytes = stackalloc byte[CryptoConstants.SHA256_HASH_LEN];
 420        ComputeSha256(perCommitmentPoint, basepoint, hashBytes);
 21
 22        // Create a private key from the hash (this represents the scalar value)
 423        var hashPrivateKey = new Key(hashBytes.ToArray());
 24
 25        // Get the EC point representation of hash*G
 426        var hashPoint = hashPrivateKey.PubKey;
 27
 28        // Add the base point to the hash point (EC point addition)
 29        // NBitcoin doesn't have direct point addition, so we use a trick with BIP32 derivation
 430        return AddPubKeys(basepoint, hashPoint);
 31    }
 32
 33    /// <summary>
 34    /// Derives a private key using the formula: basepoint_secret + SHA256(per_commitment_point || basepoint)
 35    /// </summary>
 36    public Key DerivePrivateKey(Key basepointSecret, PubKey perCommitmentPoint)
 37    {
 438        var basepoint = basepointSecret.PubKey;
 439        Span<byte> hashBytes = stackalloc byte[CryptoConstants.SHA256_HASH_LEN];
 440        ComputeSha256(perCommitmentPoint, basepoint, hashBytes);
 41
 42        // Create a private key from the hash
 443        var hashPrivateKey = new Key(hashBytes.ToArray());
 44
 45        // Combine the two private keys
 446        return AddPrivateKeys(basepointSecret, hashPrivateKey);
 47    }
 48
 49    /// <summary>
 50    /// Derives the revocation public key
 51    /// </summary>
 52    public PubKey DeriveRevocationPubKey(PubKey revocationBasepoint, PubKey perCommitmentPoint)
 53    {
 454        Span<byte> hash1 = stackalloc byte[CryptoConstants.SHA256_HASH_LEN];
 455        Span<byte> hash2 = stackalloc byte[CryptoConstants.SHA256_HASH_LEN];
 456        ComputeSha256(revocationBasepoint, perCommitmentPoint, hash1);
 457        ComputeSha256(perCommitmentPoint, revocationBasepoint, hash2);
 58
 59        // Calculate revocation_basepoint * SHA256(revocation_basepoint || per_commitment_point)
 460        var term1PrivKey = new Key(hash1.ToArray());
 461        var term1 = MultiplyPubKey(revocationBasepoint, term1PrivKey.ToBytes());
 62
 63        // Calculate per_commitment_point * SHA256(per_commitment_point || revocation_basepoint)
 464        var term2PrivKey = new Key(hash2.ToArray());
 465        var term2 = MultiplyPubKey(perCommitmentPoint, term2PrivKey.ToBytes());
 66
 67        // Add the two terms
 468        return AddPubKeys(term1, term2);
 69    }
 70
 71    /// <summary>
 72    /// Derives the revocation private key when both secrets are known
 73    /// </summary>
 74    public Key DeriveRevocationPrivKey(Key revocationBasepointSecret, Key perCommitmentSecret)
 75    {
 476        var revocationBasepoint = revocationBasepointSecret.PubKey;
 477        var perCommitmentPoint = perCommitmentSecret.PubKey;
 78
 479        Span<byte> hash1 = stackalloc byte[CryptoConstants.SHA256_HASH_LEN];
 480        Span<byte> hash2 = stackalloc byte[CryptoConstants.SHA256_HASH_LEN];
 481        ComputeSha256(revocationBasepoint, perCommitmentPoint, hash1);
 482        ComputeSha256(perCommitmentPoint, revocationBasepoint, hash2);
 83
 84        // Calculate revocation_basepoint_secret * SHA256(revocation_basepoint || per_commitment_point)
 485        var term1 = MultiplyPrivateKey(revocationBasepointSecret, hash1.ToArray());
 86
 87        // Calculate per_commitment_secret * SHA256(per_commitment_point || revocation_basepoint)
 488        var term2 = MultiplyPrivateKey(perCommitmentSecret, hash2.ToArray());
 89
 90        // Add the two terms
 491        return AddPrivateKeys(term1, term2);
 92    }
 93
 94    /// <summary>
 95    /// Generates per-commitment secret from seed and index
 96    /// </summary>
 97    public static byte[] GeneratePerCommitmentSecret(byte[] seed, ulong index)
 98    {
 28899        using var sha256 = new Sha256();
 100
 288101        var secret = new byte[seed.Length];
 288102        Buffer.BlockCopy(seed, 0, secret, 0, seed.Length);
 103
 28224104        for (var b = 47; b >= 0; b--)
 105        {
 13824106            if (((index >> b) & 1) == 0)
 107            {
 108                continue;
 109            }
 110
 111            // Flip bit (b % 8) in byte (b / 8)
 8760112            secret[b / 8] ^= (byte)(1 << (b % 8));
 8760113            sha256.AppendData(secret);
 8760114            sha256.GetHashAndReset(secret);
 115        }
 116
 288117        return secret;
 288118    }
 119
 120    /// <summary>
 121    /// Helper method to calculate SHA256(point1 || point2)
 122    /// </summary>
 123    private static void ComputeSha256(PubKey point1, PubKey point2, Span<byte> buffer)
 124    {
 24125        using var sha256 = new Sha256();
 24126        sha256.AppendData(point1.ToBytes());
 24127        sha256.AppendData(point2.ToBytes());
 24128        sha256.GetHashAndReset(buffer);
 48129    }
 130
 131    /// <summary>
 132    /// Adds two public keys (EC point addition)
 133    /// </summary>
 134    private static PubKey AddPubKeys(PubKey pubKey1, PubKey pubKey2)
 135    {
 136        // Create ECPubKey objects
 8137        if (!ECPubKey.TryCreate(pubKey1.ToBytes(), NLightningContext.Instance, out _, out var ecPubKey1))
 0138            throw new ArgumentException("Invalid public key", nameof(pubKey1));
 139
 8140        if (!ECPubKey.TryCreate(pubKey2.ToBytes(), NLightningContext.Instance, out _, out var ecPubKey2))
 0141            throw new ArgumentException("Invalid public key", nameof(pubKey2));
 142
 143        // Use TryCombine to add the pubkeys
 8144        if (!ECPubKey.TryCombine(NLightningContext.Instance, [ecPubKey1, ecPubKey2], out var combinedPubKey))
 0145            throw new InvalidOperationException("Failed to combine public keys");
 146
 147        // Create a new PubKey from the combined ECPubKey
 8148        return new PubKey(combinedPubKey!.ToBytes());
 149    }
 150
 151    /// <summary>
 152    /// Multiplies a public key by a scalar
 153    /// </summary>
 154    private static PubKey MultiplyPubKey(PubKey pubKey, byte[] scalar)
 155    {
 8156        ArgumentNullException.ThrowIfNull(pubKey);
 8157        if (scalar is not { Length: 32 })
 0158            throw new ArgumentException("Scalar must be 32 bytes", nameof(scalar));
 159
 160        // Convert PubKey to ECPubKey
 8161        if (!ECPubKey.TryCreate(pubKey.ToBytes(), NLightningContext.Instance, out var compressed, out var ecPubKey))
 0162            throw new ArgumentException("Invalid public key", nameof(pubKey));
 163
 164        // Multiply using TweakMul
 8165        var multipliedPubKey = ecPubKey.TweakMul(scalar);
 166
 167        // Create a new PubKey from the result
 8168        return new PubKey(multipliedPubKey.ToBytes(compressed));
 169    }
 170
 171    /// <summary>
 172    /// Adds two private keys (modular addition in the EC field)
 173    /// </summary>
 174    private Key AddPrivateKeys(Key key1, Key key2)
 175    {
 8176        ArgumentNullException.ThrowIfNull(key1);
 8177        ArgumentNullException.ThrowIfNull(key2);
 178
 179        // Extract the bytes from the second key
 8180        var key2Bytes = key2.ToBytes();
 181
 182        // Create a temporary ECPrivKey from the first key's bytes
 8183        if (!NLightningContext.Instance.TryCreateECPrivKey(key1.ToBytes(), out var ecKey1))
 0184            throw new InvalidOperationException("Invalid first private key");
 185
 186        // Add the second key to the first using TweakAdd
 8187        var resultKey = ecKey1.TweakAdd(key2Bytes);
 188
 189        // Create a new Key with the result
 8190        return new Key(resultKey.sec.ToBytes());
 191    }
 192
 193    /// <summary>
 194    /// Multiplies a private key by a scalar
 195    /// </summary>
 196    private static Key MultiplyPrivateKey(Key key, byte[] scalar)
 197    {
 8198        ArgumentNullException.ThrowIfNull(key);
 8199        if (scalar is not { Length: 32 })
 0200            throw new ArgumentException("Scalar must be 32 bytes", nameof(scalar));
 201
 202        // Create a temporary ECPrivKey from the key's bytes
 8203        if (!NLightningContext.Instance.TryCreateECPrivKey(key.ToBytes(), out var ecKey))
 0204            throw new InvalidOperationException("Invalid private key");
 205
 206        // Multiply using TweakMul
 8207        var multipliedKey = ecKey.TweakMul(scalar);
 208
 209        // Create a new Key with the result
 8210        return new Key(multipliedKey.sec.ToBytes());
 211    }
 212}