< Summary - Combined Code Coverage

Information
Class: NLightning.Infrastructure.Bitcoin.Services.KeyDerivationService
Assembly: NLightning.Infrastructure.Bitcoin
File(s): /home/runner/work/nlightning/nlightning/src/NLightning.Infrastructure.Bitcoin/Services/KeyDerivationService.cs
Tag: 36_15743069263
Line coverage
89%
Covered lines: 70
Uncovered lines: 8
Coverable lines: 78
Total lines: 225
Line coverage: 89.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.Bitcoin/Services/KeyDerivationService.cs

#LineLine coverage
 1using NBitcoin;
 2using NBitcoin.Secp256k1;
 3
 4namespace NLightning.Infrastructure.Bitcoin.Services;
 5
 6using Crypto.Contexts;
 7using Domain.Crypto.Constants;
 8using Domain.Crypto.ValueObjects;
 9using Domain.Protocol.Interfaces;
 10using Infrastructure.Crypto.Hashes;
 11
 12public class KeyDerivationService : IKeyDerivationService
 13{
 14    /// <summary>
 15    /// Derives a public key using the formula: basepoint + SHA256(per_commitment_point || basepoint) * G
 16    /// </summary>
 17    public CompactPubKey DerivePublicKey(CompactPubKey compactBasepoint, CompactPubKey compactPerCommitmentPoint)
 18    {
 419        var basePoint = new PubKey(compactBasepoint);
 420        var percCommitmentPoint = new PubKey(compactPerCommitmentPoint);
 21
 22        // Calculate SHA256(per_commitment_point || basepoint)
 423        Span<byte> hashBytes = stackalloc byte[CryptoConstants.Sha256HashLen];
 424        ComputeSha256(percCommitmentPoint, basePoint, hashBytes);
 25
 26        // Create a private key from the hash (this represents the scalar value)
 427        var hashPrivateKey = new Key(hashBytes.ToArray());
 28
 29        // Get the EC point representation of hash*G
 430        var hashPoint = hashPrivateKey.PubKey;
 31
 32        // Add the base point to the hash point (EC point addition)
 33        // NBitcoin doesn't have direct point addition, so we use a trick with BIP32 derivation
 434        return AddPubKeys(basePoint, hashPoint);
 35    }
 36
 37    /// <summary>
 38    /// Derives a private key using the formula: basepoint_secret + SHA256(per_commitment_point || basepoint)
 39    /// </summary>
 40    public PrivKey DerivePrivateKey(PrivKey basepointSecretPriv, CompactPubKey compactPerCommitmentPoint)
 41    {
 442        var basepointSecret = new Key(basepointSecretPriv);
 443        var perCommitmentPoint = new PubKey(compactPerCommitmentPoint);
 44
 445        Span<byte> hashBytes = stackalloc byte[CryptoConstants.Sha256HashLen];
 446        ComputeSha256(perCommitmentPoint, basepointSecret.PubKey, hashBytes);
 47
 48        // Create a private key from the hash
 449        var hashPrivateKey = new Key(hashBytes.ToArray());
 50
 51        // Combine the two private keys
 452        return AddPrivateKeys(basepointSecret, hashPrivateKey).ToBytes();
 53    }
 54
 55    /// <summary>
 56    /// Derives the revocation public key
 57    /// </summary>
 58    public CompactPubKey DeriveRevocationPubKey(CompactPubKey compactRevocationBasepoint,
 59                                                CompactPubKey compactPerCommitmentPoint)
 60    {
 461        var revocationBasepoint = new PubKey(compactRevocationBasepoint);
 462        var perCommitmentPoint = new PubKey(compactPerCommitmentPoint);
 63
 464        Span<byte> hash1 = stackalloc byte[CryptoConstants.Sha256HashLen];
 465        Span<byte> hash2 = stackalloc byte[CryptoConstants.Sha256HashLen];
 466        ComputeSha256(revocationBasepoint, perCommitmentPoint, hash1);
 467        ComputeSha256(perCommitmentPoint, revocationBasepoint, hash2);
 68
 69        // Calculate revocation_basepoint * SHA256(revocation_basepoint || per_commitment_point)
 470        var term1PrivKey = new Key(hash1.ToArray());
 471        var term1 = MultiplyPubKey(revocationBasepoint, term1PrivKey.ToBytes());
 72
 73        // Calculate per_commitment_point * SHA256(per_commitment_point || revocation_basepoint)
 474        var term2PrivKey = new Key(hash2.ToArray());
 475        var term2 = MultiplyPubKey(perCommitmentPoint, term2PrivKey.ToBytes());
 76
 77        // Add the two terms
 478        return AddPubKeys(term1, term2);
 79    }
 80
 81    /// <summary>
 82    /// Derives the revocation private key when both secrets are known
 83    /// </summary>
 84    public PrivKey DeriveRevocationPrivKey(PrivKey revocationBasepointSecretPriv, PrivKey perCommitmentSecretPriv)
 85    {
 486        var revocationBasepointSecret = new Key(revocationBasepointSecretPriv);
 487        var perCommitmentSecret = new Key(perCommitmentSecretPriv);
 88
 489        var revocationBasepoint = revocationBasepointSecret.PubKey;
 490        var perCommitmentPoint = perCommitmentSecret.PubKey;
 91
 492        Span<byte> hash1 = stackalloc byte[CryptoConstants.Sha256HashLen];
 493        Span<byte> hash2 = stackalloc byte[CryptoConstants.Sha256HashLen];
 494        ComputeSha256(revocationBasepoint, perCommitmentPoint, hash1);
 495        ComputeSha256(perCommitmentPoint, revocationBasepoint, hash2);
 96
 97        // Calculate revocation_basepoint_secret * SHA256(revocation_basepoint || per_commitment_point)
 498        var term1 = MultiplyPrivateKey(revocationBasepointSecret, hash1.ToArray());
 99
 100        // Calculate per_commitment_secret * SHA256(per_commitment_point || revocation_basepoint)
 4101        var term2 = MultiplyPrivateKey(perCommitmentSecret, hash2.ToArray());
 102
 103        // Add the two terms
 4104        return AddPrivateKeys(term1, term2).ToBytes();
 105    }
 106
 107    /// <summary>
 108    /// Generates per-commitment secret from seed and index
 109    /// </summary>
 110    public Secret GeneratePerCommitmentSecret(Secret seed, ulong index)
 111    {
 288112        using var sha256 = new Sha256();
 113
 288114        var secret = new byte[CryptoConstants.Sha256HashLen];
 288115        Buffer.BlockCopy(seed, 0, secret, 0, CryptoConstants.Sha256HashLen);
 116
 28224117        for (var b = 47; b >= 0; b--)
 118        {
 13824119            if (((index >> b) & 1) == 0)
 120            {
 121                continue;
 122            }
 123
 124            // Flip bit (b % 8) in byte (b / 8)
 8760125            secret[b / 8] ^= (byte)(1 << (b % 8));
 8760126            sha256.AppendData(secret);
 8760127            sha256.GetHashAndReset(secret);
 128        }
 129
 288130        return secret;
 288131    }
 132
 133    /// <summary>
 134    /// Helper method to calculate SHA256(point1 || point2)
 135    /// </summary>
 136    private static void ComputeSha256(PubKey point1, PubKey point2, Span<byte> buffer)
 137    {
 24138        using var sha256 = new Sha256();
 24139        sha256.AppendData(point1.ToBytes());
 24140        sha256.AppendData(point2.ToBytes());
 24141        sha256.GetHashAndReset(buffer);
 48142    }
 143
 144    /// <summary>
 145    /// Adds two public keys (EC point addition)
 146    /// </summary>
 147    private static CompactPubKey AddPubKeys(PubKey pubKey1, PubKey pubKey2)
 148    {
 149        // Create ECPubKey objects
 8150        if (!ECPubKey.TryCreate(pubKey1.ToBytes(), NLightningCryptoContext.Instance, out _, out var ecPubKey1))
 0151            throw new ArgumentException("Invalid public key", nameof(pubKey1));
 152
 8153        if (!ECPubKey.TryCreate(pubKey2.ToBytes(), NLightningCryptoContext.Instance, out _, out var ecPubKey2))
 0154            throw new ArgumentException("Invalid public key", nameof(pubKey2));
 155
 156        // Use TryCombine to add the pubkeys
 8157        if (!ECPubKey.TryCombine(NLightningCryptoContext.Instance, [ecPubKey1, ecPubKey2], out var combinedPubKey))
 0158            throw new InvalidOperationException("Failed to combine public keys");
 159
 160        // Create a new PubKey from the combined ECPubKey
 8161        return new PubKey(combinedPubKey!.ToBytes()).ToBytes();
 162    }
 163
 164    /// <summary>
 165    /// Multiplies a public key by a scalar
 166    /// </summary>
 167    private static PubKey MultiplyPubKey(PubKey pubKey, byte[] scalar)
 168    {
 8169        ArgumentNullException.ThrowIfNull(pubKey);
 8170        if (scalar is not { Length: 32 })
 0171            throw new ArgumentException("Scalar must be 32 bytes", nameof(scalar));
 172
 173        // Convert PubKey to ECPubKey
 8174        if (!ECPubKey.TryCreate(pubKey.ToBytes(), NLightningCryptoContext.Instance, out var compressed, out var ecPubKey
 0175            throw new ArgumentException("Invalid public key", nameof(pubKey));
 176
 177        // Multiply using TweakMul
 8178        var multipliedPubKey = ecPubKey.TweakMul(scalar);
 179
 180        // Create a new PubKey from the result
 8181        return new PubKey(multipliedPubKey.ToBytes(compressed));
 182    }
 183
 184    /// <summary>
 185    /// Adds two private keys (modular addition in the EC field)
 186    /// </summary>
 187    private Key AddPrivateKeys(Key key1, Key key2)
 188    {
 8189        ArgumentNullException.ThrowIfNull(key1);
 8190        ArgumentNullException.ThrowIfNull(key2);
 191
 192        // Extract the bytes from the second key
 8193        var key2Bytes = key2.ToBytes();
 194
 195        // Create a temporary ECPrivKey from the first key's bytes
 8196        if (!NLightningCryptoContext.Instance.TryCreateECPrivKey(key1.ToBytes(), out var ecKey1))
 0197            throw new InvalidOperationException("Invalid first private key");
 198
 199        // Add the second key to the first using TweakAdd
 8200        var resultKey = ecKey1.TweakAdd(key2Bytes);
 201
 202        // Create a new Key with the result
 8203        return new Key(resultKey.sec.ToBytes());
 204    }
 205
 206    /// <summary>
 207    /// Multiplies a private key by a scalar
 208    /// </summary>
 209    private static Key MultiplyPrivateKey(Key key, byte[] scalar)
 210    {
 8211        ArgumentNullException.ThrowIfNull(key);
 8212        if (scalar is not { Length: 32 })
 0213            throw new ArgumentException("Scalar must be 32 bytes", nameof(scalar));
 214
 215        // Create a temporary ECPrivKey from the key's bytes
 8216        if (!NLightningCryptoContext.Instance.TryCreateECPrivKey(key.ToBytes(), out var ecKey))
 0217            throw new InvalidOperationException("Invalid private key");
 218
 219        // Multiply using TweakMul
 8220        var multipliedKey = ecKey.TweakMul(scalar);
 221
 222        // Create a new Key with the result
 8223        return new Key(multipliedKey.sec.ToBytes());
 224    }
 225}