Skip to content

Conversation

vincenzopalazzo
Copy link
Collaborator

@vincenzopalazzo vincenzopalazzo commented Aug 4, 2025

replacing #88 and building on the v7 API

Summary by CodeRabbit

  • New Features
    • Introduced ArkNote: create, encode/decode, and redeem notes; integrates into batching and off-chain addresses.
    • Added virtual UTXO scripting utilities and address generation.
    • Expanded gRPC with an Admin API (including note creation and admin insights).
    • Client gains methods to create notes and redeem one or many.
  • Tests
    • Added end-to-end test covering ArkNote creation and redemption.
    • Added test vectors for addresses and notes.
  • Chores
    • CI updated to allow e2e tests to run independently of linting.
    • New integration-tests task for clean environment runs.

Copy link

coderabbitai bot commented Aug 4, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds ArkNote feature across core, client, and gRPC: new ArkNote type and VirtualUtxoScript, proof-of-funds preimage path, client methods to create and redeem notes, admin gRPC proto and client wiring, E2E test, Cargo deps, CI workflow tweak, and a new integration-tests Just target.

Changes

Cohort / File(s) Summary of changes
CI workflow
.github/workflows/ci.yml
Removed e2e-tests dependency on clippy; jobs can run in parallel.
Client batch/join integration
ark-client/src/batch.rs, ark-client/src/lib.rs
Integrated ArkNote as batch input; extended join_next_batch signature/visibility; conditioned forfeit submission; added Client APIs: redeem_notes, redeem_note, create_arknote; adjusted callers.
Core: ArkNote and VTXO scripting
ark-core/src/arknote.rs, ark-core/src/vtxo.rs, ark-core/src/lib.rs, ark-core/src/proof_of_funds.rs, ark-core/Cargo.toml, ark-core/test_vectors.json
Introduced ArkNote type, encoding/decoding, status, txout/outpoint; added VirtualUtxoScript; re-exported modules; extended proof-of-funds Input with optional preimage and From; added bs58/hex deps; added test vectors.
gRPC admin API
ark-grpc/proto/ark/v1/admin.proto, ark-grpc/build.rs, ark-grpc/src/client.rs
Added AdminService proto (notes, rounds, intents, market hours, sweeps); included in build; wired AdminServiceClient and create_arknote method in client.
E2E tests
e2e-tests/tests/e2e_arknote.rs
New ignored E2E test covering note creation, parsing, and redemption with balance checks.
Dev tooling
justfile
Removed Redis from arkd-setup; added comprehensive integration-tests target orchestrating fresh environment and E2E run.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant App as App
  participant Client as Ark Client
  participant Core as ark-core (PoF/Batch)
  participant Server as Ark Server

  rect rgba(200,230,255,0.3)
  note over App,Server: ArkNote creation via Admin API
  App->>Client: create_arknote(amount)
  Client->>Server: AdminService.CreateNote(amount, quantity=1)
  Server-->>Client: notes: [arknote_str]
  Client-->>App: arknote_str
  end

  rect rgba(200,255,200,0.3)
  note over App,Server: ArkNote redemption via batch join
  App->>Client: redeem_note(rng, arknote)
  Client->>Client: get_offchain_address()
  Client->>Core: join_next_batch(arknotes=[ArkNote], outputs=Board{to_address,to_amount})
  activate Core
  Core->>Core: convert ArkNote -> PoF::Input(preimage=Some)
  Core->>Server: submit batch intent/proof
  Server-->>Core: batch accepted, txid (optional)
  deactivate Core
  Client-->>App: Option<Txid>
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Possibly related PRs

Suggested reviewers

  • bonomat
  • luckysori
✨ 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/arknotes-on-v7

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Collaborator Author

@vincenzopalazzo vincenzopalazzo left a comment

Choose a reason for hiding this comment

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

My Self Review for tomorrow morning

@vincenzopalazzo vincenzopalazzo force-pushed the macros/arknotes-on-v7 branch 3 times, most recently from 8283bd0 to a79107c Compare August 6, 2025 15:22
@vincenzopalazzo vincenzopalazzo changed the title [WIP] ark-core: implement ArkNote encoding and cross-language validation ark-core: implement ArkNote encoding and cross-language validation Aug 13, 2025
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements ArkNote encoding and cross-language validation functionality for the ark-core library. It adds support for creating, encoding, decoding, and redeeming ArkNotes - fake VTXO coins that can be spent by revealing a preimage. The implementation includes comprehensive test vectors for validation and extends the client to support ArkNote operations.

Key changes:

  • Add ArkNote struct with encoding/decoding functionality using SHA256 preimage validation
  • Implement VirtualUtxoScript for taproot script management
  • Add admin service support for ArkNote creation via gRPC
  • Extend proof-of-funds system to handle ArkNote inputs

Reviewed Changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
ark-core/src/arknote.rs Core ArkNote implementation with encoding/decoding and test vectors
ark-core/src/vtxo.rs VirtualUtxoScript implementation and VTXO extensions for ArkNotes
ark-core/src/proof_of_funds.rs Extended proof system to support ArkNote inputs with preimage validation
ark-client/src/lib.rs Client methods for creating and redeeming ArkNotes
ark-client/src/batch.rs Batch processing support for ArkNote redemption
ark-grpc/proto/ark/v1/admin.proto Admin service protobuf definitions
ark-grpc/src/client.rs gRPC client support for admin operations
e2e-tests/tests/e2e_arknote.rs End-to-end integration tests for ArkNote functionality

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.


sig
}
// FIXME: this is an horrible hack to handle arknotes.
Copy link

Copilot AI Aug 13, 2025

Choose a reason for hiding this comment

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

The FIXME comment indicates this is a temporary hack. Consider implementing a proper enum-based solution for different input types as suggested in the comment on line 52.

Copilot uses AI. Check for mistakes.

vtxo_script: VirtualUtxoScript,
tap_tree_bytes: Vec<String>, // Cache for tap_tree() method
status: Status,
// FIXME: this is necessary?
Copy link

Copilot AI Aug 13, 2025

Choose a reason for hiding this comment

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

The FIXME comment about the necessity of extra_witness field should be resolved or converted to proper documentation explaining when this field is used.

Suggested change
// FIXME: this is necessary?
/// Extra witness data required for spending the ArkNote.
/// This field is used to store additional witness elements that may be needed
/// when constructing spending transactions, such as signatures or scripts
/// required by the spending conditions of the note.

Copilot uses AI. Check for mistakes.

@vincenzopalazzo vincenzopalazzo force-pushed the macros/arknotes-on-v7 branch 2 times, most recently from 31cd7d3 to 7385b39 Compare August 26, 2025 12:44
This commit is implementing the ark note functionality,
allowing for the creation and management of ark notes within the system.

Co-Developed-by: @luckysori
Signed-off-by: Vincenzo Palazzo <[email protected]>
This include a sequence of functonality that allow
to work with notes with arkd.

Co-Developed-by: @luckysori
Signed-off-by: Vincenzo Palazzo <[email protected]>
Signed-off-by: Vincenzo Palazzo <[email protected]>
vincenzopalazzo and others added 2 commits August 26, 2025 14:51
Sometimes it is nice to have a fixup commit and be able to
run the tests without having to pass clippy or other repo rules
passing before running the e2e tests.

Signed-off-by: Vincenzo Palazzo <[email protected]>
This commit introduces a new integration-tests recipe that provides a complete
end-to-end testing workflow by:

• Cleaning up existing test environment (stopping nigiri, killing arkd processes)
• Setting up fresh Bitcoin regtest environment with nigiri
• Rebuilding arkd from latest master branch
• Configuring and starting arkd services (redis, wallet, main daemon)
• Funding the arkd wallet with 20 UTXOs for testing
• Running the complete e2e test suite

This recipe ensures a clean, reproducible testing environment for integration
tests by resetting all components and dependencies before running tests.

Co-authored-by: Copilot <[email protected]>
Co-authored-by: @luckysori
Signed-off-by: Vincenzo Palazzo <[email protected]>
vincenzopalazzo

This comment was marked as off-topic.

