mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-01-18 05:12:45 +01:00
signpsbt: add signonly parameter to restrict/enforce what inputs to sign.
This is an extra safety check for dual funding, where we only want to sign the inputs we provided! Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> Changelog-Added: JSON-RPC: `signpsbt` takes an optional `signonly` array to limit what inputs to sign.
This commit is contained in:
parent
aab3808668
commit
7435d50970
@ -1155,12 +1155,13 @@ class LightningRpc(UnixDomainSocketRpc):
|
||||
}
|
||||
return self.call("utxopsbt", payload)
|
||||
|
||||
def signpsbt(self, psbt):
|
||||
def signpsbt(self, psbt, signonly=None):
|
||||
"""
|
||||
Add internal wallet's signatures to PSBT
|
||||
"""
|
||||
payload = {
|
||||
"psbt": psbt,
|
||||
"signonly": signonly,
|
||||
}
|
||||
return self.call("signpsbt", payload)
|
||||
|
||||
|
@ -738,17 +738,42 @@ def test_sign_and_send_psbt(node_factory, bitcoind, chainparams):
|
||||
with pytest.raises(RpcError, match=r"No wallet inputs to sign"):
|
||||
l1.rpc.signpsbt(l2_funding['psbt'])
|
||||
|
||||
# With signonly it will fail if it can't sign it.
|
||||
with pytest.raises(RpcError, match=r"is unknown"):
|
||||
l1.rpc.signpsbt(l2_funding['psbt'], signonly=[0])
|
||||
|
||||
# Add some of our own PSBT inputs to it
|
||||
l1_funding = l1.rpc.fundpsbt(satoshi=Millisatoshi(3 * amount * 1000),
|
||||
feerate=7500,
|
||||
startweight=42,
|
||||
reserve=True)
|
||||
l1_num_inputs = len(bitcoind.rpc.decodepsbt(l1_funding['psbt'])['tx']['vin'])
|
||||
l2_num_inputs = len(bitcoind.rpc.decodepsbt(l2_funding['psbt'])['tx']['vin'])
|
||||
|
||||
# Join and add an output
|
||||
# Join and add an output (reorders!)
|
||||
joint_psbt = bitcoind.rpc.joinpsbts([l1_funding['psbt'], l2_funding['psbt'],
|
||||
output_pbst])
|
||||
|
||||
half_signed_psbt = l1.rpc.signpsbt(joint_psbt)['signed_psbt']
|
||||
# Ask it to sign inputs it doesn't know, it will fail.
|
||||
with pytest.raises(RpcError, match=r"is unknown"):
|
||||
l1.rpc.signpsbt(joint_psbt,
|
||||
signonly=list(range(l1_num_inputs + 1)))
|
||||
|
||||
# Similarly, it can't sign inputs it doesn't know.
|
||||
sign_success = []
|
||||
for i in range(l1_num_inputs + l2_num_inputs):
|
||||
try:
|
||||
l1.rpc.signpsbt(joint_psbt, signonly=[i])
|
||||
except RpcError:
|
||||
continue
|
||||
sign_success.append(i)
|
||||
assert len(sign_success) == l1_num_inputs
|
||||
|
||||
# But it can sign all the valid ones at once.
|
||||
half_signed_psbt = l1.rpc.signpsbt(joint_psbt, signonly=sign_success)['signed_psbt']
|
||||
for s in sign_success:
|
||||
assert bitcoind.rpc.decodepsbt(half_signed_psbt)['inputs'][s]['partial_signatures'] is not None
|
||||
|
||||
totally_signed = l2.rpc.signpsbt(half_signed_psbt)['signed_psbt']
|
||||
|
||||
broadcast_tx = l1.rpc.sendpsbt(totally_signed)
|
||||
|
@ -1221,8 +1221,17 @@ struct command_result *param_psbt(struct command *cmd,
|
||||
json_tok_full(buffer, tok));
|
||||
}
|
||||
|
||||
static bool in_only_inputs(const u32 *only_inputs, u32 this)
|
||||
{
|
||||
for (size_t i = 0; i < tal_count(only_inputs); i++)
|
||||
if (only_inputs[i] == this)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static struct command_result *match_psbt_inputs_to_utxos(struct command *cmd,
|
||||
struct wally_psbt *psbt,
|
||||
const u32 *only_inputs,
|
||||
struct utxo ***utxos)
|
||||
{
|
||||
*utxos = tal_arr(cmd, struct utxo *, 0);
|
||||
@ -1230,11 +1239,21 @@ static struct command_result *match_psbt_inputs_to_utxos(struct command *cmd,
|
||||
struct utxo *utxo;
|
||||
struct bitcoin_txid txid;
|
||||
|
||||
if (only_inputs && !in_only_inputs(only_inputs, i))
|
||||
continue;
|
||||
|
||||
wally_tx_input_get_txid(&psbt->tx->inputs[i], &txid);
|
||||
utxo = wallet_utxo_get(*utxos, cmd->ld->wallet,
|
||||
&txid, psbt->tx->inputs[i].index);
|
||||
if (!utxo)
|
||||
if (!utxo) {
|
||||
if (only_inputs)
|
||||
return command_fail(cmd, LIGHTNINGD,
|
||||
"Aborting PSBT signing. UTXO %s:%u is unknown (and specified by signonly)",
|
||||
type_to_string(tmpctx, struct bitcoin_txid,
|
||||
&txid),
|
||||
psbt->tx->inputs[i].index);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Oops we haven't reserved this utxo yet! */
|
||||
if (!is_reserved(utxo, get_block_height(cmd->ld->topology)))
|
||||
@ -1249,6 +1268,32 @@ static struct command_result *match_psbt_inputs_to_utxos(struct command *cmd,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct command_result *param_input_numbers(struct command *cmd,
|
||||
const char *name,
|
||||
const char *buffer,
|
||||
const jsmntok_t *tok,
|
||||
u32 **input_nums)
|
||||
{
|
||||
struct command_result *res;
|
||||
const jsmntok_t *arr, *t;
|
||||
size_t i;
|
||||
|
||||
res = param_array(cmd, name, buffer, tok, &arr);
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
*input_nums = tal_arr(cmd, u32, arr->size);
|
||||
json_for_each_arr(i, t, arr) {
|
||||
u32 *num;
|
||||
res = param_number(cmd, name, buffer, t, &num);
|
||||
if (res)
|
||||
return res;
|
||||
(*input_nums)[i] = *num;
|
||||
tal_free(num);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct command_result *json_signpsbt(struct command *cmd,
|
||||
const char *buffer,
|
||||
const jsmntok_t *obj UNNEEDED,
|
||||
@ -1258,17 +1303,27 @@ static struct command_result *json_signpsbt(struct command *cmd,
|
||||
struct json_stream *response;
|
||||
struct wally_psbt *psbt, *signed_psbt;
|
||||
struct utxo **utxos;
|
||||
u32 *input_nums;
|
||||
|
||||
if (!param(cmd, buffer, params,
|
||||
p_req("psbt", param_psbt, &psbt),
|
||||
p_opt("signonly", param_input_numbers, &input_nums),
|
||||
NULL))
|
||||
return command_param_failed();
|
||||
|
||||
/* Sanity check! */
|
||||
for (size_t i = 0; i < tal_count(input_nums); i++) {
|
||||
if (input_nums[i] >= psbt->num_inputs)
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"signonly[%zu]: %u out of range",
|
||||
i, input_nums[i]);
|
||||
}
|
||||
|
||||
/* We have to find/locate the utxos that are ours on this PSBT,
|
||||
* so that the HSM knows how/what to sign for (it's possible some of
|
||||
* our utxos require more complicated data to sign for e.g.
|
||||
* closeinfo outputs */
|
||||
res = match_psbt_inputs_to_utxos(cmd, psbt, &utxos);
|
||||
res = match_psbt_inputs_to_utxos(cmd, psbt, input_nums, &utxos);
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
@ -1334,7 +1389,7 @@ static struct command_result *json_sendpsbt(struct command *cmd,
|
||||
/* We have to find/locate the utxos that are ours on this PSBT,
|
||||
* so that we know who to mark as used.
|
||||
*/
|
||||
res = match_psbt_inputs_to_utxos(cmd, psbt, &utxos);
|
||||
res = match_psbt_inputs_to_utxos(cmd, psbt, NULL, &utxos);
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user