ln-service 59.3.7 → 59.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Versions
2
2
 
3
+ ## 59.4.0
4
+
5
+ - `sendMessage`: Add method to send a network onion message
6
+ - `subscribeToMessages`: Add method to listen to network onion messages
7
+
3
8
  ## 59.3.7
4
9
 
5
10
  - Add support for LND 0.21.3
package/README.md CHANGED
@@ -257,6 +257,7 @@ for `unlocker` methods.
257
257
  - [restrictMacaroon](#restrictmacaroon) - Add limitations to a macaroon
258
258
  - [revokeAccess](#revokeaccess) - Revoke all access macaroons given to an id
259
259
  - [routeFromChannels](#routefromchannels) - Convert channel series to a route
260
+ - [sendMessage](#sendmessage) - Send a network onion message
260
261
  - [sendMessageToPeer](#sendmessagetopeer) - Send a custom message to a peer
261
262
  - [sendToChainAddress](#sendtochainaddress) - Send on-chain to an address
262
263
  - [sendToChainAddresses](#sendtochainaddresses) - Send on-chain to addresses
@@ -281,6 +282,7 @@ for `unlocker` methods.
281
282
  - [subscribeToGraph](#subscribetograph) - Subscribe to network graph updates
282
283
  - [subscribeToInvoice](#subscribetoinvoice) - Subscribe to invoice updates
283
284
  - [subscribeToInvoices](#subscribetoinvoices) - Subscribe to all invoices
285
+ - [subscribeToMessages](#subscribetomessages) - Subscribe to network messages
284
286
  - [subscribeToOpenRequests](#subscribetoopenrequests) - Approve open requests
285
287
  - [subscribeToPastPayment](#subscribetopastpayment) - Subscribe to a payment
286
288
  - [subscribeToPastPayments](#subscribetopastpayments) - Subscribe to all sent
@@ -5140,6 +5142,53 @@ const res = routeFromChannels({channels, destination, height, mtokens});
5140
5142
  const {route} = res;
5141
5143
  ```
5142
5144
 
5145
+ ### sendMessage
5146
+
5147
+ Send a generic message over the network
5148
+
5149
+ The receiver of the message needs to first publish an inbound path to them,
5150
+ this is supplied via `inbound`. To send, add a series of relaying node ids
5151
+ that support message passing, to a landmark node provided by the receiver.
5152
+
5153
+ This method is not supported in LND 0.20.4 and below
5154
+
5155
+ {
5156
+ inbound: [{
5157
+ encrypted_data: <Encrypted Data Hex String>
5158
+ relay_key: <Blinded Relaying Public Key Into Destination Hex String>
5159
+ }]
5160
+ key: <Message Path Key Hex String>
5161
+ lnd: <Authenticated LND API Object>
5162
+ [message]: {
5163
+ type: <Message Payload Record Type Number String>
5164
+ value: <Message Payload Hex String>
5165
+ }
5166
+ outbound: [<Relaying Node Public Key Out of Source Hex String>]
5167
+ [reply]: [<Reply Path Relaying Node Public Key Back To Sender Hex String>]
5168
+ }
5169
+
5170
+ @returns via cbk or Promise
5171
+ {
5172
+ [reply]: <Reply Identifier Hex String>
5173
+ }
5174
+
5175
+ Example:
5176
+
5177
+ ```node
5178
+ const {sendMessage} = require('ln-service');
5179
+
5180
+ const {reply} = await sendMessage({
5181
+ lnd,
5182
+ inbound: blindedPath.path,
5183
+ key: blindedPath.key,
5184
+ outbound: [peer.public_key, blindedPath.introduction_node],
5185
+ message: {type, value},
5186
+ reply: [peer.public_key, own.public_key],
5187
+ });
5188
+
5189
+ // Wait for message reply on `reply` id
5190
+ ```
5191
+
5143
5192
  ### sendMessageToPeer
5144
5193
 
5145
5194
  Send a custom message to a connected peer
@@ -6165,6 +6214,67 @@ const sub = subscribeToInvoices({lnd});
6165
6214
  const [lastUpdatedInvoice] = await once(sub, 'invoice_updated');
6166
6215
  ```
6167
6216
 
6217
+ ### subscribeToMessages
6218
+
6219
+ Subscribe to received onion messages
6220
+
6221
+ Requires `offchain:read` permission
6222
+
6223
+ This method is not supported in LND 0.20.4 and below
6224
+
6225
+ {
6226
+ lnd: <Authenticated LND API Object>
6227
+ }
6228
+
6229
+ @returns
6230
+ <EventEmitter Object>
6231
+
6232
+ @event 'message_received'
6233
+ {
6234
+ encrypted: <Encrypted Data Hex String>
6235
+ key: <Path Key Hex String>
6236
+ [message]: {
6237
+ type: <Message Payload Record Type Number String>
6238
+ value: <Message Payload Hex String>
6239
+ }
6240
+ onion: <Onion Packet Hex String>
6241
+ [reply]: {
6242
+ inbound: [{
6243
+ encrypted_data: <Encrypted Data Hex String>
6244
+ relay_key: <Blinded Relay Key Hex String>
6245
+ }]
6246
+ [introduction_edge]: <Introduction Node Edge Format Channel Id String>
6247
+ [introduction_node]: <Introduction Node Public Key Hex String>
6248
+ key: <Path Key Hex String>
6249
+ }
6250
+ via: <Message Received Via Peer with Id Public Key Hex String>
6251
+ }
6252
+
6253
+ ```node
6254
+ const {decryptBlindedPath} = require('bolt04');
6255
+ const {diffieHellmanComputeSecret} = require('ln-service');
6256
+ const {subscribeToMessages} = require('ln-service');
6257
+
6258
+ const sub = subscribeToMessages({lnd});
6259
+
6260
+ sub.on('message_received', async ({custom, encrypted, key}) => {
6261
+ // The message id is encrypted
6262
+ const {secret} = await diffieHellmanComputeSecret({
6263
+ lnd,
6264
+ partner_public_key: key,
6265
+ });
6266
+
6267
+ const decrypted = decryptBlindedPath({encrypted, key, secret});
6268
+
6269
+ // The received id should be equal to the one given out
6270
+ if (decrypted.id !== blindedPath.id) {
6271
+ return;
6272
+ }
6273
+
6274
+ // Message to path received
6275
+ });
6276
+ ```
6277
+
6168
6278
  ### subscribeToOpenRequests
6169
6279
 
6170
6280
  Subscribe to inbound channel open requests
package/index.js CHANGED
@@ -112,6 +112,7 @@ const {requestChainFeeIncrease} = require('lightning');
112
112
  const {restrictMacaroon} = require('./macaroons');
113
113
  const {revokeAccess} = require('lightning');
114
114
  const {routeFromChannels} = require('bolt07');
115
+ const {sendMessage} = require('lightning');
115
116
  const {sendMessageToPeer} = require('lightning');
116
117
  const {sendToChainAddress} = require('lightning');
117
118
  const {sendToChainAddresses} = require('lightning');
@@ -134,6 +135,7 @@ const {subscribeToForwards} = require('lightning');
134
135
  const {subscribeToGraph} = require('lightning');
135
136
  const {subscribeToInvoice} = require('lightning');
136
137
  const {subscribeToInvoices} = require('lightning');
138
+ const {subscribeToMessages} = require('lightning');
137
139
  const {subscribeToOpenRequests} = require('lightning');
138
140
  const {subscribeToPastPayment} = require('lightning');
139
141
  const {subscribeToPastPayments} = require('lightning');
@@ -279,6 +281,7 @@ module.exports = {
279
281
  restrictMacaroon,
280
282
  revokeAccess,
281
283
  routeFromChannels,
284
+ sendMessage,
282
285
  sendMessageToPeer,
283
286
  sendToChainAddress,
284
287
  sendToChainAddresses,
@@ -301,6 +304,7 @@ module.exports = {
301
304
  subscribeToGraph,
302
305
  subscribeToInvoice,
303
306
  subscribeToInvoices,
307
+ subscribeToMessages,
304
308
  subscribeToOpenRequests,
305
309
  subscribeToPastPayment,
306
310
  subscribeToPastPayments,
package/package.json CHANGED
@@ -14,25 +14,23 @@
14
14
  "dependencies": {
15
15
  "bolt07": "2.0.0",
16
16
  "invoices": "6.1.4",
17
- "lightning": "12.2.11",
17
+ "lightning": "12.3.0",
18
18
  "macaroon": "3.0.4"
19
19
  },
20
20
  "description": "Interaction helper for your Lightning Network daemon",
21
21
  "devDependencies": {
22
- "@alexbosworth/blockchain": "4.7.2",
22
+ "@alexbosworth/blockchain": "4.8.0",
23
23
  "async": "3.2.6",
24
24
  "asyncjs-util": "1.2.12",
25
25
  "bip32": "5.0.1",
26
26
  "bip66": "2.0.0",
27
- "bn.js": "5.2.5",
28
- "bs58check": "4.0.0",
29
- "ecpair": "3.0.1",
30
- "ln-docker-daemons": "8.0.4",
27
+ "bolt04": "1.0.0",
28
+ "ecpair": "3.0.2",
29
+ "ln-docker-daemons": "8.0.5",
31
30
  "p2tr": "3.0.0",
32
31
  "portfinder": "1.0.38",
33
32
  "psbt": "6.0.3",
34
- "tiny-secp256k1": "2.2.4",
35
- "varuint-bitcoin": "2.0.0"
33
+ "tiny-secp256k1": "2.2.4"
36
34
  },
37
35
  "engines": {
38
36
  "node": ">=22"
@@ -63,5 +61,5 @@
63
61
  "integration-test-0.20.0": "DOCKER_LND_VERSION=v0.20.0-beta npm run test",
64
62
  "test": "echo $DOCKER_LND_VERSION && node test/runner"
65
63
  },
66
- "version": "59.3.7"
64
+ "version": "59.4.0"
67
65
  }
@@ -0,0 +1,180 @@
1
+ const {equal} = require('node:assert').strict;
2
+ const test = require('node:test');
3
+
4
+ const asyncRetry = require('async/retry');
5
+ const {blindedPathFromHops} = require('bolt04');
6
+ const {decryptBlindedPath} = require('bolt04');
7
+ const {setupChannel} = require('ln-docker-daemons');
8
+ const {spawnLightningCluster} = require('ln-docker-daemons');
9
+
10
+ const {diffieHellmanComputeSecret} = require('./../../');
11
+ const {sendMessage} = require('./../../');
12
+ const {subscribeToMessages} = require('./../../');
13
+
14
+ const capacity = 1e6;
15
+ const interval = 10;
16
+ const size = 5;
17
+ const times = 1000;
18
+ const type = '805805';
19
+ const value = Buffer.from('hello world').toString('hex');
20
+
21
+ // Subscribing to messages should surface message events
22
+ test('Subscribe to messages', async t => {
23
+ const {kill, nodes} = await spawnLightningCluster({size});
24
+ const messages = {};
25
+
26
+ const [control, alice, bob, charlie, dave] = nodes;
27
+
28
+ const {generate, lnd} = control;
29
+
30
+ try {
31
+ // Dave creates a blinded path into their node, given out of band to CTRL
32
+ const blindedPath = (() => {
33
+ const hops = [bob.id, charlie.id, dave.id];
34
+
35
+ const {id, key, path} = blindedPathFromHops({hops});
36
+
37
+ return {id, key, path, introduction_node: bob.id};
38
+ })();
39
+
40
+ // Control listens for a reply to their message
41
+ const subReply = subscribeToMessages({lnd});
42
+
43
+ subReply.on('message_received', async ({encrypted, key, message})=> {
44
+ // The message id is encrypted
45
+ const {secret} = await diffieHellmanComputeSecret({
46
+ lnd,
47
+ partner_public_key: key,
48
+ });
49
+
50
+ const decrypted = decryptBlindedPath({encrypted, key, secret});
51
+
52
+ messages.reply = {received_value: message.value, reply_id: decrypted.id};
53
+
54
+ return;
55
+ });
56
+
57
+ // Dave listens for a message to be received on their blinded path
58
+ const sub = subscribeToMessages({lnd: dave.lnd});
59
+
60
+ sub.on('message_received', async ({encrypted, key, message, reply}) => {
61
+ const {secret} = await diffieHellmanComputeSecret({
62
+ lnd: dave.lnd,
63
+ partner_public_key: key,
64
+ });
65
+
66
+ const decrypted = decryptBlindedPath({key, encrypted, secret});
67
+
68
+ // The received id should be equal to the one given out
69
+ if (decrypted.id !== blindedPath.id) {
70
+ return;
71
+ }
72
+
73
+ await sendMessage({
74
+ lnd: dave.lnd,
75
+ inbound: reply.inbound.map(hop => ({
76
+ encrypted_data: hop.encrypted_data,
77
+ relay_key: hop.relay_key,
78
+ })),
79
+ key: reply.key,
80
+ message: {type, value: message.value + message.value},
81
+ outbound: [charlie.id, bob.id, reply.introduction_node],
82
+ });
83
+ });
84
+
85
+ // Make channels between all nodes so they will relay messages
86
+ {
87
+ await asyncRetry({interval, times}, async () => {
88
+ await generate({});
89
+
90
+ await setupChannel({
91
+ capacity,
92
+ generate,
93
+ lnd,
94
+ to: alice,
95
+ });
96
+ });
97
+
98
+ // Make sure that send message is supported by LND
99
+ try {
100
+ await sendMessage({
101
+ lnd,
102
+ inbound: blindedPath.path,
103
+ key: blindedPath.key,
104
+ message: {type, value},
105
+ outbound: [alice.id, blindedPath.introduction_node],
106
+ reply: [alice.id, control.id],
107
+ });
108
+ } catch (err) {
109
+ await kill({});
110
+
111
+ const [code, message] = err;
112
+
113
+ equal(code, 501, 'Method not supported');
114
+ equal(message, 'SendOnionMessageMethodUnsupported', 'Unknown method');
115
+
116
+ return;
117
+ }
118
+
119
+ await asyncRetry({interval, times}, async () => {
120
+ await alice.generate({});
121
+
122
+ await setupChannel({
123
+ capacity,
124
+ generate: alice.generate,
125
+ lnd: alice.lnd,
126
+ to: bob,
127
+ });
128
+ });
129
+
130
+ await asyncRetry({interval, times}, async () => {
131
+ await bob.generate({});
132
+
133
+ await setupChannel({
134
+ capacity,
135
+ generate: bob.generate,
136
+ lnd: bob.lnd,
137
+ to: charlie,
138
+ });
139
+ });
140
+
141
+ await asyncRetry({interval, times}, async () => {
142
+ await charlie.generate({});
143
+
144
+ await setupChannel({
145
+ capacity,
146
+ generate: charlie.generate,
147
+ lnd: charlie.lnd,
148
+ to: dave,
149
+ });
150
+ });
151
+ }
152
+
153
+ const {reply} = await sendMessage({
154
+ lnd,
155
+ inbound: blindedPath.path,
156
+ key: blindedPath.key,
157
+ message: {type, value},
158
+ outbound: [alice.id, blindedPath.introduction_node],
159
+ reply: [alice.id, control.id],
160
+ });
161
+
162
+ // Wait for the message send and response
163
+ await asyncRetry({interval, times}, async () => {
164
+ if (!messages.reply) {
165
+ throw new Error('ExpectedMessageReplyReceived');
166
+ }
167
+
168
+ return;
169
+ });
170
+
171
+ equal(messages.reply.reply_id, reply, 'Got correct message reply id');
172
+ equal(messages.reply.received_value, value + value, 'Got message reply');
173
+
174
+ await kill({});
175
+ } catch (err) {
176
+ await kill({});
177
+
178
+ throw err;
179
+ }
180
+ });
@@ -3,7 +3,7 @@ const {equal} = require('node:assert').strict;
3
3
  const test = require('node:test');
4
4
 
5
5
  const bip32 = require('bip32');
6
- const bs58check = require('bs58check').default;
6
+ const {decodeBase58} = require('@alexbosworth/blockchain');
7
7
  const ecc = require('tiny-secp256k1')
8
8
  const {spawnLightningCluster} = require('ln-docker-daemons');
9
9
 
@@ -60,10 +60,12 @@ test(`Get master public keys`, async () => {
60
60
  const masterIdentityKey = keys.find(n => n.named === identityKeyName);
61
61
 
62
62
  // Convert the key from base58 into its raw form
63
- const rawKey = bs58check.decode(masterIdentityKey.extended_public_key);
63
+ const {payload} = decodeBase58({
64
+ encoded: masterIdentityKey.extended_public_key,
65
+ });
64
66
 
65
- const chainCode = Buffer.from(chainCodeFromMasterPublicKey(rawKey));
66
- const publicKey = Buffer.from(publicKeyFromMasterPublicKey(rawKey));
67
+ const chainCode = chainCodeFromMasterPublicKey(payload);
68
+ const publicKey = publicKeyFromMasterPublicKey(payload);
67
69
 
68
70
  // Make a bip32 object to derive from
69
71
  const masterKey = fromPublicKey(publicKey, chainCode);
@@ -1,32 +0,0 @@
1
- const BN = require('bn.js');
2
-
3
- const {encode} = require('varuint-bitcoin');
4
-
5
- const decBase = 10;
6
- const {isBuffer} = Buffer;
7
-
8
- /** Convert an array of script buffer elements to a fully formed script
9
-
10
- @param
11
- [<Script Element Buffer>, <Script OP_CODE Decimal Number>]
12
-
13
- @throws
14
- <Error> when a script element length exceeds maximum
15
-
16
- @returns
17
- <Script Hex>
18
- */
19
- module.exports = scriptElements => {
20
- // Convert numbers to buffers and hex data to pushdata
21
- const fullScript = scriptElements
22
- .map(element => {
23
- if (isBuffer(element)) {
24
- return Buffer.concat([encode(element.length), element]);
25
- } else {
26
- return new BN(element, decBase).toArrayLike(Buffer);
27
- }
28
- })
29
- .reduce((element, script) => Buffer.concat([element, script]));
30
-
31
- return fullScript.toString('hex');
32
- };