diff --git a/contracts/ZKMultisig.sol b/contracts/ZKMultisig.sol
index 19fa4d8..a822940 100644
--- a/contracts/ZKMultisig.sol
+++ b/contracts/ZKMultisig.sol
@@ -1,97 +1,83 @@
 // SPDX-License-Identifier: MIT
 pragma solidity ^0.8.4;
 
-import {IZKMultisig} from "./interfaces/IZKMultisig.sol";
-
-import {SparseMerkleTree} from "@solarity/solidity-lib/libs/data-structures/SparseMerkleTree.sol";
-import {PRECISION, PERCENTAGE_100} from "@solarity/solidity-lib/utils/Globals.sol";
-import {Paginator} from "@solarity/solidity-lib/libs/arrays/Paginator.sol";
-
 import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
 import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
 import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
 import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
 import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
+import {Address} from "@openzeppelin/contracts/utils/Address.sol";
 
-import {PoseidonUnit1L} from "@iden3/contracts/lib/Poseidon.sol";
+import {SparseMerkleTree} from "@solarity/solidity-lib/libs/data-structures/SparseMerkleTree.sol";
+import {PRECISION, PERCENTAGE_100} from "@solarity/solidity-lib/utils/Globals.sol";
+import {Paginator} from "@solarity/solidity-lib/libs/arrays/Paginator.sol";
+import {VerifierHelper} from "@solarity/solidity-lib/libs/zkp/snarkjs/VerifierHelper.sol";
+
+import {IZKMultisig} from "./interfaces/IZKMultisig.sol";
+import {PoseidonUnit1L} from "./libs/Poseidon.sol";
 
 contract ZKMultisig is UUPSUpgradeable, IZKMultisig {
     using SparseMerkleTree for SparseMerkleTree.Bytes32SMT;
     using EnumerableSet for EnumerableSet.Bytes32Set;
     using EnumerableSet for EnumerableSet.UintSet;
     using Paginator for EnumerableSet.UintSet;
+    using VerifierHelper for address;
+    using Address for address;
     using Math for uint256;
 
-    enum ParticipantsAction {
-        ADD,
-        REMOVE
+    struct ProposalData {
+        ProposalContent content;
+        uint256 proposalEndTime;
+        EnumerableSet.UintSet blinders;
+        uint256 requiredQuorum;
+        bool executed;
     }
 
-    uint256 public constant TREE_SIZE = 20;
+    uint256 public constant PARTICIPANTS_TREE_DEPTH = 20;
+    uint256 public constant MIN_QUORUM_SIZE = 1;
+
+    address public _participantVerifier;
 
-    SparseMerkleTree.Bytes32SMT internal _bytes32Tree;
+    SparseMerkleTree.Bytes32SMT internal _participantsSMTTree;
     EnumerableSet.Bytes32Set internal _participants;
     EnumerableSet.UintSet internal _proposalIds;
 
     uint256 private _quorumPercentage;
 
-    mapping(uint256 => ProposalInfoView) private _proposals;
-    mapping(uint256 => uint256) private _blinders;
-
-    event Initialized(uint256 participantsAmount, uint256 quorumPercentage);
-    event RootUpdated(bytes32 indexed root);
-    event QuorumPercentageUpdated(uint256 indexed newQuorumPercentage);
+    mapping(uint256 => ProposalData) private _proposals;
 
     modifier onlyThis() {
         require(msg.sender == address(this), "ZKMultisig: Not authorized call");
         _;
     }
 
-    modifier withRootUpdate() {
-        _;
-        _notifyRoot();
-    }
-
-    modifier withQuorumUpdate() {
-        _;
-        _notifyQourumPercentage();
-    }
-
     constructor() {
         _disableInitializers();
     }
 
     function initialize(
         uint256[] memory participants_,
-        uint256 quorumPercentage_
+        uint256 quorumPercentage_,
+        address participantVerifier_
     ) external initializer {
-        __ZKMultisig_init(participants_, quorumPercentage_);
-    }
+        require(participantVerifier_ != address(0), "ZKMultisig: Invalid verifier address");
 
-    function __ZKMultisig_init(
-        uint256[] memory participants_,
-        uint256 quorumPercentage_
-    ) internal {
         _updateQourumPercentage(quorumPercentage_);
-        _bytes32Tree.initialize(uint32(TREE_SIZE));
+        _participantsSMTTree.initialize(uint32(PARTICIPANTS_TREE_DEPTH));
         _addParticipants(participants_);
+
+        _participantVerifier = participantVerifier_;
     }
 
-    function addParticipants(
-        uint256[] calldata participantsToAdd_
-    ) external onlyThis withRootUpdate {
+    function addParticipants(uint256[] calldata participantsToAdd_) external onlyThis {
         _addParticipants(participantsToAdd_);
     }
 
-    function removeParticipants(
-        uint256[] calldata participantsToRemove_
-    ) external onlyThis withRootUpdate {
+    function removeParticipants(uint256[] calldata participantsToRemove_) external onlyThis {
         _removeParticipants(participantsToRemove_);
     }
 
-    function updateQuorumPercentage(
-        uint256 newQuorumPercentage_
-    ) external onlyThis withQuorumUpdate {
+    function updateQuorumPercentage(uint256 newQuorumPercentage_) external onlyThis {
         _updateQourumPercentage(newQuorumPercentage_);
     }
 
@@ -101,32 +87,37 @@ contract ZKMultisig is UUPSUpgradeable, IZKMultisig {
         uint256 salt_,
         ZKParams calldata proofData_
     ) external returns (uint256) {
+        // validate inputs
+        require(duration_ > 0, "ZKMultisig: Invalid duration");
+        require(content_.target != address(0), "ZKMultisig: Invalid target");
+
         uint256 proposalId_ = _computeProposalId(content_, salt_);
 
+        // validate proposal state
         require(
-            _proposals[proposalId_].status == ProposalStatus.NONE,
+            !_proposalIds.contains(proposalId_) &&
+                _getProposalStatus(proposalId_) == ProposalStatus.NONE,
             "ZKMultisig: Proposal already exists"
         );
 
-        require(duration_ > 0, "ZKMultisig: Invalid duration");
+        // validate zk params
+        _validateZKParams(proposalId_, proofData_);
 
-        uint256 votesCount_ = 1; // 1 vote from creator
-        uint256 requiredQuorum_ = ((_participants.length() * _quorumPercentage) / PERCENTAGE_100)
-            .max(1);
+        ProposalData storage _proposal = _proposals[proposalId_];
+        _proposalIds.add(proposalId_);
 
-        _proposals[proposalId_] = ProposalInfoView({
-            content: content_,
-            proposalEndTime: block.timestamp + duration_,
-            status: votesCount_ >= requiredQuorum_
-                ? ProposalStatus.ACCEPTED
-                : ProposalStatus.VOTING,
-            votesCount: votesCount_,
-            requiredQuorum: requiredQuorum_
-        });
+        _proposal.content = content_;
+        _proposal.proposalEndTime = block.timestamp + duration_;
+        _proposal.requiredQuorum = ((_participants.length() * _quorumPercentage) / PERCENTAGE_100)
+            .max(MIN_QUORUM_SIZE);
 
-        _proposalIds.add(proposalId_);
-        // assign proposalId to blinder
-        _blinders[proofData_.inputs[0]] = proposalId_;
+        require(
+            _getProposalStatus(proposalId_) == ProposalStatus.VOTING,
+            "ZKMultisig: Incorrect proposal voting state after creation"
+        );
+
+        // vote on behalf of the creator
+        _vote(proposalId_, proofData_.inputs[0]);
 
         emit ProposalCreated(proposalId_, content_);
 
@@ -134,56 +125,46 @@ contract ZKMultisig is UUPSUpgradeable, IZKMultisig {
     }
 
     function vote(uint256 proposalId_, ZKParams calldata proofData_) external {
-        ProposalInfoView storage _proposal = _proposals[proposalId_];
-        uint256 blinder_ = proofData_.inputs[0];
-
         require(
-            _proposal.status == ProposalStatus.VOTING,
+            _getProposalStatus(proposalId_) == ProposalStatus.VOTING,
             "ZKMultisig: Proposal is not in voting state"
         );
 
-        require(block.timestamp < _proposal.proposalEndTime, "ZKMultisig: Proposal expired");
-
-        require(!_isBlinderVoted(proposalId_, blinder_), "ZKMultisig: Already voted");
+        _validateZKParams(proposalId_, proofData_);
 
-        _blinders[blinder_] = proposalId_;
-
-        _proposal.votesCount += 1;
-
-        if (_proposal.votesCount >= _proposal.requiredQuorum) {
-            _proposal.status = ProposalStatus.ACCEPTED;
-        }
+        _vote(proposalId_, proofData_.inputs[0]);
 
-        emit ProposalVoted(proposalId_, blinder_);
+        emit ProposalVoted(proposalId_, proofData_.inputs[0]);
     }
 
-    function execute(uint256 proposalId_) external {
-        ProposalInfoView storage _proposal = _proposals[proposalId_];
-
+    function execute(uint256 proposalId_) external payable {
         require(
-            _proposal.status == ProposalStatus.ACCEPTED,
+            _getProposalStatus(proposalId_) == ProposalStatus.ACCEPTED,
             "ZKMultisig: Proposal is not accepted"
         );
 
-        (bool success, ) = _proposal.content.target.call{value: _proposal.content.value}(
-            _proposal.content.data
-        );
+        ProposalData storage _proposal = _proposals[proposalId_];
+
+        require(msg.value == _proposal.content.value, "ZKMultisig: Invalid value");
 
-        require(success, "ZKMultisig: Proposal execution failed");
+        _proposal.content.target.functionCallWithValue(
+            _proposal.content.data,
+            _proposal.content.value
+        );
 
-        _proposal.status = ProposalStatus.EXECUTED;
+        _proposal.executed = true;
 
         emit ProposalExecuted(proposalId_);
     }
 
     function getParticipantsSMTRoot() external view returns (bytes32) {
-        return _bytes32Tree.getRoot();
+        return _participantsSMTTree.getRoot();
     }
 
     function getParticipantsSMTProof(
         bytes32 publicKeyHash_
     ) external view override returns (SparseMerkleTree.Proof memory) {
-        return _bytes32Tree.getProof(publicKeyHash_);
+        return _participantsSMTTree.getProof(publicKeyHash_);
     }
 
     function getParticipantsCount() external view returns (uint256) {
@@ -210,21 +191,24 @@ contract ZKMultisig is UUPSUpgradeable, IZKMultisig {
     }
 
     function getProposalInfo(uint256 proposalId_) external view returns (ProposalInfoView memory) {
-        return _proposals[proposalId_];
+        ProposalData storage _proposal = _proposals[proposalId_];
+
+        return
+            ProposalInfoView({
+                content: _proposal.content,
+                proposalEndTime: _proposal.proposalEndTime,
+                status: _getProposalStatus(proposalId_),
+                votesCount: _proposal.blinders.length(),
+                requiredQuorum: _proposal.requiredQuorum
+            });
     }
 
     function getProposalStatus(uint256 proposalId_) external view returns (ProposalStatus) {
-        return _proposals[proposalId_].status;
+        return _getProposalStatus(proposalId_);
     }
 
-    // double check doc, bc there uint248(keccak256(abi.encode(block.chainid, address(this), proposalId_))) is used
     function getProposalChallenge(uint256 proposalId_) external view returns (uint256) {
-        return
-            uint256(
-                PoseidonUnit1L.poseidon(
-                    [uint256(keccak256(abi.encode(block.chainid, address(this), proposalId_)))]
-                )
-            );
+        return _getProposalChallenge(proposalId_);
     }
 
     function computeProposalId(
@@ -245,45 +229,23 @@ contract ZKMultisig is UUPSUpgradeable, IZKMultisig {
 
     function _addParticipants(uint256[] memory participantsToAdd_) internal {
         require(
-            _participants.length() + participantsToAdd_.length <= 2 ** TREE_SIZE,
+            _participants.length() + participantsToAdd_.length <= 2 ** PARTICIPANTS_TREE_DEPTH,
             "ZKMultisig: Too many participants"
         );
-        _processParticipants(participantsToAdd_, ParticipantsAction.ADD);
 
-        // require(participantsToAdd.length > 0, "ZKMultisig: No participants to add");
-        // for (uint256 i = 0; i < participantsToAdd.length; i++) {
-        //     uint256 participant_ = participantsToAdd[i];
-        //     bytes32 participantKey_ = keccak256(abi.encodePacked(participant_));
-        //     if (_uintTree.getProof(participantKey_).existence) {
-        //         continue;
-        //         // or revert?
-        //     }
-        //     _uintTree.add(participantKey_, participant_);
-        // }
+        _processParticipants(participantsToAdd_, true);
     }
 
     function _removeParticipants(uint256[] memory participantsToRemove_) internal {
-        require(
-            _participants.length() > participantsToRemove_.length,
-            "ZKMultisig: Cannot remove all participants"
-        );
-        _processParticipants(participantsToRemove_, ParticipantsAction.REMOVE);
+        _processParticipants(participantsToRemove_, false);
 
-        // require(participantsToRemove.length > 0, "ZKMultisig: No participants to remove");
-        // for (uint256 i = 0; i < participantsToRemove.length; i++) {
-        //     uint256 participant_ = participantsToRemove[i];
-        //     bytes32 participantKey_ = keccak256(abi.encodePacked(participant_));
-        //     if (_uintTree.getProof(participantKey_).existence) {
-        //         _uintTree.remove(participantKey_);
-        //         // should revert if false?
-        //     }
-        // }
+        require(_participants.length() > 0, "ZKMultisig: Cannot remove all participants");
     }
 
     function _updateQourumPercentage(uint256 newQuorumPercentage_) internal {
         require(
             newQuorumPercentage_ > 0 &&
-                newQuorumPercentage_ <= 100 &&
+                newQuorumPercentage_ <= PERCENTAGE_100 &&
                 newQuorumPercentage_ != _quorumPercentage,
             "ZKMultisig: Invalid quorum percentage"
         );
@@ -291,6 +253,75 @@ contract ZKMultisig is UUPSUpgradeable, IZKMultisig {
         _quorumPercentage = newQuorumPercentage_;
     }
 
+    // internal vote skipping validation
+    function _vote(uint256 proposalId_, uint256 blinder_) internal {
+        ProposalData storage _proposal = _proposals[proposalId_];
+        _proposal.blinders.add(blinder_);
+    }
+
+    function _validateZKParams(uint256 proposalId_, ZKParams calldata proofData_) internal view {
+        require(proofData_.inputs.length == 3, "ZKMultisig: Invalid proof data");
+
+        require(
+            !_isBlinderVoted(proposalId_, proofData_.inputs[0]),
+            "ZKMultisig: Blinder already voted"
+        );
+
+        require(
+            proofData_.inputs[1] == _getProposalChallenge(proposalId_),
+            "ZKMultisig: Invalid challenge"
+        );
+
+        require(
+            proofData_.inputs[2] == uint256(_participantsSMTTree.getRoot()),
+            "ZKMultisig: Invalid SMT root"
+        );
+
+        require(
+            _participantVerifier.verifyProof(
+                proofData_.inputs,
+                VerifierHelper.ProofPoints({a: proofData_.a, b: proofData_.b, c: proofData_.c})
+            ),
+            "ZKMultisig: Invalid proof"
+        );
+    }
+
+    function _getProposalStatus(uint256 proposalId_) internal view returns (ProposalStatus) {
+        ProposalData storage _proposal = _proposals[proposalId_];
+
+        // Check if the proposal exists by verifying the end time
+        if (_proposal.proposalEndTime == 0) {
+            return ProposalStatus.NONE;
+        }
+
+        // Check if the proposal has been executed
+        if (_proposal.executed) {
+            return ProposalStatus.EXECUTED;
+        }
+
+        // Check if the proposal has met the quorum requirement
+        if (_proposal.blinders.length() >= _proposal.requiredQuorum) {
+            return ProposalStatus.ACCEPTED;
+        }
+
+        // Check if the proposal is still within the voting period
+        if (_proposal.proposalEndTime > block.timestamp) {
+            return ProposalStatus.VOTING;
+        }
+
+        // If the proposal has not met the quorum and the voting period has expired
+        return ProposalStatus.EXPIRED;
+    }
+
+    function _getProposalChallenge(uint256 proposalId_) internal view returns (uint256) {
+        return
+            uint256(
+                PoseidonUnit1L.poseidon(
+                    [uint256(keccak256(abi.encode(block.chainid, address(this), proposalId_)))]
+                )
+            );
+    }
+
     function _computeProposalId(
         ProposalContent calldata content_,
         uint256 salt_
@@ -303,39 +334,26 @@ contract ZKMultisig is UUPSUpgradeable, IZKMultisig {
         uint256 proposalId_,
         uint256 blinderToCheck_
     ) internal view returns (bool) {
-        return _blinders[blinderToCheck_] == proposalId_;
+        return _proposals[proposalId_].blinders.contains(blinderToCheck_);
     }
 
-    function _notifyRoot() internal {
-        emit RootUpdated(_bytes32Tree.getRoot());
-    }
-
-    function _notifyQourumPercentage() internal {
-        emit QuorumPercentageUpdated(_quorumPercentage);
-    }
-
-    function _processParticipants(
-        uint256[] memory participants_,
-        ParticipantsAction action_
-    ) private {
+    function _processParticipants(uint256[] memory participants_, bool isAdding_) private {
         require(participants_.length > 0, "Multisig: No participants to process");
 
         for (uint256 i = 0; i < participants_.length; i++) {
             bytes32 participant_ = bytes32(participants_[i]);
             bytes32 participantKey_ = keccak256(abi.encodePacked(participant_));
 
-            bool nodeExists = _bytes32Tree.getProof(participantKey_).existence;
-
-            // revert in false case?
-            if (!nodeExists && action_ == ParticipantsAction.ADD) {
-                _bytes32Tree.add(participantKey_, participant_);
-                _participants.add(participant_);
-            }
-
-            // revert in false case?
-            if (nodeExists && action_ == ParticipantsAction.REMOVE) {
-                _bytes32Tree.remove(participantKey_);
-                _participants.remove(participant_);
+            if (isAdding_) {
+                if (!_participants.contains(participant_)) {
+                    _participantsSMTTree.add(participantKey_, participant_);
+                    _participants.add(participant_);
+                }
+            } else {
+                if (_participants.contains(participant_)) {
+                    _participantsSMTTree.remove(participantKey_);
+                    _participants.remove(participant_);
+                }
             }
         }
     }
diff --git a/contracts/ZKMultisigFactory.sol b/contracts/ZKMultisigFactory.sol
index 620ff0a..02e8104 100644
--- a/contracts/ZKMultisigFactory.sol
+++ b/contracts/ZKMultisigFactory.sol
@@ -20,12 +20,19 @@ contract ZKMultisigFactory is EIP712, IZKMultisigFactory {
 
     bytes32 private constant KDF_MESSAGE_TYPEHASH = keccak256("KDF(address zkMultisigAddress)");
 
-    string private constant EIP712_NAME = "ZKMultisigFactory";
-    string private constant EIP712_VERSION = "1";
-
+    address private _participantVerifier;
     address private immutable _zkMulsigImplementation;
 
-    constructor(address zkMultisigImplementation_) EIP712(EIP712_NAME, EIP712_VERSION) {
+    constructor(
+        address zkMultisigImplementation_,
+        address participantVerifier_
+    ) EIP712("ZKMultisigFactory", "1") {
+        require(
+            zkMultisigImplementation_ != address(0) && participantVerifier_ != address(0),
+            "ZKMultisigFactory: Invalid implementation or verifier address"
+        );
+
+        _participantVerifier = participantVerifier_;
         _zkMulsigImplementation = zkMultisigImplementation_;
     }
 
@@ -41,7 +48,11 @@ contract ZKMultisigFactory is EIP712, IZKMultisigFactory {
             )
         );
 
-        IZKMultisig(zkMultisigAddress_).initialize(participants_, quorumPercentage_);
+        IZKMultisig(zkMultisigAddress_).initialize(
+            participants_,
+            quorumPercentage_,
+            _participantVerifier
+        );
 
         _zkMultisigs.add(zkMultisigAddress_);
 
diff --git a/contracts/interfaces/IZKMultisig.sol b/contracts/interfaces/IZKMultisig.sol
index 2e6d7bc..c8b8acb 100644
--- a/contracts/interfaces/IZKMultisig.sol
+++ b/contracts/interfaces/IZKMultisig.sol
@@ -39,7 +39,11 @@ interface IZKMultisig {
 
     event ProposalExecuted(uint256 indexed proposalId);
 
-    function initialize(uint256[] memory participants_, uint256 quorumPercentage_) external;
+    function initialize(
+        uint256[] memory participants_,
+        uint256 quorumPercentage_,
+        address participantVerifier_
+    ) external;
 
     function addParticipants(uint256[] calldata participantsToAdd) external;
 
@@ -56,7 +60,7 @@ interface IZKMultisig {
 
     function vote(uint256 proposalId, ZKParams calldata proofData) external;
 
-    function execute(uint256 proposalId) external;
+    function execute(uint256 proposalId) external payable;
 
     function getParticipantsSMTRoot() external view returns (bytes32);
 
diff --git a/contracts/libs/Poseidon.sol b/contracts/libs/Poseidon.sol
new file mode 100644
index 0000000..9892599
--- /dev/null
+++ b/contracts/libs/Poseidon.sol
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.4;
+
+library PoseidonUnit1L {
+    function poseidon(uint256[1] calldata) public pure returns (uint256) {}
+}
diff --git a/package-lock.json b/package-lock.json
index e99de26..34cd070 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,15 +1,14 @@
 {
-  "name": "hardhat-template",
+  "name": "zk-multisig",
   "version": "1.0.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
-      "name": "hardhat-template",
+      "name": "zk-multisig",
       "version": "1.0.0",
       "license": "MIT",
       "dependencies": {
-        "@iden3/contracts": "2.1.2",
         "@openzeppelin/contracts": "5.0.2",
         "@openzeppelin/contracts-upgradeable": "5.0.2",
         "@solarity/solidity-lib": "2.7.11",
@@ -999,15 +998,6 @@
         "node": ">=14"
       }
     },
-    "node_modules/@iden3/contracts": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/@iden3/contracts/-/contracts-2.1.2.tgz",
-      "integrity": "sha512-0I+Cmbqn0nNEV4eSg8BqGZSbveY8WokposmwmmKE12gR/IKZAH4MsUHmz8uOTaS2q4bclT3iMEPWu3+vYGg77g==",
-      "dependencies": {
-        "@openzeppelin/contracts": "^5.0.2",
-        "@openzeppelin/contracts-upgradeable": "^5.0.2"
-      }
-    },
     "node_modules/@isaacs/cliui": {
       "version": "8.0.2",
       "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
diff --git a/package.json b/package.json
index 8d82c2c..0284577 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
 {
-  "name": "hardhat-template",
+  "name": "zk-multisig",
   "version": "1.0.0",
   "license": "MIT",
   "author": "Distributed Lab",
@@ -33,7 +33,6 @@
     "@openzeppelin/contracts": "5.0.2",
     "@openzeppelin/contracts-upgradeable": "5.0.2",
     "@solarity/solidity-lib": "2.7.11",
-    "@iden3/contracts": "2.1.2",
     "dotenv": "16.4.5",
     "hardhat": "2.20.1",
     "typechain": "8.3.2"