Socket
Socket
Sign inDemoInstall

bitcore-lib-cash

Package Overview
Dependencies
20
Maintainers
3
Versions
99
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 10.0.17 to 10.0.18

test/data/chipnet-121957.dat

12

lib/opcode.js

@@ -246,4 +246,12 @@ 'use strict';

OP_RESERVED3: 206,
OP_RESERVED4: 207
// introspection of tokens
OP_UTXOTOKENCATEGORY: 206,
OP_UTXOTOKENCOMMITMENT: 207,
OP_UTXOTOKENAMOUNT: 208,
OP_OUTPUTTOKENCATEGORY: 209,
OP_OUTPUTTOKENCOMMITMENT: 210,
OP_OUTPUTTOKENAMOUNT: 211,
OP_RESERVED3: 212,
OP_RESERVED4: 213
};

@@ -250,0 +258,0 @@

@@ -426,8 +426,19 @@ var Address = require('../address');

*/
Script.prototype.isScriptHashOut = function() {
Script.prototype.isScriptHashOut = function(fEnableP2SH32) {
var buf = this.toBuffer();
return (buf.length === 23 &&
const isP2SH20 = (buf.length === 23 &&
buf[0] === Opcode.OP_HASH160 &&
buf[1] === 0x14 &&
buf[buf.length - 1] === Opcode.OP_EQUAL);
if (isP2SH20) {
return true;
}
if (!fEnableP2SH32) {
return false;
}
const isP2SH32 = (buf.length === 35 &&
buf[0] === Opcode.OP_HASH256 &&
buf[1] === 0x20 &&
buf[buf.length - 1] === Opcode.OP_EQUAL);
return isP2SH32;
};

@@ -434,0 +445,0 @@

@@ -9,2 +9,3 @@ 'use strict';

var BufferWriter = require('../encoding/bufferwriter');
var BufferReader = require('../encoding/bufferreader');
var Script = require('../script');

@@ -33,2 +34,3 @@ var $ = require('../util/preconditions');

}
this.tokenData = args.tokenData;
} else {

@@ -81,2 +83,43 @@ throw new TypeError('Unrecognized argument for Output');

const maximumAmount = new BN('9223372036854775807');
const nftCapabilityNumberToLabel = ['none', 'mutable', 'minting'];
const nftCapabilityLabelToNumber = {
'none': 0,
'mutable': 1,
'minting': 2,
};
Object.defineProperty(Output.prototype, 'tokenData', {
configurable: false,
enumerable: true,
get: function() {
return this._tokenData;
},
set: function(tokenData) {
if (typeof tokenData === "object") {
$.checkState(typeof tokenData.category !== "undefined", 'tokenData must have a category (a hex-encoded string or buffer)');
const categoryBuf = typeof tokenData.category === 'string' ? Buffer.from(tokenData.category, 'hex') : Buffer.from(tokenData.category);
$.checkState(categoryBuf.length === 32, 'tokenData must have a 32-byte category');
const category = categoryBuf.toString('hex');
$.checkState(typeof tokenData.amount !== "undefined", 'tokenData must have an amount (from 0 to 9223372036854775807)');
$.checkState(typeof tokenData.amount !== "number" || tokenData.amount <= Number.MAX_SAFE_INTEGER, 'to avoid precision loss, tokenData amount must provided as a string for values greater than 9007199254740991.');
const amount = new BN(tokenData.amount);
$.checkState(amount.gten(0), 'tokenData amount must be greater than or equal to 0');
$.checkState(amount.lte(maximumAmount), 'tokenData amount must be less than or equal to 9223372036854775807.');
if(typeof tokenData.nft === "object"){
const nft = {};
nft.capability = tokenData.nft.capability === undefined ? 'none' : String(tokenData.nft.capability);
$.checkState(nftCapabilityNumberToLabel.includes(nft.capability), 'nft capability must be "none", "mutable", or "minting".');
const commitment = tokenData.nft.commitment === undefined ? Buffer.of() : typeof tokenData.nft.commitment === 'string' ? Buffer.from(tokenData.nft.commitment, 'hex') : Buffer.from(tokenData.nft.commitment);
$.checkState(commitment.length <= 40, 'nft commitment length must be less than or equal to 40 bytes.');
nft.commitment = commitment.toString('hex');
this._tokenData = { category, amount, nft };
} else {
$.checkState(amount.gtn(0), 'tokenData must encode at least one token');
this._tokenData = { category, amount };
}
}
}
});
Output.prototype.invalidSatoshis = function() {

@@ -118,2 +161,6 @@ if (this._satoshis > MAX_SAFE_INTEGER) {

obj.script = this._scriptBuffer.toString('hex');
if(this._tokenData !== undefined) {
obj.tokenData = this._tokenData;
obj.tokenData.amount = obj.tokenData.amount.toString();
}
return obj;

@@ -154,2 +201,3 @@ };

}
$.checkState(this._scriptBuffer[0] !== PREFIX_TOKEN, 'Invalid output script: output script may not begin with PREFIX_TOKEN (239).');
return this;

@@ -165,5 +213,20 @@ };

}
return '<Output (' + this.satoshis + ' sats) ' + scriptStr + '>';
let tokenInfo = '';
if(typeof this._tokenData !== "undefined") {
const nftInfo = typeof this._tokenData.nft === "undefined" ?
'' : `; nft [capability: ${this._tokenData.nft.capability}; commitment: ${this._tokenData.nft.commitment}]`;
tokenInfo = `(token category: ${this._tokenData.category}; amount: ${this._tokenData.amount}${nftInfo} ) `
}
return '<Output (' + this.satoshis + ' sats) ' + tokenInfo + scriptStr + '>';
};
const PREFIX_TOKEN = 0xef;
const HAS_AMOUNT = 0b00010000;
const HAS_NFT = 0b00100000;
const HAS_COMMITMENT_LENGTH = 0b01000000;
const RESERVED_BIT = 0b10000000;
const categoryLength = 32;
const tokenFormatMask = 0xf0;
const nftCapabilityMask = 0x0f;
const maximumCapability = 2;
Output.fromBufferReader = function(br) {

@@ -174,3 +237,36 @@ var obj = {};

if (size !== 0) {
obj.script = br.read(size);
var scriptSlot = br.read(size);
if(scriptSlot[0] === PREFIX_TOKEN) {
$.checkState(scriptSlot.length >= 34, 'Invalid token prefix: insufficient length.');
const tokenDataAndBytecode = BufferReader(scriptSlot.slice(1));
obj.tokenData = {};
obj.tokenData.category = tokenDataAndBytecode.read(categoryLength).reverse();
const tokenBitfield = tokenDataAndBytecode.readUInt8();
const prefixStructure = tokenBitfield & tokenFormatMask;
$.checkState((prefixStructure & RESERVED_BIT) === 0, 'Invalid token prefix: reserved bit is set.');
const nftCapabilityInt = tokenBitfield & nftCapabilityMask;
$.checkState(nftCapabilityInt <= maximumCapability, `Invalid token prefix: capability must be none (0), mutable (1), or minting (2). Capability value: ${nftCapabilityInt}`);
const hasNft = (prefixStructure & HAS_NFT) !== 0;
const hasCommitmentLength = (prefixStructure & HAS_COMMITMENT_LENGTH) !== 0;
if (hasCommitmentLength && !hasNft) $.checkState(false, 'Invalid token prefix: commitment requires an NFT.');
const hasAmount = (prefixStructure & HAS_AMOUNT) !== 0;
if(hasNft) {
obj.tokenData.nft = {};
obj.tokenData.nft.capability = nftCapabilityNumberToLabel[nftCapabilityInt];
if(hasCommitmentLength) {
const length = tokenDataAndBytecode.readVarintNum();
$.checkState(length > 0, 'Invalid token prefix: if encoded, commitment length must be greater than 0.');
obj.tokenData.nft.commitment = tokenDataAndBytecode.read(length);
} else {
obj.tokenData.nft.commitment = Buffer.of();
}
} else {
$.checkState(nftCapabilityInt === 0, 'Invalid token prefix: capability requires an NFT.');
$.checkState(hasAmount, 'Invalid token prefix: must encode at least one token.');
}
obj.tokenData.amount = hasAmount? tokenDataAndBytecode.readVarintBN() : new BN(0);
obj.script = tokenDataAndBytecode.readAll();
} else {
obj.script = scriptSlot;
}
} else {

@@ -188,2 +284,31 @@ obj.script = Buffer.from([]);

var script = this._scriptBuffer;
if(typeof this._tokenData !== "undefined") {
const tokenPrefix = new BufferWriter();
tokenPrefix.writeUInt8(PREFIX_TOKEN);
tokenPrefix.write(Buffer.from(this._tokenData.category, 'hex').reverse());
const hasNft = this._tokenData.nft === undefined ? 0 : HAS_NFT;
const capabilityInt = this._tokenData.nft === undefined ?
0 : nftCapabilityLabelToNumber[this._tokenData.nft.capability];
const hasCommitmentLength = this._tokenData.nft !== undefined &&
this._tokenData.nft.commitment.length > 0 ? HAS_COMMITMENT_LENGTH : 0;
const amount = new BN(this._tokenData.amount);
const hasAmount = amount.gtn(0) ? HAS_AMOUNT : 0;
const tokenBitfield =
hasNft | capabilityInt | hasCommitmentLength | hasAmount;
tokenPrefix.writeUInt8(tokenBitfield);
if(hasCommitmentLength) {
const commitment = Buffer.from(this._tokenData.nft.commitment, 'hex');
tokenPrefix.writeVarintNum(commitment.length);
tokenPrefix.write(commitment);
}
if(hasAmount) {
tokenPrefix.writeVarintBN(amount);
}
const tokenPrefixBuffer = tokenPrefix.toBuffer();
const totalLength = tokenPrefixBuffer.length + script.length;
writer.writeVarintNum(totalLength);
writer.write(tokenPrefixBuffer);
writer.write(script);
return writer;
}
writer.writeVarintNum(script.length);

@@ -190,0 +315,0 @@ writer.write(script);

@@ -1269,2 +1269,58 @@ 'use strict';

/**
* @returns {bool} whether the token validation algorithm is satisifed by this transaction
*/
Transaction.prototype.validateTokens = function() {
const tokenInputs = this.inputs.filter(input => input.output.tokenData);
const tokenOutputs = this.outputs.filter(output => output.tokenData);
const outputsGroupedByCategory = _.groupBy(tokenOutputs, (output) => output.tokenData.category);
Object.values(outputsGroupedByCategory).forEach(categoryOutputs => {
const category = categoryOutputs[0].tokenData.category;
let unusedCategoryInputs = tokenInputs.filter(input => input.output.tokenData.category === category);
const inputFungibleAmount = unusedCategoryInputs.reduce((sum, input) => {
const tokenAmount = input.output.tokenData.amount;
return tokenAmount ? sum.add(tokenAmount) : sum;
}, new BN(0));
let mintedAmount = new BN(0);
let sentAmount = new BN(0);
categoryOutputs.forEach(output => {
const mintingUtxo = this.inputs.find(input => input.prevTxId.toString('hex') === output.tokenData.category);
const tokenAmount = output.tokenData.amount;
if (mintingUtxo) {
if (mintingUtxo.outputIndex !== 0) {
throw new Error('the transaction creates an immutable token for a category without a matching minting token or sufficient mutable tokens.');
}
mintedAmount = mintedAmount.add(tokenAmount);
} else {
if (output.tokenData.nft) {
const parentUtxo = unusedCategoryInputs.filter(input => input.output.tokenData.nft).find(input => {
if (output.tokenData.nft.capability === 'none') {
if (input.output.tokenData.nft.commitment === output.tokenData.nft.commitment) {
return true;
}
return input.output.tokenData.nft.capability !== 'none';
}
return input.output.tokenData.nft.capability !== 'none';
});
if (!parentUtxo) {
throw new Error('the transaction creates an immutable token for a category without a matching minting token or sufficient mutable tokens.');
}
if (parentUtxo.output.tokenData.nft.capability !== 'minting') {
unusedCategoryInputs = unusedCategoryInputs.filter(input => !(input.prevTxId === parentUtxo.prevTxId && input.outputIndex === parentUtxo.outputIndex));
}
}
sentAmount = sentAmount.add(tokenAmount);
}
});
if (mintedAmount.gt(new BN('9223372036854775807'))) {
throw new Error('the transaction outputs include a sum of fungible tokens for a category exceeding the maximum supply (9223372036854775807)');
}
if (sentAmount.gt(inputFungibleAmount)) {
throw new Error("the sum of fungible tokens in the transaction's outputs exceed that of the transactions inputs for a category");
}
});
return true;
};
/**
* Check that a transaction passes basic sanity tests. If not, return a string

@@ -1271,0 +1327,0 @@ * describing the error. This function contains the same logic as

4

package.json
{
"name": "bitcore-lib-cash",
"version": "10.0.17",
"version": "10.0.18",
"description": "A pure and powerful JavaScript Bitcoin Cash library.",

@@ -55,3 +55,3 @@ "author": "BitPay <dev@bitpay.com>",

"license": "MIT",
"gitHead": "857b05eec8deda181b7ba4a7da9e52d3548946a2"
"gitHead": "2a67b8e66387080edeca102c84a8ec00cd31567e"
}

@@ -96,4 +96,4 @@ 'use strict';

describe('@map', function() {
it('should have a map containing 141 elements', function() {
_.size(Opcode.map).should.equal(141);
it('should have a map containing 147 elements', function() {
_.size(Opcode.map).should.equal(147);
});

@@ -100,0 +100,0 @@ });

@@ -240,2 +240,10 @@ 'use strict';

if (flagstr.indexOf('ENABLE_TOKENS') !== -1) {
flags = flags | Interpreter.SCRIPT_ENABLE_TOKENS;
}
if (flagstr.indexOf('ENABLE_P2SH_32') !== -1) {
flags = flags | Interpreter.SCRIPT_ENABLE_P2SH_32;
}
return flags;

@@ -320,3 +328,3 @@ };

describe('libauth vmb evaluation fixtures', () => {
const flags = getFlags('P2SH CLEANSTACK MINIMALDATA VERIFY_CHECKLOCKTIMEVERIFY NATIVE_INTROSPECTION 64_BIT_INTEGERS');
const flags = getFlags('P2SH CLEANSTACK MINIMALDATA VERIFY_CHECKLOCKTIMEVERIFY NATIVE_INTROSPECTION 64_BIT_INTEGERS ENABLE_TOKENS ENABLE_P2SH_32');
const getOutputsFromHex = outputsHex => {

@@ -334,11 +342,29 @@ const reader = new BufferReader(Buffer.from(outputsHex,'hex'));

const inputIndex = test[7] || 0;
const tx = new Transaction(txHex);
const outputs = getOutputsFromHex(sourceOutputsHex);
tx.inputs.forEach((input, index) => input.output = outputs[index]);
const scriptSig = tx.inputs[inputIndex].script;
const scriptPubkey = tx.inputs[inputIndex].output.script;
it(`should pass vmb_tests vector ${testId}`, () => {
const valid = Interpreter().verify(scriptSig, scriptPubkey, tx, inputIndex, flags);
const expectedValidity = !labels[0].endsWith('invalid');
valid.should.equal(expectedValidity);
const shouldFail = labels.includes('chip_cashtokens_invalid') || !labels.some(label => label.includes('cashtokens')) && labels.includes('2022_invalid');
const expectedValidity = !shouldFail;
let tx;
try {
tx = new Transaction(txHex);
} catch (e) {
false.should.equal(expectedValidity);
return;
}
try {
const outputs = getOutputsFromHex(sourceOutputsHex);
tx.inputs.forEach((input, index) => input.output = outputs[index]);
tx.validateTokens();
} catch (e) {
false.should.equal(expectedValidity);
return;
}
const scriptSig = tx.inputs[inputIndex].script;
const scriptPubkey = tx.inputs[inputIndex].output.script;
const interpreter = Interpreter();
try {
const valid = interpreter.verify(scriptSig, scriptPubkey, tx, inputIndex, flags);
valid.should.equal(expectedValidity);
} catch (err) {
false.should.equal(expectedValidity);
}
});

@@ -345,0 +371,0 @@ });

@@ -184,2 +184,23 @@ "use strict";

it('output token data must be valid', function() {
expect(() => new Output({ satoshis: 0, script: '51', tokenData: {} })).to.throw('Invalid state: tokenData must have a category (a hex-encoded string or buffer)');
expect(() => new Output({ satoshis: 0, script: '51', tokenData: { category: '', amount: 1 } })).to.throw('Invalid state: tokenData must have a 32-byte category');
expect(() => new Output({ satoshis: 1000, script: '51', tokenData: { category: '0102030405060708091011121314151617181920212223242526272829303132', amount: 0 } })).to.throw('Invalid state: tokenData must encode at least one token');
expect(() => new Output({ satoshis: 1000, script: '51', tokenData: { category: '0102030405060708091011121314151617181920212223242526272829303132', amount: 1 } })).to.not.throw();
expect(() => new Output({ satoshis: 1000, script: '51', tokenData: { category: '0102030405060708091011121314151617181920212223242526272829303132', amount: -1 } })).to.throw();
expect(() => new Output({ satoshis: 1000, script: '51', tokenData: { category: '0102030405060708091011121314151617181920212223242526272829303132', amount: 9007199254740991 } })).to.not.throw();
expect(() => new Output({ satoshis: 1000, script: '51', tokenData: { category: '0102030405060708091011121314151617181920212223242526272829303132', amount: 9007199254740992 } })).to.throw('Invalid state: to avoid precision loss, tokenData amount must provided as a string for values greater than 9007199254740991.');
expect(() => new Output({ satoshis: 1000, script: '51', tokenData: { category: '0102030405060708091011121314151617181920212223242526272829303132', amount: '9223372036854775808' } })).to.throw('Invalid state: tokenData amount must be less than or equal to 9223372036854775807.');
expect(() => new Output({ satoshis: 1000, script: '51', tokenData: { category: '0102030405060708091011121314151617181920212223242526272829303132', amount: 0, nft: {} } })).to.not.throw();
expect(() => new Output({ satoshis: 1000, script: '51', tokenData: { category: '0102030405060708091011121314151617181920212223242526272829303132', amount: 0, nft: { capability: 'unknown' } } })).to.throw('Invalid state: nft capability must be "none", "mutable", or "minting".');
expect(() => new Output({ satoshis: 1000, script: '51', tokenData: { category: '0102030405060708091011121314151617181920212223242526272829303132', amount: 0, nft: { capability: 'none' } } })).to.not.throw();
expect(new Output({ satoshis: 1000, script: '51', tokenData: { category: '0102030405060708091011121314151617181920212223242526272829303132', amount: 0, nft: { commitment: '' } } })).to.deep.equal(new Output({ satoshis: 1000, script: '51', tokenData: { category: '0102030405060708091011121314151617181920212223242526272829303132', amount: 0, nft: { commitment: Buffer.of() } } }));
expect(() => new Output({ satoshis: 1000, script: '51', tokenData: { category: '0102030405060708091011121314151617181920212223242526272829303132', amount: 0, nft: { commitment: Buffer.from(new Uint8Array(41)) } } })).to.throw('Invalid state: nft commitment length must be less than or equal to 40 bytes.');
});
it('output creation fails if script includes token prefix', function() {
expect(() => new Output({ satoshis: 1000, script: 'ef' })).to.throw('Invalid output script: output script may not begin with PREFIX_TOKEN (239).');
expect(() => new Output({ satoshis: 1000, script: '00ef' })).to.not.throw();
});
it("sets script to null if it is an InvalidBuffer", function() {

@@ -186,0 +207,0 @@ var output = new Output({

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc