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
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ members = [
"contracts/shared",
"contracts/reserve_contract",
"contracts/native_transfer",
"contracts/claim_verifier",

]
18 changes: 18 additions & 0 deletions contracts/claim_verifier/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "claim-verifier"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["rlib", "cdylib"]

[dependencies]
soroban-sdk = "22.0.0"
bridgelet-shared = { path = "../shared" }

[dev-dependencies]
soroban-sdk = { version = "22.0.0", features = ["testutils"] }
bridgelet-shared = { path = "../shared", features = ["testutils"] }

[features]
testutils = ["soroban-sdk/testutils"]
11 changes: 11 additions & 0 deletions contracts/claim_verifier/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use soroban_sdk::contracterror;

#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Error {
AlreadyInitialized = 1,
NotInitialized = 2,
AuthorizedSignerNotSet = 3,
InvalidSignature = 4,
SignatureVerificationFailed = 5,
}
14 changes: 14 additions & 0 deletions contracts/claim_verifier/src/event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use soroban_sdk::{contracttype, symbol_short, Address, Env};

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VerificationSucceeded {
pub destination: Address,
pub nonce: u64,
}

pub fn emit_verification_succeeded(env: &Env, destination: Address, nonce: u64) {
let event = VerificationSucceeded { destination, nonce };
env.events()
.publish((symbol_short!("verified"),), event);
}
101 changes: 101 additions & 0 deletions contracts/claim_verifier/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#![no_std]

mod errors;
mod events;

use soroban_sdk::{contract, contractimpl, Address, BytesN, Env};

pub use errors::Error;
pub use events::VerificationSucceeded;

#[contract]
pub struct ClaimVerifierContract;

#[contractimpl]
impl ClaimVerifierContract {
/// Initialize with the authorized signer public key
///
/// # Arguments
/// * `authorized_signer` - Ed25519 public key (32 bytes)
///
/// # Errors
/// * `Error::AlreadyInitialized` - called more than once
pub fn initialize(env: Env, authorized_signer: BytesN<32>) -> Result<(), Error> {
if env.storage().instance().has(&"signer") {
return Err(Error::AlreadyInitialized);
}

env.storage()
.instance()
.set(&"signer", &authorized_signer);

Ok(())
}

/// Verify an Ed25519 sweep authorization signature
///
/// Message format matches sweep_controller/authorization.rs:
/// hash(destination + nonce + contract_id)
///
/// # Arguments
/// * `destination` - Destination wallet address
/// * `nonce` - Current sweep nonce
/// * `signature` - Ed25519 signature (64 bytes)
///
/// # Errors
/// * `Error::NotInitialized` - contract not initialized
/// * `Error::AuthorizedSignerNotSet` - no signer stored
/// * `Error::SignatureVerificationFailed` - signature is invalid
pub fn verify(
env: Env,
destination: Address,
nonce: u64,
signature: BytesN<64>,
) -> Result<(), Error> {
// Get authorized signer
let authorized_signer: BytesN<32> = env
.storage()
.instance()
.get(&"signer")
.ok_or(Error::AuthorizedSignerNotSet)?;

// Construct message: hash(destination + nonce + contract_id)
let message = Self::construct_message(&env, &destination, nonce);

// Verify Ed25519 signature
env.crypto()
.ed25519_verify(&authorized_signer, &message.into(), &signature);

// Emit success event
events::emit_verification_succeeded(&env, destination, nonce);

Ok(())
}

// Private helper — constructs the message hash identical to
// sweep_controller/authorization.rs construct_sweep_message
fn construct_message(env: &Env, destination: &Address, nonce: u64) -> BytesN<32> {
use soroban_sdk::xdr::ToXdr;

let contract_id = env.current_contract_address();
let mut message = soroban_sdk::Bytes::new(env);

let dest_bytes = destination.to_xdr(env);
message.append(&dest_bytes);

message.push_back(((nonce >> 56) & 0xFF) as u8);
message.push_back(((nonce >> 48) & 0xFF) as u8);
message.push_back(((nonce >> 40) & 0xFF) as u8);
message.push_back(((nonce >> 32) & 0xFF) as u8);
message.push_back(((nonce >> 24) & 0xFF) as u8);
message.push_back(((nonce >> 16) & 0xFF) as u8);
message.push_back(((nonce >> 8) & 0xFF) as u8);
message.push_back((nonce & 0xFF) as u8);

let contract_bytes = contract_id.to_xdr(env);
message.append(&contract_bytes);

env.crypto().sha256(&message).into()
}
}

