Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions smite-ir/src/generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//! protocol flow but delegates value selection and variable reuse to
//! `ProgramBuilder`.

mod announcement_signatures;
mod channel_announcement;
mod channel_ready;
mod channel_update;
Expand All @@ -13,6 +14,7 @@ mod funding_flow;
mod node_announcement;
mod open_channel;

pub use announcement_signatures::AnnouncementSignaturesGenerator;
pub use channel_announcement::ChannelAnnouncementGenerator;
pub use channel_ready::ChannelReadyGenerator;
pub use channel_update::ChannelUpdateGenerator;
Expand All @@ -35,6 +37,7 @@ pub trait Generator {
/// here may be used by the custom mutator library.
#[derive(Clone, Copy)]
pub enum AnyGenerator {
AnnouncementSignatures(AnnouncementSignaturesGenerator),
ChannelAnnouncement(ChannelAnnouncementGenerator),
ChannelUpdate(ChannelUpdateGenerator),
NodeAnnouncement(NodeAnnouncementGenerator),
Expand All @@ -47,6 +50,7 @@ pub enum AnyGenerator {
impl AnyGenerator {
/// All variants. Keep in sync with the enum definition.
pub const ALL: &[Self] = &[
Self::AnnouncementSignatures(AnnouncementSignaturesGenerator),
Self::ChannelAnnouncement(ChannelAnnouncementGenerator),
Self::ChannelUpdate(ChannelUpdateGenerator),
Self::NodeAnnouncement(NodeAnnouncementGenerator),
Expand All @@ -60,6 +64,7 @@ impl AnyGenerator {
impl Generator for AnyGenerator {
fn generate(&self, builder: &mut ProgramBuilder, rng: &mut impl Rng) {
match self {
Self::AnnouncementSignatures(generator) => generator.generate(builder, rng),
Self::ChannelAnnouncement(generator) => generator.generate(builder, rng),
Self::ChannelUpdate(generator) => generator.generate(builder, rng),
Self::NodeAnnouncement(generator) => generator.generate(builder, rng),
Expand Down
138 changes: 138 additions & 0 deletions smite-ir/src/generators/announcement_signatures.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//! Generator for `announcement_signatures` message flow.

use rand::Rng;

use super::Generator;
use super::open_channel::append_open_channel;
use crate::builder::ProgramBuilder;
use crate::operation::AcceptChannelField;
use crate::{Operation, VariableType};

/// Generates an announced channel and signs its `channel_announcement`.
///
/// Emits instructions to:
/// 1. Open, fund, and confirm an announced channel
/// 2. Complete the `channel_ready` exchange
/// 3. Look up the `short_channel_id` of the confirmed funding output
/// 4. Build and send `announcement_signatures`
///
/// The signatures cover the `channel_announcement` body the target rebuilds
/// for itself, so they only verify if every field matches what the target
/// already knows: the channel's real `short_channel_id`, our node identity,
/// and the funding keys the channel was opened with.
#[derive(Clone, Copy)]
pub struct AnnouncementSignaturesGenerator;

impl AnnouncementSignaturesGenerator {
/// Blocks mined before the `channel_ready` exchange. Eight is the deepest
/// default `minimum_depth` across the targets, which is what gates their
/// `channel_ready`, and it also clears the six confirmations BOLT 7
/// requires before a channel may be announced.
pub const MIN_DEPTH_BLOCKS: u8 = 8;

/// Blocks mined after the `channel_ready` exchange. Targets re-check
/// whether a channel may be announced as new blocks arrive, so give them
/// one while the channel is usable.
pub const POST_READY_BLOCKS: u8 = 1;
}

impl Generator for AnnouncementSignaturesGenerator {
fn generate(&self, builder: &mut ProgramBuilder, rng: &mut impl Rng) {
// The funding key pair is generated fresh so the funding transaction
// can later be signed with the key `open_channel` commits to. Its
// secret signs the announcement as well, as `bitcoin_key_1`.
let bitcoin_sk = builder.generate_fresh(VariableType::PrivateKey, rng);
let funding_pubkey = builder.append(Operation::DerivePoint, &[bitcoin_sk]);

// Build and send open_channel. The channel is announced, since targets
// ignore `announcement_signatures` for a private one.
let open_channel = append_open_channel(builder, rng, funding_pubkey, true);

// Receive accept_channel.
let accept_channel = builder.append(
Operation::RecvAcceptChannel,
&[open_channel.sent_open_channel],
);

// The target announces this key as `bitcoin_key_2`, so the funding
// output has to be the one committing to it.
let acceptor_funding_pubkey = builder.append(
Operation::ExtractAcceptChannel(AcceptChannelField::FundingPubkey),
&[accept_channel],
);

// Create the BOLT 3 funding transaction.
let funding_transaction = builder.append(
Operation::CreateFundingTransaction,
&[
funding_pubkey,
acceptor_funding_pubkey,
open_channel.funding_satoshis,
open_channel.feerate_per_kw,
],
);

// Build and send funding_created.
let sent_funding_created = builder.append(
Operation::SendFundingCreated,
&[
funding_transaction,
bitcoin_sk,
open_channel.temporary_channel_id,
],
);

// Receive funding_signed.
let channel_id = builder.append(Operation::RecvFundingSigned, &[sent_funding_created]);

// Broadcast the funding transaction and confirm it.
builder.append(Operation::BroadcastTransaction, &[funding_transaction]);
builder.append(Operation::MineBlocks(Self::MIN_DEPTH_BLOCKS), &[]);

// Build and send channel_ready. An announced channel is reached by its
// real short_channel_id, so no alias is sent.
let second_per_commitment_point = builder.generate_fresh(VariableType::Point, rng);
let alias = builder.generate_fresh(VariableType::ShortChannelId, rng);
builder.append(
Operation::SendChannelReady {
include_alias: false,
},
&[channel_id, second_per_commitment_point, alias],
);

// Receive channel_ready.
builder.append(Operation::RecvChannelReady, &[]);
builder.append(Operation::MineBlocks(Self::POST_READY_BLOCKS), &[]);

// The announcement covers the channel's real short_channel_id.
let short_channel_id =
builder.append(Operation::LookupShortChannelId, &[funding_transaction]);

// Targets rebuild the announcement body with empty channel features,
// so any other value changes the digest and fails every signature
// check.
let features = builder.append(Operation::LoadFeatures(Vec::new()), &[]);
let chain_hash = builder.pick_variable(VariableType::ChainHash, rng);

// Our node secret comes from the context because the target verifies
// `node_signature` against the identity we handshook with.
let node_sk = builder.append(Operation::LoadLocalNodeSecretFromContext, &[]);
let target_node_id = builder.append(Operation::LoadTargetPubkeyFromContext, &[]);

// Build and send announcement_signatures.
let msg = builder.append(
Operation::BuildAnnouncementSignatures,
&[
channel_id,
features,
chain_hash,
short_channel_id,
node_sk,
target_node_id,
bitcoin_sk,
acceptor_funding_pubkey,
],
);
builder.append(Operation::SendMessage, &[msg]);
}
}
2 changes: 1 addition & 1 deletion smite-ir/src/generators/funding_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ impl Generator for FundingFlowGenerator {
let funding_pubkey = builder.append(Operation::DerivePoint, &[funding_privkey]);

// Build and send open_channel.
let open_channel = append_open_channel(builder, rng, funding_pubkey);
let open_channel = append_open_channel(builder, rng, funding_pubkey, false);

// Receive accept_channel.
let accept_channel = builder.append(
Expand Down
45 changes: 37 additions & 8 deletions smite-ir/src/generators/open_channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,25 @@ impl OpenChannelGenerator {
/// and CLN allow up to 483, while LDK and Eclair cap 0FC channels at 114
/// due to the v3 package size limit.
pub const MAX_MAX_ACCEPTED_HTLCS: u16 = 114;
/// Keep channels unannounced: clearing `announce_channel` keeps
/// `option_scid_alias` valid, while LDK and LND reject announced channels
/// that negotiate it.
pub const CHANNEL_FLAGS: u8 = 0;
/// BOLT 2 `channel_flags` bit 0. Set, the channel is announced to the
/// network, which every target requires before it acts on
/// `announcement_signatures`. Clear, the channel stays private, which keeps
/// `option_scid_alias` valid: LDK and LND reject announced channels that
/// negotiate it. All other bits are undefined and must stay zero.
pub const ANNOUNCE_CHANNEL_FLAG: u8 = 0b0000_0001;

/// Channel types an announced channel may negotiate. LDK and LND reject an
/// announced channel whose type includes `option_scid_alias` (bit 46) or
/// `option_zeroconf` (bit 50), since neither has an announceable
/// `short_channel_id`.
pub const ANNOUNCEABLE_CHANNEL_TYPES: &[ChannelTypeVariant] = &[
ChannelTypeVariant::StaticRemoteKey,
ChannelTypeVariant::Anchors,
ChannelTypeVariant::ZeroFeeCommitments,
ChannelTypeVariant::SimpleTaproot,
ChannelTypeVariant::SimpleTaprootStaging,
ChannelTypeVariant::ScriptEnforcedLease,
];
}

/// Instruction indices produced by [`append_open_channel`], for later
Expand All @@ -84,10 +99,14 @@ pub struct OpenChannelVars {

/// Appends the instructions that generate bounded channel parameters, then
/// build and send `open_channel` using `funding_pubkey`.
///
/// When `announce` is set the channel is opened as an announced one, which the
/// gossip flows need and which restricts the channel types it may negotiate.
pub fn append_open_channel(
builder: &mut ProgramBuilder,
rng: &mut impl Rng,
funding_pubkey: usize,
announce: bool,
) -> OpenChannelVars {
type Bounds = OpenChannelGenerator;

Expand Down Expand Up @@ -146,13 +165,23 @@ pub fn append_open_channel(
),
&[],
);
let channel_flags = builder.append(Operation::LoadU8(Bounds::CHANNEL_FLAGS), &[]);
let flags = if announce {
Bounds::ANNOUNCE_CHANNEL_FLAG
} else {
0
};
let channel_flags = builder.append(Operation::LoadU8(flags), &[]);
let shutdown_script_variant = ShutdownScriptVariant::random(rng);
let upfront_shutdown_script =
builder.append(Operation::LoadShutdownScript(shutdown_script_variant), &[]);
let variant = *ChannelTypeVariant::ALL
let channel_types = if announce {
Bounds::ANNOUNCEABLE_CHANNEL_TYPES
} else {
ChannelTypeVariant::ALL
};
let variant = *channel_types
.choose(rng)
.expect("ChannelTypeVariant::ALL is non-empty");
.expect("channel type list is non-empty");
let channel_type = builder.append(Operation::LoadChannelType(variant), &[]);

// Build and send open_channel.
Expand Down Expand Up @@ -198,7 +227,7 @@ impl Generator for OpenChannelGenerator {
let funding_pubkey = builder.generate_fresh(VariableType::Point, rng);

// Build and send open_channel.
let open_channel = append_open_channel(builder, rng, funding_pubkey);
let open_channel = append_open_channel(builder, rng, funding_pubkey, false);

// Receive accept_channel.
builder.append(
Expand Down
1 change: 1 addition & 0 deletions smite-ir/src/mutators/operation_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ fn mutate_operation(op: &mut Operation, rng: &mut impl Rng) -> bool {
| Operation::CreateFundingTransaction
| Operation::LoadTargetPubkeyFromContext
| Operation::LoadChainHashFromContext
| Operation::LoadLocalNodeSecretFromContext
| Operation::BuildOpenChannel
| Operation::BuildChannelAnnouncement
| Operation::BuildChannelUpdate
Expand Down
16 changes: 15 additions & 1 deletion smite-ir/src/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ pub enum Operation {
LoadTargetPubkeyFromContext,
/// Load the chain hash from the program context.
LoadChainHashFromContext,
/// Load our node's secret key from the program context. This is the Noise
/// static key the connection was established with, so it is the identity
/// targets verify our gossip signatures against.
LoadLocalNodeSecretFromContext,

// -- Compute: derive a variable from inputs --
/// Derive a compressed public key from a private key. The executor
Expand Down Expand Up @@ -532,6 +536,9 @@ impl fmt::Display for Operation {
Self::LoadChannelType(v) => write!(f, "LoadChannelType({v})"),
Self::LoadTargetPubkeyFromContext => write!(f, "LoadTargetPubkeyFromContext()"),
Self::LoadChainHashFromContext => write!(f, "LoadChainHashFromContext()"),
Self::LoadLocalNodeSecretFromContext => {
write!(f, "LoadLocalNodeSecretFromContext()")
}
// Operations with inputs: parens added by Program::Display.
Self::DerivePoint => write!(f, "DerivePoint"),
Self::ExtractAcceptChannel(field) => write!(f, "Extract{field}"),
Expand Down Expand Up @@ -581,7 +588,9 @@ impl Operation {
Self::LoadU8(_) => Some(VariableType::U8),
Self::LoadBytes(_) | Self::LoadShutdownScript(_) => Some(VariableType::Bytes),
Self::LoadFeatures(_) | Self::LoadChannelType(_) => Some(VariableType::Features),
Self::LoadPrivateKey(_) => Some(VariableType::PrivateKey),
Self::LoadPrivateKey(_) | Self::LoadLocalNodeSecretFromContext => {
Some(VariableType::PrivateKey)
}
Self::LoadChannelId(_) | Self::RecvFundingSigned => Some(VariableType::ChannelId),
Self::LoadTargetPubkeyFromContext | Self::DerivePoint => Some(VariableType::Point),
Self::LoadChainHashFromContext => Some(VariableType::ChainHash),
Expand Down Expand Up @@ -625,6 +634,7 @@ impl Operation {
| Self::LoadChannelType(_)
| Self::LoadTargetPubkeyFromContext
| Self::LoadChainHashFromContext
| Self::LoadLocalNodeSecretFromContext
| Self::RecvChannelReady
| Self::MineBlocks(_) => vec![],

Expand Down Expand Up @@ -748,6 +758,7 @@ impl Operation {
| Self::LoadChannelType(_)
| Self::LoadTargetPubkeyFromContext
| Self::LoadChainHashFromContext
| Self::LoadLocalNodeSecretFromContext
| Self::DerivePoint
| Self::ExtractAcceptChannel(_)
| Self::CreateFundingTransaction
Expand Down Expand Up @@ -795,6 +806,7 @@ impl Operation {
| Self::LoadChannelType(_)
| Self::LoadTargetPubkeyFromContext
| Self::LoadChainHashFromContext
| Self::LoadLocalNodeSecretFromContext
| Self::DerivePoint
| Self::ExtractAcceptChannel(_)
| Self::BuildOpenChannel
Expand Down Expand Up @@ -843,6 +855,7 @@ impl Operation {
| Self::LoadChannelType(_)
| Self::LoadTargetPubkeyFromContext
| Self::LoadChainHashFromContext
| Self::LoadLocalNodeSecretFromContext
| Self::DerivePoint
| Self::ExtractAcceptChannel(_)
| Self::BuildOpenChannel
Expand Down Expand Up @@ -906,6 +919,7 @@ impl Operation {

Self::LoadTargetPubkeyFromContext
| Self::LoadChainHashFromContext
| Self::LoadLocalNodeSecretFromContext
| Self::DerivePoint
| Self::CreateFundingTransaction
| Self::BuildOpenChannel
Expand Down
Loading