@vincenzopalazzo vincenzopalazzo marked this pull request as ready for review August 26, 2025 13:06
coderabbitai[bot]

This comment was marked as off-topic.

Copy link
Collaborator

@luckysori luckysori left a comment

Choose a reason for hiding this comment

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

Nice work! Thanks for pushing this forward.

I tried to review this thoroughly, but let me know if you want to discuss the feedback synchronously.

I think we're now going in the right direction and we just have to simplify and remodel some things.

Comment on lines +534 to +542
/// Redeem multiple ArkNotes by settling them into new VTXOs
///
/// This method takes ArkNote objects and creates a batch transaction to convert
/// them into spendable VTXOs at the user's offchain address.
pub async fn redeem_notes<R>(
&self,
rng: &mut R,
arknotes: Vec<ArkNote>,
) -> Result<Option<Txid>, Error>
Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess ark-client should be able to keep track of your Ark notes for you. It's weird to have to pass in the notes from the outside.

Having said that, it might still be useful to expose a method which lets you pick which VTXOs/boarding outputs/notes to settle.

self.redeem_notes(rng, vec![arknote]).await
}

pub async fn create_arknote(&self, amount: Amount) -> Result<ArkNote, Error> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

AFAIK, this should not be part of the public API of the Client, because it only makes sense to use this for testing (it calls an admin API on the server).

I'm not against using it in the tests if we have to, but this only needs the GRPC client and doesn't use any of the ark_client::Client's state. I think this should just be a function defined in e2e-tests.

Comment on lines +254 to +259
pub(crate) async fn join_next_batch<R>(
&self,
rng: &mut R,
onchain_inputs: Vec<batch::OnChainInput>,
vtxo_inputs: Vec<batch::VtxoInput>,
arknotes: Vec<ArkNote>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't we just continue the pattern of using the settle API to settle everything?

I do think that it might also be useful to create another API which lets you choose what you are settling, but I'm not sure we want to be adding redeem_notes/redeem_note. To me it's more natural to settle everything (including Ark notes) with settle.

Comment on lines +94 to +96
let admin_client = AdminServiceClient::connect(self.url.clone())
.await
.map_err(Error::connect)?;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmmm, I think we should expose a separate admin client. An ark_grpc::Client in production will never use an admin client.

I would just create a separate module admin_client.rs and move all the relevant bits there.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

nice idea thanks!

ark-bdk-wallet = { path = "../ark-bdk-wallet" }
ark-client = { path = "../ark-client" }
ark-core = { path = "../ark-core" }
ark-grpc = { path = "../ark-grpc" }
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍 To be able to use the admin client API. But I believe this is not being used directly in the e2e tests yet.

Comment on lines 83 to 91
// this is inside the taproot script path
let node_script = value.note_script.clone();
let Some(control_block) =
spending_info.control_block(&(node_script.clone(), LeafVersion::TapScript))
else {
// FIXME: probably we need a tryfrom?
panic!("no control block found");
};

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we need to model things differently so that the conversion from Ark note to proof of funds input is infallible. An Ark note always has the same structure AFAIK, so it should just work.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ok to me this just sounds calling .unwrap or expect on the control_block ?

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: 2

♻️ Duplicate comments (5)
ark-core/src/arknote.rs (5)

55-56: Resolve FIXME on extra_witness or remove the field

If not required by spending logic, drop it; otherwise, replace FIXME with a doc comment explaining when/why it’s used.

Apply (if dropping):

-    // FIXME: this is necessary?
-    extra_witness: Vec<Vec<u8>>,
+    // extra witness not needed for ArkNote spend path

And delete the initializer and accessor accordingly.


58-59: Avoid exposing note_script as pub; add a getter

Keep invariants internal; provide a read-only accessor.

-    pub note_script: ScriptBuf,
+    note_script: ScriptBuf,

Add:

+    pub fn note_script(&self) -> &ScriptBuf {
+        &self.note_script
+    }

30-34: Prefer an enum for Status over a bare bool

A bool won’t scale (e.g., Pending/Confirmed/Spent). Define an enum and consider Default.

-#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
-pub struct Status {
-    pub confirmed: bool,
-}
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
+pub enum Status {
+    Pending,
+    Confirmed,
+    Spent,
+}

Update usages accordingly (e.g., Status::Confirmed).


212-231: Offer FromStr impl for DEFAULT_HRP parsing

Add core::str::FromStr so consumers can parse via "str".parse().

Example:

impl core::str::FromStr for ArkNote {
    type Err = Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Self::from_string(s)
    }
}

