/// <summary> /// Recovers the ephemeral key used to sign the transformed nonce in the authentication data. /// Throws an exception if the recovered key is invalid, or could not be recovered. /// </summary> /// <param name="receiverPrivateKey">The private key of the receiver used to generate the shared secret.</param> /// <returns>Returns the remote ephemeral public key for the keypair which signed this authentication data.</returns> public virtual (EthereumEcdsa remoteEphemeralPublicKey, uint?chainId) RecoverDataFromSignature(EthereumEcdsa receiverPrivateKey) { // Create an EC provider with the given public key. EthereumEcdsa publicKey = EthereumEcdsa.Create(PublicKey, EthereumEcdsaKeyType.Public); // Generate the shared secret using ECDH between our local private key and this remote public key byte[] ecdhKey = receiverPrivateKey.ComputeECDHKey(publicKey); // Obtain our transformed nonce data. byte[] transformedNonceData = GetTransformedNonce(ecdhKey); // We want our signature in r,s,v format. BigInteger ecdsa_r = BigIntegerConverter.GetBigInteger(R, false, 32); BigInteger ecdsa_s = BigIntegerConverter.GetBigInteger(S, false, 32); (byte recoveryId, uint?chainId) = EthereumEcdsa.GetRecoveryAndChainIDFromV(V); // Recover the public key from the data provided. EthereumEcdsa remoteEphemeralPublickey = EthereumEcdsa.Recover(transformedNonceData, recoveryId, ecdsa_r, ecdsa_s); // Verify the key is valid // Return the ephemeral key return(remoteEphemeralPublickey, chainId); }
public virtual void Sign(EthereumEcdsa localPrivateKey, EthereumEcdsa ephemeralPrivateKey, EthereumEcdsa receiverPublicKey, uint?chainID = null) { // Generate the shared secret using ECDH between our local private key and this remote public key byte[] ecdhKey = localPrivateKey.ComputeECDHKey(receiverPublicKey); // If our nonce is null, generate a new one Nonce = Nonce ?? RLPxSession.GenerateNonce(); // Verify the nonce is the correct length. if (Nonce.Length != RLPxSession.NONCE_SIZE) { // Throw an exception if an invalid nonce was provided. throw new ArgumentException($"Invalid size nonce provided for RLPx session when signing auth message. Should be {RLPxSession.NONCE_SIZE} bytes but was {Nonce?.Length}."); } // Obtain our transformed nonce data. byte[] transformedNonceData = GetTransformedNonce(ecdhKey); // Sign the transformed data. var signature = ephemeralPrivateKey.SignData(transformedNonceData); // We want our signature in r,s,v format. R = BigIntegerConverter.GetBytes(signature.r, 32); S = BigIntegerConverter.GetBytes(signature.s, 32); V = EthereumEcdsa.GetVFromRecoveryID(chainID, signature.RecoveryID); // Set our local public key and the public key hash. PublicKey = localPrivateKey.ToPublicKeyArray(false, true); }
public static byte[] Encrypt(EthereumEcdsa remotePublicKey, byte[] data, byte[] sharedMacData = null) { // If we have no shared mac data, we set it as a blank array sharedMacData = sharedMacData ?? Array.Empty <byte>(); // Generate a random private key EthereumEcdsa senderPrivateKey = EthereumEcdsa.Generate(new SystemRandomAccountDerivation()); // Generate the elliptic curve diffie hellman ("ECDH") shared key byte[] ecdhKey = senderPrivateKey.ComputeECDHKey(remotePublicKey); // Perform NIST SP 800-56 Concatenation Key Derivation Function ("KDF") Memory <byte> keyData = DeriveKeyKDF(ecdhKey, 32); // Split the AES encryption key and MAC from the derived key data. var aesKey = keyData.Slice(0, 16).ToArray(); byte[] hmacSha256Key = keyData.Slice(16, 16).ToArray(); hmacSha256Key = _sha256.ComputeHash(hmacSha256Key); // We generate a counter for our aes-128-ctr operation. byte[] counter = new byte[AesCtr.BLOCK_SIZE]; _randomNumberGenerator.GetBytes(counter); // Encrypt the data accordingly. byte[] encryptedData = AesCtr.Encrypt(aesKey, data, counter); // Obtain the sender's public key to compile our message. byte[] localPublicKey = senderPrivateKey.ToPublicKeyArray(false, true); // We'll want to put this data into the message in the following order (where || is concatenation): // ECIES_HEADER_BYTE (1 byte) || sender's public key (64 bytes) || counter (16 bytes) || encrypted data (arbitrary length) || tag (32 bytes) // This gives us a total size of 113 + data.Length byte[] result = new byte[ECIES_ADDITIONAL_OVERHEAD + encryptedData.Length]; // Define a pointer and copy in our data as suggested. int offset = 0; result[offset++] = ECIES_HEADER_BYTE; Array.Copy(localPublicKey, 0, result, offset, localPublicKey.Length); offset += localPublicKey.Length; Array.Copy(counter, 0, result, offset, counter.Length); offset += counter.Length; Array.Copy(encryptedData, 0, result, offset, encryptedData.Length); offset += encryptedData.Length; // We still have to copy the tag, which is a HMACSHA256 of our counter + encrypted data + shared mac. // We copy the data into a buffer for this hash computation since counter + encrypted data are already aligned. byte[] tagPreimage = new byte[counter.Length + encryptedData.Length + sharedMacData.Length]; Array.Copy(result, 65, tagPreimage, 0, counter.Length + encryptedData.Length); Array.Copy(sharedMacData, 0, tagPreimage, counter.Length + encryptedData.Length, sharedMacData.Length); // Obtain a HMACSHA256 provider HMACSHA256 hmacSha256 = new HMACSHA256(hmacSha256Key); // Compute a hash of our counter + encrypted data + shared mac data. byte[] tag = hmacSha256.ComputeHash(tagPreimage); // Copy the tag into our result buffer. Array.Copy(tag, 0, result, offset, tag.Length); offset += tag.Length; // Return the resulting data. return(result); }
public static byte[] Decrypt(EthereumEcdsa localPrivateKey, Memory <byte> message, Memory <byte> sharedMacData) { // Verify our provided key type if (localPrivateKey.KeyType != EthereumEcdsaKeyType.Private) { throw new ArgumentException("ECIES could not decrypt data because the provided key was not a private key."); } // Verify the size of our message (layout specified in Encrypt() describes this value) if (message.Length <= ECIES_ADDITIONAL_OVERHEAD) { throw new ArgumentException("ECIES could not decrypt data because the provided data did not contain enough information."); } // Verify the first byte of our data int offset = 0; if (message.Span[offset++] != ECIES_HEADER_BYTE) { throw new ArgumentException("ECIES could not decrypt data because the provided data had an invalid header."); } // Extract the sender's public key from the data. Memory <byte> remotePublicKeyData = message.Slice(offset, 64); EthereumEcdsa remotePublicKey = EthereumEcdsa.Create(remotePublicKeyData, EthereumEcdsaKeyType.Public); offset += remotePublicKeyData.Length; Memory <byte> counter = message.Slice(offset, AesCtr.BLOCK_SIZE); offset += counter.Length; Memory <byte> encryptedData = message.Slice(offset, message.Length - offset - 32); offset += encryptedData.Length; byte[] tag = message.Slice(offset, message.Length - offset).ToArray(); offset += tag.Length; // Generate the elliptic curve diffie hellman ("ECDH") shared key byte[] ecdhKey = localPrivateKey.ComputeECDHKey(remotePublicKey); // Perform NIST SP 800-56 Concatenation Key Derivation Function ("KDF") Memory <byte> keyData = DeriveKeyKDF(ecdhKey, 32); // Split the AES encryption key and MAC from the derived key data. var aesKey = keyData.Slice(0, 16).ToArray(); byte[] hmacSha256Key = keyData.Slice(16, 16).ToArray(); hmacSha256Key = _sha256.ComputeHash(hmacSha256Key); // Next we'll want to verify our tag (HMACSHA256 hash of counter + encrypted data + shared mac data). // We copy the data into a buffer for this hash computation since counter + encrypted data are already aligned. byte[] tagPreimage = new byte[counter.Length + encryptedData.Length + sharedMacData.Length]; Memory <byte> tagPreimageMemory = tagPreimage; counter.CopyTo(tagPreimageMemory); encryptedData.CopyTo(tagPreimageMemory.Slice(counter.Length, encryptedData.Length)); sharedMacData.CopyTo(tagPreimageMemory.Slice(counter.Length + encryptedData.Length)); // Obtain a HMACSHA256 provider HMACSHA256 hmacSha256 = new HMACSHA256(hmacSha256Key); // Compute a hash of our counter + encrypted data + shared mac data. byte[] validTag = hmacSha256.ComputeHash(tagPreimage); // Verify our tag is valid if (!tag.SequenceEqual(validTag)) { throw new ArgumentException("ECIES could not decrypt data because the hash/tag on the counter/encrypted data/shared mac data was invalid."); } // Decrypt the data accordingly. byte[] decryptedData = AesCtr.Decrypt(aesKey, encryptedData.ToArray(), counter.ToArray()); // Return the decrypted data. return(decryptedData); }