Skip to content

Commit 92c9734

Browse files
committed
pytest: create warning if we grind signature shorter than 71 bytes, don't fail.
One in 256 times, we will grind a signature to 70 bytes (or shorter). This breaks our feerate tests. Unfortunately the grinding is deterministic, so there doesn't seem to be a way to avoid it. So we add a log message, and then we skip the feerate test if it happens. Signed-off-by: Rusty Russell <[email protected]>
1 parent 0ad5a21 commit 92c9734

File tree

10 files changed

+61
-21
lines changed

10 files changed

+61
-21
lines changed

common/hsm_version.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
* v5 with preapprove_check: 0ed6dd4ea2c02b67c51b1420b3d07ab2227a4c06ce7e2942d946967687e9baf7
2828
* v6 no secret from get_per_commitment_point: 0cad1790beb3473d64355f4cb4f64daa80c28c8a241998b7ef0223385d7ffff9
2929
* v6 with sign_bolt12_2 (tweak using node id): 8fcb731279a10af3f95aeb8be1da6b2ced76a1984afa18c5f46a03515d70ea0e
30-
* v6 (internal rework only): 22dc3adfb0d63dbd6a92ff1daacbb6218c5efa3080f8910933a18683ce75bf5f
30+
* v6 with dev_warn_on_overgrind: a273b68e19336073e551c01a78bcd1e1f8cc510da7d0dde3afc45e249f9830cc
3131
*/
3232
#define HSM_MIN_VERSION 5
3333
#define HSM_MAX_VERSION 6

hsmd/hsmd.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -442,8 +442,11 @@ static struct io_plan *preinit_hsm(struct io_conn *conn,
442442
if (tlv->no_preapprove_check)
443443
dev_no_preapprove_check = *tlv->no_preapprove_check;
444444

445-
status_debug("preinit: dev_fail_preapprove = %u, dev_no_preapprove_check = %u",
446-
dev_fail_preapprove, dev_no_preapprove_check);
445+
if (tlv->warn_on_overgrind)
446+
dev_warn_on_overgrind = *tlv->warn_on_overgrind;
447+
448+
status_debug("preinit: dev_fail_preapprove = %u, dev_no_preapprove_check = %u, dev_warn_on_overgrind = %u",
449+
dev_fail_preapprove, dev_no_preapprove_check, dev_warn_on_overgrind);
447450
/* We don't send a reply, just read next */
448451
return client_read_next(conn, c);
449452
}

hsmd/hsmd_wire.csv

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ tlvtype,hsmd_dev_preinit_tlvs,fail_preapprove,1
1414
tlvdata,hsmd_dev_preinit_tlvs,fail_preapprove,fail,bool,
1515
tlvtype,hsmd_dev_preinit_tlvs,no_preapprove_check,3
1616
tlvdata,hsmd_dev_preinit_tlvs,no_preapprove_check,disable,bool,
17+
tlvtype,hsmd_dev_preinit_tlvs,warn_on_overgrind,5
18+
tlvdata,hsmd_dev_preinit_tlvs,warn_on_overgrind,enable,bool,
1719

1820
#include <bitcoin/chainparams.h>
1921
# Start the HSM.

hsmd/libhsmd.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ bool initialized = false;
4040
/* Do we fail all preapprove requests? */
4141
bool dev_fail_preapprove = false;
4242
bool dev_no_preapprove_check = false;
43+
bool dev_warn_on_overgrind = false;
4344

4445
struct hsmd_client *hsmd_client_new_main(const tal_t *ctx, u64 capabilities,
4546
void *extra)
@@ -553,6 +554,7 @@ static void sign_our_inputs(struct hsm_utxo **utxos, struct wally_psbt *psbt)
553554
for (size_t j = 0; j < psbt->num_inputs; j++) {
554555
struct privkey privkey;
555556
struct pubkey pubkey;
557+
bool needed_sig;
556558

557559
if (!wally_psbt_input_spends(&psbt->inputs[j],
558560
&utxo->outpoint))
@@ -590,6 +592,10 @@ static void sign_our_inputs(struct hsm_utxo **utxos, struct wally_psbt *psbt)
590592
wally_psbt_signing_cache_enable(psbt, 0);
591593
is_cache_enabled = true;
592594
}
595+
596+
/* We watch for pre-taproot variable-length sigs */
597+
needed_sig = (psbt->inputs[j].signatures.num_items == 0);
598+
593599
if (wally_psbt_sign(psbt, privkey.secret.data,
594600
sizeof(privkey.secret.data),
595601
EC_FLAG_GRIND_R) != WALLY_OK) {
@@ -602,6 +608,14 @@ static void sign_our_inputs(struct hsm_utxo **utxos, struct wally_psbt *psbt)
602608
j, fmt_pubkey(tmpctx, &pubkey),
603609
fmt_wally_psbt(tmpctx, psbt));
604610
}
611+
if (dev_warn_on_overgrind
612+
&& needed_sig
613+
&& psbt->inputs[j].signatures.num_items == 1
614+
&& psbt->inputs[j].signatures.items[0].value_len < 71) {
615+
hsmd_status_fmt(LOG_BROKEN, NULL,
616+
"overgrind: short signature length %zu",
617+
psbt->inputs[j].signatures.items[0].value_len);
618+
}
605619
tal_wally_end(psbt);
606620
}
607621
}

