Skip to content

A tiny firewall for LND that allows or denies channel openings and payment routings.

License

Notifications You must be signed in to change notification settings

callebtc/electronwall

Repository files navigation

Logo of electronwall.

A tiny firewall for LND that can filter Lightning channel opening requests and HTLC forwards based on your custom rules.

electronwall uses filter lists that either allow (allowlist) or reject (denylist) events from a list of node public keys for channel openings, or channel IDs and channel pairs for payment routings.

You can also write custom rules using a builtin Javascript engine.

Note: electronwall is in early development and highly experimental software. Use at your own risk. Please report issues.

electronwall0 4

Install

From source

Build from source (you may need to install go for this):

git clone https://github.com/callebtc/electronwall.git
cd electronwall
go build .

Binaries

You can download a binary for your system here. You'll still need a config file.

Config

Edit config.yaml.example and rename to config.yaml.

Run

./electronwall

Rules

Passthrough

The passthrough option is a mode for both ChannelMode and ForwardMode in the config.yaml file. When set to passthrough, electronwall will not apply any allowlist, denylist, or programmable rules to channel open requests or HTLC forwards. Instead, it will simply pass through all requests without any checks.

Allowlist and denylist

Allowlist and denylist rules are set in config.yaml under the appropriate keys. See the example config.

Programmable rules

electronwall has a Javascript engine called goja that allows you to set custom rules. Note that you can only use pure Javascript (ECMAScript), you can't import a ton of other dependcies like with web applications.

Rules are saved in the rules/ directory. There are two files, one for channel open requests ChannelAccept.js and one for HTLC forwards HtlcForward.js.

electronwall passes contextual information to the Javascript engine that you can use to create rich rules. See below for a list of objects that are currently supported.

Here is one rather complex rule for channel accept decisions in ChannelAccept.js for demonstration purposes:

// only channels > 0.75 Msat
ChannelAccept.Event.FundingAmt >= 750000 && 
// nodes with high 1ML availability score
ChannelAccept.OneMl.Noderank.Availability > 100 &&
// nodes with a low enough 1ML age rank
ChannelAccept.OneMl.Noderank.Age < 10000 &&
( 
   // only nodes with Amboss contact data
   ChannelAccept.Amboss.Socials.Info.Email ||
   ChannelAccept.Amboss.Socials.Info.Twitter ||
   ChannelAccept.Amboss.Socials.Info.Telegram 
) &&
(
   // elitist: either nodes with amboss prime
   ChannelAccept.Amboss.Amboss.IsPrime ||
   // or nodes with high-ranking capacity
   ChannelAccept.Amboss.GraphInfo.Metrics.CapacityRank < 1000 ||
   // or nodes with high-ranking channel count
   ChannelAccept.Amboss.GraphInfo.Metrics.ChannelsRank < 1000
)

Here is an example HtlcForward.js for deciding on HTLC forwards:

if (
   // only forward amounts larger than 100 sat
   HtlcForward.Event.OutgoingAmountMsat >= 100000
) { true } else { false }

Contextual information

Here is a list of all objects that are passed to the Javascript engine. You need to look at the structure of these objects in order to use them in a custom rule like the example above.

LND: ChannelAcceptRequest *.Event

