// Use strict
"use strict";


// Classes

// Crypto class
class Crypto {

	// Public
	
		// Derive child key
		static deriveChildKey(extendedPrivateKey, paths, useBip39 = false) {
			
			// Return promise
			return new Promise(function(resolve, reject) {
			
				// Get secret key and chain code from extended private key
				var secretKey = extendedPrivateKey.subarray(0, Crypto.SECP256K1_SECRET_KEY_LENGTH);
				var chainCode = extendedPrivateKey.subarray(Crypto.CHAIN_CODE_LENGTH);
				
				// Set update values
				var updateValues = new Promise(function(resolve, reject) {
				
					// Resolve secret key and chain code
					resolve([
					
						// Copy secret key
						new Uint8Array(secretKey),
						
						// Copy chain code
						new Uint8Array(chainCode)
					]);
				});
				
				// Initialize updating values
				var updatingValues = [updateValues];
			
				// Go through all of the paths
				for(let i = 0; i < paths["length"]; ++i) {
				
					// Set update to run after the previous update
					updateValues = updateValues.then(function(values) {
					
						// Return promise
						return new Promise(function(resolve, reject) {
					
							// Get updated secret key and chain code
							var updatedSecretKey = values[Crypto.SECRET_KEY_INDEX];
							var updatedChainCode = values[Crypto.CHAIN_CODE_INDEX];
							
							// Return deriving the next secret key and chain code using the path
							return Crypto.deriveSecretKeyAndChainCode(updatedSecretKey, updatedChainCode, paths[i], useBip39).then(function(updatedValues) {
							
								// Securely clear updated secret key and chain code
								updatedSecretKey.fill(0);
								updatedChainCode.fill(0);
							
								// Resolve updated values
								resolve(updatedValues);
							
							// Catch errors
							}).catch(function(error) {
							
								// Securely clear secret key and chain code
								updatedSecretKey.fill(0);
								updatedChainCode.fill(0);
							
								// Reject error
								reject(error);
							});
						});
					
					// Catch errors
					}).catch(function(error) {
					
						// Return Promise
						return new Promise(function(resolve, reject) {
					
							// Reject error
							reject(error);
						});
					});
					
					// Append updating value to list
					updatingValues.push(updateValues);
				}
				
				// Return updating all values
				return Promise.all(updatingValues).then(function(values) {
				
					// Get updated secret key and chain code
					var updatedSecretKey = values[values["length"] - 1][Crypto.SECRET_KEY_INDEX];
					var updatedChainCode = values[values["length"] - 1][Crypto.CHAIN_CODE_INDEX];
					
					// Initialize new extended private key
					var newExtendedPrivateKey = Common.mergeArrays([
					
						// Updated secret key
						updatedSecretKey,
						
						// Chain code
						updatedChainCode
					]);
					
					// Securely clear updated secret key and chain code
					updatedSecretKey.fill(0);
					updatedChainCode.fill(0);
					
					// Check if new extended private key's secret key is a valid secret key
					if(Secp256k1Zkp.isValidSecretKey(newExtendedPrivateKey.subarray(0, Crypto.SECP256K1_SECRET_KEY_LENGTH)) === true) {
					
						// Resolve new extended private key
						resolve(newExtendedPrivateKey);
					}
					
					// Otherwise
					else {
					
						// Securely clear new extended private key
						newExtendedPrivateKey.fill(0);
					
						// Reject error
						reject("Invalid extended private key.");
					}
				
				// Catch errors
				}).catch(function(error) {
				
					// Reject error
					reject(error);
				});
			});
		}
	
		// Commit
		static commit(extendedPrivateKey, amount, identifier, switchType) {
		
			// Return promise
			return new Promise(function(resolve, reject) {
			
				// Return deriving secret key from extended private key, amount, identifier, and switch type
				return Crypto.deriveSecretKey(extendedPrivateKey, amount, identifier, switchType).then(function(secretKey) {
				
					// Check if performing Pedersen commit with the secret key and amount was successful
					var commit = Secp256k1Zkp.pedersenCommit(secretKey, amount.toFixed());
					
					if(commit !== Secp256k1Zkp.OPERATION_FAILED) {
					
						// Resolve commit
						resolve(commit);
					}
					
					// Otherwise
					else {
					
						// Securely clear secret key
						secretKey.fill(0);
					
						// Reject error
						reject("Performing Pedersen commit failed.");
					}
				
				// Catch errors
				}).catch(function(error) {
				
					// Reject error
					reject(error);
				});
			});
		}
		
