ton-blockchain / multisig-contract-v2

Multiowner wallet
54 stars 19 forks source link

Abuse of dynamic recovery threshold #29

Closed hpiri closed 6 months ago

hpiri commented 6 months ago

MultiSigWallet

This is a MultiSigWallet contract for Ton Coin.

Features

Usage

  1. Deploy the contract to the Ton blockchain.
  2. Add owners to the contract.
  3. Create a transaction.
  4. Sign the transaction with the required number of signatures.
  5. Execute the transaction.

Requirements

Installation

Running

Testing

Documentation

For more information, please see the documentation.

License

This project is licensed under the MIT license.

Contributing

Contributions are welcome.

Code

MultiSigWallet


pragma ton-solidity >= 0.40.0;

import "ton-solidity/contracts/utils/Random.sol";

struct Transaction {
    address to;
    uint value;
    bool executed;
    uint numSignatures;
    mapping(address => bool) signatures;
    mapping(address => bool) approvals; // Multi-step approval
    uint nonce;
    uint requestId; // TON VRF request ID
    uint expirationTime; // Transaction expiration time
}

event TransactionCreated(uint transactionId);
event TransactionSigned(uint transactionId, address owner);
event TransactionApproved(uint transactionId, address approver);
event TransactionExecuted(uint transactionId);
event TransactionNonceGenerated(uint transactionId, uint nonce);
event RequestRandomWords(uint requestId);
event RecoveryThresholdReached(address recovery);
event OwnerAdded(address owner);
event OwnerRemoved(address owner);
event RequiredSignaturesChanged(uint requiredSignatures);
event ContractUpgraded(address newContract);

address[] private owners;
uint private requiredSignatures;
mapping(address => bool) private isOwner;
uint private transactionCount;
mapping(uint => Transaction) private transactions;
uint constant MAX_GAS_PER_TX = 1000000; // Limit gas per transaction (optional)

Random public random;

constructor(address[] _owners, uint _requiredSignatures, address _random) {
    owners = _owners;
    requiredSignatures = _requiredSignatures;
    for (uint i = 0; i < _owners.length; i++) {
        isOwner[_owners[i]] = true;
    }
    random = Random(_random);
}

function createTransaction(address _to, uint _value, uint _expirationTime)
    external onlyOwner returns (uint) {
    uint _nonce;
    transactions[transactionCount] = Transaction(_to, _value, false, 0, 0, 0, _expirationTime);
    emit TransactionCreated(transactionCount);
    return transactionCount++;
}

function generateRandomNumber(uint _transactionId) external onlyOwner validTransaction(_transactionId) {
    require(transactions[_transactionId].nonce == 0, "Nonce already generated");
    transactions[_transactionId].nonce = random.generate();
    emit TransactionNonceGenerated(_transactionId, transactions[_transactionId].nonce);
}

function signTransaction(uint _transactionId) external validTransaction(_transactionId) {
    require(!transactions[_transactionId].signatures[msg.sender], "Already signed");
    transactions[_transactionId].signatures[msg.sender] = true;
    transactions[_transactionId].numSignatures++;
    emit TransactionSigned(_transactionId, msg.sender);
}

function approveTransaction(uint _transactionId) external validTransaction(_transactionId) {
    require(approvalsEnabled && !transactions[_transactionId].approvals[msg.sender], "Already approved");
    transactions[_transactionId].approvals[msg.sender] = true;
    emit TransactionApproved(_transactionId, msg.sender);
}

function executeTransaction(uint _transactionId) external validTransaction(_transactionId) {
    require(transactions[_transactionId].numSignatures >= requiredSignatures, "Not enough signatures");
    require(transactions[_transactionId].approvals[msg.sender], "Not approved");
    transactions[_transactionId].executed = true;
    emit TransactionExecuted(_transactionId);
}
function addOwner(address _owner) external onlyOwner {
    require(!isOwner[_owner], "Owner already exists");
    owners.push(_owner);
    isOwner[_owner] = true;
    emit OwnerAdded(_owner);
}

function removeOwner(address _owner) external onlyOwner {
    require(owners.length > requiredSignatures, "Cannot remove owner below required signatures");
    require(_owner != msg.sender, "Cannot remove yourself");
    isOwner[_owner] = false;
    for (uint i = 0; i < owners.length; i++) {
        if (owners[i] == _owner) {
            owners[i] = owners[owners.length - 1];
            owners.pop();
            break;
        }
    }
    emit OwnerRemoved(_owner);
}

function changeRequiredSignatures(uint _requiredSignatures) external onlyOwner {
    require(_requiredSignatures > 0, "Required signatures must be greater than 0");
    requiredSignatures = _requiredSignatures;
    emit RequiredSignaturesChanged(_requiredSignatures);
}

function upgradeContract(address _newContract) external onlyOwner {
    emit ContractUpgraded(_newContract);
    // TODO: Implement contract upgrade logic
}

modifier onlyOwner() {
    require(isOwner[msg.sender], "Only owner can call this function");
    _;
}

modifier validTransaction(uint _transactionId) {
    require(_transactionId < transactionCount, "Invalid transaction ID");
    _;
}

function recoverFunds(address _to) external {
    require(recoveryThresholdReached(), "Recovery threshold not reached");
    // TODO: Implement funds recovery logic
}

function setApprovalsEnabled(bool _enabled) external onlyOwner {
    approvalsEnabled = _enabled;
}

function getOwners() external view returns (address[] memory) {
    return owners;
}

function getRequiredSignatures() external view returns (uint) {
    return requiredSignatures;
}

function getTransactionCount() external view returns (uint) {
    return transactionCount;
}

function getTransaction(uint _transactionId) external view returns (Transaction memory) {
    return transactions[_transactionId];
}

function isOwner(address _owner) external view returns (bool) {
    return isOwner[_owner];
}

function recoveryThresholdReached() public view returns (bool) {
  // Default threshold: Majority of owners (rounded up)
  uint threshold = owners.length / 2 + 1;

  // Implement custom logic for recovery threshold here (optional)
  // For example, check for a specific number of signatures or a specific time period

  return _recoveryThresholdReached(threshold);
}

function _recoveryThresholdReached(uint _threshold) private view returns (bool) {
  uint numApprovals = 0;
  for (uint i = 0; i < owners.length; i++) {
    if (recoveryApprovals[owners[i]]) {
      numApprovals++;
    }
  }
  return numApprovals >= _threshold;
}

1. To provide a MultiSigWallet 2.0 with:
-Dynamic recovery threshold
-Multi-step recovery logic
-Time-based transaction approval
-Penalty mechanism for delayed approval

2. Potential error:
-Abuse of dynamic recovery threshold

3. Solution:
-Implement time restrictions for threshold changes.
-Require majority or 2/3 signatures for threshold changes.
-Limit the number of threshold changes within a time period.
-Use a more complex algorithm for calculating the dynamic recovery threshold.
-Allow owners to set different access levels for different transactions.
-Implement a warning system for sensitive or unusual transactions.

https://t.me/hp_473
hpiri commented 6 months ago

https://t.me/hp_473

ProgramCrafter commented 6 months ago

Completely irrelevant to repository in question.

hpiri commented 6 months ago

How?😊

tolya-yanot commented 6 months ago

chatgpt, ban