hsmd/libhsmd.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,6 @@ extern struct secret *dev_force_bip32_seed;
100100
extern bool dev_fail_preapprove;
101101
/* If they specify --dev-no-preapprove-check it ends up in here. */
102102
extern bool dev_no_preapprove_check;
103+
/* If they specify --dev-warn-on-overgrind it ends up in here. */
104+
extern bool dev_warn_on_overgrind;
103105
#endif /* LIGHTNING_HSMD_LIBHSMD_H */

lightningd/hsm_control.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ struct ext_key *hsm_init(struct lightningd *ld)
119119
&ld->dev_hsmd_fail_preapprove);
120120
tlv->no_preapprove_check = tal_dup(tlv, bool,
121121
&ld->dev_hsmd_no_preapprove_check);
122+
tlv->warn_on_overgrind = tal_dup(tlv, bool,
123+
&ld->dev_hsmd_warn_on_overgrind);
122124

123125
msg = towire_hsmd_dev_preinit(tmpctx, tlv);
124126
if (!wire_sync_write(ld->hsm_fd, msg))

lightningd/lightningd.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ static struct lightningd *new_lightningd(const tal_t *ctx)
151151
ld->dev_allow_shutdown_destination_change = false;
152152
ld->dev_hsmd_no_preapprove_check = false;
153153
ld->dev_hsmd_fail_preapprove = false;
154+
ld->dev_hsmd_warn_on_overgrind = false;
154155
ld->dev_handshake_no_reply = false;
155156
ld->dev_strict_forwarding = false;
156157
ld->dev_limit_connections_inflight = false;

