Skip to content

smite: extend accept_channel oracle with key reuse checks - #259

Open
NishantBansal2003 wants to merge 4 commits into
lnfuzz:masterfrom
NishantBansal2003:more-accept-chan-oracles
Open

NishantBansal2003 wants to merge 4 commits into
lnfuzz:masterfrom
NishantBansal2003:more-accept-chan-oracles

Conversation

@NishantBansal2003

@NishantBansal2003 NishantBansal2003 commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

ref: #209 (comment)

Some more oracles added in this PR:

open_channel

  • chain_hash != regtest
  • channel_reserve_satoshis >= funding_satoshis
  • dust_limit_satoshis > 10_000 sat
  • to_self_delay > 2016
  • feerate_per_kw == 0 for non 0FC channels

accept_channel

  • dust_limit_satoshis > 10_000 sat
  • htlc_minimum_msat > max_htlc_value_in_flight_msat
  • htlc_minimum_msat > open_channel.funding_satoshis
  • to_self_delay == 0
  • max_accepted_htlcs == 0
  • pubkey match any pubkeys from open_channel and accept_channel
  • per_commitment_point should not be reused

Notes

  • CLN defaults to_self_delay to 6 in accept_channel on regtest and 144 on mainnet, so I think we should only check that to_self_delay != 0
  • CLN defaults the feerate_per_kw floor to 1 on regtest and 253 on mainnet when accepting the open_channel message, so I think we should only check that feerate_per_kw != 0

Comment thread smite/src/oracles/accept_channel.rs
Comment thread smite/src/oracles/accept_channel.rs Outdated
Comment thread smite-scenarios/src/executor.rs Outdated
Comment thread smite-scenarios/src/executor.rs Outdated
Comment thread smite/src/oracles/accept_channel.rs Outdated
Comment thread smite-scenarios/src/executor/tests.rs Outdated
Comment thread smite-scenarios/src/executor/tests/harness.rs
Comment thread smite/src/oracles/accept_channel.rs
Comment thread smite/src/oracles/accept_channel.rs Outdated
Record each pubkey sent in open_channel, accept_channel and
channel_ready with its origin (side, channel, field). We may send the
same pubkey more than once, so ours can have several origins. The
target must never send a pubkey already sent by either side, so each
of its pubkeys has exactly one origin.

This prepares for an oracle that detects pubkey reuse by the target.

Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Report accept_channel messages that use a pubkey already revealed by
either side, or share one across their own fields.

Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
@NishantBansal2003 NishantBansal2003 changed the title smite: extend accept_channel oracle with sanity and key reuse checks smite: extend accept_channel oracle with key reuse checks Sep 30, 2026
@NishantBansal2003
NishantBansal2003 marked this pull request as ready for review September 30, 2026 06:48
Comment thread smite-scenarios/src/executor.rs Outdated
Comment thread smite-scenarios/src/executor.rs Outdated
Comment thread smite/src/bolt/types.rs Outdated
Comment thread smite-scenarios/src/executor/tests.rs Outdated
Comment thread smite-scenarios/src/executor/tests.rs Outdated
Comment on lines +451 to +468
// Check that each pubkey is unrevealed.
for (field, pubkey) in &pubkeys {
if let Some(origins) = revealed_pubkeys.get(pubkey) {
let origins: Vec<String> = origins.iter().map(ToString::to_string).collect();
return Err(format!(
"{field} {pubkey} was already sent as {}",
origins.join(", "),
));
}
}

