UNPKG

@btc-vision/transaction

Version:

OPNet transaction library allows you to create and sign transactions for the OPNet network.

432 lines 14.9 kB
import { AddressMap } from '../deterministic/AddressMap.js'; import { ExtendedAddressMap } from '../deterministic/ExtendedAddressMap.js'; import { Address } from '../keypair/Address.js'; import { ADDRESS_BYTE_LENGTH, EXTENDED_ADDRESS_BYTE_LENGTH, I128_BYTE_LENGTH, I16_BYTE_LENGTH, I32_BYTE_LENGTH, I64_BYTE_LENGTH, I8_BYTE_LENGTH, SCHNORR_SIGNATURE_BYTE_LENGTH, U128_BYTE_LENGTH, U16_BYTE_LENGTH, U256_BYTE_LENGTH, U32_BYTE_LENGTH, U64_BYTE_LENGTH, U8_BYTE_LENGTH, } from '../utils/lengths.js'; export class BinaryReader { buffer; currentOffset = 0; constructor(bytes) { this.buffer = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); } get byteLength() { return this.buffer.byteLength; } // Helpers for comparisons; unchanged static stringCompare(a, b) { return a.localeCompare(b); } static bigintCompare(a, b) { if (a < b) return -1; if (a > b) return 1; return 0; } static numberCompare(a, b) { if (a < b) return -1; if (a > b) return 1; return 0; } setBuffer(bytes) { this.buffer = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); this.currentOffset = 0; } length() { return this.buffer.byteLength; } bytesLeft() { return this.buffer.byteLength - this.currentOffset; } // ------------------- Signed Integer Readers ------------------- // /** * Reads a single signed byte (i8). */ readI8() { this.verifyEnd(this.currentOffset + I8_BYTE_LENGTH); const value = this.buffer.getInt8(this.currentOffset); this.currentOffset += I8_BYTE_LENGTH; return value; } /** * Reads a signed 16-bit integer. By default, big-endian. * @param be - Endianness; true means big-endian (the default). */ readI16(be = true) { this.verifyEnd(this.currentOffset + I16_BYTE_LENGTH); const value = this.buffer.getInt16(this.currentOffset, !be); this.currentOffset += I16_BYTE_LENGTH; return value; } /** * Reads a signed 32-bit integer. By default, big-endian. * @param be - Endianness; true means big-endian (the default). */ readI32(be = true) { this.verifyEnd(this.currentOffset + I32_BYTE_LENGTH); const value = this.buffer.getInt32(this.currentOffset, !be); this.currentOffset += I32_BYTE_LENGTH; return value; } /** * Reads a signed 64-bit integer. By default, big-endian. * @param be - Endianness; true means big-endian (the default). */ readI64(be = true) { this.verifyEnd(this.currentOffset + I64_BYTE_LENGTH); const value = this.buffer.getBigInt64(this.currentOffset, !be); this.currentOffset += I64_BYTE_LENGTH; return value; } // ------------------- Unsigned Integer Readers ------------------- // /** * Reads a single unsigned byte (u8). */ readU8() { this.verifyEnd(this.currentOffset + U8_BYTE_LENGTH); const value = this.buffer.getUint8(this.currentOffset); this.currentOffset += U8_BYTE_LENGTH; return value; } /** * Reads an unsigned 16-bit integer. By default, big-endian. * @param be - Endianness; true means big-endian (the default). */ readU16(be = true) { this.verifyEnd(this.currentOffset + U16_BYTE_LENGTH); const value = this.buffer.getUint16(this.currentOffset, !be); this.currentOffset += U16_BYTE_LENGTH; return value; } /** * Reads an unsigned 32-bit integer. By default, big-endian. * @param be - Endianness; true means big-endian (the default). */ readU32(be = true) { this.verifyEnd(this.currentOffset + U32_BYTE_LENGTH); const value = this.buffer.getUint32(this.currentOffset, !be); this.currentOffset += U32_BYTE_LENGTH; return value; } /** * Reads an unsigned 64-bit integer. By default, big-endian. * @param be - Endianness; true means big-endian (the default). */ readU64(be = true) { this.verifyEnd(this.currentOffset + U64_BYTE_LENGTH); const value = this.buffer.getBigUint64(this.currentOffset, !be); this.currentOffset += U64_BYTE_LENGTH; return value; } /** * Reads a 128-bit unsigned integer. By default, read big-endian. * @param be - Endianness; true => big-endian (default). */ readU128(be = true) { const raw = this.readBytes(U128_BYTE_LENGTH); let bytes = raw; // If data was written in little-endian, we reverse before interpreting if (!be) { bytes = this.reverseBytes(raw); } return BigInt('0x' + this.toHexString(bytes)); } /** * Reads a 256-bit unsigned integer. Same approach as readU128. * @param be - Endianness; true => big-endian (default). */ readU256(be = true) { const raw = this.readBytes(U256_BYTE_LENGTH); let bytes = raw; if (!be) { bytes = this.reverseBytes(raw); } return BigInt('0x' + this.toHexString(bytes)); } /** * Reads a 128-bit signed integer. Interpret the sign bit if big-endian. * @param be - Endianness; true => big-endian (default). */ readI128(be = true) { const raw = this.readBytes(I128_BYTE_LENGTH); let bytes = raw; if (!be) { bytes = this.reverseBytes(raw); } // Construct as a 128-bit two's complement let value = BigInt('0x' + this.toHexString(bytes)); // If the top bit is set (sign bit in big-endian), interpret negative const signBitMask = 0x80; if (bytes[0] & signBitMask) { // (1 << 128) const twoTo128 = BigInt(1) << BigInt(128); // 2's complement value = value - twoTo128; } return value; } /** * Read a boolean (u8 != 0). */ readBoolean() { return this.readU8() !== 0; } /** * Reads 32 bits */ readSelector() { return this.readU32(true); } /** * Reads a raw sequence of bytes (length must be known). * If zeroStop = true, stops if we encounter 0x00 early. */ readBytes(length, zeroStop = false) { this.verifyEnd(this.currentOffset + length); let bytes = new Uint8Array(length); for (let i = 0; i < length; i++) { const b = this.buffer.getUint8(this.currentOffset++); if (zeroStop && b === 0) { bytes = new Uint8Array(bytes.subarray(0, i)); break; } bytes[i] = b; } return bytes; } /** * Reads a string of the given length in raw bytes. By default, do NOT zero-stop * (matching how we wrote the raw bytes). */ readString(length) { const textDecoder = new TextDecoder(); const bytes = this.readBytes(length, false); return textDecoder.decode(bytes); } /** * Reads a string that was written as [u16 length][raw bytes]. */ readStringWithLength(be = true) { const length = this.readU32(be); return this.readString(length); } /** * Reads an address (32 bytes MLDSA key hash only). */ readAddress() { const bytes = Array.from(this.readBytes(ADDRESS_BYTE_LENGTH)); return new Address(bytes); } /** * Reads the tweaked public key portion (32 bytes) and returns it as a raw Uint8Array. * Use this when you only need the tweaked key without the full Address object. */ readTweakedPublicKey() { this.verifyEnd(this.currentOffset + ADDRESS_BYTE_LENGTH); return this.readBytes(ADDRESS_BYTE_LENGTH); } /** * Reads a full address with both MLDSA key hash and tweaked public key (64 bytes total). * Format: [32 bytes tweakedPublicKey][32 bytes MLDSA key hash] * * This is the equivalent of btc-runtime's readExtendedAddress(). * * @returns An Address instance with both keys set */ readExtendedAddress() { this.verifyEnd(this.currentOffset + EXTENDED_ADDRESS_BYTE_LENGTH); // Read tweaked public key first (32 bytes) const tweakedPublicKey = Array.from(this.readBytes(ADDRESS_BYTE_LENGTH)); // Read MLDSA key hash (32 bytes) const mldsaKeyHash = Array.from(this.readBytes(ADDRESS_BYTE_LENGTH)); return new Address(mldsaKeyHash, tweakedPublicKey); } /** * Reads a Schnorr signature with its associated full Address. * Format: [64 bytes full Address][64 bytes signature] * * Used for deserializing signed data where both the signer's address * and their Schnorr signature are stored together. * * @returns A SchnorrSignature containing the address and signature */ readSchnorrSignature() { this.verifyEnd(this.currentOffset + EXTENDED_ADDRESS_BYTE_LENGTH + SCHNORR_SIGNATURE_BYTE_LENGTH); const address = this.readExtendedAddress(); const signature = this.readBytes(SCHNORR_SIGNATURE_BYTE_LENGTH); return { address, signature }; } /** * Reads bytes written as [u32 length][bytes]. * @param maxLength if > 0, enforces an upper bound * @param be */ readBytesWithLength(maxLength = 0, be = true) { const length = this.readU32(be); if (maxLength > 0 && length > maxLength) { throw new Error('Data length exceeds maximum length.'); } return this.readBytes(length); } // ------------------ Array readers ------------------ // readArrayOfBuffer(be = true) { const length = this.readU16(be); const result = new Array(length); for (let i = 0; i < length; i++) { result[i] = this.readBytesWithLength(); } return result; } readAddressArray(be = true) { const length = this.readU16(be); const result = new Array(length); for (let i = 0; i < length; i++) { result[i] = this.readAddress(); } return result; } readU256Array(be = true) { const length = this.readU16(be); const result = new Array(length); for (let i = 0; i < length; i++) { result[i] = this.readU256(be); } return result; } readU128Array(be = true) { const length = this.readU16(be); const result = new Array(length); for (let i = 0; i < length; i++) { result[i] = this.readU128(be); } return result; } readU64Array(be = true) { const length = this.readU16(be); const result = new Array(length); for (let i = 0; i < length; i++) { result[i] = this.readU64(be); } return result; } readU32Array(be = true) { const length = this.readU16(be); const result = new Array(length); for (let i = 0; i < length; i++) { result[i] = this.readU32(be); } return result; } readU16Array(be = true) { const length = this.readU16(be); const result = new Array(length); for (let i = 0; i < length; i++) { result[i] = this.readU16(be); } return result; } readU8Array() { const length = this.readU16(true); // by default big-endian const result = new Array(length); for (let i = 0; i < length; i++) { result[i] = this.readU8(); } return result; } readStringArray(be = true) { const length = this.readU16(be); const result = new Array(length); for (let i = 0; i < length; i++) { result[i] = this.readStringWithLength(be); } return result; } readBytesArray(be = true) { const length = this.readU16(be); const result = new Array(length); for (let i = 0; i < length; i++) { result[i] = this.readBytesWithLength(0, be); } return result; } /** * Reads [u16 length][ (address, u256) pairs ]. */ readAddressValueTuple(be = true) { const length = this.readU16(be); const result = new AddressMap(); for (let i = 0; i < length; i++) { const address = this.readAddress(); const value = this.readU256(be); if (result.has(address)) { throw new Error('Duplicate address found in map'); } result.set(address, value); } return result; } /** * Reads an array of full addresses (64 bytes each). * Format: [u16 length][FullAddress 0][FullAddress 1]... */ readExtendedAddressArray(be = true) { const length = this.readU16(be); const result = new Array(length); for (let i = 0; i < length; i++) { result[i] = this.readExtendedAddress(); } return result; } /** * Reads a map of full Address -> u256 using the tweaked key for map lookup. * Format: [u16 length][FullAddress key][u256 value]... * * This is the equivalent of btc-runtime's readExtendedAddressMapU256(). */ readExtendedAddressMapU256(be = true) { const length = this.readU16(be); const result = new ExtendedAddressMap(); for (let i = 0; i < length; i++) { const address = this.readExtendedAddress(); const value = this.readU256(be); if (result.has(address)) { throw new Error('Duplicate tweaked address found in map'); } result.set(address, value); } return result; } // --------------------------------------------------- // getOffset() { return this.currentOffset; } setOffset(offset) { this.currentOffset = offset; } /** * Verifies we have enough bytes in the buffer to read up to `size`. */ verifyEnd(size) { if (size > this.buffer.byteLength) { throw new Error(`Attempt to read beyond buffer length: requested up to byte offset ${size}, but buffer is only ${this.buffer.byteLength} bytes.`); } } /** * Utility: reverses a byte array in-place or returns a reversed copy. */ reverseBytes(bytes) { const out = new Uint8Array(bytes.length); for (let i = 0; i < bytes.length; i++) { out[i] = bytes[bytes.length - 1 - i]; } return out; } /** * Utility: turn bytes into a hex string without `0x` prefix. */ toHexString(bytes) { return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join(''); } } //# sourceMappingURL=BinaryReader.js.map