Compromised Keys Hacks

  • December 02, 2021Badger DAO: A front-end attack, due to a basic OPSEC error of granting unlimited approvals to an EOA, $120 million taken in various forms of wBTC and ERC20.
  • December 05, 2021BitMart: the self-proclaimed “Most Trusted Crypto Trading Platform”, has lost ~$196M from two of its hot wallets on Ethereum and BSC.
  • December 07, 2021–8ight Finance: keys got compromised, no further details shared. $1.75 million taken.
  • December 11, 2021AscendEX: lost $77.7M, again from a “compromised” hot wallet.
  • December 13, 2021Vulcan Forged: $140M wiped from users wallets due to compromised managed wallet provider.


There isn’t much to write about, the self-proclaimed “Most Trusted Crypto Trading Platform”, has lost 100M USD worth of several ERC20 tokens, from one of its Ethereum hot wallets and 96M USD a BSC wallet.


An unknown party inserted additional approvals to send users’ tokens to their own address.

8ight Finance

Their opsec might have been bad, though not as bad as their English in the below Discord announcement.


Ironically, Ascendex made the following Tweet the day before the “hack”, they were quick to delete it afterwards, however somebody managed to immortalized it first:

Vulcan Forged

In order to facilitate these use cases, user accounts are linked to an integrated wallet — a service provided by Venly.

Preventative Techniques

For Users

1. Know what you are approving.

Check the approved address yourself. Don’t trust the site’s UI.

  • Is the contract brand new?
  • Who deployed it?
  • Where did the funds come from to the deployer
  • Is it a proxy?

2. Know how much you are approving.

Never approved more than you plan to use. You can always approve more in the future. Yes, it costs a few $ more, so is psychological help once you will get rugged.

3. Approvals are per token.

So if you approved WETH on some shady contract only your WETH is at risk due to that approval.

4. Be extra tight with your approvals on proxies.

You are not only approving the current implementation, you are also approving the next implementation, and the next implementation, and the next implementation….

5. Quarterly review of all your approvals. Go over each approval, verify if it makes sense. See what is less of a hassle, revoking the odd approval or migrating all tokens to a fresh address.

6. If you are doing an infinite approval, you should have a good reason for it. It should not be your default.

Not sure what an infinite approval looks like? it looks like this:

For Developers

We’ve seen this many times in the past, a project fails to implent access controls and something unexpected happens, e.g. Parity “hack” where a user accidentally destroyed the contract.


OpenZeppelin ownable pattern where ownership of a contract is assigned to a EOA, multiple EOAs, multisig accounts, or governance. The private keys for these accounts can be held in wallets such as MetaMask, hardware wallets, or more advanced setups using secure vaults with Defender Relay. If multisigs are used, they can be managed through a user-friendly UI with Defender Admin or Gnosis Safe.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract MyToken is ERC721, Ownable {
using Counters for Counters.Counter;

Counters.Counter private _tokenIdCounter;

constructor() ERC721("MyToken", "MTK") {}

function safeMint(address to) public onlyOwner {
uint256 tokenId = _tokenIdCounter.current();
_safeMint(to, tokenId);

Role-based access controls

The number and privileges of roles will heavily depend on each particular application and the teams behind them. Too many roles can be difficult to manage, but it limits the risk of granting all control to a single account.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract MyToken is ERC721, AccessControl {
using Counters for Counters.Counter;

bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
Counters.Counter private _tokenIdCounter;

constructor() ERC721("MyToken", "MTK") {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(MINTER_ROLE, msg.sender);

function safeMint(address to) public onlyRole(MINTER_ROLE) {
uint256 tokenId = _tokenIdCounter.current();
_safeMint(to, tokenId);

// The following functions are overrides required by Solidity.

function supportsInterface(bytes4 interfaceId)
override(ERC721, AccessControl)
returns (bool)
return super.supportsInterface(interfaceId);


Timelocks are a common mechanism for allowing time-delayed opt-out changes in a system.


Smart-contract-based governance modules are becoming increasingly common in decentralized applications. In simple terms, they can be understood as:

  • Guardians (privileged accounts with special powers) can help in at least two ways:
  • Push for proposals if there’s no active community participation.
  • Stop proposals if there’s malicious intent.
  • Delegation mechanisms of voting power may also allow interested actors to participate on behalf of others that may not be always interested or available.
  • If governance has full control over a system, and there are no privileged accounts involved, setting up off-chain validations, checklists and documentation on procedures becomes crucial to ensure the system is always modified to follow standard procedures.

Key Management

Question the code

Many of these questions are well beyond the scope of a code review. Still, they can serve as triggers for development teams to build a more comprehensive threat model that considers other components aside from smart contracts.

  • Who controls the privileged account? A single private key? Multiple keys with a multisig? A governance module?
  • Does the privileged account hold any other role in the system? If so, could that introduce any risk worth analyzing?
  • How are the private keys stored? Are there backups? Who’s got access to them? Are access operations logged, monitored and audited?
  • How are the private keys generated? Do they sign transactions in standard ways or is the system using some custom implementation?

Secure All Administrative Keys

All private keys that are used by individuals to sign administrative transactions to smart contracts should be stored in hardware wallets that are offline except when used to sign transactions, with the mnemonic for account recovery stored on paper also offline. For further protection, never share the private keys across devices and never use the same internet-connected device to sign transactions with multiple administrative keys. In other words, try to maintain a “one internet-connect device one administrative key” policy at all times.

import KMS from 'aws-sdk/clients/kms';

public async sign(kms: KMS, keyIdOrAlias: string, payload: Buffer): Promise<Buffer> {
const params: KMS.SignRequest = {
KeyId: keyIdOrAlias,
SigningAlgorithm: 'ECDSA_SHA_256',
Message: payload,
MessageType: 'DIGEST',

const response = await this.kms.sign(params).promise();
if (Buffer.isBuffer(response.Signature)) {
return response.Signature;
throw new Error(`Error using key ${keyIdOrAlias} signing: ${payload.toString()}`);

Use multiple signatures for critical administrative tasks

Critical administrative tasks should not be controlled by a single signer. Requiring multiple signatures to execute these sensitive tasks reduces the impact of private keys being lost or compromised.

  • Assign different roles for different types of tasks. OpenZeppelin Contracts provides the Roles library for implementing role-based access control.
  • Define a hierarchy of permission levels linked to the roles.
  • Require a higher number of signatures for roles higher in the hierarchy.
  • Use independent multisignatures for every role.
  • Give ownership of every private key to a different individual.
  • Train every key-holder on the secure handling of their devices and communications. The Electronic Frontier Foundation published good resources for safe online communications.
  • Distribute the keys to individuals that guarantee diversity of geographic locations, hardware manufacturers, and software applications.
  • Do not use a 1 of N multisignature because losing control of a single private key means the system is immediately compromised.
  • Do not use an N of N multisignature because it does not provide any redundancy in case some of the private keys are lost.
  • Consider using an M of N multisignature, with M = (N/2) + 1 to balance concerns. A lower M allows for quicker response. A higher M requires a stronger majority support.
  • For configuration tasks, like changing parameters, a 2 of 3 multisignature might be enough. Consider using other kinds of fail-safes, like allowing the parameters to vary only by a small delta and with some time window before they are applied, so the functioning of system does not completely change with a single transaction. Consider separating the tasks that are adjustments from the ones that disable or enable functionality. For example, do not allow a parameter to be adjusted to 0 if it means a functionality will stop working; define a separate function with a separate role to disable it.
  • For more critical and less common tasks, like upgrading the implementation of a contract, a 3 of 5 multisignature might be better. If the tasks are time-sensitive, the key-holders must agree to be on-call to react immediately after being notified.
  • For tasks that require representation from different stakeholders, consider giving ownership of private keys to two representatives of each stakeholder category.
  • Document the roles, corresponding tasks, and key-holder contacts in a private repository.
  • Document the threat model for every role and task.
  • Automate the monitoring, administrative triggers, and notification of key-holders.
  • Test and run simulations of all the situations that will require multiple signatures.


The following are just a few examples of events to monitor for, specifically related to (privileged) accounts activity.

Privileged Administration Transactions

If approved administrators did not trigger the related transaction / set of transactions, private keys controlling the privileged account may have been compromised. The entire system, including users’ funds, might be at risk. Also, mistakes in executing administrative transactions could lead to unintended and potentially widespread effects. Instituting a post-transaction audit and review process for administrative changes is considered a security best practice.

  • Changes in sensitive parameters of core contracts
  • Calls to functions which add, renounce, or transfer ownership
  • Withdrawals of central funds
  • Calls to function which allow or block access to assets or capabilities for specific accounts
  • Calls using privileged accounts not related to expected administrator operations, detected via inspection of mined transactions

Spikes in Account Activity

Extremely frequent use of the protocol by a single account may be the reflection of malicious operations on the protocol, either attempting to exploit a zero-day vulnerability or spam transactions in the network. This takes particular relevance for accounts that never interacted with the protocol before.

Drop in System Funds

When funds stored in contracts drop significantly, it may be an indicator of either users exiting the system or a vulnerability being exploited to steal funds at large. Setup monitoring to detect funds dropping below specified thresholds, and notify administrators or stakeholders when such drops occur.




Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store


Oxford-based blockchain and zero knowledge consultancy and auditing firm