Files
grin-web-wallet/scripts/slatepack.js
2024-12-20 18:08:44 -08:00

1858 lines
51 KiB
JavaScript
Executable File

// 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;