public void DeriveEncryptionParameters() { // Verify the session state is correct. if (SessionState != RLPxSessionState.AcknowledgementCompleted) { throw new Exception("RLPx encryption parameter deriviation should only occur after authentication and acknowledgement was processed."); } // Verify we have all required information if (AuthData == null || AuthAckData == null || RemoteEphermalPublicKey == null || InitiatorNonce == null || ResponderNonce == null) { throw new Exception("RLPx deriving encryption information failed: Insufficient data collected from handshake."); } // Generate the ecdh key with both ephemeral keys byte[] ecdhEphemeralKey = LocalEphemeralPrivateKey.ComputeECDHKey(RemoteEphermalPublicKey); // Generate a shared secret: Keccak256(ecdhEphemeralKey || Keccak256(ResponderNonce || InitiatorNonce)) byte[] combinedNonceHash = KeccakHash.ComputeHashBytes(ResponderNonce.Concat(InitiatorNonce)); byte[] sharedSecret = KeccakHash.ComputeHashBytes(ecdhEphemeralKey.Concat(combinedNonceHash)); // Derive the token as a hash of the shared secret. Token = KeccakHash.ComputeHashBytes(sharedSecret); // Derive AES secret: Keccak256(ecdhEphemeralKey || sharedSecret) AesSecret = KeccakHash.ComputeHashBytes(ecdhEphemeralKey.Concat(sharedSecret)); // Derive Mac secret: Keccak256(ecdhEphemeralKey || AesSecret) MacSecret = KeccakHash.ComputeHashBytes(ecdhEphemeralKey.Concat(AesSecret)); // Create our AES providers for incoming and outgoing traffic/frames. // Counter is 0, so it doesn't need to be provided, default value will handle this. IngressAes = new AesCtr(AesSecret); EgressAes = new AesCtr(AesSecret); // Next we'll want to derive our incoming (ingress) and outgoing (egress) traffic message authentication code ("MAC") // The initial state is based off of keccak((MacSecret ^ nonce) || auth/auth-ack). Later states update data from packet frames. // We begin by calculating the xor'd nonces byte[] initiatorTranformedNonce = (byte[])InitiatorNonce.Clone(); byte[] responderTransformedNonce = (byte[])ResponderNonce.Clone(); int loopSize = Math.Min(initiatorTranformedNonce.Length, MacSecret.Length); for (int i = 0; i < loopSize; i++) { initiatorTranformedNonce[i] ^= MacSecret[i]; responderTransformedNonce[i] ^= MacSecret[i]; } // Next we'll want to hash the data with our hash providers. KeccakHash initiatorOutgoing = KeccakHash.Create(); initiatorOutgoing.Update(responderTransformedNonce, 0, responderTransformedNonce.Length); initiatorOutgoing.Update(AuthData, 0, AuthData.Length); KeccakHash responderOutgoing = KeccakHash.Create(); responderOutgoing.Update(initiatorTranformedNonce, 0, initiatorTranformedNonce.Length); responderOutgoing.Update(AuthAckData, 0, AuthAckData.Length); // Assign the correct hash providers based off of role if (Role == RLPxSessionRole.Initiator) { EgressMac = initiatorOutgoing; IngressMac = responderOutgoing; } else { EgressMac = responderOutgoing; IngressMac = initiatorOutgoing; } }
/// <summary> /// Creates authentication acknowledgement data to send to the initiator, signalling that their authentication /// data was successfully verified, and providing the initiator with information that they have already provided /// to the responder (so they can both derive the same shared secrets). /// </summary> /// <param name="nonce">The nonce to use for the authentication data. If null, new data is generated.</param> /// <returns>Returns the encrypted serialized authentication acknowledgement data.</returns> public byte[] CreateAuthenticationAcknowledgement(byte[] nonce = null) { // Verify this is the responder role. if (Role != RLPxSessionRole.Responder) { throw new Exception("RLPx auth-ack data should only be created by the responder."); } // Verify the session state is correct. if (SessionState != RLPxSessionState.AuthenticationCompleted) { throw new Exception("RLPx auth-ack creation should only be performed on a session after auth was received/verified."); } // If the nonce is null, generate a new one. if (nonce == null) { nonce = new byte[NONCE_SIZE]; _randomNumberGenerator.GetBytes(nonce); } // Set the responder nonce ResponderNonce = nonce ?? GenerateNonce(); // If we are using EIP8 RLPxAuthAckBase authAckMessage = null; if (UsingEIP8Authentication) { // We use EIP8 authentication acknowledgement authAckMessage = new RLPxAuthAckEIP8() { EphemeralPublicKey = LocalEphemeralPrivateKey.ToPublicKeyArray(false, true), Nonce = ResponderNonce, Version = MAX_SUPPORTED_VERSION, }; } else { // We use standard authentication acknowledgement authAckMessage = new RLPxAuthAckStandard() { EphemeralPublicKey = LocalEphemeralPrivateKey.ToPublicKeyArray(false, true), Nonce = ResponderNonce, TokenFound = false, // TODO: Check for a saved session key from before, and set this accordingly. }; } // Serialize the authentication-acknowledgement data byte[] serializedData = authAckMessage.Serialize(); // Encrypt the data accordingly (standard uses no shared mac data, EIP8 has 2 bytes which prefix the resulting encrypted data). if (UsingEIP8Authentication) { // Obtain two bytes of mac data by EIP8 standards (big endian 16-bit unsigned integer equal to the size of the resulting ECIES encrypted data). // We can calculate this as the amount of overhead data ECIES adds is static in size. byte[] sharedMacData = BigIntegerConverter.GetBytes(serializedData.Length + Ecies.ECIES_ADDITIONAL_OVERHEAD, 2); // Encrypt the serialized data with the shared mac data, prefix the result with it. byte[] encryptedSerializedData = Ecies.Encrypt(RemotePublicKey, serializedData, sharedMacData); AuthAckData = sharedMacData.Concat(encryptedSerializedData); } else { // Encrypt it using ECIES without any shared mac data. AuthAckData = Ecies.Encrypt(RemotePublicKey, serializedData, null); } // Set the session state SessionState = RLPxSessionState.AcknowledgementCompleted; // Return the auth-ack data return(AuthAckData); }