< Summary - Combined Code Coverage

Information
Class: NLightning.Infrastructure.Protocol.Services.SecretStorageService
Assembly: NLightning.Infrastructure
File(s): /home/runner/work/nlightning/nlightning/src/NLightning.Infrastructure/Protocol/Services/SecretStorageService.cs
Tag: 36_15743069263
Line coverage
71%
Covered lines: 71
Uncovered lines: 28
Coverable lines: 99
Total lines: 266
Line coverage: 71.7%
Branch coverage
70%
Covered branches: 31
Total branches: 44
Branch coverage: 70.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
InsertSecret(...)100%88100%
DeriveOldSecret(...)100%66100%
StorePerCommitmentSeed(...)0%620%
GetPerCommitmentSeed()0%620%
StoreBasepointPrivateKey(...)0%2040%
GetBasepointPrivateKey(...)100%210%
LoadFromIndex(...)100%210%
GetBucketIndex(...)75%4.25475%
DeriveSecret(...)100%44100%
FreeSecret(...)50%2.02283.33%
ReleaseUnmanagedResources()70%13.71066.67%
Dispose(...)100%22100%
Dispose()100%11100%
Finalize()100%210%

File(s)

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

#LineLine coverage
 1using System.Runtime.InteropServices;
 2using System.Security.Cryptography;
 3
 4namespace NLightning.Infrastructure.Protocol.Services;
 5
 6using Crypto.Factories;
 7using Crypto.Hashes;
 8using Crypto.Interfaces;
 9using Domain.Crypto.Constants;
 10using Domain.Crypto.ValueObjects;
 11using Domain.Protocol.Enums;
 12using Domain.Protocol.Interfaces;
 13using Models;
 14
 15/// <summary>
 16/// Provides efficient storage of per-commitment secrets
 17/// </summary>
 18public class SecretStorageService : ISecretStorageService
 19{
 4820    private readonly StoredSecret?[] _knownSecrets = new StoredSecret?[49];
 4821    private readonly ICryptoProvider _cryptoProvider = CryptoFactory.GetCryptoProvider();
 4822    private IntPtr _perCommitmentSeedPtr = IntPtr.Zero;
 4823    private readonly Dictionary<BasepointType, IntPtr> _basepointSecrets = new();
 24
 25    /// <inheritdoc/>
 26    public bool InsertSecret(Secret secret, ulong index)
 27    {
 28        // Find the bucket for this secret
 43229        var bucket = GetBucketIndex(index);
 30
 43231        var storedSecret = new byte[CryptoConstants.SecretLen];
 43232        var derivedSecret = new byte[CryptoConstants.SecretLen];
 33        // Verify this secret can derive all previously known secrets
 1016034        for (var b = 0; b < bucket; b++)
 35        {
 468036            if (_knownSecrets[b] == null)
 37                continue;
 38
 448839            DeriveSecret(secret, bucket, _knownSecrets[b]!.Index, derivedSecret);
 40
 41            // Compare with stored secret (copied from secure memory)
 448842            Marshal.Copy(_knownSecrets[b]!.SecretPtr, storedSecret, 0, CryptoConstants.SecretLen);
 43
 448844            if (!CryptographicOperations.FixedTimeEquals(derivedSecret, storedSecret))
 45            {
 46                // Securely wipe the temporary copy
 3247                _cryptoProvider.MemoryZero(Marshal.UnsafeAddrOfPinnedArrayElement(storedSecret, 0),
 3248                                           CryptoConstants.SecretLen);
 3249                _cryptoProvider.MemoryZero(Marshal.UnsafeAddrOfPinnedArrayElement(derivedSecret, 0),
 3250                                           CryptoConstants.SecretLen);
 3251                return false; // Secret verification failed
 52            }
 53
 54            // Securely wipe the temporary copies
 445655            _cryptoProvider.MemoryZero(Marshal.UnsafeAddrOfPinnedArrayElement(storedSecret, 0),
 445656                                       CryptoConstants.SecretLen);
 445657            _cryptoProvider.MemoryZero(Marshal.UnsafeAddrOfPinnedArrayElement(derivedSecret, 0),
 445658                                       CryptoConstants.SecretLen);
 59        }
 60
 40061        if (_knownSecrets[bucket] != null)
 62        {
 63            // Free previous secret in this bucket if it exists
 10464            FreeSecret(_knownSecrets[bucket]!.SecretPtr);
 65        }
 66
 67        // Allocate secure memory for the new secret
 40068        var securePtr = _cryptoProvider.MemoryAlloc(CryptoConstants.SecretLen);
 69
 70        // Lock memory to prevent swapping
 40071        _cryptoProvider.MemoryLock(securePtr, CryptoConstants.SecretLen);
 72
 73        // Copy secret to secure memory
 40074        Marshal.Copy(secret, 0, securePtr, CryptoConstants.SecretLen);
 75
 76        // Store in the appropriate bucket
 40077        _knownSecrets[bucket] = new StoredSecret(index, securePtr);
 78
 40079        return true;
 80    }
 81
 82    /// <inheritdoc/>
 83    /// <exception cref="InvalidOperationException">Thrown when the secret cannot be derived</exception>
 84    public Secret DeriveOldSecret(ulong index)
 85    {
 8486        Span<byte> derivedSecret = stackalloc byte[CryptoConstants.SecretLen];
 87        // Try to find a base secret that can derive this one
 112088        for (var b = 0; b < _knownSecrets.Length; b++)
 89        {
 55690            if (_knownSecrets[b] == null)
 91                continue;
 92
 93            // Check if this secret can derive the requested index
 28894            var mask = ~((1UL << b) - 1);
 28895            if ((index & mask) != (_knownSecrets[b]!.Index & mask))
 96            {
 97                continue;
 98            }
 99
 100            // Found a base secret that can derive the requested one
 80101            var baseSecret = new byte[CryptoConstants.Sha256HashLen];
 80102            Marshal.Copy(_knownSecrets[b]!.SecretPtr, baseSecret, 0, CryptoConstants.Sha256HashLen);
 103
 80104            DeriveSecret(baseSecret, b, index, derivedSecret);
 105
 106            // Securely wipe the temporary base secret
 80107            _cryptoProvider
 80108               .MemoryZero(Marshal.UnsafeAddrOfPinnedArrayElement(baseSecret, 0), CryptoConstants.Sha256HashLen);
 109
 80110            return new Secret(derivedSecret.ToArray()); // Success
 111        }
 112
 4113        throw new InvalidOperationException($"Cannot derive secret for index {index}");
 114    }
 115
 116    /// <inheritdoc/>
 117    public void StorePerCommitmentSeed(Secret secret)
 118    {
 119        // Free existing seed if any
 0120        if (_perCommitmentSeedPtr != IntPtr.Zero)
 0121            FreeSecret(_perCommitmentSeedPtr);
 122
 123        // Allocate secure memory for the seed
 0124        _perCommitmentSeedPtr = _cryptoProvider.MemoryAlloc(CryptoConstants.SecretLen);
 0125        _cryptoProvider.MemoryLock(_perCommitmentSeedPtr, CryptoConstants.SecretLen);
 0126        Marshal.Copy(secret, 0, _perCommitmentSeedPtr, CryptoConstants.SecretLen);
 0127    }
 128
 129    /// <inheritdoc/>
 130    /// <exception cref="InvalidOperationException">Thrown when the per-commitment seed is not stored</exception>
 131    public Secret GetPerCommitmentSeed()
 132    {
 0133        if (_perCommitmentSeedPtr == IntPtr.Zero)
 0134            throw new InvalidOperationException("Per-commitment seed not stored");
 135
 0136        var seed = new byte[CryptoConstants.SecretLen];
 0137        Marshal.Copy(_perCommitmentSeedPtr, seed, 0, CryptoConstants.SecretLen);
 0138        return seed;
 139    }
 140
 141    /// <inheritdoc/>
 142    public void StoreBasepointPrivateKey(BasepointType type, PrivKey privKey)
 143    {
 144        // Free existing key if any
 0145        if (_basepointSecrets.TryGetValue(type, out var existingPtr) && existingPtr != IntPtr.Zero)
 0146            FreeSecret(existingPtr);
 147
 148        // Allocate secure memory for the private key
 0149        var securePtr = _cryptoProvider.MemoryAlloc(CryptoConstants.SecretLen);
 0150        _cryptoProvider.MemoryLock(securePtr, CryptoConstants.SecretLen);
 0151        Marshal.Copy(privKey, 0, securePtr, CryptoConstants.SecretLen);
 152
 0153        _basepointSecrets[type] = securePtr;
 0154    }
 155
 156    /// <inheritdoc/>
 157    /// <exception cref="InvalidOperationException">Thrown when the basepoint private key is not stored</exception>
 158    public PrivKey GetBasepointPrivateKey(uint keyIndex, BasepointType type)
 159    {
 0160        throw new NotImplementedException("Getting basepoint private keys is not implemented yet.");
 161    }
 162
 163    /// <inheritdoc/>
 164    public void LoadFromIndex(uint index)
 165    {
 0166        throw new NotImplementedException("Loading from index is not implemented yet.");
 167    }
 168
 169    private static int GetBucketIndex(ulong index)
 170    {
 10272171        for (var b = 0; b < 48; b++)
 172        {
 5136173            if (((index >> b) & 1) == 1)
 174            {
 432175                return b;
 176            }
 177        }
 178
 0179        return 48; // For index 0 (seed)
 180    }
 181
 182    private static void DeriveSecret(ReadOnlySpan<byte> baseSecret, int bits, ulong index, Span<byte> derivedSecret)
 183    {
 4568184        using var sha256 = new Sha256();
 185
 4568186        baseSecret.CopyTo(derivedSecret);
 187
 287040188        for (var b = bits - 1; b >= 0; b--)
 189        {
 138952190            if (((index >> b) & 1) == 0)
 191            {
 192                continue;
 193            }
 194
 69468195            derivedSecret[b / 8] ^= (byte)(1 << (b % 8));
 196
 69468197            sha256.AppendData(derivedSecret);
 69468198            sha256.GetHashAndReset(derivedSecret);
 199        }
 9136200    }
 201
 202    /// <summary>
 203    /// Securely frees a secret from memory
 204    /// </summary>
 205    private void FreeSecret(IntPtr secretPtr)
 206    {
 400207        if (secretPtr == IntPtr.Zero)
 0208            return;
 209
 210        // Wipe memory before freeing
 400211        _cryptoProvider.MemoryZero(secretPtr, CryptoConstants.Sha256HashLen);
 212
 213        // Unlock memory
 400214        _cryptoProvider.MemoryUnlock(secretPtr, CryptoConstants.Sha256HashLen);
 215
 216        // Free memory
 400217        _cryptoProvider.MemoryFree(secretPtr);
 400218    }
 219
 220    private void ReleaseUnmanagedResources()
 221    {
 222        // Free all secrets
 4800223        for (var i = 0; i < _knownSecrets.Length; i++)
 224        {
 2352225            if (_knownSecrets[i] == null)
 226                continue;
 227
 296228            FreeSecret(_knownSecrets[i]!.SecretPtr);
 296229            _knownSecrets[i] = null;
 230        }
 231
 232        // Free per-commitment seed
 48233        if (_perCommitmentSeedPtr != IntPtr.Zero)
 234        {
 0235            FreeSecret(_perCommitmentSeedPtr);
 0236            _perCommitmentSeedPtr = IntPtr.Zero;
 237        }
 238
 239        // Free basepoint secrets
 96240        foreach (var kvp in _basepointSecrets)
 241        {
 0242            if (kvp.Value != IntPtr.Zero)
 0243                FreeSecret(kvp.Value);
 244        }
 245
 48246        _basepointSecrets.Clear();
 48247    }
 248
 249    private void Dispose(bool disposing)
 250    {
 48251        ReleaseUnmanagedResources();
 48252        if (disposing)
 48253            _cryptoProvider.Dispose();
 48254    }
 255
 256    public void Dispose()
 257    {
 48258        Dispose(true);
 48259        GC.SuppressFinalize(this);
 48260    }
 261
 262    ~SecretStorageService()
 263    {
 0264        Dispose(false);
 0265    }
 266}