Пример #1
0
        /// <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);
        }
Пример #2
0
        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);
        }
Пример #3
0
        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));
        }
Пример #4
0
        /// <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));
        }
Пример #5
0
        /// <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);
        }
Пример #6
0
        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);
        }
Пример #7
0
        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}");
        }
Пример #8
0
        /// <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;
        }
Пример #9
0
        /// <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;
        }
Пример #10
0
        /// <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));
        }
Пример #11
0
        /// <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);
        }