UNPKG

@node-lightning/wire

Version:
266 lines (238 loc) 9.36 kB
import { BufferReader, BufferWriter } from "@node-lightning/bufio"; import { BitField, ShortChannelId, Value } from "@node-lightning/core"; import { shortChannelIdFromBuffer } from "@node-lightning/core"; import * as crypto from "@node-lightning/crypto"; import { Checksum } from "../domain/Checksum"; import { ChannelUpdateMessageFlags } from "../flags/ChannelUpdateMessageFlags"; import { ChannelUpdateChannelFlags } from "../flags/ChanneUpdateChannelFlags"; import { MessageType } from "../MessageType"; import { IWireMessage } from "./IWireMessage"; /** * After a channel has been announced, each side independently announces the fees * and minimum expiry delta it requires to relay HTLCs through this channel. A * node can broadcast this message multiple times in order to change fees. */ export class ChannelUpdateMessage implements IWireMessage { /** * Deserializes the message from a Buffer. The message * is not validated in this function. */ public static deserialize(payload: Buffer): ChannelUpdateMessage { const instance = new ChannelUpdateMessage(); const reader = new BufferReader(payload); reader.readUInt16BE(); // read off type instance.signature = reader.readBytes(64); instance.chainHash = reader.readBytes(32); instance.shortChannelId = shortChannelIdFromBuffer(reader.readBytes(8)); instance.timestamp = reader.readUInt32BE(); instance.messageFlags = BitField.fromNumber(reader.readUInt8()); instance.channelFlags = BitField.fromNumber(reader.readUInt8()); instance.cltvExpiryDelta = reader.readUInt16BE(); instance.htlcMinimumMsat = Value.fromMilliSats(reader.readUInt64BE()); instance.feeBaseMsat = Value.fromMilliSats(reader.readUInt32BE()); instance.feeProportionalMillionths = Value.fromMicroSats(reader.readUInt32BE()); // has optional_channel_htlc_max if (instance.hasHtlcMaximumMsatFlag) { instance.htlcMaximumMsat = Value.fromMilliSats(reader.readUInt64BE()); } return instance; } /** * Performs a double SHA-256 hash of the message with all * data excluding the signature data */ public static hashForSignature(message: ChannelUpdateMessage): Buffer { const raw = message.serialize().slice(66); return crypto.hash256(raw); } /** * Performs signature validation for the message by * hashing the data post signature. A passing signature * indicates that the message was submitted by the node * that owns the channel. Becuase the nodeId is not included * in the message, we need to obtain the nodeId by accessing * the ChannelAnnouncementMessage and determining the public key * @param message * @param pubkey 33-byte ECDSA public key */ public static validateSignature(message: ChannelUpdateMessage, pubkey: Buffer) { const sigmsg = ChannelUpdateMessage.hashForSignature(message); return crypto.verifySig(sigmsg, message.signature, pubkey); } /** * Message type is 258 */ public type: MessageType = MessageType.ChannelUpdate; /** * 64-byte buffer containing the ECDSA secp256k1 signature of the double * SHA256 hash of the message as signed by the originating node. */ public signature: Buffer; /** * Must set chain_hash to a 32-byte hash that uniquely identifies * the chain that the channel opened within. */ public chainHash: Buffer; /** * ShortChannelId is a unique reference to the funding output of the * channel. */ public shortChannelId: ShortChannelId; /** * Timestamp of the update message and is used to indicate ordering of * messages if multiple messages are sent by the same node. */ public timestamp: number; /** * Indicate the presence of optional fields in the channel_update message. * bit, field * 0, htlc_maximum_msat */ public messageFlags: BitField<ChannelUpdateMessageFlags> = new BitField(); /** * Indicates the direction of the channel: it identifies the node that this * update originated from and signals various options concerning the channel * such as whether it is disabled. * bit, name * 0, direction * 1, disabled */ public channelFlags: BitField<ChannelUpdateChannelFlags> = new BitField(); /** * The number of blocks the channel will subtract from an incoming * HTLC's cltv_expiry. */ public cltvExpiryDelta: number; /** * The minimum HTLC value (in millisatoshi) that the channel peer * will accept. */ public htlcMinimumMsat: Value; /** * The maximum value (in millisatoshi) it will send through this * channel for a single HTLC. This value must be less than the * channel capacity. This value will only be available when the * message flag option_channel_htlc_max is set. */ public htlcMaximumMsat: Value; /** * The base fee (in millisatoshi) the channel will charge for * any HTLC. */ public feeBaseMsat: Value; /** * The amount (in millionths of a satoshi) it will charge per * transferred satoshi. */ public feeProportionalMillionths: Value; /** * Returns true when message flags have the optional * maximum HTLC msat value available */ public get hasHtlcMaximumMsatFlag(): boolean { return this.messageFlags.isSet(0); } /** * Direction is determined by channel_flags bit 0. * When set to 0, node_1 is the sender. When set to 1 * node_2 is the sender */ public get direction(): number { return this.channelFlags.isSet(0) ? 1 : 0; } public set direction(val: number) { if (val === 0) { this.channelFlags.unset(0); } else if (val === 1) { this.channelFlags.set(0); } else { throw new Error("Invlid direction"); } } /** * Disabled flag is determined by channel_flags bit 1. * When set to 0, the channel is active. When set to 1 * the channel is disabled. */ public get disabled(): boolean { return this.channelFlags.isSet(1); } public set disabled(val: boolean) { if (val) { this.channelFlags.set(1); } else { this.channelFlags.unset(1); } } /** * Serializes the instance into a Buffer that can be * transmitted over the wire */ public serialize() { const len = 2 + // type 64 + // signature 32 + // chain_hash 8 + // short_channel_id 4 + // timestamp 1 + // message_flags 1 + // channel_flags 2 + // cltv_expiry_delta 8 + // htlc_minimum_msat 4 + // fee_base_msat 4 + // fee_proportional_millionths (this.hasHtlcMaximumMsatFlag ? 8 : 0); const writer = new BufferWriter(Buffer.alloc(len)); writer.writeUInt16BE(this.type); writer.writeBytes(this.signature); writer.writeBytes(this.chainHash); writer.writeBytes(this.shortChannelId.toBuffer()); writer.writeUInt32BE(this.timestamp); writer.writeUInt8(this.messageFlags.toNumber()); writer.writeUInt8(this.channelFlags.toNumber()); writer.writeUInt16BE(this.cltvExpiryDelta); writer.writeUInt64BE(this.htlcMinimumMsat.msats); writer.writeUInt32BE(Number(this.feeBaseMsat.msats)); writer.writeUInt32BE(Number(this.feeProportionalMillionths.microsats)); if (this.hasHtlcMaximumMsatFlag) { writer.writeUInt64BE(this.htlcMaximumMsat.msats); } return writer.toBuffer(); } /** * CRC32C checksum is calculated from the raw channel_update * message and excludes the signature and timestamp. * * @remarks * Defined in BOLT 07 gossip queries section: * https://github.com/lightningnetwork/lightning-rfc/blob/master/07-routing-gossip.md#query-messages * */ public checksum(): Checksum { const len = 2 + // type 32 + // chain_hash 8 + // short_channel_id 1 + // message_flags 1 + // channel_flags 2 + // cltv_expiry_delta 8 + // htlc_minimum_msat 4 + // fee_base_msat 4 + // fee_proportional_millionths (this.hasHtlcMaximumMsatFlag ? 8 : 0); const writer = new BufferWriter(Buffer.alloc(len)); writer.writeUInt16BE(this.type); writer.writeBytes(this.chainHash); writer.writeBytes(this.shortChannelId.toBuffer()); writer.writeUInt8(Number(this.messageFlags)); writer.writeUInt8(Number(this.channelFlags)); writer.writeUInt16BE(this.cltvExpiryDelta); writer.writeUInt64BE(this.htlcMinimumMsat.msats); writer.writeUInt32BE(Number(this.feeBaseMsat.msats)); writer.writeUInt32BE(Number(this.feeProportionalMillionths.microsats)); if (this.hasHtlcMaximumMsatFlag) { writer.writeUInt64BE(this.htlcMaximumMsat.msats); } return Checksum.fromBuffer(writer.toBuffer()); } }