		// Commit amount
		static commitAmount(amount) {
		
			// Check if performing Pedersen commit with zero blinding factor and amount was successful
			var commit = Secp256k1Zkp.pedersenCommit(Crypto.ZERO_BLINDING_FACTOR, amount.toFixed());
			
			if(commit !== Secp256k1Zkp.OPERATION_FAILED) {
			
				// Return commit
				return commit;
			}
			
			// Otherwise
			else {
			
				// Throw error
				throw "Performing Pedersen commit failed.";
			}
		}
		
		// Proof
		static proof(extendedPrivateKey, amount, identifier, switchType, proofBuilder) {
		
			// Return promise
			return new Promise(function(resolve, reject) {
			
				// Return getting commit from extended private key, amount, identifier, and switch type
				return Crypto.commit(extendedPrivateKey, amount, identifier, switchType).then(function(commit) {
				
					// Return deriving secret key from extended private key, amount, identifier, and switch type
					return Crypto.deriveSecretKey(extendedPrivateKey, amount, identifier, switchType).then(function(secretKey) {
					
						// Return getting rewind nonce from the commit
						return proofBuilder.rewindNonce(commit).then(function(rewindNonce) {
						
							// Return getting private nonce from the commit
							return proofBuilder.privateNonce(commit).then(function(privateNonce) {
							
								// Try
								try {
							
									// Get proof message from identifier and switch type
									var message = proofBuilder.proofMessage(identifier, switchType);
								}
								
								// Catch errors
								catch(error) {
								
									// Securely clear secret key
									secretKey.fill(0);
								
									// Securely clear rewind nonce
									rewindNonce.fill(0);
								
									// Securely clear private nonce
									privateNonce.fill(0);
								
									// Reject error
									reject("Getting proof message failed.");
									
									// Return
									return;
								}
								
								// Check if creating bulletproof with the secret key, amount, rewind nonce, private nonce, and message was successful
								var bulletproof = Secp256k1Zkp.createBulletproof(secretKey, amount.toFixed(), rewindNonce, privateNonce, new Uint8Array([]), message);
								
								if(bulletproof !== Secp256k1Zkp.OPERATION_FAILED) {
								
									// Securely clear secret key
									secretKey.fill(0);
									
									// Securely clear rewind nonce
									rewindNonce.fill(0);
								
									// Securely clear private nonce
									privateNonce.fill(0);
								
									// Resolve bulletproof
									resolve(bulletproof);
								}
								
								// Otherwise
								else {
								
									// Securely clear secret key
									secretKey.fill(0);
								
									// Securely clear rewind nonce
									rewindNonce.fill(0);
								
									// Securely clear private nonce
									privateNonce.fill(0);
								
									// Reject error
									reject("Getting bulletproof failed.");
								}
								
							// Catch errors
							}).catch(function(error) {
							
								// Securely clear secret key
								secretKey.fill(0);
							
								// Securely clear rewind nonce
								rewindNonce.fill(0);
							
								// Reject error
								reject(error);
							});
						
						// Catch errors
						}).catch(function(error) {
						
							// Securely clear secret key
							secretKey.fill(0);
						
							// Reject error
							reject(error);
						});
					
					// Catch errors
					}).catch(function(error) {
					
						// Reject error
						reject(error);
					});
				
				// Catch errors
				}).catch(function(error) {
				
					// Reject error
					reject(error);
				});
			});
		}
		
