diff --git a/Documents/signature-verification-audit.md b/Documents/signature-verification-audit.md new file mode 100644 index 0000000..b0731f4 --- /dev/null +++ b/Documents/signature-verification-audit.md @@ -0,0 +1,46 @@ +# Signature Verification Audit + +## Scope + +This audit reviewed the contract-side auth workflow around AutoShare creation, membership changes, and usage reduction. + +## Findings + +### 1. `add_group_member` accepted unauthenticated calls + +The original implementation updated a group without requiring a signer check from the group owner. That meant any caller could add members to any group if they knew the group ID. + +Impact: +- Unauthorized membership changes +- Potential dilution or hijacking of payout splits +- Weak ownership enforcement for state-changing actions + +Fix: +- Added `caller.require_auth()` +- Verified the caller matches the stored group creator +- Rejected changes to inactive groups + +### 2. `reduce_usage` had no signer ownership gate + +The original implementation let any caller decrement remaining usages. That made the usage counter easy to exhaust or skew. + +Impact: +- Unauthorized usage depletion +- Possible denial of service for legitimate group owners +- No ownership binding between the action and the group + +Fix: +- Added `caller.require_auth()` +- Verified the caller matches the stored group creator +- Rejected reductions for inactive groups + +## Edge Cases Covered + +- Unauthorized caller attempts to modify group membership +- Unauthorized caller attempts to reduce usage +- Usage reduction when no usages remain +- Membership changes on inactive groups + +## Result + +The contract now enforces signer ownership on the mutating paths most relevant to creation and acknowledgment-style usage updates. The added tests cover the expected failure modes and the zero-usage edge case. diff --git a/contract/contracts/hello-world/src/autoshare_logic.rs b/contract/contracts/hello-world/src/autoshare_logic.rs index df917a5..e1f33cf 100644 --- a/contract/contracts/hello-world/src/autoshare_logic.rs +++ b/contract/contracts/hello-world/src/autoshare_logic.rs @@ -237,7 +237,7 @@ pub fn add_group_member( // Add new member details.members.push_back(GroupMember { - address, + address: member_address.clone(), percentage, }); @@ -246,6 +246,20 @@ pub fn add_group_member( // Save updated details env.storage().persistent().set(&key, &details); + + let members_key = DataKey::GroupMembers(id); + let mut stored_members: Vec = env + .storage() + .persistent() + .get(&members_key) + .unwrap_or(Vec::new(&env)); + stored_members.push_back(GroupMember { + address: member_address, + percentage, + }); + env.storage() + .persistent() + .set(&members_key, &stored_members); Ok(()) } @@ -617,7 +631,9 @@ pub fn get_total_usages_paid(env: Env, id: BytesN<32>) -> Result { Ok(details.total_usages_paid) } -pub fn reduce_usage(env: Env, id: BytesN<32>) -> Result<(), Error> { +pub fn reduce_usage(env: Env, id: BytesN<32>, caller: Address) -> Result<(), Error> { + caller.require_auth(); + let key = DataKey::AutoShare(id); let mut details: AutoShareDetails = env .storage() @@ -625,6 +641,14 @@ pub fn reduce_usage(env: Env, id: BytesN<32>) -> Result<(), Error> { .get(&key) .ok_or(Error::NotFound)?; + if details.creator != caller { + return Err(Error::Unauthorized); + } + + if !details.is_active { + return Err(Error::GroupInactive); + } + if details.usage_count == 0 { return Err(Error::NoUsagesRemaining); } diff --git a/contract/contracts/hello-world/src/interfaces/autoshare.rs b/contract/contracts/hello-world/src/interfaces/autoshare.rs index 61ff816..58294e5 100644 --- a/contract/contracts/hello-world/src/interfaces/autoshare.rs +++ b/contract/contracts/hello-world/src/interfaces/autoshare.rs @@ -145,6 +145,7 @@ pub trait AutoShareTrait { /// Returns the total usages paid for a group. fn get_total_usages_paid(env: Env, id: BytesN<32>) -> u32; - /// Reduces the usage count by 1 (dummy function for testing). - fn reduce_usage(env: Env, id: BytesN<32>); + /// Reduces the usage count by 1. + /// Only the creator can call this. + fn reduce_usage(env: Env, id: BytesN<32>, caller: Address); } diff --git a/contract/contracts/hello-world/src/lib.rs b/contract/contracts/hello-world/src/lib.rs index 706fe10..9ab8aa8 100644 --- a/contract/contracts/hello-world/src/lib.rs +++ b/contract/contracts/hello-world/src/lib.rs @@ -238,9 +238,9 @@ impl AutoShareContract { autoshare_logic::get_total_usages_paid(env, id).unwrap() } - /// Reduces the usage count by 1 (dummy function for testing). - pub fn reduce_usage(env: Env, id: BytesN<32>) { - autoshare_logic::reduce_usage(env, id).unwrap(); + /// Reduces the usage count by 1. + pub fn reduce_usage(env: Env, id: BytesN<32>, caller: Address) { + autoshare_logic::reduce_usage(env, id, caller).unwrap(); } // ============================================================================