use solana_program::hash::hashv;
use solana_program::pubkey::Pubkey;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AirdropAllocation {
pub recipient: Pubkey,
pub amount: u64,
}
impl AirdropAllocation {
pub fn new(recipient: Pubkey, amount: u64) -> Self {
Self { recipient, amount }
}
pub fn compute_leaf_hash(&self) -> [u8; 32] {
compute_leaf_hash(&self.recipient, self.amount)
}
}
#[derive(Clone, Debug)]
pub struct MerkleTree {
root: [u8; 32],
tree: Vec<Vec<[u8; 32]>>,
allocations: Vec<AirdropAllocation>,
}
impl MerkleTree {
pub fn new(mut allocations: Vec<AirdropAllocation>) -> Result<Self, String> {
if allocations.is_empty() {
return Err("Cannot create merkle tree from empty allocations list".to_string());
}
allocations.sort_by(|a, b| a.recipient.cmp(&b.recipient));
let mut leaves: Vec<[u8; 32]> = allocations.iter().map(|a| a.compute_leaf_hash()).collect();
if leaves.len() == 1 {
leaves.push(leaves[0]); }
let mut tree = vec![leaves.clone()];
while leaves.len() > 1 {
let mut new_level = Vec::new();
for chunk in leaves.chunks(2) {
if chunk.len() == 2 {
let combined = hashv(&[&chunk[0], &chunk[1]]).to_bytes();
new_level.push(combined);
} else {
let combined = hashv(&[&chunk[0], &chunk[0]]).to_bytes();
new_level.push(combined);
}
}
leaves = new_level;
tree.push(leaves.clone());
}
let root = tree
.last()
.ok_or("Failed to get tree root: tree is empty")?
.first()
.ok_or("Failed to get tree root: root level is empty")?
.clone();
Ok(Self {
root,
tree,
allocations,
})
}
pub fn root(&self) -> [u8; 32] {
self.root
}
pub fn generate_proof(&self, allocation_index: usize) -> Result<MerkleProof, String> {
if allocation_index >= self.allocations.len() {
return Err(format!(
"Allocation index {} out of bounds (max: {})",
allocation_index,
self.allocations.len() - 1
));
}
let mut path = Vec::new();
let mut indices = Vec::new();
let mut current_index = allocation_index;
for level in 0..self.tree.len() - 1 {
let current_level = &self.tree[level];
let sibling_index = if current_index % 2 == 0 {
current_index + 1
} else {
current_index - 1
};
if sibling_index < current_level.len() {
let sibling_hash = current_level[sibling_index];
path.push(sibling_hash);
indices.push(current_index % 2 == 0);
} else {
let current_hash = current_level[current_index];
path.push(current_hash);
indices.push(true);
}
current_index /= 2;
}
Ok(MerkleProof::new(path, indices))
}
pub fn find_and_generate_proof(
&self,
recipient: &Pubkey,
) -> Option<Result<MerkleProof, String>> {
self.allocations
.iter()
.position(|a| &a.recipient == recipient)
.map(|index| self.generate_proof(index))
}
pub fn allocation_count(&self) -> usize {
self.allocations.len()
}
pub fn get_allocation(&self, index: usize) -> Option<&AirdropAllocation> {
self.allocations.get(index)
}
pub fn allocations(&self) -> &[AirdropAllocation] {
&self.allocations
}
}
#[derive(Clone, Debug)]
pub struct MerkleProof {
pub path: Vec<[u8; 32]>,
pub indices: Vec<bool>,
}
impl MerkleProof {
pub fn new(path: Vec<[u8; 32]>, indices: Vec<bool>) -> Self {
Self { path, indices }
}
pub fn verify(&self, recipient: &Pubkey, amount: u64, merkle_root: [u8; 32]) -> bool {
if self.path.len() != self.indices.len() {
return false;
}
let leaf_hash = compute_leaf_hash(recipient, amount);
let mut current_hash = leaf_hash;
for (i, &path_node) in self.path.iter().enumerate() {
let is_right = self.indices[i];
if is_right {
current_hash = hashv(&[¤t_hash, &path_node]).to_bytes();
} else {
current_hash = hashv(&[&path_node, ¤t_hash]).to_bytes();
}
}
current_hash == merkle_root
}
}
pub fn compute_leaf_hash(recipient: &Pubkey, amount: u64) -> [u8; 32] {
hashv(&[recipient.as_ref(), &amount.to_le_bytes()]).to_bytes()
}
pub fn verify_merkle_proof(
proof: &MerkleProof,
recipient: &Pubkey,
amount: u64,
merkle_root: [u8; 32],
) -> bool {
proof.verify(recipient, amount, merkle_root)
}