ton3-core
Version:
TON low-level API tools
270 lines • 12 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CellType = exports.Cell = void 0;
const mask_1 = require("./mask");
const slice_1 = require("./slice");
const helpers_1 = require("../utils/helpers");
const numbers_1 = require("../utils/numbers");
const bits_1 = require("../utils/bits");
const hash_1 = require("../utils/hash");
const HASH_BITS = 256;
const DEPTH_BITS = 16;
var CellType;
(function (CellType) {
CellType[CellType["Ordinary"] = -1] = "Ordinary";
CellType[CellType["PrunedBranch"] = 1] = "PrunedBranch";
CellType[CellType["LibraryReference"] = 2] = "LibraryReference";
CellType[CellType["MerkleProof"] = 3] = "MerkleProof";
CellType[CellType["MerkleUpdate"] = 4] = "MerkleUpdate";
})(CellType || (CellType = {}));
exports.CellType = CellType;
const validateOrdinary = (bits, refs) => {
if (bits.length > 1023)
throw new Error(`Ordinary cell can't has more than 1023 bits, got "${bits.length}"`);
if (refs.length > 4)
throw new Error(`Ordinary cell can't has more than 4 refs, got "${refs.length}"`);
};
const validatePrunedBranch = (bits, refs) => {
const minSize = 8 + 8 + (1 * (HASH_BITS + DEPTH_BITS));
if (bits.length < minSize) {
throw new Error(`Pruned Branch cell can't has less than (8 + 8 + 256 + 16) bits, got "${bits.length}"`);
}
if (refs.length !== 0) {
throw new Error(`Pruned Branch cell can't has refs, got "${refs.length}"`);
}
const type = (0, numbers_1.bitsToIntUint)(bits.slice(0, 8), { type: 'int' });
if (type !== CellType.PrunedBranch) {
throw new Error(`Pruned Branch cell type must be exactly ${CellType.PrunedBranch}, got "${type}"`);
}
const mask = new mask_1.Mask((0, numbers_1.bitsToIntUint)(bits.slice(8, 16), { type: 'uint' }));
if (mask.level < 1 || mask.level > 3) {
throw new Error(`Pruned Branch cell level must be >= 1 and <= 3, got "${mask.level}"`);
}
const { hashCount } = mask.apply(mask.level - 1);
const size = 8 + 8 + (hashCount * (HASH_BITS + DEPTH_BITS));
if (bits.length !== size) {
throw new Error(`Pruned Branch cell with level "${mask.level}" must have exactly ${size} bits, got "${bits.length}"`);
}
};
const validateLibraryReference = (bits, refs) => {
const size = 8 + HASH_BITS;
if (bits.length !== size) {
throw new Error(`Library Reference cell must have exactly (8 + 256) bits, got "${bits.length}"`);
}
if (refs.length !== 0) {
throw new Error(`Library Reference cell can't has refs, got "${refs.length}"`);
}
const type = (0, numbers_1.bitsToIntUint)(bits.slice(0, 8), { type: 'int' });
if (type !== CellType.LibraryReference) {
throw new Error(`Library Reference cell type must be exactly ${CellType.LibraryReference}, got "${type}"`);
}
};
const validateMerkleProof = (bits, refs) => {
const size = 8 + HASH_BITS + DEPTH_BITS;
if (bits.length !== size) {
throw new Error(`Merkle Proof cell must have exactly (8 + 256 + 16) bits, got "${bits.length}"`);
}
if (refs.length !== 1) {
throw new Error(`Merkle Proof cell must have exactly 1 ref, got "${refs.length}"`);
}
const type = (0, numbers_1.bitsToIntUint)(bits.slice(0, 8), { type: 'int' });
if (type !== CellType.MerkleProof) {
throw new Error(`Merkle Proof cell type must be exactly ${CellType.MerkleProof}, got "${type}"`);
}
const data = Array.from(bits.slice(8));
const proofHash = (0, helpers_1.bitsToHex)(data.splice(0, HASH_BITS));
const proofDepth = (0, numbers_1.bitsToIntUint)(data.splice(0, DEPTH_BITS), { type: 'uint' });
const refHash = refs[0].hash(0);
const refDepth = refs[0].depth(0);
if (proofHash !== refHash) {
throw new Error(`Merkle Proof cell ref hash must be exactly "${proofHash}", got "${refHash}"`);
}
if (proofDepth !== refDepth) {
throw new Error(`Merkle Proof cell ref depth must be exactly "${proofDepth}", got "${refDepth}"`);
}
};
const validateMerkleUpdate = (bits, refs) => {
const size = 8 + (2 * (HASH_BITS + DEPTH_BITS));
if (bits.length !== size) {
throw new Error(`Merkle Update cell must have exactly (8 + (2 * (256 + 16))) bits, got "${bits.length}"`);
}
if (refs.length !== 2) {
throw new Error(`Merkle Update cell must have exactly 2 refs, got "${refs.length}"`);
}
const type = (0, numbers_1.bitsToIntUint)(bits.slice(0, 8), { type: 'int' });
if (type !== CellType.MerkleUpdate) {
throw new Error(`Merkle Update cell type must be exactly ${CellType.MerkleUpdate}, got "${type}"`);
}
const data = Array.from(bits.slice(8));
const hashes = [data.splice(0, HASH_BITS), data.splice(0, HASH_BITS)].map(el => (0, helpers_1.bitsToHex)(el));
const depths = [data.splice(0, DEPTH_BITS), data.splice(0, DEPTH_BITS)].map(el => (0, numbers_1.bitsToIntUint)(el, { type: 'uint' }));
refs.forEach((ref, i) => {
const proofHash = hashes[i];
const proofDepth = depths[i];
const refHash = ref.hash(0);
const refDepth = ref.depth(0);
if (proofHash !== refHash) {
throw new Error(`Merkle Update cell ref #${i} hash must be exactly "${proofHash}", got "${refHash}"`);
}
if (proofDepth !== refDepth) {
throw Error(`Merkle Update cell ref #${i} depth must be exactly "${proofDepth}", got "${refDepth}"`);
}
});
};
const getMapper = (type) => {
const map = new Map([
[CellType.Ordinary, {
validate: validateOrdinary,
mask: (_b, r) => new mask_1.Mask(r.reduce((acc, el) => acc | el.mask.value, 0))
}],
[CellType.PrunedBranch, {
validate: validatePrunedBranch,
mask: (b) => new mask_1.Mask((0, numbers_1.bitsToIntUint)(b.slice(8, 16), { type: 'uint' }))
}],
[CellType.LibraryReference, {
validate: validateLibraryReference,
mask: () => new mask_1.Mask(0)
}],
[CellType.MerkleProof, {
validate: validateMerkleProof,
mask: (_b, r) => new mask_1.Mask(r[0].mask.value >> 1)
}],
[CellType.MerkleUpdate, {
validate: validateMerkleUpdate,
mask: (_b, r) => new mask_1.Mask((r[0].mask.value | r[1].mask.value) >> 1)
}]
]);
const result = map.get(type);
if (result === undefined) {
throw new Error('Unknown cell type');
}
return result;
};
class Cell {
constructor(options) {
this.hashes = [];
this.depths = [];
const { bits = [], refs = [], type = CellType.Ordinary } = options || {};
const { validate, mask } = getMapper(type);
validate(bits, refs);
this._mask = mask(bits, refs);
this._type = type;
this._bits = bits;
this._refs = refs;
this.initialize();
}
get bits() {
return Array.from(this._bits);
}
get refs() {
return Array.from(this._refs);
}
get mask() {
return this._mask;
}
get type() {
return this._type;
}
get exotic() {
return this._type !== CellType.Ordinary;
}
initialize() {
const hasRefs = this._refs.length > 0;
const isMerkle = [CellType.MerkleProof, CellType.MerkleUpdate].includes(this.type);
const isPrunedBranch = this.type === CellType.PrunedBranch;
const hashIndexOffset = isPrunedBranch
? this.mask.hashCount - 1
: 0;
for (let levelIndex = 0, hashIndex = 0; levelIndex <= this.mask.level; levelIndex++) {
if (!this.mask.isSignificant(levelIndex)) {
continue;
}
if (hashIndex < hashIndexOffset) {
hashIndex++;
continue;
}
if ((hashIndex === hashIndexOffset && levelIndex !== 0 && !isPrunedBranch)
|| (hashIndex !== hashIndexOffset && levelIndex === 0 && isPrunedBranch)) {
throw new Error('Can\'t deserialize cell');
}
const refLevel = levelIndex + Number(isMerkle);
const refsDescriptor = this.getRefsDescriptor(this.mask.apply(levelIndex));
const bitsDescriptor = this.getBitsDescriptor();
const data = hashIndex !== hashIndexOffset
? (0, helpers_1.hexToBits)(this.hashes[hashIndex - hashIndexOffset - 1])
: this.getAugmentedBits();
const { depthRepr, hashRepr, depth } = this._refs.reduce((acc, ref) => {
const refDepth = ref.depth(refLevel);
const refHash = ref.hash(refLevel);
acc.depthRepr = acc.depthRepr.concat(this.getDepthDescriptor(refDepth));
acc.hashRepr = acc.hashRepr.concat((0, helpers_1.hexToBits)(refHash));
acc.depth = Math.max(acc.depth, refDepth);
return acc;
}, { depthRepr: [], hashRepr: [], depth: 0 });
const representation = [].concat(refsDescriptor, bitsDescriptor, data, depthRepr, hashRepr);
if (this._refs.length && depth >= 1024) {
throw new Error('Cell depth can\'t be more than 1024');
}
const dest = hashIndex - hashIndexOffset;
this.depths[dest] = depth + Number(hasRefs);
this.hashes[dest] = (0, hash_1.sha256)((0, helpers_1.bitsToBytes)(representation));
hashIndex++;
}
}
getDepthDescriptor(depth) {
const descriptor = Uint8Array.from([Math.floor(depth / 256), depth % 256]);
return (0, helpers_1.bytesToBits)(descriptor);
}
getRefsDescriptor(mask) {
const value = this._refs.length + (Number(this.exotic) * 8) + ((mask ? mask.value : this.mask.value) * 32);
const descriptor = Uint8Array.from([value]);
return (0, helpers_1.bytesToBits)(descriptor);
}
getBitsDescriptor() {
const value = Math.ceil(this._bits.length / 8) + Math.floor(this._bits.length / 8);
const descriptor = Uint8Array.from([value]);
return (0, helpers_1.bytesToBits)(descriptor);
}
getAugmentedBits() {
return (0, bits_1.augment)(this._bits);
}
hash(level = 3) {
if (this.type !== CellType.PrunedBranch) {
return this.hashes[this.mask.apply(level).hashIndex];
}
const { hashIndex } = this.mask.apply(level);
const { hashIndex: thisHashIndex } = this.mask;
const skip = 16 + (hashIndex * HASH_BITS);
return hashIndex !== thisHashIndex
? (0, helpers_1.bitsToHex)(this._bits.slice(skip, skip + HASH_BITS))
: this.hashes[0];
}
depth(level = 3) {
if (this.type !== CellType.PrunedBranch) {
return this.depths[this.mask.apply(level).hashIndex];
}
const { hashIndex } = this.mask.apply(level);
const { hashIndex: thisHashIndex } = this.mask;
const skip = 16 + (thisHashIndex * HASH_BITS) + (hashIndex * DEPTH_BITS);
return hashIndex !== thisHashIndex
? (0, numbers_1.bitsToIntUint)(this._bits.slice(skip, skip + DEPTH_BITS), { type: 'uint' })
: this.depths[0];
}
slice() {
return slice_1.Slice.parse(this);
}
print(indent = 1, size = 0) {
const bits = Array.from(this._bits);
const areDivisible = bits.length % 4 === 0;
const augmented = !areDivisible ? (0, bits_1.augment)(bits, 4) : bits;
const fiftHex = `${(0, helpers_1.bitsToHex)(augmented).toUpperCase()}${!areDivisible ? '_' : ''}`;
const output = [`${' '.repeat(indent * size)}x{${fiftHex}}\n`];
this._refs.forEach(ref => output.push(ref.print(indent, size + 1)));
return output.join('');
}
eq(cell) {
return this.hash() === cell.hash();
}
}
exports.Cell = Cell;
//# sourceMappingURL=cell.js.map