UNPKG

@btc-vision/transaction

Version:

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

407 lines 15 kB
import { AddressMap } from '../deterministic/AddressMap.js'; import { ExtendedAddressMap } from '../deterministic/ExtendedAddressMap.js'; import { Address } from '../keypair/Address.js'; import { BufferHelper } from '../utils/BufferHelper.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'; import { BinaryReader } from './BinaryReader.js'; export class BinaryWriter { currentOffset = 0; buffer; constructor(length = 0) { this.buffer = this.getDefaultBuffer(length); } static estimateArrayOfBufferLength(values) { if (values.length > 65535) throw new Error('Array size is too large'); let totalLength = U16_BYTE_LENGTH; for (let i = 0; i < values.length; i++) { totalLength += U32_BYTE_LENGTH + values[i].length; // each entry has a u32 length prefix } return totalLength; } writeU8(value) { if (value > 255) throw new Error('u8 value is too large.'); this.allocSafe(U8_BYTE_LENGTH); this.buffer.setUint8(this.currentOffset++, value); } writeU16(value, be = true) { if (value > 65535) throw new Error('u16 value is too large.'); this.allocSafe(U16_BYTE_LENGTH); this.buffer.setUint16(this.currentOffset, value, !be); this.currentOffset += 2; } writeU32(value, be = true) { if (value > 4294967295) throw new Error('u32 value is too large.'); this.allocSafe(U32_BYTE_LENGTH); this.buffer.setUint32(this.currentOffset, value, !be); this.currentOffset += 4; } writeU64(value, be = true) { if (value > 18446744073709551615n) throw new Error('u64 value is too large.'); this.allocSafe(U64_BYTE_LENGTH); this.buffer.setBigUint64(this.currentOffset, value, !be); this.currentOffset += 8; } // ------------------- Signed Integer Writers ------------------- // /** * Writes a signed 8-bit integer. */ writeI8(value) { if (value < -128 || value > 127) throw new Error('i8 value is out of range.'); this.allocSafe(I8_BYTE_LENGTH); this.buffer.setInt8(this.currentOffset, value); this.currentOffset += I8_BYTE_LENGTH; } /** * Writes a signed 16-bit integer. By default big-endian (be = true). */ writeI16(value, be = true) { if (value < -32768 || value > 32767) throw new Error('i16 value is out of range.'); this.allocSafe(I16_BYTE_LENGTH); this.buffer.setInt16(this.currentOffset, value, !be); this.currentOffset += I16_BYTE_LENGTH; } /** * Writes a signed 32-bit integer. By default big-endian (be = true). */ writeI32(value, be = true) { if (value < -2147483648 || value > 2147483647) throw new Error('i32 value is out of range.'); this.allocSafe(I32_BYTE_LENGTH); this.buffer.setInt32(this.currentOffset, value, !be); this.currentOffset += I32_BYTE_LENGTH; } /** * Writes a signed 64-bit integer. By default big-endian (be = true). */ writeI64(value, be = true) { if (value < -9223372036854775808n || value > 9223372036854775807n) { throw new Error('i64 value is out of range.'); } this.allocSafe(I64_BYTE_LENGTH); this.buffer.setBigInt64(this.currentOffset, value, !be); this.currentOffset += I64_BYTE_LENGTH; } // ---------------------------------------------------------------- // writeSelector(value) { this.writeU32(value, true); } writeBoolean(value) { this.writeU8(value ? 1 : 0); } writeI128(bigIntValue, be = true) { if (bigIntValue > 170141183460469231731687303715884105727n || bigIntValue < -170141183460469231731687303715884105728n) { throw new Error('i128 value is too large.'); } this.allocSafe(I128_BYTE_LENGTH); const bytesToHex = BufferHelper.valueToUint8Array(bigIntValue, I128_BYTE_LENGTH); if (bytesToHex.byteLength !== I128_BYTE_LENGTH) { throw new Error(`Invalid i128 value: ${bigIntValue}`); } if (be) { for (let i = 0; i < bytesToHex.byteLength; i++) { this.writeU8(bytesToHex[i]); } } else { for (let i = bytesToHex.byteLength - 1; i >= 0; i--) { this.writeU8(bytesToHex[i]); } } } writeU256(bigIntValue, be = true) { if (bigIntValue > 115792089237316195423570985008687907853269984665640564039457584007913129639935n && bigIntValue < 0n) { throw new Error('u256 value is too large or negative.'); } this.allocSafe(U256_BYTE_LENGTH); const bytesToHex = BufferHelper.valueToUint8Array(bigIntValue); if (bytesToHex.byteLength !== U256_BYTE_LENGTH) { throw new Error(`Invalid u256 value: ${bigIntValue}`); } if (be) { for (let i = 0; i < bytesToHex.byteLength; i++) { this.writeU8(bytesToHex[i]); } } else { for (let i = bytesToHex.byteLength - 1; i >= 0; i--) { this.writeU8(bytesToHex[i]); } } } writeU128(bigIntValue, be = true) { if (bigIntValue > 340282366920938463463374607431768211455n && bigIntValue < 0n) { throw new Error('u128 value is too large or negative.'); } this.allocSafe(U128_BYTE_LENGTH); const bytesToHex = BufferHelper.valueToUint8Array(bigIntValue, U128_BYTE_LENGTH); if (bytesToHex.byteLength !== U128_BYTE_LENGTH) { throw new Error(`Invalid u128 value: ${bigIntValue}`); } if (be) { for (let i = 0; i < bytesToHex.byteLength; i++) { this.writeU8(bytesToHex[i]); } } else { for (let i = bytesToHex.byteLength - 1; i >= 0; i--) { this.writeU8(bytesToHex[i]); } } } writeBytes(value) { this.allocSafe(value.byteLength); for (let i = 0; i < value.byteLength; i++) { this.writeU8(value[i]); } } writeString(value) { const encoder = new TextEncoder(); const bytes = encoder.encode(value); this.allocSafe(bytes.length); this.writeBytes(bytes); } writeStringWithLength(value) { const encoder = new TextEncoder(); const bytes = encoder.encode(value); this.allocSafe(U32_BYTE_LENGTH + bytes.length); this.writeU32(bytes.length); this.writeBytes(bytes); } /** * Writes an address (32 bytes MLDSA key hash only). */ writeAddress(value) { this.verifyAddress(value); this.writeBytes(value); } /** * Writes the tweaked public key from an Address (32 bytes). * @param value - The Address containing the tweaked public key */ writeTweakedPublicKey(value) { const tweakedKey = value.tweakedPublicKeyToBuffer(); this.allocSafe(ADDRESS_BYTE_LENGTH); this.writeBytes(tweakedKey); } /** * Writes a full address with both tweaked public key and MLDSA key hash (64 bytes total). * Format: [32 bytes tweakedPublicKey][32 bytes MLDSA key hash] * * This is the equivalent of btc-runtime's writeExtendedAddress(). * * @param value - The Address containing both keys */ writeExtendedAddress(value) { this.allocSafe(EXTENDED_ADDRESS_BYTE_LENGTH); // Write tweaked public key first (32 bytes) this.writeTweakedPublicKey(value); // Write MLDSA key hash (32 bytes) this.writeBytes(value); } /** * Writes a Schnorr signature with its associated full Address. * Format: [64 bytes full Address][64 bytes signature] * * Used for serializing signed data where both the signer's address * and their Schnorr signature need to be stored together. * * @param address - The signer's Address (with both MLDSA and tweaked keys) * @param signature - The 64-byte Schnorr signature * @throws {Error} If signature is not exactly 64 bytes */ writeSchnorrSignature(address, signature) { if (signature.length !== SCHNORR_SIGNATURE_BYTE_LENGTH) { throw new Error(`Invalid Schnorr signature length: expected ${SCHNORR_SIGNATURE_BYTE_LENGTH}, got ${signature.length}`); } this.allocSafe(EXTENDED_ADDRESS_BYTE_LENGTH + SCHNORR_SIGNATURE_BYTE_LENGTH); this.writeExtendedAddress(address); this.writeBytes(signature); } getBuffer(clear = true) { const buf = new Uint8Array(this.buffer.byteLength); for (let i = 0; i < this.buffer.byteLength; i++) { buf[i] = this.buffer.getUint8(i); } if (clear) this.clear(); return buf; } reset() { this.currentOffset = 0; this.buffer = this.getDefaultBuffer(4); } toBytesReader() { return new BinaryReader(this.getBuffer()); } getOffset() { return this.currentOffset; } setOffset(offset) { this.currentOffset = offset; } clear() { this.currentOffset = 0; this.buffer = this.getDefaultBuffer(); } [Symbol.dispose]() { this.clear(); } allocSafe(size) { if (this.currentOffset + size > this.buffer.byteLength) { this.resize(size); } } writeAddressValueTuple(map, be = true) { if (map.size > 65535) throw new Error('Map size is too large'); this.writeU16(map.size, be); const keys = Array.from(map.keys()); for (let i = 0; i < keys.length; i++) { const key = keys[i]; const value = map.get(key); if (value === null || value === undefined) throw new Error('Value not found'); this.writeAddress(key); this.writeU256(value, be); } } /** * Writes 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 writeExtendedAddressMapU256(). */ writeExtendedAddressMapU256(map, be = true) { if (map.size > 65535) throw new Error('Map size is too large'); this.writeU16(map.size, be); for (const [key, value] of map.entries()) { this.writeExtendedAddress(key); this.writeU256(value, be); } } writeBytesWithLength(value) { this.writeU32(value.length); this.writeBytes(value); } writeArrayOfBuffer(values, be = true) { const totalLength = BinaryWriter.estimateArrayOfBufferLength(values); this.allocSafe(totalLength); this.writeU16(values.length, be); for (let i = 0; i < values.length; i++) { this.writeU32(values[i].length, be); this.writeBytes(values[i]); } } writeAddressArray(value) { if (value.length > 65535) throw new Error('Array size is too large'); this.writeU16(value.length); for (let i = 0; i < value.length; i++) { this.writeAddress(value[i]); } } /** * Writes an array of full addresses (64 bytes each). * Format: [u16 length][FullAddress 0][FullAddress 1]... */ writeExtendedAddressArray(value) { if (value.length > 65535) throw new Error('Array size is too large'); this.allocSafe(U16_BYTE_LENGTH + value.length * EXTENDED_ADDRESS_BYTE_LENGTH); this.writeU16(value.length); for (let i = 0; i < value.length; i++) { this.writeExtendedAddress(value[i]); } } writeU32Array(value, be = true) { if (value.length > 65535) throw new Error('Array size is too large'); this.writeU16(value.length, be); for (let i = 0; i < value.length; i++) { this.writeU32(value[i], be); } } writeU256Array(value, be = true) { if (value.length > 65535) throw new Error('Array size is too large'); this.writeU16(value.length, be); for (let i = 0; i < value.length; i++) { this.writeU256(value[i], be); } } writeU128Array(value, be = true) { if (value.length > 65535) throw new Error('Array size is too large'); this.writeU16(value.length, be); for (let i = 0; i < value.length; i++) { this.writeU128(value[i], be); } } writeStringArray(value) { if (value.length > 65535) throw new Error('Array size is too large'); this.writeU16(value.length); for (let i = 0; i < value.length; i++) { this.writeStringWithLength(value[i]); } } writeU16Array(value, be = true) { if (value.length > 65535) throw new Error('Array size is too large'); this.writeU16(value.length, be); for (let i = 0; i < value.length; i++) { this.writeU16(value[i], be); } } writeU8Array(value) { if (value.length > 65535) throw new Error('Array size is too large'); this.writeU16(value.length); for (let i = 0; i < value.length; i++) { this.writeU8(value[i]); } } writeU64Array(value, be = true) { if (value.length > 65535) throw new Error('Array size is too large'); this.writeU16(value.length, be); for (let i = 0; i < value.length; i++) { this.writeU64(value[i], be); } } writeBytesArray(value) { if (value.length > 65535) throw new Error('Array size is too large'); this.writeU16(value.length); for (let i = 0; i < value.length; i++) { this.writeBytesWithLength(value[i]); } } verifyAddress(pubKey) { if (pubKey.byteLength > ADDRESS_BYTE_LENGTH) { throw new Error(`Address is too long ${pubKey.byteLength} > ${ADDRESS_BYTE_LENGTH} bytes`); } } resize(size) { const buf = new Uint8Array(this.buffer.byteLength + size); for (let i = 0; i < this.buffer.byteLength; i++) { buf[i] = this.buffer.getUint8(i); } this.buffer = new DataView(buf.buffer, buf.byteOffset, buf.byteLength); } getDefaultBuffer(length = 0) { return new DataView(new ArrayBuffer(length)); } } //# sourceMappingURL=BinaryWriter.js.map