mirror of
https://github.com/transatoshi-mw/grin-web-wallet.git
synced 2025-10-06 15:52:47 +00:00
1858 lines
51 KiB
JavaScript
Executable File
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;
|