type ChannelAcceptRequest struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	// The pubkey of the node that wishes to open an inbound channel.
	NodePubkey []byte `protobuf:"bytes,1,opt,name=node_pubkey,json=nodePubkey,proto3" json:"node_pubkey,omitempty"`
	// The hash of the genesis block that the proposed channel resides in.
	ChainHash []byte `protobuf:"bytes,2,opt,name=chain_hash,json=chainHash,proto3" json:"chain_hash,omitempty"`
	// The pending channel id.
	PendingChanId []byte `protobuf:"bytes,3,opt,name=pending_chan_id,json=pendingChanId,proto3" json:"pending_chan_id,omitempty"`
	// The funding amount in satoshis that initiator wishes to use in the
	// channel.
	FundingAmt uint64 `protobuf:"varint,4,opt,name=funding_amt,json=fundingAmt,proto3" json:"funding_amt,omitempty"`
	// The push amount of the proposed channel in millisatoshis.
	PushAmt uint64 `protobuf:"varint,5,opt,name=push_amt,json=pushAmt,proto3" json:"push_amt,omitempty"`
	// The dust limit of the initiator's commitment tx.
	DustLimit uint64 `protobuf:"varint,6,opt,name=dust_limit,json=dustLimit,proto3" json:"dust_limit,omitempty"`
	// The maximum amount of coins in millisatoshis that can be pending in this
	// channel.
	MaxValueInFlight uint64 `protobuf:"varint,7,opt,name=max_value_in_flight,json=maxValueInFlight,proto3" json:"max_value_in_flight,omitempty"`
	// The minimum amount of satoshis the initiator requires us to have at all
	// times.
	ChannelReserve uint64 `protobuf:"varint,8,opt,name=channel_reserve,json=channelReserve,proto3" json:"channel_reserve,omitempty"`
	// The smallest HTLC in millisatoshis that the initiator will accept.
	MinHtlc uint64 `protobuf:"varint,9,opt,name=min_htlc,json=minHtlc,proto3" json:"min_htlc,omitempty"`
	// The initial fee rate that the initiator suggests for both commitment
	// transactions.
	FeePerKw uint64 `protobuf:"varint,10,opt,name=fee_per_kw,json=feePerKw,proto3" json:"fee_per_kw,omitempty"`
	//
	//The number of blocks to use for the relative time lock in the pay-to-self
	//output of both commitment transactions.
	CsvDelay uint32 `protobuf:"varint,11,opt,name=csv_delay,json=csvDelay,proto3" json:"csv_delay,omitempty"`
	// The total number of incoming HTLC's that the initiator will accept.
	MaxAcceptedHtlcs uint32 `protobuf:"varint,12,opt,name=max_accepted_htlcs,json=maxAcceptedHtlcs,proto3" json:"max_accepted_htlcs,omitempty"`
	// A bit-field which the initiator uses to specify proposed channel
	// behavior.
	ChannelFlags uint32 `protobuf:"varint,13,opt,name=channel_flags,json=channelFlags,proto3" json:"channel_flags,omitempty"`
	// The commitment type the initiator wishes to use for the proposed channel.
	CommitmentType CommitmentType `protobuf:"varint,14,opt,name=commitment_type,json=commitmentType,proto3,enum=lnrpc.CommitmentType" json:"commitment_type,omitempty"`
}

1ML node information *.OneMl

type OneML_NodeInfoResponse struct {
	LastUpdate int    `json:"last_update"`
	PubKey     string `json:"pub_key"`
	Alias      string `json:"alias"`
	Addresses  []struct {
		Network string `json:"network"`
		Addr    string `json:"addr"`
	} `json:"addresses"`
	Color        string `json:"color"`
	Capacity     int    `json:"capacity"`
	Channelcount int    `json:"channelcount"`
	Noderank     struct {
		Capacity     int `json:"capacity"`
		Channelcount int `json:"channelcount"`
		Age          int `json:"age"`
		Growth       int `json:"growth"`
		Availability int `json:"availability"`
	} `json:"noderank"`
}

Amboss node information *.Amboss

type Amboss_NodeInfoResponse struct {
	Socials struct {
		Info struct {
			Email            string      `json:"email"`
			Telegram         string      `json:"telegram"`
			Twitter          string      `json:"twitter"`
			LightningAddress string      `json:"lightning_address"`
			Website          string      `json:"website"`
			Pubkey           string      `json:"pubkey"`
			MinChannelSize   interface{} `json:"minChannelSize"`
			Message          string      `json:"message"`
			TwitterVerified  bool        `json:"twitter_verified"`
			Updated          time.Time   `json:"updated"`
		} `json:"info"`
	} `json:"socials"`
	GraphInfo struct {
		LastUpdate time.Time `json:"last_update"`
		Metrics    struct {
			Capacity     string `json:"capacity"`
			CapacityRank int    `json:"capacity_rank"`
			Channels     int    `json:"channels"`
			ChannelsRank int    `json:"channels_rank"`
		} `json:"metrics"`
		Node struct {
			Addresses []struct {
				Addr   string `json:"addr"`
				IPInfo struct {
					City        string `json:"city"`
					Country     string `json:"country"`
					CountryCode string `json:"country_code"`
				} `json:"ip_info"`
				Network string `json:"network"`
			} `json:"addresses"`
			LastUpdate int    `json:"last_update"`
			Color      string `json:"color"`
			Features   []struct {
				FeatureID  string `json:"feature_id"`
				IsKnown    bool   `json:"is_known"`
				IsRequired bool   `json:"is_required"`
				Name       string `json:"name"`
			} `json:"features"`
		} `json:"node"`
	} `json:"graph_info"`
	Amboss struct {
		IsFavorite            bool `json:"is_favorite"`
		IsPrime               bool `json:"is_prime"`
		NumberFavorites       int  `json:"number_favorites"`
		NewChannelGossipDelta struct {
			Mean string `json:"mean"`
			Sd   string `json:"sd"`
		} `json:"new_channel_gossip_delta"`
		Notifications struct {
			NumberSubscribers int `json:"number_subscribers"`
		} `json:"notifications"`
	} `json:"amboss"`
}

Network information *.Network

TBD