lightningd/lightningd.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ struct lightningd {
353353
/* hsmd characteristic tweaks */
354354
bool dev_hsmd_no_preapprove_check;
355355
bool dev_hsmd_fail_preapprove;
356+
bool dev_hsmd_warn_on_overgrind;
356357

357358
/* Tell connectd not to talk after handshake */
358359
bool dev_handshake_no_reply;

lightningd/options.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,10 @@ static void dev_register_opts(struct lightningd *ld)
936936
opt_set_crash_timeout, NULL,
937937
ld,
938938
"Crash if we are still going after this long.");
939+
clnopt_noarg("--dev-warn-on-overgrind", OPT_DEV,
940+
opt_set_bool,
941+
&ld->dev_hsmd_warn_on_overgrind,
942+
"Warn if we create signatures that are not exactly 71 bytes.");
939943
/* This is handled directly in daemon_developer_mode(), so we ignore it here */
940944
clnopt_noarg("--dev-debug-self", OPT_DEV,
941945
opt_ignore,

tests/test_wallet.py

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,25 @@ def feerate_from_psbt(bitcoind, node, psbt):
300300
return fee / weight * 1000
301301

302302

303+
# I wish we could force libwally to use different entropy and thus force it to
304+
# create 71-byte sigs always!
305+
def did_short_sig(node):
306+
# This can take a moment to appear in the log!
307+
time.sleep(1)
308+
return node.daemon.is_in_log('overgrind: short signature length')
309+
310+
311+
def check_feerate(node, actual_feerate, expected_feerate):
312+
# Feerate can't be lower.
313+
assert actual_feerate > expected_feerate - 2
314+
if not did_short_sig(node):
315+
assert actual_feerate < expected_feerate + 2
316+
317+
303318
def test_txprepare(node_factory, bitcoind, chainparams):
304319
amount = 1000000
305-
l1 = node_factory.get_node(random_hsm=True)
320+
l1 = node_factory.get_node(random_hsm=True, options={'dev-warn-on-overgrind': None},
321+
broken_log='overgrind: short signature length')
306322
addr = chainparams['example_addr']
307323

308324
# Add some funds to withdraw later
@@ -322,8 +338,7 @@ def test_txprepare(node_factory, bitcoind, chainparams):
322338
# 4 inputs, 2 outputs (3 if we have a fee output).
323339
assert len(decode['vin']) == 4
324340
assert len(decode['vout']) == 2 if not chainparams['feeoutput'] else 3
325-
# Feerate should be ~ as we asked for
326-
assert normal_feerate_perkw - 2 < feerate_from_psbt(bitcoind, l1, prep['psbt']) < normal_feerate_perkw + 2
341+
check_feerate(l1, feerate_from_psbt(bitcoind, l1, prep['psbt']), normal_feerate_perkw)
327342

328343
# One output will be correct.
329344
outnum = [i for i, o in enumerate(decode['vout']) if o['value'] == Decimal(amount * 3) / 10**8][0]
@@ -351,8 +366,7 @@ def test_txprepare(node_factory, bitcoind, chainparams):
351366
assert decode['vout'][0]['value'] > Decimal(amount * 6) / 10**8 - Decimal(0.0002)
352367
assert decode['vout'][0]['scriptPubKey']['type'] == 'witness_v0_keyhash'
353368
assert scriptpubkey_addr(decode['vout'][0]['scriptPubKey']) == addr
354-
# Feerate should be ~ as we asked for
355-
assert normal_feerate_perkw - 2 < feerate_from_psbt(bitcoind, l1, prep2['psbt']) < normal_feerate_perkw + 2
369+
check_feerate(l1, feerate_from_psbt(bitcoind, l1, prep2['psbt']), normal_feerate_perkw)
356370

357371
# If I cancel the first one, I can get those first 4 outputs.
358372
discard = l1.rpc.txdiscard(prep['txid'])
@@ -371,8 +385,7 @@ def test_txprepare(node_factory, bitcoind, chainparams):
371385
assert decode['vout'][0]['value'] > Decimal(amount * 4) / 10**8 - Decimal(0.0002)
372386
assert decode['vout'][0]['scriptPubKey']['type'] == 'witness_v0_keyhash'
373387
assert scriptpubkey_addr(decode['vout'][0]['scriptPubKey']) == addr
374-
# Feerate should be ~ as we asked for
375-
assert normal_feerate_perkw - 1 < feerate_from_psbt(bitcoind, l1, prep3['psbt']) < normal_feerate_perkw + 1
388+
check_feerate(l1, feerate_from_psbt(bitcoind, l1, prep3['psbt']), normal_feerate_perkw)
376389

377390
# Cannot discard twice.
378391
with pytest.raises(RpcError, match=r'not an unreleased txid'):
@@ -393,17 +406,15 @@ def test_txprepare(node_factory, bitcoind, chainparams):
393406
assert decode['vout'][0]['value'] > Decimal(amount * 10) / 10**8 - Decimal(0.0003)
394407
assert decode['vout'][0]['scriptPubKey']['type'] == 'witness_v0_keyhash'
395408
assert scriptpubkey_addr(decode['vout'][0]['scriptPubKey']) == addr
396-
# Feerate should be ~ as we asked for
397-
assert normal_feerate_perkw - 2 < feerate_from_psbt(bitcoind, l1, prep4['psbt']) < normal_feerate_perkw + 2
409+
check_feerate(l1, feerate_from_psbt(bitcoind, l1, prep4['psbt']), normal_feerate_perkw)
398410
l1.rpc.txdiscard(prep4['txid'])
399411

400412
# Try passing in a utxo set
401413
utxos = [utxo["txid"] + ":" + str(utxo["output"])
402414
for utxo in l1.rpc.listfunds()["outputs"]][:4]
403415
prep5 = l1.rpc.txprepare([{addr:
404416
Millisatoshi(amount * 3.5 * 1000)}], utxos=utxos)
405-
# Feerate should be ~ as we asked for
406-
assert normal_feerate_perkw - 2 < feerate_from_psbt(bitcoind, l1, prep3['psbt']) < normal_feerate_perkw + 2
417+
check_feerate(l1, feerate_from_psbt(bitcoind, l1, prep3['psbt']), normal_feerate_perkw)
407418

408419
# Try passing unconfirmed utxos
409420
unconfirmed_utxo = l1.rpc.withdraw(l1.rpc.newaddr()["bech32"], 10**5)
@@ -414,7 +425,7 @@ def test_txprepare(node_factory, bitcoind, chainparams):
414425
# Feerate should be ~ as we asked for
415426
unconfirmed_tx = bitcoind.rpc.getrawmempool(True)[unconfirmed_utxo["txid"]]
416427
feerate_perkw = int(unconfirmed_tx['fees']['base'] * 100_000_000) * 1000 / unconfirmed_tx['weight']
417-
assert normal_feerate_perkw - 1 < feerate_perkw < normal_feerate_perkw + 1
428+
check_feerate(l1, feerate_perkw, normal_feerate_perkw)
418429

419430
decode = bitcoind.rpc.decoderawtransaction(prep5['unsigned_tx'])
420431
assert decode['txid'] == prep5['txid']
@@ -444,15 +455,15 @@ def test_txprepare(node_factory, bitcoind, chainparams):
444455
prep5 = l1.rpc.txprepare([{addr: Millisatoshi(amount * 3 * 1000)},
445456
{addr: 'all'}])
446457
# Feerate should be ~ as we asked for
447-
assert normal_feerate_perkw - 2 < feerate_from_psbt(bitcoind, l1, prep5['psbt']) < normal_feerate_perkw + 2
458+
check_feerate(l1, feerate_from_psbt(bitcoind, l1, prep5['psbt']), normal_feerate_perkw)
448459
l1.rpc.txdiscard(prep5['txid'])
449460
with pytest.raises(RpcError, match=r"'all'"):
450461
prep5 = l1.rpc.txprepare([{addr: 'all'}, {addr: 'all'}])
451462

452463
prep5 = l1.rpc.txprepare([{addr: Millisatoshi(amount * 3 * 500 + 100000)},
453464
{addr: Millisatoshi(amount * 3 * 500 - 100000)}])
454465
# Feerate should be ~ as we asked for
455-
assert normal_feerate_perkw - 2 < feerate_from_psbt(bitcoind, l1, prep5['psbt']) < normal_feerate_perkw + 2
466+
check_feerate(l1, feerate_from_psbt(bitcoind, l1, prep5['psbt']), normal_feerate_perkw)
456467
decode = bitcoind.rpc.decoderawtransaction(prep5['unsigned_tx'])
457468
assert decode['txid'] == prep5['txid']
458469
# 4 inputs, 3 outputs(include change).
@@ -484,7 +495,8 @@ def test_txprepare(node_factory, bitcoind, chainparams):
484495

485496
def test_txprepare_feerate(node_factory, bitcoind, chainparams):
486497
# Make sure it works at different feerates!
487-
l1, l2 = node_factory.get_nodes(2)
498+
l1, l2 = node_factory.get_nodes(2, opts={'dev-warn-on-overgrind': None,
499+
'broken_log': 'overgrind: short signature length'})
488500

489501
# Add some funds to withdraw later
490502
for i in range(20):
@@ -505,7 +517,7 @@ def test_txprepare_feerate(node_factory, bitcoind, chainparams):
505517
fee_output = 1
506518
else:
507519
fee_output = 0
508-
if len(bitcoind.rpc.decoderawtransaction(prep['unsigned_tx'])['vout']) == 1 + 1 + fee_output:
520+
if len(bitcoind.rpc.decoderawtransaction(prep['unsigned_tx'])['vout']) == 1 + 1 + fee_output and not did_short_sig(l1):
509521
assert actual_feerate < feerate + 2
510522
l1.rpc.txdiscard(prep['txid'])
511523

@@ -561,8 +573,7 @@ def test_fundpsbt_feerates(node_factory, bitcoind, chainparams, addrtype):
561573
# We never actually added that `amount` output to PSBT, so that appears as "fee"
562574
fee = int(txinfo['fees']['base'] * 100_000_000) - amount
563575
actual_feerate = fee / (txinfo['weight'] / 1000)
564-
# Out by one errors (due to rounding) change feerate by 2.
565-
assert feerate - 2 < actual_feerate < feerate + 2
576+
check_feerate(l1, actual_feerate, feerate)
566577

567578

568579
def test_reserveinputs(node_factory, bitcoind, chainparams):

0 commit comments

Comments
 (0)