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