Skip to content

Conversation

vincenzopalazzo
Copy link
Collaborator

@vincenzopalazzo vincenzopalazzo commented Sep 9, 2025

This is built on top of a commit from #98 and introduces the vthlc that will be needed for the lightning swaps

This PR is just FYI for the readers who want to double-check my code!

Summary by CodeRabbit

  • New Features
    • Introduced a new Lightning module providing VHTLC construction with validation, six spending paths, Taproot output/address derivation, and access to all script variants.
  • Examples
    • Added a runnable example demonstrating VHTLC setup, script inspection, and address/scriptPubKey retrieval.
  • Tests
    • Added fixture-driven tests covering valid/invalid cases, script encodings, Taproot data, and address generation.
  • Chores
    • Added the new crate to the workspace and a script to generate cross-language test vectors.

Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
Copy link

coderabbitai bot commented Sep 9, 2025

Walkthrough

Adds a new workspace crate ark-lightning with a VHTLC module, exposing script builders and Taproot construction. Introduces crate manifest, library entry, VHTLC implementation, example, a Node.js test-vector generator, and a fixture-driven Rust test suite with JSON fixtures. Updates root Cargo.toml to include the new member.

Changes

Cohort / File(s) Summary
Workspace configuration
Cargo.toml
Adds ark-lightning to [workspace].members; resolver unchanged.
New crate manifest
ark-lightning/Cargo.toml
Creates crate ark-lightning (v0.1.0, edition 2024) with dependencies on ark-core, bitcoin, ark-secp256k1 (as musig), serde, thiserror; dev-deps and an example target declared.
Library modules
ark-lightning/src/lib.rs, ark-lightning/src/vhtlc.rs
Adds crate docs and publicly exposes vhtlc module. Implements VHTLC types (VhtlcError, VhtlcOptions, VhtlcScript), script builders for six paths, Taproot tree construction, scriptPubKey/address derivation, and script map retrieval.
Example and tooling
ark-lightning/examples/vhtlc_example.rs, ark-lightning/scripts/generate_ts_vectors.js
Adds a Rust example demonstrating VHTLC construction and script inspection. Adds a Node.js script to produce TypeScript SDK-based script vectors and a testnet address.
Tests and fixtures
ark-lightning/tests/fixtures/vhtlc.json, ark-lightning/tests/vhtlc_fixtures.rs
Introduces JSON fixtures with valid/invalid cases and taproot/script vectors. Adds Rust tests to validate script hexes, taproot data, address derivation, and input validation against fixtures.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Dev as Developer
  participant V as VhtlcOptions
  participant VS as VhtlcScript
  participant B as bitcoin::script & taproot

  Dev->>V: Construct with keys, preimage, locktimes, delays
  Dev->>VS: new(VhtlcOptions)
  activate VS
  VS->>V: validate()
  V-->>VS: Ok / Error
  alt valid
    VS->>B: build six scripts (claim/refund/... variants)
    VS->>B: assemble Taproot tree (leaves + weights)
    B-->>VS: TaprootSpendInfo (tweaked key)
    VS-->>Dev: VhtlcScript
  else invalid
    VS-->>Dev: VhtlcError
  end
  deactivate VS
Loading
sequenceDiagram
  autonumber
  participant App as App/Test
  participant VS as VhtlcScript
  participant B as bitcoin::address

  App->>VS: claim_script()/refund_script()/...
  VS-->>App: ScriptBuf

  App->>VS: script_pubkey()
  VS-->>App: Option<ScriptBuf>

  App->>VS: address(Network, serverXOnly)
  VS->>B: derive address from tweaked key + server
  B-->>VS: ArkAddress
  VS-->>App: Option<ArkAddress>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • luckysori

Pre-merge checks (3 passed)

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title “lightning: implement the htlc script” directly reflects the main addition of the pull request—the implementation of the vHTLC (hash time-lock contract) module for Lightning swaps—and succinctly conveys the primary change without extraneous details.
Description Check ✅ Passed The description clearly states that the PR builds on a prior commit and introduces the vHTLC functionality needed for Lightning swaps, which directly corresponds to the changes in the codebase.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch macros/lightning-swapsv2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (17)
ark-lightning/Cargo.toml (1)

1-4: Confirm toolchain supports edition = "2024"

If CI uses an older stable, the build will fail. Pin a compatible toolchain via rust-toolchain.toml to avoid surprises.

Example rust-toolchain.toml:

[toolchain]
channel = "stable" # or a specific version known to support edition 2024
components = ["clippy", "rustfmt"]
ark-lightning/scripts/generate_ts_vectors.js (2)

1-6: Make SDK path configurable + add shebang/strict mode

