---
# System prepended metadata

title: LN SendCustomMessage Document

---

# LN SendCustomMessage Document

`SendCustomMessage`についてのドキュメント

**Version: v0.0.1**

## Setup

### Install [btcd](https://github.com/btcsuite/btcd)

```shell
git clone https://github.com/btcsuite/btcd $GOPATH/src/github.com/btcsuite/btcd
cd $GOPATH/src/github.com/btcsuite/btcd
GO111MODULE=on go install -v . ./cmd/...
```

### Install [lnd](https://github.com/lightningnetwork/lnd)

```shell
git@github.com:lightningnetwork/lnd.git
# compile tagging signrpc routerrpc
make tags="signrpc routerrpc"
make install tags="signrpc routerrpc"
```

### Open Channel

```
# check path and env
export GOPATH=~/YOUR_GO_DIRECTORY 
export PATH=$PATH:$GOPATH/bin
export BTCD_DATA_PATH=YOUR_BTCD_DATA_PATH

# run btcd
btcd --txindex --simnet --rpcuser=kek --rpcpass=kek --datadir="BTCD_DATA_PATH"

# create workspaces
cd $GOPATH
mkdir dev
cd dev
mkdir alice bob

# alice
cd $GOPATH/dev/alice
lnd --rpclisten=localhost:10001 --listen=localhost:10011 --restlisten=localhost:8001 --datadir=data --logdir=log --debuglevel=info --bitcoin.simnet --bitcoin.active --bitcoin.node=btcd --btcd.rpcuser=kek --btcd.rpcpass=kek --routerrpc.attemptcost=0 --accept-keysend
lncli --rpcserver=localhost:10001 --macaroonpath="$GOPATH"/dev/alice/data/chain/bitcoin/simnet/admin.macaroon create
alias lncli-alice="lncli --rpcserver=localhost:10001 --macaroonpath= --macaroonpath=$GOPATH/dev/alice/data/chain/bitcoin/simnet/admin.macaroon"
lncli-alice newaddress np2wkh

# bob
cd $GOPATH/dev/bob
lnd --rpclisten=localhost:10002 --listen=localhost:10012 --restlisten=localhost:8002 --datadir=data --logdir=log --debuglevel=info --bitcoin.simnet --bitcoin.active --bitcoin.node=btcd --btcd.rpcuser=kek --btcd.rpcpass=kek --routerrpc.attemptcost=0 --accept-keysend
lncli --rpcserver=localhost:10002 --macaroonpath="$GOPATH"/dev/bob/data/chain/bitcoin/simnet/admin.macaroon create
alias lncli-bob="lncli --rpcserver=localhost:10002 --macaroonpath= --macaroonpath=$GOPATH/dev/bob/data/chain/bitcoin/simnet/admin.macaroon"
lncli-bob newaddress np2wkh

# get BTC
# stop your btcd and run following command
btcd --txindex --simnet --rpcuser=kek --rpcpass=kek --datadir="$BTCD_DATA_PATH" --miningaddr=<ALICE_ADDRESS>
btcctl --simnet --rpcuser=kek --rpcpass=kek generate 400 -C "$BTCD_DATA_PATH"

cd $GOPATH/dev/alice
lncli-alice walletbalance

# create p2p network
# get bob pubkey
lncli-bob getinfo
lncli-alice connect <BOB_PUBKEY>@localhost:10012
lncli-alice listpeers

# open channel
lncli-alice openchannel --node_key=<BOB_PUBKEY> --local_amt=1000000
btcctl --simnet --rpcuser=kek --rpcpass=kek generate 6 -C "$BTCD_DATA_PATH"
lncli-alice listchannels

# pay
lncli-bob addinvoice --amt=10000
lncli-alice sendpayment --pay_req=<INVOICE>
lncli-alice listchannels
```

## SendCustomMessageについて

- インボイスなしにピアのノードに、メッセージを送信することができる。
- **公開鍵で指定されるノードは、ピアである必要がある。ノードと直接接続されている必要がある。**
- 受信側はメッセージをサブスクライブする必要がある。
- メッセージの形式: 16進数文字列にエンコードする必要がある。
- データの最大サイズ: `65533` 下記参照。
- LND versionのminimumバージョンの指定がある。

### SendCustomMessageのインターフェイス

```javascript
/* lncli: `sendcustom`
SendCustomMessage sends a custom peer message.
*/
rpc SendCustomMessage (SendCustomMessageRequest)
    returns (SendCustomMessageResponse);

/* lncli: `subscribecustom`
SubscribeCustomMessages subscribes to a stream of incoming custom peer
messages.
*/
rpc SubscribeCustomMessages (SubscribeCustomMessagesRequest)
    returns (stream CustomMessage);
}

message CustomMessage {
    // Peer from which the message originates
    bytes peer = 1;

    // Message type. This value will be in the custom range (>= 32768).
    uint32 type = 2;

    // Raw message data
    bytes data = 3;
}

message SendCustomMessageRequest {
    // Peer to send the message to
    bytes peer = 1;xxx

    // Message type. This value needs to be in the custom range (>= 32768).
    uint32 type = 2;

    // Raw message data.
    bytes data = 3;
}
```

### インターフェイスの例

送信メッセージは、16進数でエンコードする必要がある。または、typeは、`32768`から`65533`の間で指定する。

```javascript
// SendMessage
{
  message: Buffer.from(message).toString("hex"),
  public_key: '021c5096cb9a5c6b150b6fae93d0026c90482de3393c7f88d8b9c0cd11708a6cd3'
  type: 32768,
}

// ResponseMessage
{
  message: '68656c6c6f20776f726c64', // need to be decoded
  public_key: '021c5096cb9a5c6b150b6fae93d0026c90482de3393c7f88d8b9c0cd11708a6cd3',
  type: 32768
}
```

### メッセージのデータ量の制限

メッセージデータは、最大`65533`までで、データサイズの制限を超過すると下記エラーが出る。

```
// error message
message payload is too large - encoded 81920 bytes, but maximum message payload is 65533 bytes
```

### 試す

**ピア間でのメッセージ送信**

条件: AliceとBob、BobとCharlieはそれぞれPeerで直接繋がっているが、AliceとCharlieはPeerで繋がっていない。

結果: AliceとBob間、BobとCharlie間のメッセージ送受信を行うことはできるが、AliceとCharilie間のメッセージの送受信を行うことはできない。

```
   (1)                        (1)                         (1)
+ ----- +                   + --- +                   + ------- +
| Alice | <----  peer ----> | Bob | <----  peer ----> | Charlie |
+ ----- +                   + --- +                   + ------- +
    |                          |                           |
    |                          |                           |
    + <- can send message - -> + <- can send message  - -> +
    |                          |                           |
    |                          |                           |
    + <- - - -- - - - cannot send message - -  - -- - - -> +
```

AliceからCharilieへメッセージを送った際のエラーメッセージ

```
err: Error: 5 NOT_FOUND: peer is not connected // Alice peers don't have Charilie.
```

**ピア間での疑似ブロードキャスト送信**

条件: AliceとBob、BobとCharlieはそれぞれPeerで直接繋がっているが、AliceとCharlieはPeerで繋がっていない。

結果: AliceとBobにメッセージを送信後に、メッセージイベントをサブスクライブしたBobがCharlieにメッセージを送信することができる。

```
   (1)                        (1)                         (1)
+ ----- +                   + --- +                   + ------- +
| Alice | <----  peer ----> | Bob | <----  peer ----> | Charlie |
+ ----- +                   + --- +                   + ------- +
    |                          |                           |
    |                          |                           |
    + <- can send message - -> + <- can send message  - -> +
    |                          |                           |
    |                          |                           |
    + <- - - -- - - - can send message - -  - - - - - - -> +
```

### 簡易的な実装

```javascript
import {
  AuthenticatedLnd,
  LndAuthenticationWithMacaroon,
  authenticatedLndGrpc,
  getWalletInfo,
  getPeers as getLightningPeers,
  GetPeersResult,
  sendMessageToPeer,
  subscribeToPeerMessages,
  LightningMessage,
} from "lightning";
import { config } from "../config";
import { ReceivedMessage } from "../interfaces";
import { logger } from "../lib/logger";
import { accessDatabase } from '../lib/database';

const { lnd: lndConfig } = config;

class LndProvider {
  public _name: string;
  private _lnd: AuthenticatedLnd;
  private _publicKey: string;
  public _messages: ReceivedMessage[];

  constructor(name: string, socket: string, macaroon: string) {
    this._name = name;

    try {
      const { lnd } = authenticatedLndGrpc({
        cert: lndConfig.tlsCert,
        macaroon,
        socket,
      } as LndAuthenticationWithMacaroon);

      this._lnd = lnd;
    } catch (error) {
      const { message } = <Error>error;

      logger.error(`cannot initialize lnd ${message}`);
    }
  }

  async getPublicKey(): Promise<string> {
    if (this._publicKey) return this._publicKey;

    const publicKey = (await getWalletInfo({ lnd: this._lnd })).public_key;
    this._publicKey = publicKey;

    return publicKey;
  }

  async getPeers(): Promise<GetPeersResult> {
    return await getLightningPeers({ lnd: this._lnd });
  }

  // Peer means be just visible target.
  sendMessage(public_key: string, message: string): void {
    try {
      sendMessageToPeer(
        {
          lnd: this._lnd,
          message: Buffer.from(message).toString("hex"),
          public_key,
        },
        (res) => {
          // you don't have any notification from subscriber
          console.log("res", res);
        },
      );
    } catch (error) {
      const { message } = <Error>error;

      logger.error(`cannot send message ${message}`);
    }
  }

  /*
  export type LightningMessage = {
    // Message Hex String
    message: string;
    // To Peer Public Key Hex String
    public_key: string;
    // Message Type Number
    type?: number;
  };
  */
  subscribeMessage(): void {
    const sub = subscribeToPeerMessages({ lnd: this._lnd });

    sub.on("message_received", async (received: LightningMessage): Promise<void> => {
      console.log(`${this._name} received message`, received);

      try {
        const rawMsg = Buffer.from(received.message, "hex").toString();
        const parsed = JSON.parse(rawMsg) as ReceivedMessage;
        console.log("parsed message is ", parsed);

        await accessDatabase();

        if (parsed.broadcast) {
          await this.broadcastMessage(received, rawMsg);
        }
        
        this._messages.push(parsed)
      } catch (error) {
        console.error(error);
      }
    });
  }

  async broadcastMessage(received: LightningMessage, rawMsg: string): Promise<void> {
    try {
      const peers = await this.getPeers();
      const filterPeers = peers.peers.filter((peer) => peer.public_key != received.public_key);

      filterPeers.map((peer) => {
        this.sendMessage(peer.public_key, rawMsg);
      });
    } catch (error) {
      console.error(error);
    }
  }
}

export default LndProvider;
```

### Bash

```shell
docker run -it -v "$PWD":/usr/local/ --rm busybox
base64 -w0 /usr/local/tls.cert
base64 -w0 /usr/local/admin.macaroon
```

### 参考資料

- [lightning.proto](https://github.com/lightningnetwork/lnd/blob/master/lnrpc/lightning.proto#L561)
- [c-lightning sencustommsg](https://lightning.readthedocs.io/lightning-sendcustommsg.7.html)
- [lncli/cmd_custom](https://github.com/lightningnetwork/lnd/blob/e6c65f1cbd5a7f3f1aed7b5ef7720a7112156d78/cmd/lncli/cmd_custom.go)

## Umbrel

```bash
# get public key
docker exec -it lnd lincli getinfo

# error lightninglabs/lnd:v0.13.3-betaだとサポートエラーになる
SendMessageToPeerMethodNotSupported
```

## TODO

- peerが切れたときの対応を行う。
- 外部ipを開放する必要がある。
- umbrelの0.13.3-betaだと駄目。