		// Derive secret key
		static deriveSecretKey(extendedPrivateKey, amount, identifier, switchType) {
			
			// Return promise
			return new Promise(function(resolve, reject) {
			
				// Initialize paths
				var paths = new Uint32Array(identifier.getDepth());
				
				// Go through all the identifier's paths
				for(var i = 0; i < identifier.getDepth(); ++i) {
				
					// Set path to identifier's path
					paths[i] = identifier.getPaths()[i];
				}
				
				// Return deriving the child key at the path from the extended private key
				return Crypto.deriveChildKey(extendedPrivateKey, paths).then(function(childKey) {
				
					// Get secret key from the child key
					var secretKey = new Uint8Array(childKey.subarray(0, Crypto.SECP256K1_SECRET_KEY_LENGTH));
					
					// Securely clear child key
					childKey.fill(0);
					
					// Check if switch type is none
					if(switchType === Crypto.SWITCH_TYPE_NONE)
					
						// Resolve secret key
						resolve(secretKey);
					
					// Otherwise check if switch type is regular
					else if(switchType === Crypto.SWITCH_TYPE_REGULAR) {
					
						// Check if getting blind switch of secret key and amount failed
						var blindSwitch = Secp256k1Zkp.blindSwitch(secretKey, amount.toFixed());
						
						if(blindSwitch === Secp256k1Zkp.OPERATION_FAILED) {
						
							// Securely clear secret key
							secretKey.fill(0);
						
							// Reject error
							reject("Performing blind switch failed.");
							
							// Return
							return;
						}
						
						// Securely clear secret key
						secretKey.fill(0);
						
						// Check if blind switch is a valid secret key
						if(Secp256k1Zkp.isValidSecretKey(blindSwitch) === true) {
					
							// Resolve blind switch
							resolve(blindSwitch);
						}
						
						// Otherwise
						else {
						
							// Securely clear blind switch
							blindSwitch.fill(0);
						
							// Reject error
							reject("Blind switch isn't a valid secret key.");
						}
					}
					
					// Otherwise
					else {
					
						// Securely clear secret key
						secretKey.fill(0);
					
						// Reject error
						reject("Invalid switch type.");
					}
				
				// Catch errors
				}).catch(function(error) {
				
					// Reject error
					reject(error);
				});
			});
		}
		
		// Signature message
		static signatureMessage(data) {
		
			// Check if creating message was successful
			var message = Blake2b.compute(Crypto.SINGLE_SIGNER_MESSAGE_LENGTH, data, new Uint8Array([]));
			
			if(message !== Blake2b.OPERATION_FAILED) {
			
				// Return message
				return message;
			}
			
			// Otherwise
			else {
			
				// Throw error
				throw "Creating signature message failed.";
			}
		}
		
		// Root public key
		static rootPublicKey(extendedPrivateKey) {
		
			// Return promise
			return new Promise(function(resolve, reject) {
		
				// Get secret key from extended private key
				var secretKey = extendedPrivateKey.subarray(0, Crypto.SECP256K1_SECRET_KEY_LENGTH);
				
				// Check if getting public key from secret key was successful
				var publicKey = Secp256k1Zkp.publicKeyFromSecretKey(secretKey);
				
				if(publicKey !== Secp256k1Zkp.OPERATION_FAILED) {
				
					// Resolve public key
					resolve(publicKey);
				}
				
				// Otherwise
				else {
				
					// Reject error
					reject("Getting public key failed.");
				}
			});
		}
		
