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
74 changes: 74 additions & 0 deletions smite-ir/src/generators/announcement_signatures.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//! Generator for `announcement_signatures` message flow.

use rand::Rng;

use super::Generator;
use super::funding_flow::append_funding_flow;
use crate::builder::ProgramBuilder;
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 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) {
// Open, fund, and confirm the channel. It is announced, since targets
// ignore `announcement_signatures` for a private one. The funding
// secret signs the announcement as `bitcoin_key_1`, and the target
// announces the acceptor's funding key as `bitcoin_key_2`.
let funding = append_funding_flow(builder, rng, true);
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.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,
&[
funding.channel_id,
features,
chain_hash,
short_channel_id,
node_sk,
target_node_id,
funding.funding_privkey,
funding.acceptor_funding_pubkey,
],
);
builder.append(Operation::SendMessage, &[msg]);
}
}
168 changes: 107 additions & 61 deletions smite-ir/src/generators/funding_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,68 +18,114 @@ use crate::{Operation, VariableType};
#[derive(Clone, Copy)]
pub struct FundingFlowGenerator;

impl FundingFlowGenerator {
/// Blocks mined to confirm an announced channel's funding transaction.
/// 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 ANNOUNCED_MIN_DEPTH_BLOCKS: u8 = 8;
}

/// Variables produced by [`append_funding_flow`] that callers may reuse.
pub struct FundingFlowVars {
/// Our funding private key, committed to by the funding output.
pub funding_privkey: usize,
/// The acceptor's funding public key from `accept_channel`.
pub acceptor_funding_pubkey: usize,
/// The confirmed funding transaction.
pub funding_transaction: usize,
/// The channel id returned by `funding_signed`.
pub channel_id: usize,
}

/// Appends the complete v1 outbound channel funding flow to `builder`.
///
/// If `announce` is set, the channel is opened as announced, its funding
/// transaction is mined deep enough to be announced, and `channel_ready`
/// carries no alias, since an announced channel is reached by its real
/// `short_channel_id`.
pub fn append_funding_flow(
builder: &mut ProgramBuilder,
rng: &mut impl Rng,
announce: bool,
) -> FundingFlowVars {
// The funding key pair is generated fresh so the funding transaction
// can later be signed with the key `open_channel` commits to.
let funding_privkey = builder.generate_fresh(VariableType::PrivateKey, rng);
let funding_pubkey = builder.append(Operation::DerivePoint, &[funding_privkey]);

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

// Receive accept_channel.
let accept_channel = builder.append(
Operation::RecvAcceptChannel,
&[open_channel.sent_open_channel],
);
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,
funding_privkey,
open_channel.temporary_channel_id,
],
);

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

// Broadcast the funding transaction.
builder.append(Operation::BroadcastTransaction, &[funding_transaction]);

// Mine blocks to confirm the funding transaction.
let blocks = if announce {
FundingFlowGenerator::ANNOUNCED_MIN_DEPTH_BLOCKS
} else {
rng.random_range(1..=16)
};
builder.append(Operation::MineBlocks(blocks), &[]);

// Channel ready parameters.
let second_per_commitment_point = builder.generate_fresh(VariableType::Point, rng);
let short_channel_id = builder.generate_fresh(VariableType::ShortChannelId, rng);
let include_alias = !announce && rng.random();

// Build and send channel_ready.
builder.append(
Operation::SendChannelReady { include_alias },
&[channel_id, second_per_commitment_point, short_channel_id],
);

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

FundingFlowVars {
funding_privkey,
acceptor_funding_pubkey,
funding_transaction,
channel_id,
}
}

impl Generator for FundingFlowGenerator {
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.
let funding_privkey = builder.generate_fresh(VariableType::PrivateKey, rng);
let funding_pubkey = builder.append(Operation::DerivePoint, &[funding_privkey]);

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

// Receive accept_channel.
let accept_channel = builder.append(
Operation::RecvAcceptChannel,
&[open_channel.sent_open_channel],
);
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,
funding_privkey,
open_channel.temporary_channel_id,
],
);

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

// Broadcast the funding transaction.
builder.append(Operation::BroadcastTransaction, &[funding_transaction]);

// Mine blocks to confirm the funding transaction.
builder.append(Operation::MineBlocks(rng.random_range(1..=16)), &[]);

// Channel ready parameters.
let second_per_commitment_point = builder.generate_fresh(VariableType::Point, rng);
let short_channel_id = builder.generate_fresh(VariableType::ShortChannelId, rng);
let include_alias = rng.random();

// Build and send channel_ready.
builder.append(
Operation::SendChannelReady { include_alias },
&[channel_id, second_per_commitment_point, short_channel_id],
);

// Receive channel_ready.
builder.append(Operation::RecvChannelReady, &[]);
append_funding_flow(builder, rng, false);
}
}
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
Loading