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

6765 lines
195 KiB
JavaScript
Executable File

// Use strict
"use strict";
// Classes
// Slate class
class Slate {
// Public
// Initialize
static initialize() {
// Initialize request index
Slate.requestIndex = 0;
// Create worker
Slate.worker = new Worker(Slate.WORKER_FILE_LOCATION);
// Window before unload event
$(window).on("beforeunload", function() {
// Get current request index
var currentRequestIndex = Slate.requestIndex++;
// Check if current request index is at the max safe integer
if(currentRequestIndex === Number.MAX_SAFE_INTEGER)
// Reset request index
Slate.requestIndex = 0;
// Send worker an uninitialize request
Slate.worker.postMessage([
// Request index
currentRequestIndex,
// Type
Slate.UNINITIALIZE_REQUEST_TYPE
]);
// Terminate worker
Slate.worker.terminate();
});
// Return promise
return new Promise(function(resolve, reject) {
// Worker on error
Slate.worker["onerror"] = function() {
// Reject error
reject("Failed to create slate worker.");
};
// Worker on message
Slate.worker["onmessage"] = function(event) {
// Get message
var message = event["data"];
// Get message's request index
var requestIndex = message[Slate.MESSAGE_REQUEST_INDEX_OFFSET];
// Check message's type
switch(message[Slate.MESSAGE_TYPE_OFFSET]) {
// Initialize request type
case Slate.INITIALIZE_REQUEST_TYPE:
// Get message's status
var status = message[Slate.MESSAGE_STATUS_OFFSET];
// Check if worker initialized successfully
if(status === Slate.STATUS_SUCCESS_VALUE)
// Resolve
resolve();
// Otherwise
else
// Reject error
reject("Failed to initialize slate worker.");
// Break
break;
// Default
default:
// Get message's response
var response = message[Slate.MESSAGE_RESPONSE_OFFSET];
// Trigger response request index event
$(document).trigger(Slate.RESPONSE_EVENT + requestIndex.toFixed(), [
// Response
response
]);
// Break
break;
}
};
// Get current request index
var currentRequestIndex = Slate.requestIndex++;
// Check if current request index is at the max safe integer
if(currentRequestIndex === Number.MAX_SAFE_INTEGER)
// Reset request index
Slate.requestIndex = 0;
// Send worker an initialize request
Slate.worker.postMessage([
// Request index
currentRequestIndex,
// Type
Slate.INITIALIZE_REQUEST_TYPE,
// URL query string
Common.URL_QUERY_STRING_SEPARATOR + encodeURIComponent(Consensus.OVERRIDE_WALLET_TYPE_URL_PARAMETER_NAME).replace(/%20/ug, "+") + Common.URL_QUERY_STRING_PARAMETER_VALUE_SEPARATOR + encodeURIComponent(Consensus.walletTypeToText(Consensus.getWalletType())).replace(/%20/ug, "+") + Common.URL_QUERY_STRING_PARAMETER_SEPARATOR + encodeURIComponent(Consensus.OVERRIDE_NETWORK_TYPE_URL_PARAMETER_NAME).replace(/%20/ug, "+") + Common.URL_QUERY_STRING_PARAMETER_VALUE_SEPARATOR + encodeURIComponent(Consensus.networkTypeToText(Consensus.getNetworkType())).replace(/%20/ug, "+")
]);
});
}
// Constructor
constructor(serializedSlateOrAmount, isMainnet, purposeOrFee, initialSendSlateOrheight, lockHeight = Slate.NO_LOCK_HEIGHT, relativeHeight = Slate.NO_RELATIVE_HEIGHT, timeToLiveCutOffHeight = Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT, senderAddress = Slate.NO_SENDER_ADDRESS, receiverAddress = Slate.NO_RECEIVER_ADDRESS, preferredVersions = []) {
// Reset
this.reset();
// Check if a binary serialize slate is provided
if(serializedSlateOrAmount instanceof Uint8Array === true) {
// Get serialized slate
var serializedSlate = serializedSlateOrAmount;
// Get purpose
var purpose = purposeOrFee;
// Get initial send slate
var initialSendSlate = initialSendSlateOrheight;
// Unserialize the serialized slate
this.unserialize(serializedSlate, isMainnet, purpose, initialSendSlate);
}
// Otherwise check if a serialized slate is provided
else if(Object.isObject(serializedSlateOrAmount) === true) {
// Get serialized slate
var serializedSlate = serializedSlateOrAmount;
// Get purpose
var purpose = purposeOrFee;
// Get initial send slate
var initialSendSlate = initialSendSlateOrheight;
// Unserialize the serialized slate
this.unserialize(serializedSlate, isMainnet, purpose, initialSendSlate);
}
// Otherwise check if arguments are provided
else if(serializedSlateOrAmount instanceof BigNumber === true) {
// Get amount
var amount = serializedSlateOrAmount;
// Get fee
var fee = purposeOrFee;
// Get height
var height = initialSendSlateOrheight;
// Set amount to provided amount
this.amount = amount;
// Set fee to provided fee
this.fee = fee;
// Set height to provided height
this.height = height;
// Set lock height to provided lock height
this.lockHeight = lockHeight;
// Set relative height to provided relative height
this.relativeHeight = relativeHeight;
// Set time to live cut off height to provided time to live cut off height
this.timeToLiveCutOffHeight = timeToLiveCutOffHeight;
// Set sender address to provided sender address
this.senderAddress = senderAddress;
// Set receiver address to provided receiver address
this.receiverAddress = receiverAddress;
// Create kernel
this.kernels.push(new SlateKernel(this.getKernelFeatures(), this.getFee(), this.getLockHeight(), this.getRelativeHeight()));
// Set block header version to header version at the height
this.blockHeaderVersion = Consensus.getHeaderVersion(isMainnet, this.getHeight());
// Set version to the minimum compatible version
this.version = this.minimumCompatibleVersion(preferredVersions);
// Set original version to version
this.originalVersion = this.getVersion();
}
// Otherwise
else {
// Throw error
throw "Unsupported slate.";
}
}
// Serialize
serialize(isMainnet, purpose, preferBinary = false) {
// Check version
switch((this.getVersion() instanceof BigNumber === true) ? this.getVersion().toFixed() : this.getVersion()) {
// Version two and three
case Slate.VERSION_TWO.toFixed():
case Slate.VERSION_THREE.toFixed():
// Set self
var self = this;
// Create serialized slate
var serializedSlate = {
// Amount
"amount": this.getAmount().toFixed(),
// Fee
"fee": this.getFee().toFixed(),
// Height
"height": this.getHeight().toFixed(),
// ID
"id": this.getId().serialize(),
// Lock height
"lock_height": this.getLockHeight().toFixed(),
// Number of participants
"num_participants": this.getNumberOfParticipants(),
// Participant data
"participant_data": this.getParticipants().map(function(participant) {
// Return serialized participant
return participant.serialize(self);
}),
// Transaction
"tx": {
// Body
"body": {
// Inputs
"inputs": this.getInputs().map(function(input) {
// Return serialized input
return input.serialize(self);
}),
// Kernels
"kernels": this.getKernels().map(function(kernel) {
// Return serialized kernel
return kernel.serialize(self);
}),
// Outputs
"outputs": this.getOutputs().map(function(output) {
// Return serialized output
return output.serialize(self);
})
},
// Offset
"offset": Common.toHexString(this.getOffset())
},
// Version info
"version_info": {
// Block header version
"block_header_version": this.getBlockHeaderVersion(),
// Original version
"orig_version": this.getOriginalVersion(),
// Version
"version": this.getVersion()
}
};
// Check if slate's version is at least version three
if(this.getVersion().isGreaterThanOrEqualTo(Slate.VERSION_THREE) === true) {
// Set serialized slate's time to live cut off height
serializedSlate["ttl_cutoff_height"] = (this.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT) ? this.getTimeToLiveCutOffHeight().toFixed() : Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT;
// Check wallet type
switch(Consensus.getWalletType()) {
// MWC wallet
case Consensus.MWC_WALLET_TYPE:
// Set serialized slate's payment proof
serializedSlate["payment_proof"] = (this.hasPaymentProof() === true) ? {
// Receiver address
"receiver_address": this.getReceiverAddress(),
// Receiver signature
"receiver_signature": (this.getReceiverSignature() !== Slate.NO_RECEIVER_SIGNATURE) ? Common.toHexString(this.getReceiverSignature()) : null,
// Sender address
"sender_address": this.getSenderAddress()
} : Slate.NO_PAYMENT_PROOF;
// Set serialized slate's coin type
serializedSlate["coin_type"] = Slate.COIN_TYPE;
// Set serialized slate's network type
serializedSlate["network_type"] = Slate.getNetworkType(isMainnet);
// Break
break;
// EPIC wallet
case Consensus.EPIC_WALLET_TYPE:
// Set serialized slate's payment proof
serializedSlate["payment_proof"] = (this.hasPaymentProof() === true) ? {
// Receiver address
"receiver_address": Common.toHexString(Tor.torAddressToPublicKey(this.getReceiverAddress())),
// Receiver signature
"receiver_signature": (this.getReceiverSignature() !== Slate.NO_RECEIVER_SIGNATURE) ? Common.toHexString(this.getReceiverSignature()) : null,
// Sender address
"sender_address": Common.toHexString(Tor.torAddressToPublicKey(this.getSenderAddress()))
} : Slate.NO_PAYMENT_PROOF;
// Break
break;
}
}
// Return serialized slate
return serializedSlate;
// Version Slatepack
case Slate.VERSION_SLATEPACK:
// Initialize bit writer
var bitWriter = new BitWriter();
// Try
try {
// Write purpose
bitWriter.setBits(purpose, Slate.COMPACT_SLATE_PURPOSE_LENGTH);
// Write ID
bitWriter.setBytes(this.getId().getData());
// Write mainnet
bitWriter.setBits(isMainnet ? Slate.COMPACT_BOOLEAN_TRUE : Slate.COMPACT_BOOLEAN_FALSE, Slate.COMPACT_BOOLEAN_LENGTH);
// Check if purpose is send initial
if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) {
// Write amount
Slate.compactUint64(this.getAmount(), true, bitWriter);
// Write fee
Slate.compactUint64(this.getFee(), true, bitWriter);
}
// Write height
Slate.compactUint64(this.getHeight(), false, bitWriter);
// Write lock height
Slate.compactUint64(this.getLockHeight(), false, bitWriter);
// Check if time to live cut off height exists
if(this.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT) {
// Write time to live cut off height exists
bitWriter.setBits(Slate.COMPACT_BOOLEAN_TRUE, Slate.COMPACT_BOOLEAN_LENGTH);
// Write time to live cut off height
Slate.compactUint64(this.getTimeToLiveCutOffHeight(), false, bitWriter);
}
// Otherwise
else {
// Write time to live cut off height doesn't exist
bitWriter.setBits(Slate.COMPACT_BOOLEAN_FALSE, Slate.COMPACT_BOOLEAN_LENGTH);
}
// Check if purpose is send initial
if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) {
// Write participant
this.getParticipant(SlateParticipant.SENDER_ID).serialize(this, bitWriter);
// Check if has payment proof
if(this.hasPaymentProof() === true) {
// Write payment proof exists
bitWriter.setBits(Slate.COMPACT_BOOLEAN_TRUE, Slate.COMPACT_BOOLEAN_LENGTH);
// Write sender address
Slate.compactProofAddress(this.getSenderAddress(), isMainnet, bitWriter);
// Write receiver address
Slate.compactProofAddress(this.getReceiverAddress(), isMainnet, bitWriter);
}
// Otherwise
else {
// Write payment proof doesn't exist
bitWriter.setBits(Slate.COMPACT_BOOLEAN_FALSE, Slate.COMPACT_BOOLEAN_LENGTH);
}
}
// Check if purpose is send response
if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_RESPONSE) {
// Write offset
bitWriter.setBytes(this.getOffset());
// Go through all outputs
for(var i = 0; i < this.getOutputs()["length"]; ++i) {
// Get output
var output = this.getOutputs()[i];
// Write output
output.serialize(this, bitWriter);
// Check if not at the last output
if(i !== this.getOutputs()["length"] - 1) {
// Write continue to next output
bitWriter.setBits(Slate.COMPACT_BOOLEAN_TRUE, Slate.COMPACT_BOOLEAN_LENGTH);
}
// Otherwise
else {
// Write no more outputs
bitWriter.setBits(Slate.COMPACT_BOOLEAN_FALSE, Slate.COMPACT_BOOLEAN_LENGTH);
}
}
// Go through all kernels
for(var i = 0; i < this.getKernels()["length"]; ++i) {
// Get kernel
var kernel = this.getKernels()[i];
// Write kernel
kernel.serialize(this, bitWriter);
// Check if not at the last kernel
if(i !== this.getKernels()["length"] - 1) {
// Write continue to next kernel
bitWriter.setBits(Slate.COMPACT_BOOLEAN_TRUE, Slate.COMPACT_BOOLEAN_LENGTH);
}
// Otherwise
else {
// Write no more kernels
bitWriter.setBits(Slate.COMPACT_BOOLEAN_FALSE, Slate.COMPACT_BOOLEAN_LENGTH);
}
}
// Write participant
this.getParticipant(SlateParticipant.SENDER_ID.plus(1)).serialize(this, bitWriter);
// Check if has payment proof
if(this.hasPaymentProof() === true) {
// Write payment proof exists
bitWriter.setBits(Slate.COMPACT_BOOLEAN_TRUE, Slate.COMPACT_BOOLEAN_LENGTH);
// Write sender address
Slate.compactProofAddress(this.getSenderAddress(), isMainnet, bitWriter);
// Write receiver address
Slate.compactProofAddress(this.getReceiverAddress(), isMainnet, bitWriter);
// Check if receiver signature exists
if(this.getReceiverSignature() !== Slate.NO_RECEIVER_SIGNATURE) {
// Write receiver signature exists
bitWriter.setBits(Slate.COMPACT_BOOLEAN_TRUE, Slate.COMPACT_BOOLEAN_LENGTH);
// Check if receiver signature is too short or too long
if(this.getReceiverSignature()["length"] < Crypto.ED25519_SIGNATURE_LENGTH || this.getReceiverSignature()["length"] - Crypto.ED25519_SIGNATURE_LENGTH >= Math.pow(2, Slate.COMPACT_PROOF_SIGNATURE_LENGTH_LENGTH)) {
// Throw error
throw "Receiver signature is too short or too long.";
}
// Write receiver signature length
bitWriter.setBits(this.getReceiverSignature()["length"] - Crypto.ED25519_SIGNATURE_LENGTH, Slate.COMPACT_PROOF_SIGNATURE_LENGTH_LENGTH);
// Write receiver signature
bitWriter.setBytes(this.getReceiverSignature());
}
// Otherwise
else {
// Write receiver signature doesn't exist
bitWriter.setBits(Slate.COMPACT_BOOLEAN_FALSE, Slate.COMPACT_BOOLEAN_LENGTH);
}
}
// Otherwise
else {
// Write payment proof doesn't exist
bitWriter.setBits(Slate.COMPACT_BOOLEAN_FALSE, Slate.COMPACT_BOOLEAN_LENGTH);
// Write receiver signature doesn't exist
bitWriter.setBits(Slate.COMPACT_BOOLEAN_FALSE, Slate.COMPACT_BOOLEAN_LENGTH);
}
}
// Return serialized slate
return bitWriter.getBytes();
}
// Catch errors
catch(error) {
// Throw error
throw "Compacting slate failed.";
}
// Break
break;
// Version four
case Slate.VERSION_FOUR.toFixed():
// Check if prefer binary
if(preferBinary === true) {
// Initialize bit writer
var bitWriter = new BitWriter();
// Try
try {
// Write version
bitWriter.setBytes(this.getVersion().toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT16));
// Write block header version
bitWriter.setBytes(this.getBlockHeaderVersion().toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT16));
// Write ID
bitWriter.setBytes(this.getId().getData());
// Write purpose
bitWriter.setBytes([purpose + 1]);
// Write offset
bitWriter.setBytes((purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) ? Slate.ZERO_OFFSET : this.getOffset());
// Initialize optional fields
var optionalFields = 0;
// Check if number of participants isn't the default
if(this.getNumberOfParticipants().isEqualTo(Slate.DEFAULT_NUMBER_OF_PARTICIPANTS) === false) {
// Set that optional fields includes number of participants
optionalFields |= 0b00000001;
}
// Check if purpose is send initial
if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) {
// Check if amount isn't zero
if(this.getAmount().isZero() === false) {
// Set that optional fields includes amount
optionalFields |= 0b00000010;
}
// Check if fee isn't zero
if(this.getFee().isZero() === false) {
// Set that optional fields includes fee
optionalFields |= 0b00000100;
}
}
// Check if kernel features isn't plain
if(this.getKernelFeatures() !== SlateKernel.PLAIN_FEATURES) {
// Set that optional fields includes features
optionalFields |= 0b00001000;
}
// Check if time to live cut off height exists
if(this.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT) {
// Set that optional fields includes time to live cut off height
optionalFields |= 0b00010000;
}
// Write optional fields
bitWriter.setBytes([optionalFields]);
// Check if optional fields includes number of participants
if((optionalFields & 0b00000001) !== 0) {
// Write number of participants
bitWriter.setBytes(this.getNumberOfParticipants().toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT8));
}
// Check if optional fields includes amount
if((optionalFields & 0b00000010) !== 0) {
// Write amount
bitWriter.setBytes(this.getAmount().toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64));
}
// Check if optional fields includes fee
if((optionalFields & 0b00000100) !== 0) {
// Write fee
bitWriter.setBytes(this.getFee().toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64));
}
// Check if optional fields includes features
if((optionalFields & 0b00001000) !== 0) {
// Write features
bitWriter.setBytes([this.getKernelFeatures()]);
}
// Check if optional fields includes time to live cut off height
if((optionalFields & 0b00010000) !== 0) {
// Write time to live cut off height
bitWriter.setBytes(this.getTimeToLiveCutOffHeight().toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64));
}
// Write participants length
bitWriter.setBytes([1]);
// Check if purpose is send initial
if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) {
// Write sender participant
this.getParticipant(SlateParticipant.SENDER_ID).serialize(this, bitWriter);
}
// Otherwise
else {
// Write receiver participant
this.getParticipant(SlateParticipant.SENDER_ID.plus(1)).serialize(this, bitWriter);
}
// Initialize component fields
var componentFields = 0;
// Check if purpose is send response
if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_RESPONSE) {
// Check if inputs or outputs exists
if(this.getInputs()["length"] + this.getOutputs()["length"] !== 0) {
// Set that component fields includes inputs and outputs
componentFields |= 0b00000001;
}
}
// Check if has payment proof
if(this.hasPaymentProof() === true) {
// Set that component fields includes payment proof
componentFields |= 0b00000010;
}
// Write component fields
bitWriter.setBytes([componentFields]);
// Check if component fields includes inputs and outputs
if((componentFields & 0b00000001) !== 0) {
// Write inputs and outputs length
bitWriter.setBytes((new BigNumber(this.getInputs()["length"] + this.getOutputs()["length"])).toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT16));
// Go through all inputs
for(var i = 0; i < this.getInputs()["length"]; ++i) {
// Get input
var input = this.getInputs()[i];
// Write input or output is input
bitWriter.setBytes([0]);
// Write input
input.serialize(this, bitWriter);
}
// Go through all outputs
for(var i = 0; i < this.getOutputs()["length"]; ++i) {
// Get output
var output = this.getOutputs()[i];
// Write input or output is output
bitWriter.setBytes([1]);
// Write output
output.serialize(this, bitWriter);
}
}
// Check if component fields includes payment proof
if((componentFields & 0b00000010) !== 0) {
// Write sender address
bitWriter.setBytes(Slatepack.slatepackAddressToPublicKey(this.getSenderAddress()));
// Write receiver address
bitWriter.setBytes(Slatepack.slatepackAddressToPublicKey(this.getReceiverAddress()));
// Check if receiver signature exists
if(this.getReceiverSignature() !== Slate.NO_RECEIVER_SIGNATURE) {
// Write receiver signature exists
bitWriter.setBytes([1]);
// Write receiver signature
bitWriter.setBytes(this.getReceiverSignature());
}
// Otherwise
else {
// Write receiver signature doesn't exists
bitWriter.setBytes([0]);
}
}
// Check kernel features
switch(this.getKernelFeatures()) {
// Plain features
case SlateKernel.PLAIN_FEATURES:
// Break
break;
// Height locked features
case SlateKernel.HEIGHT_LOCKED_FEATURES:
// Write lock height
bitWriter.setBytes(this.getLockHeight().toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64));
// Break
break;
// Default
default:
// Throw error
throw "Unsupported features.";
}
// Return serialized slate
return bitWriter.getBytes();
}
// Catch errors
catch(error) {
// Throw error
throw "Compacting slate failed.";
}
}
// Otherwise
else {
// Create serialized slate
var serializedSlate = {
// ID
"id": this.getId().serialize(),
// Purpose
"sta": Slate.purposeToText(purpose),
// Version
"ver": this.getVersion().toFixed() + Slate.VERSION_SEPARATOR + this.getBlockHeaderVersion().toFixed()
};
// Check if number of participants isn't the default
if(this.getNumberOfParticipants().isEqualTo(Slate.DEFAULT_NUMBER_OF_PARTICIPANTS) === false) {
// Set serialized slate's number of participants
serializedSlate["num_parts"] = this.getNumberOfParticipants();
}
// Check if time to live cut off height exists
if(this.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT) {
// Set serialized slate's time to live cut off height
serializedSlate["ttl"] = this.getTimeToLiveCutOffHeight().toFixed();
}
// Check kernel features
switch(this.getKernelFeatures()) {
// Plain features
case SlateKernel.PLAIN_FEATURES:
// Break
break;
// Height locked features
case SlateKernel.HEIGHT_LOCKED_FEATURES:
// Set serialized slate's features
serializedSlate["feat"] = this.getKernelFeatures();
// Set serialized slate's features arguments
serializedSlate["feat_args"] = {
// Lock height
"lock_hgt": this.getLockHeight().toFixed()
};
// Break
break;
// Default
default:
// Throw error
throw "Unsupported features.";
}
// Check if has payment proof
if(this.hasPaymentProof() === true) {
// Set serialized slate's payment proof
serializedSlate["proof"] = {
// Receiver address
"raddr": Common.toHexString(Slatepack.slatepackAddressToPublicKey(this.getReceiverAddress())),
// Sender address
"saddr": Common.toHexString(Slatepack.slatepackAddressToPublicKey(this.getSenderAddress()))
};
// Check if receiver signature exists
if(this.getReceiverSignature() !== Slate.NO_RECEIVER_SIGNATURE) {
// Set payment proof's receiver signature
serializedSlate["proof"]["rsig"] = Common.toHexString(this.getReceiverSignature());
}
}
// Check if purpose is send initial
if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) {
// Set serialized slate's amount
serializedSlate["amt"] = this.getAmount().toFixed();
// Set serialized slate's fee
serializedSlate["fee"] = this.getFee().toFixed();
// Set serialized slate's participants
serializedSlate["sigs"] = [this.getParticipant(SlateParticipant.SENDER_ID).serialize(this)];
}
// Check if purpose is send response
if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_RESPONSE) {
// Set serialized slate's offset
serializedSlate["off"] = Common.toHexString(this.getOffset());
// Set serialized slate's participants
serializedSlate["sigs"] = [this.getParticipant(SlateParticipant.SENDER_ID.plus(1)).serialize(this)];
// Set serialized slate's inputs and outputs
serializedSlate["coms"] = [];
// Go through all inputs
for(var i = 0; i < this.getInputs()["length"]; ++i) {
// Get input
var input = this.getInputs()[i];
// Add serialized input to the serialized slate's inputs and outputs
serializedSlate["coms"].push(input.serialize(this));
}
// Go through all outputs
for(var i = 0; i < this.getOutputs()["length"]; ++i) {
// Get output
var output = this.getOutputs()[i];
// Add serialized output to the serialized slate's inputs and outputs
serializedSlate["coms"].push(output.serialize(this));
}
}
// Return serialzied slate
return serializedSlate;
}
// Break
break;
// Default
default:
// Throw error
throw "Unsupported slate version.";
}
}
// Get transaction
getTransaction() {
// Return transaction
return {
// Body
"body": {
// Inputs
"inputs": this.getInputs().map(function(input) {
// Return input's transaction
return input.getTransaction();
}),
// Kernels
"kernels": this.getKernels().map(function(kernel) {
// Return kernel's transaction
return kernel.getTransaction();
}),
// Outputs
"outputs": this.getOutputs().map(function(output) {
// Return output's transaction
return output.getTransaction();
})
},
// Offset
"offset": Common.toHexString(this.getOffset())
};
}
// Get number of participants
getNumberOfParticipants() {
// Return number of participants
return this.numberOfParticipants;
}
// Get ID
getId() {
// Return ID
return this.id;
}
// Change ID
changeId() {
// Set ID to a random UUID
this.id = new Uuid(Uuid.randomSerializedUuid());
}
// Get offset
getOffset() {
// Return offset
return this.offset;
}
// Get inputs
getInputs() {
// Return inputs
return this.inputs;
}
// Get outputs
getOutputs() {
// Return outputs
return this.outputs;
}
// Get kernels
getKernels() {
// Return kernels
return this.kernels;
}
// Get amount
getAmount() {
// Return amount
return this.amount;
}
// Get fee
getFee() {
// Return fee
return this.fee;
}
// Get height
getHeight() {
// Return height
return this.height;
}
// Get lock height
getLockHeight() {
// Return lock height
return this.lockHeight;
}
// Get relative height
getRelativeHeight() {
// Return relative height
return this.relativeHeight;
}
// Get time to live cut off height
getTimeToLiveCutOffHeight() {
// Return time to live cut off height
return this.timeToLiveCutOffHeight;
}
// Get participants
getParticipants() {
// Return participants
return this.participants;
}
// Get version
getVersion() {
// Return version
return this.version;
}
// Get original version
getOriginalVersion() {
// Return original version
return this.originalVersion;
}
// Get block header version
getBlockHeaderVersion() {
// Return block header version
return this.blockHeaderVersion;
}
// Get receiver address
getReceiverAddress() {
// Return receiver address
return this.receiverAddress;
}
// Get receiver signature
getReceiverSignature() {
// Return receiver signature
return this.receiverSignature;
}
// Set receiver address
setReceiverAddress(receiverAddress) {
// Set receiver address
this.receiverAddress = receiverAddress;
}
// Set receiver signature
setReceiverSignature(receiverSignature, isMainnet) {
// Set receiver signature
this.receiverSignature = receiverSignature;
// Check if receiver signature failed to be verified
if(this.verifyReceiverSignature(isMainnet) === false) {
// Return false
return false;
}
// Return true
return true;
}
// Get sender address
getSenderAddress() {
// Return sender address
return this.senderAddress;
}
// Get participant
getParticipant(participantId) {
// Go through all participants
for(var i = 0; i < this.getParticipants()["length"]; ++i) {
// Get participant
var participant = this.getParticipants()[i];
// Check if participant has the specified participant ID
if(participant.getId().isEqualTo(participantId) === true) {
// Return participant
return participant;
}
}
// Return no participant
return Slate.NO_PARTICIPANT;
}
// Add outputs
addOutputs(outputs, updateKernel = true) {
// Check if updating kernel
if(updateKernel === true) {
// Update kernel
this.updateKernel();
}
// Go through all outputs
for(var i = 0; i < outputs["length"]; ++i) {
// Get output
var output = outputs[i];
// Append output to list
this.outputs.push(output);
}
// Check if sorting failed
if(this.sort() === false) {
// Return false
return false;
}
// Check if verifying weight failed
if(this.verifyWeight() === false) {
// Return false
return false;
}
// Check if verifying sorted and unique failed
if(this.verifySortedAndUnique() === false) {
// Return false
return false;
}
// Check if verifying no cut through failed
if(this.verifyNoCutThrough() === false) {
// Return false
return false;
}
// Return true
return true;
}
// Add inputs
addInputs(inputs, updateKernel = true, expectedNumberOfOutputs = 0) {
// Check if updating kernel
if(updateKernel === true) {
// Update kernel
this.updateKernel();
}
// Go through all inputs
for(var i = 0; i < inputs["length"]; ++i) {
// Get input
var input = inputs[i];
// Append input to list
this.inputs.push(input);
}
// Check if sorting failed
if(this.sort() === false) {
// Return false
return false;
}
// Check if verifying weight failed
if(this.verifyWeight(expectedNumberOfOutputs) === false) {
// Return false
return false;
}
// Check if verifying sorted and unique failed
if(this.verifySortedAndUnique() === false) {
// Return false
return false;
}
// Check if verifying no cut through failed
if(this.verifyNoCutThrough() === false) {
// Return false
return false;
}
// Return true
return true;
}
// Create offset
createOffset() {
// While offset isn't a valid secret key
do {
// Fill offset with random values
crypto.getRandomValues(this.offset);
} while(Secp256k1Zkp.isValidSecretKey(this.getOffset()) !== true);
}
// Apply offset
applyOffset(secretKeyOrHardwareWallet, hardwareWalletLockedText = HardwareWallet.NO_TEXT, hardwareWalletLockedTextArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
// Set self
var self = this;
// 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 secret key isn't a valid secret key
if(Secp256k1Zkp.isValidSecretKey(secretKey) !== true) {
// Reject error
reject("Secret key isn't a valid secret key.");
}
// Otherwise
else {
// Check if getting blind offset from secret key and offset failed
var blindOffset = Secp256k1Zkp.blindSum([
// Secret key
secretKey
], [
// Offset
self.getOffset()
]);
if(blindOffset === Secp256k1Zkp.OPERATION_FAILED) {
// Reject error
reject("Getting blind offset from secret key and offset failed.");
}
// Otherwise check if blind offset isn't a valid secret key
else if(Secp256k1Zkp.isValidSecretKey(blindOffset) !== true) {
// Securely clear blind offset
blindOffset.fill(0);
// Reject error
reject("Blind offset isn't a valid secret key.");
}
// Otherwise
else {
// Resolve blind offset
resolve(blindOffset);
}
}
}
// Otherwise
else {
// Get hardware wallet
var hardwareWallet = secretKeyOrHardwareWallet;
// Return applying offset to the transaction with the hardware wallet
return hardwareWallet.applyOffsetToTransaction(self.getOffset(), hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(secretNonceIndex) {
// Resolve secret nonce index
resolve(secretNonceIndex);
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
}
});
}
// Combine offsets
combineOffsets(offset) {
// Check if updating offset with the offset failed
this.offset = Secp256k1Zkp.blindSum([
// Offset
this.getOffset(),
// Offset
offset
], []);
if(this.getOffset() === Secp256k1Zkp.OPERATION_FAILED) {
// Return false
return false;
}
// Otherwise check if the result isn't a valid secret key
else if(Secp256k1Zkp.isValidSecretKey(this.getOffset()) !== true) {
// Return false
return false;
}
// Otherwise
else {
// Return true
return true;
}
}
// Add participant
addParticipant(secretKeyOrHardwareWallet, secretNonce, message, isMainnet, hardwareWalletLockedText = HardwareWallet.NO_TEXT, hardwareWalletLockedTextArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
// Set self
var self = this;
// Return promise
return new Promise(function(resolve, reject) {
// Set participant ID to the sender ID
var participantId = SlateParticipant.SENDER_ID;
// While participant ID exists
while(self.getParticipant(participantId) !== Slate.NO_PARTICIPANT) {
// Increment participant ID
participantId = participantId.plus(1);
}
// Get public blind excess
var getPublicBlindExcess = 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 public blind excess from secret key failed
var publicBlindExcess = Secp256k1Zkp.publicKeyFromSecretKey(secretKey);
if(publicBlindExcess === Secp256k1Zkp.OPERATION_FAILED) {
// Reject error
reject("Getting public blind excess failed.");
}
// Otherwise
else {
// Resolve public blind excess
resolve(publicBlindExcess);
}
}
// Otherwise
else {
// Get hardware wallet
var hardwareWallet = secretKeyOrHardwareWallet;
// Return getting the transaction public key with the hardware wallet
return hardwareWallet.getTransactionPublicKey(hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(publicBlindExcess) {
// Resolve public blind excess
resolve(publicBlindExcess);
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
}
});
};
// Return getting public blind excess
return getPublicBlindExcess().then(function(publicBlindExcess) {
// Get message signature
var getMessageSignature = function() {
// Return promise
return new Promise(function(resolve, reject) {
// Check if a message exists
if(message !== SlateParticipant.NO_MESSAGE) {
// Check if a secret key is provided
if(secretKeyOrHardwareWallet instanceof Uint8Array === true) {
// Get secret key
var secretKey = secretKeyOrHardwareWallet;
// Get message's hash
var messageHash = Blake2b.compute(Crypto.SINGLE_SIGNER_MESSAGE_LENGTH, (new TextEncoder()).encode(message), new Uint8Array());
// Check if getting message's hash failed
if(messageHash === Blake2b.OPERATION_FAILED) {
// Reject error
reject("Getting message's hash failed.");
// Return
return;
}
// Check if creating message signature from the message hash, secret key, and public blind excess failed
var messageSignature = Secp256k1Zkp.createSingleSignerSignature(messageHash, secretKey, Secp256k1Zkp.NO_SECRET_NONCE, publicBlindExcess, Secp256k1Zkp.NO_PUBLIC_NONCE, Secp256k1Zkp.NO_PUBLIC_NONCE_TOTAL);
if(messageSignature === Secp256k1Zkp.OPERATION_FAILED) {
// Reject error
reject("Creating message signature.");
// Return
return;
}
// Resolve message signature
resolve(messageSignature);
}
// Otherwise
else {
// Get hardware wallet
var hardwareWallet = secretKeyOrHardwareWallet;
// Return getting the transaction message signature with the hardware wallet
return hardwareWallet.getTransactionMessageSignature(message, hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(messageSignature) {
// Resolve message signature
resolve(messageSignature);
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
}
}
// Otherwise
else {
// Resolve no message signature
resolve(SlateParticipant.NO_MESSAGE_SIGNATURE);
}
});
};
// Return getting message signature
return getMessageSignature().then(function(messageSignature) {
// Get public nonce
var getPublicNonce = function() {
// Return promise
return new Promise(function(resolve, reject) {
// Check if a secret key is provided
if(secretKeyOrHardwareWallet instanceof Uint8Array === true) {
// Check if getting a public nonce from secret nonce failed
var publicNonce = Secp256k1Zkp.publicKeyFromSecretKey(secretNonce);
if(publicNonce === Secp256k1Zkp.OPERATION_FAILED) {
// Reject error
reject("Getting a public nonce failed.");
}
// Otherwise
else {
// Resolve public nonce
resolve(publicNonce);
}
}
// Otherwise
else {
// Get hardware wallet
var hardwareWallet = secretKeyOrHardwareWallet;
// Return getting the transaction public nonce with the hardware wallet
return hardwareWallet.getTransactionPublicNonce(hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(publicNonce) {
// Resolve public nonce
resolve(publicNonce);
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
}
});
};
// Return getting public nonce
return getPublicNonce().then(function(publicNonce) {
// Save receiver signature
var oldReceiverSignature = self.getReceiverSignature();
// Get partial signature
var getPartialSignature = function() {
// Return promise
return new Promise(function(resolve, reject) {
// Check if participant ID is the sender ID
if(participantId.isEqualTo(SlateParticipant.SENDER_ID) === true) {
// Set no partial signature
resolve(SlateParticipant.NO_PARTIAL_SIGNATURE);
}
// Otherwise
else {
// Return creating partial signature
return self.createPartialSignature(secretKeyOrHardwareWallet, secretNonce, true, isMainnet, hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(partialSignature) {
// Resolve partial signature
resolve(partialSignature);
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
}
});
};
// Return getting partial signature
return getPartialSignature().then(function(partialSignature) {
// Try
try {
// Create participant
var participant = new SlateParticipant(participantId, publicBlindExcess, publicNonce, partialSignature, message, messageSignature);
}
// Catch errors
catch(error) {
// Restore old receiver signature
self.setReceiverSignature(oldReceiverSignature);
// Reject error
reject("Creating participant failed.");
// Return
return;
}
// Add participant to list
self.getParticipants().push(participant);
// Check if partial signatures failed to be verified
if(self.verifyPartialSignatures() === false) {
// Remove participant from list
self.getParticipants().pop();
// Restore old receiver signature
self.setReceiverSignature(oldReceiverSignature);
// Reject error
reject("Verifying partial signature failed.");
}
// Otherwise
else {
// Resolve
resolve();
}
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
});
}
// Finalize
finalize(secretKeyOrHardwareWallet, secretNonce, baseFee, isMainnet, verifyAsynchronous = false, hardwareWalletLockedText = HardwareWallet.NO_TEXT, hardwareWalletLockedTextArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
// Set self
var self = this;
// Return promise
return new Promise(function(resolve, reject) {
// Return creating partial signature
return self.createPartialSignature(secretKeyOrHardwareWallet, secretNonce, false, isMainnet, hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(partialSignature) {
// Get sender participant
var senderParticipant = self.getParticipant(SlateParticipant.SENDER_ID);
// Save sender participant's partial signature
var oldPartialSignature = senderParticipant.getPartialSignature();
// Set sender participant's partial signature
senderParticipant.setPartialSignature(partialSignature);
// Check if partial signatures failed to be verified
if(self.verifyPartialSignatures() === false) {
// Restore sender participant's old partial signature
senderParticipant.setPartialSignature(oldPartialSignature);
// Reject error
reject("Verifying partial signature failed.");
}
// Otherwise
else {
// Initialize partial signatures
var partialSignatures = [];
// Go through all participants
for(var i = 0; i < self.getParticipants()["length"]; ++i) {
// Get participant
var participant = self.getParticipants()[i];
// Check if participant is complete
if(participant.isComplete() === true) {
// Add participant's partial signature to list
partialSignatures.push(participant.getPartialSignature());
}
// Otherwise
else {
// Restore sender participant's old partial signature
senderParticipant.setPartialSignature(oldPartialSignature);
// Reject error
reject("Participant isn't complete.");
// Return
return;
}
}
// Get public nonce sum from combining participant's public nonces
var publicNonceSum = Secp256k1Zkp.combinePublicKeys(self.getParticipants().map(function(participant) {
// Return participant's public nonce
return participant.getPublicNonce();
}));
// Check if getting public nonce sum failed
if(publicNonceSum === Secp256k1Zkp.OPERATION_FAILED) {
// Restore sender participant's old partial signature
senderParticipant.setPartialSignature(oldPartialSignature);
// Reject error
reject("Getting public nonce sum failed.");
}
// Otherwise
else {
// Get public blind excess sum from combining participant's public blind excesses
var publicBlindExcessSum = Secp256k1Zkp.combinePublicKeys(self.getParticipants().map(function(participant) {
// Return participant's public blind excess
return participant.getPublicBlindExcess();
}));
// Check if getting public blind excess sum failed
if(publicBlindExcessSum === Secp256k1Zkp.OPERATION_FAILED) {
// Restore sender participant's old partial signature
senderParticipant.setPartialSignature(oldPartialSignature);
// Reject error
reject("Getting public blind excess sum failed.");
}
// Otherwise
else {
// Get final signature from adding partial signatures and inclusing the public nonce sum
var finalSignature = Secp256k1Zkp.addSingleSignerSignatures(partialSignatures, publicNonceSum);
// Check if getting final signature failed
if(finalSignature === Secp256k1Zkp.OPERATION_FAILED) {
// Restore sender participant's old partial signature
senderParticipant.setPartialSignature(oldPartialSignature);
// Reject error
reject("Getting final signature failed.");
}
// Otherwise
else {
// Try
try {
// Get message to sign
var messageToSign = SlateKernel.signatureMessage(self.getKernelFeatures(), self.getFee(), self.getLockHeight(), self.getRelativeHeight());
}
// Catch errors
catch(error) {
// Restore sender participant's old partial signature
senderParticipant.setPartialSignature(oldPartialSignature);
// Reject error
reject("Getting message to sign failed.");
// Return
return;
}
// Check if final signature doesn't verify the slate
if(Secp256k1Zkp.verifySingleSignerSignature(finalSignature, messageToSign, Secp256k1Zkp.NO_PUBLIC_NONCE, publicBlindExcessSum, publicBlindExcessSum, false) !== true) {
// Restore sender participant's old partial signature
senderParticipant.setPartialSignature(oldPartialSignature);
// Reject error
reject("Verifying final signature failed.");
}
// Otherwise check if only one kernel doesn't exist
else if(self.getKernels()["length"] !== 1) {
// Restore sender participant's old partial signature
senderParticipant.setPartialSignature(oldPartialSignature);
// Reject error
reject("Only one kernel doesn't exist.");
}
// Otherwise check if kernel is already complete
else if(self.getKernels()[0].isComplete() === true) {
// Restore sender participant's old partial signature
senderParticipant.setPartialSignature(oldPartialSignature);
// Reject error
reject("Kernel is already complete.");
}
// Otherwise
else {
// Save kernel's excess
var oldExcess = self.getKernels()[0].getExcess();
// Set kernel's excess
self.getKernels()[0].setExcess(self.getExcess());
// Save kernel's excess signature
var oldExcessSignature = self.getKernels()[0].getExcessSignature();
// Check if setting kernel's excess signature failed
if(self.getKernels()[0].setExcessSignature(finalSignature) === false) {
// Restore kernel's old excess and excess signature
self.getKernels()[0].setExcess(oldExcess);
self.getKernels()[0].setExcessSignature(oldExcessSignature);
// Restore sender participant's old partial signature
senderParticipant.setPartialSignature(oldPartialSignature);
// Reject error
reject("Setting kernel's excess signature failed.");
}
// Otherwise
else {
// Check if verify asynchronous
if(verifyAsynchronous === true) {
// Return verifying after finalize asynchronous
return Slate.verifyAfterFinalizeAsynchronous(self, baseFee, isMainnet).then(function() {
// Resolve
resolve();
// Catch errors
}).catch(function(error) {
// Restore kernel's old excess and excess signature
self.getKernels()[0].setExcess(oldExcess);
self.getKernels()[0].setExcessSignature(oldExcessSignature);
// Restore sender participant's old partial signature
senderParticipant.setPartialSignature(oldPartialSignature);
// Reject error
reject("Verifying after finalize failed.");
});
}
// Otherwise
else {
// Check if verifying after finalize failed
if(self.verifyAfterFinalize(baseFee, isMainnet) === false) {
// Restore kernel's old excess and excess signature
self.getKernels()[0].setExcess(oldExcess);
self.getKernels()[0].setExcessSignature(oldExcessSignature);
// Restore sender participant's old partial signature
senderParticipant.setPartialSignature(oldPartialSignature);
// Reject error
reject("Verifying after finalize failed.");
}
// Otherwise
else {
// Resolve
resolve();
}
}
}
}
}
}
}
}
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
});
}
// Get offset excess
getOffsetExcess() {
// Check if performing Pedersen commit with the offset and zero failed
var offsetExcess = Secp256k1Zkp.pedersenCommit(this.getOffset(), (new BigNumber(0)).toFixed());
if(offsetExcess === Secp256k1Zkp.OPERATION_FAILED) {
// Throw error
throw "Performing Pedersen commit with the offset and zero failed.";
}
// Return offset excess
return offsetExcess;
}
// Get excess
getExcess(publicBlindExcess = Slate.NO_PUBLIC_BLIND_EXCESS) {
// Check if compact
if(this.isCompact() === true) {
// Get participants public blind excesses
var publicBlindExcesses = this.getParticipants().map(function(participant) {
// Return participant's public blind excess
return participant.getPublicBlindExcess();
});
// Check if a public blind excess is provided
if(publicBlindExcess !== Slate.NO_PUBLIC_BLIND_EXCESS) {
// Append public blind excess to list
publicBlindExcesses.push(publicBlindExcess);
}
// Get public blind excess sum from combining participant's public blind excesses
var publicBlindExcessSum = Secp256k1Zkp.combinePublicKeys(publicBlindExcesses);
// Check if getting public blind excess sum failed
if(publicBlindExcessSum === Secp256k1Zkp.OPERATION_FAILED) {
// Throw error
throw "Getting public blind excess sum failed.";
}
// Check if getting excess from public blind excess sum failed
var excess = Secp256k1Zkp.publicKeyToPedersenCommit(publicBlindExcessSum);
if(excess === Secp256k1Zkp.OPERATION_FAILED) {
// Throw error
throw "Getting excess from public blind excess sum failed.";
}
// Return excess
return excess;
}
// Otherwise
else {
// Try
try {
// Get offset excess
var offsetExcess = this.getOffsetExcess();
}
// Catch errors
catch(error) {
// Throw error
throw error;
}
// Check if getting transaction excess from commits sum failed
var transactionExcess = this.getCommitsSum();
if(transactionExcess === false) {
// Throw error
throw "Getting transaction excess from commits sum failed.";
}
// Check if getting excess from transaction excess and offset excess failed
var excess = Secp256k1Zkp.pedersenCommitSum([
// Transaction excess
transactionExcess
], [
// Offset excess
offsetExcess
]);
if(excess === Secp256k1Zkp.OPERATION_FAILED) {
// Throw error
throw "Getting excess from transaction excess and offset excess failed.";
}
// Return excess
return excess;
}
}
// Is equal to
isEqualTo(slate) {
// Check if ID aren't equal
if(Common.arraysAreEqual(this.getId().getData(), slate.getId().getData()) === false)
// Return false
return false;
// Check if amounts aren't equal
if(this.getAmount().isEqualTo(slate.getAmount()) === false)
// Return false
return false;
// Check if fees aren't equal
if(this.getFee().isEqualTo(slate.getFee()) === false)
// Return false
return false;
// Check if lock heights aren't equal
if(this.getLockHeight().isEqualTo(slate.getLockHeight()) === false)
// Return false
return false;
// Check if relative heights aren't equal
if((this.getRelativeHeight() === Slate.NO_RELATIVE_HEIGHT && slate.getRelativeHeight() !== Slate.NO_RELATIVE_HEIGHT) || (this.getRelativeHeight() !== Slate.NO_RELATIVE_HEIGHT && slate.getRelativeHeight() === Slate.NO_RELATIVE_HEIGHT) || (this.getRelativeHeight() !== Slate.NO_RELATIVE_HEIGHT && this.getRelativeHeight().isEqualTo(slate.getRelativeHeight()) === false))
// Return false
return false;
// Check if heights aren't equal
if((this.getHeight() === Slate.UNKNOWN_HEIGHT && slate.getHeight() !== Slate.UNKNOWN_HEIGHT) || (this.getHeight() !== Slate.UNKNOWN_HEIGHT && slate.getHeight() === Slate.UNKNOWN_HEIGHT) || (this.getHeight() !== Slate.UNKNOWN_HEIGHT && this.getHeight().isEqualTo(slate.getHeight()) === false))
// Return false
return false;
// Check if time to live cutoff heights aren't equal
if((this.getTimeToLiveCutOffHeight() === Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT && slate.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT) || (this.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT && slate.getTimeToLiveCutOffHeight() === Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT) || (this.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT && this.getTimeToLiveCutOffHeight().isEqualTo(slate.getTimeToLiveCutOffHeight()) === false))
// Return false
return false;
// Check if number of participants aren't equal
if(this.getNumberOfParticipants().isEqualTo(slate.getNumberOfParticipants()) === false)
// Return false
return false;
// Check if not compact
if(this.isCompact() === false) {
// Check if offsets aren't equal
if(Common.arraysAreEqual(this.getOffset(), slate.getOffset()) === false)
// Return false
return false;
}
// Otherwise
else {
// Check if offsets are equal
if(Common.arraysAreEqual(this.getOffset(), slate.getOffset()) === true)
// Return false
return false;
}
// Check if block header versions aren't equal
if(this.getBlockHeaderVersion().isEqualTo(slate.getBlockHeaderVersion()) === false)
// Return false
return false;
// Check if original versions aren't equal
if((this.getOriginalVersion() instanceof BigNumber === true && (slate.getOriginalVersion() instanceof BigNumber === false || this.getOriginalVersion().isEqualTo(slate.getOriginalVersion()) === false)) || (typeof this.getOriginalVersion() === "string" && (typeof slate.getOriginalVersion() !== "string" || this.getOriginalVersion() !== slate.getOriginalVersion()))) {
// Check if orignal version difference isn't allowed
if(this.getOriginalVersion() instanceof BigNumber === false || slate.getOriginalVersion() instanceof BigNumber === false || this.getOriginalVersion().isEqualTo(Slate.VERSION_TWO) === false || slate.getOriginalVersion().isEqualTo(Slate.VERSION_THREE) === false) {
// Return false
return false;
}
}
// Check if versions aren't compatible
if((this.getVersion() instanceof BigNumber === true && (slate.getVersion() instanceof BigNumber === false || this.getVersion().isLessThan(slate.getVersion()) === true)) || (typeof this.getVersion() === "string" && (typeof slate.getVersion() !== "string" || this.getVersion() !== slate.getVersion())))
// Return false
return false;
// Check if receiver addresses aren't equal
if(this.getReceiverAddress() !== slate.getReceiverAddress())
// Return false
return false;
// Check if sender addresses aren't equal
if(this.getSenderAddress() !== slate.getSenderAddress())
// Return false
return false;
// Check if number of inputs changed
if(this.getInputs()["length"] !== slate.getInputs()["length"])
// Return false
return false;
// Go through all inputs
for(var i = 0; i < this.getInputs()["length"]; ++i) {
// Check if inputs aren't equal
if(this.getInputs()[i].isEqualTo(slate.getInputs()[i]) === false)
// Return false
return false;
}
// Check if number of kernels changed
if(this.getKernels()["length"] !== slate.getKernels()["length"])
// Return false
return false;
// Go through all kernels
for(var i = 0; i < this.getKernels()["length"]; ++i) {
// Check if kernels aren't equal
if(this.getKernels()[i].isEqualTo(slate.getKernels()[i]) === false)
// Return false
return false;
}
// Go through all outputs
for(var i = 0; i < this.getOutputs()["length"]; ++i) {
// Set output found to false
var outputFound = false;
// Go through all slate's outputs
for(var j = 0; j < slate.getOutputs()["length"]; ++j) {
// Check if outputs are equal
if(this.getOutputs()[i].isEqualTo(slate.getOutputs()[j]) === true) {
// Set output found
outputFound = true;
// Break
break;
}
}
// Check if output wasn't found
if(outputFound === false)
// Return falswe
return false;
}
// Go through all participants
for(var i = 0; i < this.getParticipants()["length"]; ++i) {
// Set participant found to false
var participantFound = false;
// Go through all slate's participants
for(var j = 0; j < slate.getParticipants()["length"]; ++j) {
// Check if participants are equal
if(this.getParticipants()[i].isEqualTo(slate.getParticipants()[j]) === true) {
// Set participant found
participantFound = true;
// Break
break;
}
}
// Check if participant wasn't found
if(participantFound === false)
// Return falswe
return false;
}
// Return true
return true;
}
// Is compact
isCompact() {
// Return if version is Slatepack version or version four
return this.getVersion() === Slate.VERSION_SLATEPACK || (this.getVersion() instanceof BigNumber === true && this.getVersion().isGreaterThanOrEqualTo(Slate.VERSION_FOUR) === true);
}
// Get display kernel features
getDisplayKernelFeatures() {
// Check kernel features
switch(this.getKernelFeatures()) {
// Plain features
case SlateKernel.PLAIN_FEATURES:
// Return display kernel features
return Language.getDefaultTranslation('plain');
// Coinbase features
case SlateKernel.COINBASE_FEATURES:
// Return display kernel features
return Language.getDefaultTranslation('coinbase');
// Height locked features
case SlateKernel.HEIGHT_LOCKED_FEATURES:
// Return display kernel features
return Language.getDefaultTranslation('height locked');
// No recent duplicate features
case SlateKernel.NO_RECENT_DUPLICATE_FEATURES:
// Return display kernel features
return Language.getDefaultTranslation('no recent duplicate');
}
}
// Get payment proof message
static getPaymentProofMessage(amount, commit, senderAddress) {
// Check wallet type
switch(Consensus.getWalletType()) {
// MWC wallet
case Consensus.MWC_WALLET_TYPE:
// Return creating message from commit, sender address, and amount
return (new TextEncoder()).encode(Common.toHexString(commit) + senderAddress + amount.toFixed());
// GRIN wallet
case Consensus.GRIN_WALLET_TYPE:
// Return creating message from amount, commit, and sender address
return Common.mergeArrays([
// Amount
amount.toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64),
// Commit
commit,
// Sender address
Slatepack.slatepackAddressToPublicKey(senderAddress)
]);
// EPIC wallet
case Consensus.EPIC_WALLET_TYPE:
// Return creating message from amount, commit, and sender address
return Common.mergeArrays([
// Amount
amount.toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64),
// Commit
commit,
// Sender address
Tor.torAddressToPublicKey(senderAddress)
]);
}
}
// Supported versions
static get SUPPORTED_VERSIONS() {
// Check wallet type
switch(Consensus.getWalletType()) {
// MWC wallet
case Consensus.MWC_WALLET_TYPE:
// Return supported versions
return [
// Version Slatepack
Slate.VERSION_SLATEPACK,
// Version three B
"V" + Slate.VERSION_THREE.toFixed() + "B",
// Version three
"V" + Slate.VERSION_THREE.toFixed(),
// Version two
"V" + Slate.VERSION_TWO.toFixed()
];
// GRIN wallet
case Consensus.GRIN_WALLET_TYPE:
// Return supported versions
return [
// Version four
"V" + Slate.VERSION_FOUR.toFixed()
];
// EPIC wallet
case Consensus.EPIC_WALLET_TYPE:
// Return supported versions
return [
// Version three
"V" + Slate.VERSION_THREE.toFixed(),
// Version two
"V" + Slate.VERSION_TWO.toFixed()
];
}
}
// Get required fee
static getRequiredFee(numberOfInputs, numberOfOutputs, numberOfKernels, baseFee) {
// Check wallet type
switch(Consensus.getWalletType()) {
// MWC or EPIC wallet
case Consensus.MWC_WALLET_TYPE:
case Consensus.EPIC_WALLET_TYPE:
// Get body weight from the number of inputs, outputs, and kernels
//TODO mwc-wallet changed this to use weight_by_iok when it should still be using old_weight_by_iok until hard fork
var bodyWeight = new BigNumber(numberOfOutputs).multipliedBy(Slate.BODY_WEIGHT_OUTPUT_FACTOR).plus(Math.max(numberOfKernels, 1)).minus(numberOfInputs);
// Check if body weight is less than the minimum body weight
if(bodyWeight.isLessThan(Slate.MINIMUM_BODY_WEIGHT) === true)
// Set body weight to the minimum body weight
bodyWeight = new BigNumber(Slate.MINIMUM_BODY_WEIGHT);
// Break
break;
// GRIN wallet
case Consensus.GRIN_WALLET_TYPE:
// Get body weight from the number of inputs, outputs, and kernels
var bodyWeight = Slate.getWeight(numberOfInputs, numberOfOutputs, numberOfKernels);
// Break
break;
}
// Get transaction fee from body weight and the base fee
var transactionFee = bodyWeight.multipliedBy(baseFee);
// Return transaction fee
return transactionFee;
}
// Uncompact uint64
static uncompactUint64(bitReader, hasHundreds) {
// Check if has hundreds
if(hasHundreds === true) {
// Get number of hundreds
var numberOfhundreds = bitReader.getBits(Slate.COMPACT_NUMBER_OF_HUNDREDS_LENGTH);
}
// Get number of digits
var numberOfDigits = bitReader.getBits(Slate.COMPACT_NUMBER_OF_DIGITS_LENGTH) + 1;
// Initialize digit bytes
var digitBytes = new Uint8Array(1 + Math.floor((numberOfDigits - 1) / Common.BITS_IN_A_BYTE));
// Go through all digits
for(var i = 0, j = numberOfDigits; j > 0; ++i, j -= Common.BITS_IN_A_BYTE) {
// Get digit byte
digitBytes[i] = bitReader.getBits(Math.min(j, Common.BITS_IN_A_BYTE));
}
// Check if number of digits isn't an exact number of bytes
if(numberOfDigits > Common.BITS_IN_A_BYTE && numberOfDigits % Common.BITS_IN_A_BYTE !== 0) {
// Go through all digit bytes backwards
for(var i = digitBytes["length"] - 1; i >= 0; --i) {
// Check if byte isn't the last byte
if(i !== digitBytes["length"] - 1) {
// Shift bits in the byte right
digitBytes[i] >>>= Common.BITS_IN_A_BYTE - numberOfDigits % Common.BITS_IN_A_BYTE;
}
// Check if byte isn't the first byte
if(i !== 0) {
// Update byte with shifted bits from the next byte
digitBytes[i] |= digitBytes[i - 1] << (numberOfDigits % Common.BITS_IN_A_BYTE);
}
}
}
// Get result of digit bytes
var result = new BigNumber(Common.HEX_PREFIX + Common.toHexString(digitBytes));
// Check if has hundreds
if(hasHundreds === true) {
// Go through all hundreds
for(var i = 0; i < numberOfhundreds; ++i) {
// Update result
result = result.multipliedBy(Slate.COMPACT_HUNDREDS_SCALING_FACTOR);
}
}
// Return result
return result;
}
// Compact uint64
static compactUint64(value, hasHundreds, bitWriter) {
// Check if has hundreds
if(hasHundreds === true) {
// Initialize number of hundreds
var numberOfhundreds = 0;
// Go through all hundreds in the value
while(value.modulo(Slate.COMPACT_HUNDREDS_SCALING_FACTOR).isZero() === true && numberOfhundreds < Math.pow(2, Slate.COMPACT_NUMBER_OF_HUNDREDS_LENGTH) - 1) {
// Remove hundred from the value
value = value.dividedToIntegerBy(Slate.COMPACT_HUNDREDS_SCALING_FACTOR);
// Increment number of hundreds
++numberOfhundreds;
}
}
// Initialize number of digits
var numberOfDigits = 1;
// Go through all digits in the value
for(var i = new BigNumber(1); numberOfDigits < Math.pow(2, Slate.COMPACT_NUMBER_OF_DIGITS_LENGTH) && i.isLessThan(value) === true; i = i.multipliedBy(2)) {
// Increment number of digits
++numberOfDigits;
}
// Check if has hundreds
if(hasHundreds === true) {
// Write number of hundreds
bitWriter.setBits(numberOfhundreds, Slate.COMPACT_NUMBER_OF_HUNDREDS_LENGTH);
}
// Write number of digits
bitWriter.setBits(numberOfDigits - 1, Slate.COMPACT_NUMBER_OF_DIGITS_LENGTH);
// Write value
var bytes = value.toBytes(BigNumber.BIG_ENDIAN, Math.ceil(numberOfDigits / Common.BITS_IN_A_BYTE));
// Go through all bytes
for(var i = 0; i < bytes["length"]; ++i) {
// Check if there isn't an exact number of bytes
if(numberOfDigits % Common.BITS_IN_A_BYTE !== 0) {
// Check if not the last byte
if(i !== bytes["length"] - 1) {
// Remove upper bits
bytes[i] <<= Common.BITS_IN_A_BYTE - numberOfDigits % Common.BITS_IN_A_BYTE;
// Include lower bits from next byte
bytes[i] |= bytes[i + 1] >>> (numberOfDigits % Common.BITS_IN_A_BYTE);
// Write byte
bitWriter.setBits(bytes[i], Common.BITS_IN_A_BYTE);
}
// Otherwise
else {
// Write byte
bitWriter.setBits(bytes[i], numberOfDigits % Common.BITS_IN_A_BYTE);
}
}
// Otherwise
else {
// Write byte
bitWriter.setBits(bytes[i], Common.BITS_IN_A_BYTE);
}
}
}
// Uncompact public key
static uncompactPublicKey(bitReader) {
// Get number of bytes
var numberOfBytes = bitReader.getBits(Slate.COMPACT_PUBLIC_KEY_LENGTH_LENGTH);
// Return public key
return bitReader.getBytes(numberOfBytes);
}
// Compact public key
static compactPublicKey(publicKey, bitWriter) {
// Check if public key is too long
if(publicKey["length"] >= Math.pow(2, Slate.COMPACT_PUBLIC_KEY_LENGTH_LENGTH)) {
// Throw error
throw "Public key is too long.";
}
// Write number of bytes
bitWriter.setBits(publicKey["length"], Slate.COMPACT_PUBLIC_KEY_LENGTH_LENGTH);
// Write public key
bitWriter.setBytes(publicKey);
}
// Parse slate asynchronous
static parseSlateAsynchronous(serializedSlate, isMainnet, purpose, initialSendSlate) {
// Check if serialized slate is binary
if(serializedSlate instanceof Uint8Array === true) {
// Get serialized slate data
var serializedSlateData = serializedSlate.slice()["buffer"];
}
// Return promise
return new Promise(function(resolve, reject) {
// Return sending parse slate request
return Slate.sendRequest([
// Type
Slate.PARSE_SLATE_REQUEST_TYPE,
// Serialized slate
(serializedSlate instanceof Uint8Array === true) ? serializedSlateData : Common.serializeObject(serializedSlate),
// Is mainnet
isMainnet,
// Purpose
purpose,
// Initial send slate
Common.serializeObject(initialSendSlate)
], (serializedSlate instanceof Uint8Array === true) ? [
// Serialized slate data
serializedSlateData
] : []).then(function(response) {
// Check if response is valid
if(response !== Slate.STATUS_FAILED_VALUE) {
// Resolve response
resolve(Common.unserializeObject(response));
}
// Otherwise
else {
// Reject
reject();
}
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
});
}
// Add outputs asynchronous
static addOutputsAsynchronous(slate, outputs, updateKernel = true) {
// Return promise
return new Promise(function(resolve, reject) {
// Return sending add outputs request
return Slate.sendRequest([
// Type
Slate.ADD_OUTPUTS_REQUEST_TYPE,
// Slate
Common.serializeObject(slate),
// Outputs
Common.serializeObject(outputs),
// Update kernel
updateKernel
]).then(function(response) {
// Check if response is valid
if(response !== Slate.STATUS_FAILED_VALUE) {
// Resolve response
resolve(Common.unserializeObject(response));
}
// Otherwise
else {
// Reject
reject();
}
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
});
}
// Add inputs asynchronous
static addInputsAsynchronous(slate, inputs, updateKernel = true, expectedNumberOfOutputs = 0) {
// Return promise
return new Promise(function(resolve, reject) {
// Return sending add inputs request
return Slate.sendRequest([
// Type
Slate.ADD_INPUTS_REQUEST_TYPE,
// Slate
Common.serializeObject(slate),
// Inputs
Common.serializeObject(inputs),
// Update kernel
updateKernel,
// Expected number of outputs
expectedNumberOfOutputs
]).then(function(response) {
// Check if response is valid
if(response !== Slate.STATUS_FAILED_VALUE) {
// Resolve response
resolve(Common.unserializeObject(response));
}
// Otherwise
else {
// Reject
reject();
}
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
});
}
// Verify after finalize asynchronous
static verifyAfterFinalizeAsynchronous(slate, baseFee, isMainnet) {
// Return promise
return new Promise(function(resolve, reject) {
// Return sending verify after finalize request
return Slate.sendRequest([
// Type
Slate.VERIFY_AFTER_FINALIZE_REQUEST_TYPE,
// Slate
Common.serializeObject(slate),
// Base fee
Common.serializeObject(baseFee),
// Is mainnet
isMainnet
]).then(function(response) {
// Check if response is valid
if(response !== Slate.STATUS_FAILED_VALUE) {
// Resolve
resolve();
}
// Otherwise
else {
// Reject
reject();
}
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
});
}
// No time to live cut off height
static get NO_TIME_TO_LIVE_CUT_OFF_HEIGHT() {
// Return no time to live cut off height
return null;
}
// No payment proof
static get NO_PAYMENT_PROOF() {
// Return payment proof
return null;
}
// Newest version
static get NEWEST_VERSION() {
// Check wallet type
switch(Consensus.getWalletType()) {
// MWC wallet
case Consensus.MWC_WALLET_TYPE:
// Return newest version
return Slate.VERSION_SLATEPACK;
// GRIN wallet
case Consensus.GRIN_WALLET_TYPE:
// Return newest version
return Slate.VERSION_FOUR;
// EPIC wallet
case Consensus.EPIC_WALLET_TYPE:
// Return newest version
return Slate.VERSION_THREE;
}
}
// No secret nonce
static get NO_SECRET_NONCE() {
// Return no secret nonce
return null;
}
// Unknown height
static get UNKNOWN_HEIGHT() {
// Return unknown height
return null;
}
// Minimum fee
static get MINIMUM_FEE() {
// Return minimum fee
return 1;
}
// Maximum fee
static get MAXIMUM_FEE() {
// Check wallet type
switch(Consensus.getWalletType()) {
// MWC or EPIC wallet
case Consensus.MWC_WALLET_TYPE:
case Consensus.EPIC_WALLET_TYPE:
// Return maximum fee
return Number.POSITIVE_INFINITY;
// GRIN wallet
case Consensus.GRIN_WALLET_TYPE:
// Return maximum fee
return Math.pow(2, 40) - 1;
}
}
// Initialize request type
static get INITIALIZE_REQUEST_TYPE() {
// Return initialize request type
return 0;
}
// Uninitialize request type
static get UNINITIALIZE_REQUEST_TYPE() {
// Return uninitialize request type
return Slate.INITIALIZE_REQUEST_TYPE + 1;
}
// Parse slate request type
static get PARSE_SLATE_REQUEST_TYPE() {
// Return parse slate request type
return Slate.UNINITIALIZE_REQUEST_TYPE + 1;
}
// Add outputs request type
static get ADD_OUTPUTS_REQUEST_TYPE() {
// Return add outputs request type
return Slate.PARSE_SLATE_REQUEST_TYPE + 1;
}
// Add inputs request type
static get ADD_INPUTS_REQUEST_TYPE() {
// Return add inputs request type
return Slate.ADD_OUTPUTS_REQUEST_TYPE + 1;
}
// Verify after finalize request type
static get VERIFY_AFTER_FINALIZE_REQUEST_TYPE() {
// Return verify after finalize request type
return Slate.ADD_INPUTS_REQUEST_TYPE + 1;
}
// Message request index offset
static get MESSAGE_REQUEST_INDEX_OFFSET() {
// Return message request index offset
return 0;
}
// Message type offset
static get MESSAGE_TYPE_OFFSET() {
// Return message type offset
return Slate.MESSAGE_REQUEST_INDEX_OFFSET + 1;
}
// Message initialize URL query string offset
static get MESSAGE_INITIALIZE_URL_QUERY_STRING_OFFSET() {
// Return message initialize URL query string offset
return Slate.MESSAGE_TYPE_OFFSET + 1;
}
// Message parse slate serialized slate offset
static get MESSAGE_PARSE_SLATE_SERIALIZED_SLATE_OFFSET() {
// Return message parse slate serialized slate offset
return Slate.MESSAGE_TYPE_OFFSET + 1;
}
// Message parse slate is mainnet offset
static get MESSAGE_PARSE_SLATE_IS_MAINNET_OFFSET() {
// Return message parse slate is mainnet offset
return Slate.MESSAGE_PARSE_SLATE_SERIALIZED_SLATE_OFFSET + 1;
}
// Message parse slate purpose offset
static get MESSAGE_PARSE_SLATE_PURPOSE_OFFSET() {
// Return message parse slate purpose offset
return Slate.MESSAGE_PARSE_SLATE_IS_MAINNET_OFFSET + 1;
}
// Message parse slate initial send slate offset
static get MESSAGE_PARSE_SLATE_INITIAL_SEND_SLATE_OFFSET() {
// Return message parse slate initial send slate offset
return Slate.MESSAGE_PARSE_SLATE_PURPOSE_OFFSET + 1;
}
// Message add outputs slate offset
static get MESSAGE_ADD_OUTPUTS_SLATE_OFFSET() {
// Return message add outputs slate offset
return Slate.MESSAGE_TYPE_OFFSET + 1;
}
// Message add outputs outputs offset
static get MESSAGE_ADD_OUTPUTS_OUTPUTS_OFFSET() {
// Return message add outputs outputs offset
return Slate.MESSAGE_ADD_OUTPUTS_SLATE_OFFSET + 1;
}
// Message add outputs update kernel offset
static get MESSAGE_ADD_OUTPUTS_UPDATE_KERNEL_OFFSET() {
// Return message add outputs update kernel offset
return Slate.MESSAGE_ADD_OUTPUTS_OUTPUTS_OFFSET + 1;
}
// Message add inputs slate offset
static get MESSAGE_ADD_INPUTS_SLATE_OFFSET() {
// Return message add inputs slate offset
return Slate.MESSAGE_TYPE_OFFSET + 1;
}
// Message add inputs inputs offset
static get MESSAGE_ADD_INPUTS_INPUTS_OFFSET() {
// Return message add inputs inputs offset
return Slate.MESSAGE_ADD_INPUTS_SLATE_OFFSET + 1;
}
// Message add inputs update kernel offset
static get MESSAGE_ADD_INPUTS_UPDATE_KERNEL_OFFSET() {
// Return message add inputs update kernel offset
return Slate.MESSAGE_ADD_INPUTS_INPUTS_OFFSET + 1;
}
// Message add inputs expected number of outputs offset
static get MESSAGE_ADD_INPUTS_EXPECTED_NUMBER_OF_OUTPUTS_OFFSET() {
// Return message add inputs expected number of outputs offset
return Slate.MESSAGE_ADD_INPUTS_UPDATE_KERNEL_OFFSET + 1;
}
// Message verify after finalize slate offset
static get MESSAGE_VERIFY_AFTER_FINALIZE_SLATE_OFFSET() {
// Return message verify after finalize slate offset
return Slate.MESSAGE_TYPE_OFFSET + 1;
}
// Message verify after finalize base fee offset
static get MESSAGE_VERIFY_AFTER_FINALIZE_BASE_FEE_OFFSET() {
// Return message verify after finalize base fee offset
return Slate.MESSAGE_VERIFY_AFTER_FINALIZE_SLATE_OFFSET + 1;
}
// Message verify after finalize is mainnet offset
static get MESSAGE_VERIFY_AFTER_FINALIZE_IS_MAINNET_OFFSET() {
// Return message verify after finalize is mainnet offset
return Slate.MESSAGE_VERIFY_AFTER_FINALIZE_BASE_FEE_OFFSET + 1;
}
// Status success value
static get STATUS_SUCCESS_VALUE() {
// Return status success value
return true;
}
// Status failed value
static get STATUS_FAILED_VALUE() {
// Return status failed value
return false;
}
// Private
// Reset
reset() {
// Set version to newest version
this.version = Slate.NEWEST_VERSION;
// Set original version to version
this.originalVersion = this.getVersion();
// Set block header version to the first header version
this.blockHeaderVersion = Consensus.LEGACY_HEADER_VERSION;
// Set number of participants to the default number of participants
this.numberOfParticipants = Slate.DEFAULT_NUMBER_OF_PARTICIPANTS;
// Set offset to zero offset
this.offset = Slate.ZERO_OFFSET;
// Set inputs to nothing
this.inputs = [];
// Set outputs to nothing
this.outputs = [];
// Set kernels to nothing
this.kernels = [];
// Set amount to minimum amount
this.amount = new BigNumber(Slate.MINIMUM_AMOUNT);
// Set fee to minimum fee
this.fee = new BigNumber(Slate.MINIMUM_FEE);
// Set height to unknown height
this.height = Slate.UNKNOWN_HEIGHT;
// Set lock height to no lock height
this.lockHeight = Slate.NO_LOCK_HEIGHT;
// Set relative height to no relative height
this.relativeHeight = Slate.NO_RELATIVE_HEIGHT;
// Set time to live cutoff height to no time to live cut off height
this.timeToLiveCutOffHeight = Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT;
// Set participants to nothing
this.participants = [];
// Set receiver address to no receiver address
this.receiverAddress = Slate.NO_RECEIVER_ADDRESS;
// Set receiver signature to no receiver signature
this.receiverSignature = Slate.NO_RECEIVER_SIGNATURE;
// Set sender address to no sender address
this.senderAddress = Slate.NO_SENDER_ADDRESS;
// Change ID
this.changeId();
}
// Unserialize
unserialize(serializedSlate, isMainnet, purpose, initialSendSlate) {
// Detect slate's version
var detectedVersion = Slate.detectVersion(serializedSlate, isMainnet);
// Check if version is unknown or not supported
if(detectedVersion === Slate.UNKNOWN_VERSION || Slate.SUPPORTED_VERSIONS.indexOf((detectedVersion instanceof BigNumber === true) ? "V" + detectedVersion.toFixed() : detectedVersion) === Common.INDEX_NOT_FOUND) {
// Throw error
throw "Unsupported slate.";
}
// Check slate's version
switch((detectedVersion instanceof BigNumber === true) ? detectedVersion.toFixed() : detectedVersion) {
// Version two and three
case Slate.VERSION_TWO.toFixed():
case Slate.VERSION_THREE.toFixed():
// Check if serialized slate's version info isn't supported
if("version_info" in serializedSlate === false || Object.isObject(serializedSlate["version_info"]) === false) {
// Throw error
throw "Unsupported slate.";
}
// Check if version info's version isn't supported
if("version" in serializedSlate["version_info"] === false || (Common.isNumberString(serializedSlate["version_info"]["version"]) === false && serializedSlate["version_info"]["version"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["version_info"]["version"])).isInteger() === false || (new BigNumber(serializedSlate["version_info"]["version"])).isLessThan(Slate.VERSION_ONE) === true) {
// Throw error
throw "Unsupported slate.";
}
// Set version to serialized slate's version
this.version = new BigNumber(serializedSlate["version_info"]["version"]);
// Check if serialized slate's number of participants isn't supported
if("num_participants" in serializedSlate === false || (Common.isNumberString(serializedSlate["num_participants"]) === false && serializedSlate["num_participants"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["num_participants"])).isInteger() === false || (new BigNumber(serializedSlate["num_participants"])).isLessThan(Slate.MINIMUM_NUMBER_OF_PARTICIPANTS) === true) {
// Throw error
throw "Unsupported slate.";
}
// Set number of participants to serialized slate's number of participants
this.numberOfParticipants = new BigNumber(serializedSlate["num_participants"]);
// Check if serialized slate's amount isn't supported
if("amount" in serializedSlate === false || (Common.isNumberString(serializedSlate["amount"]) === false && serializedSlate["amount"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["amount"])).isInteger() === false || (new BigNumber(serializedSlate["amount"])).isLessThan(Slate.MINIMUM_AMOUNT) === true) {
// Throw error
throw "Unsupported slate.";
}
// Set amount to serialized slate's amount
this.amount = new BigNumber(serializedSlate["amount"]);
// Check if serialized slate's fee isn't supported
if("fee" in serializedSlate === false || (Common.isNumberString(serializedSlate["fee"]) === false && serializedSlate["fee"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["fee"])).isInteger() === false || (new BigNumber(serializedSlate["fee"])).isLessThan(Slate.MINIMUM_FEE) === true) {
// Throw error
throw "Unsupported slate.";
}
// Set fee to serialized slate's fee
this.fee = new BigNumber(serializedSlate["fee"]);
// Check if serialized slate's height isn't supported
if("height" in serializedSlate === false || (Common.isNumberString(serializedSlate["height"]) === false && serializedSlate["height"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["height"])).isInteger() === false || (new BigNumber(serializedSlate["height"])).isLessThan(Consensus.FIRST_BLOCK_HEIGHT) === true) {
// Throw error
throw "Unsupported slate.";
}
// Set height to serialized slate's height
this.height = new BigNumber(serializedSlate["height"]);
// Check if serialized slate's lock height isn't supported
if("lock_height" in serializedSlate === false || (Common.isNumberString(serializedSlate["lock_height"]) === false && serializedSlate["lock_height"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["lock_height"])).isInteger() === false || (new BigNumber(serializedSlate["lock_height"])).isLessThan(Slate.NO_LOCK_HEIGHT) === true) {
// Throw error
throw "Unsupported slate.";
}
// Set lock height to serialized slate's lock height
this.lockHeight = new BigNumber(serializedSlate["lock_height"]);
// Check if version info's original version isn't supported
if("orig_version" in serializedSlate["version_info"] === false || (Common.isNumberString(serializedSlate["version_info"]["orig_version"]) === false && serializedSlate["version_info"]["orig_version"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["version_info"]["orig_version"])).isInteger() === false || (new BigNumber(serializedSlate["version_info"]["orig_version"])).isLessThan(Slate.VERSION_ONE) === true) {
// Throw error
throw "Unsupported slate.";
}
// Set original version to serialized slate's original version
this.originalVersion = new BigNumber(serializedSlate["version_info"]["orig_version"]);
// Check if version info's block header version isn't supported
if("block_header_version" in serializedSlate["version_info"] === false || (Common.isNumberString(serializedSlate["version_info"]["block_header_version"]) === false && serializedSlate["version_info"]["block_header_version"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["version_info"]["block_header_version"])).isInteger() === false || Consensus.isValidHeaderVersion(isMainnet, this.getHeight(), new BigNumber(serializedSlate["version_info"]["block_header_version"])) === false) {
// Throw error
throw "Unsupported slate.";
}
// Set block header version to serialized slate's block header version
this.blockHeaderVersion = new BigNumber(serializedSlate["version_info"]["block_header_version"]);
// Check if version is at least version three
if(this.getVersion().isGreaterThanOrEqualTo(Slate.VERSION_THREE) === true) {
// Check if serialized slate's time to live cut off height isn't supported
if("ttl_cutoff_height" in serializedSlate === false || (serializedSlate["ttl_cutoff_height"] !== null && ((Common.isNumberString(serializedSlate["ttl_cutoff_height"]) === false && serializedSlate["ttl_cutoff_height"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["ttl_cutoff_height"])).isInteger() === false || (new BigNumber(serializedSlate["ttl_cutoff_height"])).isLessThanOrEqualTo(this.getHeight()) === true || (new BigNumber(serializedSlate["ttl_cutoff_height"])).isLessThan(this.getLockHeight()) === true))) {
// Throw error
throw "Unsupported slate.";
}
// Set time to live cut off height to serialized slate's time to live cut off height
this.timeToLiveCutOffHeight = (serializedSlate["ttl_cutoff_height"] !== null) ? new BigNumber(serializedSlate["ttl_cutoff_height"]) : Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT;
// Check if serialized slate's payment proof isn't supported
if("payment_proof" in serializedSlate === false || (serializedSlate["payment_proof"] !== null && Object.isObject(serializedSlate["payment_proof"]) === false)) {
// Throw error
throw "Unsupported slate.";
}
// Check if payment proof's receiver address isn't supported
if(serializedSlate["payment_proof"] !== null && ("receiver_address" in serializedSlate["payment_proof"] === false || typeof serializedSlate["payment_proof"]["receiver_address"] !== "string")) {
// Throw error
throw "Unsupported slate.";
}
// Check if serialized slate provided a payment proof
if(serializedSlate["payment_proof"] !== null) {
// Check wallet type
switch(Consensus.getWalletType()) {
// MWC wallet
case Consensus.MWC_WALLET_TYPE:
// Check receiver address's length
switch(serializedSlate["payment_proof"]["receiver_address"]["length"]) {
// MQS address length
case Mqs.ADDRESS_LENGTH:
// Try
try {
// Get public key from receiver's MQS address
Mqs.mqsAddressToPublicKey(serializedSlate["payment_proof"]["receiver_address"], isMainnet);
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Break
break;
// Tor address length
case Tor.ADDRESS_LENGTH:
// Try
try {
// Get public key from receiver's Tor address
Tor.torAddressToPublicKey(serializedSlate["payment_proof"]["receiver_address"]);
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Break
break;
// Default
default:
// Throw error
throw "Unsupported slate.";
}
// Set receiver address to serialized slate's receiver address
this.receiverAddress = serializedSlate["payment_proof"]["receiver_address"];
// Break
break;
// EPIC wallet
case Consensus.EPIC_WALLET_TYPE:
// Check if payment proof's receiver address isn't supported
if(Common.isHexString(serializedSlate["payment_proof"]["receiver_address"]) === false || Common.hexStringLength(serializedSlate["payment_proof"]["receiver_address"]) !== Crypto.ED25519_PUBLIC_KEY_LENGTH) {
// Throw error
throw "Unsupported slate.";
}
// Try
try {
// Set receiver address to the Tor address created from the receiver's public key
this.receiverAddress = Tor.publicKeyToTorAddress(Common.fromHexString(serializedSlate["payment_proof"]["receiver_address"]));
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Break
break;
}
}
// Otherwise
else {
// Set receiver address to no receiver address
this.receiverAddress = Slate.NO_RECEIVER_ADDRESS;
}
// Check if payment proof's receiver signature isn't supported
if(serializedSlate["payment_proof"] !== null && ("receiver_signature" in serializedSlate["payment_proof"] === false || (serializedSlate["payment_proof"]["receiver_signature"] !== null && Common.isHexString(serializedSlate["payment_proof"]["receiver_signature"]) === false))) {
// Throw error
throw "Unsupported slate.";
}
// Check if serialized slate provided a payment proof and a serialized signature
if(serializedSlate["payment_proof"] !== null && serializedSlate["payment_proof"]["receiver_signature"] !== null) {
// Check wallet type
switch(Consensus.getWalletType()) {
// MWC wallet
case Consensus.MWC_WALLET_TYPE:
// Check if payment proof's receiver signature isn't supported
if(Common.hexStringLength(serializedSlate["payment_proof"]["receiver_signature"]) > Crypto.MAXIMUM_MESSAGE_HASH_SIGNATURE_LENGTH && Common.hexStringLength(serializedSlate["payment_proof"]["receiver_signature"]) !== Crypto.ED25519_SIGNATURE_LENGTH) {
// Throw error
throw "Unsupported slate.";
}
// Break
break;
// EPIC wallet
case Consensus.EPIC_WALLET_TYPE:
// Check if payment proof's receiver signature isn't supported
if(Common.hexStringLength(serializedSlate["payment_proof"]["receiver_signature"]) !== Crypto.ED25519_SIGNATURE_LENGTH) {
// Throw error
throw "Unsupported slate.";
}
// Break
break;
}
}
// Set receiver signature to serialized slate's receiver signature
this.receiverSignature = (serializedSlate["payment_proof"] !== null && serializedSlate["payment_proof"]["receiver_signature"] !== null) ? Common.fromHexString(serializedSlate["payment_proof"]["receiver_signature"]) : Slate.NO_RECEIVER_SIGNATURE;
// Check if payment proof's sender address isn't supported
if(serializedSlate["payment_proof"] !== null && ("sender_address" in serializedSlate["payment_proof"] === false || typeof serializedSlate["payment_proof"]["sender_address"] !== "string")) {
// Throw error
throw "Unsupported slate.";
}
// Check if serialized slate provided a payment proof
if(serializedSlate["payment_proof"] !== null) {
// Check wallet type
switch(Consensus.getWalletType()) {
// MWC wallet
case Consensus.MWC_WALLET_TYPE:
// Check sender address's length
switch(serializedSlate["payment_proof"]["sender_address"]["length"]) {
// MQS address length
case Mqs.ADDRESS_LENGTH:
// Try
try {
// Get public key from sender's MQS address
Mqs.mqsAddressToPublicKey(serializedSlate["payment_proof"]["sender_address"], isMainnet);
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Break
break;
// Tor address length
case Tor.ADDRESS_LENGTH:
// Try
try {
// Get public key from sender's Tor address
Tor.torAddressToPublicKey(serializedSlate["payment_proof"]["sender_address"]);
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Break
break;
// Default
default:
// Throw error
throw "Unsupported slate.";
}
// Set sender address to serialized slate's sender address
this.senderAddress = serializedSlate["payment_proof"]["sender_address"];
// Break
break;
// EPIC wallet
case Consensus.EPIC_WALLET_TYPE:
// Check if payment proof's sender address isn't supported
if(Common.isHexString(serializedSlate["payment_proof"]["sender_address"]) === false || Common.hexStringLength(serializedSlate["payment_proof"]["sender_address"]) !== Crypto.ED25519_PUBLIC_KEY_LENGTH) {
// Throw error
throw "Unsupported slate.";
}
// Try
try {
// Set sender address to the Tor address created from the sender's public key
this.senderAddress = Tor.publicKeyToTorAddress(Common.fromHexString(serializedSlate["payment_proof"]["sender_address"]));
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Break
break;
}
}
// Otherwise
else {
// Set sender address to no sender address
this.senderAddress = Slate.NO_SENDER_ADDRESS;
}
}
// Check if serialized slate's ID isn't supported
if("id" in serializedSlate === false || typeof serializedSlate["id"] !== "string") {
// Throw error
throw "Unsupported slate.";
}
// Try
try {
// Set ID to serialized slate's ID
this.id = new Uuid(serializedSlate["id"]);
// Check if ID isn't a random UUID
if(this.getId().isRandom() === false) {
// Throw error
throw "Unsupported slate.";
}
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Check if serialized slate's transaction isn't supported
if("tx" in serializedSlate === false || Object.isObject(serializedSlate["tx"]) === false) {
// Throw error
throw "Unsupported slate.";
}
// Check if transaction's body isn't supported
if("body" in serializedSlate["tx"] === false || Object.isObject(serializedSlate["tx"]["body"]) === false) {
// Throw error
throw "Unsupported slate.";
}
// Check if body's inputs are invalid
if("inputs" in serializedSlate["tx"]["body"] === false || Array.isArray(serializedSlate["tx"]["body"]["inputs"]) === false) {
// Throw error
throw "Unsupported slate.";
}
// Try
try {
// Set self
var self = this;
// Set inputs to serialized slate's inputs
this.inputs = serializedSlate["tx"]["body"]["inputs"].map(function(input) {
// Check if input isn't supported
if(Object.isObject(input) === false) {
// Throw error
throw "Unsupported slate.";
}
// Return slate input
return new SlateInput(input, self);
});
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Check if no inputs exist
if(this.getInputs()["length"] === 0) {
// Throw error
throw "Unsupported slate.";
}
// Check if body's outputs are invalid
if("outputs" in serializedSlate["tx"]["body"] === false || Array.isArray(serializedSlate["tx"]["body"]["outputs"]) === false) {
// Throw error
throw "Unsupported slate.";
}
// Try
try {
// Set outputs to serialized slate's outputs
this.outputs = serializedSlate["tx"]["body"]["outputs"].map(function(output) {
// Check if output isn't supported
if(Object.isObject(output) === false) {
// Throw error
throw "Unsupported slate.";
}
// Return slate output
return new SlateOutput(output, self);
});
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Check if body's kernels are invalid
if("kernels" in serializedSlate["tx"]["body"] === false || Array.isArray(serializedSlate["tx"]["body"]["kernels"]) === false) {
// Throw error
throw "Unsupported slate.";
}
// Try
try {
// Set kernels to serialized slate's kernels
this.kernels = serializedSlate["tx"]["body"]["kernels"].map(function(kernel) {
// Check if kernel isn't supported
if(Object.isObject(kernel) === false) {
// Throw error
throw "Unsupported slate.";
}
// Return slate kernel
return new SlateKernel(kernel, self, isMainnet);
});
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Check if transaction's offset isn't supported
if("offset" in serializedSlate["tx"] === false || Common.isHexString(serializedSlate["tx"]["offset"]) === false || Common.hexStringLength(serializedSlate["tx"]["offset"]) !== Crypto.BLINDING_FACTOR_LENGTH || Secp256k1Zkp.isValidSecretKey(Common.fromHexString(serializedSlate["tx"]["offset"])) !== true) {
// Throw error
throw "Unsupported slate.";
}
// Set offset to serialized slate's offset
this.offset = Common.fromHexString(serializedSlate["tx"]["offset"]);
// Check if serialized slate's participant data isn't supported
if("participant_data" in serializedSlate === false || Array.isArray(serializedSlate["participant_data"]) === false || this.getNumberOfParticipants().isLessThan(serializedSlate["participant_data"]["length"]) === true) {
// Throw error
throw "Unsupported slate.";
}
// Try
try {
// Set participants to serialized slate's participant data
this.participants = serializedSlate["participant_data"].map(function(participant) {
// Check if participant isn't supported
if(Object.isObject(participant) === false) {
// Throw error
throw "Unsupported slate.";
}
// Return slate participant
return new SlateParticipant(participant, self);
});
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Break
break;
// Version Slatepack
case Slate.VERSION_SLATEPACK:
// Initialize bit reader for the serialized slate
var bitReader = new BitReader(serializedSlate);
// Try
try {
// Set version to detected version
this.version = detectedVersion;
// Set original version to version
this.originalVersion = this.getVersion();
// Check if serialized slate's purpose isn't correct
if(bitReader.getBits(Slate.COMPACT_SLATE_PURPOSE_LENGTH) !== purpose) {
// Throw error
throw "Unsupported slate.";
}
// Set ID to serialized slate's ID
this.id = new Uuid(Uuid.serializeData(bitReader.getBytes(Uuid.BYTE_LENGTH)));
// Check if ID isn't a random UUID
if(this.getId().isRandom() === false) {
// Throw error
throw "Unsupported slate.";
}
// Get serialized slate's mainnet
var mainnet = bitReader.getBits(Slate.COMPACT_BOOLEAN_LENGTH) === Slate.COMPACT_BOOLEAN_TRUE;
// Check if network isn't supported
if(mainnet !== isMainnet) {
// Throw error
throw "Unsupported slate.";
}
// Check if purpose is send initial
if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) {
// Set amount to serialized slate's amount
this.amount = Slate.uncompactUint64(bitReader, true);
// Check if serialized slate's amount isn't supported
if(this.getAmount().isLessThan(Slate.MINIMUM_AMOUNT) === true) {
// Throw error
throw "Unsupported slate.";
}
// Set fee to serialized slate's fee
this.fee = Slate.uncompactUint64(bitReader, true);
// Check if serialized slate's fee isn't supported
if(this.getFee().isLessThan(Slate.MINIMUM_FEE) === true) {
// Throw error
throw "Unsupported slate.";
}
}
// Set height to serialized slate's height
this.height = Slate.uncompactUint64(bitReader, false);
// Check if serialized slate's height isn't supported
if(this.getHeight().isLessThan(Consensus.FIRST_BLOCK_HEIGHT) === true) {
// Throw error
throw "Unsupported slate.";
}
// Set block header version to header version at the height
this.blockHeaderVersion = Consensus.getHeaderVersion(isMainnet, this.getHeight());
// Set lock height to serialized slate's lock height
this.lockHeight = Slate.uncompactUint64(bitReader, false);
// Check if serialized slate's lock height isn't supported
if(this.getLockHeight().isLessThan(Slate.NO_LOCK_HEIGHT) === true) {
// Throw error
throw "Unsupported slate.";
}
// Check if serialized slate contains a time to live cut off height
if(bitReader.getBits(Slate.COMPACT_BOOLEAN_LENGTH) === Slate.COMPACT_BOOLEAN_TRUE) {
// Set time to live cut off height to serialized slate's time to live cut off height
this.timeToLiveCutOffHeight = Slate.uncompactUint64(bitReader, false);
// Check if serialized slate's time to live cut off height isn't supported
if(this.getTimeToLiveCutOffHeight().isLessThanOrEqualTo(this.getHeight()) === true || this.getTimeToLiveCutOffHeight().isLessThan(this.getLockHeight()) === true) {
// Throw error
throw "Unsupported slate.";
}
}
// Otherwise
else {
// Set time to live cut off height to serialized slate's time to live cut off height
this.timeToLiveCutOffHeight = Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT;
}
// Check if purpose is send initial
if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) {
// Add serialized slate's participant to the participants
this.participants.push(new SlateParticipant(bitReader, this));
// Check if serialized slate contains proof addresses
if(bitReader.getBits(Slate.COMPACT_BOOLEAN_LENGTH) === Slate.COMPACT_BOOLEAN_TRUE) {
// Set sender address to serialized slate's sender address
this.senderAddress = Slate.uncompactProofAddress(bitReader, isMainnet);
// Check sender address's length
switch(this.getSenderAddress()["length"]) {
// MQS address length
case Mqs.ADDRESS_LENGTH:
// Try
try {
// Get public key from sender's MQS address
Mqs.mqsAddressToPublicKey(this.getSenderAddress(), isMainnet);
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Break
break;
// Tor address length
case Tor.ADDRESS_LENGTH:
// Try
try {
// Get public key from sender's Tor address
Tor.torAddressToPublicKey(this.getSenderAddress());
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Break
break;
// Default
default:
// Throw error
throw "Unsupported slate.";
}
// Set receiver address to serialized slate's receiver address
this.receiverAddress = Slate.uncompactProofAddress(bitReader, isMainnet);
// Check receiver address's length
switch(this.getReceiverAddress()["length"]) {
// MQS address length
case Mqs.ADDRESS_LENGTH:
// Try
try {
// Get public key from receiver's MQS address
Mqs.mqsAddressToPublicKey(this.getReceiverAddress(), isMainnet);
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Break
break;
// Tor address length
case Tor.ADDRESS_LENGTH:
// Try
try {
// Get public key from receiver's Tor address
Tor.torAddressToPublicKey(this.getReceiverAddress());
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Break
break;
// Default
default:
// Throw error
throw "Unsupported slate.";
}
}
// Otherwise
else {
// Set receiver address to serialized slate's receiver address
this.receiverAddress = Slate.NO_RECEIVER_ADDRESS;
// Set sender address to serialized slate's sender address
this.senderAddress = Slate.NO_SENDER_ADDRESS;
}
// Set kernels to serialized slate's kernels
this.kernels.push(new SlateKernel(this.getKernelFeatures(), this.getFee(), this.getLockHeight(), this.getRelativeHeight()));
}
// Check if purpose is send response
if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_RESPONSE) {
// Set number of participants to the initial send slate's number of participants
this.numberOfParticipants = initialSendSlate.getNumberOfParticipants();
// Set amount to inital send slate's amount
this.amount = initialSendSlate.getAmount();
// Set fee to inital send slate's fee
this.fee = initialSendSlate.getFee();
// Set inputs to initial send slate's inputs
this.inputs = initialSendSlate.getInputs().map(function(input) {
// Return input
return new SlateInput(input.getFeatures(), input.getCommit());
});
// Set outputs to initial send slate's outputs
this.outputs = initialSendSlate.getOutputs().map(function(output) {
// Return output
return new SlateOutput(output.getFeatures(), output.getCommit(), output.getProof());
});
// Set participants to initial send slate's participants
this.participants = initialSendSlate.getParticipants().map(function(participant) {
// Return participant
return new SlateParticipant(participant.getId(), participant.getPublicBlindExcess(), participant.getPublicNonce(), participant.getPartialSignature(), participant.getMessage(), participant.getMessageSignature());
});
// Set offset to serialized slate's offset
this.offset = bitReader.getBytes(Crypto.BLINDING_FACTOR_LENGTH);
// Check if serialized slate's offset isn't supported
if(Secp256k1Zkp.isValidSecretKey(this.getOffset()) !== true) {
// Throw error
throw "Unsupported slate.";
}
// Go through all serialized slate's outputs
var outputs = [];
do {
// Append serialized slate's output to outputs
outputs.push(new SlateOutput(bitReader, this));
} while(bitReader.getBits(Slate.COMPACT_BOOLEAN_LENGTH) === Slate.COMPACT_BOOLEAN_TRUE);
// Add outputs
this.addOutputs(outputs, false);
// Go through all serialized slate's kernels
do {
// Append serialized slate's kernel to kernels
this.kernels.push(new SlateKernel(bitReader, this, isMainnet));
} while(bitReader.getBits(Slate.COMPACT_BOOLEAN_LENGTH) === Slate.COMPACT_BOOLEAN_TRUE);
// Add serialized slate's participant to the participants
this.participants.push(new SlateParticipant(bitReader, this));
// Check if serialized slate contains proof addresses
if(bitReader.getBits(Slate.COMPACT_BOOLEAN_LENGTH) === Slate.COMPACT_BOOLEAN_TRUE) {
// Set sender address to serialized slate's sender address
this.senderAddress = Slate.uncompactProofAddress(bitReader, isMainnet);
// Check sender address's length
switch(this.getSenderAddress()["length"]) {
// MQS address length
case Mqs.ADDRESS_LENGTH:
// Try
try {
// Get public key from sender's MQS address
Mqs.mqsAddressToPublicKey(this.getSenderAddress(), isMainnet);
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Break
break;
// Tor address length
case Tor.ADDRESS_LENGTH:
// Try
try {
// Get public key from sender's Tor address
Tor.torAddressToPublicKey(this.getSenderAddress());
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Break
break;
// Default
default:
// Throw error
throw "Unsupported slate.";
}
// Set receiver address to serialized slate's receiver address
this.receiverAddress = Slate.uncompactProofAddress(bitReader, isMainnet);
// Check receiver address's length
switch(this.getReceiverAddress()["length"]) {
// MQS address length
case Mqs.ADDRESS_LENGTH:
// Try
try {
// Get public key from receiver's MQS address
Mqs.mqsAddressToPublicKey(this.getReceiverAddress(), isMainnet);
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Break
break;
// Tor address length
case Tor.ADDRESS_LENGTH:
// Try
try {
// Get public key from receiver's Tor address
Tor.torAddressToPublicKey(this.getReceiverAddress());
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Break
break;
// Default
default:
// Throw error
throw "Unsupported slate.";
}
}
// Otherwise
else {
// Set receiver address to serialized slate's receiver address
this.receiverAddress = Slate.NO_RECEIVER_ADDRESS;
// Set sender address to serialized slate's sender address
this.senderAddress = Slate.NO_SENDER_ADDRESS;
}
// Check if serialized slate contains a proof signature
if(bitReader.getBits(Slate.COMPACT_BOOLEAN_LENGTH) === Slate.COMPACT_BOOLEAN_TRUE) {
// Check if doesn't have a payment proof
if(this.hasPaymentProof() === false) {
// Throw error
throw "Unsupported slate.";
}
// Get serialized slate's receiver signature length
var receiverSignatureLength = bitReader.getBits(Slate.COMPACT_PROOF_SIGNATURE_LENGTH_LENGTH) + Crypto.ED25519_SIGNATURE_LENGTH;
// Check if serialized slate's receiver signature length isn't supported
if(receiverSignatureLength > Crypto.MAXIMUM_MESSAGE_HASH_SIGNATURE_LENGTH && receiverSignatureLength !== Crypto.ED25519_SIGNATURE_LENGTH) {
// Throw error
throw "Unsupported slate.";
}
// Set receiver signature to serialized slate's receiver signature
this.receiverSignature = bitReader.getBytes(receiverSignatureLength);
}
// Otherwise
else {
// Set receiver signature to serialized slate's receiver signature
this.receiverSignature = Slate.NO_RECEIVER_SIGNATURE;
}
}
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Break
break;
// Version four
case Slate.VERSION_FOUR.toFixed():
// Set version to detected version
this.version = detectedVersion;
// Set original version to version
this.originalVersion = this.getVersion();
// Check if serialized slate is binary
if(serializedSlate instanceof Uint8Array === true) {
// Initialize bit reader for the serialized slate
var bitReader = new BitReader(serializedSlate);
// Try
try {
// Skip serialized slate's version
bitReader.getBytes(Common.BYTES_IN_A_UINT16);
// Set block header version to serialized slate's block header version
this.blockHeaderVersion = new BigNumber(Common.HEX_PREFIX + Common.toHexString(bitReader.getBytes(Common.BYTES_IN_A_UINT16)));
// Set ID to serialized slate's ID
this.id = new Uuid(Uuid.serializeData(bitReader.getBytes(Uuid.BYTE_LENGTH)));
// Check if ID isn't a random UUID
if(this.getId().isRandom() === false) {
// Throw error
throw "Unsupported slate.";
}
// Check if serialized slate's purpose isn't correct
if(bitReader.getBytes(1)[0] !== purpose + 1) {
// Throw error
throw "Unsupported slate.";
}
// Set offset to serialized slate's offset
this.offset = bitReader.getBytes(Crypto.BLINDING_FACTOR_LENGTH);
// Check if serialized slate's offset isn't supported
if((purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL && Common.arraysAreEqual(this.getOffset(), Slate.ZERO_OFFSET) === false && Secp256k1Zkp.isValidSecretKey(this.getOffset()) !== true) || (purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_RESPONSE && Secp256k1Zkp.isValidSecretKey(this.getOffset()) !== true)) {
// Throw error
throw "Unsupported slate.";
}
// Get serialized slate's optional fields
var optionalFields = bitReader.getBytes(1)[0];
// Set number of participants to serialized slate's number of participants if it includes it
this.numberOfParticipants = ((optionalFields & 0b00000001) !== 0) ? new BigNumber(bitReader.getBytes(1)[0]) : Slate.DEFAULT_NUMBER_OF_PARTICIPANTS;
// Check if serialized slate's number of participants isn't supported
if(this.getNumberOfParticipants().isLessThan(Slate.MINIMUM_NUMBER_OF_PARTICIPANTS) === true) {
// Throw error
throw "Unsupported slate.";
}
// Set amount to serialized slate's amount if it includes it
this.amount = ((optionalFields & 0b00000010) !== 0) ? new BigNumber(Common.HEX_PREFIX + Common.toHexString(bitReader.getBytes(Common.BYTES_IN_A_UINT64))) : ((purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) ? new BigNumber(0) : initialSendSlate.getAmount());
// Check if serialized slate's amount isn't supported
if(this.getAmount().isLessThan(Slate.MINIMUM_AMOUNT) === true) {
// Throw error
throw "Unsupported slate.";
}
// Set fee to serialized slate's fee if it includes it
this.fee = ((optionalFields & 0b00000100) !== 0) ? new BigNumber(Common.HEX_PREFIX + Common.toHexString(bitReader.getBytes(Common.BYTES_IN_A_UINT64))) : ((purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) ? new BigNumber(0) : initialSendSlate.getFee());
// Check if serialized slate's fee isn't supported
if(this.getFee().isLessThan(Slate.MINIMUM_FEE) === true) {
// Throw error
throw "Unsupported slate.";
}
// Get serialized slate's features if it includes it
var features = ((optionalFields & 0b00001000) !== 0) ? bitReader.getBytes(1)[0] : SlateKernel.PLAIN_FEATURES;
// Set time to live cut off height to serialized slate's time to live cut off height if it includes it
this.timeToLiveCutOffHeight = ((optionalFields & 0b00010000) !== 0) ? new BigNumber(Common.HEX_PREFIX + Common.toHexString(bitReader.getBytes(Common.BYTES_IN_A_UINT64))) : Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT;
// Get serialized sate's participants length
var participantsLength = bitReader.getBytes(1)[0];
// Check if purpose is send response
if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_RESPONSE) {
// Set height to initial send slate's height
this.height = initialSendSlate.getHeight();
// Set inputs to initial send slate's inputs
this.inputs = initialSendSlate.getInputs().map(function(input) {
// Return input
return new SlateInput(input.getFeatures(), input.getCommit());
});
// Set outputs to initial send slate's outputs
this.outputs = initialSendSlate.getOutputs().map(function(output) {
// Return output
return new SlateOutput(output.getFeatures(), output.getCommit(), output.getProof());
});
// Set participants to initial send slate's participants
this.participants = initialSendSlate.getParticipants().map(function(participant) {
// Return participant
return new SlateParticipant(participant.getId(), participant.getPublicBlindExcess(), participant.getPublicNonce(), participant.getPartialSignature(), participant.getMessage(), participant.getMessageSignature());
});
}
// Check if serialized slate's participants isn't supported
if(this.getNumberOfParticipants().isLessThan(participantsLength + this.getParticipants()["length"]) === true) {
// Throw error
throw "Unsupported slate.";
}
// Go through all of the serialized slate's participants
for(var i = 0; i < participantsLength; ++i) {
// Add serialized slate's participant to the participants
this.participants.push(new SlateParticipant(bitReader, this));
}
// Get serialized slate's component fields
var componentFields = bitReader.getBytes(1)[0];
// Check if serialized slate includes inputs and outputs
if((componentFields & 0b00000001) !== 0) {
// Get serialized slate's inputs and outputs length
var inputsAndOutputsLength = (new BigNumber(Common.HEX_PREFIX + Common.toHexString(bitReader.getBytes(Common.BYTES_IN_A_UINT16)))).toNumber();
// Go through all of the serialized slate's inputs and outputs
var inputs = [];
var outputs = [];
for(var i = 0; i < inputsAndOutputsLength; ++i) {
// Check if input or output is an input
if(bitReader.getBytes(1)[0] === 0) {
// Append serialized slate's input to inputs
inputs.push(new SlateInput(bitReader, this));
}
// Otherwise
else {
// Append serialized slate's output to outputs
outputs.push(new SlateOutput(bitReader, this));
}
}
// Check if purpose is send response
if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_RESPONSE) {
// Add inputs
this.addInputs(inputs, false, outputs["length"]);
// Add outputs
this.addOutputs(outputs, false);
}
}
// Check if serialized slate includes payment proof
if((componentFields & 0b00000010) !== 0) {
// Set sender address to the Slatepack address created from the serialized slate's sender public key
this.senderAddress = Slatepack.publicKeyToSlatepackAddress(bitReader.getBytes(Crypto.ED25519_PUBLIC_KEY_LENGTH));
// Set receiver address to the Slatepack address created from the serialized slate's receiver public key
this.receiverAddress = Slatepack.publicKeyToSlatepackAddress(bitReader.getBytes(Crypto.ED25519_PUBLIC_KEY_LENGTH));
// Check if serialized slate includes a payment proof's receiver signature
if(bitReader.getBytes(1)[0] !== 0) {
// Set receiver signature to serialized slate's receiver signature
this.receiverSignature = bitReader.getBytes(Crypto.ED25519_SIGNATURE_LENGTH);
}
}
// Check serialized slate's features
switch(features) {
// Plain features
case SlateKernel.PLAIN_FEATURES:
// Break
break;
// Height locked features
case SlateKernel.HEIGHT_LOCKED_FEATURES:
// Set lock height to serialized slate's lock height
this.lockHeight = new BigNumber(Common.HEX_PREFIX + Common.toHexString(bitReader.getBytes(Common.BYTES_IN_A_UINT64)));
// Check if serialized slate's lock height isn't supported
if(this.getLockHeight().isLessThan(Slate.NO_LOCK_HEIGHT) === true) {
// Throw error
throw "Unsupported slate.";
}
// Break
break;
// Default
default:
// Throw error
throw "Unsupported slate.";
}
// Check if serialized slate's time to live cut off height isn't supported
if(this.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT && (this.getTimeToLiveCutOffHeight().isLessThan(Consensus.FIRST_BLOCK_HEIGHT) === true || (this.getLockHeight().isEqualTo(Slate.NO_LOCK_HEIGHT) === false && this.getTimeToLiveCutOffHeight().isLessThan(this.getLockHeight()) === true))) {
// Throw error
throw "Unsupported slate.";
}
// Set kernels to serialized slate's kernels
this.kernels.push(new SlateKernel(this.getKernelFeatures(), this.getFee(), this.getLockHeight(), this.getRelativeHeight()));
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
}
// Otherwise
else {
// Check if serialized slate's purpose isn't correct
if("sta" in serializedSlate === false || typeof serializedSlate["sta"] !== "string" || serializedSlate["sta"] !== Slate.purposeToText(purpose)) {
// Throw error
throw "Unsupported slate.";
}
// Check if serialized slate's number of participants isn't supported
if("num_parts" in serializedSlate === true && ((Common.isNumberString(serializedSlate["num_parts"]) === false && serializedSlate["num_parts"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["num_parts"])).isInteger() === false || (new BigNumber(serializedSlate["num_parts"])).isLessThan(Slate.MINIMUM_NUMBER_OF_PARTICIPANTS) === true)) {
// Throw error
throw "Unsupported slate.";
}
// Set number of participants to serialized slate's number of participants
this.numberOfParticipants = ("num_parts" in serializedSlate === true) ? new BigNumber(serializedSlate["num_parts"]) : Slate.DEFAULT_NUMBER_OF_PARTICIPANTS;
// Check if serialized slate's ID isn't supported
if("id" in serializedSlate === false || typeof serializedSlate["id"] !== "string") {
// Throw error
throw "Unsupported slate.";
}
// Try
try {
// Set ID to serialized slate's ID
this.id = new Uuid(serializedSlate["id"]);
// Check if ID isn't a random UUID
if(this.getId().isRandom() === false) {
// Throw error
throw "Unsupported slate.";
}
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Check if serialized slate's block header version isn't supported
if("ver" in serializedSlate === false || typeof serializedSlate["ver"] !== "string" || Slate.VERSION_PATTERN.test(serializedSlate["ver"]) === false) {
// Throw error
throw "Unsupported slate.";
}
// Set block header version to serialized slate's block header version
this.blockHeaderVersion = new BigNumber(serializedSlate["ver"].split(Slate.VERSION_SEPARATOR)[1]);
// Check if serialzed slate contains features
if("feat" in serializedSlate === true) {
// Check if serialized slate's features isn't supported
if((Common.isNumberString(serializedSlate["feat"]) === false && serializedSlate["feat"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["feat"])).isGreaterThan(Number.MAX_SAFE_INTEGER) === true) {
// Throw error
throw "Unsupported slate.";
}
// Check serialized slate's features
switch((new BigNumber(serializedSlate["feat"])).toNumber()) {
// Plain features
case SlateKernel.PLAIN_FEATURES:
// Break
break;
// Height locked features
case SlateKernel.HEIGHT_LOCKED_FEATURES:
// Check if serialized slate's lock height isn't supported
if("feat_args" in serializedSlate === false || serializedSlate["feat_args"] === null || Object.isObject(serializedSlate["feat_args"]) === false || "lock_hgt" in serializedSlate["feat_args"] === false || (Common.isNumberString(serializedSlate["feat_args"]["lock_hgt"]) === false && serializedSlate["feat_args"]["lock_hgt"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["feat_args"]["lock_hgt"])).isInteger() === false || (new BigNumber(serializedSlate["feat_args"]["lock_hgt"])).isLessThan(Slate.NO_LOCK_HEIGHT) === true) {
// Throw error
throw "Unsupported slate.";
}
// Set lock height to serialized slate's lock height
this.lockHeight = new BigNumber(serializedSlate["feat_args"]["lock_hgt"]);
// Break
break;
// Default
default:
// Throw error
throw "Unsupported slate.";
}
}
// Otherwise
else {
// Check if serialized slate's features arguments isn't supported
if("feat_args" in serializedSlate === true && serializedSlate["feat_args"] !== null) {
// Throw error
throw "Unsupported slate.";
}
}
// Check if serialized slate's time to live cut off height isn't supported
if("ttl" in serializedSlate === true && serializedSlate["ttl"] !== null && ((Common.isNumberString(serializedSlate["ttl"]) === false && serializedSlate["ttl"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["ttl"])).isInteger() === false || (new BigNumber(serializedSlate["ttl"])).isLessThan(Consensus.FIRST_BLOCK_HEIGHT) === true || (this.getLockHeight().isEqualTo(Slate.NO_LOCK_HEIGHT) === false && (new BigNumber(serializedSlate["ttl"])).isLessThan(this.getLockHeight()) === true))) {
// Throw error
throw "Unsupported slate.";
}
// Set time to live cut off height to serialized slate's time to live cut off height
this.timeToLiveCutOffHeight = ("ttl" in serializedSlate === true && serializedSlate["ttl"] !== null) ? new BigNumber(serializedSlate["ttl"]) : Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT;
// Check if purpose is send response
if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_RESPONSE) {
// Set participants to initial send slate's participants
this.participants = initialSendSlate.getParticipants().map(function(participant) {
// Return participant
return new SlateParticipant(participant.getId(), participant.getPublicBlindExcess(), participant.getPublicNonce(), participant.getPartialSignature(), participant.getMessage(), participant.getMessageSignature());
});
}
// Check if serialized slate's participants isn't supported
if("sigs" in serializedSlate === false || Array.isArray(serializedSlate["sigs"]) === false || this.getNumberOfParticipants().isLessThan(serializedSlate["sigs"]["length"] + this.getParticipants()["length"]) === true) {
// Throw error
throw "Unsupported slate.";
}
// Try
try {
// Go through all of the serialized slate's participants
for(var i = 0; i < serializedSlate["sigs"]["length"]; ++i) {
// Get participant
var participant = serializedSlate["sigs"][i];
// Check if participant isn't supported
if(Object.isObject(participant) === false) {
// Throw error
throw "Unsupported slate.";
}
// Append slate participant to participants
this.participants.push(new SlateParticipant(participant, this));
}
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Check if serialized slate contains a payment proof
if("proof" in serializedSlate === true && serializedSlate["proof"] !== null) {
// Check if serialized slate's proof isn't supported
if(Object.isObject(serializedSlate["proof"]) === false) {
// Throw error
throw "Unsupported slate.";
}
// Check if payment proof's receiver address isn't supported
if("raddr" in serializedSlate["proof"] === false || Common.isHexString(serializedSlate["proof"]["raddr"]) === false || Common.hexStringLength(serializedSlate["proof"]["raddr"]) !== Crypto.ED25519_PUBLIC_KEY_LENGTH) {
// Throw error
throw "Unsupported slate.";
}
// Try
try {
// Set receiver address to the Slatepack address created from the receiver's public key
this.receiverAddress = Slatepack.publicKeyToSlatepackAddress(Common.fromHexString(serializedSlate["proof"]["raddr"]));
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Check if payment proof's receiver signature isn't supported
if("rsig" in serializedSlate["proof"] === true && serializedSlate["proof"]["rsig"] !== null && (Common.isHexString(serializedSlate["proof"]["rsig"]) === false || Common.hexStringLength(serializedSlate["proof"]["rsig"]) !== Crypto.ED25519_SIGNATURE_LENGTH)) {
// Throw error
throw "Unsupported slate.";
}
// Set receiver signature to serialized slate's receiver signature
this.receiverSignature = ("rsig" in serializedSlate["proof"] === true && serializedSlate["proof"]["rsig"] !== null) ? Common.fromHexString(serializedSlate["proof"]["rsig"]) : Slate.NO_RECEIVER_SIGNATURE;
// Check if payment proof's sender address isn't supported
if("saddr" in serializedSlate["proof"] === false || Common.isHexString(serializedSlate["proof"]["saddr"]) === false || Common.hexStringLength(serializedSlate["proof"]["saddr"]) !== Crypto.ED25519_PUBLIC_KEY_LENGTH) {
// Throw error
throw "Unsupported slate.";
}
// Try
try {
// Set sender address to the Slatepack address created from the sender's public key
this.senderAddress = Slatepack.publicKeyToSlatepackAddress(Common.fromHexString(serializedSlate["proof"]["saddr"]));
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
}
// Check if purpose is send initial
if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL) {
// Check if serialized slate contains inputs and outputs isn't supported
if("coms" in serializedSlate === true && serializedSlate["coms"] !== null) {
// Throw error
throw "Unsupported slate.";
}
// Check if serialized slate's amount isn't supported
if("amt" in serializedSlate === false || (Common.isNumberString(serializedSlate["amt"]) === false && serializedSlate["amt"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["amt"])).isInteger() === false || (new BigNumber(serializedSlate["amt"])).isLessThan(Slate.MINIMUM_AMOUNT) === true) {
// Throw error
throw "Unsupported slate.";
}
// Set amount to serialized slate's amount
this.amount = new BigNumber(serializedSlate["amt"]);
// Check if serialized slate's fee isn't supported
if("fee" in serializedSlate === false || (Common.isNumberString(serializedSlate["fee"]) === false && serializedSlate["fee"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["fee"])).isInteger() === false || (new BigNumber(serializedSlate["fee"])).isLessThan(Slate.MINIMUM_FEE) === true) {
// Throw error
throw "Unsupported slate.";
}
// Set fee to serialized slate's fee
this.fee = new BigNumber(serializedSlate["fee"]);
// Check if serialized slate's offset isn't supported
if("off" in serializedSlate === true && (Common.isHexString(serializedSlate["off"]) === false || Common.arraysAreEqual(Common.fromHexString(serializedSlate["off"]), Slate.ZERO_OFFSET) === false)) {
// Throw error
throw "Unsupported slate.";
}
}
// Check if purpose is send response
if(purpose === Slate.COMPACT_SLATE_PURPOSE_SEND_RESPONSE) {
// Set height to initial send slate's height
this.height = initialSendSlate.getHeight();
// Set inputs to initial send slate's inputs
this.inputs = initialSendSlate.getInputs().map(function(input) {
// Return input
return new SlateInput(input.getFeatures(), input.getCommit());
});
// Set outputs to initial send slate's outputs
this.outputs = initialSendSlate.getOutputs().map(function(output) {
// Return output
return new SlateOutput(output.getFeatures(), output.getCommit(), output.getProof());
});
// Check if serialized slate contains inputs and outputs isn't supported
if("coms" in serializedSlate === false || serializedSlate["coms"] === null || Array.isArray(serializedSlate["coms"]) === false) {
// Throw error
throw "Unsupported slate.";
}
// Initialize inputs and outputs
var inputs = [];
var outputs = [];
// Try
try {
// Go through all serialized slate's input and outputs
for(var i = 0; i < serializedSlate["coms"]["length"]; ++i) {
// Get serialize slate's value
var value = serializedSlate["coms"][i];
// Check if value isn't supported
if(Object.isObject(value) === false) {
// Throw error
throw "Unsupported slate.";
}
// Check if value is an output
if("p" in value === true && value["p"] !== null) {
// Add value to list of outputs
outputs.push(new SlateOutput(value, this));
}
// Otherwise
else {
// Add value to list of inputs
inputs.push(new SlateInput(value, this));
}
}
}
// Catch errors
catch(error) {
// Throw error
throw "Unsupported slate.";
}
// Add inputs
this.addInputs(inputs, false, outputs["length"]);
// Add outputs
this.addOutputs(outputs, false);
// Check if serialized slate's amount isn't supported
if("amt" in serializedSlate === true && ((Common.isNumberString(serializedSlate["amt"]) === false && serializedSlate["amt"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["amt"])).isInteger() === false || (new BigNumber(serializedSlate["amt"])).isLessThan(Slate.MINIMUM_AMOUNT) === true)) {
// Throw error
throw "Unsupported slate.";
}
// Set amount to serialized slate's amount
this.amount = ("amt" in serializedSlate === true) ? new BigNumber(serializedSlate["amt"]) : initialSendSlate.getAmount();
// Check if serialized slate's fee isn't supported
if("fee" in serializedSlate === true && ((Common.isNumberString(serializedSlate["fee"]) === false && serializedSlate["fee"] instanceof BigNumber === false) || (new BigNumber(serializedSlate["fee"])).isInteger() === false || (new BigNumber(serializedSlate["fee"])).isLessThan(Slate.MINIMUM_FEE) === true)) {
// Throw error
throw "Unsupported slate.";
}
// Set fee to serialized slate's fee
this.fee = ("fee" in serializedSlate === true) ? new BigNumber(serializedSlate["fee"]) : initialSendSlate.getFee();
// Check if serialized slate's offset isn't supported
if("off" in serializedSlate === false || Common.isHexString(serializedSlate["off"]) === false || Common.hexStringLength(serializedSlate["off"]) !== Crypto.BLINDING_FACTOR_LENGTH || Secp256k1Zkp.isValidSecretKey(Common.fromHexString(serializedSlate["off"])) !== true) {
// Throw error
throw "Unsupported slate.";
}
// Set offset to serialized slate's offset
this.offset = Common.fromHexString(serializedSlate["off"]);
}
// Set kernels to serialized slate's kernels
this.kernels.push(new SlateKernel(this.getKernelFeatures(), this.getFee(), this.getLockHeight(), this.getRelativeHeight()));
}
// Break
break;
// Default
default:
// Throw error
throw "Unsupported slate.";
}
// Set participant IDs
var participantIds = [];
// Set sender participant exists
var senderParticipantExists = false;
// Go through all participants
for(var i = 0; i < this.getParticipants()["length"]; ++i) {
// Get participant
var participant = this.getParticipants()[i];
// Check if participant's ID already exists
if(participantIds.indexOf(participant.getId().toFixed()) !== Common.INDEX_NOT_FOUND) {
// Throw error
throw "Unsupported slate.";
}
// Append participant's ID to list
participantIds.push(participant.getId().toFixed());
// Check if participant is a sender
if(participant.isSender() === true) {
// Set sender participant exists
senderParticipantExists = true;
}
}
// Check if a sender participant doesn't exist
if(senderParticipantExists === false) {
// Throw error
throw "Unsupported slate.";
}
// Check if partial signatures failed to be verified
if(this.verifyPartialSignatures() === false) {
// Throw error
throw "Unsupported slate.";
}
// Check if receiver signature failed to be verified
if(this.verifyReceiverSignature(isMainnet) === false) {
// Throw error
throw "Unsupported slate.";
}
// Check if verifying weight failed
if(this.verifyWeight() === false) {
// Throw error
throw "Unsupported slate.";
}
// Check if verifying no recent duplicate kernels failed
if(this.verifyNoRecentDuplicateKernels(isMainnet) === false) {
// Throw error
throw "Unsupported slate.";
}
// Check if verifying sorted and unique failed
if(this.verifySortedAndUnique() === false) {
// Throw error
throw "Unsupported slate.";
}
// Check if verifying no cut through failed
if(this.verifyNoCutThrough() === false) {
// Throw error
throw "Unsupported slate.";
}
// Check if no kernels exist
if(this.getKernels()["length"] === 0) {
// Throw error
throw "Unsupported slate.";
}
// Check if kernel's features differ from the slate's
if(this.getKernelFeatures() !== this.getKernels()[0].getFeatures()) {
// Throw error
throw "Unsupported slate.";
}
// Check if kernel is complete
if(this.getKernels()[0].isComplete() === true) {
// Check if verifying kernel sums failed
if(this.verifyKernelSums() === false) {
// Throw error
throw "Unsupported slate.";
}
}
}
// Minimum compatible version
minimumCompatibleVersion(preferredVersions) {
// Check wallet type
switch(Consensus.getWalletType()) {
// MWC wallet
case Consensus.MWC_WALLET_TYPE:
// Check if version Slatepack is preferred and kernel features are plain
if(preferredVersions.indexOf(Slate.VERSION_SLATEPACK) !== Common.INDEX_NOT_FOUND && this.getKernelFeatures() === SlateKernel.PLAIN_FEATURES) {
// Return version Slatepack
return Slate.VERSION_SLATEPACK;
}
// Otherwise check if time to live cut off height or payment proof is used
else if(this.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT || this.hasPaymentProof() === true) {
// Return version three
return Slate.VERSION_THREE;
}
// Otherwise
else {
// Check if version two isn't preferred
if(preferredVersions["length"] !== 0 && preferredVersions.indexOf("V" + Slate.VERSION_TWO.toFixed()) === Common.INDEX_NOT_FOUND) {
// Return version three
return Slate.VERSION_THREE;
}
// Otherwise
else {
// Return version two
return Slate.VERSION_TWO;
}
}
// Break
break;
// GRIN wallet
case Consensus.GRIN_WALLET_TYPE:
// Return version four
return Slate.VERSION_FOUR;
// EPIC wallet
case Consensus.EPIC_WALLET_TYPE:
// Check if time to live cut off height or payment proof is used
if(this.getTimeToLiveCutOffHeight() !== Slate.NO_TIME_TO_LIVE_CUT_OFF_HEIGHT || this.hasPaymentProof() === true) {
// Return version three
return Slate.VERSION_THREE;
}
// Otherwise
else {
// Check if version two isn't preferred
if(preferredVersions["length"] !== 0 && preferredVersions.indexOf("V" + Slate.VERSION_TWO.toFixed()) === Common.INDEX_NOT_FOUND) {
// Return version three
return Slate.VERSION_THREE;
}
// Otherwise
else {
// Return version two
return Slate.VERSION_TWO;
}
}
// Break
break;
}
}
// Verify partial signatures
verifyPartialSignatures() {
// Try
try {
// Get message to sign
var messageToSign = SlateKernel.signatureMessage(this.getKernelFeatures(), this.getFee(), this.getLockHeight(), this.getRelativeHeight());
}
// Catch errors
catch(error) {
// Return false
return false;
}
// Get public nonce sum from combining participant's public nonces
var publicNonceSum = Secp256k1Zkp.combinePublicKeys(this.getParticipants().map(function(participant) {
// Return participant's public nonce
return participant.getPublicNonce();
}));
// Check if getting public nonce sum failed
if(publicNonceSum === Secp256k1Zkp.OPERATION_FAILED) {
// Return false
return false;
}
// Get public blind excess sum from combining participant's public blind excesses
var publicBlindExcessSum = Secp256k1Zkp.combinePublicKeys(this.getParticipants().map(function(participant) {
// Return participant's public blind excess
return participant.getPublicBlindExcess();
}));
// Check if getting public blind excess sum failed
if(publicBlindExcessSum === Secp256k1Zkp.OPERATION_FAILED) {
// Return false
return false;
}
// Go through all participants
for(var i = 0; i < this.getParticipants()["length"]; ++i) {
// Get participant
var participant = this.getParticipants()[i];
// Check if participant is complete
if(participant.isComplete() === true) {
// Check if partial signature doesn't verify the message
if(Secp256k1Zkp.verifySingleSignerSignature(participant.getPartialSignature(), messageToSign, publicNonceSum, participant.getPublicBlindExcess(), publicBlindExcessSum, true) !== true) {
// Return false
return false;
}
}
}
// Return true
return true;
}
// Verify receiver signature
verifyReceiverSignature(isMainnet) {
// Check if receiver signature exists
if(this.getReceiverSignature() !== Slate.NO_RECEIVER_SIGNATURE) {
// Try
try {
// Get excess
var excess = this.getExcess();
}
// Catch errors
catch(error) {
// Return false
return false;
}
// Try
try {
// Get message from amount, excess, and sender address
var message = Slate.getPaymentProofMessage(this.getAmount(), excess, this.getSenderAddress());
// Check wallet type
switch(Consensus.getWalletType()) {
// MWC wallet
case Consensus.MWC_WALLET_TYPE:
// Check receiver address's length
switch(this.getReceiverAddress()["length"]) {
// MQS address length
case Mqs.ADDRESS_LENGTH:
// Get message hash
var messageHash = new Uint8Array(sha256.arrayBuffer(message));
// Get receiver address's public key
var receiverAddressPublicKey = Mqs.mqsAddressToPublicKey(this.getReceiverAddress(), isMainnet);
// Check if receiver signature doesn't verify the message hash
if(Secp256k1Zkp.verifyMessageHashSignature(this.getReceiverSignature(), messageHash, receiverAddressPublicKey) !== true) {
// Return false
return false;
}
// Break
break;
// Tor address length
case Tor.ADDRESS_LENGTH:
// Get receiver address's public key
var receiverAddressPublicKey = Tor.torAddressToPublicKey(this.getReceiverAddress());
// Check if receiver signature doesn't verify the message
if(Ed25519.verify(message, this.getReceiverSignature(), receiverAddressPublicKey) !== true) {
// Return false
return false;
}
// Break
break;
}
// Break
break;
// GRIN wallet
case Consensus.GRIN_WALLET_TYPE:
// Check receiver address's length
switch(this.getReceiverAddress()["length"]) {
// Slatepack address length
case Slatepack.ADDRESS_LENGTH:
// Get receiver address's public key
var receiverAddressPublicKey = Slatepack.slatepackAddressToPublicKey(this.getReceiverAddress());
// Check if receiver signature doesn't verify the message
if(Ed25519.verify(message, this.getReceiverSignature(), receiverAddressPublicKey) !== true) {
// Return false
return false;
}
// Break
break;
}
// Break
break;
// EPIC wallet
case Consensus.EPIC_WALLET_TYPE:
// Check receiver address's length
switch(this.getReceiverAddress()["length"]) {
// Tor address length
case Tor.ADDRESS_LENGTH:
// Get receiver address's public key
var receiverAddressPublicKey = Tor.torAddressToPublicKey(this.getReceiverAddress());
// Check if receiver signature doesn't verify the message
if(Ed25519.verify(message, this.getReceiverSignature(), receiverAddressPublicKey) !== true) {
// Return false
return false;
}
// Break
break;
}
// Break
break;
}
}
// Catch errors
catch(error) {
// Return false
return false;
}
}
// Return tru
return true;
}
// Verify weight
verifyWeight(expectedNumberOfOutputs = 0) {
// Get coinbase weight
var coinbaseWeight = Consensus.BLOCK_OUTPUT_WEIGHT + Consensus.BLOCK_KERNEL_WEIGHT;
// Get maximum transaction weight while leaving room for a coinbase
var maximumTransactionWeight = Math.max(Consensus.MAXIMUM_BLOCK_WEIGHT - coinbaseWeight, 0);
// Check if weight is too heavy
if(Slate.getWeight(this.getInputs()["length"], this.getOutputs()["length"] + expectedNumberOfOutputs, this.getKernels()["length"]).isGreaterThan(maximumTransactionWeight) === true)
// Return false
return false;
// Return true
return true;
}
// Verify no recent duplicate kernels
verifyNoRecentDuplicateKernels(isMainnet) {
// Check if no recent duplicate kernels is enabled
if(Consensus.isNoRecentDuplicateKernelsEnabled(isMainnet) === true) {
// Initialize no recent duplicate kernels excesses
var noRecentDuplicateKernelsExcesses = [];
// Go through all kernels
for(var i = 0; i < this.getKernels()["length"]; ++i) {
// Get kernel
var kernel = this.getKernels()[i];
// Check if kernel is no recent duplicate
if(kernel.isNoRecentDuplicate() === true) {
// Check if kernel's excess already exists in the list
if(noRecentDuplicateKernelsExcesses.indexOf(Common.toHexString(kernel.getExcess())) !== Common.INDEX_NOT_FOUND) {
// Return false
return false;
}
// Append kernel's excess to list
noRecentDuplicateKernelsExcesses.push(Common.toHexString(kernel.getExcess()));
}
}
}
// Return true
return true;
}
// Verify sorted and unique
verifySortedAndUnique() {
// Check if inputs aren't sorted and unique
if(Slate.isSortedAndUnique(this.getInputs().map(function(input) {
// Return input's hash
return input.getHash();
})) === false) {
// Return false
return false;
}
// Check if outputs aren't sorted and unique
if(Slate.isSortedAndUnique(this.getOutputs().map(function(output) {
// Return output's hash
return output.getHash();
})) === false) {
// Return false
return false;
}
// Try
try {
// Check if kernels aren't sorted and unique
if(Slate.isSortedAndUnique(this.getKernels().map(function(kernel) {
// Return kernel's hash
return kernel.getHash();
})) === false) {
// Return false
return false;
}
}
// Catch errors
catch(error) {
// Return false
return false;
}
// Return true
return true;
}
// Verify no cut through
verifyNoCutThrough() {
// Initialize serialized hashes
var serializedHashes = [];
// Go through all inputs
for(var i = 0; i < this.getInputs()["length"]; ++i) {
// Get input's serialized hash
var serializedHash = this.getInputs()[i].getHash().serialize();
// Check if serialized hash already exists in the list
if(serializedHashes.indexOf(serializedHash) !== Common.INDEX_NOT_FOUND) {
// Return false
return false;
}
// Append serialized hash to list
serializedHashes.push(serializedHash);
}
// Go through all outputs
for(var i = 0; i < this.getOutputs()["length"]; ++i) {
// Get input's serialized hash
var serializedHash = this.getOutputs()[i].getHash().serialize();
// Check if serialized hash already exists in the list
if(serializedHashes.indexOf(serializedHash) !== Common.INDEX_NOT_FOUND) {
// Return false
return false;
}
// Append serialized hash to list
serializedHashes.push(serializedHash);
}
// Return true
return true;
}
// Verify fees
verifyFees(baseFee) {
// Get the required transaction fee
var transactionFee = Slate.getRequiredFee(this.getInputs()["length"], this.getOutputs()["length"], this.getKernels()["length"], baseFee);
// Check if transaction fee is greater than the overage
if(transactionFee.isGreaterThan(this.getOverage()) === true) {
// Return false
return false;
}
// Check if transaction fee is greater than the received amount
if(transactionFee.isGreaterThan(this.getAmount().plus(this.getFee())) === true) {
// Return false
return false;
}
// Check if transaction fee is less than the minimum fee
if(transactionFee.isLessThan(Slate.MINIMUM_FEE) === true) {
// Return false
return false;
}
return true;
}
// Verify kernel sums
verifyKernelSums() {
// Set kernel excesses to all kernel's excesses
var kernelExcesses = this.getKernels().map(function(kernel) {
// Return kernel's excess
return kernel.getExcess();
});
// Go through all kernel excesses
for(var i = 0; i < kernelExcesses["length"]; ++i) {
// Get kernel excess
var kernelExcess = kernelExcesses[i];
// Check if kernel excess is a zero commit
if(Common.arraysAreEqual(kernelExcess, Slate.ZERO_COMMIT) === true) {
// Remove kernel excess from list
kernelExcesses.splice(i--, 1);
}
}
// Check if getting kernels sum from kernel excesses failed
var kernelsSum = Secp256k1Zkp.pedersenCommitSum(kernelExcesses, []);
if(kernelsSum === Secp256k1Zkp.OPERATION_FAILED) {
// Return false
return false;
}
// Initialize kernel commits
var kernelCommits = [
// Kernels sum
kernelsSum
];
// Try
try {
// Get offset excess
var offsetExcess = this.getOffsetExcess();
}
// Catch errors
catch(error) {
// Return false
return false;
}
// Append offset excess to kernel commits
kernelCommits.push(offsetExcess);
// Check if getting kernels sum with offset from kernel commits failed
var kernelsSumWithOffset = Secp256k1Zkp.pedersenCommitSum(kernelCommits, []);
if(kernelsSumWithOffset === Secp256k1Zkp.OPERATION_FAILED) {
// Return false
return false;
}
// Check if getting commits sum failed
var commitsSum = this.getCommitsSum();
if(commitsSum === false) {
// Return false
return false;
}
// Check if commits sum isn't equal to the kernels sum with offset
if(Common.arraysAreEqual(commitsSum, kernelsSumWithOffset) === false) {
// Return false
return false;
}
// Return true
return true;
}
// Get kernel features
getKernelFeatures() {
// Check if lock height exists
if(this.getLockHeight().isEqualTo(Slate.NO_LOCK_HEIGHT) === false) {
// Return height locked kernel features
return SlateKernel.HEIGHT_LOCKED_FEATURES;
}
// Check if relative height exists
else if(this.getRelativeHeight() !== Slate.NO_RELATIVE_HEIGHT) {
// Return no recent duplicate kernel features
return SlateKernel.NO_RECENT_DUPLICATE_FEATURES;
}
// Otherwise
else {
// Return plain kernel features
return SlateKernel.PLAIN_FEATURES;
}
}
// Get overage
getOverage() {
// Initialize overage
var overage = new BigNumber(0);
// Go through all kernels
for(var i = 0; i < this.getKernels()["length"]; ++i) {
// Get kernel
var kernel = this.getKernels()[i];
// Check kernel's features
switch(kernel.getFeatures()) {
// Plain, height locked, or no recent duplicate features
case SlateKernel.PLAIN_FEATURES:
case SlateKernel.HEIGHT_LOCKED_FEATURES:
case SlateKernel.NO_RECENT_DUPLICATE_FEATURES:
// Add kernel's fee to overage
overage = overage.plus(kernel.getFee());
// Break
break;
}
}
// Return overage
return overage;
}
// Get commits sum
getCommitsSum() {
// Set input commits to all input's commits
var inputCommits = this.getInputs().map(function(input) {
// Return input's commit
return input.getCommit();
});
// Set output commits to all output's commits
var outputCommits = this.getOutputs().map(function(output) {
// Return output's commit
return output.getCommit();
});
// Get overage
var overage = this.getOverage();
// Check if overage isn't zero
if(overage.isZero() === false) {
// Try
try {
// Get over commit from the overage's absolute value
var overCommit = Crypto.commitAmount(overage.absoluteValue());
}
// Catch errors
catch(error) {
// Return false
return false;
}
// Check if overage is negative
if(overage.isNegative() === true) {
// Append over commit to input commits
inputCommits.push(overCommit);
}
// Otherwise
else {
// Append over commit to output commits
outputCommits.push(overCommit);
}
}
// Go through all input commits
for(var i = 0; i < inputCommits["length"]; ++i) {
// Get input commit
var inputCommit = inputCommits[i];
// Check if input commit is a zero commit
if(Common.arraysAreEqual(inputCommit, Slate.ZERO_COMMIT) === true) {
// Remove input commit from list
inputCommits.splice(i--, 1);
}
}
// Go through all output commits
for(var i = 0; i < outputCommits["length"]; ++i) {
// Get output commit
var outputCommit = outputCommits[i];
// Check if output commit is a zero commit
if(Common.arraysAreEqual(outputCommit, Slate.ZERO_COMMIT) === true) {
// Remove output commit from list
outputCommits.splice(i--, 1);
}
}
// Check if getting commits sum from output commits and input commits failed
var commitsSum = Secp256k1Zkp.pedersenCommitSum(outputCommits, inputCommits);
if(commitsSum === Secp256k1Zkp.OPERATION_FAILED) {
// Return false
return false;
}
// Return commits sum
return commitsSum;
}
// Update kernel
updateKernel() {
// Remove all kernels
this.kernels = [];
// Check kernel features
switch(this.getKernelFeatures()) {
// Plain features
case SlateKernel.PLAIN_FEATURES:
// Set kernel fee to fee
var kernelFee = this.getFee();
// Set kernel lock height to no lock height
var kernelLockHeight = Slate.NO_LOCK_HEIGHT;
// Set kernel relative height to no relative height
var kernelRelativeHeight = Slate.NO_RELATIVE_HEIGHT;
// Break
break;
// Height locked features
case SlateKernel.HEIGHT_LOCKED_FEATURES:
// Set kernel fee to fee
var kernelFee = this.getFee();
// Set kernel lock height to lock height
var kernelLockHeight = this.getLockHeight();
// Set kernel relative height to no relative height
var kernelRelativeHeight = Slate.NO_RELATIVE_HEIGHT;
// Break
break;
// No recent duplpicate features
case SlateKernel.NO_RECENT_DUPLICATE_FEATURES:
// Set kernel fee to fee
var kernelFee = this.getFee();
// Set kernel lock height to no lock height
var kernelLockHeight = Slate.NO_LOCK_HEIGHT;
// Set kernel relative height to relative height
var kernelRelativeHeight = this.getRelativeHeight();
// Break
break;
}
// Create kernel with kernel features, kernel fee, and kernel lock height
var kernel = new SlateKernel(this.getKernelFeatures(), kernelFee, kernelLockHeight, kernelRelativeHeight);
// Append kernel to list
this.kernels.push(kernel);
}
// Sort
sort() {
// Sort the inputs
this.inputs.sort(function(inputOne, inputTwo) {
// Return the result of comparing the input's hashes
return inputOne.getHash().compare(inputTwo.getHash());
});
// Sort the outputs
this.outputs.sort(function(outputOne, outputTwo) {
// Return the result of comparing the output's hashes
return outputOne.getHash().compare(outputTwo.getHash());
});
// Try
try {
// Sort the kernels
this.kernels.sort(function(kernelOne, kernelTwo) {
// Return the result of comparing the kernel's hashes
return kernelOne.getHash().compare(kernelTwo.getHash());
});
}
// Catch errors
catch(error) {
// Return false
return false;
}
// Return true
return true;
}
// Has payment proof
hasPaymentProof() {
// Return if receiver address and sender address exist
return this.getReceiverAddress() !== Slate.NO_RECEIVER_ADDRESS && this.getSenderAddress() !== Slate.NO_SENDER_ADDRESS;
}
// Create partial signature
createPartialSignature(secretKeyOrHardwareWallet, secretNonce, isNewParticipant, isMainnet, hardwareWalletLockedText = HardwareWallet.NO_TEXT, hardwareWalletLockedTextArguments = [], allowUnlock = false, preventMessages = false, cancelOccurred = Common.NO_CANCEL_OCCURRED) {
// Set self
var self = this;
// Return promise
return new Promise(function(resolve, reject) {
// Get public blind excess
var getPublicBlindExcess = 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 public blind excess from secret key failed
var publicBlindExcess = Secp256k1Zkp.publicKeyFromSecretKey(secretKey);
if(publicBlindExcess === Secp256k1Zkp.OPERATION_FAILED) {
// Reject error
reject("Getting public blind excess failed.");
}
// Otherwise
else {
// Resolve public blind excess
resolve(publicBlindExcess);
}
}
// Otherwise
else {
// Get hardware wallet
var hardwareWallet = secretKeyOrHardwareWallet;
// Return getting the transaction public key with the hardware wallet
return hardwareWallet.getTransactionPublicKey(hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(publicBlindExcess) {
// Resolve public blind excess
resolve(publicBlindExcess);
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
}
});
};
// Return getting public blind excess
return getPublicBlindExcess().then(function(publicBlindExcess) {
// Get public nonce
var getPublicNonce = function() {
// Return promise
return new Promise(function(resolve, reject) {
// Check if a secret key is provided
if(secretKeyOrHardwareWallet instanceof Uint8Array === true) {
// Check if getting a public nonce from secret nonce failed
var publicNonce = Secp256k1Zkp.publicKeyFromSecretKey(secretNonce);
if(publicNonce === Secp256k1Zkp.OPERATION_FAILED) {
// Reject error
reject("Getting a public nonce failed.");
}
// Otherwise
else {
// Resolve public nonce
resolve(publicNonce);
}
}
// Otherwise
else {
// Get hardware wallet
var hardwareWallet = secretKeyOrHardwareWallet;
// Return getting the transaction public nonce with the hardware wallet
return hardwareWallet.getTransactionPublicNonce(hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(publicNonce) {
// Resolve public nonce
resolve(publicNonce);
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
}
});
};
// Return getting public nonce
return getPublicNonce().then(function(publicNonce) {
// Try
try {
// Get message to sign
var messageToSign = SlateKernel.signatureMessage(self.getKernelFeatures(), self.getFee(), self.getLockHeight(), self.getRelativeHeight());
}
// Catch errors
catch(error) {
// Reject error
reject("Getting message to sign failed.");
// Return
return;
}
// Get participants public nonces
var publicNonces = self.getParticipants().map(function(participant) {
// Return participant's public nonce
return participant.getPublicNonce();
});
// Check if is new participant
if(isNewParticipant === true) {
// Append public nonce to list
publicNonces.push(publicNonce);
}
// Get public nonce sum from combining participant's public nonces
var publicNonceSum = Secp256k1Zkp.combinePublicKeys(publicNonces);
// Check if getting public nonce sum failed
if(publicNonceSum === Secp256k1Zkp.OPERATION_FAILED) {
// Reject error
reject("Getting public nonce sum failed.");
}
// Otherwise
else {
// Get participants public blind excesses
var publicBlindExcesses = self.getParticipants().map(function(participant) {
// Return participant's public blind excess
return participant.getPublicBlindExcess();
});
// Check if is new participant
if(isNewParticipant === true) {
// Append public blind excess to list
publicBlindExcesses.push(publicBlindExcess);
}
// Get public blind excess sum from combining participant's public blind excesses
var publicBlindExcessSum = Secp256k1Zkp.combinePublicKeys(publicBlindExcesses);
// Check if getting public blind excess sum failed
if(publicBlindExcessSum === Secp256k1Zkp.OPERATION_FAILED) {
// Reject error
reject("Getting public blind excess sum failed.");
}
// Otherwise
else {
// Get partial signature
var getPartialSignature = 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 creating partial signature from the message to sign, secret key, secret nonce, public blind excess sum, and public nonce sum failed
var partialSignature = Secp256k1Zkp.createSingleSignerSignature(messageToSign, secretKey, secretNonce, publicBlindExcessSum, publicNonceSum, publicNonceSum);
if(partialSignature === Secp256k1Zkp.OPERATION_FAILED) {
// Reject error
reject("Creating partial signature failed.");
}
// Otherwise
else {
// Resolve partial signature
resolve(partialSignature);
}
}
// Otherwise
else {
// Get hardware wallet
var hardwareWallet = secretKeyOrHardwareWallet;
// Try
try {
// Get excess
var excess = self.getExcess((isNewParticipant === true) ? publicBlindExcess : Slate.NO_PUBLIC_BLIND_EXCESS);
}
// Catch errors
catch(error) {
// Reject error
reject(error);
// Return
return;
}
// Check if not a new participant
if(isNewParticipant === false) {
// Return getting transaction information with the public nonce sum, public blind excess sum, kernel information, and payment proof information with the hardware wallet
return hardwareWallet.getTransactionInformation(publicNonceSum, publicBlindExcessSum, self.getKernelFeatures(), self.getLockHeight(), self.getRelativeHeight(), (self.getSenderAddress() !== Slate.NO_SENDER_ADDRESS) ? excess : HardwareWallet.NO_KERNEL_COMMIT, self.getSenderAddress(), self.getReceiverSignature(), hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(transactionInformation) {
// Resolve partial signature
resolve(transactionInformation[HardwareWallet.TRANSACTION_INFORMATION_SIGNATURE_INDEX]);
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
}
// Otherwise
else {
// Return getting transaction information with the public nonce sum, public blind excess sum, and kernel information with the hardware wallet
return hardwareWallet.getTransactionInformation(publicNonceSum, publicBlindExcessSum, self.getKernelFeatures(), self.getLockHeight(), self.getRelativeHeight(), (self.getSenderAddress() !== Slate.NO_SENDER_ADDRESS) ? excess : HardwareWallet.NO_KERNEL_COMMIT, self.getReceiverAddress(), Slate.NO_RECEIVER_SIGNATURE, hardwareWalletLockedText, hardwareWalletLockedTextArguments, allowUnlock, preventMessages, cancelOccurred).then(function(transactionInformation) {
// Check if transaction information contains a payment proof
if(transactionInformation[HardwareWallet.TRANSACTION_INFORMATION_PAYMENT_PROOF_INDEX] !== HardwareWallet.NO_PAYMENT_PROOF) {
// Set receiver signature
self.setReceiverSignature(transactionInformation[HardwareWallet.TRANSACTION_INFORMATION_PAYMENT_PROOF_INDEX]);
// Resolve partial signature
resolve(transactionInformation[HardwareWallet.TRANSACTION_INFORMATION_SIGNATURE_INDEX]);
}
// Otherwise
else {
// Resolve partial signature
resolve(transactionInformation[HardwareWallet.TRANSACTION_INFORMATION_SIGNATURE_INDEX]);
}
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
}
}
});
};
// Return getting the partial signature
return getPartialSignature().then(function(partialSignature) {
// Resolve partial signature
resolve(partialSignature);
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
}
}
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
// Catch errors
}).catch(function(error) {
// Reject error
reject(error);
});
});
}
// Verify after finalize
verifyAfterFinalize(baseFee, isMainnet) {
// Check if sorting failed
if(this.sort() === false) {
// Return false
return false;
}
// Otherwise check if verifying weight failed
else if(this.verifyWeight() === false) {
// Return false
return false;
}
// Otherwise check if verifying sorted and unique failed
else if(this.verifySortedAndUnique() === false) {
// Return false
return false;
}
// Otherwise check if verifying no cut through failed
else if(this.verifyNoCutThrough() === false) {
// Return false
return false;
}
// Otherwise check if verifying fees failed
else if(this.verifyFees(baseFee) === false) {
// Return false
return false;
}
// Otherwise check if kernel isn't complete
else if(this.getKernels()[0].isComplete() === false) {
// Return false
return false;
}
// Otherwise check if verifying kernel sums failed
else if(this.verifyKernelSums() === false) {
// Return false
return false;
}
// Otherwise check if has payment proof and the receiver signature doesn't exist
else if(this.hasPaymentProof() === true && this.getReceiverSignature() === Slate.NO_RECEIVER_SIGNATURE) {
// Return false
return false;
}
// Otherwise check if receiver signature failed to be verified
else if(this.verifyReceiverSignature(isMainnet) === false) {
// Return false
return false;
}
// Otherwise check if verifying no recent duplicate kernels failed
else if(this.verifyNoRecentDuplicateKernels(isMainnet) === false) {
// Return false
return false;
}
// Otherwise
else {
// Return true
return true;
}
}
// Get weight
static getWeight(numberOfInputs, numberOfOutputs, numberOfKernels) {
// Get inputs weight
var inputsWeight = (new BigNumber(numberOfInputs)).multipliedBy(Consensus.BLOCK_INPUT_WEIGHT);
// Get outputs weight
var outputsWeight = (new BigNumber(numberOfOutputs)).multipliedBy(Consensus.BLOCK_OUTPUT_WEIGHT);
// Get kernels weight
var kernelsWeight = (new BigNumber(Math.max(numberOfKernels, 1))).multipliedBy(Consensus.BLOCK_KERNEL_WEIGHT);
// Return sum of weights
return inputsWeight.plus(outputsWeight).plus(kernelsWeight);
}
// Detect version
static detectVersion(serializedSlate, isMainnet) {
// Check wallet type
switch(Consensus.getWalletType()) {
// MWC wallet
case Consensus.MWC_WALLET_TYPE:
// Check if a binary serialized slate is provided
if(serializedSlate instanceof Uint8Array === true) {
// Return version Slatepack
return Slate.VERSION_SLATEPACK;
}
// Otherwise check if serialized slate is an object
else if(Object.isObject(serializedSlate) === true) {
// Check if serialized slate contains an invalid coin type
if("coin_type" in serializedSlate === true && serializedSlate["coin_type"] !== Slate.COIN_TYPE) {
// Return unknown version
return Slate.UNKNOWN_VERSION;
}
// Otherwise check if serialized slate contains an invalid network type
else if("network_type" in serializedSlate === true && serializedSlate["network_type"] !== Slate.getNetworkType(isMainnet)) {
// Return unknown version
return Slate.UNKNOWN_VERSION;
}
// Otherwise check if serialized slate contains a version info structure
else if("version_info" in serializedSlate === true && Object.isObject(serializedSlate["version_info"]) === true && "version" in serializedSlate["version_info"] === true && (Common.isNumberString(serializedSlate["version_info"]["version"]) === true || serializedSlate["version_info"]["version"] instanceof BigNumber === true) && (new BigNumber(serializedSlate["version_info"]["version"])).isInteger() === true && (new BigNumber(serializedSlate["version_info"]["version"])).isPositive() === true) {
// Return version info structure's version
return new BigNumber(serializedSlate["version_info"]["version"]);
}
// Otherwise check if serialized slate contains a version
else if("version" in serializedSlate === true && (Common.isNumberString(serializedSlate["version"]) === true || serializedSlate["version"] instanceof BigNumber === true) && (new BigNumber(serializedSlate["version"])).isInteger() === true && (new BigNumber(serializedSlate["version"])).isPositive() === true) {
// Return version one
return Slate.VERSION_ONE;
}
// Otherwise
else {
// Return unknown version
return Slate.UNKNOWN_VERSION;
}
}
// Otherwise
else {
// Return unknown version
return Slate.UNKNOWN_VERSION;
}
// Break
break;
// GRIN wallet
case Consensus.GRIN_WALLET_TYPE:
// Check if serialized slate is an object
if(Object.isObject(serializedSlate) === true) {
// Check if serialized slate contains a version
if("ver" in serializedSlate === true && typeof serializedSlate["ver"] === "string" && Slate.VERSION_PATTERN.test(serializedSlate["ver"]) === true) {
// Return serialized slate's version
return new BigNumber(serializedSlate["ver"].split(Slate.VERSION_SEPARATOR)[0]);
}
// Otherwise
else {
// Return unknown version
return Slate.UNKNOWN_VERSION;
}
}
// Otherwise check if a binary serialized slate is provided
else if(serializedSlate instanceof Uint8Array === true) {
// Check if serialized slate contains a version
if(serializedSlate["length"] >= Common.BYTES_IN_A_UINT16) {
// Return serialized slate's version
return new BigNumber(Common.HEX_PREFIX + Common.toHexString(serializedSlate.subarray(0, Common.BYTES_IN_A_UINT16)));
}
// Otherwise
else {
// Return unknown version
return Slate.UNKNOWN_VERSION;
}
}
// Otherwise
else {
// Return unknown version
return Slate.UNKNOWN_VERSION;
}
// Break
break;
// EPIC wallet
case Consensus.EPIC_WALLET_TYPE:
// Check if serialized slate is an object
if(Object.isObject(serializedSlate) === true) {
// Check if serialized slate contains a version info structure
if("version_info" in serializedSlate === true && Object.isObject(serializedSlate["version_info"]) === true && "version" in serializedSlate["version_info"] === true && (Common.isNumberString(serializedSlate["version_info"]["version"]) === true || serializedSlate["version_info"]["version"] instanceof BigNumber === true) && (new BigNumber(serializedSlate["version_info"]["version"])).isInteger() === true && (new BigNumber(serializedSlate["version_info"]["version"])).isPositive() === true) {
// Return version info structure's version
return new BigNumber(serializedSlate["version_info"]["version"]);
}
// Otherwise check if serialized slate contains a version
else if("version" in serializedSlate === true && (Common.isNumberString(serializedSlate["version"]) === true || serializedSlate["version"] instanceof BigNumber === true) && (new BigNumber(serializedSlate["version"])).isInteger() === true && (new BigNumber(serializedSlate["version"])).isPositive() === true) {
// Return version one
return Slate.VERSION_ONE;
}
// Otherwise
else {
// Return unknown version
return Slate.UNKNOWN_VERSION;
}
}
// Otherwise
else {
// Return unknown version
return Slate.UNKNOWN_VERSION;
}
// Break
break;
}
}
// Is sorted and unique
static isSortedAndUnique(hashes) {
// Go through all hashes
for(var i = 1; i < hashes["length"]; ++i) {
// Get last hash
var lastHash = hashes[i - 1];
// Get current hash
var currentHash = hashes[i];
// Get result of comparing the hashes
var compareResult = currentHash.compare(lastHash);
// Check if current hash is less than or equal to the last hash
if(compareResult === Common.SORT_LESS_THAN || compareResult === Common.SORT_EQUAL)
// Return false
return false;
}
// Return true
return true;
}
// Get network type
static getNetworkType(isMainnet) {
// Check if is mainnet
if(isMainnet === true) {
// Return network type
return "mainnet";
}
// Otherwise
else {
// Return network type
return "floonet";
}
}
// Uncompact proof address
static uncompactProofAddress(bitReader, isMainnet) {
// Check if proof address is MQS proof address
if(bitReader.getBits(Slate.COMPACT_BOOLEAN_LENGTH) === Slate.COMPACT_BOOLEAN_TRUE) {
// Get proof address
var proofAddress = Mqs.publicKeyToMqsAddress(Slate.uncompactPublicKey(bitReader), isMainnet);
}
// Otherwise
else {
// Get proof address
var proofAddress = Tor.publicKeyToTorAddress(bitReader.getBytes(Crypto.ED25519_PUBLIC_KEY_LENGTH));
}
// Return proof address
return proofAddress;
}
// Compact proof address
static compactProofAddress(proofAddress, isMainnet, bitWriter) {
// Check proof address's length
switch(proofAddress["length"]) {
// MQS address length
case Mqs.ADDRESS_LENGTH:
// Write that proof address is an MQS proof address
bitWriter.setBits(Slate.COMPACT_BOOLEAN_TRUE, Slate.COMPACT_BOOLEAN_LENGTH);
// Write proof address
Slate.compactPublicKey(Mqs.mqsAddressToPublicKey(proofAddress, isMainnet), bitWriter);
// Break
break;
// Tor address length
case Tor.ADDRESS_LENGTH:
// Write that proof address is a Tor proof address
bitWriter.setBits(Slate.COMPACT_BOOLEAN_FALSE, Slate.COMPACT_BOOLEAN_LENGTH);
// Write proof address
bitWriter.setBytes(Tor.torAddressToPublicKey(proofAddress));
// Break
break;
}
}
// Purpose to text
static purposeToText(purpose) {
// Check purpose
switch(purpose) {
// Send initial purpose
case Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL:
// Return purpose as text
return "S1";
// Send response purpose
case Slate.COMPACT_SLATE_PURPOSE_SEND_RESPONSE:
// Return purpose as text
return "S2";
}
}
// Send request
static sendRequest(request, transfer) {
// Get current request index
var currentRequestIndex = Slate.requestIndex++;
// Check if current request index is at the max safe integer
if(currentRequestIndex === Number.MAX_SAFE_INTEGER)
// Reset request index
Slate.requestIndex = 0;
// Add current request index to request
request.unshift(currentRequestIndex);
// Return promise
return new Promise(function(resolve, reject) {
// Response current request index event
$(document).one(Slate.RESPONSE_EVENT + currentRequestIndex.toFixed(), function(event, response) {
// Resolve response
resolve(response);
});
// Send worker the request
Slate.worker.postMessage(request, transfer);
});
}
// Version one
static get VERSION_ONE() {
// Return version one
return new BigNumber(1);
}
// Version two
static get VERSION_TWO() {
// Return version two
return new BigNumber(2);
}
// Version three
static get VERSION_THREE() {
// Return version three
return new BigNumber(3);
}
// Version four
static get VERSION_FOUR() {
// Return version four
return new BigNumber(4);
}
// Version Slatepack
static get VERSION_SLATEPACK() {
// Return version Slatepack
return "SP";
}
// Unknown version
static get UNKNOWN_VERSION() {
// Return unknown version
return null;
}
// No lock height
static get NO_LOCK_HEIGHT() {
// Return no lock height
return new BigNumber(0);
}
// No relative height
static get NO_RELATIVE_HEIGHT() {
// Return no relative height
return null;
}
// Minimum number of participants
static get MINIMUM_NUMBER_OF_PARTICIPANTS() {
// Return minimum number of participants
return 2;
}
// Zero offset
static get ZERO_OFFSET() {
// No zero offset
return Crypto.ZERO_BLINDING_FACTOR;
}
// No participant
static get NO_PARTICIPANT() {
// Return no participant
return null;
}
// No receiver address
static get NO_RECEIVER_ADDRESS() {
// Return no receiver address
return null;
}
// No receiver signature
static get NO_RECEIVER_SIGNATURE() {
// Return no receiver signature
return null;
}
// No sender address
static get NO_SENDER_ADDRESS() {
// Return no sender address
return null;
}
// Zero commit
static get ZERO_COMMIT() {
// Return zero commit
return (new Uint8Array(Crypto.COMMIT_LENGTH)).fill(0);
}
// Body weight output factor
static get BODY_WEIGHT_OUTPUT_FACTOR() {
// Return body weight output factor
return 4;
}
// Minimum body weight
static get MINIMUM_BODY_WEIGHT() {
// Return minimum body weight
return 1;
}
// Default number of participants
static get DEFAULT_NUMBER_OF_PARTICIPANTS() {
// Return default number of participants
return new BigNumber(2);
}
// Coin type
static get COIN_TYPE() {
// Check wallet type
switch(Consensus.getWalletType()) {
// MWC wallet
case Consensus.MWC_WALLET_TYPE:
// Return coin type
return "mwc";
}
}
// Compact boolean length
static get COMPACT_BOOLEAN_LENGTH() {
// Return compact boolean length
return 1;
}
// Compact boolean false
static get COMPACT_BOOLEAN_FALSE() {
// Return compact boolean false
return 0;
}
// Compact boolean true
static get COMPACT_BOOLEAN_TRUE() {
// Return compact boolean true
return Slate.COMPACT_BOOLEAN_FALSE + 1;
}
// Compact slate purpose length
static get COMPACT_SLATE_PURPOSE_LENGTH() {
// Return compact slate purpose length
return 3;
}
// Compact slate purpose send initial
static get COMPACT_SLATE_PURPOSE_SEND_INITIAL() {
// Return compact slate purpose send initial
return 0;
}
// Compact slate purpose send response
static get COMPACT_SLATE_PURPOSE_SEND_RESPONSE() {
// Return compact slate purpose send response
return Slate.COMPACT_SLATE_PURPOSE_SEND_INITIAL + 1;
}
// Compact proof signature length length
static get COMPACT_PROOF_SIGNATURE_LENGTH_LENGTH() {
// Return compact proof signature length length
return 4;
}
// Compact number of hundreds length
static get COMPACT_NUMBER_OF_HUNDREDS_LENGTH() {
// Return compact number of hundreds length
return 3;
}
// Compact number of digits length
static get COMPACT_NUMBER_OF_DIGITS_LENGTH() {
// Return compact number of digits length
return 6;
}
// Compact hundreds scaling factor
static get COMPACT_HUNDREDS_SCALING_FACTOR() {
// Return compact hundreds scaling factor
return 100;
}
// Compact public key length length
static get COMPACT_PUBLIC_KEY_LENGTH_LENGTH() {
// Return compact public key length length
return 7;
}
// No public blind excess
static get NO_PUBLIC_BLIND_EXCESS() {
// Return no public blind excess
return null;
}
// Version pattern
static get VERSION_PATTERN() {
// Return version pattern
return /^(?:0|[1-9]\d*):(?:0|[1-9]\d*)$/u;
}
// Version separator
static get VERSION_SEPARATOR() {
// Return version separator
return ":";
}
// Minimum amount
static get MINIMUM_AMOUNT() {
// Return minimum amount
return 1;
}
// Message status offset
static get MESSAGE_STATUS_OFFSET() {
// Return message status offset
return Slate.MESSAGE_TYPE_OFFSET + 1;
}
// Message response offset
static get MESSAGE_RESPONSE_OFFSET() {
// Return message response offset
return Slate.MESSAGE_TYPE_OFFSET + 1;
}
// Response event
static get RESPONSE_EVENT() {
// Return response event
return "SlateResponseEvent";
}
// Worker file location
static get WORKER_FILE_LOCATION() {
// Return worker file location
return "." + getResource("./scripts/slate_worker.js");
}
}
// Main function
// Set global object's slate
globalThis["Slate"] = Slate;