		// Address key
		static addressKey(extendedPrivateKey, index) {
		
			// Return promise
			return new Promise(function(resolve, reject) {
			
				// Check wallet type
				switch(Consensus.getWalletType()) {
				
					// MWC wallet
					case Consensus.MWC_WALLET_TYPE:
			
						// Return deriving root key from extended private key, address key amount, root identifier, and regular switch type
						return Crypto.deriveSecretKey(extendedPrivateKey, Crypto.ADDRESS_KEY_AMOUNT, new Identifier(Identifier.ROOT_SERIALIZED_IDENTIFIER), Crypto.SWITCH_TYPE_REGULAR).then(function(rootKey) {
						
							// Return creating crypto key from address key seed
							return crypto["subtle"].importKey("raw", (new TextEncoder()).encode(Crypto.ADDRESS_KEY_SEED), {
							
								// Name
								"name": Crypto.ADDRESS_KEY_ENCRYPTION_ALGORITHM,
								
								// Hash
								"hash": {
								
									// Name
									"name": Crypto.ADDRESS_KEY_DIGEST_ALGORITHM
								}
							}, false, [
							
								// Sign
								"sign"
							
							]).then(function(cryptoKey) {
							
								// Return creating address extended private key from signing the root key
								return crypto["subtle"].sign(Crypto.ADDRESS_KEY_ENCRYPTION_ALGORITHM, cryptoKey, rootKey).then(function(addressExtendedPrivateKey) {
								
									// Securely clear root key
									rootKey.fill(0);
								
									// Get address extended private key in correct format
									addressExtendedPrivateKey = new Uint8Array(addressExtendedPrivateKey);
									
									// Get secret key and chain code from address extended private key
									var secretKey = addressExtendedPrivateKey.subarray(0, Crypto.SECP256K1_SECRET_KEY_LENGTH);
									var chainCode = addressExtendedPrivateKey.subarray(Crypto.CHAIN_CODE_LENGTH);
									
									// Check if secret key is a valid secret key
									if(Secp256k1Zkp.isValidSecretKey(secretKey) === true) {
									
										// Return updating the next secret key and chain code using the index
										return Crypto.deriveSecretKeyAndChainCode(secretKey, chainCode, index).then(function(value) {
										
											// Securely clear address extended private key
											addressExtendedPrivateKey.fill(0);
											
											// Securely clear the chain code
											value[Crypto.CHAIN_CODE_INDEX].fill(0);
										
											// Resolve the secret key
											resolve(value[Crypto.SECRET_KEY_INDEX]);
										
										// Catch errors
										}).catch(function(error) {
										
											// Securely clear address extended private key
											addressExtendedPrivateKey.fill(0);
										
											// Reject error
											reject(error);
										});
									}
									
									// Otherwise
									else {
									
										// Securely clear address extended private key
										addressExtendedPrivateKey.fill(0);
									
										// Reject error
										reject("Secret key is not a valid secret key.");
									}
								
								// Catch errors
								}).catch(function(error) {
								
									// Securely clear root key
									rootKey.fill(0);
								
									// Reject error
									reject(error);
								});
							
							// Catch errors
							}).catch(function(error) {
							
								// Securely clear root key
								rootKey.fill(0);
							
								// Reject error
								reject(error);
							});
						
						// Catch errors
						}).catch(function(error) {
						
							// Reject error
							reject(error);
						});
					
					// GRIN or EPIC wallet
					case Consensus.GRIN_WALLET_TYPE:
					case Consensus.EPIC_WALLET_TYPE:
					
						// Create child identifier
						var childIdentifier = new Identifier().getChild();
						childIdentifier.getPaths()[1] = 1;
						childIdentifier.getPaths()[childIdentifier.getDepth() - 1] = index;
						
						// Return deriving root key from extended private key, child identifier, and no switch type
						return Crypto.deriveSecretKey(extendedPrivateKey, new BigNumber(0), childIdentifier, Crypto.SWITCH_TYPE_NONE).then(function(rootKey) {
						
							// Check if getting secret key from root key was successful
							var secretKey = Blake2b.compute(Crypto.SECP256K1_SECRET_KEY_LENGTH, rootKey, new Uint8Array([]));
							
							if(secretKey !== Blake2b.OPERATION_FAILED) {
							
								// Securely clear root key
								rootKey.fill(0);
							
								// Check if secret key is a valid secret key
								if(Secp256k1Zkp.isValidSecretKey(secretKey) === true) {
							
									// Resolve the secret key
									resolve(secretKey);
								}
								
								// Otherwise
								else {
								
									// Securely clear secret key
									secretKey.fill(0);
									
									// Reject error
									reject("Secret key is not a valid secret key.");
								}
							}
							
							// Otherwise
							else {
							
								// Securely clear root key
								rootKey.fill(0);
							
								// Reject error
								reject("Getting secret key from root key failed.");
							}
						
						// Catch errors
						}).catch(function(error) {
						
							// Reject error
							reject(error);
						});
				}
			});
		}
		
		// Login key
		static loginKey(extendedPrivateKey) {
		
			// Return promise
			return new Promise(function(resolve, reject) {
			
				// Create child identifier
				var childIdentifier = new Identifier().getChild();
				childIdentifier.getPaths()[1] = 2;
				
				// Return deriving root key from extended private key, child identifier, and no switch type
				return Crypto.deriveSecretKey(extendedPrivateKey, new BigNumber(0), childIdentifier, Crypto.SWITCH_TYPE_NONE).then(function(rootKey) {
				
					// Check if getting secret key from root key was successful
					var secretKey = Blake2b.compute(Crypto.SECP256K1_SECRET_KEY_LENGTH, rootKey, new Uint8Array([]));
					
					if(secretKey !== Blake2b.OPERATION_FAILED) {
					
						// Securely clear root key
						rootKey.fill(0);
					
						// Check if secret key is a valid secret key
						if(Secp256k1Zkp.isValidSecretKey(secretKey) === true) {
					
							// Resolve the secret key
							resolve(secretKey);
						}
						
						// Otherwise
						else {
						
							// Securely clear secret key
							secretKey.fill(0);
							
							// Reject error
							reject("Secret key is not a valid secret key.");
						}
					}
					
					// Otherwise
					else {
					
						// Securely clear root key
						rootKey.fill(0);
					
						// Reject error
						reject("Getting secret key from root key failed.");
					}
				
				// Catch errors
				}).catch(function(error) {
				
					// Reject error
					reject(error);
				});
			});
		}
		
