// Use strict "use strict"; // Classes // Slate kernel class class SlateKernel { // Public // Constructor constructor(serializedSlateKernelOrFeatures, slateOrFee, lockHeightOrIsMainnet, relativeHeight = Slate.NO_RELATIVE_HEIGHT, excess = SlateKernel.ZERO_EXCESS, excessSignature = SlateKernel.ZERO_EXCESS_SIGNATURE) { // Reset this.reset(); // Check if a binary serialized slate kernel is provided if(serializedSlateKernelOrFeatures instanceof BitReader === true) { // Get serialized slate kernel var serializedSlateKernel = serializedSlateKernelOrFeatures; // Get slate var slate = slateOrFee; // Get is mainnet var isMainnet = lockHeightOrIsMainnet; // Unserialize the serialized slate kernel this.unserialize(serializedSlateKernel, slate, isMainnet); } // Otherwise check if a serialized slate kernel is provided else if(Object.isObject(serializedSlateKernelOrFeatures) === true) { // Get serialized slate kernel var serializedSlateKernel = serializedSlateKernelOrFeatures; // Get slate var slate = slateOrFee; // Get is mainnet var isMainnet = lockHeightOrIsMainnet; // Unserialize the serialized slate kernel this.unserialize(serializedSlateKernel, slate, isMainnet); } // Otherwise check if arguments are provided else if(typeof serializedSlateKernelOrFeatures === "number") { // Get features var features = serializedSlateKernelOrFeatures; // Get fee var fee = slateOrFee; // Get lock height var lockHeight = lockHeightOrIsMainnet; // Set features to provided features this.features = features; // Set fee to provided fee this.fee = fee; // Set lock height to provided lock height this.lockHeight = lockHeight; // Set relative height to provided relative height this.relativeHeight = relativeHeight; // Set excess to provided excess this.excess = excess; // Set excess signature to provided excess signature this.excessSignature = excessSignature; // Check if complete if(this.isComplete() === true) { // Check if excess signature failed to be verified if(this.verifyExcessSignature() === false) { // Throw error throw "Unsupported kernel."; } } } // Otherwise else { // Throw error throw "Unsupported kernel."; } } // Serialize serialize(slateOrVersion, bitWriter) { // Get slate version var slateVersion = (slateOrVersion instanceof Slate === true) ? slateOrVersion.getVersion() : slateOrVersion; // Check slate's version switch((slateVersion instanceof BigNumber === true) ? slateVersion.toFixed() : slateVersion) { // Version two and three case Slate.VERSION_TWO.toFixed(): case Slate.VERSION_THREE.toFixed(): // Create serialized slate kernel var serializedSlateKernel = { // Features "features": SlateKernel.featuresToText(this.getFeatures()), // Excess signature "excess_sig": Common.toHexString(this.getExcessSignature()), // Excess "excess": Common.toHexString(this.getExcess()) }; // Check features switch(this.getFeatures()) { // Coinbase features case SlateKernel.COINBASE_FEATURES: // Set serialized slate kernel's fee to zero serializedSlateKernel["fee"] = (new BigNumber(0)).toFixed(); // Set serialized slate kernel's lock height to no lock height serializedSlateKernel["lock_height"] = Slate.NO_LOCK_HEIGHT.toFixed(); // Break break; // Plain features case SlateKernel.PLAIN_FEATURES: // Set serialized slate kernel's fee to fee serializedSlateKernel["fee"] = this.getFee().toFixed(); // Set serialized slate kernel's lock height to no lock height serializedSlateKernel["lock_height"] = Slate.NO_LOCK_HEIGHT.toFixed(); // Break break; // Height locked features case SlateKernel.HEIGHT_LOCKED_FEATURES: // Set serialized slate kernel's fee to fee serializedSlateKernel["fee"] = this.getFee().toFixed(); // Set serialized slate kernel's lock height to lock height serializedSlateKernel["lock_height"] = this.getLockHeight().toFixed(); // Break break; // No recent duplicate features case SlateKernel.NO_RECENT_DUPLICATE_FEATURES: // Set serialized slate kernel's fee to fee serializedSlateKernel["fee"] = this.getFee().toFixed(); // Set serialized slate kernel's relative height to relative height serializedSlateKernel["relative_height"] = this.getRelativeHeight().toFixed(); // Break break; } // Return serialized slate kernel return serializedSlateKernel; // Version Slatepack case Slate.VERSION_SLATEPACK: // Try try { // Check features switch(this.getFeatures()) { // Plain features case SlateKernel.PLAIN_FEATURES: // Write fee Slate.compactUint64(this.getFee(), true, bitWriter); // Break break; // Default default: // Throw error throw "Unsupported features."; } // Write excess bitWriter.setBytes(this.getExcess()); // Write excess signature bitWriter.setBytes(this.getExcessSignature()); } // Catch errors catch(error) { // Throw error throw "Unsupported kernel."; } // Break break; // Default default: // Throw error throw "Unsupported slate version."; } } // Get transaction getTransaction() { // Create transaction var transaction = { // Excess signature "excess_sig": Common.toHexString(this.getExcessSignature()), // Excess "excess": Common.toHexString(this.getExcess()) }; // Check features switch(this.getFeatures()) { // Coinbase features case SlateKernel.COINBASE_FEATURES: // Set transaction's features transaction["features"] = { // Feature [SlateKernel.featuresToText(this.getFeatures())]: {} }; // Break break; // Plain features case SlateKernel.PLAIN_FEATURES: // Set transaction's features transaction["features"] = { // Feature [SlateKernel.featuresToText(this.getFeatures())]: { // Fee "fee": this.getFee() } }; // Break break; // Height locked features case SlateKernel.HEIGHT_LOCKED_FEATURES: // Set transaction's features transaction["features"] = { // Feature [SlateKernel.featuresToText(this.getFeatures())]: { // Fee "fee": this.getFee(), // Lock height "lock_height": this.getLockHeight() } }; // Break break; // No recent duplicate features case SlateKernel.NO_RECENT_DUPLICATE_FEATURES: // Set transaction's features transaction["features"] = { // Feature [SlateKernel.featuresToText(this.getFeatures())]: { // Fee "fee": this.getFee(), // Relative height "relative_height": this.getRelativeHeight() } }; // Break break; } // Return transaction return transaction; } // Get features getFeatures() { // Return features return this.features; } // Get fee getFee() { // Return fee return this.fee; } // Get lock height getLockHeight() { // Return lock height return this.lockHeight; } // Relative height getRelativeHeight() { // Return relative height return this.relativeHeight; } // Get excess signature getExcessSignature() { // Return excess signature return this.excessSignature; } // Set excess signature setExcessSignature(excessSignature) { // Set excess signature this.excessSignature = excessSignature; // Check if complete if(this.isComplete() === true) { // Check if excess signature failed to be verified if(this.verifyExcessSignature() === false) { // Return false return false; } } // Return true return true; } // Get excess getExcess() { // Return excess return this.excess; } // Set excess setExcess(excess) { // Set excess this.excess = excess; } // Is plain isPlain() { // Return if features is plain return this.getFeatures() === SlateKernel.PLAIN_FEATURES; } // Is coinbase isCoinbase() { // Return if features is coinbase return this.getFeatures() === SlateKernel.COINBASE_FEATURES; } // Is height locked isHeightLocked() { // Return if features is height locked return this.getFeatures() === SlateKernel.HEIGHT_LOCKED_FEATURES; } // Is no recent duplicate isNoRecentDuplicate() { // Return if features is no recent duplicate return this.getFeatures() === SlateKernel.NO_RECENT_DUPLICATE_FEATURES; } // Get hash getHash() { // Check features switch(this.getFeatures()) { // Coinbase features case SlateKernel.COINBASE_FEATURES: // Return hash return new Hash([ // Features new Uint8Array([this.getFeatures()]), // Zero fee (new BigNumber(0)).toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64), // No lock height Slate.NO_LOCK_HEIGHT.toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64), // Excess this.getExcess(), // Excess signature this.getExcessSignature() ]); // Plain features case SlateKernel.PLAIN_FEATURES: // Return hash return new Hash([ // Features new Uint8Array([this.getFeatures()]), // Fee this.getFee().toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64), // No lock height Slate.NO_LOCK_HEIGHT.toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64), // Excess this.getExcess(), // Excess signature this.getExcessSignature() ]); // Height locked features case SlateKernel.HEIGHT_LOCKED_FEATURES: // Return hash return new Hash([ // Features new Uint8Array([this.getFeatures()]), // Fee this.getFee().toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64), // Lock height this.getLockHeight().toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64), // Excess this.getExcess(), // Excess signature this.getExcessSignature() ]); // No recent duplicate features case SlateKernel.NO_RECENT_DUPLICATE_FEATURES: // Return hash return new Hash([ // Features new Uint8Array([this.getFeatures()]), // Fee this.getFee().toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64), // Relative height this.getRelativeHeight().toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64), // Excess this.getExcess(), // Excess signature this.getExcessSignature() ]); } } // Is equal to isEqualTo(slateKernel) { // Check if features aren't equal if(this.getFeatures() !== slateKernel.getFeatures()) // Return false return false; // Check if feed aren't equal if(this.getFee().isEqualTo(slateKernel.getFee()) === false) // Return false return false; // Check if lock heights aren't equal if(this.getLockHeight().isEqualTo(slateKernel.getLockHeight()) === false) // Return false return false; // Check if relative heights aren't equal if((this.getRelativeHeight() === Slate.NO_RELATIVE_HEIGHT && slateKernel.getRelativeHeight() !== Slate.NO_RELATIVE_HEIGHT) || (this.getRelativeHeight() !== Slate.NO_RELATIVE_HEIGHT && slateKernel.getRelativeHeight() === Slate.NO_RELATIVE_HEIGHT) || (this.getRelativeHeight() !== Slate.NO_RELATIVE_HEIGHT && this.getRelativeHeight().isEqualTo(slateKernel.getRelativeHeight()) === false)) // Return false return false; // Check if excess signatures aren't equal if(Common.arraysAreEqual(this.getExcessSignature(), slateKernel.getExcessSignature()) === false) // Return false return false; // Check if excesses aren't equal if(Common.arraysAreEqual(this.getExcess(), slateKernel.getExcess()) === false) // Return false return false; // Return true return true; } // Is complete isComplete() { // Return if excess signature exists exists return Common.arraysAreEqual(this.getExcessSignature(), SlateKernel.ZERO_EXCESS_SIGNATURE) === false; } // Signature message static signatureMessage(features, fee = new BigNumber(0), lockHeight = Slate.NO_LOCK_HEIGHT, relativeHeight = Slate.NO_RELATIVE_HEIGHT) { // Check features switch(features) { // Coinbase features case SlateKernel.COINBASE_FEATURES: // Set data to features var data = new Uint8Array([features]); // Break break; // Plain features case SlateKernel.PLAIN_FEATURES: // Set data to features followed by the fee var data = Common.mergeArrays([ // Features new Uint8Array([features]), // Fee fee.toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64) ]); // Break break; // Height locked features case SlateKernel.HEIGHT_LOCKED_FEATURES: // Set data to features followed by the fee followed by the lock height var data = Common.mergeArrays([ // Features new Uint8Array([features]), // Fee fee.toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64), // Lock height lockHeight.toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64) ]); // Break break; // No recent duplicate features case SlateKernel.NO_RECENT_DUPLICATE_FEATURES: // Set data to features followed by the fee followed by the relative height var data = Common.mergeArrays([ // Features new Uint8Array([features]), // Fee fee.toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT64), // Relative height relativeHeight.toBytes(BigNumber.BIG_ENDIAN, Common.BYTES_IN_A_UINT16) ]); // Break break; } // Return creating a signature message from the data return Crypto.signatureMessage(data); } // Features to text static featuresToText(features) { // Check features switch(features) { // Plain features case SlateKernel.PLAIN_FEATURES: // Return plain text return "Plain"; // Coinbase features case SlateKernel.COINBASE_FEATURES: // Return coinbase text return "Coinbase"; // Height locked features case SlateKernel.HEIGHT_LOCKED_FEATURES: // Return height locked text return "HeightLocked"; // No recent duplicate features case SlateKernel.NO_RECENT_DUPLICATE_FEATURES: // Return no recent duplicate text return "NoRecentDuplicate"; // Default default: // Return unsupported features return SlateKernel.UNSUPPORTED_FEATURES; } } // Plain features static get PLAIN_FEATURES() { // Return plain features return 0; } // Coinbase features static get COINBASE_FEATURES() { // Return coinbase features return SlateKernel.PLAIN_FEATURES + 1; } // Height locked features static get HEIGHT_LOCKED_FEATURES() { // Return height locked features return SlateKernel.COINBASE_FEATURES + 1; } // No recent duplicate features static get NO_RECENT_DUPLICATE_FEATURES() { // Return height locked features return SlateKernel.HEIGHT_LOCKED_FEATURES + 1; } // Zero excess static get ZERO_EXCESS() { // No zero excess return (new Uint8Array(Crypto.COMMIT_LENGTH)).fill(0); } // Zero excess signature static get ZERO_EXCESS_SIGNATURE() { // No zero excess signature return (new Uint8Array(Crypto.SINGLE_SIGNER_SIGNATURE_LENGTH)).fill(0); } // Transaction features length static get TRANSACTION_FEATURES_LENGTH() { // Return transaction features length return 1; } // Private // Reset reset() { // Set features to plain features this.features = SlateKernel.PLAIN_FEATURES; // Set fee to minimum fee this.fee = new BigNumber(Slate.MINIMUM_FEE); // 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 excess signature to zero signature this.excessSignature = SlateKernel.ZERO_EXCESS_SIGNATURE; // Set excess to zero excess this.excess = SlateKernel.ZERO_EXCESS; } // Unserialize unserialize(serializedSlateKernel, slate, isMainnet) { // Check slate's version switch((slate.getVersion() instanceof BigNumber === true) ? slate.getVersion().toFixed() : slate.getVersion()) { // Version two and three case Slate.VERSION_TWO.toFixed(): case Slate.VERSION_THREE.toFixed(): // Check if serialized slate kernel's features isn't supported if("features" in serializedSlateKernel === false || SlateKernel.textToFeatures(serializedSlateKernel["features"]) === SlateKernel.UNSUPPORTED_FEATURES || SlateKernel.textToFeatures(serializedSlateKernel["features"]) === SlateKernel.COINBASE_FEATURES) { // Throw error throw "Unsupported kernel."; } // Set features to serialized slate kernel's features this.features = SlateKernel.textToFeatures(serializedSlateKernel["features"]); // Check features switch(this.getFeatures()) { // Plain features case SlateKernel.PLAIN_FEATURES: // Check if serialized slate kernel's fee isn't supported if("fee" in serializedSlateKernel === false || (Common.isNumberString(serializedSlateKernel["fee"]) === false && serializedSlateKernel["fee"] instanceof BigNumber === false) || (new BigNumber(serializedSlateKernel["fee"])).isInteger() === false || (new BigNumber(serializedSlateKernel["fee"])).isLessThan(Slate.MINIMUM_FEE) === true) { // Throw error throw "Unsupported kernel."; } // Set fee to serialized slate kernel's fee this.fee = new BigNumber(serializedSlateKernel["fee"]); // Break break; // Height locked features case SlateKernel.HEIGHT_LOCKED_FEATURES: // Check if serialized slate kernel's fee isn't supported if("fee" in serializedSlateKernel === false || (Common.isNumberString(serializedSlateKernel["fee"]) === false && serializedSlateKernel["fee"] instanceof BigNumber === false) || (new BigNumber(serializedSlateKernel["fee"])).isInteger() === false || (new BigNumber(serializedSlateKernel["fee"])).isLessThan(Slate.MINIMUM_FEE) === true) { // Throw error throw "Unsupported kernel."; } // Set fee to serialized slate kernel's fee this.fee = new BigNumber(serializedSlateKernel["fee"]); // Check if serialized slate kernel's lock height isn't supported if("lock_height" in serializedSlateKernel === false || (Common.isNumberString(serializedSlateKernel["lock_height"]) === false && serializedSlateKernel["lock_height"] instanceof BigNumber === false) || (new BigNumber(serializedSlateKernel["lock_height"])).isInteger() === false || (new BigNumber(serializedSlateKernel["lock_height"])).isLessThan(Slate.NO_LOCK_HEIGHT) === true) { // Throw error throw "Unsupported kernel."; } // Set lock height to serialized slate kernel's lock height this.lockHeight = new BigNumber(serializedSlateKernel["lock_height"]); // Break break; // No recent duplicate features case SlateKernel.NO_RECENT_DUPLICATE_FEATURES: // Check if no recent duplicate kernels isn't enabled if(Consensus.isNoRecentDuplicateKernelsEnabled(isMainnet) === false) { // Throw error throw "Unsupported kernel."; } // Check if serialized slate kernel's fee isn't supported if("fee" in serializedSlateKernel === false || (Common.isNumberString(serializedSlateKernel["fee"]) === false && serializedSlateKernel["fee"] instanceof BigNumber === false) || (new BigNumber(serializedSlateKernel["fee"])).isInteger() === false || (new BigNumber(serializedSlateKernel["fee"])).isLessThan(Slate.MINIMUM_FEE) === true) { // Throw error throw "Unsupported kernel."; } // Set fee to serialized slate kernel's fee this.fee = new BigNumber(serializedSlateKernel["fee"]); // Check if serialized slate kernel's relative height isn't supported if("relative_height" in serializedSlateKernel === false || (Common.isNumberString(serializedSlateKernel["relative_height"]) === false && serializedSlateKernel["relative_height"] instanceof BigNumber === false) || (new BigNumber(serializedSlateKernel["relative_height"])).isInteger() === false || (new BigNumber(serializedSlateKernel["relative_height"])).isLessThan(SlateKernel.MINIMUM_RECENT_HEIGHT) === true || (new BigNumber(serializedSlateKernel["relative_height"])).isGreaterThan(SlateKernel.MAXIMUM_RECENT_HEIGHT) === true) { // Throw error throw "Unsupported kernel."; } // Set relative height to serialized slate kernel's relative height this.relativeHeight = new BigNumber(serializedSlateKernel["relative_height"]); // Break break; } // Check if serialized slate kernel's excess signature isn't supported if("excess_sig" in serializedSlateKernel === false || Common.isHexString(serializedSlateKernel["excess_sig"]) === false || Secp256k1Zkp.isValidSingleSignerSignature(Common.fromHexString(serializedSlateKernel["excess_sig"])) !== true) { // Throw error throw "Unsupported kernel."; } // Set excess signature to serialized slate kernel's excess signature this.excessSignature = Secp256k1Zkp.singleSignerSignatureFromData(Common.fromHexString(serializedSlateKernel["excess_sig"])); // Check if excess signature isn't a valid signature if(this.getExcessSignature() === Secp256k1Zkp.OPERATION_FAILED) { // Throw error throw "Unsupported kernel."; } // Check if serialized slate kernel's excess isn't supported if("excess" in serializedSlateKernel === false || Common.isHexString(serializedSlateKernel["excess"]) === false || Common.hexStringLength(serializedSlateKernel["excess"]) !== Crypto.COMMIT_LENGTH || (Common.arraysAreEqual(Common.fromHexString(serializedSlateKernel["excess"]), SlateKernel.ZERO_EXCESS) === false && Secp256k1Zkp.isValidCommit(Common.fromHexString(serializedSlateKernel["excess"])) !== true)) { // Throw error throw "Unsupported kernel."; } // Set excess to serialized slate kernel's excess this.excess = Common.fromHexString(serializedSlateKernel["excess"]); // Break break; // Version Slatepack case Slate.VERSION_SLATEPACK: // Get bit reader var bitReader = serializedSlateKernel; // Try try { // Set fee to serialized slate kernel's fee this.fee = Slate.uncompactUint64(bitReader, true); // Check if serialized slate kernel's fee isn't supported if(this.getFee().isLessThan(Slate.MINIMUM_FEE) === true) { // Throw error throw "Unsupported kernel."; } // Set excess to serialized slate kernel's excess this.excess = bitReader.getBytes(Crypto.COMMIT_LENGTH); // Check if excess is invalid if(Common.arraysAreEqual(this.excess, SlateKernel.ZERO_EXCESS) === false && Secp256k1Zkp.isValidCommit(this.excess) !== true) { // Throw error throw "Unsupported kernel."; } // Check if serialized slate kernel's excess signature is invalid var excessSignature = bitReader.getBytes(Crypto.SINGLE_SIGNER_SIGNATURE_LENGTH); if(Secp256k1Zkp.isValidSingleSignerSignature(excessSignature) !== true) { // Throw error throw "Unsupported kernel."; } // Set excess signature to serialized slate kernel's excess signature this.excessSignature = Secp256k1Zkp.singleSignerSignatureFromData(excessSignature); // Check if excess signature isn't a valid signature if(this.getExcessSignature() === Secp256k1Zkp.OPERATION_FAILED) { // Throw error throw "Unsupported kernel."; } } // Catch errors catch(error) { // Throw error throw "Unsupported kernel."; } // Break break; // Default default: // Throw error throw "Unsupported kernel."; } // Check if complete if(this.isComplete() === true) { // Check if excess signature failed to be verified if(this.verifyExcessSignature() === false) { // Throw error throw "Unsupported kernel."; } } } // Verify excess signature verifyExcessSignature() { // Try try { // Get message to sign var messageToSign = SlateKernel.signatureMessage(this.getFeatures(), this.getFee(), this.getLockHeight(), this.getRelativeHeight()); } // Catch errors catch(error) { // Return false return false; } // Create a public key from the excess var publicKey = Secp256k1Zkp.pedersenCommitToPublicKey(this.getExcess()); // Check if the public key isn't a valid public key if(publicKey === Secp256k1Zkp.OPERATION_FAILED) { // Return false return false; } // Check if partial signature doesn't verify the excess if(Secp256k1Zkp.verifySingleSignerSignature(this.getExcessSignature(), messageToSign, Secp256k1Zkp.NO_PUBLIC_NONCE, publicKey, publicKey, false) !== true) { // Return false return false; } // Return true return true; } // Text to feature static textToFeatures(text) { // Check text switch(text) { // Plain text case "Plain": // Return plain features return SlateKernel.PLAIN_FEATURES; // Coinbase text case "Coinbase": // Return coinbase features return SlateKernel.COINBASE_FEATURES; // Height locked text case "HeightLocked": // Return height locked features return SlateKernel.HEIGHT_LOCKED_FEATURES; // No recent duplicate text case "NoRecentDuplicate": // Return no recent duplicate features return SlateKernel.NO_RECENT_DUPLICATE_FEATURES; // Default default: // Return unsupported features return SlateKernel.UNSUPPORTED_FEATURES; } } // Unsupported features static get UNSUPPORTED_FEATURES() { // Return unsupported features return SlateKernel.NO_RECENT_DUPLICATE_FEATURES + 1; } // Minimum recent height static get MINIMUM_RECENT_HEIGHT() { // Return minimum recent height return 1; } // Maximum recent height static get MAXIMUM_RECENT_HEIGHT() { // Return maximum recent height return Consensus.BLOCK_HEIGHT_WEEK; } } // Main function // Set global object's slate kernel globalThis["SlateKernel"] = SlateKernel;