161-168: Stop silent truncation: guard u64→u32 cast in encode()

Casting sats to u32 will wrap for values > u32::MAX. Add a hard guard to fail loudly at encode-time (at minimum).

Apply:

     // Use big-endian to match TypeScript's writeUInt32BE
-    result.extend_from_slice(&(self.value.to_sat() as u32).to_be_bytes());
+    let sats = self.value.to_sat();
+    assert!(
+        sats <= u32::MAX as u64,
+        "ArkNote value exceeds 32-bit encoding"
+    );
+    result.extend_from_slice(&(sats as u32).to_be_bytes());
🧹 Nitpick comments (9)
ark-core/src/arknote.rs (9)

87-88: Avoid unnecessary expect on Txid construction

Use from_slice is fine but the expect is redundant; the slice length is guaranteed. Prefer a comment or use from_slice without expect where available.

- let txid = Txid::from_slice(preimage_hash.as_byte_array()).expect("valid txid");
+ let txid = Txid::from_slice(preimage_hash.as_byte_array()).unwrap();

36-42: Consider Base58Check to catch typos/corruption

You’re emitting raw base58 without checksum. Switch to bs58 with_check for encode/decode to detect errors early.

-        let value = format!("{}{}", self.hrp, bs58::encode(encoded).into_string());
+        let value = format!("{}{}", self.hrp, bs58::encode(encoded).with_check().into_string());

And in from_string_with_hrp:

-        let decoded = bs58::decode(encoded)
-            .into_vec()
+        let decoded = bs58::decode(encoded)
+            .with_check(None)
+            .into_vec()
             .map_err(|e| Error::ad_hoc(format!("failed to decode base58: {e}")))?;

134-137: extra_witness() always returns Some — drop Option for a plain slice

Returning Option here adds noise; this is unconditionally Some.

-    pub fn extra_witness(&self) -> Option<&[Vec<u8>]> {
-        Some(&self.extra_witness)
-    }
+    pub fn extra_witness(&self) -> &[Vec<u8>] {
+        &self.extra_witness
+    }

If you remove the field per previous comment, drop this API.


53-54: Naming + allocation: tap_tree_bytes is hex strings and clones on access

  • Rename to tap_tree_hex (it’s Vec, not bytes).
  • Return a slice to avoid cloning on every call.
-    tap_tree_bytes: Vec<String>, // Cache for tap_tree() method
+    tap_tree_hex: Vec<String>, // Hex-encoded cache for tap_tree()
@@
-    pub fn tap_tree(&self) -> Vec<String> {
-        self.tap_tree_bytes.clone()
-    }
+    pub fn tap_tree(&self) -> &[String] {
+        &self.tap_tree_hex
+    }

Also applies to: 139-143


36-42: Delimiter-free “hrp + payload” is ambiguous; consider a separator

Optional: use a separator (e.g., ':') to future-proof HRP parsing and avoid accidental overlaps.

Also applies to: 212-231


83-84: Reuse Secp256k1 context

Instantiate once (e.g., lazy_static/OnceCell) to avoid per-call cost.

static SECP: once_cell::sync::Lazy<Secp256k1<bitcoin::secp256k1::VerifyOnly>> =
    once_cell::sync::Lazy::new(Secp256k1::verification_only);

145-154: Indexing scripts()[0] assumes at least one script

Add an assertion to document invariant.

-        &self.vtxo_script.scripts()[0]
+        {
+            let scripts = self.vtxo_script.scripts();
+            debug_assert!(!scripts.is_empty(), "ArkNote expects one script");
+            &scripts[0]
+        }

170-173: Nit: to_encoded_string() duplicates to_string()