// Check that each pubkey appears in only one field.
let mut fields = HashMap::new();
for (field, pubkey) in &pubkeys {
if let Some(other_field) = fields.insert(pubkey, field) {
return Err(format!("{other_field} and {field} share pubkey {pubkey}"));
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: these loops can be combined.

Suggested change
// Check that each pubkey is unrevealed.
for (field, pubkey) in &pubkeys {
if let Some(origins) = revealed_pubkeys.get(pubkey) {
let origins: Vec<String> = origins.iter().map(ToString::to_string).collect();
return Err(format!(
"{field} {pubkey} was already sent as {}",
origins.join(", "),
));
}
}
// Check that each pubkey appears in only one field.
let mut fields = HashMap::new();
for (field, pubkey) in &pubkeys {
if let Some(other_field) = fields.insert(pubkey, field) {
return Err(format!("{other_field} and {field} share pubkey {pubkey}"));
}
}
// Check that each pubkey is unrevealed and appears in only one field.
let mut seen = HashMap::new();
for (field, pubkey) in accept_channel.pubkeys() {
if let Some(origins) = revealed_pubkeys.get(pubkey) {
let origins: Vec<String> = origins.iter().map(ToString::to_string).collect();
return Err(format!(
"{field} {pubkey} was already sent as {}",
origins.join(", "),
));
}
if let Some(other_field) = seen.insert(pubkey, field) {
return Err(format!("{other_field} and {field} share pubkey {pubkey}"));
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but the reason to keep them separate is that if a pubkey is reused from an earlier negotiation, where its secret has already been revealed (eg in commitment dance), that is a much more serious bug than a key being reused within the same negotiation

Comment thread smite-scenarios/src/executor.rs Outdated
Comment on lines +256 to +260
/// Every pubkey sent on the wire by either side, mapped to each of its
/// origins. We may send the same pubkey multiple times, so ours can have
/// multiple origins. The target must never send a pubkey already sent by
/// either side, so each of theirs has exactly one origin.
revealed_pubkeys: HashMap<PublicKey, Vec<KeyOrigin>>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to track multiple origins? I'd be happy with just the first one getting printed in the error message since it simplifies the code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking is that tracking only the first origin is not really useful, the reasons are:

  • This key-reuse check is likely to be non-deterministic (or maybe it would be deterministic in Bedrock? Not sure)
  • If the target reuses a pubkey from its own previously sent pubkeys, we can be fairly sure that there is an issue somewhere in its key generation in that path. But if the target generates a key that we previously sent to it, I don't think we would be able to pinpoint the issue if we only track the first origin

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's nondeterministic, that probably means the target has weak entropy, in which case I doubt having multiple origins helps.

If the target reuses one of our keys, can't we easily check the program to any additional places we send them our key, if needed?

In every case I can think of, one origin is enough to diagnose the bug.

Comment thread smite-scenarios/src/executor/tests/harness.rs Outdated
Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
@NishantBansal2003
NishantBansal2003 force-pushed the more-accept-chan-oracles branch from 4b5f3b9 to 6ae5679 Compare October 1, 2026 12:29
Comment thread smite-scenarios/src/executor.rs Outdated
Comment on lines +256 to +260
/// Every pubkey sent on the wire by either side, mapped to each of its
/// origins. We may send the same pubkey multiple times, so ours can have
/// multiple origins. The target must never send a pubkey already sent by
/// either side, so each of theirs has exactly one origin.
revealed_pubkeys: HashMap<PublicKey, Vec<KeyOrigin>>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's nondeterministic, that probably means the target has weak entropy, in which case I doubt having multiple origins helps.

If the target reuses one of our keys, can't we easily check the program to any additional places we send them our key, if needed?

In every case I can think of, one origin is enough to diagnose the bug.

Comment thread smite-scenarios/src/executor.rs Outdated
Comment on lines +1269 to +1277
// The target must never send a pubkey already sent by either side, so the
// `second_per_commitment_point` is recorded with this as its only origin.
//
// TODO: Once we have the channel_ready oracle, update this to return a
// violation from the target instead of panicking.
assert!(
!revealed_pubkeys.contains_key(&cr.second_per_commitment_point),
"target sent an already sent pubkey",
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spec actually allows second_per_commitment_point repeats: https://github.com/lightning/bolts/blob/1aadb719b4007c4cea0ba6e36b08c4fb53788dee/02-peer-protocol.md?plain=1#L1091

I think we'll be able to handle that in the channel_ready oracle, but in the meantime we shouldn't panic here. For this PR we should also swap the should_panic executor test with one that ensures channel_ready can be resent with a new alias, and we can add current test back in with the channel_ready oracle.

Also once we implement reconnect I think there will be other spec-compliant ways to get repeated points.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for reuse, we could check the whole origin along with the key. If both the key and origin match the new one, it means the pubkey was simply sent again for the same message and field. Otherwise, it is a violation. Once we have the commitment scenario, we could also add the commitment number to the origin and track and compare the origin with respect to the commitment number.

Comment thread smite-scenarios/src/executor/tests.rs Outdated
Comment on lines +1297 to +1300
let (mut fx, _) = recv_channel_ready_fixture();
let mut sk_bytes = [0u8; 32];
sk_bytes[31] = 1;
let oc = SampleOpenChannel::new(PointSource::Secret(sk_bytes));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let (mut fx, _) = recv_channel_ready_fixture();
let mut sk_bytes = [0u8; 32];
sk_bytes[31] = 1;
let oc = SampleOpenChannel::new(PointSource::Secret(sk_bytes));
let (mut fx, target_pcp) = recv_channel_ready_fixture();
let mut sk_bytes = [0u8; 32];
sk_bytes[31] = 1;
let oc = SampleOpenChannel::new(PointSource::Secret(sk_bytes));
assert_eq!(target_pcp, oc.funding_pubkey);


fx.run(&b.build());
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fixup removed a bunch of test coverage that we should still be able to recover using public-API tests.

  • target's channel_ready reuses accept_channel PCP
  • target's accept_channel reuses accept_channel pubkey
  • target's channel_ready reuses our channel_ready PCP
  • target's accept_channel reuses channel_ready PCP

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added for accept_channel pubkey reuse checks, since we removed the assert for channel_ready pubkey reuse. I will add those checks to the channel_ready oracle

Comment thread smite/src/pending_channel.rs Outdated
Comment on lines 3 to 4
//! Remembers the `open_channel`/`accept_channel` parameters of each channel
//! being established, so later steps can build commitments from them.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doc comments needs update.

to_self_delay: 144,
max_accepted_htlcs: 483,
funding_pubkey: sample_pubkey(1),
funding_pubkey: sample_pubkey(7),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a comment explaining the reason the keys go 7-2-3-4-... instead of 1-2-3-4-...

Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants