# 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だと駄目。