Consider removing or documenting why both exist; they’re identical.


598-608: Base58 error message specificity

If you adopt Base58Check, error messages will improve; otherwise consider adding guidance (“invalid alphabet or checksum”).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ddb37e2 and e71c732.

📒 Files selected for processing (2)
  • ark-core/src/arknote.rs (1 hunks)
  • e2e-tests/tests/e2e_arknote.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • e2e-tests/tests/e2e_arknote.rs
🔇 Additional comments (3)
ark-core/src/arknote.rs (3)

239-245: TxOut construction LGTM


321-399: Add a negative test for > u32::MAX values

Ensure encoding rejects values that exceed 32-bit.

Add:

#[test]
#[should_panic(expected = "exceeds 32-bit")]
fn test_arknote_value_overflow_panics_in_encode() {
    let note = ArkNote::new([0x11; PREIMAGE_LENGTH], Amount::from_sat(u32::MAX as u64 + 1));
    let _ = note.encode(); // should assert or, if using try_new*, construction should fail instead
}

If you adopt try_new*, switch to expecting an Err on construction.

Also applies to: 434-459, 567-621


200-205: decode_with_hrp constructs via new_with_hrp → consider try_new_with_hrp

If you switch to fallible constructors, decode_with_hrp should call try_new_with_hrp and propagate errors.

Comment on lines +83 to +86
let secp = Secp256k1::new();
let vtxo_script = VirtualUtxoScript::new(&secp, vec![note_script.clone()])
.expect("failed to create VirtualUtxoScript");

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid expect() in library code; propagate VirtualUtxoScript::new error

Don’t panic in constructors; bubble errors instead (see previous diff).

🤖 Prompt for AI Agents
In ark-core/src/arknote.rs around lines 83 to 86, remove the expect() call so
the VirtualUtxoScript::new error is propagated instead of panicking: update the
enclosing function/signature to return Result (or propagate an existing Result)
and replace the expect with the ? operator, making sure to import/return the
appropriate error type and adjust upstream callsites to handle the Result; do
not catch and panic here.

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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
ark-core/src/vtxo.rs (3)

75-81: Inverted length check causes false error.

This check rejects the correct case and lets the incorrect case pass.

-            if leaf_distribution.len() == scripts.len() {
-                return Err(Error::ad_hoc("wrong leaf distribution calculated"));
-            }
+            if leaf_distribution.len() != scripts.len() {
+                return Err(Error::ad_hoc("wrong leaf distribution calculated"));
+            }

However, see next comment: the distribution function itself is incorrect for n ≥ 3 and can produce extra entries.


225-255: Replace buggy leaf-depth calculation with a simple, consistent scheme.

calculate_leaf_depths over-allocates (e.g., n=3 → 4 depths). Align with VirtualUtxoScript::new and add leaves at depth 1. This also keeps cross-language behavior predictable.

-            let scripts = [vec![forfeit_script, redeem_script], extra_scripts.clone()].concat();
-
-            let leaf_distribution = calculate_leaf_depths(scripts.len());
-
-            if leaf_distribution.len() == scripts.len() {
-                return Err(Error::ad_hoc("wrong leaf distribution calculated"));
-            }
-
-            let mut builder = TaprootBuilder::new();
-            for (script, depth) in scripts.iter().zip(leaf_distribution.iter()) {
-                builder = builder
-                    .add_leaf(*depth as u8, script.clone())
-                    .map_err(Error::ad_hoc)?;
-            }
+            let scripts = [vec![forfeit_script, redeem_script], extra_scripts.clone()].concat();
+
+            let mut builder = TaprootBuilder::new();
+            for script in scripts {
+                builder = builder.add_leaf(1, script).map_err(Error::ad_hoc)?;
+            }

161-168: Avoid expect() in a fallible API.

get_spend_info returns Result but still uses expect(...). Return an error instead.

-        let control_block = self
-            .spend_info
-            .control_block(&(script, LeafVersion::TapScript))
-            .expect("forfeit script");
+        let control_block = self
+            .spend_info
+            .control_block(&(script, LeafVersion::TapScript))
+            .ok_or_else(|| Error::ad_hoc("script not found in spend_info"))?;
♻️ Duplicate comments (3)
ark-core/src/proof_of_funds.rs (2)

