public static EcdhRatchetStep InitializeServer(IKeyDerivation kdf, IDigest digest, IKeyAgreement previousKeyPair, byte[] rootKey, ArraySegment <byte> remotePublicKey, IKeyAgreement keyPair, byte[] receiveHeaderKey, byte[] sendHeaderKey) { if (receiveHeaderKey.Length != 32 || sendHeaderKey.Length != 32) { throw new InvalidOperationException("Keys need to be 32 bytes."); } Log.Verbose($"--Initialize ECDH Ratchet"); Log.Verbose($"Root Key: {Log.ShowBytes(rootKey)}"); Log.Verbose($"Prev ECDH Private: ({Log.ShowBytes(previousKeyPair.GetPublicKey())})"); Log.Verbose($"ECDH Public: {Log.ShowBytes(remotePublicKey)}"); Log.Verbose($"Curr ECDH Private: ({Log.ShowBytes(keyPair.GetPublicKey())})"); var e = new EcdhRatchetStep { EcdhKey = keyPair, ReceiveHeaderKey = receiveHeaderKey, SendHeaderKey = sendHeaderKey }; // receive chain Log.Verbose(" --Receiving Chain"); var rcderived = previousKeyPair.DeriveKey(remotePublicKey); rcderived = digest.ComputeDigest(rcderived); Log.Verbose($" C Input Key: {Log.ShowBytes(rootKey)}"); Log.Verbose($" C Key Info: {Log.ShowBytes(rcderived)}"); var rckeys = kdf.GenerateKeys(rcderived, rootKey, 3, 32); Log.Verbose($" C Key Out 0: {Log.ShowBytes(rckeys[0])}"); Log.Verbose($" C Key Out 1: {Log.ShowBytes(rckeys[1])}"); Log.Verbose($" C Key Out 2: {Log.ShowBytes(rckeys[2])}"); rootKey = rckeys[0]; e.ReceivingChain.Initialize(rckeys[1]); e.NextReceiveHeaderKey = rckeys[2]; // send chain Log.Verbose(" --Sending Chain"); var scderived = keyPair.DeriveKey(remotePublicKey); scderived = digest.ComputeDigest(scderived); Log.Verbose($" C Input Key: {Log.ShowBytes(rootKey)}"); Log.Verbose($" C Key Info: {Log.ShowBytes(scderived)}"); var sckeys = kdf.GenerateKeys(scderived, rootKey, 3, 32); Log.Verbose($" C Key Out 0: {Log.ShowBytes(sckeys[0])}"); Log.Verbose($" C Key Out 1: {Log.ShowBytes(sckeys[1])}"); Log.Verbose($" C Key Out 2: {Log.ShowBytes(sckeys[2])}"); rootKey = sckeys[0]; e.SendingChain.Initialize(sckeys[1]); e.NextSendHeaderKey = sckeys[2]; // next root key Log.Verbose($"Next Root Key: ({Log.ShowBytes(rootKey)})"); e.NextRootKey = rootKey; return(e); }
public static EcdhRatchetStep[] InitializeClient(IKeyDerivation kdf, IDigest digest, byte[] rootKey, ArraySegment <byte> remotePublicKey0, ArraySegment <byte> remotePublicKey1, IKeyAgreement keyPair, byte[] receiveHeaderKey, byte[] sendHeaderKey, IKeyAgreement nextKeyPair) { if (receiveHeaderKey.Length != 32 || sendHeaderKey.Length != 32) { throw new InvalidOperationException("Keys need to be 32 bytes."); } Log.Verbose($"--Initialize ECDH Ratchet CLIENT"); Log.Verbose($"Root Key: {Log.ShowBytes(rootKey)}"); Log.Verbose($"ECDH Public 0: {Log.ShowBytes(remotePublicKey0)}"); Log.Verbose($"ECDH Public 1: {Log.ShowBytes(remotePublicKey1)}"); Log.Verbose($"ECDH Private: ({Log.ShowBytes(keyPair.GetPublicKey())})"); var e0 = new EcdhRatchetStep { EcdhKey = keyPair, SendHeaderKey = sendHeaderKey }; // receive chain doesn't exist Log.Verbose(" --Receiving Chain"); // send chain Log.Verbose(" --Sending Chain"); var scderived = keyPair.DeriveKey(remotePublicKey0); scderived = digest.ComputeDigest(scderived); Log.Verbose($" C Input Key: {Log.ShowBytes(rootKey)}"); Log.Verbose($" C Key Info: {Log.ShowBytes(scderived)}"); var sckeys = kdf.GenerateKeys(scderived, rootKey, 3, 32); Log.Verbose($" C Key Out 0: {Log.ShowBytes(sckeys[0])}"); Log.Verbose($" C Key Out 1: {Log.ShowBytes(sckeys[1])}"); Log.Verbose($" C Key Out 2: {Log.ShowBytes(sckeys[2])}"); rootKey = sckeys[0]; e0.SendingChain.Initialize(sckeys[1]); var nextSendHeaderKey = sckeys[2]; var e1 = InitializeServer(kdf, digest, keyPair, rootKey, remotePublicKey1, nextKeyPair, receiveHeaderKey, nextSendHeaderKey); return(new[] { e0, e1 }); }
public static EcdhRatchetStep Create(IKeyAgreement EcdhKey, byte[] NextRootKey, int receivingGeneration, byte[] receivingHeaderKey, byte[] receivingNextHeaderKey, byte[] receivingChainKey, int sendingGeneration, byte[] sendingHeaderKey, byte[] sendingNextHeaderKey, byte[] sendingChainKey) { if (NextRootKey != null && NextRootKey.Length != 32) { throw new InvalidOperationException("The next root key size needs to be 32 bytes"); } if (receivingHeaderKey != null && receivingHeaderKey.Length != 32) { throw new InvalidOperationException("The receiving header key size needs to be 32 bytes"); } if (receivingNextHeaderKey != null && receivingNextHeaderKey.Length != 32) { throw new InvalidOperationException("The next receiving header key size needs to be 32 bytes"); } if (receivingChainKey != null && receivingChainKey.Length != 32) { throw new InvalidOperationException("The receiving chain key size needs to be 32 bytes"); } if (sendingHeaderKey != null && sendingHeaderKey.Length != 32) { throw new InvalidOperationException("The sending header key size needs to be 32 bytes"); } if (sendingNextHeaderKey != null && sendingNextHeaderKey.Length != 32) { throw new InvalidOperationException("The next sending header key size needs to be 32 bytes"); } if (sendingChainKey != null && sendingChainKey.Length != 32) { throw new InvalidOperationException("The sending chain key size needs to be 32 bytes"); } var step = new EcdhRatchetStep() { EcdhKey = EcdhKey, NextRootKey = NextRootKey, ReceiveHeaderKey = receivingHeaderKey, NextReceiveHeaderKey = receivingNextHeaderKey, SendHeaderKey = sendingHeaderKey, NextSendHeaderKey = sendingNextHeaderKey }; step.ReceivingChain.Initialize(receivingChainKey); step.ReceivingChain.Generation = receivingGeneration; step.SendingChain.Initialize(sendingChainKey); step.SendingChain.Generation = sendingGeneration; return(step); }
private void ReceiveFirstMessage(State state, byte[] payload, EcdhRatchetStep ecdhRatchetStep) { if (!(state is ServerState serverState)) { throw new InvalidOperationException("Only the server can receive the first client message."); } if (serverState.FirstReceiveHeaderKey == null) { throw new InvalidOperationException("Invalid message received"); } // check the first receive header key mac var payloadExceptMac = new ArraySegment <byte>(payload, 0, payload.Length - MacSize); var maciv = new ArraySegment <byte>(payload, 0, 16); var mac = new ArraySegment <byte>(payload, payload.Length - MacSize, MacSize); var Mac = new Poly(AesFactory); Mac.Init(serverState.FirstReceiveHeaderKey, maciv, MacSize); Mac.Process(payloadExceptMac); var compareMac = Mac.Compute(); if (!mac.Matches(compareMac)) { throw new InvalidOperationException("The first received message does not have the correct MAC"); } var data = DeconstructMessage(state, payload, serverState.FirstReceiveHeaderKey, ecdhRatchetStep, false); if (data.Length < InitializationNonceSize || !serverState.NextInitializationNonce.Matches(data, 0, InitializationNonceSize)) { throw new InvalidOperationException("The first received message did not contain the correct payload"); } serverState.FirstSendHeaderKey = null; serverState.FirstReceiveHeaderKey = null; serverState.LocalEcdhRatchetStep0 = null; serverState.LocalEcdhRatchetStep1 = null; serverState.RootKey = null; }
private static void ShredRatchet(EcdhRatchetStep r) { (r.EcdhKey as IDisposable)?.Dispose(); r.EcdhKey = null; r.NextReceiveHeaderKey?.Shred(); r.NextReceiveHeaderKey = null; r.NextRootKey?.Shred(); r.NextRootKey = null; r.NextSendHeaderKey?.Shred(); r.NextSendHeaderKey = null; r.ReceiveHeaderKey?.Shred(); r.ReceiveHeaderKey = null; r.SendHeaderKey?.Shred(); r.SendHeaderKey = null; r.ReceivingChain.ChainKey?.Shred(); r.ReceivingChain.ChainKey = null; r.ReceivingChain.OldChainKey?.Shred(); r.ReceivingChain.OldChainKey = null; r.SendingChain.ChainKey?.Shred(); r.SendingChain.ChainKey = null; r.SendingChain.OldChainKey?.Shred(); r.SendingChain.OldChainKey = null; }
private byte[] ProcessInitialization(State state, byte[] dataReceived, byte[] headerKeyUsed, EcdhRatchetStep ecdhRatchetStep, bool usedApplicationHeaderKey) { byte[] sendback; if (Configuration.IsClient) { Log.Verbose("\n\n###CLIENT"); var clientState = (ClientState)state; if (dataReceived == null) { // step 1: send first init request from client sendback = SendInitializationRequest(state); } else { if (usedApplicationHeaderKey) { if (clientState.Ratchets.Count == 0 && clientState.LocalEcdhForInit != null) { // step 2: init response from server ReceiveInitializationResponse(clientState, dataReceived); sendback = SendFirstClientMessage(clientState); } else { throw new InvalidOperationException("Unexpected initialization message"); } } else { // step 3: receive first message from server ReceiveFirstResponse(clientState, dataReceived, headerKeyUsed, ecdhRatchetStep); // initialization completed successfully. sendback = null; } } } else { Log.Verbose("\n\n###SERVER"); var serverState = (ServerState)state; if (dataReceived == null) { throw new InvalidOperationException("Only the client can send initialization without having received a response first"); } if (usedApplicationHeaderKey) { // step 1: client init request (ArraySegment <byte> initializationNonce, ArraySegment <byte> remoteEcdhForInit) = ReceiveInitializationRequest(serverState, dataReceived); sendback = SendInitializationResponse(serverState, initializationNonce, remoteEcdhForInit); } else { // step 2: first message from client ReceiveFirstMessage(serverState, dataReceived, ecdhRatchetStep); sendback = SendFirstResponse(serverState); } } if (sendback != null && sendback.Length > Configuration.MaximumMessageSize) { throw new InvalidOperationException("The MTU is too small"); } return(sendback); }
private byte[] DeconstructMessage(State state, byte[] payload, byte[] headerKey, EcdhRatchetStep ratchetUsed, bool usedNextHeaderKey) { if (state == null) { throw new ArgumentNullException(nameof(state)); } if (payload == null) { throw new ArgumentNullException(nameof(payload)); } if (headerKey == null) { throw new ArgumentNullException(nameof(headerKey)); } var messageSize = payload.Length; var encryptedNonce = new ArraySegment <byte>(payload, 0, NonceSize); // decrypt the nonce var headerEncryptionNonce = new ArraySegment <byte>(payload, payload.Length - MacSize - HeaderIVSize, HeaderIVSize); var hcipher = new AesCtrMode(GetHeaderKeyCipher(headerKey), headerEncryptionNonce); var decryptedNonce = hcipher.Process(encryptedNonce); CheckNonce(headerEncryptionNonce); // get the ecdh bit var hasEcdh = (decryptedNonce[0] & 0b1000_0000) != 0; decryptedNonce[0] &= 0b0111_1111; // extract ecdh if needed var step = BigEndianBitConverter.ToInt32(decryptedNonce); if (hasEcdh) { var clientEcdhPublic = new ArraySegment <byte>(hcipher.Process(new ArraySegment <byte>(payload, NonceSize, EcNumSize))); if (ratchetUsed == null) { // an override header key was used. // this means we have to initialize the ratchet if (!(state is ServerState serverState)) { throw new InvalidOperationException("Only the server can initialize a ratchet."); } ratchetUsed = EcdhRatchetStep.InitializeServer(KeyDerivation, Digest, serverState.LocalEcdhRatchetStep0, serverState.RootKey, clientEcdhPublic, serverState.LocalEcdhRatchetStep1, serverState.FirstReceiveHeaderKey, serverState.FirstSendHeaderKey); serverState.Ratchets.Add(ratchetUsed); } else { if (usedNextHeaderKey) { // perform ecdh ratchet IKeyAgreement newEcdh = KeyAgreementFactory.GenerateNew(); // this is the hottest line in the deconstruct process: EcdhRatchetStep newRatchet = ratchetUsed.Ratchet(KeyDerivation, Digest, clientEcdhPublic, newEcdh); state.Ratchets.Add(newRatchet); ratchetUsed = newRatchet; } } } // get the inner payload key from the receive chain if (ratchetUsed == null) { throw new InvalidOperationException("An override header key was used but the message did not contain ECDH parameters"); } (var key, var _) = ratchetUsed.ReceivingChain.RatchetForReceiving(KeyDerivation, step); CheckNonce(key); // get the encrypted payload var payloadOffset = hasEcdh ? NonceSize + EcNumSize : NonceSize; var encryptedPayload = new ArraySegment <byte>(payload, payloadOffset, messageSize - payloadOffset - MacSize); // decrypt the inner payload var icipher = new AesCtrMode(AesFactory.GetAes(true, key), decryptedNonce); var decryptedInnerPayload = icipher.Process(encryptedPayload); return(decryptedInnerPayload); }
private (byte[] headerKeyUsed, EcdhRatchetStep ratchetUsed, bool usedNextHeaderKey, bool usedApplicationKey) InterpretMessageMAC(State state, byte[] payload, byte[] overrideHeaderKey = null) { // get some basic parts var messageSize = payload.Length; var payloadExceptMac = new ArraySegment <byte>(payload, 0, messageSize - MacSize); var mac = new ArraySegment <byte>(payload, messageSize - MacSize, MacSize); // find the header key by checking the mac var maciv = new byte[16]; Array.Copy(payload, maciv, 16); var Mac = new Poly(AesFactory); var usedNextHeaderKey = false; var usedApplicationHeaderKey = false; byte[] headerKey = null; EcdhRatchetStep ratchetUsed = null; if (overrideHeaderKey != null) { headerKey = overrideHeaderKey; Mac.Init(headerKey, maciv, MacSize); Mac.Process(payloadExceptMac); var compareMac = Mac.Compute(); if (!mac.Matches(compareMac)) { throw new InvalidOperationException("Could not decrypt the incoming message with given header key"); } } else { // if we are initialized check the mac using ratchet receive header keys. if (state.Ratchets != null && !state.Ratchets.IsEmpty) { foreach (EcdhRatchetStep ratchet in state.Ratchets.Enumerate()) { headerKey = ratchet.ReceiveHeaderKey; Mac.Init(headerKey, maciv, MacSize); Mac.Process(payloadExceptMac); var compareMac = Mac.Compute(); if (mac.Matches(compareMac)) { ratchetUsed = ratchet; break; } else if (ratchet.NextReceiveHeaderKey != null) { headerKey = ratchet.NextReceiveHeaderKey; Mac.Init(headerKey, maciv, MacSize); Mac.Process(payloadExceptMac); compareMac = Mac.Compute(); if (mac.Matches(compareMac)) { usedNextHeaderKey = true; ratchetUsed = ratchet; break; } } } } if (ratchetUsed == null) { // we're either not initialized or this is an initialization message. // To determine that we mac using the application key headerKey = Configuration.ApplicationKey; Mac.Init(headerKey, maciv, MacSize); Mac.Process(payloadExceptMac); var compareMac = Mac.Compute(); if (mac.Matches(compareMac)) { usedApplicationHeaderKey = true; } else { headerKey = null; } } } return(headerKey, ratchetUsed, usedNextHeaderKey, usedApplicationHeaderKey); }
private byte[] ConstructMessage(ArraySegment <byte> message, bool includeEcdh, EcdhRatchetStep step) { if (message == null) { throw new ArgumentNullException(nameof(message)); } // message format: // <nonce (4)>, <payload, padding>, mac(12) // <nonce (4), ecdh (32)>, <payload, padding>, mac(12) // get the payload key and nonce (var payloadKey, var messageNumber) = step.SendingChain.RatchetForSending(KeyDerivation); var nonce = BigEndianBitConverter.GetBytes(messageNumber); // make sure the first bit is not set as we use that bit to indicate // the presence of new ECDH parameters if ((nonce[0] & 0b1000_0000) != 0) { throw new InvalidOperationException($"The message number is too big. Cannot encrypt more than {((uint)1 << 31) - 1} messages without exchanging keys"); } // calculate some sizes var headerSize = NonceSize + (includeEcdh ? EcNumSize : 0); var overhead = headerSize + MacSize; var messageSize = message.Count; var maxMessageSize = Configuration.MaximumMessageSize - overhead; var minPayloadSize = Configuration.MinimumMessageSize - overhead; // build the payload: <payload, padding> ArraySegment <byte> payload; if (messageSize < minPayloadSize) { payload = new ArraySegment <byte>(new byte[minPayloadSize]); Array.Copy(message.Array, message.Offset, payload.Array, 0, message.Count); } else if (messageSize > maxMessageSize) { throw new InvalidOperationException("The message doesn't fit inside the MTU"); } else { payload = message; } // encrypt the payload var icipher = new AesCtrMode(AesFactory.GetAes(true, payloadKey), nonce); var encryptedPayload = icipher.Process(payload); // build the header: <nonce(4), ecdh(32)?> var header = new byte[headerSize]; Array.Copy(nonce, header, NonceSize); if (includeEcdh) { var ratchetPublicKey = step.GetPublicKey(); Array.Copy(ratchetPublicKey, 0, header, nonce.Length, ratchetPublicKey.Length); // set the has ecdh bit header[0] |= 0b1000_0000; } else { // clear the has ecdh bit header[0] &= 0b0111_1111; } // encrypt the header using the header key and using the // last 16 bytes of the message as the nonce. var headerEncryptionNonce = new ArraySegment <byte>(encryptedPayload, encryptedPayload.Length - HeaderIVSize, HeaderIVSize); var hcipher = new AesCtrMode(GetHeaderKeyCipher(step.SendHeaderKey), headerEncryptionNonce); var encryptedHeader = hcipher.Process(header); // mac the message: <header>, <payload>, mac(12) // the mac uses the header encryption derived key (all 32 bytes) var Mac = new Poly(AesFactory); var maciv = new byte[16]; var epl = encryptedPayload.Length; var ehl = encryptedHeader.Length; if (ehl < maciv.Length) { Array.Copy(encryptedHeader, maciv, ehl); Array.Copy(encryptedPayload, 0, maciv, ehl, 16 - ehl); } else { Array.Copy(encryptedHeader, maciv, maciv.Length); } Mac.Init(step.SendHeaderKey, maciv, MacSize); Mac.Process(encryptedHeader); Mac.Process(encryptedPayload); var mac = Mac.Compute(); // construct the resulting message var result = new byte[ehl + epl + mac.Length]; Array.Copy(encryptedHeader, 0, result, 0, ehl); Array.Copy(encryptedPayload, 0, result, ehl, epl); Array.Copy(mac, 0, result, ehl + epl, mac.Length); return(result); }
private void ReceiveFirstResponse(State state, byte[] data, byte[] headerKey, EcdhRatchetStep ecdhRatchetStep) { if (!(state is ClientState clientState)) { throw new InvalidOperationException("Only the client can receive the first response."); } var contents = DeconstructMessage(state, data, headerKey, ecdhRatchetStep, false); if (contents == null || contents.Length < InitializationNonceSize) { throw new InvalidOperationException("The first response from the server was not valid"); } var nonce = new ArraySegment <byte>(contents, 0, InitializationNonceSize); if (!nonce.Matches(clientState.InitializationNonce)) { throw new InvalidOperationException("The first response from the server did not contain the correct nonce"); } clientState.InitializationNonce = null; }
private void ReceiveInitializationResponse(State state, byte[] data) { if (!(state is ClientState clientState)) { throw new InvalidOperationException("Only the client can receive an init response."); } var messageSize = data.Length; var macOffset = messageSize - MacSize; var headerIvOffset = macOffset - HeaderIVSize; var headerSize = InitializationNonceSize + EcNumSize; var payloadSize = messageSize - headerSize - MacSize; // decrypt header var cipher = new AesCtrMode(AesFactory.GetAes(true, Configuration.ApplicationKey), data, headerIvOffset, HeaderIVSize); var decryptedHeader = cipher.Process(data, 0, headerSize); Array.Copy(decryptedHeader, 0, data, 0, headerSize); // new nonce(16), ecdh pubkey(32), <nonce(16), server pubkey(32), // new ecdh pubkey(32) x2, signature(64)>, mac(12) var nonce = new ArraySegment <byte>(data, 0, InitializationNonceSize); var rootEcdhKey = new ArraySegment <byte>(data, InitializationNonceSize, EcNumSize); var encryptedPayload = new ArraySegment <byte>(data, headerSize, payloadSize); CheckNonce(nonce); // decrypt payload IKeyAgreement rootEcdh = clientState.LocalEcdhForInit; var rootPreKey = rootEcdh.DeriveKey(rootEcdhKey); rootPreKey = Digest.ComputeDigest(rootPreKey); cipher = new AesCtrMode(AesFactory.GetAes(true, rootPreKey), nonce); var decryptedPayload = cipher.Process(encryptedPayload); Array.Copy(decryptedPayload, 0, data, headerSize, payloadSize); // extract some goodies var oldNonce = new ArraySegment <byte>(data, headerSize, InitializationNonceSize); var serverPubKey = new ArraySegment <byte>(data, headerSize + InitializationNonceSize, EcNumSize); var remoteRatchetEcdh0 = new ArraySegment <byte>(data, headerSize + InitializationNonceSize + EcNumSize, EcNumSize); var remoteRatchetEcdh1 = new ArraySegment <byte>(data, headerSize + InitializationNonceSize + EcNumSize * 2, EcNumSize); // make sure the nonce sent back by the server (which is encrypted and signed) // matches the nonce we sent previously if (!oldNonce.Matches(clientState.InitializationNonce)) { throw new InvalidOperationException("Nonce did not match"); } // verify that the signature matches IVerifier verifier = VerifierFactory.Create(serverPubKey); if (!verifier.VerifySignedMessage(Digest, new ArraySegment <byte>(data, 0, payloadSize + headerSize))) { throw new InvalidOperationException("The signature was invalid"); } // keep the server public key around clientState.ServerPublicKey = serverPubKey.ToArray(); // store the new nonce we got from the server clientState.InitializationNonce = nonce.ToArray(); Log.Verbose($"storing iniitlizaionta nonce: {Log.ShowBytes(nonce)}"); // we now have enough information to construct our double ratchet IKeyAgreement localStep0EcdhRatchet = KeyAgreementFactory.GenerateNew(); IKeyAgreement localStep1EcdhRatchet = KeyAgreementFactory.GenerateNew(); // initialize client root key and ecdh ratchet var genKeys = KeyDerivation.GenerateKeys(rootPreKey, clientState.InitializationNonce, 3, 32); var rootKey = genKeys[0]; var receiveHeaderKey = genKeys[1]; var sendHeaderKey = genKeys[2]; clientState.Ratchets.Add(EcdhRatchetStep.InitializeClient(KeyDerivation, Digest, rootKey, remoteRatchetEcdh0, remoteRatchetEcdh1, localStep0EcdhRatchet, receiveHeaderKey, sendHeaderKey, localStep1EcdhRatchet)); clientState.LocalEcdhForInit = null; }
protected void ReadRatchet(Stream stream, IKeyAgreementFactory kexFac) { List <EcdhRatchetStep> steps = new List <EcdhRatchetStep>(); bool last = true; bool secondToLast = false; for (; ;) { // check if this is still a ratchet record var b = stream.ReadByte(); stream.Seek(-1, SeekOrigin.Current); if (b < 0 || (b & 0b1110_0000) == 0) { break; } if (last) { var genbytes = new byte[8]; stream.Read(genbytes, 0, 8); genbytes[0] &= 0b0001_1111; var rgeneration = BigEndianBitConverter.ToInt32(genbytes, 0); // hot var sgeneration = BigEndianBitConverter.ToInt32(genbytes, 4); // hot and important var ecdh = kexFac.Deserialize(stream); var nextRootKey = new byte[KeySizeInBytes]; var sHeaderKey = new byte[KeySizeInBytes]; var sNextHeaderKey = new byte[KeySizeInBytes]; var sChainKey = new byte[KeySizeInBytes]; var rHeaderKey = new byte[KeySizeInBytes]; var rNextHeaderKey = new byte[KeySizeInBytes]; var rChainKey = new byte[KeySizeInBytes]; stream.Read(nextRootKey, 0, KeySizeInBytes); // cold stream.Read(sHeaderKey, 0, KeySizeInBytes); // cold stream.Read(sNextHeaderKey, 0, KeySizeInBytes); // cold stream.Read(sChainKey, 0, KeySizeInBytes); // hot and important stream.Read(rHeaderKey, 0, KeySizeInBytes); // cold stream.Read(rNextHeaderKey, 0, KeySizeInBytes); // cold stream.Read(rChainKey, 0, KeySizeInBytes); // hot steps.Add(EcdhRatchetStep.Create(ecdh, nextRootKey, rgeneration, rHeaderKey, rNextHeaderKey, rChainKey, sgeneration, sHeaderKey, sNextHeaderKey, sChainKey)); } else if (secondToLast) { if ((b & 0b1000_0000) != 0) { // send and receive chain var genbytes = new byte[8]; stream.Read(genbytes, 0, 8); genbytes[0] &= 0b0001_1111; var rgeneration = BigEndianBitConverter.ToInt32(genbytes, 0); // hot var sgeneration = BigEndianBitConverter.ToInt32(genbytes, 4); // hot and important var sHeaderKey = new byte[KeySizeInBytes]; var sChainKey = new byte[KeySizeInBytes]; var rHeaderKey = new byte[KeySizeInBytes]; var rChainKey = new byte[KeySizeInBytes]; stream.Read(sHeaderKey, 0, KeySizeInBytes); // cold stream.Read(sChainKey, 0, KeySizeInBytes); // hot and important stream.Read(rHeaderKey, 0, KeySizeInBytes); // cold stream.Read(rChainKey, 0, KeySizeInBytes); // hot steps.Add(EcdhRatchetStep.Create(null, null, rgeneration, rHeaderKey, null, rChainKey, sgeneration, sHeaderKey, null, sChainKey)); } else { // only sending chain - the client starts with only a sending chain as the first generation var genbytes = new byte[4]; stream.Read(genbytes, 0, 4); genbytes[0] &= 0b0001_1111; var sgeneration = BigEndianBitConverter.ToInt32(genbytes, 0); // hot and important var sHeaderKey = new byte[KeySizeInBytes]; var sChainKey = new byte[KeySizeInBytes]; stream.Read(sHeaderKey, 0, KeySizeInBytes); // cold stream.Read(sChainKey, 0, KeySizeInBytes); // hot and important steps.Add(EcdhRatchetStep.Create(null, null, 0, null, null, null, sgeneration, sHeaderKey, null, sChainKey)); } }