6 changes: 5 additions & 1 deletion contracts/sweep_controller/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@ crate-type = ["cdylib", "rlib"]

[dependencies]
soroban-sdk = "22.0.0"
native-transfer = { path = "../native_transfer" }
bridgelet-shared = { path = "../shared", version = "0.1.0" }
ephemeral_account = { path = "../ephemeral_account", version = "0.1.0" }
claim-verifier = { path = "../claim_verifier" }

soroban-token-sdk = "22.0.0"

[dev-dependencies]
soroban-sdk = { version = "22.0.0", features = ["testutils"] }
bridgelet-shared = { path = "../shared", features = ["testutils"] }

native-transfer = { path = "../native_transfer", features = ["testutils"] }
claim-verifier = { path = "../claim_verifier", features = ["testutils"] }

[profile.release]
opt-level = "z"
Expand All @@ -31,3 +34,4 @@ lto = true
[profile.release-with-logs]
inherits = "release"
debug-assertions = true

26 changes: 18 additions & 8 deletions contracts/sweep_controller/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#![no_std]
use claim_verifier::ClaimVerifierContractClient as ClaimVerifierClient;



mod authorization;
mod errors;
Expand Down Expand Up @@ -31,6 +34,9 @@ impl SweepController {
authorized_signer: BytesN<32>,
authorized_destination: Option<Address>,
creator: Address,
native_transfer_address: Address,
native_asset_address: Address,
claim_verifier_address: Address,
) -> Result<(), Error> {
// Check if already initialized
if storage::get_authorized_signer(&env).is_some() {
Expand All @@ -47,6 +53,10 @@ impl SweepController {

// Initialize the sweep nonce to 0
storage::init_sweep_nonce(&env);
storage::set_native_transfer_address(&env, &native_transfer_address);
storage::set_native_asset_address(&env, &native_asset_address);
storage::set_claim_verifier_address(&env, &claim_verifier_address);


// Store authorized destination if provided
if let Some(destination) = authorized_destination {
Expand Down Expand Up @@ -84,17 +94,17 @@ impl SweepController {
}
}

// Verify authorization
let auth_ctx = AuthContext::new(
ephemeral_account.clone(),
destination.clone(),
auth_signature.clone(),
);
auth_ctx.verify(&env)?;
// Verify authorization via claim_verifier contract
let claim_verifier_address = storage::get_claim_verifier_address(&env)
.ok_or(Error::AuthorizationFailed)?;
let verifier = ClaimVerifierClient::new(&env, &claim_verifier_address);
let nonce = storage::get_sweep_nonce(&env);
verifier.verify(&destination, &nonce, &auth_signature);

// Increment nonce after successful verification to prevent replay attacks
// Increment nonce after successful verification
authorization::increment_nonce(&env);


// Call ephemeral account contract to validate and authorize sweep
// This triggers the account's sweep() method which updates state
let account_client = EphemeralAccountClient::new(&env, &ephemeral_account);
Expand Down
16 changes: 16 additions & 0 deletions contracts/sweep_controller/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ pub enum DataKey {
AuthorizedDestination,
/// Creator address (the address that initialized the contract)
Creator,
NativeTransferAddress,
NativeAssetAddress,
ClaimVerifierAddress,
}

/// Set the authorized signer public key
Expand Down Expand Up @@ -125,3 +128,16 @@ pub fn set_creator(env: &Env, creator: &Address) {
pub fn get_creator(env: &Env) -> Option<Address> {
env.storage().instance().get(&DataKey::Creator)
}

pub fn set_claim_verifier_address(env: &Env, address: &Address) {
env.storage()
.instance()
.set(&DataKey::ClaimVerifierAddress, address);
}

pub fn get_claim_verifier_address(env: &Env) -> Option<Address> {
env.storage()
.instance()
.get(&DataKey::ClaimVerifierAddress)
}

Loading