paid-services 9.0.1 → 9.2.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.
Files changed (132) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/groups/assemble/coordinate_group.js +3 -9
  3. package/groups/assemble_channel_group.js +6 -8
  4. package/groups/coordinate_channel_group.js +1 -7
  5. package/groups/create_group_channel.js +4 -8
  6. package/groups/fanout/assemble_fanout_group.js +3 -6
  7. package/groups/fanout/coordinate_fanout.js +4 -9
  8. package/groups/fanout/create_group_fanout.js +1 -7
  9. package/groups/fanout/get_fanout_funding.js +2 -6
  10. package/groups/fanout/join_fanout.js +2 -7
  11. package/groups/funding/sign_and_fund_group_channel.js +2 -6
  12. package/groups/join_channel_group.js +3 -11
  13. package/groups/messages/decode_signed_funding.js +3 -5
  14. package/groups/p2p/register_fanout_proposal.js +3 -9
  15. package/groups/p2p/register_pending_open.js +3 -8
  16. package/index.js +8 -0
  17. package/network/create_network_path.js +122 -0
  18. package/network/encode_network_response.js +37 -0
  19. package/network/get_message_hops.js +130 -0
  20. package/network/get_path_id.js +61 -0
  21. package/network/get_path_outbound.js +82 -0
  22. package/network/get_relaying_route.js +100 -0
  23. package/network/index.js +15 -0
  24. package/network/make_network_request.js +152 -0
  25. package/network/parse_network_request.js +34 -0
  26. package/network/path_introduction.js +65 -0
  27. package/network/regular_reply_bytes.js +66 -0
  28. package/network/relays_as_hops.js +37 -0
  29. package/network/reply_introduction.js +44 -0
  30. package/network/request_network_reply.js +239 -0
  31. package/network/resolve_path_introductions.js +100 -0
  32. package/network/return_network_response.js +115 -0
  33. package/network/route_relays.js +44 -0
  34. package/network/service_network_messages.js +278 -0
  35. package/network/service_network_requests.js +180 -0
  36. package/offers/check_offer_invoice_reply.js +82 -0
  37. package/offers/constants.json +5 -0
  38. package/offers/get_offer_invoice.js +194 -0
  39. package/offers/index.js +5 -0
  40. package/offers/invoice_request_for_offer.js +147 -0
  41. package/offers/offer_invoice_failure.js +32 -0
  42. package/offers/pay_offer.js +101 -0
  43. package/offers/pay_offer_invoice.js +138 -0
  44. package/offers/random_secret.js +24 -0
  45. package/p2p/service_peer_requests.js +0 -3
  46. package/package.json +13 -8
  47. package/service_records/add_service_label.js +55 -0
  48. package/service_records/constants.json +21 -0
  49. package/service_records/decode_records.js +49 -0
  50. package/service_records/decode_service_label.js +79 -0
  51. package/service_records/decode_service_record.js +106 -0
  52. package/service_records/encode_service_label.js +71 -0
  53. package/service_records/encode_service_record.js +116 -0
  54. package/service_records/get_record_key.js +84 -0
  55. package/service_records/hex_as_list.js +60 -0
  56. package/service_records/hex_as_version.js +27 -0
  57. package/service_records/index.js +37 -0
  58. package/service_records/known_types.js +27 -0
  59. package/service_records/list_as_hex.js +34 -0
  60. package/service_records/number_as_truncated.js +39 -0
  61. package/service_records/open_sealed_data.js +50 -0
  62. package/service_records/read_record_key_index.js +95 -0
  63. package/service_records/read_service_label.js +62 -0
  64. package/service_records/record_key_for_secret.js +33 -0
  65. package/service_records/record_point.js +24 -0
  66. package/service_records/seal_key_for_salt.js +41 -0
  67. package/service_records/truncated_as_number.js +33 -0
  68. package/service_records/version_as_hex.js +28 -0
  69. package/service_records/without_record.js +43 -0
  70. package/support/add_offer_records.js +49 -0
  71. package/support/amounts_as_hex.js +35 -0
  72. package/support/bip353_name_as_hex.js +36 -0
  73. package/support/bitcoin_chains.json +7 -0
  74. package/support/call_or_throw.js +23 -0
  75. package/support/constants.json +72 -0
  76. package/support/create_menu_request.js +91 -0
  77. package/support/create_support_offer.js +365 -0
  78. package/support/decode_menu_entry.js +129 -0
  79. package/support/decode_safe_number.js +28 -0
  80. package/support/decode_suggested_amounts.js +63 -0
  81. package/support/decode_support_menu.js +168 -0
  82. package/support/decode_support_record.js +123 -0
  83. package/support/encode_menu_entry.js +128 -0
  84. package/support/encode_payer_offer.js +104 -0
  85. package/support/encode_suggested_amounts.js +56 -0
  86. package/support/encode_support_menu.js +132 -0
  87. package/support/encode_support_record.js +104 -0
  88. package/support/erroneous_request_field.js +27 -0
  89. package/support/erroneous_request_fields.json +48 -0
  90. package/support/get_support_menu.js +357 -0
  91. package/support/has_unknown_even_record.js +23 -0
  92. package/support/hex_as_amounts.js +34 -0
  93. package/support/hex_as_bip353_name.js +55 -0
  94. package/support/index.js +15 -0
  95. package/support/invoice_for_request.js +116 -0
  96. package/support/invoice_paths_to_fit.js +80 -0
  97. package/support/is_reply_too_large.js +24 -0
  98. package/support/is_support_offer.js +64 -0
  99. package/support/known_record_types.js +37 -0
  100. package/support/length_without_record.js +25 -0
  101. package/support/menu_id_for_pages.js +49 -0
  102. package/support/menu_offers.js +173 -0
  103. package/support/menu_pages.js +81 -0
  104. package/support/menu_signature_preimage.js +74 -0
  105. package/support/offer_bitcoin_network.js +45 -0
  106. package/support/offer_for_request.js +124 -0
  107. package/support/offer_records_for_payer.js +103 -0
  108. package/support/offer_suggested_amounts.js +75 -0
  109. package/support/offer_with_paths.js +53 -0
  110. package/support/open_support_note.js +81 -0
  111. package/support/payer_offer_args.js +39 -0
  112. package/support/payer_offer_for_request.js +171 -0
  113. package/support/payer_offer_signature_preimage.js +59 -0
  114. package/support/payer_offer_signer.js +95 -0
  115. package/support/payer_offer_types.js +21 -0
  116. package/support/read_menu_request.js +53 -0
  117. package/support/related_menu_levels.js +39 -0
  118. package/support/related_menus.js +171 -0
  119. package/support/reply_invoice_error.js +41 -0
  120. package/support/request_key.js +31 -0
  121. package/support/seal_support_note.js +63 -0
  122. package/support/service_support_offer.js +1032 -0
  123. package/support/shared_result.js +30 -0
  124. package/support/sign_invoice_for_request.js +125 -0
  125. package/support/sign_payer_offer.js +203 -0
  126. package/support/sign_support_menus.js +140 -0
  127. package/support/support_note_for_invoice.js +63 -0
  128. package/support/support_note_key.js +47 -0
  129. package/support/tlv_stream_records.js +26 -0
  130. package/support/unpaid_invoices.js +47 -0
  131. package/support/verify_menu_signature.js +44 -0
  132. package/support/verify_offer_reference.js +90 -0
