/// <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 void EciesDecryptStaticTest(string receiverPrivateKey, string expectedResult, string encryptedData) { // Generate a keypair EthereumEcdsa keypair = EthereumEcdsa.Create(receiverPrivateKey.HexToBytes(), EthereumEcdsaKeyType.Private); // Decrypt the encrypted data with the given key. byte[] decrypted = Ecies.Decrypt(keypair, encryptedData.HexToBytes(), null); // Verify the decrypted result was as expected. Assert.Equal(expectedResult.HexToBytes(), decrypted); }
public void ProvidedHandshakeData() { // Define our nonces byte[] initiatorNonce = "abfee9cf900416b3c08c33e640739d5f27320ec140f024c07587a01bf2118910".HexToBytes(); byte[] responderNonce = "312d4b3f55134880afc60f1a605dbd89dfbc8bc275e0f110221c5b09a23e382e".HexToBytes(); // Create the initiator and responder keypairs EthereumEcdsa initiatorKeyPair = EthereumEcdsa.Create("549f2abf6141a90ff2d21d4a7d8f297d6629d52c279890343840b94100cfe873".HexToBytes(), EthereumEcdsaKeyType.Private); EthereumEcdsa initiatorEphemeralKeyPair = EthereumEcdsa.Create("42fe8795681de5ea19ea164348e1f0321cf2ae03c0731785a0753a503be0a91f".HexToBytes(), EthereumEcdsaKeyType.Private); EthereumEcdsa responderKeyPair = EthereumEcdsa.Create("259e1377ac0022c638b951c64446964c03b2743edd977953c83b72889493c2c4".HexToBytes(), EthereumEcdsaKeyType.Private); EthereumEcdsa responderEphemeralKeypair = EthereumEcdsa.Create("7ad9d85886f3ad51d231dc0a3fe346ffc0aedddcbc1d910a23f7d8215dc4f6f1".HexToBytes(), EthereumEcdsaKeyType.Private); // Initiate RLPx sessions for each role. RLPxSession initiatorSession = new RLPxSession(RLPxSessionRole.Initiator, initiatorKeyPair, initiatorEphemeralKeyPair); RLPxSession responderSession = new RLPxSession(RLPxSessionRole.Responder, responderKeyPair, responderEphemeralKeypair); // Create authentication data (initiator) (should work) byte[] authData = initiatorSession.CreateAuthentiation(responderKeyPair, (byte[])initiatorNonce.Clone()); // Verify the authentication data (responder) (should work) responderSession.VerifyAuthentication(authData); // Create an authentication acknowledgement (responder) (should work) byte[] authAckData = responderSession.CreateAuthenticationAcknowledgement((byte[])responderNonce.Clone()); // Verify the authentication acknowledgement (initiator) (should work) initiatorSession.VerifyAuthenticationAcknowledgement(authAckData); // Set our auth and auth-ack packets (UNCOMMENT/COMMENT THIS ACCORDINGLY FOR TESTING, AS DESCRIBED IN THE SKIP MESSAGE MARKING THIS TEST). //initiatorSession.AuthData = "04ce3672878bc585003184ae8397e7ee3484beb2edadcdb167eca1c34d8369022ce2bda5f02d165c64ea86e12712124759e57d5cac57e2c2b5dbf3b9273b8b1b51c4b879deadc142e5aba12505e307bebf043e0a39153590cd6d388e97997e1fb11e4aaf96084ead8b39f1f2f0992c0e5048219c00e7ca3729ea598ddeeef00ba61b1f6807f500c16eaedf2be25d9715cc664c56c65c1ad4878b564d245ff7b2a416d03ad40b67d7ad859a51be44634925a44f54158dcb23cf4ee7f4c6312cc4315b358be1976d7e0639b3276df741d17510ab7f8804a38a81114584c1320c9f8f55ae2dbd16e14752a5dc96a91af8bffef75e0bb69754d2e4c08e7f3f4aacb15492fa543378cf3839f1be272ad63ce9d874a253e3af358a0dd8d22122137af955e1e6d011f2c0b1792ce24466579e89fb6f44".HexToBytes(); //initiatorSession.AuthAckData = "040be304b52267f5f834b9491dd96beac89195495bbe5fae78cb82cb1bee5906c595af9a56038813f724c7936c492a3ee1f3dfa1dd826249d88acc4f4b0f2ce4f2f48faef6f34a64cbb85b13d138f82b5d447958e6b7c278b748510602dc1043f026f771ca17b9763e14dee7f6e3902d3eeb0f7f8b07da71ccb4107e8cfe53d65ccafbc70b65a0c9ef590c26d207866f9e6d3b0f9ab16cea65e93674b4f155a901e312b83dbb51aed6695afac10ec9b7ea0d1c846310a16a6c45ba2473f85d56894259caf3c425e44d9a90072e049920a6b3".HexToBytes(); //responderSession.AuthData = initiatorSession.AuthData; //responderSession.AuthAckData = initiatorSession.AuthAckData; // Derive our resulting data initiatorSession.DeriveEncryptionParameters(); responderSession.DeriveEncryptionParameters(); // Verify properties Assert.Equal(initiatorNonce, initiatorSession.InitiatorNonce); Assert.Equal(responderNonce, initiatorSession.ResponderNonce); Assert.Equal(initiatorNonce, responderSession.InitiatorNonce); Assert.Equal(responderNonce, responderSession.ResponderNonce); // Get our initial message authentication codes Assert.Equal("a459dc46f089e803a641d02a495965a5fa73e183a2fe5d938f598ff3fd7196d4", initiatorSession.EgressMac.Hash.ToHexString(false)); Assert.Equal("cc4e5144935e90fade946a394df07c293916c51fb4f283f2b0570732a7f474b4", initiatorSession.IngressMac.Hash.ToHexString(false)); Assert.Equal("a459dc46f089e803a641d02a495965a5fa73e183a2fe5d938f598ff3fd7196d4", responderSession.IngressMac.Hash.ToHexString(false)); Assert.Equal("cc4e5144935e90fade946a394df07c293916c51fb4f283f2b0570732a7f474b4", responderSession.EgressMac.Hash.ToHexString(false)); }
/// <summary> /// Obtains the public key for this current key instance (if private, derives public, if public, returns as is). /// </summary> /// <returns>Returns this key if it is a public key, otherwise obtains the public key from this key.</returns> public ExtendedKey GetExtendedPublicKey() { // If this is already a public key, return itself if (KeyType == EthereumEcdsaKeyType.Public) { return(this); } // This is a private key, so we derive our public key information // at this level from this private key. return(new ExtendedKey( EthereumEcdsa.Create(InternalKey.ToPublicKeyArray(), EthereumEcdsaKeyType.Public), ChainCode, Depth, ChildIndex, Fingerprint)); }
/// <summary> /// Initializes the extended key from the provided seed. /// </summary> /// <param name="seed">The seed to initialize this extended key from.</param> private void InitializeFromSeed(byte[] seed) { // Create a new HMACSHA512 instance HMACSHA512 hmacSha512 = new HMACSHA512(SeedHMACKey); // Compute the hash on our seed. byte[] hash = hmacSha512.ComputeHash(seed); // Set the key as the first 32 bytes of "hash" byte[] keyData = new byte[EthereumEcdsa.PRIVATE_KEY_SIZE]; Array.Copy(hash, 0, keyData, 0, keyData.Length); InternalKey = EthereumEcdsa.Create(keyData, EthereumEcdsaKeyType.Private); // Initialize the chain code ChainCode = new byte[CHAIN_CODE_SIZE]; // We derive the chain code as the data immediately following key data. Array.Copy(hash, 32, ChainCode, 0, ChainCode.Length); }
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); }
protected override void EndProcessing() { string filePath; if (Path.IsPathRooted(FilePath)) { filePath = Path.GetFullPath(FilePath); } else { filePath = Path.GetFullPath(Path.Join(SessionState.Path.CurrentLocation.Path, FilePath)); } if (!File.Exists(filePath)) { Host.UI.WriteErrorLine($"File does not exist at: {filePath}"); return; } var fileContent = File.ReadAllText(filePath); var dataJson = JObject.Parse(fileContent); string[][] accountArrayHex; if (dataJson.TryGetValue(LocalAccountsUtil.JSON_ENCRYPTED_ACCOUNTS_KEY, out var token)) { if (string.IsNullOrWhiteSpace(Password)) { Host.UI.WriteErrorLine($"No password parameter specified and accounts are encryped in file {FilePath}"); return; } var encrypedAccounts = token.Value <string>(); string decrypedContent; try { decrypedContent = AesUtil.DecryptString(encrypedAccounts, Password); } catch (Exception ex) { Host.UI.WriteErrorLine(ex.ToString()); Host.UI.WriteErrorLine("Failed to decrypt account data. Incorrect password?"); return; } accountArrayHex = JsonConvert.DeserializeObject <string[][]>(decrypedContent); } else { if (!string.IsNullOrWhiteSpace(Password)) { Host.UI.WriteErrorLine($"Password parameter specified but accounts are encryped in file {FilePath}"); return; } accountArrayHex = dataJson[LocalAccountsUtil.JSON_ACCOUNTS_KEY].ToObject <string[][]>(); } var accounts = accountArrayHex .Select(a => EthereumEcdsa.Create(HexUtil.HexToBytes(a[1]), EthereumEcdsaKeyType.Private)) .Select(a => (a.EcdsaKeyPairToAddress(), a)) .ToArray(); GlobalVariables.AccountKeys = accounts; Host.UI.WriteLine($"Loaded {accounts.Length} accounts from {filePath}"); }
/// <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> /// Verifies the provided encrypted serialized authentication 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="authData">The encrypted serialized authentication data to verify.</param> public void VerifyAuthentication(byte[] authData) { // Verify this is the responder role. if (Role != RLPxSessionRole.Responder) { throw new Exception("RLPx auth data should only be verified by the responder."); } // Verify the session state is correct. if (SessionState != RLPxSessionState.Initial) { throw new Exception("RLPx auth verification should only be performed on a new session."); } // 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. RLPxAuthBase authenticationMessage = null; try { // Set the auth data AuthData = authData.Slice(0, AUTH_STANDARD_ENCRYPTED_SIZE); // The authentication data can simply be decrypted without any shared mac. byte[] decryptedAuthData = Ecies.Decrypt(LocalPrivateKey, AuthData, null); // Try to parse the decrypted authentication data. authenticationMessage = new RLPxAuthStandard(decryptedAuthData); UsingEIP8Authentication = false; // Set the remote version RemoteVersion = MAX_SUPPORTED_VERSION; } catch (Exception authStandardEx) { try { // EIP8 has a uint16 denoting encrypted data size, and the data to decrypt follows. Memory <byte> authDataMem = authData; Memory <byte> sharedMacDataMem = authDataMem.Slice(0, 2); ushort encryptedSize = (ushort)BigIntegerConverter.GetBigInteger(sharedMacDataMem.Span, false, sharedMacDataMem.Length); Memory <byte> encryptedData = authDataMem.Slice(2, encryptedSize); // Set our auth data AuthData = authDataMem.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. RLPxAuthEIP8 authEip8Message = new RLPxAuthEIP8(decryptedAuthData); UsingEIP8Authentication = true; // Set the generic authentication data object. authenticationMessage = authEip8Message; // Set the remote version RemoteVersion = authEip8Message.Version; } catch (Exception authEip8Ex) { string exceptionMessage = "Could not deserialize RLPx auth data in either standard or EIP8 format.\r\n"; exceptionMessage += $"Standard Auth Error: {authStandardEx.Message}\r\n"; exceptionMessage += $"EIP8 Auth Error: {authEip8Ex.Message}"; throw new Exception(exceptionMessage); } } // Set the initiator nonce InitiatorNonce = authenticationMessage.Nonce; // Set the remote public key. RemotePublicKey = EthereumEcdsa.Create(authenticationMessage.PublicKey, EthereumEcdsaKeyType.Public); // Try to recover the public key (with the "receiver" private key, which in this case is our local private key, since our role is responder). // If this fails, it will throw an exception as we expect this method to if any failure occurs. (EthereumEcdsa remoteEphermalPublicKey, uint?chainId) = authenticationMessage.RecoverDataFromSignature(LocalPrivateKey); RemoteEphermalPublicKey = remoteEphermalPublicKey; // TODO: Verify the chain id. // Set the session state SessionState = RLPxSessionState.AuthenticationCompleted; }
/// <summary> /// Obtains the parent private key if provided the parent public key. This key must be a private key. /// </summary> /// <param name="parentPublicKey">The public key of the parent of this extended key.</param> /// <returns>Returns the private key of the parent of this extended key.</returns> public ExtendedKey GetParentPrivateKey(ExtendedKey parentPublicKey) { // Verify this key is a private key if (KeyType != EthereumEcdsaKeyType.Private) { // This key is not a private key. throw new ArgumentNullException("Could not obtain parent private key. Can only obtain the parent private key of a private key. This key is a public key."); } else if (parentPublicKey == null) { // The public key is null. throw new ArgumentNullException("Could not obtain parent private key. Provided parent public key argument is null."); } else if (parentPublicKey.KeyType == EthereumEcdsaKeyType.Private) { // The public key was not a public key. throw new ArgumentException("Could not obtain parent private key. Provided parent public key argument is not a public key."); } else if (Hardened) { throw new ArgumentException("Could not obtain parent private key if this key is a hardened key."); } else if (Depth == 0) { throw new ArgumentException("Could not obtain parent private key for this key because this key is a top level key."); } else if (!parentPublicKey.IsChild(this)) { // The provided parent public key is not a parent. throw new ArgumentException("Could not obtain parent private key for this key because the provided parent public key argument is not a parent to this key."); } // Obtain the hash used to derive this current key, from the parent. byte[] hash = parentPublicKey.ComputeChildHash(ChildIndex, parentPublicKey.InternalKey.ToPublicKeyArray(true, false)); // Set the child key data as the first 32 bytes of "hash" byte[] childKeyData = new byte[EthereumEcdsa.PRIVATE_KEY_SIZE]; Array.Copy(hash, 0, childKeyData, 0, childKeyData.Length); // Initialize the child chain code byte[] childChainCode = new byte[CHAIN_CODE_SIZE]; // We derive the child chain code as the data immediately following key data. Array.Copy(hash, 32, childChainCode, 0, childChainCode.Length); // Verify the chain code is equal if (!ChainCode.SequenceEqual(childChainCode)) { throw new ArgumentException("Derived chain code from the parent at this key's child index that did not match this key's chain code."); } // Convert the key data to an integer BigInteger childKeyInt = BigIntegerConverter.GetBigInteger(childKeyData, false, childChainCode.Length); // Convert this private key to an integer byte[] thisKeyData = InternalKey.ToPrivateKeyArray(); BigInteger thisKeyInt = BigIntegerConverter.GetBigInteger(thisKeyData, false, thisKeyData.Length); // Compute our parent key BigInteger parentPrivateKeyInt = ((thisKeyInt - childKeyInt) + Secp256k1Curve.N) % Secp256k1Curve.N; // Obtain our new key from this byte[] computedParentKeyData = BigIntegerConverter.GetBytes(parentPrivateKeyInt, EthereumEcdsa.PRIVATE_KEY_SIZE); // Obtain the parent private key EthereumEcdsa parentPrivateKey = EthereumEcdsa.Create(computedParentKeyData, EthereumEcdsaKeyType.Private); // Create the parent extended private key return(new ExtendedKey(parentPrivateKey, parentPublicKey.ChainCode, parentPublicKey.Depth, parentPublicKey.ChildIndex, parentPublicKey.Fingerprint)); }
/// <summary> /// Computes the child key and child chain code for a given child key/index relative from this extended key. /// </summary> /// <param name="index">The child key/index to derive a key/chain code for.</param> /// <returns>Returns a key and chain code for a child relative from this extended key. Derives a key of the same type as this key.</returns> private (EthereumEcdsa childKey, byte[] childChainCode) GetChildKeyInternal(uint index) { // Declare a hash we will obtain of our key. byte[] hash = null; // If this is a hardened directory/key/index if (KeyPath.CheckHardenedDirectoryIndex(index)) { // Verify we aren't trying to derive a hardened key from a public key (since private is required). if (KeyType == EthereumEcdsaKeyType.Public) { // Throw an exception because hardened keys mean we need the private key, but this is the public key. throw new ArgumentException("Hierarchically deterministic child key cannot be derived from a public key when the child key index is hardened. Hardened keys can only be derived when the private key is known."); } // Obtain the message to hash. (0x00 byte prefixing the private key to pad it to 33 bytes long). byte[] hashMessage = new byte[] { 0 }.Concat(InternalKey.ToPrivateKeyArray()); // Next we hash our key data. hash = ComputeChildHash(index, hashMessage); } else { // Compute the hash on our public key and index. hash = ComputeChildHash(index, InternalKey.ToPublicKeyArray(true, false)); } // Set the child key data as the first 32 bytes of "hash" byte[] childKeyData = new byte[EthereumEcdsa.PRIVATE_KEY_SIZE]; Array.Copy(hash, 0, childKeyData, 0, childKeyData.Length); // Initialize the child chain code byte[] childChainCode = new byte[CHAIN_CODE_SIZE]; // We derive the child chain code as the data immediately following key data. Array.Copy(hash, 32, childChainCode, 0, childChainCode.Length); // Convert the key data to an integer BigInteger childKeyInt = BigIntegerConverter.GetBigInteger(childKeyData, false, childChainCode.Length); // If the child key is above N if (childKeyInt >= Secp256k1Curve.N) { throw new ArgumentException("Calculated child key value cannot exceed or equal N on the secp256k1 curve. Hierarchically deterministic child key cannot derive here. Try again."); } // Define our resulting key to obtain EthereumEcdsa childKey = null; // Obtain our child key depending on type.; if (KeyType == EthereumEcdsaKeyType.Public) { // Obtain our public key and add it to G * childKey var q = Secp256k1Curve.Parameters.Curve.DecodePoint(InternalKey.ToPublicKeyArray(true, false)); q = Secp256k1Curve.Parameters.G.Multiply(childKeyInt.ToBouncyCastleBigInteger()).Add(q); if (q.IsInfinity) { throw new ArgumentException("Calculated child key value point is infinity. This is a very rare occurrence. Hierarchically deterministic child key cannot derive here."); } // Normalize our point. q = q.Normalize(); var p = Secp256k1Curve.DomainParameters.Curve.CreatePoint(q.XCoord.ToBigInteger(), q.YCoord.ToBigInteger()); var encoded = p.GetEncoded(compressed: true); // Derive our child data. childKey = EthereumEcdsa.Create(encoded, EthereumEcdsaKeyType.Public); } else { // Add our private key to our parsed new key, mod N, to derive our new key. BigInteger computedChildKeyInt = (BigIntegerConverter.GetBigInteger(InternalKey.ToPrivateKeyArray()) + childKeyInt) % Secp256k1Curve.N; // Verify our computed child key is non-zero if (computedChildKeyInt == 0) { throw new ArgumentException("Calculated child private key is zero. This is a very rare occurrence. Hierarchically deterministic child key cannot derive here."); } // Obtain our new key from this byte[] computedChildKeyData = BigIntegerConverter.GetBytes(computedChildKeyInt, EthereumEcdsa.PRIVATE_KEY_SIZE); // Initialize our key childKey = EthereumEcdsa.Create(computedChildKeyData, EthereumEcdsaKeyType.Private); } // Return our obtained data. return(childKey, childChainCode); }