Improves portability when running from different repos/dirs.

+#!/usr/bin/env node
+'use strict';
 
-const { VhtlcScript } = require('../path/to/ts-sdk'); // Adjust path as needed
+const sdkPath = process.argv[2] || '../path/to/ts-sdk'; // allow CLI override
+const { VhtlcScript } = require(sdkPath); // e.g.: node generate_ts_vectors.js ../ts-sdk/dist

52-58: Return non-zero exit code on failure

So CI/automation can detect failures.

 } catch (error) {
   console.error('Error generating TypeScript vectors:', error);
   console.log('\nPlease ensure:');
   console.log('1. You are running this in the arkade-os/ts-sdk directory');
   console.log('2. The path to VhtlcScript is correct');
   console.log('3. All dependencies are installed (npm install)');
+  process.exitCode = 1;
 }
ark-lightning/tests/fixtures/vhtlc.json (2)

130-172: Invalid-fixture error messages likely don’t match Rust errors

Your fixtures expect messages like “preimage hash must be 20 bytes”, but VhtlcError currently formats as “Invalid preimage hash length: expected 20 bytes, got N”. Align either the fixtures or the Rust errors, or assert substrings instead of exact string equality.


31-35: Gitleaks false positives on public test keys

Detections on tweaked/internal pubkeys are harmless test vectors. Silence them via an allowlist for ark-lightning/tests/fixtures/*.json to keep CI green.

Example .gitleaks.toml snippet:

[allowlist]
paths = [
  "ark-lightning/tests/fixtures/.*\\.json"
]

Also applies to: 74-77, 116-119

ark-lightning/src/vhtlc.rs (6)

151-161: Fix Clippy: remove needless borrows in push_slice

Satisfies CI and avoids extra references.

             .push_opcode(OP_HASH160)
-            .push_slice(&self.options.preimage_hash)
+            .push_slice(self.options.preimage_hash)
             .push_opcode(OP_EQUAL)
@@
             .push_opcode(OP_HASH160)
-            .push_slice(&self.options.preimage_hash)
+            .push_slice(self.options.preimage_hash)
             .push_opcode(OP_EQUAL)

Also applies to: 197-209


274-278: Replace unwrap() with expect() to appease Clippy and document invariants

These unwraps are safe by construction; use expect with brief context.

-            let b = lst.pop().unwrap();
-            let a = lst.pop().unwrap();
+            let b = lst.pop().expect("pop b: lst.len() >= 2 by loop condition");
+            let a = lst.pop().expect("pop a: lst.len() >= 2 by loop condition");
@@
-        Ok(lst.into_iter().next().unwrap())
+        Ok(lst
+            .into_iter()
+            .next()
+            .expect("non-empty: checked at function start"))

Also applies to: 296-298


381-383: Check address() return type and remove redundant .into()

If ArkAddress::new(...) returns Option<ArkAddress>, .into() is redundant. If it returns ArkAddress, wrap with Some(...). Please confirm and simplify accordingly.


74-116: Optional: tighten validation to match fixtures and BIP semantics

  • Consider enforcing CLTV domain (height vs timestamp) for refund_locktime and rejecting ambiguous values.
  • If seconds-based CSV is allowed, ensure multiples of 512 and a minimum of 512 seconds when the “seconds” bit is set (BIP68).

240-358: Deterministic tie-break for equal weights in TapTree

All weights are 1, so ordering is driven by insertion order. To reduce cross-impl drift when order changes, add a secondary sort key (e.g., script bytes or leaf-hash) when weights are equal.


23-35: Remove unused error variant or wire it up

InvalidPublicKeyLength isn’t used. Either validate incoming keys and use it, or drop the variant.

ark-lightning/src/lib.rs (1)

5-6: Re-export primary types from the crate root for ergonomic imports

This lets downstreams do use ark_lightning::{VhtlcScript, VhtlcOptions, VhtlcError};.

 pub mod vhtlc;
+
+// Convenient re-exports
+pub use vhtlc::{VhtlcError, VhtlcOptions, VhtlcScript};
ark-lightning/examples/vhtlc_example.rs (1)

7-56: Prefer fallible example with ? over unwrap/expect and also show address derivation

Improves robustness and demonstrates a full flow including address generation.

-use bitcoin::Sequence;
+use bitcoin::{Network, Sequence};
 use bitcoin::XOnlyPublicKey;
 use std::str::FromStr;
 
-fn main() {
+fn main() -> Result<(), Box<dyn std::error::Error>> {
     // Create test keys for sender, receiver, and server
-    let sender = XOnlyPublicKey::from_str(
+    let sender = XOnlyPublicKey::from_str(
         "18845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166",
-    )
-    .unwrap();
+    )?;
 
-    let receiver = XOnlyPublicKey::from_str(
+    let receiver = XOnlyPublicKey::from_str(
         "28845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166",
-    )
-    .unwrap();
+    )?;
 
-    let server = XOnlyPublicKey::from_str(
+    let server = XOnlyPublicKey::from_str(
         "38845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166",
-    )
-    .unwrap();
+    )?;
 
     // Create a preimage hash (in a real scenario, this would be the hash of a secret)
     let preimage_hash = [42u8; 20];
 
     // Configure the VHTLC options
     let options = VhtlcOptions {
         sender,
         receiver,
         server,
         preimage_hash,
         refund_locktime: 100000, // Block height for CLTV
-        unilateral_claim_delay: Sequence::from_seconds_ceil(3600).unwrap(), // 1 hour
-        unilateral_refund_delay: Sequence::from_seconds_ceil(7200).unwrap(), // 2 hours
-        unilateral_refund_without_receiver_delay: Sequence::from_seconds_ceil(10800).unwrap(), /* 3 hours */
+        unilateral_claim_delay: Sequence::from_seconds_ceil(3600)?, // 1 hour
+        unilateral_refund_delay: Sequence::from_seconds_ceil(7200)?, // 2 hours
+        unilateral_refund_without_receiver_delay: Sequence::from_seconds_ceil(10800)?, /* 3 hours */
     };
 
     // Create the VHTLC script
-    let vhtlc = VhtlcScript::new(options).expect("Failed to create VHTLC");
+    let vhtlc = VhtlcScript::new(options)?;
 
     // Get the taproot output key and script pubkey
     if let Some(taproot_info) = vhtlc.taproot_info() {
         println!("Taproot output key: {}", taproot_info.output_key());
 
         if let Some(script_pubkey) = vhtlc.script_pubkey() {
             println!("Script pubkey: {}", script_pubkey);
         }
     }
 
+    // Derive and print a testnet address
+    let addr = vhtlc.address(Network::Testnet, server)?;
+    println!("Address (testnet): {}", addr);
+
+    Ok(())
 }
ark-lightning/tests/vhtlc_fixtures.rs (4)

376-380: Make the CSV encoding assertion robust

String-matching "0111" is brittle. Decode the script and assert the pushed integer equals 17 before OP_CSV.

Example approach:

use bitcoin::opcodes::all::OP_CHECKSEQUENCEVERIFY as OP_CSV;
use bitcoin::script::Instruction;

let mut iter = unilateral_claim.instructions();
let (push, csv) = (iter.next().unwrap()?, iter.nth(5).unwrap()?); // adjust index to your layout
match push {
    Instruction::PushBytes(bytes) => assert_eq!(bytes, &[0x11]),
    Instruction::PushNum(n) => assert_eq!(n, 17.into()),
    _ => panic!("Expected CSV delay push"),
}
assert!(matches!(csv, Instruction::Op(op) if op == OP_CSV));

250-254: Use to_string() for address formatting

Address::to_string() is canonical and avoids any ambiguity around encode().

-        let address_str = addr.encode();
+        let address_str = addr.to_string();

38-40: Remove unused decoded_scripts from fixtures or assert on it

Currently parsed but unused; either drop it from ValidTestCase or validate expected disassembly to prevent fixture drift.


135-139: Embed fixtures at compile time to avoid path issues

Optional: use include_str! to make tests hermetic to the working directory.

let fixtures_json = include_str!("fixtures/vhtlc.json");
let fixtures: Fixtures = serde_json::from_str(fixtures_json).expect("Failed to parse fixtures");
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aee5459 and 48c921d.

📒 Files selected for processing (8)
  • Cargo.toml (1 hunks)
  • ark-lightning/Cargo.toml (1 hunks)
  • ark-lightning/examples/vhtlc_example.rs (1 hunks)
  • ark-lightning/scripts/generate_ts_vectors.js (1 hunks)
  • ark-lightning/src/lib.rs (1 hunks)
  • ark-lightning/src/vhtlc.rs (1 hunks)
  • ark-lightning/tests/fixtures/vhtlc.json (1 hunks)
  • ark-lightning/tests/vhtlc_fixtures.rs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
ark-lightning/examples/vhtlc_example.rs (2)
ark-lightning/scripts/generate_ts_vectors.js (1)
  • vhtlc (20-20)
ark-lightning/src/vhtlc.rs (3)
  • new (137-145)
  • taproot_info (368-370)
  • script_pubkey (372-379)
ark-lightning/src/lib.rs (1)
ark-lightning/scripts/generate_ts_vectors.js (1)
  • vhtlc (20-20)
ark-lightning/scripts/generate_ts_vectors.js (1)
ark-lightning/src/vhtlc.rs (1)
  • address (381-383)
ark-lightning/tests/vhtlc_fixtures.rs (2)
ark-lightning/src/vhtlc.rs (8)
  • new (137-145)
  • claim_script (150-161)
  • refund_script (166-175)
  • refund_without_receiver_script (180-190)
  • unilateral_claim_script (195-208)
  • unilateral_refund_script (213-224)
  • unilateral_refund_without_receiver_script (229-238)
  • taproot_info (368-370)
ark-core/src/boarding_output.rs (1)
  • output_key (111-113)
ark-lightning/src/vhtlc.rs (1)
ark-lightning/scripts/generate_ts_vectors.js (1)
  • address (48-48)
🪛 Gitleaks (8.27.2)
ark-lightning/tests/fixtures/vhtlc.json

[high] 34-34: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 76-76: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 118-118: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🪛 GitHub Actions: CI
ark-lightning/src/vhtlc.rs

[error] 153-153: Clippy: needlessly_borrows_for_generic_args. Remove the borrow: change .push_slice(&self.options.preimage_hash) to .push_slice(self.options.preimage_hash).


[error] 199-199: Clippy: needlessly_borrows_for_generic_args. Remove the borrow: change .push_slice(&self.options.preimage_hash) to .push_slice(self.options.preimage_hash).


[error] 275-275: Clippy: unwrap_used. Used unwrap() on an Option value; this may panic. Consider using expect() or proper handling of None.


[error] 276-276: Clippy: unwrap_used. Used unwrap() on an Option value; this may panic. Consider using expect() or proper handling of None.


[error] 297-297: Clippy: unwrap_used. Used unwrap() on an Option value; this may panic. Consider using expect() or proper handling of None.

🔇 Additional comments (3)
Cargo.toml (1)

13-14: LGTM: add ark-lightning to workspace members

Workspace wiring looks correct.

ark-lightning/scripts/generate_ts_vectors.js (1)

47-50: Verify address API parity with Rust

TS uses vhtlc.address('testnet'), while Rust’s address requires (network, server: XOnlyPublicKey). Ensure both derive the same address for the same inputs, or document the discrepancy.

ark-lightning/src/lib.rs (1)

1-5: LGTM: minimal, clear module exposure

Publicly exposing vhtlc from the crate root looks good.

Comment on lines +97 to +103
"blocks" => {
if self.value == 0 {
return Err("unilateral claim delay must greater than 0".to_string());
}
Ok(Sequence::from_height(self.value as u16))
}
"seconds" => {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix potential u16 wrap when converting block delays

Sequence::from_height(self.value as u16) will silently wrap for values > 65535. Add an upper bound check and improve the error text.

-            "blocks" => {
-                if self.value == 0 {
-                    return Err("unilateral claim delay must greater than 0".to_string());
-                }
-                Ok(Sequence::from_height(self.value as u16))
-            }
+            "blocks" => {
+                if self.value == 0 {
+                    return Err("blocks timelock must be greater than 0".to_string());
+                }
+                if self.value > u16::MAX as u32 {
+                    return Err(format!("blocks timelock must be <= {}", u16::MAX));
+                }
+                Ok(Sequence::from_height(self.value as u16))
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"blocks" => {
if self.value == 0 {
return Err("unilateral claim delay must greater than 0".to_string());
}
Ok(Sequence::from_height(self.value as u16))
}
"seconds" => {
"blocks" => {
if self.value == 0 {
return Err("blocks timelock must be greater than 0".to_string());
}
if self.value > u16::MAX as u32 {
return Err(format!("blocks timelock must be <= {}", u16::MAX));
}
Ok(Sequence::from_height(self.value as u16))
}
🤖 Prompt for AI Agents
In ark-lightning/tests/vhtlc_fixtures.rs around lines 97 to 103, the conversion
Sequence::from_height(self.value as u16) can wrap for values > 65535; add an
explicit upper-bound check (if self.value > u16::MAX) and return a clear Err
like "unilateral claim delay must be between 1 and 65535" (preserving the
existing zero check) otherwise safely cast to u16 and call
Sequence::from_height.

@luckysori
Copy link
Collaborator

This PR is just FYI for the readers who want to double-check my code!

@vincenzopalazzo: Since you mention that this is just informational, let's close it. Anyone can still find it even if it's closed.

@luckysori luckysori closed this Sep 18, 2025
@vincenzopalazzo
Copy link
Collaborator Author

Mh @luckysori yeah probably it dependes if you want review a big blob or a sequence of patch but anyway I am fine

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