Support async signing in chanmon_consistency
What changed, and why it matters
This commit only changes test and fuzzing code. It adds new test controls that let developers temporarily disable or enable individual signer operations during fuzz testing, and it removes one unused signer-operation flag. There is no change to production Lightning code, so it does not create a real-world security vulnerability or fix one.
No security action required; treat as a normal test-infrastructure change during review.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch extends the chanmon_consistency fuzz harness to toggle SignerOp variants per signer and unblocks the node when an operation is re-enabled. It also removes the unused ValidateCounterpartyRevocation variant from the SignerOp enum and relaxes several #[cfg(test)] gates to #[cfg(any(test, feature = “_test_utils”))] so the test signer can be used under the _test_utils feature. All changes are confined to test/fuzz infrastructure.
Changed components
fuzz/src/chanmon_consistency.rslightning/src/util/test_channel_signer.rsInspect captured patch +85 / −18
diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs
index ced89f5..0d4fe88 100644
--- a/fuzz/src/chanmon_consistency.rs
+++ b/fuzz/src/chanmon_consistency.rs
@@ -79,7 +79,7 @@ use lightning::util::errors::APIError;
use lightning::util::hash_tables::*;
use lightning::util::logger::Logger;
use lightning::util::ser::{LengthReadable, ReadableArgs, Writeable, Writer};
-use lightning::util::test_channel_signer::{EnforcementState, TestChannelSigner};
+use lightning::util::test_channel_signer::{EnforcementState, SignerOp, TestChannelSigner};
use lightning::util::test_utils::TestWalletSource;
use lightning_invoice::RawBolt11Invoice;
@@ -448,6 +448,14 @@ impl SignerProvider for KeyProvider {
}
}
+// Since this fuzzer is only concerned with live-channel operations, we don't need to worry about
+// any signer operations that come after a force close.
+const SUPPORTED_SIGNER_OPS: [SignerOp; 3] = [
+ SignerOp::SignCounterpartyCommitment,
+ SignerOp::GetPerCommitmentPoint,
+ SignerOp::ReleaseCommitmentSecret,
+];
+
impl KeyProvider {
fn make_enforcement_state_cell(
&self, commitment_seed: [u8; 32],
@@ -460,6 +468,22 @@ impl KeyProvider {
let cell = revoked_commitments.get(&commitment_seed).unwrap();
Arc::clone(cell)
}
+
+ fn disable_supported_ops_for_all_signers(&self) {
+ let enforcement_states = self.enforcement_states.lock().unwrap();
+ for (_, state) in enforcement_states.iter() {
+ for signer_op in SUPPORTED_SIGNER_OPS {
+ state.lock().unwrap().disabled_signer_ops.insert(signer_op);
+ }
+ }
+ }
+
+ fn enable_op_for_all_signers(&self, signer_op: SignerOp) {
+ let enforcement_states = self.enforcement_states.lock().unwrap();
+ for (_, state) in enforcement_states.iter() {
+ state.lock().unwrap().disabled_signer_ops.remove(&signer_op);
+ }
+ }
}
// Returns a bool indicating whether the payment failed.
@@ -2404,6 +2428,46 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(
monitor_c = new_monitor_c;
},
+ 0xc0 => keys_manager_a.disable_supported_ops_for_all_signers(),
+ 0xc1 => keys_manager_b.disable_supported_ops_for_all_signers(),
+ 0xc2 => keys_manager_c.disable_supported_ops_for_all_signers(),
+ 0xc3 => {
+ keys_manager_a.enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment);
+ nodes[0].signer_unblocked(None);
+ },
+ 0xc4 => {
+ keys_manager_b.enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment);
+ nodes[1].signer_unblocked(None);
+ },
+ 0xc5 => {
+ keys_manager_c.enable_op_for_all_signers(SignerOp::SignCounterpartyCommitment);
+ nodes[2].signer_unblocked(None);
+ },
+ 0xc6 => {
+ keys_manager_a.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint);
+ nodes[0].signer_unblocked(None);
+ },
+ 0xc7 => {
+ keys_manager_b.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint);
+ nodes[1].signer_unblocked(None);
+ },
+ 0xc8 => {
+ keys_manager_c.enable_op_for_all_signers(SignerOp::GetPerCommitmentPoint);
+ nodes[2].signer_unblocked(None);
+ },
+ 0xc9 => {
+ keys_manager_a.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret);
+ nodes[0].signer_unblocked(None);
+ },
+ 0xca => {
+ keys_manager_b.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret);
+ nodes[1].signer_unblocked(None);
+ },
+ 0xcb => {
+ keys_manager_c.enable_op_for_all_signers(SignerOp::ReleaseCommitmentSecret);
+ nodes[2].signer_unblocked(None);
+ },
+
0xf0 => {
for id in &chan_ab_ids {
complete_monitor_update(&monitor_a, id, &complete_first);
@@ -2504,6 +2568,15 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(
peers_bc_disconnected = false;
}
+ for op in SUPPORTED_SIGNER_OPS {
+ keys_manager_a.enable_op_for_all_signers(op);
+ keys_manager_b.enable_op_for_all_signers(op);
+ keys_manager_c.enable_op_for_all_signers(op);
+ }
+ nodes[0].signer_unblocked(None);
+ nodes[1].signer_unblocked(None);
+ nodes[2].signer_unblocked(None);
+
macro_rules! process_all_events {
() => { {
let mut last_pass_no_updates = false;
diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs
index 3bacd76..70eb322 100644
--- a/lightning/src/util/test_channel_signer.rs
+++ b/lightning/src/util/test_channel_signer.rs
@@ -103,7 +103,6 @@ pub enum SignerOp {
ReleaseCommitmentSecret,
ValidateHolderCommitment,
SignCounterpartyCommitment,
- ValidateCounterpartyRevocation,
SignHolderCommitment,
SignJusticeRevokedOutput,
SignJusticeRevokedHtlc,
@@ -121,7 +120,6 @@ impl SignerOp {
SignerOp::ReleaseCommitmentSecret,
SignerOp::ValidateHolderCommitment,
SignerOp::SignCounterpartyCommitment,
- SignerOp::ValidateCounterpartyRevocation,
SignerOp::SignHolderCommitment,
SignerOp::SignJusticeRevokedOutput,
SignerOp::SignJusticeRevokedHtlc,
@@ -186,7 +184,7 @@ impl TestChannelSigner {
self.get_enforcement_state().disabled_signer_ops.insert(signer_op);
}
- #[cfg(test)]
+ #[cfg(any(test, feature = "_test_utils"))]
fn is_signer_available(&self, signer_op: SignerOp) -> bool {
!self.get_enforcement_state().disabled_signer_ops.contains(&signer_op)
}
@@ -196,7 +194,7 @@ impl ChannelSigner for TestChannelSigner {
fn get_per_commitment_point(
&self, idx: u64, secp_ctx: &Secp256k1<secp256k1::All>,
) -> Result<PublicKey, ()> {
- #[cfg(test)]
+ #[cfg(any(test, feature = "_test_utils"))]
if !self.is_signer_available(SignerOp::GetPerCommitmentPoint) {
return Err(());
}
@@ -204,7 +202,7 @@ impl ChannelSigner for TestChannelSigner {
}
fn release_commitment_secret(&self, idx: u64) -> Result<[u8; 32], ()> {
- #[cfg(test)]
+ #[cfg(any(test, feature = "_test_utils"))]
if !self.is_signer_available(SignerOp::ReleaseCommitmentSecret) {
return Err(());
}
@@ -236,10 +234,6 @@ impl ChannelSigner for TestChannelSigner {
}
fn validate_counterparty_revocation(&self, idx: u64, _secret: &SecretKey) -> Result<(), ()> {
- #[cfg(test)]
- if !self.is_signer_available(SignerOp::ValidateCounterpartyRevocation) {
- return Err(());
- }
let mut state = self.state.lock().unwrap();
if !self.disable_all_state_policy_checks {
assert!(idx == state.last_counterparty_revoked_commitment || idx == state.last_counterparty_revoked_commitment - 1, "expecting to validate the current or next counterparty revocation - trying {}, current {}", idx, state.last_counterparty_revoked_commitment);
@@ -272,7 +266,7 @@ impl EcdsaChannelSigner for TestChannelSigner {
) -> Result<(Signature, Vec<Signature>), ()> {
self.verify_counterparty_commitment_tx(channel_parameters, commitment_tx, secp_ctx);
- #[cfg(test)]
+ #[cfg(any(test, feature = "_test_utils"))]
if !self.is_signer_available(SignerOp::SignCounterpartyCommitment) {
return Err(());
}
@@ -317,7 +311,7 @@ impl EcdsaChannelSigner for TestChannelSigner {
&self, channel_parameters: &ChannelTransactionParameters,
commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1<secp256k1::All>,
) -> Result<Signature, ()> {
- #[cfg(test)]
+ #[cfg(any(test, feature = "_test_utils"))]
if !self.is_signer_available(SignerOp::SignHolderCommitment) {
return Err(());
}
@@ -354,7 +348,7 @@ impl EcdsaChannelSigner for TestChannelSigner {
input: usize, amount: u64, per_commitment_key: &SecretKey,
secp_ctx: &Secp256k1<secp256k1::All>,
) -> Result<Signature, ()> {
- #[cfg(test)]
+ #[cfg(any(test, feature = "_test_utils"))]
if !self.is_signer_available(SignerOp::SignJusticeRevokedOutput) {
return Err(());
}
@@ -375,7 +369,7 @@ impl EcdsaChannelSigner for TestChannelSigner {
input: usize, amount: u64, per_commitment_key: &SecretKey, htlc: &HTLCOutputInCommitment,
secp_ctx: &Secp256k1<secp256k1::All>,
) -> Result<Signature, ()> {
- #[cfg(test)]
+ #[cfg(any(test, feature = "_test_utils"))]
if !self.is_signer_available(SignerOp::SignJusticeRevokedHtlc) {
return Err(());
}
@@ -396,7 +390,7 @@ impl EcdsaChannelSigner for TestChannelSigner {
&self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor,
secp_ctx: &Secp256k1<secp256k1::All>,
) -> Result<Signature, ()> {
- #[cfg(test)]
+ #[cfg(any(test, feature = "_test_utils"))]
if !self.is_signer_available(SignerOp::SignHolderHtlcTransaction) {
return Err(());
}
@@ -462,7 +456,7 @@ impl EcdsaChannelSigner for TestChannelSigner {
input: usize, amount: u64, per_commitment_point: &PublicKey, htlc: &HTLCOutputInCommitment,
secp_ctx: &Secp256k1<secp256k1::All>,
) -> Result<Signature, ()> {
- #[cfg(test)]
+ #[cfg(any(test, feature = "_test_utils"))]
if !self.is_signer_available(SignerOp::SignCounterpartyHtlcTransaction) {
return Err(());
}
@@ -483,7 +477,7 @@ impl EcdsaChannelSigner for TestChannelSigner {
&self, channel_parameters: &ChannelTransactionParameters, closing_tx: &ClosingTransaction,
secp_ctx: &Secp256k1<secp256k1::All>,
) -> Result<Signature, ()> {
- #[cfg(test)]
+ #[cfg(any(test, feature = "_test_utils"))]
if !self.is_signer_available(SignerOp::SignClosingTransaction) {
return Err(());
}
@@ -504,7 +498,7 @@ impl EcdsaChannelSigner for TestChannelSigner {
anchor_tx.input[input].previous_output.vout == 0
|| anchor_tx.input[input].previous_output.vout == 1
);
- #[cfg(test)]
+ #[cfg(any(test, feature = "_test_utils"))]
if !self.is_signer_available(SignerOp::SignHolderAnchorInput) {
return Err(());
}
Why this scored 13/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.