paid-services 10.2.0 → 10.3.1
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 +5 -0
- package/network/constants.json +3 -1
- package/network/get_path_after_self.js +222 -0
- package/network/get_path_outbound.js +91 -7
- package/network/request_network_reply.js +8 -5
- package/network/return_network_response.js +33 -17
- package/network/service_network_messages.js +23 -14
- package/offers/get_offer_invoice.js +5 -5
- package/package.json +5 -6
- package/swaps/complete_off_to_on_swap.js +13 -27
- package/swaps/complete_on_to_off_swap.js +7 -17
- package/swaps/make_private_key.js +26 -0
- package/swaps/request_loop_out.js +1 -7
- package/swaps/start_off_to_on_swap.js +10 -8
- package/swaps/start_on_to_off_swap.js +12 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## Version 10.3.1
|
|
4
|
+
|
|
5
|
+
- `getOfferInvoice`: Add support for offer paths that start at the payer
|
|
6
|
+
- `serviceSupportOffer`: Add support for reply paths that start at the node
|
|
7
|
+
|
|
3
8
|
## Version 10.2.0
|
|
4
9
|
|
|
5
10
|
- `serviceSupportOffer`: Add `start` event with the support offer
|
package/network/constants.json
CHANGED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {decryptBlindedPath} = require('bolt04');
|
|
3
|
+
const {diffieHellmanComputeSecret} = require('ln-service');
|
|
4
|
+
const {getChannel} = require('ln-service');
|
|
5
|
+
const {getChannels} = require('ln-service');
|
|
6
|
+
const {returnResult} = require('asyncjs-util');
|
|
7
|
+
|
|
8
|
+
const {knownHopRecordTypes} = require('./constants');
|
|
9
|
+
const {typePathId} = require('./constants');
|
|
10
|
+
|
|
11
|
+
const hasPathId = records => records.some(n => n.type === typePathId);
|
|
12
|
+
const hopsAfterFirst = hops => hops.slice(1);
|
|
13
|
+
const idsOf = channel => [channel.id].concat(channel.other_ids);
|
|
14
|
+
const {isArray} = Array;
|
|
15
|
+
const isEven = type => !(BigInt(type) % BigInt(2));
|
|
16
|
+
const isKnown = record => knownHopRecordTypes.includes(record.type);
|
|
17
|
+
const isPublicKey = n => typeof n === 'string' && /^[0-9A-F]{66}$/i.test(n);
|
|
18
|
+
const isUnknownEven = record => !isKnown(record) && isEven(record.type);
|
|
19
|
+
|
|
20
|
+
/** Get the blinded path after self, for a blinded path that starts at self
|
|
21
|
+
|
|
22
|
+
Self reads its own hop as an introduction node would. The path goes on from
|
|
23
|
+
the next node, which the hop names, or which is the peer at the other end
|
|
24
|
+
of the channel that the hop names.
|
|
25
|
+
|
|
26
|
+
As BOLT 4 asks of a node that forwards, the hop of self is not used when it
|
|
27
|
+
has an unknown even record, when it has a path id, or when it allows
|
|
28
|
+
features, since no onion message features are defined.
|
|
29
|
+
|
|
30
|
+
{
|
|
31
|
+
hops: [{
|
|
32
|
+
encrypted_data: <Encrypted Recipient Data Hex String>
|
|
33
|
+
relay_key: <Blinded Relaying Node Public Key Hex String>
|
|
34
|
+
}]
|
|
35
|
+
identity: <Node Identity Public Key Hex String>
|
|
36
|
+
key: <First Hop Path Key Public Key Hex String>
|
|
37
|
+
lnd: <Authenticated LND API Object>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@returns via cbk or Promise
|
|
41
|
+
{
|
|
42
|
+
hops: [{
|
|
43
|
+
encrypted_data: <Encrypted Recipient Data Hex String>
|
|
44
|
+
relay_key: <Blinded Relaying Node Public Key Hex String>
|
|
45
|
+
}]
|
|
46
|
+
introduction_node: <Next Node Public Key Hex String>
|
|
47
|
+
key: <Next Hop Path Key Public Key Hex String>
|
|
48
|
+
}
|
|
49
|
+
*/
|
|
50
|
+
module.exports = (args, cbk) => {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
return asyncAuto({
|
|
53
|
+
// Check arguments
|
|
54
|
+
validate: cbk => {
|
|
55
|
+
if (!isArray(args.hops) || !args.hops.length) {
|
|
56
|
+
return cbk([400, 'ExpectedBlindedPathHopsToGetPathAfterSelf']);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!isPublicKey(args.identity)) {
|
|
60
|
+
return cbk([400, 'ExpectedIdentityPublicKeyToGetPathAfterSelf']);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!args.key) {
|
|
64
|
+
return cbk([400, 'ExpectedPathKeyToGetPathAfterSelf']);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!args.lnd) {
|
|
68
|
+
return cbk([400, 'ExpectedLndToGetPathAfterSelf']);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return cbk();
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
// Compute the shared secret of the path key and the node identity key
|
|
75
|
+
getSecret: ['validate', ({}, cbk) => {
|
|
76
|
+
return diffieHellmanComputeSecret({
|
|
77
|
+
lnd: args.lnd,
|
|
78
|
+
partner_public_key: args.key,
|
|
79
|
+
},
|
|
80
|
+
cbk);
|
|
81
|
+
}],
|
|
82
|
+
|
|
83
|
+
// Decrypt the data of the hop of self to read where the path goes next
|
|
84
|
+
decrypt: ['getSecret', ({getSecret}, cbk) => {
|
|
85
|
+
const [own] = args.hops;
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
return cbk(null, decryptBlindedPath({
|
|
89
|
+
encrypted: own.encrypted_data,
|
|
90
|
+
key: args.key,
|
|
91
|
+
secret: getSecret.secret,
|
|
92
|
+
}));
|
|
93
|
+
} catch (err) {
|
|
94
|
+
return cbk([503, 'FailedToDecryptOwnBlindedPathHop', {err}]);
|
|
95
|
+
}
|
|
96
|
+
}],
|
|
97
|
+
|
|
98
|
+
// Check the hop of self can be followed, as a forwarding node would
|
|
99
|
+
check: ['decrypt', ({decrypt}, cbk) => {
|
|
100
|
+
// An unknown even record cannot be ignored, so the hop cannot be used
|
|
101
|
+
if (decrypt.records.some(isUnknownEven)) {
|
|
102
|
+
return cbk([
|
|
103
|
+
400,
|
|
104
|
+
'UnexpectedUnknownRequiredRecordInOwnBlindedPathHop',
|
|
105
|
+
]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// No features are defined for onion messages, so a feature bit in
|
|
109
|
+
// the allowed features is an unknown feature, and the hop is not used
|
|
110
|
+
if (!!decrypt.features.length) {
|
|
111
|
+
return cbk([400, 'UnsupportedFeatureInOwnBlindedPathHop']);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// A hop with no next node or channel is the final hop of the path
|
|
115
|
+
if (!decrypt.next_node_id && !decrypt.next_channel_id) {
|
|
116
|
+
return cbk([400, 'UnexpectedBlindedPathToSelf']);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// A hop that goes on to a next node is not one with a path id record,
|
|
120
|
+
// which is present even when the path id is empty
|
|
121
|
+
if (hasPathId(decrypt.records)) {
|
|
122
|
+
return cbk([400, 'UnexpectedPathIdInOwnBlindedPathHop']);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return cbk();
|
|
126
|
+
}],
|
|
127
|
+
|
|
128
|
+
// The hops after self, which the path goes on over
|
|
129
|
+
hops: ['check', ({}, cbk) => {
|
|
130
|
+
const hops = hopsAfterFirst(args.hops);
|
|
131
|
+
|
|
132
|
+
// The next node has a hop of its own to send to
|
|
133
|
+
if (!hops.length) {
|
|
134
|
+
return cbk([400, 'ExpectedHopsAfterSelfInBlindedPath']);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return cbk(null, hops);
|
|
138
|
+
}],
|
|
139
|
+
|
|
140
|
+
// Get the channels of self when the next node is named by a channel
|
|
141
|
+
getChannels: ['decrypt', 'hops', ({decrypt}, cbk) => {
|
|
142
|
+
// Exit early when the hop names the next node
|
|
143
|
+
if (!!decrypt.next_node_id) {
|
|
144
|
+
return cbk();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return getChannels({lnd: args.lnd}, cbk);
|
|
148
|
+
}],
|
|
149
|
+
|
|
150
|
+
// Find the next channel among the channels of self
|
|
151
|
+
channel: ['decrypt', 'getChannels', ({decrypt, getChannels}, cbk) => {
|
|
152
|
+
// Exit early when the hop names the next node
|
|
153
|
+
if (!getChannels) {
|
|
154
|
+
return cbk();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const id = decrypt.next_channel_id;
|
|
158
|
+
|
|
159
|
+
// A private channel can be named by one of its aliases
|
|
160
|
+
return cbk(null, getChannels.channels.find(n => idsOf(n).includes(id)));
|
|
161
|
+
}],
|
|
162
|
+
|
|
163
|
+
// Look for the next channel in the graph when self does not list it
|
|
164
|
+
getChannel: ['channel', 'decrypt', ({channel, decrypt}, cbk) => {
|
|
165
|
+
// Exit early when the next node is already known
|
|
166
|
+
if (!!decrypt.next_node_id || !!channel) {
|
|
167
|
+
return cbk();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return getChannel({
|
|
171
|
+
id: decrypt.next_channel_id,
|
|
172
|
+
lnd: args.lnd,
|
|
173
|
+
},
|
|
174
|
+
(err, res) => {
|
|
175
|
+
// Exit early when the channel is not in the graph
|
|
176
|
+
if (!!err) {
|
|
177
|
+
return cbk([503, 'FailedToFindNextPeerInBlindedPath']);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return cbk(null, res);
|
|
181
|
+
});
|
|
182
|
+
}],
|
|
183
|
+
|
|
184
|
+
// The next node is the one that the hop names, or is across the channel
|
|
185
|
+
next: [
|
|
186
|
+
'channel',
|
|
187
|
+
'decrypt',
|
|
188
|
+
'getChannel',
|
|
189
|
+
({channel, decrypt, getChannel}, cbk) =>
|
|
190
|
+
{
|
|
191
|
+
// Exit early when the hop names the next node
|
|
192
|
+
if (!!decrypt.next_node_id) {
|
|
193
|
+
return cbk(null, decrypt.next_node_id);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Exit early when the channel is a channel of self
|
|
197
|
+
if (!!channel) {
|
|
198
|
+
return cbk(null, channel.partner_public_key);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const keys = getChannel.policies.map(n => n.public_key);
|
|
202
|
+
|
|
203
|
+
// The channel in the graph has to be one that self forwards over
|
|
204
|
+
if (!keys.includes(args.identity)) {
|
|
205
|
+
return cbk([503, 'FailedToFindNextPeerInBlindedPath']);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return cbk(null, keys.find(n => n !== args.identity));
|
|
209
|
+
}],
|
|
210
|
+
|
|
211
|
+
// The path goes on from the next node, with the next hop path key
|
|
212
|
+
path: ['decrypt', 'hops', 'next', ({decrypt, hops, next}, cbk) => {
|
|
213
|
+
return cbk(null, {
|
|
214
|
+
hops,
|
|
215
|
+
introduction_node: next,
|
|
216
|
+
key: decrypt.next_path_key,
|
|
217
|
+
});
|
|
218
|
+
}],
|
|
219
|
+
},
|
|
220
|
+
returnResult({reject, resolve, of: 'path'}, cbk));
|
|
221
|
+
});
|
|
222
|
+
};
|
|
@@ -1,28 +1,48 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
|
+
const asyncWhilst = require('async/whilst');
|
|
2
3
|
const {getChannel} = require('ln-service');
|
|
4
|
+
const {getIdentity} = require('ln-service');
|
|
3
5
|
const {returnResult} = require('asyncjs-util');
|
|
4
6
|
|
|
5
7
|
const getMessageHops = require('./get_message_hops');
|
|
8
|
+
const getPathAfterSelf = require('./get_path_after_self');
|
|
6
9
|
const pathIntroduction = require('./path_introduction');
|
|
7
10
|
|
|
8
11
|
const edgeChannel = edge => edge.split('x').slice(0, -1).join('x');
|
|
12
|
+
const {isArray} = Array;
|
|
13
|
+
const isPublicKey = n => typeof n === 'string' && /^[0-9A-F]{66}$/i.test(n);
|
|
9
14
|
|
|
10
|
-
/** Get the outbound hops to reach the introduction node of a blinded path
|
|
15
|
+
/** Get the outbound hops to reach the introduction node of a blinded path,
|
|
16
|
+
with the hops and the path key to send over
|
|
11
17
|
|
|
12
18
|
When the introduction node is not a connected peer, the message is relayed
|
|
13
19
|
along a payment route through nodes that relay onion messages
|
|
14
20
|
|
|
21
|
+
A path that starts at self is sent from the next node of the path: self
|
|
22
|
+
reads its own hops, as an introduction node would, and sends over the hops
|
|
23
|
+
after them. A path that does not start at self is sent over as it is.
|
|
24
|
+
|
|
15
25
|
{
|
|
16
26
|
[is_expecting_reply]: <Reply Comes Back Along the Same Nodes Bool>
|
|
17
27
|
lnd: <Authenticated LND API Object>
|
|
18
28
|
path: {
|
|
29
|
+
hops: [{
|
|
30
|
+
encrypted_data: <Encrypted Recipient Data Hex String>
|
|
31
|
+
relay_key: <Blinded Relaying Node Public Key Hex String>
|
|
32
|
+
}]
|
|
19
33
|
[introduction_edge]: <Introduction Node Edge Format Channel Id String>
|
|
20
34
|
[introduction_node]: <Introduction Node Public Key Hex String>
|
|
35
|
+
key: <First Hop Path Key Public Key Hex String>
|
|
21
36
|
}
|
|
22
37
|
}
|
|
23
38
|
|
|
24
39
|
@returns via cbk or Promise
|
|
25
40
|
{
|
|
41
|
+
hops: [{
|
|
42
|
+
encrypted_data: <Encrypted Recipient Data Hex String>
|
|
43
|
+
relay_key: <Blinded Relaying Node Public Key Hex String>
|
|
44
|
+
}]
|
|
45
|
+
key: <First Hop Path Key Public Key Hex String>
|
|
26
46
|
outbound: [<Relaying Node Public Key Hex String>]
|
|
27
47
|
}
|
|
28
48
|
*/
|
|
@@ -39,6 +59,21 @@ module.exports = (args, cbk) => {
|
|
|
39
59
|
return cbk([400, 'ExpectedBlindedPathToGetPathOutbound']);
|
|
40
60
|
}
|
|
41
61
|
|
|
62
|
+
if (!isArray(args.path.hops) || !args.path.hops.length) {
|
|
63
|
+
return cbk([400, 'ExpectedBlindedPathHopsToGetPathOutbound']);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// A node introduction is compared to self as a public key
|
|
67
|
+
if (!!args.path.introduction_node) {
|
|
68
|
+
if (!isPublicKey(args.path.introduction_node)) {
|
|
69
|
+
return cbk([400, 'ExpectedIntroductionNodeKeyToGetPathOutbound']);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!args.path.key) {
|
|
74
|
+
return cbk([400, 'ExpectedBlindedPathKeyToGetPathOutbound']);
|
|
75
|
+
}
|
|
76
|
+
|
|
42
77
|
return cbk();
|
|
43
78
|
},
|
|
44
79
|
|
|
@@ -54,6 +89,11 @@ module.exports = (args, cbk) => {
|
|
|
54
89
|
return getChannel({id, lnd: args.lnd}, cbk);
|
|
55
90
|
}],
|
|
56
91
|
|
|
92
|
+
// Get the identity key of self, which the path can start at
|
|
93
|
+
getIdentity: ['validate', ({}, cbk) => {
|
|
94
|
+
return getIdentity({lnd: args.lnd}, cbk);
|
|
95
|
+
}],
|
|
96
|
+
|
|
57
97
|
// Determine the introduction node of the path
|
|
58
98
|
introduction: ['getChannel', ({getChannel}, cbk) => {
|
|
59
99
|
try {
|
|
@@ -62,25 +102,69 @@ module.exports = (args, cbk) => {
|
|
|
62
102
|
path: args.path,
|
|
63
103
|
});
|
|
64
104
|
|
|
65
|
-
|
|
105
|
+
// Node keys are compared in lowercase, as LND gives them
|
|
106
|
+
return cbk(null, introduction.toLowerCase());
|
|
66
107
|
} catch (err) {
|
|
67
108
|
return cbk([400, err.message]);
|
|
68
109
|
}
|
|
69
110
|
}],
|
|
70
111
|
|
|
112
|
+
// Step past the hops of self while the path starts at self
|
|
113
|
+
stepped: [
|
|
114
|
+
'getIdentity',
|
|
115
|
+
'introduction',
|
|
116
|
+
({getIdentity, introduction}, cbk) =>
|
|
117
|
+
{
|
|
118
|
+
const identity = getIdentity.public_key;
|
|
119
|
+
|
|
120
|
+
let path = {
|
|
121
|
+
hops: args.path.hops,
|
|
122
|
+
introduction_node: introduction,
|
|
123
|
+
key: args.path.key,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Self reads its own hop as an introduction node would, and the path
|
|
127
|
+
// goes on from the next node, which can be self again
|
|
128
|
+
return asyncWhilst(
|
|
129
|
+
cbk => cbk(null, path.introduction_node === identity),
|
|
130
|
+
cbk => {
|
|
131
|
+
return getPathAfterSelf({
|
|
132
|
+
identity,
|
|
133
|
+
hops: path.hops,
|
|
134
|
+
key: path.key,
|
|
135
|
+
lnd: args.lnd,
|
|
136
|
+
},
|
|
137
|
+
(err, res) => {
|
|
138
|
+
if (!!err) {
|
|
139
|
+
return cbk(err);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
path = res;
|
|
143
|
+
|
|
144
|
+
return cbk();
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
err => !!err ? cbk(err) : cbk(null, path)
|
|
148
|
+
);
|
|
149
|
+
}],
|
|
150
|
+
|
|
71
151
|
// Get the hops to relay the message to the introduction node
|
|
72
|
-
getHops: ['
|
|
152
|
+
getHops: ['stepped', ({stepped}, cbk) => {
|
|
73
153
|
return getMessageHops({
|
|
74
|
-
destination:
|
|
154
|
+
destination: stepped.introduction_node,
|
|
75
155
|
is_expecting_reply: args.is_expecting_reply,
|
|
76
156
|
lnd: args.lnd,
|
|
77
157
|
},
|
|
78
158
|
cbk);
|
|
79
159
|
}],
|
|
80
160
|
|
|
81
|
-
// The outbound hops end at the introduction node
|
|
82
|
-
outbound: ['getHops', ({getHops}, cbk) => {
|
|
83
|
-
return cbk(null, {
|
|
161
|
+
// The outbound hops end at the introduction node of the path to send
|
|
162
|
+
outbound: ['getHops', 'stepped', ({getHops, stepped}, cbk) => {
|
|
163
|
+
return cbk(null, {
|
|
164
|
+
hops: stepped.hops,
|
|
165
|
+
key: stepped.key,
|
|
166
|
+
outbound: getHops.hops,
|
|
167
|
+
});
|
|
84
168
|
}],
|
|
85
169
|
},
|
|
86
170
|
returnResult({reject, resolve, of: 'outbound'}, cbk));
|
|
@@ -27,6 +27,9 @@ const isTimeout = n => isSafeInteger(n) && n > 0;
|
|
|
27
27
|
than `max_reply_hops` keeps the hops nearest self, and the recipient routes
|
|
28
28
|
to its start.
|
|
29
29
|
|
|
30
|
+
A path that starts at self is sent from the next node of the path, and the
|
|
31
|
+
reply path goes back from there.
|
|
32
|
+
|
|
30
33
|
{
|
|
31
34
|
lnd: <Authenticated LND API Object>
|
|
32
35
|
[max_reply_hops]: <Maximum Reply Path Hops Count Number>
|
|
@@ -135,9 +138,9 @@ module.exports = (args, cbk) => {
|
|
|
135
138
|
{
|
|
136
139
|
const {outbound} = getOutbound;
|
|
137
140
|
|
|
138
|
-
// The outbound hops end at the introduction node of the
|
|
139
|
-
// is the recipient itself when
|
|
140
|
-
const isRecipient =
|
|
141
|
+
// The outbound hops end at the introduction node of the hops that are
|
|
142
|
+
// sent, which is the recipient itself when there is a single hop
|
|
143
|
+
const isRecipient = getOutbound.hops.length === 1;
|
|
141
144
|
|
|
142
145
|
const relays = !isRecipient ? outbound : outbound.slice(0, -1);
|
|
143
146
|
|
|
@@ -258,8 +261,8 @@ module.exports = (args, cbk) => {
|
|
|
258
261
|
|
|
259
262
|
// Send the message with a reply path back the way it goes
|
|
260
263
|
const sending = sendMessage({
|
|
261
|
-
inbound:
|
|
262
|
-
key:
|
|
264
|
+
inbound: getOutbound.hops,
|
|
265
|
+
key: getOutbound.key,
|
|
263
266
|
lnd: args.lnd,
|
|
264
267
|
message: args.message,
|
|
265
268
|
outbound: getOutbound.outbound,
|
|
@@ -14,8 +14,8 @@ const largeOnionBytes = 32834;
|
|
|
14
14
|
|
|
15
15
|
With `is_regular`, the response is only sent when it fits in an onion
|
|
16
16
|
message of the regular size. Otherwise it has to fit in a large one. A
|
|
17
|
-
response that does not fit is a 413 error. Give the `
|
|
18
|
-
|
|
17
|
+
response that does not fit is a 413 error. Give the `route` to the reply
|
|
18
|
+
path when it is known, as found by `getPathOutbound`.
|
|
19
19
|
|
|
20
20
|
{
|
|
21
21
|
[is_regular]: <Only Send In An Onion Message Of The Regular Size Bool>
|
|
@@ -33,7 +33,14 @@ const largeOnionBytes = 32834;
|
|
|
33
33
|
[introduction_node]: <Introduction Node Public Key Hex String>
|
|
34
34
|
key: <Path Key Hex String>
|
|
35
35
|
}
|
|
36
|
-
[
|
|
36
|
+
[route]: {
|
|
37
|
+
hops: [{
|
|
38
|
+
encrypted_data: <Encrypted Data Hex String>
|
|
39
|
+
relay_key: <Blinded Relay Key Hex String>
|
|
40
|
+
}]
|
|
41
|
+
key: <Path Key Hex String>
|
|
42
|
+
outbound: [<Relaying Node Public Key Hex String>]
|
|
43
|
+
}
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
@returns via cbk or Promise
|
|
@@ -58,23 +65,32 @@ module.exports = (args, cbk) => {
|
|
|
58
65
|
return cbk();
|
|
59
66
|
},
|
|
60
67
|
|
|
61
|
-
// Determine the
|
|
62
|
-
|
|
63
|
-
// Exit early when the
|
|
64
|
-
if (!!args.
|
|
65
|
-
return cbk(null,
|
|
68
|
+
// Determine the route to the reply path: the hops and key to send over
|
|
69
|
+
getRoute: ['validate', ({}, cbk) => {
|
|
70
|
+
// Exit early when the route is already known
|
|
71
|
+
if (!!args.route) {
|
|
72
|
+
return cbk(null, args.route);
|
|
66
73
|
}
|
|
67
74
|
|
|
68
|
-
return getPathOutbound({
|
|
75
|
+
return getPathOutbound({
|
|
76
|
+
lnd: args.lnd,
|
|
77
|
+
path: {
|
|
78
|
+
hops: args.reply.inbound,
|
|
79
|
+
introduction_edge: args.reply.introduction_edge,
|
|
80
|
+
introduction_node: args.reply.introduction_node,
|
|
81
|
+
key: args.reply.key,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
cbk);
|
|
69
85
|
}],
|
|
70
86
|
|
|
71
87
|
// Check that the response fits in an onion message that can be sent
|
|
72
|
-
checkSize: ['
|
|
88
|
+
checkSize: ['getRoute', ({getRoute}, cbk) => {
|
|
73
89
|
try {
|
|
74
90
|
const {onion} = onionForPath({
|
|
75
|
-
inbound:
|
|
76
|
-
key:
|
|
77
|
-
outbound:
|
|
91
|
+
inbound: getRoute.hops,
|
|
92
|
+
key: getRoute.key,
|
|
93
|
+
outbound: getRoute.outbound,
|
|
78
94
|
records: [args.message],
|
|
79
95
|
});
|
|
80
96
|
|
|
@@ -99,13 +115,13 @@ module.exports = (args, cbk) => {
|
|
|
99
115
|
}],
|
|
100
116
|
|
|
101
117
|
// Send the response message through the reply path
|
|
102
|
-
send: ['checkSize', '
|
|
118
|
+
send: ['checkSize', 'getRoute', ({getRoute}, cbk) => {
|
|
103
119
|
return sendMessage({
|
|
104
|
-
inbound:
|
|
105
|
-
key:
|
|
120
|
+
inbound: getRoute.hops,
|
|
121
|
+
key: getRoute.key,
|
|
106
122
|
lnd: args.lnd,
|
|
107
123
|
message: args.message,
|
|
108
|
-
outbound:
|
|
124
|
+
outbound: getRoute.outbound,
|
|
109
125
|
},
|
|
110
126
|
cbk);
|
|
111
127
|
}],
|
|
@@ -176,23 +176,32 @@ module.exports = ({delay, ids, lnd}) => {
|
|
|
176
176
|
|
|
177
177
|
const {encrypted, key, reply} = received;
|
|
178
178
|
|
|
179
|
-
let
|
|
179
|
+
let route;
|
|
180
180
|
|
|
181
|
-
// The route to the reply path
|
|
182
|
-
|
|
181
|
+
// The route to the reply path, the hops and key to send over and the
|
|
182
|
+
// outbound hops to reach them, is found once for a message
|
|
183
|
+
const getRoute = () => {
|
|
183
184
|
// Exit early when the route was already found or is being found
|
|
184
|
-
if (!!
|
|
185
|
-
return
|
|
185
|
+
if (!!route) {
|
|
186
|
+
return route;
|
|
186
187
|
}
|
|
187
188
|
|
|
188
|
-
const lookup = getPathOutbound({
|
|
189
|
+
const lookup = getPathOutbound({
|
|
190
|
+
lnd,
|
|
191
|
+
path: {
|
|
192
|
+
hops: reply.inbound,
|
|
193
|
+
introduction_edge: reply.introduction_edge,
|
|
194
|
+
introduction_node: reply.introduction_node,
|
|
195
|
+
key: reply.key,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
189
198
|
|
|
190
|
-
|
|
199
|
+
route = lookup;
|
|
191
200
|
|
|
192
201
|
// A failed lookup is not kept, so that a later reply can try again
|
|
193
202
|
lookup.catch(() => {
|
|
194
|
-
if (
|
|
195
|
-
|
|
203
|
+
if (route === lookup) {
|
|
204
|
+
route = undefined;
|
|
196
205
|
}
|
|
197
206
|
});
|
|
198
207
|
|
|
@@ -224,7 +233,7 @@ module.exports = ({delay, ids, lnd}) => {
|
|
|
224
233
|
|
|
225
234
|
// The caller is told outside of the promise chain, so that a throw in
|
|
226
235
|
// the caller is not taken as a failure to send
|
|
227
|
-
return
|
|
236
|
+
return getRoute()
|
|
228
237
|
.then(res => {
|
|
229
238
|
// Exit early when the service stopped while the route was found
|
|
230
239
|
if (isStopped) {
|
|
@@ -236,7 +245,7 @@ module.exports = ({delay, ids, lnd}) => {
|
|
|
236
245
|
lnd,
|
|
237
246
|
reply,
|
|
238
247
|
message: response,
|
|
239
|
-
|
|
248
|
+
route: res,
|
|
240
249
|
});
|
|
241
250
|
})
|
|
242
251
|
.then(() => setImmediate(() => done()), err => {
|
|
@@ -253,11 +262,11 @@ module.exports = ({delay, ids, lnd}) => {
|
|
|
253
262
|
|
|
254
263
|
// The caller is told outside of the promise chain, so that it is only
|
|
255
264
|
// called once, even when it throws
|
|
256
|
-
return
|
|
265
|
+
return getRoute()
|
|
257
266
|
.then(res => regularReplyBytes({
|
|
258
267
|
type,
|
|
259
|
-
inbound:
|
|
260
|
-
key:
|
|
268
|
+
inbound: res.hops,
|
|
269
|
+
key: res.key,
|
|
261
270
|
outbound: res.outbound,
|
|
262
271
|
}))
|
|
263
272
|
.then(res => setImmediate(() => cbk(null, res)), err => {
|
|
@@ -29,11 +29,11 @@ const typeSchnorr = 'schnorr';
|
|
|
29
29
|
The request is sent over each path of the offer in turn, waiting `timeout`
|
|
30
30
|
for each, until a path gives back an invoice, or the recipient replies
|
|
31
31
|
with an invoice error, which is returned. An offer without paths has the
|
|
32
|
-
request sent to its issuer id.
|
|
33
|
-
its
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
supported.
|
|
32
|
+
request sent to its issuer id. A path that starts at the payer is sent
|
|
33
|
+
from its next node. The reply path of a request goes back along its route,
|
|
34
|
+
so that the recipient can reach it. The route to the start of a path is
|
|
35
|
+
found over the public graph and the channels of the payer, so a payer that
|
|
36
|
+
is only connected through a peer that is itself private is not supported.
|
|
37
37
|
|
|
38
38
|
The request is signed by the `payer_id`, a new key that LND derives in key
|
|
39
39
|
family 808 at `payer_key_index`, only once the request is known to be one
|
package/package.json
CHANGED
|
@@ -19,18 +19,17 @@
|
|
|
19
19
|
"bolt01": "4.0.1",
|
|
20
20
|
"bolt04": "1.3.0",
|
|
21
21
|
"bolt07": "2.0.0",
|
|
22
|
-
"
|
|
23
|
-
"goldengate": "16.0.8",
|
|
22
|
+
"goldengate": "16.0.9",
|
|
24
23
|
"invoices": "6.3.0",
|
|
25
|
-
"ln-service": "60.1.
|
|
26
|
-
"ln-sync": "8.0.
|
|
24
|
+
"ln-service": "60.1.6",
|
|
25
|
+
"ln-sync": "8.0.8",
|
|
27
26
|
"psbt": "7.0.0",
|
|
28
27
|
"p2tr": "3.0.0",
|
|
29
28
|
"tiny-secp256k1": "2.2.4"
|
|
30
29
|
},
|
|
31
30
|
"description": "Lightning Paid Services library",
|
|
32
31
|
"devDependencies": {
|
|
33
|
-
"ln-docker-daemons": "8.1.
|
|
32
|
+
"ln-docker-daemons": "8.1.2",
|
|
34
33
|
"mock-lnd": "4.0.0"
|
|
35
34
|
},
|
|
36
35
|
"engines": {
|
|
@@ -73,5 +72,5 @@
|
|
|
73
72
|
"integration-tests": "node test/integration",
|
|
74
73
|
"test": "node --experimental-test-coverage --test test/actions/*.js test/balanced/*.js test/client/*.js test/config/*.js test/network/*.js test/offers/*.js test/p2p/*.js test/records/*.js test/respond/*.js test/server/*.js test/service_records/*.js test/swaps/*.js test/services/*.js test/support/*.js test/trades/*.js"
|
|
75
74
|
},
|
|
76
|
-
"version": "10.
|
|
75
|
+
"version": "10.3.1"
|
|
77
76
|
}
|
|
@@ -32,6 +32,7 @@ const {parsePaymentRequest} = require('ln-service');
|
|
|
32
32
|
const {pay} = require('ln-service');
|
|
33
33
|
const {payViaPaymentDetails} = require('ln-service');
|
|
34
34
|
const {pointAdd} = require('tiny-secp256k1');
|
|
35
|
+
const {pointFromScalar} = require('tiny-secp256k1');
|
|
35
36
|
const {privateAdd} = require('tiny-secp256k1');
|
|
36
37
|
const {releaseSwapOutSecret} = require('goldengate');
|
|
37
38
|
const {returnResult} = require('asyncjs-util');
|
|
@@ -43,7 +44,6 @@ const {subscribeToSpend} = require('goldengate');
|
|
|
43
44
|
const {swapScriptBranches} = require('goldengate');
|
|
44
45
|
const {taprootClaimTransaction} = require('goldengate');
|
|
45
46
|
const {taprootCoopTransaction} = require('goldengate');
|
|
46
|
-
const tinysecp = require('tiny-secp256k1');
|
|
47
47
|
const {Transaction} = require('bitcoinjs-lib');
|
|
48
48
|
const {v1OutputScript} = require('p2tr');
|
|
49
49
|
|
|
@@ -72,6 +72,7 @@ const fuzzTimelock = 1;
|
|
|
72
72
|
const getLoopCoopSignedTransaction = asyncTimeout(getCoopSignedTx, 1000 * 30);
|
|
73
73
|
const hexAsBuffer = hex => Buffer.from(hex, 'hex');
|
|
74
74
|
const {isArray} = Array;
|
|
75
|
+
const isCompressed = true;
|
|
75
76
|
const maxClaimMultiple = (r, t) => Math.min(1000, ((1000 + t) / 150) / r);
|
|
76
77
|
const maxCoopMultiple = (r, t) => Math.min(1000, ((1000 + t) / 100) / r);
|
|
77
78
|
const messageRejected = 'PaymentRejectedByDestination';
|
|
@@ -82,6 +83,7 @@ const pollInterval = {btcregtest: 100};
|
|
|
82
83
|
const ppmRate = (fee, total) => fee * 1e6 / total;
|
|
83
84
|
const preimageByteLength = 32;
|
|
84
85
|
const pubKeyAsInternalKey = key => Buffer.from(key).slice(1).toString('hex');
|
|
86
|
+
const publicKeyFor = key => Buffer.from(pointFromScalar(key, isCompressed));
|
|
85
87
|
const pushSecret = asyncTimeout(releaseSwapOutSecret, 1000 * 30);
|
|
86
88
|
const sha256 = preimage => createHash('sha256').update(preimage).digest('hex');
|
|
87
89
|
const sighash = Transaction.SIGHASH_DEFAULT;
|
|
@@ -116,9 +118,6 @@ const witnessVersionTaproot = 1;
|
|
|
116
118
|
module.exports = (args, cbk) => {
|
|
117
119
|
return new Promise((resolve, reject) => {
|
|
118
120
|
return asyncAuto({
|
|
119
|
-
// Import the ECPair library
|
|
120
|
-
ecp: async () => (await import('ecpair')).ECPairFactory(tinysecp),
|
|
121
|
-
|
|
122
121
|
// Check arguments
|
|
123
122
|
validate: cbk => {
|
|
124
123
|
if (!args.emitter) {
|
|
@@ -340,12 +339,12 @@ module.exports = (args, cbk) => {
|
|
|
340
339
|
}],
|
|
341
340
|
|
|
342
341
|
// Get the claim public key
|
|
343
|
-
getClaimKey: ['
|
|
342
|
+
getClaimKey: ['requestDetails', ({requestDetails}, cbk) => {
|
|
344
343
|
// Exit early when a private key is defined
|
|
345
344
|
if (!!requestDetails.solo_private_key) {
|
|
346
345
|
const privateKey = hexAsBuffer(requestDetails.solo_private_key);
|
|
347
346
|
|
|
348
|
-
const
|
|
347
|
+
const publicKey = publicKeyFor(privateKey);
|
|
349
348
|
|
|
350
349
|
return cbk(null, {public_key: bufferAsHex(publicKey)});
|
|
351
350
|
}
|
|
@@ -453,14 +452,12 @@ module.exports = (args, cbk) => {
|
|
|
453
452
|
|
|
454
453
|
// Derive the swap script branches
|
|
455
454
|
branches: [
|
|
456
|
-
'ecp',
|
|
457
455
|
'getClaimKey',
|
|
458
456
|
'requestDetails',
|
|
459
457
|
'responseDetails',
|
|
460
|
-
({
|
|
458
|
+
({getClaimKey, requestDetails, responseDetails}, cbk) =>
|
|
461
459
|
{
|
|
462
460
|
const swapScript = swapScriptBranches({
|
|
463
|
-
ecp,
|
|
464
461
|
claim_public_key: getClaimKey.public_key,
|
|
465
462
|
hash: sha256(hexAsBuffer(requestDetails.secret)),
|
|
466
463
|
refund_public_key: responseDetails.refund_public_key,
|
|
@@ -477,12 +474,11 @@ module.exports = (args, cbk) => {
|
|
|
477
474
|
|
|
478
475
|
// Derive the output script
|
|
479
476
|
joined: [
|
|
480
|
-
'ecp',
|
|
481
477
|
'branches',
|
|
482
478
|
'getClaimKey',
|
|
483
479
|
'requestDetails',
|
|
484
480
|
'responseDetails',
|
|
485
|
-
({
|
|
481
|
+
({branches, getClaimKey, requestDetails, responseDetails}, cbk) =>
|
|
486
482
|
{
|
|
487
483
|
// Exit early when using MuSig2 with Lightning Loop
|
|
488
484
|
if (!!args.is_loop_service) {
|
|
@@ -502,7 +498,7 @@ module.exports = (args, cbk) => {
|
|
|
502
498
|
const privateKey = requestDetails.coop_private_key;
|
|
503
499
|
|
|
504
500
|
const jointPublicKey = pointAdd(
|
|
505
|
-
|
|
501
|
+
publicKeyFor(hexAsBuffer(privateKey)),
|
|
506
502
|
hexAsBuffer(responseDetails.coop_public_key)
|
|
507
503
|
);
|
|
508
504
|
|
|
@@ -551,7 +547,6 @@ module.exports = (args, cbk) => {
|
|
|
551
547
|
payToDeposit: [
|
|
552
548
|
'checkSwapId',
|
|
553
549
|
'deadline',
|
|
554
|
-
'ecp',
|
|
555
550
|
'getDepositPayment',
|
|
556
551
|
'getNetwork',
|
|
557
552
|
'requestDetails',
|
|
@@ -560,7 +555,6 @@ module.exports = (args, cbk) => {
|
|
|
560
555
|
'swap',
|
|
561
556
|
({
|
|
562
557
|
deadline,
|
|
563
|
-
ecp,
|
|
564
558
|
getNetwork,
|
|
565
559
|
getDepositPayment,
|
|
566
560
|
requestDetails,
|
|
@@ -651,7 +645,7 @@ module.exports = (args, cbk) => {
|
|
|
651
645
|
max_fee: args.max_fee_deposit,
|
|
652
646
|
messages: [{
|
|
653
647
|
type: typePayMetadata,
|
|
654
|
-
value: bufferAsHex(
|
|
648
|
+
value: bufferAsHex(publicKeyFor(privateKey)),
|
|
655
649
|
}],
|
|
656
650
|
mtokens: responseDetails.deposit_mtokens,
|
|
657
651
|
payment: responseDetails.deposit_payment,
|
|
@@ -762,7 +756,6 @@ module.exports = (args, cbk) => {
|
|
|
762
756
|
'checkOutput',
|
|
763
757
|
'createAddress',
|
|
764
758
|
'deadline',
|
|
765
|
-
'ecp',
|
|
766
759
|
'findOutput',
|
|
767
760
|
'getFeeRate',
|
|
768
761
|
'getNetwork',
|
|
@@ -771,7 +764,6 @@ module.exports = (args, cbk) => {
|
|
|
771
764
|
({
|
|
772
765
|
createAddress,
|
|
773
766
|
deadline,
|
|
774
|
-
ecp,
|
|
775
767
|
findOutput,
|
|
776
768
|
getFeeRate,
|
|
777
769
|
getNetwork,
|
|
@@ -801,7 +793,6 @@ module.exports = (args, cbk) => {
|
|
|
801
793
|
|
|
802
794
|
const claims = feeRates.map(({rate, height}) => {
|
|
803
795
|
const {transaction} = taprootClaimTransaction({
|
|
804
|
-
ecp,
|
|
805
796
|
block_height: height - fuzzTimelock,
|
|
806
797
|
claim_script: swap.claim_script,
|
|
807
798
|
external_key: swap.external_key,
|
|
@@ -895,14 +886,12 @@ module.exports = (args, cbk) => {
|
|
|
895
886
|
|
|
896
887
|
// Push the preimage to allow for a cooperative swap
|
|
897
888
|
pushPreimage: [
|
|
898
|
-
'ecp',
|
|
899
889
|
'findOutput',
|
|
900
890
|
'loopService',
|
|
901
891
|
'requestDetails',
|
|
902
892
|
'responseDetails',
|
|
903
893
|
'signClaims',
|
|
904
894
|
asyncReflect(({
|
|
905
|
-
ecp,
|
|
906
895
|
loopService,
|
|
907
896
|
requestDetails,
|
|
908
897
|
responseDetails,
|
|
@@ -941,7 +930,7 @@ module.exports = (args, cbk) => {
|
|
|
941
930
|
const privateKey = requestDetails.coop_private_key;
|
|
942
931
|
const to = parsePaymentRequest({request: responseDetails.request});
|
|
943
932
|
|
|
944
|
-
const pubKey =
|
|
933
|
+
const pubKey = publicKeyFor(hexAsBuffer(privateKey));
|
|
945
934
|
|
|
946
935
|
const id = sha256(pubKey);
|
|
947
936
|
|
|
@@ -1024,7 +1013,6 @@ module.exports = (args, cbk) => {
|
|
|
1024
1013
|
'branches',
|
|
1025
1014
|
'createAddress',
|
|
1026
1015
|
'deadline',
|
|
1027
|
-
'ecp',
|
|
1028
1016
|
'findCoopKey',
|
|
1029
1017
|
'findOutput',
|
|
1030
1018
|
'getClaimKey',
|
|
@@ -1039,7 +1027,6 @@ module.exports = (args, cbk) => {
|
|
|
1039
1027
|
branches,
|
|
1040
1028
|
createAddress,
|
|
1041
1029
|
deadline,
|
|
1042
|
-
ecp,
|
|
1043
1030
|
findCoopKey,
|
|
1044
1031
|
findOutput,
|
|
1045
1032
|
getClaimKey,
|
|
@@ -1139,24 +1126,23 @@ module.exports = (args, cbk) => {
|
|
|
1139
1126
|
|
|
1140
1127
|
const privateKeys = privateKeysHex.map(hexAsBuffer);
|
|
1141
1128
|
|
|
1142
|
-
const coopKey =
|
|
1129
|
+
const coopKey = publicKeyFor(from(privateAdd(...privateKeys)));
|
|
1143
1130
|
|
|
1144
1131
|
const coopPrivateKey = hexAsBuffer(requestDetails.coop_private_key);
|
|
1145
1132
|
|
|
1146
1133
|
const jointPublicKey = pointAdd(
|
|
1147
|
-
|
|
1134
|
+
publicKeyFor(coopPrivateKey),
|
|
1148
1135
|
hexAsBuffer(responseDetails.coop_public_key)
|
|
1149
1136
|
);
|
|
1150
1137
|
|
|
1151
1138
|
// The public key from the private keys should be the combined pubkeys
|
|
1152
|
-
if (!coopKey.
|
|
1139
|
+
if (!coopKey.equals(from(jointPublicKey))) {
|
|
1153
1140
|
return cbk([503, 'ReceivedIncorrectPrivateCoopKey']);
|
|
1154
1141
|
}
|
|
1155
1142
|
|
|
1156
1143
|
// Derive sweep transactions at different fee rates
|
|
1157
1144
|
const coop = feeRates.map(({height, rate}) => {
|
|
1158
1145
|
const {transaction} = taprootCoopTransaction({
|
|
1159
|
-
ecp,
|
|
1160
1146
|
fee_tokens_per_vbyte: rate,
|
|
1161
1147
|
network: getNetwork.network,
|
|
1162
1148
|
output_script: swap.output_script,
|
|
@@ -22,6 +22,7 @@ const {getNetwork} = require('ln-sync');
|
|
|
22
22
|
const {getPublicKey} = require('ln-service');
|
|
23
23
|
const {hashForTree} = require('p2tr');
|
|
24
24
|
const {pointAdd} = require('tiny-secp256k1');
|
|
25
|
+
const {pointFromScalar} = require('tiny-secp256k1');
|
|
25
26
|
const {returnResult} = require('asyncjs-util');
|
|
26
27
|
const {sendToChainOutputScripts} = require('ln-service');
|
|
27
28
|
const {settleHodlInvoice} = require('ln-service');
|
|
@@ -32,7 +33,6 @@ const {subscribeToInvoice} = require('ln-service');
|
|
|
32
33
|
const {subscribeToSpend} = require('goldengate');
|
|
33
34
|
const {swapScriptBranches} = require('goldengate');
|
|
34
35
|
const {taprootRefundTransaction} = require('goldengate');
|
|
35
|
-
const tinysecp = require('tiny-secp256k1');
|
|
36
36
|
const {Transaction} = require('bitcoinjs-lib');
|
|
37
37
|
const {v1OutputScript} = require('p2tr');
|
|
38
38
|
|
|
@@ -54,12 +54,14 @@ const hexAsBase64 = hex => Buffer.from(hex, 'hex').toString('base64');
|
|
|
54
54
|
const hexAsBuffer = hex => Buffer.from(hex, 'hex');
|
|
55
55
|
const historicBlocksRange = 144 * 30;
|
|
56
56
|
const isAnnex = element => element[0] === 0x50;
|
|
57
|
+
const isCompressed = true;
|
|
57
58
|
const maxRefundMultiple = (r, t) => Math.min(100, ((1000 + t) / 150) / r);
|
|
58
59
|
const {min} = Math;
|
|
59
60
|
const minBlocks = 50;
|
|
60
61
|
const pollInterval = {btcregtest: 100};
|
|
61
62
|
const pubKeyAsInternalKey = key => Buffer.from(key).slice(1).toString('hex');
|
|
62
63
|
const pubKeyAsSecret = hexPublicKey => hexPublicKey.slice(2);
|
|
64
|
+
const publicKeyFor = key => Buffer.from(pointFromScalar(key, isCompressed));
|
|
63
65
|
const removeAnnex = stack => stack.slice(0, -1);
|
|
64
66
|
const sha256 = preimage => createHash('sha256').update(preimage).digest('hex');
|
|
65
67
|
const sighash = Transaction.SIGHASH_DEFAULT;
|
|
@@ -86,9 +88,6 @@ const witnessLengthTimeoutSweep = 3;
|
|
|
86
88
|
module.exports = (args, cbk) => {
|
|
87
89
|
return new Promise((resolve, reject) => {
|
|
88
90
|
return asyncAuto({
|
|
89
|
-
// Import the ECPair library
|
|
90
|
-
ecp: async () => (await import('ecpair')).ECPairFactory(tinysecp),
|
|
91
|
-
|
|
92
91
|
// Check arguments
|
|
93
92
|
validate: cbk => {
|
|
94
93
|
if (!args.emitter) {
|
|
@@ -190,16 +189,12 @@ module.exports = (args, cbk) => {
|
|
|
190
189
|
}],
|
|
191
190
|
|
|
192
191
|
// Get the refund key
|
|
193
|
-
getRefundKey: [
|
|
194
|
-
'ecp',
|
|
195
|
-
'recoveryDetails',
|
|
196
|
-
({ecp, recoveryDetails}, cbk) =>
|
|
197
|
-
{
|
|
192
|
+
getRefundKey: ['recoveryDetails', ({recoveryDetails}, cbk) => {
|
|
198
193
|
// Exit early when a unilateral private key is defined
|
|
199
194
|
if (!!recoveryDetails.solo_private_key) {
|
|
200
195
|
const privateKey = hexAsBuffer(recoveryDetails.solo_private_key);
|
|
201
196
|
|
|
202
|
-
const
|
|
197
|
+
const publicKey = publicKeyFor(privateKey);
|
|
203
198
|
|
|
204
199
|
return cbk(null, {public_key: bufferAsHex(publicKey)});
|
|
205
200
|
}
|
|
@@ -296,21 +291,19 @@ module.exports = (args, cbk) => {
|
|
|
296
291
|
|
|
297
292
|
// Derive swap details
|
|
298
293
|
swap: [
|
|
299
|
-
'ecp',
|
|
300
294
|
'getRefundKey',
|
|
301
295
|
'recoveryDetails',
|
|
302
296
|
'waitForDepositHold',
|
|
303
|
-
({
|
|
297
|
+
({getRefundKey, recoveryDetails, waitForDepositHold}, cbk) =>
|
|
304
298
|
{
|
|
305
299
|
const privateKey = recoveryDetails.refund_coop_private_key;
|
|
306
300
|
|
|
307
301
|
const jointPublicKey = pointAdd(
|
|
308
|
-
|
|
302
|
+
publicKeyFor(hexAsBuffer(privateKey)),
|
|
309
303
|
hexAsBuffer(waitForDepositHold.claim_coop_public_key)
|
|
310
304
|
);
|
|
311
305
|
|
|
312
306
|
const swapScript = swapScriptBranches({
|
|
313
|
-
ecp,
|
|
314
307
|
claim_public_key: recoveryDetails.claim_solo_public_key,
|
|
315
308
|
hash: recoveryDetails.hash,
|
|
316
309
|
refund_public_key: getRefundKey.public_key,
|
|
@@ -335,7 +328,6 @@ module.exports = (args, cbk) => {
|
|
|
335
328
|
|
|
336
329
|
// Generate the refund transactions against the inbound HTLC deadline
|
|
337
330
|
refunds: [
|
|
338
|
-
'ecp',
|
|
339
331
|
'getChainFee',
|
|
340
332
|
'getNetwork',
|
|
341
333
|
'getRefundKey',
|
|
@@ -345,7 +337,6 @@ module.exports = (args, cbk) => {
|
|
|
345
337
|
'swap',
|
|
346
338
|
'waitForFundHold',
|
|
347
339
|
({
|
|
348
|
-
ecp,
|
|
349
340
|
getChainFee,
|
|
350
341
|
getNetwork,
|
|
351
342
|
getRefundKey,
|
|
@@ -378,7 +369,6 @@ module.exports = (args, cbk) => {
|
|
|
378
369
|
}
|
|
379
370
|
|
|
380
371
|
return taprootRefundTransaction({
|
|
381
|
-
ecp,
|
|
382
372
|
block_height: height,
|
|
383
373
|
external_key: swap.external_key,
|
|
384
374
|
fee_tokens_per_vbyte: rate,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const {randomBytes} = require('crypto');
|
|
2
|
+
|
|
3
|
+
const {isPrivate} = require('tiny-secp256k1');
|
|
4
|
+
|
|
5
|
+
const bufferAsHex = buffer => buffer.toString('hex');
|
|
6
|
+
const privateKeyByteLength = 32;
|
|
7
|
+
|
|
8
|
+
/** Make a random private key
|
|
9
|
+
|
|
10
|
+
{}
|
|
11
|
+
|
|
12
|
+
@returns
|
|
13
|
+
{
|
|
14
|
+
private_key: <Private Key Hex String>
|
|
15
|
+
}
|
|
16
|
+
*/
|
|
17
|
+
module.exports = ({}) => {
|
|
18
|
+
const key = {};
|
|
19
|
+
|
|
20
|
+
// Random bytes have a negligible chance of being outside the key range
|
|
21
|
+
do {
|
|
22
|
+
key.bytes = randomBytes(privateKeyByteLength);
|
|
23
|
+
} while (!isPrivate(key.bytes));
|
|
24
|
+
|
|
25
|
+
return {private_key: bufferAsHex(key.bytes)};
|
|
26
|
+
};
|
|
@@ -17,7 +17,6 @@ const {parsePaymentRequest} = require('ln-service');
|
|
|
17
17
|
const {pay} = require('ln-service');
|
|
18
18
|
const {returnResult} = require('asyncjs-util');
|
|
19
19
|
const {swapScriptBranches} = require('goldengate');
|
|
20
|
-
const tinysecp = require('tiny-secp256k1');
|
|
21
20
|
|
|
22
21
|
const decodeLoopResponse = require('./decode_loop_response');
|
|
23
22
|
const decodeOffToOnRecovery = require('./decode_off_to_on_recovery');
|
|
@@ -47,9 +46,6 @@ const timeoutHeight = current => 400 + current - 3;
|
|
|
47
46
|
module.exports = ({before, lnd, recovery}, cbk) => {
|
|
48
47
|
return new Promise((resolve, reject) => {
|
|
49
48
|
return asyncAuto({
|
|
50
|
-
// Import ECPair library
|
|
51
|
-
ecp: async () => (await import('ecpair')).ECPairFactory(tinysecp),
|
|
52
|
-
|
|
53
49
|
// Check arguments
|
|
54
50
|
validate: cbk => {
|
|
55
51
|
if (!lnd) {
|
|
@@ -197,15 +193,13 @@ module.exports = ({before, lnd, recovery}, cbk) => {
|
|
|
197
193
|
// Derive swap script branches
|
|
198
194
|
script: [
|
|
199
195
|
'create',
|
|
200
|
-
'ecp',
|
|
201
196
|
'getKey',
|
|
202
197
|
'requestDetails',
|
|
203
198
|
'startHeight',
|
|
204
|
-
({create,
|
|
199
|
+
({create, getKey, requestDetails, startHeight}, cbk) =>
|
|
205
200
|
{
|
|
206
201
|
try {
|
|
207
202
|
const swap = swapScriptBranches({
|
|
208
|
-
ecp,
|
|
209
203
|
claim_public_key: getKey.public_key,
|
|
210
204
|
hash: requestDetails.hash,
|
|
211
205
|
refund_public_key: create.service_public_key,
|
|
@@ -7,15 +7,18 @@ const {getIdentity} = require('ln-service');
|
|
|
7
7
|
const {getNetwork} = require('ln-sync');
|
|
8
8
|
const {getPublicKey} = require('ln-service');
|
|
9
9
|
const {networks} = require('bitcoinjs-lib');
|
|
10
|
+
const {pointFromScalar} = require('tiny-secp256k1');
|
|
10
11
|
const {returnResult} = require('asyncjs-util');
|
|
11
|
-
const tinysecp = require('tiny-secp256k1');
|
|
12
12
|
|
|
13
13
|
const encodeOffToOnRequest = require('./encode_off_to_on_request');
|
|
14
|
+
const makePrivateKey = require('./make_private_key');
|
|
14
15
|
|
|
15
16
|
const bufferAsHex = buffer => buffer.toString('hex');
|
|
16
17
|
const family = 805;
|
|
17
18
|
const hexAsBuffer = hex => Buffer.from(hex, 'hex');
|
|
19
|
+
const isCompressed = true;
|
|
18
20
|
const makeSecret = () => randomBytes(32);
|
|
21
|
+
const publicKeyFor = key => Buffer.from(pointFromScalar(key, isCompressed));
|
|
19
22
|
const sha256 = preimage => createHash('sha256').update(preimage).digest('hex');
|
|
20
23
|
|
|
21
24
|
/** Initialize the claim details of a swap
|
|
@@ -35,9 +38,6 @@ const sha256 = preimage => createHash('sha256').update(preimage).digest('hex');
|
|
|
35
38
|
module.exports = (args, cbk) => {
|
|
36
39
|
return new Promise((resolve, reject) => {
|
|
37
40
|
return asyncAuto({
|
|
38
|
-
// Import the ECPair library
|
|
39
|
-
ecp: async () => (await import('ecpair')).ECPairFactory(tinysecp),
|
|
40
|
-
|
|
41
41
|
// Check arguments
|
|
42
42
|
validate: cbk => {
|
|
43
43
|
if (!args.lnd) {
|
|
@@ -89,8 +89,9 @@ module.exports = (args, cbk) => {
|
|
|
89
89
|
}],
|
|
90
90
|
|
|
91
91
|
// Generate the claim coop key
|
|
92
|
-
generateCoopKey: ['
|
|
93
|
-
const
|
|
92
|
+
generateCoopKey: ['network', ({}, cbk) => {
|
|
93
|
+
const privateKey = hexAsBuffer(makePrivateKey({}).private_key);
|
|
94
|
+
const publicKey = publicKeyFor(privateKey);
|
|
94
95
|
|
|
95
96
|
return cbk(null, {
|
|
96
97
|
hash: sha256(publicKey),
|
|
@@ -99,13 +100,14 @@ module.exports = (args, cbk) => {
|
|
|
99
100
|
}],
|
|
100
101
|
|
|
101
102
|
// Generate the claim solo key
|
|
102
|
-
generateSoloKey: ['
|
|
103
|
+
generateSoloKey: ['network', ({}, cbk) => {
|
|
103
104
|
// Exit early when the solo key is derived from the internal seed
|
|
104
105
|
if (!args.is_external_solo_key) {
|
|
105
106
|
return cbk(null, {});
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
const
|
|
109
|
+
const privateKey = hexAsBuffer(makePrivateKey({}).private_key);
|
|
110
|
+
const publicKey = publicKeyFor(privateKey);
|
|
109
111
|
|
|
110
112
|
return cbk(null, {
|
|
111
113
|
pub: bufferAsHex(publicKey),
|
|
@@ -7,20 +7,24 @@ const {getHeight} = require('ln-service');
|
|
|
7
7
|
const {getIdentity} = require('ln-service');
|
|
8
8
|
const {getNetwork} = require('ln-sync');
|
|
9
9
|
const {getPublicKey} = require('ln-service');
|
|
10
|
-
const {parsePaymentRequest} = require('ln-service');
|
|
11
10
|
const {networks} = require('bitcoinjs-lib');
|
|
11
|
+
const {parsePaymentRequest} = require('ln-service');
|
|
12
|
+
const {pointFromScalar} = require('tiny-secp256k1');
|
|
12
13
|
const {returnResult} = require('asyncjs-util');
|
|
13
|
-
const tinysecp = require('tiny-secp256k1');
|
|
14
14
|
|
|
15
15
|
const decodeOffToOnRequest = require('./decode_off_to_on_request');
|
|
16
16
|
const encodeOffToOnResponse = require('./encode_off_to_on_response');
|
|
17
|
+
const makePrivateKey = require('./make_private_key');
|
|
17
18
|
|
|
18
19
|
const bufferAsHex = buffer => buffer.toString('hex');
|
|
19
20
|
const defaultSwapExpiryMs = 1000 * 60 * 30;
|
|
20
21
|
const family = 805;
|
|
22
|
+
const hexAsBuffer = hex => Buffer.from(hex, 'hex');
|
|
23
|
+
const isCompressed = true;
|
|
21
24
|
const maxSwapDelta = 144 * 30;
|
|
22
25
|
const minSwapDelta = 80;
|
|
23
26
|
const {now} = Date;
|
|
27
|
+
const publicKeyFor = key => Buffer.from(pointFromScalar(key, isCompressed));
|
|
24
28
|
const refundDelta = 144;
|
|
25
29
|
const sha256 = preimage => createHash('sha256').update(preimage).digest('hex');
|
|
26
30
|
|
|
@@ -45,9 +49,6 @@ const sha256 = preimage => createHash('sha256').update(preimage).digest('hex');
|
|
|
45
49
|
module.exports = (args, cbk) => {
|
|
46
50
|
return new Promise((resolve, reject) => {
|
|
47
51
|
return asyncAuto({
|
|
48
|
-
// Import the ECPair library
|
|
49
|
-
ecp: async () => (await import('ecpair')).ECPairFactory(tinysecp),
|
|
50
|
-
|
|
51
52
|
// Check arguments
|
|
52
53
|
validate: cbk => {
|
|
53
54
|
if (!args.delta) {
|
|
@@ -118,8 +119,9 @@ module.exports = (args, cbk) => {
|
|
|
118
119
|
}],
|
|
119
120
|
|
|
120
121
|
// Generate the refund coop key
|
|
121
|
-
generateCoopKey: ['
|
|
122
|
-
const
|
|
122
|
+
generateCoopKey: ['network', ({}, cbk) => {
|
|
123
|
+
const privateKey = hexAsBuffer(makePrivateKey({}).private_key);
|
|
124
|
+
const publicKey = publicKeyFor(privateKey);
|
|
123
125
|
|
|
124
126
|
return cbk(null, {
|
|
125
127
|
hash: sha256(privateKey),
|
|
@@ -129,12 +131,13 @@ module.exports = (args, cbk) => {
|
|
|
129
131
|
}],
|
|
130
132
|
|
|
131
133
|
// Generate the refund solo key
|
|
132
|
-
generateSoloKey: ['
|
|
134
|
+
generateSoloKey: ['network', ({}, cbk) => {
|
|
133
135
|
if (!args.is_external_solo_key) {
|
|
134
136
|
return cbk(null, {});
|
|
135
137
|
}
|
|
136
138
|
|
|
137
|
-
const
|
|
139
|
+
const privateKey = hexAsBuffer(makePrivateKey({}).private_key);
|
|
140
|
+
const publicKey = publicKeyFor(privateKey);
|
|
138
141
|
|
|
139
142
|
return cbk(null, {
|
|
140
143
|
private: bufferAsHex(privateKey),
|