Skip to content
Merged
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion scripts/format.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ cargo clippy \
--workspace \
--no-deps \
--all-features \
--exclude name-service \
--exclude photon-api \
--exclude name-service \
-- -A clippy::result_large_err \
Expand Down
2 changes: 1 addition & 1 deletion sdk-libs/program-test/src/indexer/test_indexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ impl Indexer for TestIndexer {
if let Some(leaf_index) = tree.merkle_tree.get_leaf_index(hash) {
let proof = tree
.merkle_tree
.get_proof_of_leaf(leaf_index, false)
.get_proof_of_leaf(leaf_index, true)
.unwrap();
proofs.push(MerkleProof {
hash: *hash,
Expand Down
2 changes: 1 addition & 1 deletion sdk-libs/program-test/src/utils/setup_light_programs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub fn setup_light_programs(
.inspect_err(|_| {
println!("Program light_compressed_token bin not found in {}", path);
})?;
let path = format!("{}spl_noop.so", light_bin_path);
let path = format!("{}/spl_noop.so", light_bin_path);
program_test
.add_program_from_file(NOOP_PROGRAM_ID, path.clone())
.inspect_err(|_| {
Expand Down
2 changes: 2 additions & 0 deletions sdk-libs/sdk-types/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,5 @@ pub const CPI_CONTEXT_ACCOUNT_1_DISCRIMINATOR: [u8; 8] = [22, 20, 149, 218, 74,
pub const CPI_CONTEXT_ACCOUNT_2_DISCRIMINATOR: [u8; 8] = [34, 184, 183, 14, 100, 80, 183, 124];

pub const SOL_POOL_PDA: [u8; 32] = pubkey_array!("CHK57ywWSDncAoRu1F8QgwYJeXuAJyyBYT4LixLXvMZ1");

pub const ADDRESS_TREE_V2: [u8; 32] = pubkey_array!("amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx");
2 changes: 2 additions & 0 deletions sdk-libs/sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ v2 = ["light-sdk-types/v2"]
cpi-context = ["light-sdk-types/cpi-context"]
devnet = []
poseidon = ["light-hasher/poseidon", "light-compressed-account/poseidon"]
merkle-tree = ["light-concurrent-merkle-tree/solana"]


[dependencies]
Expand All @@ -45,6 +46,7 @@ light-compressed-account = { workspace = true, features = ["std"] }
light-hasher = { workspace = true, features = ["std"] }
light-account-checks = { workspace = true, features = ["solana"] }
light-zero-copy = { workspace = true }
light-concurrent-merkle-tree = { workspace = true, optional = true }

[dev-dependencies]
num-bigint = { workspace = true }
Expand Down
4 changes: 3 additions & 1 deletion sdk-libs/sdk/src/instruction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ mod system_accounts;
mod tree_info;

/// Zero-knowledge proof to prove the validity of existing compressed accounts and new addresses.
pub use light_compressed_account::instruction_data::compressed_proof::ValidityProof;
pub use light_compressed_account::instruction_data::compressed_proof::{
CompressedProof, ValidityProof,
};
pub use light_sdk_types::instruction::*;
pub use pack_accounts::*;
pub use system_accounts::*;
Expand Down
3 changes: 3 additions & 0 deletions sdk-libs/sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ pub mod token;
pub mod transfer;
pub mod utils;

#[cfg(feature = "merkle-tree")]
pub mod merkle_tree;

#[cfg(feature = "anchor")]
use anchor_lang::{AnchorDeserialize, AnchorSerialize};
#[cfg(not(feature = "anchor"))]
Expand Down
55 changes: 55 additions & 0 deletions sdk-libs/sdk/src/merkle_tree.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use solana_account_info::AccountInfo;
use solana_msg::msg;
use solana_program_error::ProgramError;

pub mod v1 {
use light_account_checks::checks::check_owner;
use light_concurrent_merkle_tree::zero_copy::ConcurrentMerkleTreeZeroCopy;
use light_hasher::Poseidon;
use light_sdk_types::ACCOUNT_COMPRESSION_PROGRAM_ID;

use super::*;

/// StateMerkleTreeAccount discriminator
pub const STATE_MERKLE_TREE_DISCRIMINATOR: [u8; 8] = [172, 43, 172, 186, 29, 73, 219, 84];
pub const STATE_MERKLE_TREE_ACCOUNT_METADATA_LEN: usize = 224;

/// Reads a root from the concurrent state merkle tree by index
pub fn read_state_merkle_tree_root(
account_info: &AccountInfo,
root_index: u16,
) -> Result<[u8; 32], ProgramError> {
if root_index as usize >= 2400 {
msg!(
"Invalid root index: {} greater than max root index {}",
root_index,
2400
);
return Err(ProgramError::InvalidArgument);
}
check_owner(&ACCOUNT_COMPRESSION_PROGRAM_ID, account_info)?;
let account_data = account_info.try_borrow_data()?;

// Check discriminator
if account_data.len() < 8 {
msg!("StateMerkleTreeAccount data too short for discriminator");
return Err(ProgramError::InvalidAccountData);
}

let discriminator = &account_data[0..8];
if discriminator != STATE_MERKLE_TREE_DISCRIMINATOR {
msg!("Invalid StateMerkleTreeAccount discriminator");
return Err(ProgramError::InvalidAccountData);
}
let required_size = STATE_MERKLE_TREE_ACCOUNT_METADATA_LEN;
if account_data.len() < required_size {
msg!("StateMerkleTreeAccount data too short for metadata");
return Err(ProgramError::InvalidAccountData);
}

let data = &account_data[required_size..];
let merkle_tree = ConcurrentMerkleTreeZeroCopy::<Poseidon, 26>::from_bytes_zero_copy(data)?;

Ok(merkle_tree.roots[root_index as usize])
Comment on lines +50 to +53
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify bounds safety when accessing merkle_tree.roots.

The function validates root_index < 2400, then accesses merkle_tree.roots[root_index as usize]. Please confirm that ConcurrentMerkleTreeZeroCopy<Poseidon, 26> guarantees a roots array of at least 2400 elements to prevent potential out-of-bounds access.


🏁 Script executed:

#!/bin/bash
# Verify the ConcurrentMerkleTreeZeroCopy type definition and roots array size
rg -A 10 "struct ConcurrentMerkleTreeZeroCopy" --type rust
rg "roots.*\[" --type rust -A 3 | rg "2400|ROOTS"

Length of output: 1813


[/run_scripts]


🏁 Script executed:

#!/bin/bash
# Search for ConcurrentMerkleTree struct definition (the inner type)
rg -A 20 "struct ConcurrentMerkleTree[^Z]" --type rust

Length of output: 3300



🏁 Script executed:

#!/bin/bash
# Search for CyclicBoundedVec definition and its size/capacity
rg -A 15 "struct CyclicBoundedVec" --type rust

Length of output: 0



🏁 Script executed:

#!/bin/bash
# Search for CyclicBoundedVec definition or type alias
rg "type CyclicBoundedVec|CyclicBoundedVec.*=" --type rust -B 2 -A 5
# Also check the concurrent-merkle-tree lib.rs for imports
rg "use.*CyclicBoundedVec|mod.*" program-libs/concurrent-merkle-tree/src/lib.rs -A 2

Length of output: 7239



🏁 Script executed:

#!/bin/bash
# Search for CyclicBoundedVec implementation in offset module
rg "pub.*CyclicBoundedVec|impl.*CyclicBoundedVec" --type rust -A 5
# Also search for how roots capacity is set
rg "roots.*capacity|capacity.*2400|ROOTS_LEN" --type rust

Length of output: 4860



🏁 Script executed:

#!/bin/bash
# Find ROOTS constant
rg "const ROOTS|ROOTS\s*=" --type rust -B 2 -A 1
# Look at from_bytes_zero_copy implementation
rg -A 30 "pub fn from_bytes_zero_copy\(" program-libs/concurrent-merkle-tree/src/zero_copy.rs | head -50
# Check the actual merkle_tree.rs file around lines 50-53
cat sdk-libs/sdk/src/merkle_tree.rs | head -70 | tail -30

Length of output: 19114



🏁 Script executed:

#!/bin/bash
# Find check_size_constraints implementation
rg -A 20 "fn check_size_constraints" --type rust
# Also search for any validation of roots capacity
rg "roots.*capacity|roots_capacity.*=" --type rust -B 2 -A 2 | grep -A 5 "check_size_constraints\|validate\|assert"

Length of output: 7256


Bounds safety issue confirmed: roots array capacity is not validated.

The concern is valid. The code validates root_index < 2400 but does not guarantee that merkle_tree.roots has capacity >= 2400. The check_size_constraints() only validates that roots_size > 0, leaving a potential out-of-bounds access.

Recommended fix: Add validation in check_size_constraints_new() to ensure roots_size >= 2400 for this use case, or verify at the call site that the deserialized roots capacity meets the requirement before accessing the array.

🤖 Prompt for AI Agents
In sdk-libs/sdk/src/merkle_tree.rs around lines 50 to 53, the code uses
merkle_tree.roots[root_index as usize] after only validating root_index < 2400
but not ensuring merkle_tree.roots has at least that many entries; update the
validation to prevent out-of-bounds access by either (a) adding a size check in
check_size_constraints_new() to require roots_size >= 2400 (and return a clear
error if not), or (b) adding a runtime guard at this call site that verifies
merkle_tree.roots.len() > root_index as usize before indexing and returns an
error if the deserialized roots capacity is insufficient.

}
}
Loading