2-2: Fix broken LeafVersion import (compilation error).

Import LeafVersion from the bitcoin crate, not from this module.

-use crate::proof_of_funds::taproot::LeafVersion;
+use bitcoin::taproot::LeafVersion;

85-90: Don’t panic in conversions; make ArkNote→Input fallible.

panic!("no control block found") in a conversion will crash the process. Use TryFrom and propagate an error.

-impl From<&ArkNote> for Input {
-    fn from(value: &ArkNote) -> Self {
+impl TryFrom<&ArkNote> for Input {
+    type Error = Error;
+    fn try_from(value: &ArkNote) -> Result<Self, Error> {
         let spending_info = value.vtxo_script().spend_info();
         // this is inside the taproot script path
         let node_script = value.note_script().clone();
-        let Some(control_block) =
-            spending_info.control_block(&(node_script.clone(), LeafVersion::TapScript))
-        else {
-            // FIXME: probably we need a tryfrom?
-            panic!("no control block found");
-        };
+        let control_block = spending_info
+            .control_block(&(node_script.clone(), LeafVersion::TapScript))
+            .ok_or_else(|| Error::ad_hoc("no control block found"))?;
 
-        Self {
+        Ok(Self {
             outpoint: value.outpoint(),
             sequence: Sequence::MAX,
             witness_utxo: TxOut {
                 value: value.value(),
                 // This should be unspendable script?
                 script_pubkey: value.vtxo_script().script_pubkey(),
             },
-            // This should be empty?
-            tapscripts: vec![],
+            // Provide the tapscripts for intent encoding.
+            tapscripts: value.vtxo_script().scripts().to_vec(),
             pk: value.vtxo_script().x_only_public_key(),
             // This contains the extra info to spend the note, right?
             spend_info: (node_script, control_block),
             is_onchain: false,
             preimage: Some(*value.preimage()),
-        }
+        })
     }
 }

Follow-up: update call sites to use Input::try_from(&note)? or (&note).try_into()?.

ark-core/src/arknote.rs (1)

166-173: Value truncation: 64-bit sats cast to u32 in encode().

This will silently wrap for values > u32::MAX. Enforce the 32‑bit invariant at construction and keep encode() safe.

Apply fallible constructors and validate amount; keep existing new* as thin wrappers if you don’t want to ripple changes yet:

@@
-    /// Create a new ArkNote with the given preimage and value
-    pub fn new(preimage: [u8; PREIMAGE_LENGTH], value: Amount) -> Self {
-        Self::new_with_hrp(preimage, value, DEFAULT_HRP.to_string())
-    }
+    /// Create a new ArkNote with the given preimage and value
+    pub fn new(preimage: [u8; PREIMAGE_LENGTH], value: Amount) -> Self {
+        Self::try_new(preimage, value).expect("valid ArkNote")
+    }
@@
-    /// Create a new ArkNote with a custom HRP
-    pub fn new_with_hrp(preimage: [u8; PREIMAGE_LENGTH], value: Amount, hrp: String) -> Self {
+    /// Create a new ArkNote with a custom HRP
+    pub fn new_with_hrp(preimage: [u8; PREIMAGE_LENGTH], value: Amount, hrp: String) -> Self {
+        Self::try_new_with_hrp(preimage, value, &hrp).expect("valid ArkNote")
+    }
+
+    /// Fallible constructor (preferred): validates amount/HRP and propagates errors.
+    pub fn try_new(preimage: [u8; PREIMAGE_LENGTH], value: Amount) -> Result<Self, Error> {
+        Self::try_new_with_hrp(preimage, value, DEFAULT_HRP)
+    }
+
+    pub fn try_new_with_hrp(
+        preimage: [u8; PREIMAGE_LENGTH],
+        value: Amount,
+        hrp: &str,
+    ) -> Result<Self, Error> {
+        let sats = value.to_sat();
+        if sats > u32::MAX as u64 {
+            return Err(Error::ad_hoc("ArkNote value exceeds 32-bit encoding"));
+        }
+        if hrp.is_empty() || hrp.len() > 32 || !hrp.chars().all(|c| c.is_ascii_alphanumeric()) {
+            return Err(Error::ad_hoc("invalid HRP"));
+        }
         let preimage_hash = sha256::Hash::hash(&preimage);
@@
-        let secp = Secp256k1::new();
-        let vtxo_script = VirtualUtxoScript::new(&secp, vec![note_script.clone()])
-            .expect("failed to create VirtualUtxoScript");
+        let secp = Secp256k1::verification_only();
+        let vtxo_script = VirtualUtxoScript::new(&secp, vec![note_script.clone()])?;
@@
-        ArkNote {
+        Ok(ArkNote {
             preimage,
             value,
-            hrp,
+            hrp: hrp.to_string(),
             txid,
             vtxo_script,
             tap_tree_bytes: encoded_scripts,
             status: Status { confirmed: true },
             extra_witness: vec![preimage.to_vec()],
             note_script,
-        }
+        })
     }
@@
-    pub fn encode(&self) -> Vec<u8> {
+    pub fn encode(&self) -> Vec<u8> {
         let mut result = Vec::with_capacity(ARKNOTE_LENGTH);
         result.extend_from_slice(&self.preimage);
         // Use big-endian to match TypeScript's writeUInt32BE
-        result.extend_from_slice(&(self.value.to_sat() as u32).to_be_bytes());
+        let sats = self.value.to_sat();
+        debug_assert!(sats <= u32::MAX as u64);
+        result.extend_from_slice(&(sats as u32).to_be_bytes());
         result
     }
🧹 Nitpick comments (3)
ark-core/src/proof_of_funds.rs (2)

52-53: Model input kind explicitly.

The preimage: Option<...> + is_onchain combination is a smell. Prefer enum InputKind { Onchain { ... }, Vtxo { ... }, ArkNote { preimage: [u8; PREIMAGE_LENGTH] } } to make witnesses/flow explicit.


230-276: Name the witness element, not “sig”.

This variable holds either a Schnorr signature or a preimage. Rename to witness_elem (and reflect in comments) to reduce confusion.

ark-core/src/arknote.rs (1)

139-143: Return a slice; the Option is always Some.

extra_witness() always returns Some. Return a slice directly.

-    pub fn extra_witness(&self) -> Option<&[Vec<u8>]> {
-        Some(&self.extra_witness)
-    }
+    pub fn extra_witness(&self) -> &[Vec<u8>] {
+        &self.extra_witness
+    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e71c732 and 9225c47.

📒 Files selected for processing (5)
  • ark-core/src/arknote.rs (1 hunks)
  • ark-core/src/lib.rs (3 hunks)
  • ark-core/src/proof_of_funds.rs (7 hunks)
  • ark-core/src/vtxo.rs (1 hunks)
  • e2e-tests/tests/e2e_arknote.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • e2e-tests/tests/e2e_arknote.rs
  • ark-core/src/lib.rs
🧰 Additional context used
🧬 Code graph analysis (3)
ark-core/src/proof_of_funds.rs (2)
ark-core/src/arknote.rs (2)
  • value (105-107)
  • preimage (110-112)
ark-core/src/vtxo.rs (1)
  • exit_script (220-222)
ark-core/src/vtxo.rs (4)
ark-core/src/proof_of_funds.rs (1)
  • new (57-76)
ark-core/src/send.rs (1)
  • script_pubkey (243-245)
ark-core/src/script.rs (1)
  • tr_script_pubkey (30-37)
ark-core/src/arknote.rs (1)
  • vtxo_script (162-164)
ark-core/src/arknote.rs (1)
ark-core/src/proof_of_funds.rs (4)
  • encode (442-447)
  • encode (465-487)
  • new (57-76)
  • decode (490-520)
🔇 Additional comments (1)
ark-core/src/vtxo.rs (1)

324-414: VirtualUtxoScript: solid, minimal API.

Creation, encoding, and accessors look good and align with the Taproot builder behavior.

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