/// <summary> /// Verifies the provided encrypted serialized authentication acknowledgement data received from the initiator. /// If verification fails, an appropriate exception will be thrown. If no exception is thrown, the verification /// has succeeded. /// </summary> /// <param name="authAckData">The encrypted serialized authentication acknowledgement data to verify.</param> public void VerifyAuthenticationAcknowledgement(byte[] authAckData) { // Verify this is the initiator role. if (Role != RLPxSessionRole.Initiator) { throw new Exception("RLPx auth-ack data should only be verified by the initiator."); } // Verify the session state is correct. if (SessionState != RLPxSessionState.AuthenticationCompleted) { throw new Exception("RLPx auth-ack verification should only be performed after auth was created/sent."); } // Try to deserialize the data as a standard authentication packet. // The data is currently encrypted serialized data, so it will also need to be decrypted. RLPxAuthAckBase authAckMessage = null; try { // Set the auth-ack data AuthAckData = authAckData.Slice(0, AUTH_ACK_STANDARD_ENCRYPTED_SIZE); // The authentication data can simply be decrypted without any shared mac. byte[] decryptedAuthData = Ecies.Decrypt(LocalPrivateKey, AuthAckData, null); // Try to parse the decrypted authentication data. authAckMessage = new RLPxAuthAckStandard(decryptedAuthData); UsingEIP8Authentication = false; // Set the remote version RemoteVersion = MAX_SUPPORTED_VERSION; } catch (Exception authAckStandardEx) { try { // EIP8 has a uint16 denoting encrypted data size, and the data to decrypt follows. Memory <byte> authAckDataMem = authAckData; Memory <byte> sharedMacDataMem = authAckDataMem.Slice(0, 2); ushort encryptedSize = (ushort)BigIntegerConverter.GetBigInteger(sharedMacDataMem.Span, false, sharedMacDataMem.Length); Memory <byte> encryptedData = authAckDataMem.Slice(2, encryptedSize); // Set our auth-ack data AuthAckData = authAckDataMem.Slice(0, sharedMacDataMem.Length + encryptedSize).ToArray(); // Split the shared mac from the authentication data byte[] decryptedAuthData = Ecies.Decrypt(LocalPrivateKey, encryptedData, sharedMacDataMem); // Try to parse the decrypted EIP8 authentication data. RLPxAuthAckEIP8 authEip8Message = new RLPxAuthAckEIP8(decryptedAuthData); UsingEIP8Authentication = true; // Set the generic authentication data object. authAckMessage = authEip8Message; // Set the remote version RemoteVersion = authEip8Message.Version; } catch (Exception authAckEip8Ex) { string exceptionMessage = "Could not deserialize RLPx auth-ack data in either standard or EIP8 format.\r\n"; exceptionMessage += $"Standard Auth-Ack Error: {authAckStandardEx.Message}\r\n"; exceptionMessage += $"EIP8 Auth-Ack Error: {authAckEip8Ex.Message}"; throw new Exception(exceptionMessage); } } // Set the responder nonce ResponderNonce = authAckMessage.Nonce; // Set the remote public key. RemoteEphermalPublicKey = EthereumEcdsa.Create(authAckMessage.EphemeralPublicKey, EthereumEcdsaKeyType.Public); // Set the session state SessionState = RLPxSessionState.AcknowledgementCompleted; }
/// <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); }