< Summary - Combined Code Coverage

Information
Class: NLightning.Infrastructure.Transport.Handshake.States.HandshakeState
Assembly: NLightning.Infrastructure
File(s): /home/runner/work/nlightning/nlightning/src/NLightning.Infrastructure/Transport/Handshake/States/HandshakeState.cs
Tag: 30_15166811759
Line coverage
84%
Covered lines: 118
Uncovered lines: 21
Coverable lines: 139
Total lines: 378
Line coverage: 84.8%
Branch coverage
73%
Covered branches: 59
Total branches: 80
Branch coverage: 73.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)66.67%13.691277.27%
get_RemoteStaticPublicKey()100%210%
WriteMessage(...)73.68%20.851982.76%
ReadMessage(...)76.19%23.512182.14%
ProcessPreMessages()66.67%15.361271.43%
EnqueueMessages()100%22100%
WriteE(...)100%11100%
WriteS(...)100%11100%
ReadE(...)100%22100%
ReadS(...)50%4.03487.5%
ProcessEs()100%22100%
ProcessSe()100%22100%
Split()100%11100%
DhAndMixKey(...)100%11100%
Clear()100%22100%
Dispose()50%2.02283.33%
Finalize()100%210%

File(s)

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

#LineLine coverage
 1using System.Diagnostics;
 2
 3namespace NLightning.Infrastructure.Transport.Handshake.States;
 4
 5using Common.Utils;
 6using Crypto.Functions;
 7using Crypto.Interfaces;
 8using Crypto.Primitives;
 9using Domain.Crypto.Constants;
 10using Enums;
 11using Interfaces;
 12using MessagePatterns;
 13using Protocol.Constants;
 14
 15/// <inheritdoc/>
 16/// <remarks> See <see href="https://github.com/lightning/bolts/blob/master/08-transport.md">Lightning Bolt8</see> for s
 17internal sealed class HandshakeState : IHandshakeState
 18{
 19    private const byte HANDSHAKE_VERSION = 0x00;
 420    private static readonly HandshakePattern s_handshakePattern = HandshakePattern.XK;
 21
 22    private readonly SymmetricState _state;
 23    private readonly Role _role;
 24    private readonly Role _initiator;
 25    private readonly KeyPair _s;
 9626    private readonly Queue<MessagePattern> _messagePatterns = new();
 27
 28    private readonly IEcdh _dh;
 29    private KeyPair? _e;
 30    private byte[]? _re;
 31    private byte[] _rs;
 32    private bool _turnToWrite;
 33    private bool _disposed;
 34
 035    public NBitcoin.PubKey RemoteStaticPublicKey => new(_rs);
 36
 37    /// <summary>
 38    /// Creates a new HandshakeState instance.
 39    /// </summary>
 40    /// <param name="initiator">If we are the initiator</param>
 41    /// <param name="s">Local Static Private Key</param>
 42    /// <param name="rs">Remote Static Public Key</param>
 43    /// <param name="ecdh">A specific DH Function, or null to use the <see cref="Ecdh">Protocol Default</see></param>
 44    /// <exception cref="ArgumentException"></exception>
 9645    public HandshakeState(bool initiator, ReadOnlySpan<byte> s, ReadOnlySpan<byte> rs, IEcdh? ecdh = null)
 46    {
 9647        _dh = ecdh ?? new Ecdh();
 48
 9649        if (s.IsEmpty)
 50        {
 051            throw new ArgumentException("Local static private key required, but not provided.", nameof(s));
 52        }
 53
 9654        if (s.Length != CryptoConstants.PRIVKEY_LEN)
 55        {
 056            throw new ArgumentException("Invalid local static private key.", nameof(s));
 57        }
 58
 9659        if (rs.IsEmpty)
 60        {
 061            throw new ArgumentException("Remote static public key required, but not provided.", nameof(rs));
 62        }
 63
 9664        if (rs.Length != CryptoConstants.PUBKEY_LEN)
 65        {
 066            throw new ArgumentException("Invalid remote static public key.", nameof(rs));
 67        }
 68
 9669        _state = new SymmetricState(ProtocolConstants.NAME);
 9670        _state.MixHash(ProtocolConstants.PROLOGUE);
 71
 9672        _role = initiator ? Role.ALICE : Role.BOB;
 9673        _initiator = Role.ALICE;
 9674        _turnToWrite = initiator;
 9675        _s = _dh.GenerateKeyPair(s);
 9676        _rs = rs.ToArray();
 77
 9678        ProcessPreMessages();
 9679        EnqueueMessages();
 9680    }
 81
 82    /// <inheritdoc/>
 83    /// <exception cref="ObjectDisposedException">Thrown if the current instance has already been disposed.</exception>
 84    /// <exception cref="InvalidOperationException">Thrown if the call to <see cref="ReadMessage"/> was expected or the 
 85    /// <exception cref="ArgumentException">Thrown if the output was greater than <see cref="ProtocolConstants.MAX_MESSA
 86    public (int, byte[]?, Encryption.Transport?) WriteMessage(ReadOnlySpan<byte> payload, Span<byte> messageBuffer)
 87    {
 9288        ExceptionUtils.ThrowIfDisposed(_disposed, nameof(HandshakeState));
 89
 9290        if (_messagePatterns.Count == 0)
 91        {
 092            throw new InvalidOperationException("Cannot call WriteMessage after the handshake has already been completed
 93        }
 94
 9295        var overhead = _messagePatterns.Peek().Overhead(CryptoConstants.PUBKEY_LEN, _state.HasKeys());
 9296        var ciphertextSize = payload.Length + overhead;
 97
 9298        if (ciphertextSize > ProtocolConstants.MAX_MESSAGE_LENGTH)
 99        {
 0100            throw new ArgumentException($"Noise message must be less than or equal to {ProtocolConstants.MAX_MESSAGE_LEN
 101        }
 102
 92103        if (ciphertextSize > messageBuffer.Length)
 104        {
 0105            throw new ArgumentException("Message buffer does not have enough space to hold the ciphertext.");
 106        }
 107
 92108        if (!_turnToWrite)
 109        {
 0110            throw new InvalidOperationException("Unexpected call to WriteMessage (should be ReadMessage).");
 111        }
 112
 92113        var next = _messagePatterns.Dequeue();
 92114        var messageBufferLength = messageBuffer.Length;
 115
 116        // write version to message buffer
 92117        messageBuffer[0] = HANDSHAKE_VERSION;
 118
 552119        foreach (var token in next.Tokens)
 120        {
 121            switch (token)
 122            {
 152123                case Token.E: messageBuffer = WriteE(messageBuffer); break;
 32124                case Token.S: messageBuffer = WriteS(messageBuffer); break;
 72125                case Token.EE: DhAndMixKey(_e, _re); break;
 80126                case Token.ES: ProcessEs(); break;
 32127                case Token.SE: ProcessSe(); break;
 0128                case Token.SS: DhAndMixKey(_s, _rs); break;
 129            }
 130        }
 131
 92132        var bytesWritten = _state.EncryptAndHash(payload, messageBuffer);
 92133        var size = messageBufferLength - messageBuffer.Length + bytesWritten;
 134
 135        Debug.Assert(ciphertextSize == size);
 136
 92137        byte[]? handshakeHash = null;
 92138        Encryption.Transport? transport = null;
 139
 92140        if (_messagePatterns.Count == 0)
 141        {
 16142            (handshakeHash, transport) = Split();
 143        }
 144
 92145        _turnToWrite = false;
 92146        return (ciphertextSize, handshakeHash, transport);
 147    }
 148
 149    /// <inheritdoc/>
 150    /// <exception cref="ObjectDisposedException">Thrown if the current instance has already been disposed.</exception>
 151    /// <exception cref="InvalidOperationException">Thrown if the call to <see cref="WriteMessage"/> was expected or the
 152    /// <exception cref="ArgumentException">Thrown if the message was greater than <see cref="ProtocolConstants.MAX_MESS
 153    /// <exception cref="System.Security.Cryptography.CryptographicException">Thrown if the decryption of the message ha
 154    public (int, byte[]?, Encryption.Transport?) ReadMessage(ReadOnlySpan<byte> message, Span<byte> payloadBuffer)
 155    {
 124156        ExceptionUtils.ThrowIfDisposed(_disposed, nameof(HandshakeState));
 157
 124158        if (_messagePatterns.Count == 0)
 159        {
 0160            throw new InvalidOperationException("Cannot call WriteMessage after the handshake has already been completed
 161        }
 162
 124163        var overhead = _messagePatterns.Peek().Overhead(CryptoConstants.PUBKEY_LEN, _state.HasKeys());
 124164        var plaintextSize = message.Length - overhead;
 165
 124166        if (message.Length > ProtocolConstants.MAX_MESSAGE_LENGTH)
 167        {
 0168            throw new ArgumentException($"Noise message must be less than or equal to {ProtocolConstants.MAX_MESSAGE_LEN
 169        }
 170
 124171        if (message.Length != overhead)
 172        {
 12173            throw new ArgumentException($"Noise message must be equal to {overhead} bytes in length.");
 174        }
 175
 112176        if (plaintextSize > payloadBuffer.Length)
 177        {
 0178            throw new ArgumentException("Payload buffer does not have enough space to hold the plaintext.");
 179        }
 180
 112181        if (_turnToWrite)
 182        {
 0183            throw new InvalidOperationException("Unexpected call to ReadMessage (should be WriteMessage).");
 184        }
 185
 112186        var next = _messagePatterns.Dequeue();
 624187        foreach (var token in next.Tokens)
 188        {
 189            switch (token)
 190            {
 160191                case Token.E: message = ReadE(message); break;
 52192                case Token.S: message = ReadS(message); break;
 52193                case Token.EE: DhAndMixKey(_e, _re); break;
 92194                case Token.ES: ProcessEs(); break;
 44195                case Token.SE: ProcessSe(); break;
 0196                case Token.SS: DhAndMixKey(_s, _rs); break;
 197            }
 198        }
 199
 88200        var bytesRead = _state.DecryptAndHash(message, payloadBuffer);
 201        Debug.Assert(bytesRead == plaintextSize);
 202
 76203        byte[]? handshakeHash = null;
 76204        Encryption.Transport? transport = null;
 205
 76206        if (_messagePatterns.Count == 0)
 207        {
 16208            (handshakeHash, transport) = Split();
 209        }
 210
 76211        _turnToWrite = true;
 76212        return (plaintextSize, handshakeHash, transport);
 213    }
 214
 215    private void ProcessPreMessages()
 216    {
 192217        foreach (var token in s_handshakePattern.Initiator.Tokens)
 218        {
 0219            if (token == Token.S)
 220            {
 0221                _state.MixHash(_role == Role.ALICE ? _s.PublicKeyBytes : _rs);
 222            }
 223        }
 224
 384225        foreach (var token in s_handshakePattern.Responder.Tokens)
 226        {
 96227            if (token == Token.S)
 228            {
 96229                _state.MixHash(_role == Role.ALICE ? _rs : _s.PublicKeyBytes);
 230            }
 231        }
 96232    }
 233
 234    private void EnqueueMessages()
 235    {
 768236        foreach (var pattern in s_handshakePattern.Patterns)
 237        {
 288238            _messagePatterns.Enqueue(pattern);
 239        }
 96240    }
 241
 242    private Span<byte> WriteE(Span<byte> buffer)
 243    {
 244        Debug.Assert(_e == null);
 245
 76246        _e = _dh.GenerateKeyPair();
 247        // Start from position 1, since we need our version there
 76248        _e.PublicKeyBytes.CopyTo(buffer[1..]);
 76249        _state.MixHash(_e.PublicKeyBytes);
 250
 251        // Don't forget to add our version length to the resulting Span
 76252        return buffer[(_e.PublicKeyBytes.Length + 1)..];
 253    }
 254
 255    private Span<byte> WriteS(Span<byte> buffer)
 256    {
 257        Debug.Assert(_s != null);
 258
 259        // Start from position 1, since we need our version there
 16260        var bytesWritten = _state.EncryptAndHash(_s.PublicKeyBytes, buffer[1..]);
 261
 262        // Don't forget to add our version length to the resulting Span
 16263        return buffer[(bytesWritten + 1)..];
 264    }
 265
 266    private ReadOnlySpan<byte> ReadE(ReadOnlySpan<byte> buffer)
 267    {
 268        Debug.Assert(_re == null);
 269
 270        // Check version
 84271        if (buffer[0] != HANDSHAKE_VERSION)
 272        {
 8273            throw new InvalidOperationException("Invalid handshake version.");
 274        }
 76275        buffer = buffer[1..];
 276
 277        // Skip the byte from the version and get all bytes from pubkey
 76278        _re = buffer[..CryptoConstants.PUBKEY_LEN].ToArray();
 76279        _state.MixHash(_re);
 280
 76281        return buffer[_re.Length..];
 282    }
 283
 284    private ReadOnlySpan<byte> ReadS(ReadOnlySpan<byte> message)
 285    {
 286        // Check version
 28287        if (message[0] != HANDSHAKE_VERSION)
 288        {
 0289            throw new InvalidOperationException("Invalid handshake version.");
 290        }
 28291        message = message[1..];
 292
 28293        var length = _state.HasKeys() ? CryptoConstants.PUBKEY_LEN + CryptoConstants.CHACHA20_POLY1305_TAG_LEN : CryptoC
 28294        var temp = message[..length];
 295
 28296        _rs = new byte[CryptoConstants.PUBKEY_LEN];
 28297        _state.DecryptAndHash(temp, _rs);
 298
 24299        return message[length..];
 300    }
 301
 302    private void ProcessEs()
 303    {
 88304        if (_role == Role.ALICE)
 305        {
 40306            DhAndMixKey(_e, _rs);
 307        }
 308        else
 309        {
 48310            DhAndMixKey(_s, _re);
 311        }
 44312    }
 313
 314    private void ProcessSe()
 315    {
 40316        if (_role == Role.ALICE)
 317        {
 16318            DhAndMixKey(_s, _re);
 319        }
 320        else
 321        {
 24322            DhAndMixKey(_e, _rs);
 323        }
 20324    }
 325
 326    private (byte[], Encryption.Transport) Split()
 327    {
 32328        var (c1, c2) = _state.Split();
 329
 32330        var handshakeHash = _state.GetHandshakeHash();
 32331        var transport = new Encryption.Transport(_role == _initiator, c1, c2);
 332
 32333        Clear();
 334
 32335        return (handshakeHash, transport);
 336    }
 337
 338    private void DhAndMixKey(KeyPair? keyPair, ReadOnlySpan<byte> publicKey)
 339    {
 340        Debug.Assert(keyPair != null);
 341        Debug.Assert(!publicKey.IsEmpty);
 342
 192343        Span<byte> sharedKey = stackalloc byte[CryptoConstants.PRIVKEY_LEN];
 192344        _dh.SecP256K1Dh(keyPair.PrivateKey, publicKey, sharedKey);
 180345        _state.MixKey(sharedKey);
 180346    }
 347
 348    private void Clear()
 349    {
 128350        _state.Dispose();
 128351        _e?.Dispose();
 128352        _s.Dispose();
 128353    }
 354
 355    private enum Role
 356    {
 357        ALICE,
 358        BOB
 359    }
 360
 361    public void Dispose()
 362    {
 96363        if (_disposed)
 364        {
 0365            return;
 366        }
 367
 96368        Clear();
 96369        GC.SuppressFinalize(this);
 370
 96371        _disposed = true;
 96372    }
 373
 374    ~HandshakeState()
 375    {
 0376        Dispose();
 0377    }
 378}