< Summary - Combined Code Coverage

Information
Class: NLightning.Infrastructure.Transport.Handshake.States.CipherState
Assembly: NLightning.Infrastructure
File(s): /home/runner/work/nlightning/nlightning/src/NLightning.Infrastructure/Transport/Handshake/States/CipherState.cs
Tag: 30_15166811759
Line coverage
94%
Covered lines: 54
Uncovered lines: 3
Coverable lines: 57
Total lines: 190
Line coverage: 94.7%
Branch coverage
83%
Covered branches: 25
Total branches: 30
Branch coverage: 83.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
InitializeKeyAndChainingKey(...)100%44100%
HasKeys()100%22100%
SetNonce(...)100%11100%
EncryptWithAd(...)100%44100%
DecryptWithAd(...)75%4.02488.89%
Encrypt(...)100%22100%
Decrypt(...)100%22100%
Rekey()50%6.03690.91%
Dispose()83.33%6.07687.5%

File(s)

/home/runner/work/nlightning/nlightning/src/NLightning.Infrastructure/Transport/Handshake/States/CipherState.cs

#LineLine coverage
 1using System.Diagnostics;
 2
 3namespace NLightning.Infrastructure.Transport.Handshake.States;
 4
 5using Common.Utils;
 6using Crypto.Ciphers;
 7using Crypto.Functions;
 8using Crypto.Primitives;
 9using Domain.Crypto.Constants;
 10
 11/// <summary>
 12/// A CipherState can encrypt and decrypt data based on its variables k
 13/// (a cipher key of 32 bytes) and n (an 8-byte unsigned integer nonce).
 14/// </summary>
 15internal sealed class CipherState : IDisposable
 16{
 17    private const ulong MAX_NONCE = 1000;
 18
 19219    private readonly ChaCha20Poly1305 _cipher = new();
 19220    private readonly Hkdf _hkdf = new();
 21
 22    private SecureMemory? _ck;
 23    private SecureMemory? _k;
 24    private ulong _n;
 25    private bool _disposed;
 26
 27    /// <summary>
 28    /// Sets _k = key. Sets _ck = ck. Sets _n = 0.
 29    /// </summary>
 30    public void InitializeKeyAndChainingKey(ReadOnlySpan<byte> key, ReadOnlySpan<byte> chainingKey)
 31    {
 26432        ExceptionUtils.ThrowIfDisposed(_disposed, nameof(CipherState));
 33
 34        Debug.Assert(key.Length == CryptoConstants.PRIVKEY_LEN);
 35        Debug.Assert(chainingKey.Length == CryptoConstants.PRIVKEY_LEN);
 36
 26437        _k ??= new SecureMemory(CryptoConstants.PRIVKEY_LEN);
 26438        key.CopyTo(_k);
 39
 26440        _ck ??= new SecureMemory(CryptoConstants.PRIVKEY_LEN);
 26441        chainingKey.CopyTo(_ck);
 42
 26443        _n = 0;
 26444    }
 45
 46    /// <summary>
 47    /// Returns true if k and ck are non-empty, false otherwise.
 48    /// </summary>
 49    public bool HasKeys()
 50    {
 1630851        ExceptionUtils.ThrowIfDisposed(_disposed, nameof(CipherState));
 52
 1630853        return _k is not null && _ck is not null;
 54    }
 55
 56    /// <summary>
 57    /// Sets n = nonce. This function is used for handling out-of-order transport messages.
 58    /// </summary>
 59    public void SetNonce(ulong nonce)
 60    {
 1661        ExceptionUtils.ThrowIfDisposed(_disposed, nameof(CipherState));
 62
 1663        _n = nonce;
 1664    }
 65
 66    /// <summary>
 67    /// If k is non-empty returns ENCRYPT(k, n++, ad, plaintext).
 68    /// Otherwise, copies the plaintext to the ciphertext parameter
 69    /// and returns the length of the plaintext.
 70    /// </summary>
 71    public int EncryptWithAd(ReadOnlySpan<byte> ad, ReadOnlySpan<byte> plaintext, Span<byte> ciphertext)
 72    {
 12873        ExceptionUtils.ThrowIfDisposed(_disposed, nameof(CipherState));
 74
 12875        if (_n == MAX_NONCE)
 76        {
 477            throw new OverflowException("Nonce has reached its maximum value.");
 78        }
 79
 12480        if (_k != null)
 81        {
 12082            return _cipher.Encrypt(_k, _n++, ad, plaintext, ciphertext);
 83        }
 84
 485        plaintext.CopyTo(ciphertext);
 486        return plaintext.Length;
 87    }
 88
 89    /// <summary>
 90    /// If k is non-empty returns DECRYPT(k, n++, ad, ciphertext).
 91    /// Otherwise, copies the ciphertext to the plaintext parameter and returns
 92    /// the length of the ciphertext. If an authentication failure occurs
 93    /// then n is not incremented and an error is signaled to the caller.
 94    /// </summary>
 95    public int DecryptWithAd(ReadOnlySpan<byte> ad, ReadOnlySpan<byte> ciphertext, Span<byte> plaintext)
 96    {
 12497        ExceptionUtils.ThrowIfDisposed(_disposed, nameof(CipherState));
 98
 99        // If nonce reaches its maximum value, rekey
 124100        if (_n == MAX_NONCE)
 101        {
 0102            throw new OverflowException("Nonce has reached its maximum value.");
 103        }
 104
 124105        if (_k == null)
 106        {
 4107            ciphertext.CopyTo(plaintext);
 4108            return ciphertext.Length;
 109        }
 110
 120111        var bytesRead = _cipher.Decrypt(_k, _n, ad, ciphertext, plaintext);
 104112        ++_n;
 113
 104114        return bytesRead;
 115    }
 116
 117    /// <summary>
 118    /// Returns ENCRYPT(k, n, null, plaintext).
 119    /// </summary>
 120    /// <param name="plaintext">Bytes to be encrypted</param>
 121    /// <param name="ciphertext">Encrypted bytes</param>
 122    /// <returns>Number of bytes written to ciphertext</returns>
 123    public int Encrypt(ReadOnlySpan<byte> plaintext, Span<byte> ciphertext)
 124    {
 8024125        ExceptionUtils.ThrowIfDisposed(_disposed, nameof(CipherState));
 126
 8024127        if (_n == MAX_NONCE)
 128        {
 12129            Rekey();
 130        }
 131
 8024132        return _cipher.Encrypt(_k!, _n++, null, plaintext, ciphertext);
 133    }
 134
 135    /// <summary>
 136    /// Returns DECRYPT(k, n, null, ciphertext).
 137    /// </summary>
 138    /// <param name="ciphertext">Bytes to be decrypted</param>
 139    /// <param name="plaintext">Decrypted bytes</param>
 140    /// <returns>Number of bytes written to plaintext</returns>
 141    public int Decrypt(ReadOnlySpan<byte> ciphertext, Span<byte> plaintext)
 142    {
 8016143        ExceptionUtils.ThrowIfDisposed(_disposed, nameof(CipherState));
 144
 8016145        if (_n == MAX_NONCE)
 146        {
 8147            Rekey();
 148        }
 8016149        return _cipher.Decrypt(_k!, _n++, null, ciphertext, plaintext);
 150    }
 151
 152    /// <summary>
 153    /// Sets k = REKEY(k).
 154    /// </summary>
 155    public void Rekey()
 156    {
 20157        ExceptionUtils.ThrowIfDisposed(_disposed, nameof(CipherState));
 158
 20159        if (!HasKeys())
 160        {
 0161            throw new NullReferenceException("Keys are missing");
 162        }
 163
 20164        _n = 0;
 165
 20166        Span<byte> key = stackalloc byte[CryptoConstants.PRIVKEY_LEN * 2];
 20167        _hkdf.ExtractAndExpand2(_ck!, _k!, key);
 168
 20169        _ck ??= new SecureMemory(CryptoConstants.PRIVKEY_LEN);
 20170        key[..CryptoConstants.PRIVKEY_LEN].CopyTo(_ck);
 171
 20172        _k ??= new SecureMemory(CryptoConstants.PRIVKEY_LEN);
 20173        key[CryptoConstants.PRIVKEY_LEN..].CopyTo(_k);
 20174    }
 175
 176    public void Dispose()
 177    {
 144178        if (_disposed)
 179        {
 0180            return;
 181        }
 182
 144183        _ck?.Dispose();
 144184        _k?.Dispose();
 144185        _hkdf.Dispose();
 144186        _cipher.Dispose();
 187
 144188        _disposed = true;
 144189    }
 190}