		// AES decrypt
		static aesDecrypt(encryptedData, key) {
		
			// Return promise
			return new Promise(function(resolve, reject) {
			
				// Return importing the key
				return crypto["subtle"].importKey("raw", key, {
				
					// Name
					"name": Crypto.AES_ALGORITHM
				
				}, false, [
				
					// Decrypt
					"decrypt"
				
				]).then(function(cryptoKey) {
				
					// Return decrypting the encrypted data using the AES key
					return crypto["subtle"].decrypt({
					
						// Name
						"name": Crypto.AES_ALGORITHM,
						
						// Initialization vector
						"iv": (new Uint8Array(Crypto.AES_INITIALIZATION_VECTOR_SIZE)).fill(0)
						
					}, cryptoKey, encryptedData).then(function(decryptedData) {
					
						// TODO Securely clear cryptoKey
					
						// Resolve decrypted data
						resolve(new Uint8Array(decryptedData));
						
					// Catch errors
					}).catch(function(error) {
					
						// TODO Securely clear cryptoKey
					
						// Reject error
						reject(error);
					});
				
				// Catch errors
				}).catch(function(error) {
				
					// Reject error
					reject(error);
				});
			});
		}
		
		// Switch type none
		static get SWITCH_TYPE_NONE() {
		
			// Return switch type none
			return 0;
		}

		// Switch type regular
		static get SWITCH_TYPE_REGULAR() {
		
			// Return switch type regular
			return Crypto.SWITCH_TYPE_NONE + 1;
		}
		
		// Secp256k1 secret key length
		static get SECP256K1_SECRET_KEY_LENGTH() {
		
			// Return secp256k1 secret key length
			return 32;
		}
		
		// Secp256k1 public key length
		static get SECP256K1_PUBLIC_KEY_LENGTH() {
		
			// Return secp256k1 public key length
			return 33;
		}
		
		// Blinding factor length
		static get BLINDING_FACTOR_LENGTH() {
		
			// Return blinding factor length
			return 32;
		}
		
		// Commit length
		static get COMMIT_LENGTH() {
		
			// Return commit length
			return 33;
		}
		
		// Single-signer signature length
		static get SINGLE_SIGNER_SIGNATURE_LENGTH() {
		
			// Return single-signer signature length
			return 64;
		}
		
		// Single-signer message length
		static get SINGLE_SIGNER_MESSAGE_LENGTH() {
		
			// Return single-signer message length
			return 32;
		}
		
		// Proof length
		static get PROOF_LENGTH() {
		
			// Return proof length
			return 675;
		}
		
		// Zero blinding factor
		static get ZERO_BLINDING_FACTOR() {
		
			// No zero blinding factor
			return (new Uint8Array(Crypto.BLINDING_FACTOR_LENGTH)).fill(0);
		}
		
		// Zero secret key
		static get ZERO_SECRET_KEY() {
		
			// No zero blinding factor
			return (new Uint8Array(Crypto.SECP256K1_SECRET_KEY_LENGTH)).fill(0);
		}
		
		// Ed25519 secret key length
		static get ED25519_SECRET_KEY_LENGTH() {
		
			// Return Ed25519 secret key length
			return 32;
		}
		
		// Ed25519 public key length
		static get ED25519_PUBLIC_KEY_LENGTH() {
		
			// Return Ed25519 public key length
			return 32;
		}
		
		// X25519 secret key length
		static get X25519_SECRET_KEY_LENGTH() {
		
			// Return X25519 secret key length
			return 32;
		}
		
		// X25519 public key length
		static get X25519_PUBLIC_KEY_LENGTH() {
		
			// Return X25519 public key length
			return 32;
		}
		
		// Ed25519 signature length
		static get ED25519_SIGNATURE_LENGTH() {
		
			// Return Ed25519 signature length
			return 64;
		}
		
		// Maximum message hash signature length
		static get MAXIMUM_MESSAGE_HASH_SIGNATURE_LENGTH() {
		
			// Return maximum message hash signature length
			return 72;
		}
		