@@ -0,0 +1,122 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {blindedPathFromHops} = require('bolt04');
3
+ const {getIdentity} = require('ln-service');
4
+ const {getPeers} = require('ln-service');
5
+ const {returnResult} = require('asyncjs-util');
6
+
7
+ const isHex = n => !(n.length % 2) && /^[0-9A-F]*$/i.test(n);
8
+ const isHexString = n => typeof n === 'string' && isHex(n);
9
+ const isLongPathId = n => n.length / 2 >= minPathIdBytes;
10
+ const isPublicKey = n => typeof n === 'string' && /^[0-9A-F]{66}$/i.test(n);
11
+ const minPathIdBytes = 16;
12
+
13
+ /** Create a blinded path to self that can be published to receive messages,
14
+ starting at a connected `introduction` peer, or at self
15
+
16
+ A given path `id` is at least 16 bytes from a secure random source, kept
17
+ private.
18
+
19
+ {
20
+ [id]: <Random Path Identifier Hex String>
21
+ [introduction]: <Introduction Connected Peer Public Key Hex String>
22
+ lnd: <Authenticated LND API Object>
23
+ }
24
+
25
+ @returns via cbk or Promise
26
+ {
27
+ id: <Path Identifier Hex String>
28
+ path: {
29
+ hops: [{
30
+ encrypted_data: <Encrypted Recipient Data Hex String>
31
+ relay_key: <Blinded Relaying Node Public Key Hex String>
32
+ }]
33
+ introduction_node: <Introduction Node Public Key Hex String>
34
+ key: <First Hop Path Key Public Key Hex String>
35
+ }
36
+ }
37
+ */
38
+ module.exports = ({id, introduction, lnd}, cbk) => {
39
+ return new Promise((resolve, reject) => {
40
+ return asyncAuto({
41
+ // Check arguments
42
+ validate: cbk => {
43
+ if (!!id && !isHexString(id)) {
44
+ return cbk([400, 'ExpectedHexEncodedPathIdToCreateNetworkPath']);
45
+ }
46
+
47
+ // A path id is at least 16 random bytes, so it cannot be guessed
48
+ if (!!id && !isLongPathId(id)) {
49
+ return cbk([400, 'ExpectedLongerPathIdToCreateNetworkPath']);
50
+ }
51
+
52
+ if (!!introduction && !isPublicKey(introduction)) {
53
+ return cbk([400, 'ExpectedIntroductionPublicKeyToCreateNetworkPath']);
54
+ }
55
+
56
+ if (!lnd) {
57
+ return cbk([400, 'ExpectedLndToCreateNetworkPath']);
58
+ }
59
+
60
+ return cbk();
61
+ },
62
+
63
+ // Get the self identity key to use as the end of the path
64
+ getIdentity: ['validate', ({}, cbk) => getIdentity({lnd}, cbk)],
65
+
66
+ // Get the connected peers to confirm the introduction is connected
67
+ getPeers: ['validate', ({}, cbk) => {
68
+ // Exit early when there is no introduction peer
69
+ if (!introduction) {
70
+ return cbk();
71
+ }
72
+
73
+ return getPeers({lnd}, cbk);
74
+ }],
75
+
76
+ // Check the introduction is a connected peer, and get the path nodes
77
+ nodes: ['getIdentity', 'getPeers', ({getIdentity, getPeers}, cbk) => {
78
+ const identity = getIdentity.public_key;
79
+
80
+ // Exit early when the path starts and ends at self
81
+ if (!introduction) {
82
+ return cbk(null, {hops: [identity], introduction: identity});
83
+ }
84
+
85
+ // Node keys are compared in lowercase, as LND gives them
86
+ const key = introduction.toLowerCase();
87
+
88
+ if (key === identity) {
89
+ return cbk([400, 'ExpectedIntroductionPeerOtherThanSelfForPath']);
90
+ }
91
+
92
+ const peerKeys = getPeers.peers.map(n => n.public_key);
93
+
94
+ // The introduction node needs to be able to forward to self
95
+ if (!peerKeys.includes(key)) {
96
+ return cbk([503, 'ExpectedConnectedIntroductionPeerToCreatePath']);
97
+ }
98
+
99
+ return cbk(null, {hops: [key, identity], introduction: key});
100
+ }],
101
+
102
+ // Create the blinded path
103
+ create: ['nodes', ({nodes}, cbk) => {
104
+ try {
105
+ const blinded = blindedPathFromHops({id, hops: nodes.hops});
106
+
107
+ return cbk(null, {
108
+ id: blinded.id,
109
+ path: {
110
+ hops: blinded.path,
111
+ introduction_node: nodes.introduction,
112
+ key: blinded.key,
113
+ },
114
+ });
115
+ } catch (err) {
116
+ return cbk([400, 'FailedToCreateNetworkPath', {err}]);
117
+ }
118
+ }],
119
+ },
120
+ returnResult({reject, resolve, of: 'create'}, cbk));
121
+ });
122
+ };
@@ -0,0 +1,37 @@
1
+ const encodePeerResponse = require('./../p2p/encode_peer_response');
2
+
3
+ const defaultServerError = [500, 'InternalServiceError'];
4
+
5
+ /** Encode a response to a network request
6
+
7
+ When the response cannot be encoded, a generic server failure is encoded in
8
+ its place, and the encoding error is returned so that it can be reported
9
+
10
+ {
11
+ [failure]: [
12
+ <Failure Code Number>
13
+ <Failure Message String>
14
+ ]
15
+ id: <Request Id Hex String>
16
+ [records]: [{
17
+ type: <Type Number String>
18
+ value: <Value Hex String>
19
+ }]
20
+ }
21
+
22
+ @returns
23
+ {
24
+ [err]: [<Error Code Number>, <Error Message String>]
25
+ message: <Encoded Response Hex String>
26
+ }
27
+ */
28
+ module.exports = ({failure, id, records}) => {
29
+ try {
30
+ return {message: encodePeerResponse({failure, id, records}).message};
31
+ } catch (err) {
32
+ return {
33
+ err: [503, err.message],
34
+ message: encodePeerResponse({id, failure: defaultServerError}).message,
35
+ };
36
+ }
37
+ };
@@ -0,0 +1,130 @@
1
+ const asyncAuto = require('async/auto');
2
+ const asyncRetry = require('async/retry');
3
+ const {getIdentity} = require('ln-service');
4
+ const {getPeers} = require('ln-service');
5
+ const {returnResult} = require('asyncjs-util');
6
+
7
+ const getRelayingRoute = require('./get_relaying_route');
8
+
9
+ const hasBit = (list, bits) => !!(list || []).find(n => bits.includes(n.bit));
10
+ const isRetry = err => !!err && err[1] === 'RetryWithoutIgnoredNodes';
11
+ const maxAttempts = 10;
12
+ const onlyChannelsBits = [66, 67];
13
+
14
+ /** Get a series of nodes to relay a message to a destination
15
+
16
+ A connected destination is sent to directly, unless it signals feature bit
17
+ 66 or 67, `option_onion_messages_only_channels`, that it only takes
18
+ messages from peers with a channel. Otherwise the message goes back along a
19
+ payment route found from the destination to self, through nodes that relay
20
+ messages.
21
+
22
+ {
23
+ destination: <Destination Node Public Key Hex String>
24
+ lnd: <Authenticated LND API Object>
25
+ }
26
+
27
+ @returns via cbk or Promise
28
+ {
29
+ hops: [<Message Hop Public Key Hex String>]
30
+ }
31
+ */
32
+ module.exports = ({destination, lnd}, cbk) => {
33
+ return new Promise((resolve, reject) => {
34
+ return asyncAuto({
35
+ // Check arguments
36
+ validate: cbk => {
37
+ // The destination key is a string so that it can be compared
38
+ if (!destination || typeof destination !== 'string') {
39
+ return cbk([400, 'ExpectedDestinationToGetMessageHops']);
40
+ }
41
+
42
+ if (!lnd) {
43
+ return cbk([400, 'ExpectedLndToGetMessageHops']);
44
+ }
45
+
46
+ return cbk();
47
+ },
48
+
49
+ // Node keys are compared in lowercase, as LND gives them
50
+ key: ['validate', ({}, cbk) => cbk(null, destination.toLowerCase())],
51
+
52
+ // Get the connected peers
53
+ getPeers: ['validate', ({}, cbk) => getPeers({lnd}, cbk)],
54
+
55
+ // A connected destination is sent to directly unless it needs a channel
56
+ isDirect: ['getPeers', 'key', ({getPeers, key}, cbk) => {
57
+ const peer = getPeers.peers.find(n => n.public_key === key);
58
+
59
+ return cbk(null, !!peer && !hasBit(peer.features, onlyChannelsBits));
60
+ }],
61
+
62
+ // Get the identity key of self, which routes are found back to
63
+ getIdentity: ['isDirect', ({isDirect}, cbk) => {
64
+ // Exit early when the destination is sent to directly
65
+ if (isDirect) {
66
+ return cbk();
67
+ }
68
+
69
+ return getIdentity({lnd}, cbk);
70
+ }],
71
+
72
+ // Find a series of nodes to relay the message
73
+ hops: [
74
+ 'getIdentity',
75
+ 'getPeers',
76
+ 'isDirect',
77
+ 'key',
78
+ ({getIdentity, getPeers, isDirect, key}, cbk) =>
79
+ {
80
+ // Exit early when the destination is sent to directly
81
+ if (isDirect) {
82
+ return cbk(null, {hops: [key]});
83
+ }
84
+
85
+ const source = getIdentity.public_key;
86
+
87
+ if (key === source) {
88
+ return cbk([400, 'UnexpectedMessageDestinationIsSelf']);
89
+ }
90
+
91
+ const ignore = [];
92
+
93
+ // Find a route of nodes that relay messages, trying again without the
94
+ // nodes that do not
95
+ return asyncRetry({errorFilter: isRetry, times: maxAttempts}, cbk => {
96
+ return getRelayingRoute({
97
+ ignore,
98
+ lnd,
99
+ source,
100
+ destination: key,
101
+ peers: getPeers.peers,
102
+ },
103
+ (err, res) => {
104
+ if (!!err) {
105
+ return cbk(err);
106
+ }
107
+
108
+ // Exit early when the route has nodes to use
109
+ if (!!res.hops) {
110
+ return cbk(null, {hops: res.hops});
111
+ }
112
+
113
+ res.ignore.forEach(n => ignore.push(n));
114
+
115
+ return cbk([503, 'RetryWithoutIgnoredNodes']);
116
+ });
117
+ },
118
+ (err, res) => {
119
+ // Exit early when every attempt found nodes that do not relay
120
+ if (isRetry(err)) {
121
+ return cbk([503, 'FailedToFindMessageHopsToDestination']);
122
+ }
123
+
124
+ return cbk(err, res);
125
+ });
126
+ }],
127
+ },
128
+ returnResult({reject, resolve, of: 'hops'}, cbk));
129
+ });
130
+ };
@@ -0,0 +1,61 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {decryptBlindedPath} = require('bolt04');
3
+ const {diffieHellmanComputeSecret} = require('ln-service');
4
+ const {returnResult} = require('asyncjs-util');
5
+
6
+ /** Get the path id of a received message from its encrypted recipient data
7
+
8
+ {
9
+ encrypted: <Encrypted Recipient Data Hex String>
10
+ key: <Path Key Hex String>
11
+ lnd: <Authenticated LND API Object>
12
+ }
13
+
14
+ @returns via cbk or Promise
15
+ {
16
+ [id]: <Path Identifier Hex String>
17
+ }
18
+ */
19
+ module.exports = ({encrypted, key, lnd}, cbk) => {
20
+ return new Promise((resolve, reject) => {
21
+ return asyncAuto({
22
+ // Check arguments
23
+ validate: cbk => {
24
+ if (!encrypted) {
25
+ return cbk([400, 'ExpectedEncryptedRecipientDataToGetPathId']);
26
+ }
27
+
28
+ if (!key) {
29
+ return cbk([400, 'ExpectedPathKeyToGetPathId']);
30
+ }
31
+
32
+ if (!lnd) {
33
+ return cbk([400, 'ExpectedLndToGetPathId']);
34
+ }
35
+
36
+ return cbk();
37
+ },
38
+
39
+ // Compute the shared secret of the path key and the node identity key
40
+ getSecret: ['validate', ({}, cbk) => {
41
+ return diffieHellmanComputeSecret({lnd, partner_public_key: key}, cbk);
42
+ }],
43
+
44
+ // Decrypt the recipient data to get the path id
45
+ decrypt: ['getSecret', ({getSecret}, cbk) => {
46
+ try {
47
+ const {id} = decryptBlindedPath({
48
+ encrypted,
49
+ key,
50
+ secret: getSecret.secret,
51
+ });
52
+
53
+ return cbk(null, {id});
54
+ } catch (err) {
55
+ return cbk([503, 'FailedToDecryptRecipientDataForPathId', {err}]);
56
+ }
57
+ }],
58
+ },
59
+ returnResult({reject, resolve, of: 'decrypt'}, cbk));
60
+ });
61
+ };
@@ -0,0 +1,82 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {getChannel} = require('ln-service');
3
+ const {returnResult} = require('asyncjs-util');
4
+
5
+ const getMessageHops = require('./get_message_hops');
6
+ const pathIntroduction = require('./path_introduction');
7
+
8
+ const edgeChannel = edge => edge.split('x').slice(0, -1).join('x');
9
+
10
+ /** Get the outbound hops to reach the introduction node of a blinded path
11
+
12
+ When the introduction node is not a connected peer, the message is relayed
13
+ along a payment route through nodes that relay onion messages
14
+
15
+ {
16
+ lnd: <Authenticated LND API Object>
17
+ path: {
18
+ [introduction_edge]: <Introduction Node Edge Format Channel Id String>
19
+ [introduction_node]: <Introduction Node Public Key Hex String>
20
+ }
21
+ }
22
+
23
+ @returns via cbk or Promise
24
+ {
25
+ outbound: [<Relaying Node Public Key Hex String>]
26
+ }
27
+ */
28
+ module.exports = ({lnd, path}, cbk) => {
29
+ return new Promise((resolve, reject) => {
30
+ return asyncAuto({
31
+ // Check arguments
32
+ validate: cbk => {
33
+ if (!lnd) {
34
+ return cbk([400, 'ExpectedLndToGetPathOutbound']);
35
+ }
36
+
37
+ if (!path) {
38
+ return cbk([400, 'ExpectedBlindedPathToGetPathOutbound']);
39
+ }
40
+
41
+ return cbk();
42
+ },
43
+
44
+ // Get the channel of the introduction edge
45
+ getChannel: ['validate', ({}, cbk) => {
46
+ // Exit early when the introduction is a node and not an edge
47
+ if (!path.introduction_edge) {
48
+ return cbk();
49
+ }
50
+
51
+ const id = edgeChannel(path.introduction_edge);
52
+
53
+ return getChannel({id, lnd}, cbk);
54
+ }],
55
+
56
+ // Determine the introduction node of the path
57
+ introduction: ['getChannel', ({getChannel}, cbk) => {
58
+ try {
59
+ const {introduction} = pathIntroduction({
60
+ path,
61
+ channel: getChannel,
62
+ });
63
+
64
+ return cbk(null, introduction);
65
+ } catch (err) {
66
+ return cbk([400, err.message]);
67
+ }
68
+ }],
69
+
70
+ // Get the hops to relay the message to the introduction node
71
+ getHops: ['introduction', ({introduction}, cbk) => {
72
+ return getMessageHops({lnd, destination: introduction}, cbk);
73
+ }],
74
+
75
+ // The outbound hops end at the introduction node
76
+ outbound: ['getHops', ({getHops}, cbk) => {
77
+ return cbk(null, {outbound: getHops.hops});
78
+ }],
79
+ },
80
+ returnResult({reject, resolve, of: 'outbound'}, cbk));
81
+ });
82
+ };
@@ -0,0 +1,100 @@
1
+ const asyncAuto = require('async/auto');
2
+ const asyncMap = require('async/map');
3
+ const {getNode} = require('ln-service');
4
+ const {getRouteToDestination} = require('ln-service');
5
+ const {returnResult} = require('asyncjs-util');
6
+
7
+ const relaysAsHops = require('./relays_as_hops');
8
+ const routeRelays = require('./route_relays');
9
+
10
+ const tokens = 1;
11
+
12
+ /** Get a route of nodes that relay a message to a destination, from a
13
+ payment route found from the destination back to self
14
+
15
+ When the route cannot be used, the nodes to avoid in the next route are
16
+ given instead.
17
+
18
+ {
19
+ destination: <Destination Node Public Key Hex String>
20
+ ignore: [{
21
+ [from_public_key]: <Avoid Route From Node Public Key Hex String>
22
+ [to_public_key]: <Avoid Route To Node Public Key Hex String>
23
+ }]
24
+ lnd: <Authenticated LND API Object>
25
+ peers: [{
26
+ public_key: <Peer Public Key Hex String>
27
+ }]
28
+ source: <Self Public Key Hex String>
29
+ }
30
+
31
+ @returns via cbk or Promise
32
+ {
33
+ [hops]: [<Message Hop Public Key Hex String>]
34
+ [ignore]: [{
35
+ [from_public_key]: <Avoid Route From Node Public Key Hex String>
36
+ [to_public_key]: <Avoid Route To Node Public Key Hex String>
37
+ }]
38
+ }
39
+ */
40
+ module.exports = ({destination, ignore, lnd, peers, source}, cbk) => {
41
+ return new Promise((resolve, reject) => {
42
+ return asyncAuto({
43
+ // Find a route from the destination back to self
44
+ getRoute: cbk => {
45
+ return getRouteToDestination({
46
+ ignore,
47
+ lnd,
48
+ tokens,
49
+ destination: source,
50
+ is_ignoring_past_failures: true,
51
+ start: destination,
52
+ },
53
+ cbk);
54
+ },
55
+
56
+ // Get the nodes that the message goes through on the route
57
+ relays: ['getRoute', ({getRoute}, cbk) => {
58
+ // Exit early when there is no route
59
+ if (!getRoute.route) {
60
+ return cbk([503, 'FailedToFindMessageHopsToDestination']);
61
+ }
62
+
63
+ return cbk(null, routeRelays({
64
+ destination,
65
+ peers,
66
+ source,
67
+ route: getRoute.route,
68
+ }));
69
+ }],
70
+
71
+ // Get the features of each node, as no features when not known
72
+ getNodes: ['relays', ({relays}, cbk) => {
73
+ return asyncMap(relays.relays || [], (key, cbk) => {
74
+ return getNode({
75
+ lnd,
76
+ is_omitting_channels: true,
77
+ public_key: key,
78
+ },
79
+ (err, res) => cbk(null, !!err ? {features: []} : res));
80
+ },
81
+ cbk);
82
+ }],
83
+
84
+ // Check that every node on the route relays messages
85
+ route: ['getNodes', 'relays', ({getNodes, relays}, cbk) => {
86
+ // Exit early when the first hop is not a peer
87
+ if (!!relays.ignore) {
88
+ return cbk(null, {ignore: relays.ignore});
89
+ }
90
+
91
+ return cbk(null, relaysAsHops({
92
+ destination,
93
+ nodes: getNodes,
94
+ relays: relays.relays,
95
+ }));
96
+ }],
97
+ },
98
+ returnResult({reject, resolve, of: 'route'}, cbk));
99
+ });
100
+ };
@@ -0,0 +1,15 @@
1
+ const createNetworkPath = require('./create_network_path');
2
+ const makeNetworkRequest = require('./make_network_request');
3
+ const requestNetworkReply = require('./request_network_reply');
4
+ const resolvePathIntroductions = require('./resolve_path_introductions');
5
+ const serviceNetworkMessages = require('./service_network_messages');
6
+ const serviceNetworkRequests = require('./service_network_requests');
7
+
8
+ module.exports = {
9
+ createNetworkPath,
10
+ makeNetworkRequest,
11
+ requestNetworkReply,
12
+ resolvePathIntroductions,
13
+ serviceNetworkMessages,
14
+ serviceNetworkRequests,
15
+ };
@@ -0,0 +1,152 @@
1
+ const {randomBytes} = require('crypto');
2
+
3
+ const asyncAuto = require('async/auto');
4
+ const {returnResult} = require('asyncjs-util');
5
+
6
+ const encodePeerRequest = require('./../p2p/encode_peer_request');
7
+ const parseRequestMessage = require('./../p2p/parse_request_message');
8
+ const requestNetworkReply = require('./request_network_reply');
9
+
10
+ const {isArray} = Array;
11
+ const isPublicKey = n => typeof n === 'string' && /^[0-9A-F]{66}$/i.test(n);
12
+ const {isSafeInteger} = Number;
13
+ const isTimeout = n => isSafeInteger(n) && n > 0;
14
+ const bufferAsHex = buffer => Buffer.from(buffer).toString('hex');
15
+ const makeId = () => bufferAsHex(randomBytes(32));
16
+ const peerRequestType = 32768;
17
+ const typeNetworkRequest = '32769';
18
+
19
+ /** Make a network request over onion messages, sent over a blinded path of
20
+ the destination, with the response returned over a reply path to self
21
+
22
+ The request fails when no response arrives within the `timeout`.
23
+
24
+ {
25
+ lnd: <Authenticated LND API Object>
26
+ path: {
27
+ hops: [{
28
+ encrypted_data: <Encrypted Recipient Data Hex String>
29
+ relay_key: <Blinded Relaying Node Public Key Hex String>
30
+ }]
31
+ [introduction_edge]: <Introduction Node Edge Format Channel Id String>
32
+ [introduction_node]: <Introduction Node Public Key Hex String>
33
+ key: <First Hop Path Key Public Key Hex String>
34
+ }
35
+ [records]: [{
36
+ type: <Type Number String>
37
+ value: <Value Hex Encoded String>
38
+ }]
39
+ [timeout]: <Response Timeout Milliseconds Number> // Default: 30000
40
+ type: <Request Type Number String>
41
+ }
42
+
43
+ @throws error via cbk or Promise
44
+ [0, NetworkRequestTimeout]
45
+
46
+ @returns via cbk or Promise
47
+ {
48
+ [records]: [{
49
+ type: <Type Number String>
50
+ value: <Value Hex Encoded String>
51
+ }]
52
+ }
53
+ */
54
+ module.exports = ({lnd, path, records, timeout, type}, cbk) => {
55
+ return new Promise((resolve, reject) => {
56
+ return asyncAuto({
57
+ // Check arguments
58
+ validate: cbk => {
59
+ if (!lnd) {
60
+ return cbk([400, 'ExpectedLndToMakeNetworkRequest']);
61
+ }
62
+
63
+ if (!path) {
64
+ return cbk([400, 'ExpectedBlindedPathToMakeNetworkRequest']);
65
+ }
66
+
67
+ if (!isArray(path.hops) || !path.hops.length) {
68
+ return cbk([400, 'ExpectedBlindedPathHopsToMakeNetworkRequest']);
69
+ }
70
+
71
+ if (!path.introduction_edge && !path.introduction_node) {
72
+ return cbk([400, 'ExpectedPathIntroductionToMakeNetworkRequest']);
73
+ }
74
+
75
+ // A node introduction is looked up as a public key
76
+ if (!!path.introduction_node && !isPublicKey(path.introduction_node)) {
77
+ return cbk([400, 'ExpectedIntroductionKeyToMakeNetworkRequest']);
78
+ }
79
+
80
+ if (!!path.introduction_edge) {
81
+ if (typeof path.introduction_edge !== 'string') {
82
+ return cbk([400, 'ExpectedIntroductionEdgeToMakeNetworkRequest']);
83
+ }
84
+ }
85
+
86
+ if (!path.key) {
87
+ return cbk([400, 'ExpectedBlindedPathKeyToMakeNetworkRequest']);
88
+ }
89
+
90
+ if (timeout !== undefined && !isTimeout(timeout)) {
91
+ return cbk([400, 'ExpectedPositiveTimeoutToMakeNetworkRequest']);
92
+ }
93
+
94
+ if (!type) {
95
+ return cbk([400, 'ExpectedTypeToMakeNetworkRequest']);
96
+ }
97
+
98
+ return cbk();
99
+ },
100
+
101
+ // Generate an id for the request
102
+ id: ['validate', ({}, cbk) => cbk(null, makeId())],
103
+
104
+ // Encode the request message
105
+ message: ['id', ({id}, cbk) => {
106
+ try {
107
+ const {message} = encodePeerRequest({id, records, type});
108
+
109
+ return cbk(null, {type: typeNetworkRequest, value: message});
110
+ } catch (err) {
111
+ return cbk([400, err.message]);
112
+ }
113
+ }],
114
+
115
+ // Send the request and wait for the reply on its reply path
116
+ getReply: ['message', ({message}, cbk) => {
117
+ return requestNetworkReply({
118
+ lnd,
119
+ message,
120
+ path,
121
+ timeout,
122
+ types: [typeNetworkRequest],
123
+ },
124
+ cbk);
125
+ }],
126
+
127
+ // Read the response to the request
128
+ request: ['getReply', 'id', ({getReply, id}, cbk) => {
129
+ try {
130
+ const {response} = parseRequestMessage({
131
+ message: getReply.reply.value,
132
+ type: peerRequestType,
133
+ });
134
+
135
+ if (!response || response.id !== id) {
136
+ return cbk([503, 'ExpectedResponseToNetworkRequest']);
137
+ }
138
+
139
+ // Exit early when the server responds with a failure code
140
+ if (!!response.failure) {
141
+ return cbk(response.failure);
142
+ }
143
+
144
+ return cbk(null, {records: response.records});
145
+ } catch (err) {
146
+ return cbk([503, 'ExpectedResponseToNetworkRequest']);
147
+ }
148
+ }],
149
+ },
150
+ returnResult({reject, resolve, of: 'request'}, cbk));
151
+ });
152
+ };