ln-service 53.7.2 → 53.9.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 +9 -1
- package/README.md +129 -0
- package/index.js +4 -0
- package/package.json +7 -5
- package/test/integration/test_add_peer.js +2 -2
- package/test/integration/test_get_closed_channels.js +0 -2
- package/test/integration/test_get_invoice.js +0 -1
- package/test/integration/test_get_node.js +13 -1
- package/test/integration/test_get_peers.js +32 -21
- package/test/integration/test_get_pending_payments.js +87 -0
- package/test/integration/test_get_wallet_info.js +6 -4
- package/test/integration/test_open_channels.js +2 -2
- package/test/integration/test_pay.js +0 -1
- package/test/integration/test_pay_private_invoice.js +0 -1
- package/test/integration/test_remove_peer.js +13 -9
- package/test/integration/test_send_message_to_peer.js +39 -28
- package/test/integration/test_send_to_chain_address.js +4 -0
- package/test/integration/test_subscribe_to_channels.js +0 -1
- package/test/integration/test_subscribe_to_invoices.js +80 -78
- package/test/integration/test_subscribe_to_peer_messages.js +6 -4
- package/test/invoicesrpc-integration/test_get_sweep_transactions.js +0 -2
- package/test/invoicesrpc-integration/test_subscribe_cancel_invoice.js +0 -1
- package/test/macros/wait_for_channel.js +1 -1
- package/test/routerrpc-integration/test_delete_forwarding_reputations.js +61 -27
- package/test/routerrpc-integration/test_get_forwarding_reputations.js +42 -14
- package/test/routerrpc-integration/test_get_route_through_hops.js +24 -15
- package/test/routerrpc-integration/test_multipath_payment.js +0 -1
- package/test/routerrpc-integration/test_pay_via_payment_details.js +0 -1
- package/test/routerrpc-integration/test_pay_via_payment_request.js +92 -80
- package/test/routerrpc-integration/test_probe_for_route.js +5 -5
- package/test/routerrpc-integration/test_subscribe_to_forward_requests.js +0 -1
- package/test/routerrpc-integration/test_subscribe_to_forwards.js +11 -2
- package/test/routerrpc-integration/test_subscribe_to_past_payments.js +0 -3
- package/test/tower_clientrpc-integration/test_get_connected_watchtowers.js +0 -3
- package/test/walletrpc-integration/test_fund_psbt.js +110 -1
- package/test/walletrpc-integration/test_get_master_public_keys.js +79 -0
- package/test/walletrpc-integration/test_partially_sign_psbt.js +5 -0
|
@@ -22,99 +22,111 @@ test(`Pay via payment request`, async ({end, equal, rejects, strictSame}) => {
|
|
|
22
22
|
|
|
23
23
|
const [{generate, lnd}, target, remote] = nodes;
|
|
24
24
|
|
|
25
|
-
const channel = await setupChannel({generate, lnd, to: target});
|
|
26
|
-
|
|
27
|
-
// Make sure that an error is returned when there is no route
|
|
28
25
|
try {
|
|
29
|
-
|
|
26
|
+
await generate({count: 100});
|
|
30
27
|
|
|
31
|
-
await
|
|
32
|
-
payViaPaymentRequest({lnd, request}),
|
|
33
|
-
[503, 'PaymentPathfindingFailedToFindPossibleRoute'],
|
|
34
|
-
'A payment with no route returns an error'
|
|
35
|
-
);
|
|
36
|
-
} catch (err) {
|
|
37
|
-
equal(err, null, 'Expected no error creating invoice');
|
|
38
|
-
}
|
|
28
|
+
const channel = await setupChannel({generate, lnd, to: target});
|
|
39
29
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
to: remote,
|
|
44
|
-
});
|
|
30
|
+
// Make sure that an error is returned when there is no route
|
|
31
|
+
try {
|
|
32
|
+
const {request} = await createInvoice({tokens, lnd: remote.lnd});
|
|
45
33
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
34
|
+
await rejects(
|
|
35
|
+
payViaPaymentRequest({lnd, request}),
|
|
36
|
+
[503, 'PaymentPathfindingFailedToFindPossibleRoute'],
|
|
37
|
+
'A payment with no route returns an error'
|
|
38
|
+
);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
equal(err, null, 'Expected no error creating invoice');
|
|
41
|
+
}
|
|
49
42
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const invoice = await createInvoice({tokens, lnd: remote.lnd});
|
|
55
|
-
|
|
56
|
-
const paid = await payViaPaymentRequest({
|
|
57
|
-
lnd,
|
|
58
|
-
max_timeout_height: height + 40 + 43,
|
|
59
|
-
messages: [{type: tlvType, value: tlvValue}],
|
|
60
|
-
request: invoice.request,
|
|
43
|
+
await addPeer({
|
|
44
|
+
lnd: target.lnd,
|
|
45
|
+
public_key: remote.id,
|
|
46
|
+
socket: remote.socket,
|
|
61
47
|
});
|
|
62
48
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
equal(paid.mtokens, '101000', 'Paid mtokens');
|
|
68
|
-
equal(paid.secret, invoice.secret, 'Paid for invoice secret');
|
|
69
|
-
|
|
70
|
-
paid.hops.forEach(n => {
|
|
71
|
-
equal(n.timeout === height + 40 || n.timeout === height + 43, true);
|
|
72
|
-
|
|
73
|
-
delete n.timeout;
|
|
74
|
-
|
|
75
|
-
return;
|
|
49
|
+
const remoteChan = await setupChannel({
|
|
50
|
+
lnd: target.lnd,
|
|
51
|
+
generate: target.generate,
|
|
52
|
+
to: remote,
|
|
76
53
|
});
|
|
77
54
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
{
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
55
|
+
await addPeer({lnd, public_key: remote.id, socket: remote.socket});
|
|
56
|
+
|
|
57
|
+
await waitForRoute({lnd, tokens, destination: remote.id});
|
|
58
|
+
|
|
59
|
+
// When a route exists, payment is successful
|
|
60
|
+
try {
|
|
61
|
+
const commitTxFee = channel.commit_transaction_fee;
|
|
62
|
+
const height = (await getWalletInfo({lnd})).current_block_height;
|
|
63
|
+
const invoice = await createInvoice({tokens, lnd: remote.lnd});
|
|
64
|
+
|
|
65
|
+
const paid = await payViaPaymentRequest({
|
|
66
|
+
lnd,
|
|
67
|
+
max_timeout_height: height + 40 + 43,
|
|
68
|
+
messages: [{type: tlvType, value: tlvValue}],
|
|
69
|
+
request: invoice.request,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
equal(paid.confirmed_at > start, true, 'Got confirmation date');
|
|
73
|
+
equal(paid.fee, 1, 'Fee tokens paid');
|
|
74
|
+
equal(paid.fee_mtokens, '1000', 'Fee mtokens tokens paid');
|
|
75
|
+
equal(paid.id, invoice.id, 'Payment hash is equal on both sides');
|
|
76
|
+
equal(paid.mtokens, '101000', 'Paid mtokens');
|
|
77
|
+
equal(paid.secret, invoice.secret, 'Paid for invoice secret');
|
|
78
|
+
|
|
79
|
+
paid.hops.forEach(n => {
|
|
80
|
+
equal(n.timeout === height + 40 || n.timeout === height + 43, true);
|
|
81
|
+
|
|
82
|
+
delete n.timeout;
|
|
83
|
+
|
|
84
|
+
return;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const expectedHops = [
|
|
88
|
+
{
|
|
89
|
+
channel: channel.id,
|
|
90
|
+
channel_capacity: 1000000,
|
|
91
|
+
fee: 1,
|
|
92
|
+
fee_mtokens: '1000',
|
|
93
|
+
forward: 100,
|
|
94
|
+
forward_mtokens: invoice.mtokens,
|
|
95
|
+
public_key: target.id,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
channel: remoteChan.id,
|
|
99
|
+
channel_capacity: 1000000,
|
|
100
|
+
fee: 0,
|
|
101
|
+
fee_mtokens: '0',
|
|
102
|
+
forward: 100,
|
|
103
|
+
forward_mtokens: '100000',
|
|
104
|
+
public_key: remote.id,
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
strictSame(paid.hops, expectedHops, 'Hops are returned');
|
|
109
|
+
|
|
110
|
+
const {payments} = await getInvoice({id: paid.id, lnd: remote.lnd});
|
|
111
|
+
|
|
112
|
+
if (!!payments.length) {
|
|
113
|
+
const [payment] = payments;
|
|
114
|
+
|
|
115
|
+
if (!!payment.messages.length) {
|
|
116
|
+
const [message] = payment.messages;
|
|
117
|
+
|
|
118
|
+
equal(message.type, tlvType, 'Got TLV message type');
|
|
119
|
+
equal(message.value, tlvValue, 'Got TLV message value');
|
|
120
|
+
}
|
|
111
121
|
}
|
|
122
|
+
} catch (err) {
|
|
123
|
+
equal(err, null, 'Expected no error paying payment request');
|
|
112
124
|
}
|
|
113
125
|
} catch (err) {
|
|
114
|
-
equal(err,
|
|
126
|
+
equal(err, 'Expected no errors');
|
|
127
|
+
} finally {
|
|
128
|
+
await kill({});
|
|
115
129
|
}
|
|
116
130
|
|
|
117
|
-
await kill({});
|
|
118
|
-
|
|
119
131
|
return end();
|
|
120
132
|
});
|
|
@@ -31,10 +31,12 @@ test('Probe for route', async ({end, equal, strictSame}) => {
|
|
|
31
31
|
|
|
32
32
|
const [{generate, lnd}, target, remote] = nodes;
|
|
33
33
|
|
|
34
|
-
// Send coins to remote so that it can accept a channel
|
|
35
|
-
await remote.generate({count});
|
|
36
|
-
|
|
37
34
|
try {
|
|
35
|
+
// Send coins to remote so that it can accept a channel
|
|
36
|
+
await remote.generate({count});
|
|
37
|
+
|
|
38
|
+
await addPeer({lnd, public_key: remote.id, socket: remote.socket});
|
|
39
|
+
|
|
38
40
|
await setupChannel({
|
|
39
41
|
generate,
|
|
40
42
|
lnd,
|
|
@@ -50,8 +52,6 @@ test('Probe for route', async ({end, equal, strictSame}) => {
|
|
|
50
52
|
to: remote,
|
|
51
53
|
});
|
|
52
54
|
|
|
53
|
-
await addPeer({lnd, public_key: remote.id, socket: remote.socket});
|
|
54
|
-
|
|
55
55
|
const invoice = await createInvoice({tokens, lnd: remote.lnd});
|
|
56
56
|
|
|
57
57
|
await delay(1000);
|
|
@@ -10,7 +10,6 @@ const {deleteForwardingReputations} = require('./../../');
|
|
|
10
10
|
const {getHeight} = require('./../../');
|
|
11
11
|
const {getInvoice} = require('./../../');
|
|
12
12
|
const {getPayment} = require('./../../');
|
|
13
|
-
const {getWalletVersion} = require('./../../');
|
|
14
13
|
const {payViaPaymentRequest} = require('./../../');
|
|
15
14
|
const {subscribeToForwardRequests} = require('./../../');
|
|
16
15
|
const {subscribeToPayViaRequest} = require('./../../');
|
|
@@ -17,7 +17,7 @@ const {subscribeToForwards} = require('./../../');
|
|
|
17
17
|
const anchorsFeatureBit = 23;
|
|
18
18
|
const interval = 10;
|
|
19
19
|
const size = 3;
|
|
20
|
-
const times =
|
|
20
|
+
const times = 1000;
|
|
21
21
|
const tokens = 100;
|
|
22
22
|
|
|
23
23
|
// Subscribing to forwards should show forwarding events
|
|
@@ -41,7 +41,16 @@ test('Subscribe to forwards', async ({end, equal, rejects, strictSame}) => {
|
|
|
41
41
|
to: remote,
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
-
await
|
|
44
|
+
await asyncRetry({interval, times}, async () => {
|
|
45
|
+
await addPeer({
|
|
46
|
+
lnd,
|
|
47
|
+
public_key: remote.id,
|
|
48
|
+
retry_count: 1,
|
|
49
|
+
retry_delay: 1,
|
|
50
|
+
socket: remote.socket,
|
|
51
|
+
timeout: 1000,
|
|
52
|
+
});
|
|
53
|
+
});
|
|
45
54
|
|
|
46
55
|
await delay(3000);
|
|
47
56
|
|
|
@@ -2,11 +2,8 @@ const asyncRetry = require('async/retry');
|
|
|
2
2
|
const {spawnLightningCluster} = require('ln-docker-daemons');
|
|
3
3
|
const {test} = require('@alexbosworth/tap');
|
|
4
4
|
|
|
5
|
-
const {addPeer} = require('./../../');
|
|
6
5
|
const {createInvoice} = require('./../../');
|
|
7
6
|
const {delay} = require('./../macros');
|
|
8
|
-
const {getChannels} = require('./../../');
|
|
9
|
-
const {getHeight} = require('./../../');
|
|
10
7
|
const {getPayment} = require('./../../');
|
|
11
8
|
const {payViaPaymentRequest} = require('./../../');
|
|
12
9
|
const {setupChannel} = require('./../macros');
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
const {test} = require('@alexbosworth/tap');
|
|
2
2
|
|
|
3
|
-
const {closeChannel} = require('./../../');
|
|
4
3
|
const {connectWatchtower} = require('./../../');
|
|
5
|
-
const {createInvoice} = require('./../../');
|
|
6
4
|
const {createCluster} = require('./../macros');
|
|
7
5
|
const {delay} = require('./../macros');
|
|
8
6
|
const {disconnectWatchtower} = require('./../../');
|
|
9
|
-
const {getChannels} = require('./../../');
|
|
10
7
|
const {getConnectedWatchtowers} = require('./../../');
|
|
11
8
|
const {getTowerServerInfo} = require('./../../');
|
|
12
9
|
const {openChannel} = require('./../../');
|
|
@@ -1,32 +1,55 @@
|
|
|
1
1
|
const asyncRetry = require('async/retry');
|
|
2
2
|
const {address} = require('bitcoinjs-lib');
|
|
3
|
+
const {createPsbt} = require('psbt');
|
|
4
|
+
const {crypto} = require('bitcoinjs-lib');
|
|
3
5
|
const {decodePsbt} = require('psbt');
|
|
6
|
+
const {networks} = require('bitcoinjs-lib');
|
|
7
|
+
const {script} = require('bitcoinjs-lib');
|
|
4
8
|
const {spawnLightningCluster} = require('ln-docker-daemons');
|
|
5
9
|
const {test} = require('@alexbosworth/tap');
|
|
6
10
|
const tinysecp = require('tiny-secp256k1');
|
|
11
|
+
const {Transaction} = require('bitcoinjs-lib');
|
|
7
12
|
|
|
13
|
+
const {broadcastChainTransaction} = require('./../../');
|
|
8
14
|
const {createChainAddress} = require('./../../');
|
|
9
15
|
const {fundPsbt} = require('./../../');
|
|
10
16
|
const {getChainBalance} = require('./../../');
|
|
11
17
|
const {getChainTransactions} = require('./../../');
|
|
12
18
|
const {getUtxos} = require('./../../');
|
|
13
19
|
const {sendToChainAddress} = require('./../../');
|
|
20
|
+
const {signPsbt} = require('./../../');
|
|
14
21
|
|
|
15
22
|
const chainAddressRowType = 'chain_address';
|
|
23
|
+
const {compile} = script;
|
|
16
24
|
const confirmationCount = 6;
|
|
17
25
|
const count = 100;
|
|
18
26
|
const description = 'description';
|
|
27
|
+
const extra = Buffer.alloc(32);
|
|
19
28
|
const {fromBech32} = address;
|
|
29
|
+
const {fromHex} = Transaction;
|
|
30
|
+
const {fromOutputScript} = address;
|
|
31
|
+
const hexAsBuffer = hex => Buffer.from(hex, 'hex');
|
|
20
32
|
const interval = retryCount => 10 * Math.pow(2, retryCount);
|
|
33
|
+
const isLowPublicKey = keyPair => keyPair.publicKey[0] === 2;
|
|
34
|
+
const makeTaprootKey = (k, h) => tinysecp.xOnlyPointAddTweak(k, h).xOnlyPubkey;
|
|
35
|
+
const nLess1 = 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140';
|
|
36
|
+
const one256BitBigEndian = '0000000000000000000000000000000000000000000000000000000000000001';
|
|
37
|
+
const OP_1 = 81;
|
|
38
|
+
const {privateAdd} = tinysecp;
|
|
39
|
+
const {privateSub} = tinysecp;
|
|
21
40
|
const regtestBech32AddressHrp = 'bcrt';
|
|
41
|
+
const shortKey = keyPair => keyPair.publicKey.slice(1, 33);
|
|
42
|
+
const {signSchnorr} = tinysecp;
|
|
43
|
+
const smallTokens = 2e5;
|
|
44
|
+
const tapHash = k => crypto.taggedHash('TapTweak', k.publicKey.slice(1, 33));
|
|
22
45
|
const times = 20;
|
|
46
|
+
const {toOutputScript} = address;
|
|
23
47
|
const tokens = 1e6;
|
|
24
48
|
const txIdHexByteLength = 64;
|
|
25
49
|
|
|
26
50
|
// Funding a transaction should result in a funded PSBT
|
|
27
51
|
test(`Fund PSBT`, async ({end, equal}) => {
|
|
28
52
|
const ecp = (await import('ecpair')).ECPairFactory(tinysecp);
|
|
29
|
-
|
|
30
53
|
const {kill, nodes} = await spawnLightningCluster({});
|
|
31
54
|
|
|
32
55
|
const [{generate, lnd}] = nodes;
|
|
@@ -97,6 +120,92 @@ test(`Fund PSBT`, async ({end, equal}) => {
|
|
|
97
120
|
equal(!!decodedInput.witness_utxo.script_pub, true, 'PSBT input address');
|
|
98
121
|
equal(decodedInput.witness_utxo.tokens, 5000000000, 'PSBT has input tokens');
|
|
99
122
|
|
|
123
|
+
// A Taproot output should be funded
|
|
124
|
+
try {
|
|
125
|
+
await generate({count});
|
|
126
|
+
|
|
127
|
+
const keyPair = ecp.makeRandom({network: networks.regtest});
|
|
128
|
+
|
|
129
|
+
const outputKey = makeTaprootKey(shortKey(keyPair), tapHash(keyPair));
|
|
130
|
+
const tweakHash = tapHash(keyPair);
|
|
131
|
+
|
|
132
|
+
const outputScript = compile([OP_1, Buffer.from(outputKey)]);
|
|
133
|
+
|
|
134
|
+
const [utxo] = (await getUtxos({lnd})).utxos.reverse();
|
|
135
|
+
|
|
136
|
+
// Make a PSBT paying to the Taproot output
|
|
137
|
+
const {psbt} = createPsbt({
|
|
138
|
+
outputs: [{tokens, script: outputScript.toString('hex')}],
|
|
139
|
+
utxos: [{id: utxo.transaction_id, vout: utxo.transaction_vout}],
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Sign the PSBT
|
|
143
|
+
const signed = await signPsbt({
|
|
144
|
+
lnd,
|
|
145
|
+
psbt: (await fundPsbt({lnd, psbt})).psbt,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Send the tx to the chain
|
|
149
|
+
await broadcastChainTransaction({lnd, transaction: signed.transaction});
|
|
150
|
+
|
|
151
|
+
// Make a new tx that will spend the output back into the wallet
|
|
152
|
+
const tx = new Transaction();
|
|
153
|
+
|
|
154
|
+
// The new tx spends the Taproot output
|
|
155
|
+
tx.addInput(
|
|
156
|
+
fromHex(signed.transaction).getHash(),
|
|
157
|
+
fromHex(signed.transaction).outs.findIndex(n => n.value === tokens)
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// Make an output to pay back into the wallet
|
|
161
|
+
const chainOutput = toOutputScript(
|
|
162
|
+
(await createChainAddress({lnd})).address,
|
|
163
|
+
networks.regtest
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// Add output to the pay back transaction
|
|
167
|
+
tx.addOutput(chainOutput, smallTokens);
|
|
168
|
+
|
|
169
|
+
const [hashToSign] = tx.ins.map((input, i) => {
|
|
170
|
+
return tx.hashForWitnessV1(
|
|
171
|
+
i,
|
|
172
|
+
[outputScript],
|
|
173
|
+
[tokens],
|
|
174
|
+
Transaction.SIGHASH_DEFAULT,
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const isLow = isLowPublicKey(keyPair);
|
|
179
|
+
const ONE = hexAsBuffer(one256BitBigEndian);
|
|
180
|
+
const subtract = privateSub(hexAsBuffer(nLess1), keyPair.privateKey);
|
|
181
|
+
|
|
182
|
+
const privateKey = isLow ? keyPair.privateKey : privateAdd(subtract, ONE);
|
|
183
|
+
|
|
184
|
+
// Only low keys are allowed to save a leading byte on the public key
|
|
185
|
+
const lowPrivateKey = privateAdd(privateKey, tweakHash);
|
|
186
|
+
|
|
187
|
+
const signature = signSchnorr(hashToSign, lowPrivateKey, extra);
|
|
188
|
+
|
|
189
|
+
// Add the signature to the input
|
|
190
|
+
tx.ins.forEach((input, i) => tx.setWitness(i, [Buffer.from(signature)]));
|
|
191
|
+
|
|
192
|
+
await broadcastChainTransaction({lnd, transaction: tx.toHex()});
|
|
193
|
+
|
|
194
|
+
await asyncRetry({interval, times}, async () => {
|
|
195
|
+
await generate({});
|
|
196
|
+
|
|
197
|
+
const {utxos} = await getUtxos({lnd});
|
|
198
|
+
|
|
199
|
+
const utxo = utxos.find(n => n.transaction_id === tx.getId());
|
|
200
|
+
|
|
201
|
+
if (!utxo || !utxo.confirmation_count) {
|
|
202
|
+
throw new Error('ExpectedReceivedTaprootSpend');
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
} catch (err) {
|
|
206
|
+
equal(err, null, 'Expected no error');
|
|
207
|
+
}
|
|
208
|
+
|
|
100
209
|
await kill({});
|
|
101
210
|
|
|
102
211
|
return end();
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const bip32 = require('bip32');
|
|
2
|
+
const bs58check = require('bs58check');
|
|
3
|
+
const ecc = require('tiny-secp256k1')
|
|
4
|
+
const {spawnLightningCluster} = require('ln-docker-daemons');
|
|
5
|
+
const {test} = require('@alexbosworth/tap');
|
|
6
|
+
|
|
7
|
+
const {createChainAddress} = require('./../../');
|
|
8
|
+
const {getMasterPublicKeys} = require('./../../');
|
|
9
|
+
|
|
10
|
+
const asHex = n => n.toString('hex');
|
|
11
|
+
const BIP32Factory = bip32.default;
|
|
12
|
+
const chainCodeFromMasterPublicKey = n => n.slice(13, 45);
|
|
13
|
+
const firstKeyPath = 'm/0/0';
|
|
14
|
+
const identityKeyName = 'act:6';
|
|
15
|
+
const p2wpkhPath = `m/84'/0'/0'`;
|
|
16
|
+
const publicKeyFromMasterPublicKey = n => n.slice(45, 78);
|
|
17
|
+
|
|
18
|
+
// Getting master public keys should return a list of master public keys
|
|
19
|
+
test(`Get master public keys`, async ({end, equal, strictSame}) => {
|
|
20
|
+
const [{id, kill, lnd}] = (await spawnLightningCluster({})).nodes;
|
|
21
|
+
|
|
22
|
+
const {fromPublicKey} = await BIP32Factory(ecc);
|
|
23
|
+
|
|
24
|
+
// This method is not supported on LND 0.13.3 and below
|
|
25
|
+
try {
|
|
26
|
+
const {keys} = await getMasterPublicKeys({lnd});
|
|
27
|
+
|
|
28
|
+
const [,, key] = keys;
|
|
29
|
+
|
|
30
|
+
// Check if extended keys are available
|
|
31
|
+
if (!key) {
|
|
32
|
+
await kill({});
|
|
33
|
+
|
|
34
|
+
return end();
|
|
35
|
+
}
|
|
36
|
+
} catch (err) {
|
|
37
|
+
strictSame(err, [501, 'GetMasterPublicKeysMethodNotSupported'], 'Got err');
|
|
38
|
+
|
|
39
|
+
await kill({});
|
|
40
|
+
|
|
41
|
+
return end();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
// Make a new address
|
|
46
|
+
const {address} = await createChainAddress({lnd});
|
|
47
|
+
|
|
48
|
+
// Get the list of accounts
|
|
49
|
+
const {keys} = await getMasterPublicKeys({lnd});
|
|
50
|
+
|
|
51
|
+
const masterP2wpkhKey = keys.find(n => n.derivation_path === p2wpkhPath);
|
|
52
|
+
|
|
53
|
+
// The address creation is reflected in the accounts metadata
|
|
54
|
+
equal(masterP2wpkhKey.external_key_count, [address].length, 'Key is used');
|
|
55
|
+
|
|
56
|
+
// Find the identity key master public key
|
|
57
|
+
const masterIdentityKey = keys.find(n => n.named === identityKeyName);
|
|
58
|
+
|
|
59
|
+
// Convert the key from base58 into its raw form
|
|
60
|
+
const rawKey = bs58check.decode(masterIdentityKey.extended_public_key);
|
|
61
|
+
|
|
62
|
+
const chainCode = chainCodeFromMasterPublicKey(rawKey);
|
|
63
|
+
const publicKey = publicKeyFromMasterPublicKey(rawKey);
|
|
64
|
+
|
|
65
|
+
// Make a bip32 object to derive from
|
|
66
|
+
const masterKey = fromPublicKey(publicKey, chainCode);
|
|
67
|
+
|
|
68
|
+
// Pull out the first key from the identity master public key
|
|
69
|
+
const identityKey = masterKey.derivePath(firstKeyPath);
|
|
70
|
+
|
|
71
|
+
equal(asHex(identityKey.publicKey), id, 'Got identity master public key');
|
|
72
|
+
} catch (err) {
|
|
73
|
+
equal(err, null, 'Expected no error');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await kill({});
|
|
77
|
+
|
|
78
|
+
return end();
|
|
79
|
+
});
|
|
@@ -158,6 +158,11 @@ test(`Partially sign PSBT`, async ({end, equal, strictSame}) => {
|
|
|
158
158
|
|
|
159
159
|
const bip32Derivations = flatten(allDerivations);
|
|
160
160
|
|
|
161
|
+
// Exit early when derivations are not supported
|
|
162
|
+
if (!!bip32Derivations.filter(n => !n).length) {
|
|
163
|
+
await partiallySignPsbt({lnd: control.lnd, psbt: base.psbt});
|
|
164
|
+
}
|
|
165
|
+
|
|
161
166
|
// Update the PSBT so that it has the consolidated details
|
|
162
167
|
const updated = updatePsbt({
|
|
163
168
|
ecp,
|