		// Nonce length
		static get NONCE_LENGTH() {
		
			// Return nonce length
			return 32;
		}
		
		// Tau x length
		static get TAU_X_LENGTH() {
		
			// Return tau x length
			return 32;
		}
		
		// AES key length
		static get AES_KEY_LENGTH() {
		
			// Return AES key length
			return 32;
		}
		
	// Private

		// Derive secret key and chain code
		static deriveSecretKeyAndChainCode(secretKey, chainCode, path, useBip39 = false) {
		
			// Return promise
			return new Promise(function(resolve, reject) {

				// Return creating crypto key from chain code
				return crypto["subtle"].importKey("raw", chainCode, {
				
					// Name
					"name": Crypto.CHAIN_CODE_ENCRYPTION_ALGORITHM,
					
					// Hash
					"hash": {
					
						// Name
						"name": Crypto.CHAIN_CODE_DIGEST_ALGORITHM
					}
				}, false, [
				
					// Sign
					"sign"
				
				]).then(function(cryptoKey) {
				
					// Check if path is hardened
					if(Identifier.isPathHardened(path) === true) {
					
						// Get hash value from secret key
						var hashValue = Common.mergeArrays([
						
							// Zero
							new Uint8Array([0]),
							
							// Secret key
							secretKey
						]);
					}
					
					// Otherwise
					else {
					
						// Check if getting hash value as public key from secret key failed
						var hashValue = Secp256k1Zkp.publicKeyFromSecretKey(secretKey);
						
						if(hashValue === Secp256k1Zkp.OPERATION_FAILED) {
						
							// TODO Securely clear cryptoKey
						
							// Reject error
							reject("Getting public key from secret key failed.");
							
							// Return
							return;
						}
					}
					
					// Create secret key and chain code
					var createSecretKeyAndChainCode = function(hashValueCandidate) {
					
						// Return promise
						return new Promise(function(resolve, reject) {
					
							// Append path to hash value candidate
							var pathBuffer = new ArrayBuffer(Uint32Array["BYTES_PER_ELEMENT"]);
							
							var pathBufferView = new DataView(pathBuffer);
							pathBufferView.setUint32(0, path, false);
							
							var newHashValue = Common.mergeArrays([
							
								// Hash value candidate
								hashValueCandidate,
								
								// Path
								new Uint8Array(pathBuffer)
							]);
							
							// Securely clear hash value candidate
							hashValueCandidate.fill(0);
							
							// Return creating new extended private key from signing the new hash value
							return crypto["subtle"].sign(Crypto.CHAIN_CODE_ENCRYPTION_ALGORITHM, cryptoKey, newHashValue).then(function(extendedPrivateKey) {
							
								// Securely clear new hash value
								newHashValue.fill(0);
							
								// Get extended private key in correct format
								extendedPrivateKey = new Uint8Array(extendedPrivateKey);
								
								// Get new secret key from the extended private key
								var newSecretKey = extendedPrivateKey.subarray(0, Crypto.SECP256K1_SECRET_KEY_LENGTH);
								
								// Check if new secret key is a valid secret key or using BIP39 and new secret key is zero
								if(Secp256k1Zkp.isValidSecretKey(newSecretKey) === true || (useBip39 === true && Common.arraysAreEqualTimingSafe(newSecretKey, Crypto.ZERO_SECRET_KEY) === true)) {
								
									// Check if adding old secret key to the new secret key was successful
									var updatedSecretKey = Secp256k1Zkp.secretKeyTweakAdd(newSecretKey, secretKey);
									
									if(updatedSecretKey !== Secp256k1Zkp.OPERATION_FAILED) {
									
										// Get updated chain code from the extended private key
										var updatedChainCode = new Uint8Array(extendedPrivateKey.subarray(Crypto.SECP256K1_SECRET_KEY_LENGTH));
										
										// Securely clear extended private key
										extendedPrivateKey.fill(0);
										
										// Resolve new extended private key
										resolve([
										
											// Updated secret key
											updatedSecretKey,
											
											// Updated chain code
											updatedChainCode
										]);
									}
									
									// Otherwise
									else {
									
										// Check if using BIP39
										if(useBip39 === true) {
										
											// Set next hash value candidate
											var nextHashValueCandidate = Common.mergeArrays([
											
												// One
												new Uint8Array([1]),
												
												// New chain code
												extendedPrivateKey.subarray(Crypto.SECP256K1_SECRET_KEY_LENGTH)
											]);
											
											// Securely clear extended private key
											extendedPrivateKey.fill(0);
											
											// Return creating secret key and chain code
											return createSecretKeyAndChainCode(nextHashValueCandidate).then(function(secretKeyAndChainCode) {
											
												// Resolve secret key and chain code
												resolve(secretKeyAndChainCode);
											
											// Catch error
											}).catch(function(error) {
											
												// Reject error
												reject(error);
											});
										}
										
										// Otherwise
										else {
									
											// Securely clear extended private key
											extendedPrivateKey.fill(0);
										
											// Reject error
											reject("Performing secret key tweak add failed.");
										}
									}
								}
						
								// Otherwise
								else {
								
									// Check if using BIP39
									if(useBip39 === true) {
									
										// Set next hash value candidate
										var nextHashValueCandidate = Common.mergeArrays([
										
											// One
											new Uint8Array([1]),
											
											// New chain code
											extendedPrivateKey.subarray(Crypto.SECP256K1_SECRET_KEY_LENGTH)
										]);
										
										// Securely clear extended private key
										extendedPrivateKey.fill(0);
										
										// Return creating secret key and chain code
										return createSecretKeyAndChainCode(nextHashValueCandidate).then(function(secretKeyAndChainCode) {
										
											// Resolve secret key and chain code
											resolve(secretKeyAndChainCode);
										
										// Catch error
										}).catch(function(error) {
										
											// Reject error
											reject(error);
										});
									}
									
									// Otherwise
									else {
									
										// Securely clear extended private key
										extendedPrivateKey.fill(0);
									
										// Reject error
										reject("New secret key isn't a valid secret key or using BIP39 and new secret key isn't zero.");
									}
								}
							
							// Catch errors
							}).catch(function(error) {
							
								// Securely clear new hash value
								newHashValue.fill(0);
							
								// Reject error
								reject("Creating new extended private key failed.");
							});
						});
					};
					
					// Return creating secret key and chain code
					return createSecretKeyAndChainCode(hashValue).then(function(secretKeyAndChainCode) {
					
						// TODO Securely clear cryptoKey
					
						// Resolve secret key and chain code
						resolve(secretKeyAndChainCode);
						
					// Catch errors
					}).catch(function(error) {
					
						// TODO Securely clear cryptoKey
					
						// Reject error
						reject(error);
					});
				
				// Catch errors
				}).catch(function(error) {
				
					// Reject error
					reject("Creating crypto key failed.");
				});
			});
		}
		
