UNPKG

@ton/core

Version:

Core TypeScript library that implements low level primitives for TON blockchain.

311 lines (269 loc) 8.69 kB
/** * Copyright (c) Whales Corp. * All Rights Reserved. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import { Address } from "../address/Address"; import { ExternalAddress } from "../address/ExternalAddress"; import { bitsForNumber } from "../utils/bitsForNumber"; import { Maybe } from "../utils/maybe"; import { BitString } from "./BitString"; /** * Class for building bit strings */ export class BitBuilder { private _buffer: Buffer; private _length: number; constructor(size: number = 1023) { this._buffer = Buffer.alloc(Math.ceil(size / 8)); this._length = 0; } /** * Current number of bits written */ get length() { return this._length; } /** * Write a single bit * @param value bit to write, true or positive number for 1, false or zero or negative for 0 */ writeBit(value: boolean | number) { // Check overflow let n = this._length; if (n > this._buffer.length * 8) { throw new Error("BitBuilder overflow"); } // Set bit if ((typeof value === 'boolean' && value === true) || (typeof value === 'number' && value > 0)) { this._buffer[(n / 8) | 0] |= 1 << (7 - (n % 8)); } // Advance this._length++; } /** * Copy bits from BitString * @param src source bits */ writeBits(src: BitString) { for (let i = 0; i < src.length; i++) { this.writeBit(src.at(i)); } } /** * Write bits from buffer * @param src source buffer */ writeBuffer(src: Buffer) { // Special case for aligned offsets if (this._length % 8 === 0) { if (this._length + src.length * 8 > this._buffer.length * 8) { throw new Error("BitBuilder overflow"); } src.copy(this._buffer, this._length / 8); this._length += src.length * 8; } else { for (let i = 0; i < src.length; i++) { this.writeUint(src[i], 8); } } } /** * Write uint value * @param value value as bigint or number * @param bits number of bits to write */ writeUint(value: bigint | number, bits: number) { if (bits < 0 || !Number.isSafeInteger(bits)) { throw Error(`invalid bit length. Got ${bits}`); } const v = BigInt(value); if (bits === 0) { if (v !== 0n) { throw Error(`value is not zero for ${bits} bits. Got ${value}`); } else { return; } } const vBits = (1n << BigInt(bits)); if (v < 0 || v >= vBits) { throw Error(`bitLength is too small for a value ${value}. Got ${bits}`); } if (this._length + bits > this._buffer.length * 8) { throw new Error("BitBuilder overflow"); } const tillByte = 8 - (this._length % 8); if (tillByte > 0) { const bidx = Math.floor(this._length / 8); if (bits < tillByte) { const wb = Number(v); this._buffer[bidx] |= wb << (tillByte - bits); this._length += bits; } else { const wb = Number(v >> BigInt(bits - tillByte)); this._buffer[bidx] |= wb; this._length += tillByte; } } bits -= tillByte; while (bits > 0) { if (bits >= 8) { this._buffer[this._length / 8] = Number((v >> BigInt(bits - 8)) & 0xffn); this._length += 8; bits -= 8; } else { this._buffer[this._length / 8] = Number((v << BigInt(8 - bits)) & 0xffn); this._length += bits; bits = 0; } } } /** * Write int value * @param value value as bigint or number * @param bits number of bits to write */ writeInt(value: bigint | number, bits: number) { let v = BigInt(value); if (bits < 0 || !Number.isSafeInteger(bits)) { throw Error(`invalid bit length. Got ${bits}`); } // Corner case for zero bits if (bits === 0) { if (value !== 0n) { throw Error(`value is not zero for ${bits} bits. Got ${value}`); } else { return; } } // Corner case for one bit if (bits === 1) { if (value !== -1n && value !== 0n) { throw Error(`value is not zero or -1 for ${bits} bits. Got ${value}`); } else { this.writeBit(value === -1n); return; } } // Check input let vBits = 1n << (BigInt(bits) - 1n); if (v < -vBits || v >= vBits) { throw Error(`value is out of range for ${bits} bits. Got ${value}`); } // Write sign if (v < 0) { this.writeBit(true); v = vBits + v; } else { this.writeBit(false); } // Write value this.writeUint(v, bits - 1); } /** * Wrtie var uint value, used for serializing coins * @param value value to write as bigint or number * @param bits header bits to write size */ writeVarUint(value: number | bigint, bits: number) { let v = BigInt(value); if (bits < 0 || !Number.isSafeInteger(bits)) { throw Error(`invalid bit length. Got ${bits}`); } if (v < 0) { throw Error(`value is negative. Got ${value}`); } // Corner case for zero if (v === 0n) { // Write zero size this.writeUint(0, bits); return; } // Calculate size const sizeBytes = Math.ceil((v.toString(2).length) / 8); // Fastest way in most environments const sizeBits = sizeBytes * 8; // Write size this.writeUint(sizeBytes, bits); // Write number this.writeUint(v, sizeBits); } /** * Wrtie var int value, used for serializing coins * @param value value to write as bigint or number * @param bits header bits to write size */ writeVarInt(value: number | bigint, bits: number) { let v = BigInt(value); if (bits < 0 || !Number.isSafeInteger(bits)) { throw Error(`invalid bit length. Got ${bits}`); } // Corner case for zero if (v === 0n) { // Write zero size this.writeUint(0, bits); return; } // Calculate size let v2 = v > 0 ? v : -v; const sizeBytes = 1 + Math.ceil((v2.toString(2).length) / 8); // Fastest way in most environments const sizeBits = sizeBytes * 8; // Write size this.writeUint(sizeBytes, bits); // Write number this.writeInt(v, sizeBits); } /** * Write coins in var uint format * @param amount amount to write */ writeCoins(amount: number | bigint) { this.writeVarUint(amount, 4); } /** * Write address * @param address write address or address external */ writeAddress(address: Maybe<Address | ExternalAddress>) { // Is empty address if (address === null || address === undefined) { this.writeUint(0, 2); // Empty address return; } // Is Internal Address if (Address.isAddress(address)) { this.writeUint(2, 2); // Internal address this.writeUint(0, 1); // No anycast this.writeInt(address.workChain, 8); this.writeBuffer(address.hash); return; } // Is External Address if (ExternalAddress.isAddress(address)) { this.writeUint(1, 2); // External address this.writeUint(address.bits, 9); this.writeUint(address.value, address.bits); return; } // Invalid address throw Error(`Invalid address. Got ${address}`); } /** * Build BitString * @returns result bit string */ build() { return new BitString(this._buffer, 0, this._length); } /** * Build into Buffer * @returns result buffer */ buffer() { if (this._length % 8 !== 0) { throw new Error("BitBuilder buffer is not byte aligned"); } return this._buffer.subarray(0, this._length / 8); } }