// Use strict
"use strict";


// Classes

// Slatepack class
class Slatepack {

	// Public
	
		// Public key to Slatepack address
		static publicKeyToSlatepackAddress(publicKey) {
		
			// Check it public key isn't the correct length
			if(publicKey["length"] !== Crypto.ED25519_PUBLIC_KEY_LENGTH) {
			
				// Throw error
				throw "Invalid public key.";
			}
		
			// Return getting Slatepack address from address public key and human-readable part
			return bech32.encode(Consensus.SLATEPACK_ADDRESS_HUMAN_READABLE_PART, bech32.toWords(publicKey));
		}
		
		// Slatepack address to public key
		static slatepackAddressToPublicKey(slatepackAddress) {
		
			// Check it Slatepack address isn't the correct length
			if(slatepackAddress["length"] !== Slatepack.ADDRESS_WITHOUT_HUMAN_READABLE_PART_LENGTH + Consensus.SLATEPACK_ADDRESS_HUMAN_READABLE_PART["length"]) {
			
				// Throw error
				throw "Invalid Slatepack address.";
			}
			
			// Try
			try {
			
				// Decode Slatepack address
				var decodedAddress = bech32.decode(slatepackAddress);
			}
			
			// Catch errors
			catch(error) {
			
				// Throw error
				throw "Invalid Slatepack address.";
			}
			
			// Check if decoded address's length is invalid
			var bytes = bech32.fromWords(decodedAddress["words"]);
			if(bytes["length"] !== Crypto.ED25519_PUBLIC_KEY_LENGTH) {
			
				// Throw error
				throw "Invalid Slatepack address.";
			}
			
			// Check if human-readable part is invalid
			if(decodedAddress["prefix"] !== Consensus.SLATEPACK_ADDRESS_HUMAN_READABLE_PART) {
			
				// Throw error
				throw "Invalid Slatepack address.";
			}
			
			// Return decoded address
			return new Uint8Array(bytes);
		}
		
		// Is encrypted Slatepack
		static isEncryptedSlatepack(slatepack) {
		
			// Check wallet type
			switch(Consensus.getWalletType()) {
			
				// MWC wallet
				case Consensus.MWC_WALLET_TYPE:
		
					// Get offset of Slatepack's header delimiter
					var headerDelimiter = slatepack.indexOf(Slatepack.COMPONENT_SEPARATOR);
					
					// Check if header delmiter doesn't exist
					if(headerDelimiter === Common.INDEX_NOT_FOUND) {
					
						// Return false
						return false;
					}
					
					// Return if Slatepack's header is an encrypted header
					return Slatepack.ENCRYPTED_HEADER_PATTERN.test(slatepack.substring(0, headerDelimiter)) === true;
			}
			
			// Return false
			return false;
		}
		
		// Get Slatepack sender public key
		static getSlatepackSenderPublicKey(slatepack) {
		
			// Get offset of Slatepack's header delimiter
			var headerDelimiter = slatepack.indexOf(Slatepack.COMPONENT_SEPARATOR);
			
			// Check if header delmiter doesn't exist
			if(headerDelimiter === Common.INDEX_NOT_FOUND) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
			
			// Check if Slatepack's header isn't an encrypted header
			if(Slatepack.ENCRYPTED_HEADER_PATTERN.test(slatepack.substring(0, headerDelimiter)) === false) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
			
			// Get offset of Slatepack's payload delimiter
			var payloadDelimiter = slatepack.indexOf(Slatepack.COMPONENT_SEPARATOR, headerDelimiter + Slatepack.COMPONENT_SEPARATOR["length"]);
			
			// Check if payload delmiter doesn't exist
			if(payloadDelimiter === Common.INDEX_NOT_FOUND) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
			
			// Get offset of Slatepack's footer delimiter
			var footerDelimiter = slatepack.indexOf(Slatepack.COMPONENT_SEPARATOR, payloadDelimiter + Slatepack.COMPONENT_SEPARATOR["length"]);
			
			// Check if footer delmiter doesn't exist
			if(footerDelimiter === Common.INDEX_NOT_FOUND) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
			
			// Check if Slatepack's footer isn't an encrypted footer
			if(Slatepack.ENCRYPTED_FOOTER_PATTERN.test(slatepack.substring(payloadDelimiter + Slatepack.COMPONENT_SEPARATOR["length"], footerDelimiter)) === false) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
			
			// Get Slatepack's payload without whitespace
			var payload = slatepack.substring(headerDelimiter + Slatepack.COMPONENT_SEPARATOR["length"], payloadDelimiter).replace(Slatepack.WHITESPACE_PATTERN, "");
			
			// Try
			try {
			
				// Decode payload
				var decodedPayload = Base58.decode(payload);
			}
			
			// Catch errors
			catch(error) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
			
			// Check if decoded payload is too short
			if(decodedPayload["length"] < Base58.CHECKSUM_LENGTH + Uint8Array["BYTES_PER_ELEMENT"]) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
			
			// Get checksum and data from the decoded payload
			var checksum = decodedPayload.subarray(0, Base58.CHECKSUM_LENGTH);
			
			var data = decodedPayload.subarray(Base58.CHECKSUM_LENGTH);
			
			// Check if checksum is invalid
			if(Common.arraysAreEqual(checksum, Base58.getChecksum(data)) === false) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
			
			// Get version from data
			var version = data[0];
			
			// Check if version isn't supported
			if(version > Slatepack.VERSION) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
			
			// Check if data's length is too short
			if(data["length"] < data["BYTES_PER_ELEMENT"] + Crypto.ED25519_PUBLIC_KEY_LENGTH + Crypto.ED25519_PUBLIC_KEY_LENGTH + Slatepack.NONCE_LENGTH + Uint16Array["BYTES_PER_ELEMENT"]) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
		
			// Get sender public key from data
			var senderPublicKey = data.subarray(data["BYTES_PER_ELEMENT"], data["BYTES_PER_ELEMENT"] + Crypto.ED25519_PUBLIC_KEY_LENGTH);
			
			// Try
			try {
			
				// Get Tor address from the sender public key
				Tor.publicKeyToTorAddress(senderPublicKey);
			}
			
			// Catch errors
			catch(error) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
			
			// Return sender public key
			return senderPublicKey;
		}
		
		// Get Slatepack receiver public key
		static getSlatepackReceiverPublicKey(slatepack) {
		
			// Get offset of Slatepack's header delimiter
			var headerDelimiter = slatepack.indexOf(Slatepack.COMPONENT_SEPARATOR);
			
			// Check if header delmiter doesn't exist
			if(headerDelimiter === Common.INDEX_NOT_FOUND) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
			
			// Check if Slatepack's header isn't an encrypted header
			if(Slatepack.ENCRYPTED_HEADER_PATTERN.test(slatepack.substring(0, headerDelimiter)) === false) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
			
			// Get offset of Slatepack's payload delimiter
			var payloadDelimiter = slatepack.indexOf(Slatepack.COMPONENT_SEPARATOR, headerDelimiter + Slatepack.COMPONENT_SEPARATOR["length"]);
			
			// Check if payload delmiter doesn't exist
			if(payloadDelimiter === Common.INDEX_NOT_FOUND) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
			
			// Get offset of Slatepack's footer delimiter
			var footerDelimiter = slatepack.indexOf(Slatepack.COMPONENT_SEPARATOR, payloadDelimiter + Slatepack.COMPONENT_SEPARATOR["length"]);
			
			// Check if footer delmiter doesn't exist
			if(footerDelimiter === Common.INDEX_NOT_FOUND) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
			
			// Check if Slatepack's footer isn't an encrypted footer
			if(Slatepack.ENCRYPTED_FOOTER_PATTERN.test(slatepack.substring(payloadDelimiter + Slatepack.COMPONENT_SEPARATOR["length"], footerDelimiter)) === false) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
			
			// Get Slatepack's payload without whitespace
			var payload = slatepack.substring(headerDelimiter + Slatepack.COMPONENT_SEPARATOR["length"], payloadDelimiter).replace(Slatepack.WHITESPACE_PATTERN, "");
			
			// Try
			try {
			
				// Decode payload
				var decodedPayload = Base58.decode(payload);
			}
			
			// Catch errors
			catch(error) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
			
			// Check if decoded payload is too short
			if(decodedPayload["length"] < Base58.CHECKSUM_LENGTH + Uint8Array["BYTES_PER_ELEMENT"]) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
			
			// Get checksum and data from the decoded payload
			var checksum = decodedPayload.subarray(0, Base58.CHECKSUM_LENGTH);
			
			var data = decodedPayload.subarray(Base58.CHECKSUM_LENGTH);
			
			// Check if checksum is invalid
			if(Common.arraysAreEqual(checksum, Base58.getChecksum(data)) === false) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
			
			// Get version from data
			var version = data[0];
			
			// Check if version isn't supported
			if(version > Slatepack.VERSION) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
			
			// Check if data's length is too short
			if(data["length"] < data["BYTES_PER_ELEMENT"] + Crypto.ED25519_PUBLIC_KEY_LENGTH + Crypto.ED25519_PUBLIC_KEY_LENGTH + Slatepack.NONCE_LENGTH + Uint16Array["BYTES_PER_ELEMENT"]) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
		
			// Get receiver public key from data
			var receiverPublicKey = data.subarray(data["BYTES_PER_ELEMENT"] + Crypto.ED25519_PUBLIC_KEY_LENGTH, data["BYTES_PER_ELEMENT"] + Crypto.ED25519_PUBLIC_KEY_LENGTH + Crypto.ED25519_PUBLIC_KEY_LENGTH);
			
			// Try
			try {
			
				// Get Tor address from the receiver public key
				Tor.publicKeyToTorAddress(receiverPublicKey);
			}
			
			// Catch errors
			catch(error) {
			
				// Return no public key
				return Slatepack.NO_PUBLIC_KEY;
			}
			
			// Return receiver public key
			return receiverPublicKey;
		}
		
		// Decode Slatepack
		static decodeSlatepack(slatepack, secretKeyOrHardwareWallet = Slatepack.NO_SECRET_KEY, hardwareWalletLockedText = HardwareWallet.NO_TEXT, hardwareWalletLockedTextArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
		
			// Return promise
			return new Promise(function(resolve, reject) {
		
				// Get offset of Slatepack's header delimiter
				var headerDelimiter = slatepack.indexOf(Slatepack.COMPONENT_SEPARATOR);
				
				// Check if header delmiter doesn't exist
				if(headerDelimiter === Common.INDEX_NOT_FOUND) {
				
					// Reject error
					reject("Unsupported Slatepack.");
					
					// Return
					return;
				}
				
				// Check wallet type
				switch(Consensus.getWalletType()) {
				
					// MWC wallet
					case Consensus.MWC_WALLET_TYPE:
				
						// Check if Slatepack's header isn't an encrypted header
						if(Slatepack.ENCRYPTED_HEADER_PATTERN.test(slatepack.substring(0, headerDelimiter)) === false) {
						
							// Check if Slatepack's header isn't a binary header
							if(Slatepack.BINARY_HEADER_PATTERN.test(slatepack.substring(0, headerDelimiter)) === false) {
							
								// Reject error
								reject("Unsupported Slatepack.");
								
								// Return
								return;
							}
							
							// Otherwise
							else {
							
								// Clear encrypted
								var encrypted = false;
							}
						}
						
						// Otherwise
						else {
						
							// Set encrypted
							var encrypted = true;
						}
						
						// Break
						break;
					
					// GRIN wallet
					case Consensus.GRIN_WALLET_TYPE:
					
						// Check if Slatepack's header is invalid
						if(Slatepack.ENCRYPTED_HEADER_PATTERN.test(slatepack.substring(0, headerDelimiter)) === false) {
						
							// Reject error
							reject("Unsupported Slatepack.");
							
							// Return
							return;
						}
						
						// Break
						break;
				}
				
				// Get offset of Slatepack's payload delimiter
				var payloadDelimiter = slatepack.indexOf(Slatepack.COMPONENT_SEPARATOR, headerDelimiter + Slatepack.COMPONENT_SEPARATOR["length"]);
				
				// Check if payload delmiter doesn't exist
				if(payloadDelimiter === Common.INDEX_NOT_FOUND) {
				
					// Reject error
					reject("Unsupported slate.");
					
					// Return
					return;
				}
				
				// Get offset of Slatepack's footer delimiter
				var footerDelimiter = slatepack.indexOf(Slatepack.COMPONENT_SEPARATOR, payloadDelimiter + Slatepack.COMPONENT_SEPARATOR["length"]);
				
				// Check if footer delmiter doesn't exist
				if(footerDelimiter === Common.INDEX_NOT_FOUND) {
				
					// Reject error
					reject("Unsupported Slatepack.");
					
					// Return
					return;
				}
				
				// Check wallet type
				switch(Consensus.getWalletType()) {
				
					// MWC wallet
					case Consensus.MWC_WALLET_TYPE:
				
						// Check if encrypted Slatepack's footer isn't an encrypted footer
						if(encrypted === true && Slatepack.ENCRYPTED_FOOTER_PATTERN.test(slatepack.substring(payloadDelimiter + Slatepack.COMPONENT_SEPARATOR["length"], footerDelimiter)) === false) {
						
							// Reject error
							reject("Unsupported Slatepack.");
							
							// Return
							return;
						}
						
						// Otherwise check if not encrypted Slatepack's footer isn't a binary footer
						else if(encrypted === false && Slatepack.BINARY_FOOTER_PATTERN.test(slatepack.substring(payloadDelimiter + Slatepack.COMPONENT_SEPARATOR["length"], footerDelimiter)) === false) {
						
							// Reject error
							reject("Unsupported Slatepack.");
							
							// Return
							return;
						}
						
						// Break
						break;
					
					// GRIN wallet
					case Consensus.GRIN_WALLET_TYPE:
					
						// Check if Slatepack's footer is invalid
						if(Slatepack.ENCRYPTED_FOOTER_PATTERN.test(slatepack.substring(payloadDelimiter + Slatepack.COMPONENT_SEPARATOR["length"], footerDelimiter)) === false) {
						
							// Reject error
							reject("Unsupported Slatepack.");
							
							// Return
							return;
						}
						
						// Break
						break;
				}
				
				// Get Slatepack's payload without whitespace
				var payload = slatepack.substring(headerDelimiter + Slatepack.COMPONENT_SEPARATOR["length"], payloadDelimiter).replace(Slatepack.WHITESPACE_PATTERN, "");
				
				// Try
				try {
				
					// Decode payload
					var decodedPayload = Base58.decode(payload);
				}
				
				// Catch errors
				catch(error) {
				
					// Reject error
					reject("Unsupported Slatepack.");
					
					// Return
					return;
				}
				
				// Check if decoded payload is too short
				if(decodedPayload["length"] < Base58.CHECKSUM_LENGTH) {
				
					// Reject error
					reject("Unsupported Slatepack.");
					
					// Return
					return;
				}
				
				// Get checksum and data from the decoded payload
				var checksum = decodedPayload.subarray(0, Base58.CHECKSUM_LENGTH);
				
				var data = decodedPayload.subarray(Base58.CHECKSUM_LENGTH);
				
				// Check if checksum is invalid
				if(Common.arraysAreEqual(checksum, Base58.getChecksum(data)) === false) {
				
					// Reject error
					reject("Unsupported Slatepack.");
					
					// Return
					return;
				}
				
				// Check wallet type
				switch(Consensus.getWalletType()) {
				
					// MWC wallet
					case Consensus.MWC_WALLET_TYPE:
					
						// Check if data's length is too short
						if(data["length"] < data["BYTES_PER_ELEMENT"]) {
						
							// Reject error
							reject("Unsupported Slatepack.");
							
							// Return
							return;
						}
				
						// Get version from data
						var version = data[0];
						
						// Check if version isn't supported
						if(version > Slatepack.VERSION) {
						
							// Reject error
							reject("Unsupported Slatepack.");
							
							// Return
							return;
						}
						
						// Check if encrypted
						if(encrypted === true) {
						
							// Check if secret key or hardware wallet don't exist
							if(secretKeyOrHardwareWallet === Slatepack.NO_SECRET_KEY) {
							
								// Reject error
								reject("Decrypting Slatepack failed.");
								
								// Return
								return;
							}
						
							// Check if data's length is too short
							if(data["length"] < data["BYTES_PER_ELEMENT"] + Crypto.ED25519_PUBLIC_KEY_LENGTH + Crypto.ED25519_PUBLIC_KEY_LENGTH + Slatepack.NONCE_LENGTH + Uint16Array["BYTES_PER_ELEMENT"]) {
							
								// Reject error
								reject("Unsupported Slatepack.");
								
								// Return
								return;
							}
						
							// Get sender public key from data
							var senderPublicKey = data.subarray(data["BYTES_PER_ELEMENT"], data["BYTES_PER_ELEMENT"] + Crypto.ED25519_PUBLIC_KEY_LENGTH);
							
							// Try
							try {
							
								// Get Tor address from the sender public key
								Tor.publicKeyToTorAddress(senderPublicKey);
							}
							
							// Catch errors
							catch(error) {
							
								// Reject error
								reject("Invalid sender public key.");
							
								// Return
								return;
							}
							
							// Get receiver public key from data
							var receiverPublicKey = data.subarray(data["BYTES_PER_ELEMENT"] + Crypto.ED25519_PUBLIC_KEY_LENGTH, data["BYTES_PER_ELEMENT"] + Crypto.ED25519_PUBLIC_KEY_LENGTH + Crypto.ED25519_PUBLIC_KEY_LENGTH);
							
							// Try
							try {
							
								// Get Tor address from the receiver public key
								Tor.publicKeyToTorAddress(receiverPublicKey);
							}
							
							// Catch errors
							catch(error) {
							
								// Reject error
								reject("Invalid receiver public key.");
							
								// Return
								return;
							}
							
							// Get expected receiver public key
							var getExpectedReceiverPublicKey = function() {
							
								// Return promise
								return new Promise(function(resolve, reject) {
						
									// Check if a secret key is provided
									if(secretKeyOrHardwareWallet instanceof Uint8Array === true) {
									
										// Get secret key
										var secretKey = secretKeyOrHardwareWallet;
								
										// Check if getting expected receiver public key from secret key failed
										var expectedReceiverPublicKey = Ed25519.publicKeyFromSecretKey(secretKey);
										
										if(expectedReceiverPublicKey === Ed25519.OPERATION_FAILED) {
										
											// Reject error
											reject("Getting expected receiver public key from secret key failed.");
											
											// Return
											return;
										}
										
										// Resolve expected receiver public key
										resolve(expectedReceiverPublicKey);
									}
									
									// Otherwise
									else {
									
										// Get hardware wallet
										var hardwareWallet = secretKeyOrHardwareWallet;
										
										// Return getting Tor address from the hardware wallet
										return hardwareWallet.getTorAddress(Wallet.PAYMENT_PROOF_TOR_ADDRESS_KEY_INDEX, hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(torAddress) {
										
											// Try
											try {
											
												// Get expected receiver public key from the Tor address
												var expectedReceiverPublicKey = Tor.torAddressToPublicKey(torAddress);
											}
											
											// Catch errors
											catch(error) {
											
												// Reject error
												reject(error);
												
												// Return
												return;
											}
											
											// Resolve expected receiver public key
											resolve(expectedReceiverPublicKey);
											
										// Catch errors
										}).catch(function(error) {
										
											// Reject error
											reject(error);
										});
									}
								});
							};
							
							// Return getting expected receiver public key
							return getExpectedReceiverPublicKey().then(function(expectedReceiverPublicKey) {
							
								// Check if receiver public key doesn't match the expected receiver public key
								if(Common.arraysAreEqual(receiverPublicKey, expectedReceiverPublicKey) === false) {
								
									// Reject error
									reject("Invalid receiver public key.");
								}
								
								// Otherwise
								else {
							
									// Get nonce form data
									var nonce = data.subarray(data["BYTES_PER_ELEMENT"] + Crypto.ED25519_PUBLIC_KEY_LENGTH + Crypto.ED25519_PUBLIC_KEY_LENGTH, data["BYTES_PER_ELEMENT"] + Crypto.ED25519_PUBLIC_KEY_LENGTH + Crypto.ED25519_PUBLIC_KEY_LENGTH + Slatepack.NONCE_LENGTH);
									
									// Get length from data
									var length = (new BigNumber(Common.HEX_PREFIX + Common.toHexString(data.subarray(data["BYTES_PER_ELEMENT"] + Crypto.ED25519_PUBLIC_KEY_LENGTH + Crypto.ED25519_PUBLIC_KEY_LENGTH + Slatepack.NONCE_LENGTH, data["BYTES_PER_ELEMENT"] + Crypto.ED25519_PUBLIC_KEY_LENGTH + Crypto.ED25519_PUBLIC_KEY_LENGTH + Slatepack.NONCE_LENGTH + Uint16Array["BYTES_PER_ELEMENT"])))).toNumber();
									
									// Check if length isn't supported
									if(length === 0 || length !== data["length"] - (data["BYTES_PER_ELEMENT"] + Crypto.ED25519_PUBLIC_KEY_LENGTH + Crypto.ED25519_PUBLIC_KEY_LENGTH + Slatepack.NONCE_LENGTH + Uint16Array["BYTES_PER_ELEMENT"])) {
									
										// Reject error
										reject("Unsupported Slatepack.");
										
										// Return
										return;
									}
									
									// Get encrypted slate from data
									var encryptedSlate = data.subarray(data["BYTES_PER_ELEMENT"] + Crypto.ED25519_PUBLIC_KEY_LENGTH + Crypto.ED25519_PUBLIC_KEY_LENGTH + Slatepack.NONCE_LENGTH + Uint16Array["BYTES_PER_ELEMENT"]);
									
									// Check if a secret key is provided
									if(secretKeyOrHardwareWallet instanceof Uint8Array === true) {
									
										// Get secret key
										var secretKey = secretKeyOrHardwareWallet;
									
										// Return decrypting the encrypted slate
										return Slatepack.decrypt(secretKey, senderPublicKey, encryptedSlate, nonce).then(function(slate) {
										
											// Check if slate's length isn't supported
											if(slate["length"] <= Uint32Array["BYTES_PER_ELEMENT"]) {
											
												// Reject error
												reject("Unsupported Slatepack.");
												
												// Return
												return;
											}
											
											// Get expected checksum from slate
											var expectedChecksum = (new BigNumber(Common.HEX_PREFIX + Common.toHexString(slate.subarray(slate["length"] - Uint32Array["BYTES_PER_ELEMENT"])))).toNumber();
											
											// Remove expected checksum from slate
											slate = slate.subarray(0, slate["length"] - Uint32Array["BYTES_PER_ELEMENT"]);
											
											// Calculate checksum
											var checksum = CRC32.buf(Common.mergeArrays([
											
												// Version
												new Uint8Array([version]),
												
												// Sender public key
												senderPublicKey,
												
												// Receiver public key
												receiverPublicKey,
												
												// Slate
												slate
											]));
											
											// Check if checksum doesn't match the expected checksum
											if((new Uint32Array([checksum]))[0] !== expectedChecksum) {
											
												// Reject error
												reject("Unsupported Slatepack.");
												
												// Return
												return;
											}
											
											// Resolve slate
											resolve(slate);
										
										// Catch errors
										}).catch(function(error) {
										
											// Reject error
											reject(error);
										});
									}
									
									// Otherwise
									else {
									
										// Get hardware wallet
										var hardwareWallet = secretKeyOrHardwareWallet;
										
										// Return decrypting the slate with the hardware wallet
										return hardwareWallet.decryptSlate(Wallet.PAYMENT_PROOF_TOR_ADDRESS_KEY_INDEX, encryptedSlate, Tor.publicKeyToTorAddress(senderPublicKey), nonce, HardwareWallet.NO_SALT, hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(slate) {
										
											// Check if slate's length isn't supported
											if(slate["length"] <= Uint32Array["BYTES_PER_ELEMENT"]) {
											
												// Reject error
												reject("Unsupported Slatepack.");
												
												// Return
												return;
											}
											
											// Get expected checksum from slate
											var expectedChecksum = (new BigNumber(Common.HEX_PREFIX + Common.toHexString(slate.subarray(slate["length"] - Uint32Array["BYTES_PER_ELEMENT"])))).toNumber();
											
											// Remove expected checksum from slate
											slate = slate.subarray(0, slate["length"] - Uint32Array["BYTES_PER_ELEMENT"]);
											
											// Calculate checksum
											var checksum = CRC32.buf(Common.mergeArrays([
											
												// Version
												new Uint8Array([version]),
												
												// Sender public key
												senderPublicKey,
												
												// Receiver public key
												receiverPublicKey,
												
												// Slate
												slate
											]));
											
											// Check if checksum doesn't match the expected checksum
											if((new Uint32Array([checksum]))[0] !== expectedChecksum) {
											
												// Reject error
												reject("Unsupported Slatepack.");
												
												// Return
												return;
											}
											
											// Resolve slate
											resolve(slate);
										
										// Catch errors
										}).catch(function(error) {
										
											// Reject error
											reject(error);
										});
									}
								}
								
							// Catch errors
							}).catch(function(error) {
							
								// Reject error
								reject(error);
							});
						}
						
						// Otherwise
						else {
						
							// Check if data's length is too short
							if(data["length"] < data["BYTES_PER_ELEMENT"] + Uint16Array["BYTES_PER_ELEMENT"]) {
							
								// Reject error
								reject("Unsupported Slatepack.");
								
								// Return
								return;
							}
						
							// Get length from data
							var length = (new BigNumber(Common.HEX_PREFIX + Common.toHexString(data.subarray(data["BYTES_PER_ELEMENT"], data["BYTES_PER_ELEMENT"] + Uint16Array["BYTES_PER_ELEMENT"])))).toNumber();
							
							// Check if length isn't supported
							if(length === 0 || length !== data["length"] - (data["BYTES_PER_ELEMENT"] + Uint16Array["BYTES_PER_ELEMENT"])) {
							
								// Reject error
								reject("Unsupported Slatepack.");
								
								// Return
								return;
							}
							
							// Get slate from data
							var slate = data.subarray(data["BYTES_PER_ELEMENT"] + Uint16Array["BYTES_PER_ELEMENT"]);
							
							// Resolve slate
							resolve(slate);
						}
						
						// Break
						break;
					
					// GRIN wallet
					case Consensus.GRIN_WALLET_TYPE:
					
						// Check if data's length is too short
						if(data["length"] < data["BYTES_PER_ELEMENT"] + data["BYTES_PER_ELEMENT"] + data["BYTES_PER_ELEMENT"] + Uint16Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"]) {
						
							// Reject error
							reject("Unsupported Slatepack.");
							
							// Return
							return;
						}
						
						// Check data's transfer mode
						switch(data[data["BYTES_PER_ELEMENT"] + data["BYTES_PER_ELEMENT"]]) {
						
							// Plain text
							case Slatepack.PLAIN_TEXT_TRANSFER_MODE:
							
								// Clear encrypted
								var encrypted = false;
								
								// Break
								break;
							
							// Age encrypted transfer mode
							case Slatepack.AGE_ENCRYPTED_TRANSFER_MODE:
							
								// Set encrypted
								var encrypted = true;
								
								// Break
								break;
							
							// Default
							default:
							
								// Reject error
								reject("Unsupported Slatepack.");
								
								// Return
								return;
						}
						
						// Get optional fields length from data
						var optionalFieldsLength = (new BigNumber(Common.HEX_PREFIX + Common.toHexString(data.subarray(data["BYTES_PER_ELEMENT"] + data["BYTES_PER_ELEMENT"] + data["BYTES_PER_ELEMENT"] + Uint16Array["BYTES_PER_ELEMENT"], data["BYTES_PER_ELEMENT"] + data["BYTES_PER_ELEMENT"] + data["BYTES_PER_ELEMENT"] + Uint16Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"])))).toNumber();
						
						// Check if data's length is too short
						if(data["length"] < data["BYTES_PER_ELEMENT"] + data["BYTES_PER_ELEMENT"] + data["BYTES_PER_ELEMENT"] + Uint16Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + optionalFieldsLength + Common.BYTES_IN_A_UINT64) {
						
							// Reject error
							reject("Unsupported Slatepack.");
							
							// Return
							return;
						}
						
						// Get length from data
						var length = new BigNumber(Common.HEX_PREFIX + Common.toHexString(data.subarray(data["BYTES_PER_ELEMENT"] + data["BYTES_PER_ELEMENT"] + data["BYTES_PER_ELEMENT"] + Uint16Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + optionalFieldsLength, data["BYTES_PER_ELEMENT"] + data["BYTES_PER_ELEMENT"] + data["BYTES_PER_ELEMENT"] + Uint16Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + optionalFieldsLength + Common.BYTES_IN_A_UINT64)));
						
						// Check if length isn't supported
						if(length.isZero() === true || length.isEqualTo(data["length"] - (data["BYTES_PER_ELEMENT"] + data["BYTES_PER_ELEMENT"] + data["BYTES_PER_ELEMENT"] + Uint16Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + optionalFieldsLength + Common.BYTES_IN_A_UINT64)) === false) {
						
							// Reject error
							reject("Unsupported Slatepack.");
							
							// Return
							return;
						}
						
						// Check if encrypted
						if(encrypted === true) {
						
							// TODO Support encrypted Grin Slatepacks
							
							// Reject error
							reject("Unsupported Slatepack.");
						}
						
						// Otherwise
						else {
						
							// Get slate from data
							var slate = data.subarray(data["BYTES_PER_ELEMENT"] + data["BYTES_PER_ELEMENT"] + data["BYTES_PER_ELEMENT"] + Uint16Array["BYTES_PER_ELEMENT"] + Uint32Array["BYTES_PER_ELEMENT"] + optionalFieldsLength + Common.BYTES_IN_A_UINT64);
							
							// Resolve slate
							resolve(slate);
						}
						
						// Break
						break;
				}
			});
		}
		
		// Encode Slatepack
		static encodeSlatepack(slate, secretKeyOrHardwareWallet = Slatepack.NO_SECRET_KEY, publicKey = Slatepack.NO_PUBLIC_KEY, hardwareWalletLockedText = HardwareWallet.NO_TEXT, hardwareWalletLockedTextArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
		
			// Return promise
			return new Promise(function(resolve, reject) {
		
				// Check if encrypting slate
				if(secretKeyOrHardwareWallet !== Slatepack.NO_SECRET_KEY && publicKey !== Slatepack.NO_PUBLIC_KEY) {
				
					// Get sender public key
					var getSenderPublicKey = function() {
					
						// Return promise
						return new Promise(function(resolve, reject) {
				
							// Check if a secret key is provided
							if(secretKeyOrHardwareWallet instanceof Uint8Array === true) {
							
								// Get secret key
								var secretKey = secretKeyOrHardwareWallet;
						
								// Check if getting sender public key from secret key failed
								var senderPublicKey = Ed25519.publicKeyFromSecretKey(secretKey);
								
								if(senderPublicKey === Ed25519.OPERATION_FAILED) {
								
									// Reject error
									reject("Getting sender public key from secret key failed.");
									
									// Return
									return;
								}
								
								// Resolve sender public key
								resolve(senderPublicKey);
							}
							
							// Otherwise
							else {
							
								// Get hardware wallet
								var hardwareWallet = secretKeyOrHardwareWallet;
								
								// Return getting Tor address from the hardware wallet
								return hardwareWallet.getTorAddress(Wallet.PAYMENT_PROOF_TOR_ADDRESS_KEY_INDEX, hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(torAddress) {
								
									// Try
									try {
									
										// Get sender public key from the Tor address
										var senderPublicKey = Tor.torAddressToPublicKey(torAddress);
									}
									
									// Catch errors
									catch(error) {
									
										// Reject error
										reject(error);
										
										// Return
										return;
									}
									
									// Resolve sender public key
									resolve(senderPublicKey);
									
								// Catch errors
								}).catch(function(error) {
								
									// Reject error
									reject(error);
								});
							}
						});
					};
					
					// Return getting sender public key
					return getSenderPublicKey().then(function(senderPublicKey) {
					
						// Calculate checksum
						var checksum = CRC32.buf(Common.mergeArrays([
						
							// Version
							new Uint8Array([Slatepack.VERSION]),
							
							// Sender public key
							senderPublicKey,
							
							// Receiver public key
							publicKey,
							
							// Slate
							slate
						]));
						
						// Check if a secret key is provided
						if(secretKeyOrHardwareWallet instanceof Uint8Array === true) {
						
							// Get secret key
							var secretKey = secretKeyOrHardwareWallet;
					
							// Return encrypting the slate and checksum
							return Slatepack.encrypt(secretKey, publicKey, Common.mergeArrays([
							
								// Slate
								slate,
								
								// Checksum
								(new BigNumber((new Uint32Array([checksum]))[0])).toBytes(BigNumber.BIG_ENDIAN, Uint32Array["BYTES_PER_ELEMENT"])
							
							])).then(function(encryptedSlate) {
							
								// Check if encrypted slate is too long
								if(encryptedSlate[Slatepack.ENCRYPTED_DATA_DATA_INDEX]["length"] > Common.UINT16_MAX_VALUE) {
								
									// Reject error
									reject("Encrypted slate is too long.");
									
									// Return
									return;
								}
							
								// Create payload
								var payload = Common.mergeArrays([
								
									// Version
									new Uint8Array([Slatepack.VERSION]),
									
									// Sender public key
									senderPublicKey,
									
									// Receiver public key
									publicKey,
									
									// Nonce
									encryptedSlate[Slatepack.ENCRYPTED_DATA_NONCE_INDEX],
									
									// Encrypted slate length
									(new BigNumber(encryptedSlate[Slatepack.ENCRYPTED_DATA_DATA_INDEX]["length"])).toBytes(BigNumber.BIG_ENDIAN, Uint16Array["BYTES_PER_ELEMENT"]),
									
									// Encrypted slate
									encryptedSlate[Slatepack.ENCRYPTED_DATA_DATA_INDEX]
								]);
							
								// Encode the payload
								var encodedPayload = Base58.encode(Common.mergeArrays([
								
									// Checksum
									Base58.getChecksum(payload),
									
									// Payload
									payload
								]));
								
								// Format the encoded payload
								encodedPayload = Slatepack.format(encodedPayload);
								
								// Add encrypted header and footer to the encoded payload
								encodedPayload = Slatepack.ENCRYPTED_HEADER + encodedPayload + Slatepack.ENCRYPTED_FOOTER;
								
								// Resolve encoded payload
								resolve(encodedPayload);
							
							// Catch errors
							}).catch(function(error) {
							
								// Reject error
								reject(error);
							});
						}
						
						// Otherwise
						else {
						
							// Get hardware wallet
							var hardwareWallet = secretKeyOrHardwareWallet;
							
							// Return encrypting the slate and checksum with the hardware wallet
							return hardwareWallet.encryptSlate(Wallet.PAYMENT_PROOF_TOR_ADDRESS_KEY_INDEX, Common.mergeArrays([
							
								// Slate
								slate,
								
								// Checksum
								(new BigNumber((new Uint32Array([checksum]))[0])).toBytes(BigNumber.BIG_ENDIAN, Uint32Array["BYTES_PER_ELEMENT"])
							
							]), Tor.publicKeyToTorAddress(publicKey), hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(encryptedSlate) {
							
								// Check if encrypted slate is too long
								if(encryptedSlate[HardwareWallet.ENCRYPTED_SLATE_DATA_INDEX]["length"] > Common.UINT16_MAX_VALUE) {
								
									// Reject error
									reject("Encrypted slate is too long.");
									
									// Return
									return;
								}
							
								// Create payload
								var payload = Common.mergeArrays([
								
									// Version
									new Uint8Array([Slatepack.VERSION]),
									
									// Sender public key
									senderPublicKey,
									
									// Receiver public key
									publicKey,
									
									// Nonce
									encryptedSlate[HardwareWallet.ENCRYPTED_SLATE_NONCE_INDEX],
									
									// Encrypted slate length
									(new BigNumber(encryptedSlate[HardwareWallet.ENCRYPTED_SLATE_DATA_INDEX]["length"])).toBytes(BigNumber.BIG_ENDIAN, Uint16Array["BYTES_PER_ELEMENT"]),
									
									// Encrypted slate
									encryptedSlate[HardwareWallet.ENCRYPTED_SLATE_DATA_INDEX]
								]);
							
								// Encode the payload
								var encodedPayload = Base58.encode(Common.mergeArrays([
								
									// Checksum
									Base58.getChecksum(payload),
									
									// Payload
									payload
								]));
								
								// Format the encoded payload
								encodedPayload = Slatepack.format(encodedPayload);
								
								// Add encrypted header and footer to the encoded payload
								encodedPayload = Slatepack.ENCRYPTED_HEADER + encodedPayload + Slatepack.ENCRYPTED_FOOTER;
								
								// Resolve encoded payload
								resolve(encodedPayload);
								
							// Catch errors
							}).catch(function(error) {
							
								// Reject error
								reject(error);
							});
						}
						
					// Catch errors
					}).catch(function(error) {
					
						// Reject error
						reject(error);
					});
				}
				
				// Otherwise
				else {
				
					// Check wallet type
					switch(Consensus.getWalletType()) {
					
						// MWC wallet
						case Consensus.MWC_WALLET_TYPE:
				
							// Check if slate is too long
							if(slate["length"] > Common.UINT16_MAX_VALUE) {
							
								// Reject error
								reject("Slate is too long.");
								
								// Return
								return;
							}
					
							// Create payload
							var payload = Common.mergeArrays([
							
								// Version
								new Uint8Array([Slatepack.VERSION]),
								
								// Length
								(new BigNumber(slate["length"])).toBytes(BigNumber.BIG_ENDIAN, Uint16Array["BYTES_PER_ELEMENT"]),
								
								// Slate
								slate
							]);
						
							// Encode the payload
							var encodedPayload = Base58.encode(Common.mergeArrays([
							
								// Checksum
								Base58.getChecksum(payload),
								
								// Payload
								payload
							]));
							
							// Format the encoded payload
							encodedPayload = Slatepack.format(encodedPayload);
							
							// Add binary header and footer to the encoded payload
							encodedPayload = Slatepack.BINARY_HEADER + encodedPayload + Slatepack.BINARY_FOOTER;
							
							// Resolve encoded payload
							resolve(encodedPayload);
							
							// Break
							break;
						
						// GRIN wallet
						case Consensus.GRIN_WALLET_TYPE:
						
							// Try
							try {
						
								// Create payload
								var payload = Common.mergeArrays([
								
									// Version
									new Uint8Array([Slatepack.MAJOR_VERSION, Slatepack.MINOR_VERSION]),
									
									// Transfer mode
									new Uint8Array([Slatepack.PLAIN_TEXT_TRANSFER_MODE]),
									
									// Optional content flags
									(new BigNumber(0)).toBytes(BigNumber.BIG_ENDIAN, Uint16Array["BYTES_PER_ELEMENT"]),
									
									// Optional fields length
									(new BigNumber(0)).toBytes(BigNumber.BIG_ENDIAN, Uint32Array["BYTES_PER_ELEMENT"]),
									
									// Length
									(new BigNumber(slate["length"])).toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64),
									
									// Slate
									slate
								]);
							}
							
							// Catch errors
							catch(error) {
							
								// Reject error
								reject(error);
								
								// Return
								return;
							}
							
							// Encode the payload
							var encodedPayload = Base58.encode(Common.mergeArrays([
							
								// Checksum
								Base58.getChecksum(payload),
								
								// Payload
								payload
							]));
							
							// Format the encoded payload
							encodedPayload = Slatepack.format(encodedPayload);
							
							// Add header and footer to the encoded payload
							encodedPayload = Slatepack.ENCRYPTED_HEADER + encodedPayload + Slatepack.ENCRYPTED_FOOTER;
							
							// Resolve encoded payload
							resolve(encodedPayload);
							
							// Break
							break;
					}
				}
			});
		}
		
		// Encrypted data nonce index
		static get ENCRYPTED_DATA_NONCE_INDEX() {
		
			// Return encrypted data nonce index
			return 0;
		}
		
		// Encrypted data data index
		static get ENCRYPTED_DATA_DATA_INDEX() {
		
			// Return encrypted data data index
			return Slatepack.ENCRYPTED_DATA_NONCE_INDEX + 1;
		}
		
		// No secret key
		static get NO_SECRET_KEY() {
		
			// Return no secret key
			return null;
		}
		
		// Address length
		static get ADDRESS_LENGTH() {
		
			// Return address length
			return Slatepack.ADDRESS_WITHOUT_HUMAN_READABLE_PART_LENGTH + Consensus.SLATEPACK_ADDRESS_HUMAN_READABLE_PART["length"];
		}
	
	// Private
	
		// Encrypt
		static encrypt(secretKey, publicKey, data) {
		
			// Return promise
			return new Promise(function(resolve, reject) {
			
				// Check if getting X25519 secret key from the secret key failed
				var x25519SecretKey = X25519.secretKeyFromEd25519SecretKey(secretKey);
				
				if(x25519SecretKey === X25519.OPERATION_FAILED) {
				
					// Reject error
					reject("Invalid secret key.");
					
					// Return
					return;
				}
				
				// Check if getting X25519 public key from the public key failed
				var x25519PublicKey = X25519.publicKeyFromEd25519PublicKey(publicKey);
				
				if(x25519PublicKey === X25519.OPERATION_FAILED) {
				
					// Securely clear X25519 secret key
					x25519SecretKey.fill(0);
				
					// Reject error
					reject("Invalid public key.");
					
					// Return
					return;
				}
		
				// Check if creating shared secret key failed
				var sharedSecretKey = X25519.sharedSecretKeyFromSecretKeyAndPublicKey(x25519SecretKey, x25519PublicKey);
				
				if(sharedSecretKey === X25519.OPERATION_FAILED) {
				
					// Securely clear X25519 secret key
					x25519SecretKey.fill(0);
				
					// Reject error
					reject("Creating shared secret key failed.");
				}
				
				// Otherwise
				else {
				
					// Securely clear X25519 secret key
					x25519SecretKey.fill(0);
					
					// Check if shared secret key isn't zero
					if(Common.arraysAreEqualTimingSafe(sharedSecretKey, (new Uint8Array(sharedSecretKey["length"])).fill(0)) === false) {
				
						// Check if data is valid
						if(data["length"] !== 0) {
					
							// Create random nonce
							var nonce = new Uint8Array(Slatepack.NONCE_LENGTH);
							
							crypto.getRandomValues(nonce);
							
							// Try
							try {
							
								// Create cipher from shared secret key and nonce
								var cipher = chacha.createCipher(sharedSecretKey, nonce);
								
								// Set cipher's AAD
								cipher.setAAD(Slatepack.AAD_VALUE);
								
								// Encrypt data with the cipher
								var encryptedData = cipher.update(data);
								
								// Finish encrypting data
								encryptedData = Common.mergeArrays([
								
									// Encrypted data
									encryptedData,
									
									// Finish encrypting data
									cipher.final()
								]);
								
								// Get tag from the cipher
								var tag = cipher.getAuthTag();
								
								// Append tag to the encrypted data
								encryptedData = Common.mergeArrays([
								
									// Encrypted data
									encryptedData,
									
									// Tag
									tag
								]);
							}
							
							// Catch errors
							catch(error) {
							
								// Securely clear shared secret key
								sharedSecretKey.fill(0);
							
								// Reject error
								reject("Encrypting data failed.");
								
								// Return
								return;
							}
							
							// Securely clear shared secret key
							sharedSecretKey.fill(0);
						
							// Resolve
							resolve([
							
								// Nonce
								nonce,
								
								// Encrypted data
								encryptedData
							]);
						}
						
						// Otherwise
						else {
						
							// Securely clear shared secret key
							sharedSecretKey.fill(0);
						
							// Reject error
							reject("Invalid data.");
						}
					}
					
					// Otherwise
					else {
					
						// Securely clear shared secret key
						sharedSecretKey.fill(0);
					
						// Reject error
						reject("Invalid shared secret key");
					}
				}
			});
		}
		
		// Decrypt
		static decrypt(secretKey, publicKey, encryptedData, nonce) {
		
			// Return promise
			return new Promise(function(resolve, reject) {
		
				// Check if getting X25519 secret key from the secret key failed
				var x25519SecretKey = X25519.secretKeyFromEd25519SecretKey(secretKey);
				
				if(x25519SecretKey === X25519.OPERATION_FAILED) {
				
					// Reject error
					reject("Invalid secret key.");
					
					// Return
					return;
				}
				
				// Check if getting X25519 public key from the public key failed
				var x25519PublicKey = X25519.publicKeyFromEd25519PublicKey(publicKey);
				
				if(x25519PublicKey === X25519.OPERATION_FAILED) {
				
					// Securely clear X25519 secret key
					x25519SecretKey.fill(0);
				
					// Reject error
					reject("Invalid public key.");
					
					// Return
					return;
				}
		
				// Check if creating shared secret key failed
				var sharedSecretKey = X25519.sharedSecretKeyFromSecretKeyAndPublicKey(x25519SecretKey, x25519PublicKey);
				
				if(sharedSecretKey === X25519.OPERATION_FAILED) {
				
					// Securely clear X25519 secret key
					x25519SecretKey.fill(0);
				
					// Reject error
					reject("Creating shared secret key failed.");
				}
				
				// Otherwise
				else {
				
					// Securely clear X25519 secret key
					x25519SecretKey.fill(0);
					
					// Check if shared secret key isn't zero
					if(Common.arraysAreEqualTimingSafe(sharedSecretKey, (new Uint8Array(sharedSecretKey["length"])).fill(0)) === false) {
				
						// Check if encrypted data is valid
						if(encryptedData["length"] > Slatepack.TAG_LENGTH) {
					
							// Try
							try {
							
								// Create decipher from shared secret key and nonce
								var decipher = chacha.createDecipher(sharedSecretKey, nonce);
								
								// Set decipher's AAD
								decipher.setAAD(Slatepack.AAD_VALUE);
								
								// Set decipher's tag
								decipher.setAuthTag(encryptedData.subarray(encryptedData["length"] - Slatepack.TAG_LENGTH));
								
								// Decrypt encrypted data with the decipher
								var decryptedData = decipher.update(encryptedData.subarray(0, encryptedData["length"] - Slatepack.TAG_LENGTH));
								
								// Finish decrypting data
								decryptedData = Common.mergeArrays([
								
									// Decrypted data
									decryptedData,
									
									// Finish decrypting data
									decipher.final()
								]);
							}
							
							// Catch errors
							catch(error) {
							
								// Securely clear shared secret key
								sharedSecretKey.fill(0);
							
								// Reject error
								reject("Decrypting data failed.");
								
								// Return
								return;
							}
							
							// Securely clear shared secret key
							sharedSecretKey.fill(0);
							
							// Resolve decrypted data
							resolve(decryptedData);
						}
						
						// Otherwise
						else {
						
							// Securely clear shared secret key
							sharedSecretKey.fill(0);
						
							// Reject error
							reject("Invalid encrypted data.");
						}
					}
					
					// Otherwise
					else {
					
						// Securely clear shared secret key
						sharedSecretKey.fill(0);
					
						// Reject error
						reject("Invalid shared secret key");
					}
				}
			});
		}
		
		// Format
		static format(slatepack) {
		
			// Initialize result
			var result = "";
			
			// Go through all characters in the Slatepack
			for(var i = 0; i < slatepack["length"]; ++i) {
			
				// Check if at a word break
				if(i !== 0 && i % Slatepack.WORD_LENGTH === 0) {
				
					// check if at a line break
					if(i % (Slatepack.WORD_LENGTH * Slatepack.WORDS_PER_LINE) === 0) {
					
						// Append new line to result
						result += "\n";
					}
					
					// Otherwise
					else {
					
						// Append space to result
						result += " ";
					}
				}
				
				// Append character to result
				result += slatepack[i];
			}
			
			// Return result
			return result;
		}
		
		// Nonce length
		static get NONCE_LENGTH() {
		
			// Return nonce length
			return 12;
		}
		
		// Tag length
		static get TAG_LENGTH() {
		
			// Return tag length
			return 16;
		}
		
		// AAD value
		static get AAD_VALUE() {
		
			// Return AAD value
			return new Uint8Array([]);
		}
		
		// Word length
		static get WORD_LENGTH() {
		
			// Return word length
			return 15;
		}
		
		// Words per line
		static get WORDS_PER_LINE() {
		
			// Return words per line
			return 200;
		}
		
		// Version
		static get VERSION() {
		
			// Return version
			return 0;
		}
		
		// Component delimiter
		static get COMPONENT_SEPARATOR() {
		
			// Return component separator
			return ".";
		}
		
		// Encrypted header
		static get ENCRYPTED_HEADER() {
		
			// Return encrypted header
			return "BEGINSLATEPACK" + Slatepack.COMPONENT_SEPARATOR + " ";
		}
		
		// Encrypted footer
		static get ENCRYPTED_FOOTER() {
		
			// Return encrypted header
			return Slatepack.COMPONENT_SEPARATOR + " ENDSLATEPACK" + Slatepack.COMPONENT_SEPARATOR;
		}
		
		// Binary header
		static get BINARY_HEADER() {
		
			// Return binary header
			return "BEGINSLATE_BIN" + Slatepack.COMPONENT_SEPARATOR + " ";
		}
		
		// Binary footer
		static get BINARY_FOOTER() {
		
			// Return binary footer
			return Slatepack.COMPONENT_SEPARATOR + " ENDSLATE_BIN" + Slatepack.COMPONENT_SEPARATOR;
		}
		
		// Encrypted header pattern
		static get ENCRYPTED_HEADER_PATTERN() {
		
			// Return encrypted header pattern
			return /^[>\n\r\t ]*BEGINSLATEPACK[>\n\r\t ]*$/u;
		}
		
		// Encrypted footer pattern
		static get ENCRYPTED_FOOTER_PATTERN() {
		
			// Return encrypted footer pattern
			return /^[>\n\r\t ]*ENDSLATEPACK[>\n\r\t ]*$/u;
		}
		
		// Binary header pattern
		static get BINARY_HEADER_PATTERN() {
		
			// Return binary header pattern
			return /^[>\n\r\t ]*BEGINSLATE_BIN[>\n\r\t ]*$/u;
		}
		
		// Binary footer pattern
		static get BINARY_FOOTER_PATTERN() {
		
			// Return binary footer pattern
			return /^[>\n\r\t ]*ENDSLATE_BIN[>\n\r\t ]*$/u;
		}
		
		// Whitespace pattern
		static get WHITESPACE_PATTERN() {
		
			// Return whitespace pattern
			return /[>\n\r\t ]/gu;
		}
		
		// No public key
		static get NO_PUBLIC_KEY() {
		
			// Return no public key
			return null;
		}
		
		// Address without human-readable part length
		static get ADDRESS_WITHOUT_HUMAN_READABLE_PART_LENGTH() {
		
			// Return address without human-readable part length
			return 59;
		}
		
		// Pain text transfer mode
		static get PLAIN_TEXT_TRANSFER_MODE() {
		
			// Return plain text transfer mode
			return 0;
		}
		
		// Age encrypted transfer mode
		static get AGE_ENCRYPTED_TRANSFER_MODE() {
		
			// Return age encrypted transfer mode
			return Slatepack.PLAIN_TEXT_TRANSFER_MODE + 1;
		}
		
		// Major version
		static get MAJOR_VERSION() {
		
			// Return major version
			return 1;
		}
		
		// Minor version
		static get MINOR_VERSION() {
		
			// Return minor version
			return 0;
		}
}


// Main function

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