Cryptography Utilities

Overview

The Cryptography Utilities provide a set of cryptographic tools for Soroban smart contracts, including hash functions, Merkle tree verification, and Merkle-based distribution systems. These utilities enable secure data verification and efficient token distribution mechanisms. The Cryptography Utilities consist of two main packages:

  • Crypto: A set of cryptographic primitives and utilities for Soroban contracts.

  • Merkle Distributor: A system for distributing tokens or other assets using Merkle proofs for verification.

Crypto Package

The crypto package provides fundamental cryptographic primitives and utilities for Soroban contracts, with a focus on hashing and Merkle tree operations.

Key Components

Hashers

Provides a generic Hasher trait and implementations for common hash functions:

  • Sha256: Implementation of the SHA-256 hash function

  • Keccak256: Implementation of the Keccak-256 hash function (used in Ethereum)

Each hasher follows the same interface:

pub trait Hasher {
    type Output;

    fn new(e: &Env) -> Self;
    fn update(&mut self, input: Bytes);
    fn finalize(self) -> Self::Output;
}

Hashable

The Hashable trait allows types to be hashed with any Hasher implementation:

pub trait Hashable {
    fn hash<H: Hasher>(&self, hasher: &mut H);
}

Built-in implementations are provided for BytesN<32> and Bytes.

Utility Functions

  • hash_pair: Hashes two values together

  • commutative_hash_pair: Hashes two values in a deterministic order (important for Merkle trees)

Merkle Tree Verification

The Verifier struct provides functionality to verify Merkle proofs:

impl<H> Verifier<H>
where
    H: Hasher<Output = Bytes32>,
{
    pub fn verify(e: &Env, proof: Vec<Bytes32>, root: Bytes32, leaf: Bytes32) -> bool {
        // Implementation verifies that the leaf is part of the tree defined by root
    }
}

Usage Examples

Hashing Data

use soroban_sdk::{Bytes, Env};
use stellar_crypto::keccak::Keccak256;
use stellar_crypto::hasher::Hasher;

// Hash some data with Keccak256
let e = Env::default();
let data = Bytes::from_slice(&e, "Hello, world!".as_bytes());

let mut hasher = Keccak256::new(&e);
hasher.update(data);
let hash = hasher.finalize();

Verifying a Merkle Proof

use soroban_sdk::{BytesN, Env, Vec};
use stellar_crypto::keccak::Keccak256;
use stellar_crypto::merkle::Verifier;

// Verify that a leaf is part of a Merkle tree
let e = Env::default();
let root = /* merkle root as BytesN<32> */;
let leaf = /* leaf to verify as BytesN<32> */;
let proof = /* proof as Vec<BytesN<32>> */;

let is_valid = Verifier::<Keccak256>::verify(&e, proof, root, leaf);

Merkle Distributor

The Merkle Distributor package builds on the crypto package to provide a system for distributing tokens or other assets using Merkle proofs for verification.

Key Concepts

IndexableNode

The IndexableNode trait defines the structure for nodes in the Merkle tree:

pub trait IndexableNode {
    fn index(&self) -> u32;
}

Each node must include a unique index that identifies its position in the Merkle tree.

MerkleDistributor

The MerkleDistributor struct provides functionality for:

  • Setting a Merkle root

  • Checking if an index has been claimed

  • Verifying proofs and marking indices as claimed

Usage Example

use soroban_sdk::{contract, contractimpl, contracttype, Address, BytesN, Env, Vec};
use stellar_crypto::keccak::Keccak256;
use stellar_merkle_distributor::{IndexableNode, MerkleDistributor};

// Define a leaf node structure
#[contracttype]
struct LeafData {
    pub index: u32,
    pub address: Address,
    pub amount: i128,
}

// Implement IndexableNode for the leaf structure
impl IndexableNode for LeafData {
    fn index(&self) -> u32 {
        self.index
    }
}

#[contract]
pub struct TokenDistributor;

#[contractimpl]
impl TokenDistributor {
    // Initialize the distributor with a Merkle root
    pub fn initialize(e: &Env, root: BytesN<32>) {
        MerkleDistributor::<Keccak256>::set_root(e, root);
    }

    // Claim tokens by providing a proof
    pub fn claim(e: &Env, leaf: LeafData, proof: Vec<BytesN<32>>) {
        // Verify the proof and mark as claimed
        MerkleDistributor::<Keccak256>::verify_and_set_claimed(e, leaf.clone(), proof);

        // Transfer tokens or perform other actions based on leaf data
        // ...
    }

    // Check if an index has been claimed
    pub fn is_claimed(e: &Env, index: u32) -> bool {
        MerkleDistributor::<Keccak256>::is_claimed(e, index)
    }
}

Use Cases

Token Airdrops

Efficiently distribute tokens to a large number of recipients without requiring individual transactions for each recipient.

NFT Distributions

Distribute NFTs to a whitelist of addresses, with each address potentially receiving different NFTs.

Off-chain Allowlists

Maintain a list of eligible addresses off-chain and allow them to claim tokens or other assets on-chain.

Snapshot-based Voting

Create a snapshot of token holders at a specific block and allow them to vote based on their holdings.