@node-lightning/wire
Version:
Lightning Network Wire Protocol
266 lines (238 loc) • 9.36 kB
text/typescript
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());
}
}