		// Chain code length
		static get CHAIN_CODE_LENGTH() {
		
			// Return chain code length
			return 32;
		}
		
		// Secret key index
		static get SECRET_KEY_INDEX() {
		
			// Return secret key index
			return 0;
		}

		// Chain code index
		static get CHAIN_CODE_INDEX() {
		
			// Return chain code index
			return Crypto.SECRET_KEY_INDEX + 1;
		}
		
		// Chain code encryption algorithm
		static get CHAIN_CODE_ENCRYPTION_ALGORITHM() {
		
			// Return chain code encryption algorithm
			return "HMAC";
		}

		// Chain code digest algorithm
		static get CHAIN_CODE_DIGEST_ALGORITHM() {
		
			// Return chain code digest algorithm
			return "SHA-512";
		}
		
		// Address key amount
		static get ADDRESS_KEY_AMOUNT() {
		
			// Return address key amount
			return new BigNumber(713);
		}
		
		// Address key seed
		static get ADDRESS_KEY_SEED() {
		
			// Return address key seed
			return "Grinbox_seed";
		}
		
		// Address key encryption algorithm
		static get ADDRESS_KEY_ENCRYPTION_ALGORITHM() {
		
			// Return address key encryption algorithm
			return "HMAC";
		}

		// Address key digest algorithm
		static get ADDRESS_KEY_DIGEST_ALGORITHM() {
		
			// Return address key digest algorithm
			return "SHA-512";
		}
		
		// AES algorithm
		static get AES_ALGORITHM() {
		
			// Return AES algorithm
			return "AES-CBC";
		}
		
		// AES initialization vector size
		static get AES_INITIALIZATION_VECTOR_SIZE() {
		
			// Return AES initialization vector size
			return 16;
		}
}


// Main function

// Set global object's crypto
globalThis["Crypto"] = Crypto;
