Photo by Benjamin Davies on Unsplash
Staying Safe as a Blockchain Dev: Safe Smart Contract and dApp Development for Beginners
Table of contents
Introduction
The blockchain ecosystem boasts multibillion-dollar decentralized protocols, applications, and networks, and it’s never been easier to lose it all in an instant. At its core, blockchain development is risky. Databases are risky. Authentication is risky. When I first learned how to program, I was told never to try to implement my own authentication. Blockchains are databases that require authentication via wallets—it’s a perfect marriage of risk.
It’s no longer enough for developers to understand development basics like safely using Git, hiding API keys, and securing a database. Blockchain developers must confront server side and user provided wallets, digital signatures, low-level programming, poorly tested smart contracts, and on-chain code that could be exploited at any moment. I’ll start from the basic development security and work up to topics specific to blockchain development.
Git Smart
Learning how to use Git can feel overwhelming. Git has no shortage of jargon like rebase, stash, merge, branch, commit, push, checkout, etc. When developers start working on an existing codebase, they will start merging, rebasing, and branching a lot more, and they likely don’t have the Git proficiency to understand how easily they can publish secrets.
Don’t commit .env
There’s never been a more efficient way to compromise an entire product’s security at once than by accidentally pushing a .env file to a public or shared repository. There may be multiple environment files (e.g. “.env.staging”, “.env.production”) if a company has a large CI/CD pipeline. These files can hold secrets like an API key, a database connection string, a blockchain node provider URL, a wallet’s private key, and much more. Malicious actors run web scrapers to collect secrets that are accidentally committed to Git, and wallets with leaked private keys will promptly be drained.
Notice that often IDE’s will mark untracked files in a different color (in this case yellow) from tracked files
Developers should ensure that their project includes a .gitignore file if they are using environment files. A .gitignore file indicates to Git which files should intentionally be left untracked. Even if the repository is private and unshared, it is best to never commit environment files to Git. It is possible but difficult to remove secrets that were accidentally committed to Git. GitHub is excellent at preserving history, so developers should use GitHub’s tool for removing secrets rather than trying to force push a committed secret into obscurity. However, it may be impossible to remove a committed secret if any clones, forks or pull request exist that include the history of the committed secret. In general, a secret that has made it to a remote repository should be considered compromised.
Notice that a .gitignore file is usually tracked to ensure other developers don’t commit unnecessary or secret files
Use environment variables properly
When using an environment variable like an API key in a program, the worst thing a developer can do is write the actual secret in the program and try to remember to delete it before committing. There is an excellent chance that the developer will forget to erase the key. Instead, developers should use packages like dotenv to access secrets in their program. When working with dotenv, developers simply need to import dotenv to a particular file and access their secret with “process.env.SECRET_NAME”.
Delete old .env files
Developers may have a mountain of old projects and .env files on their computer to keep “just in case”. These files may be synced to services like iCloud and further increase the chance that someone can gain access a sensitive file. Rather than keeping a treasure chest of secrets, developers should consider creating a “.env.sample” file to keep a template of what a project’s .env file should look like, then delete the project’s actual .env file until the project is brought back into use for demo purposes.
Blockchain Development Security Basics
Use a burner wallet for development
Developers may be tempted to use their daily use wallet for development purposes. However, this is risky for several reasons. First, it is too easy for a wallet to become compromised through regular use. A recent OpenSea phishing attack demonstrated how signing a transaction on a malicious smart contract can grant a hacker permission to drain a wallet’s assets weeks later. Hopefully someday there will be a convenient way that crypto wallets notify users that a smart contract is verified by a particular company. However, until then developers must verify the veracity of contracts themselves through Etherscan and be aware of the links that they access.
The OpenSea phishing email that users like @AJFromDiscord received
The second reason to use a burner wallet is to separate a development from a production environment. If a developer has mainnet Ethereum in a wallet, why risk leaking its private key through an unprotected .env file on a computer or offering permissions to smart contracts that may or may not have a vulnerability that could be exploited in the future? A developer should consider keeping a wallet for local development with Hardhat and test networks, a wallet with a small amount of cryptocurrency for interacting with dApps, and perhaps a wallet or multisignature wallet specifically used for mainnet smart contract deployments.
Multisignature wallets for high value projects
Multisignature wallets provide a good solution for developers working on a high-value project with others. A multisignature wallet allows developers to create a control access scheme for a wallet. Rather than trusting that an individual developer won’t run away with a project’s funds, the wallet requires a certain number of approvals before a transaction is initiated. A developer who needs to deploy a high-value smart contract can deploy the smart contract using a fresh wallet, and then use OpenZeppellin’s Ownable.sol to transfer ownership of that contract to a multisignature wallet.
A comparison of wallet types by Gnosis, the company with the most popular multisignature wallets
Don’t click links
Blockchain developers also tend to be power users of blockchain tech. This poses interesting security risks. Blockchain developers may be part of Discord communities belonging to blockchain infrastructure companies, NFTs, DAOs, DeFi protocols, and dApps. Many of these communities may airdrop tokens, request voting on proposals, or sell NFTs. However, it is so commonplace for individual admins and members for a Discord community to get hacked that it’s best to avoid clicking links unless necessary. Rather than clicking on a link to mint an NFT, why not check the project’s social media channels to find their official website and mint from there? As we saw from the Bored Apes Yacht Club project, no community is too big to be immune from a phishing scam.
A wallet’s private key is the same on mainnet and test networks
When I started working on Solidity projects, I assumed that the private key of a wallet changes once you switch networks. This was a dangerous assumption, because a developer may decide to publish their test wallet’s private key not realizing that their mainnet funds will be stolen just as quickly. In fact, private keys can technically be used to generate an address on a completely different blockchains. An Ethereum wallet’s private key can be used as a Bitcoin private key because they both use Secp256k1 cryptography. See this link for more information about the difference between private keys and seed phrases.
Double check your wallet before making transactions
Hardhat is a powerful development tool for Ethereum smart contract and dApp developers. It allows developers to simulate a blockchain with real wallets (or even fork real blockchains) that they can use for testing applications before migrating to a test network like Görli. When using Hardhat (or another local development environment), many wallets are generated and seeded with ETH to use in the local blockchain.
Though Hardhat notes that these accounts are public, developers import these accounts into their browser wallet for testing purposes and can easily forget the wallet they’re using is a Hardhat account. Below is Account #2 from the generated accounts above. In the account history is a transaction showing someone who sent .5 ETH to the wallet, likely thinking it was their personal wallet.
Solidity
Though Solidity merits many of its own articles on safe development, beginning blockchain developers should at least be aware of some general security concerns.
Solidity Versioning
When starting a new project, developers should use the latest released version of Solidity. It’s important to be aware that different versions of Solidity have different bugs. For example, it’s necessary to use the SafeMath library in Solidity versions before 0.8 because Solidity didn’t provide overflow and underflow checks at the language level, so SafeMath provided this at the library level. A lack of overflow and underflow validation has been used in attacks before.
On-Chain Governance
On-chain governance describes any system that leverages smart contracts to manage an organization, whether it be a protocol, DAO, or dApp. For example, DAO members could be given the opportunity to vote for proposals with tokens, and the proposal is automatically merged into a repository after receiving enough votes. Unfortunately, on-chain governance frameworks are still not resilient enough to counteract perverse incentives in exploiting them. Vitalik has written much about why on-chain governance is so risky. Just last month, the Beanstalk Farms protocol lost $182 million dollars in an on-chain governance attack. Developers should avoid trying to implement on-chain governance until more resilient frameworks are built.
Solidity Attack Vectors
Solidity has important vulnerabilities to protect against when writing smart contracts. It is even more important for this reason for developers to have a proper testing workflow before releasing a smart contract to a production blockchain. Like any other language, developers should at a minimum write unit tests for their code. For example, Hardhat provides a good guide on writing unit tests for smart contracts using ethers.js and Mocha.
Developers who doubt the usefulness of good unit testing should skip to the next section. After unit testing and local testing with a development environment like Hardhat, developers may deploy to a test net. Even after deploying to a test net, developers who plan to deploy a high-value smart contract may decide to pursue smart contract auditing to be sure their contract is ready for production use. There are even insurance policies to mitigate losses from smart contract exploits.
Business Logic vs. Technical Implementation
Like in any programming language, there may be no greater source of flaws in a program than the difference between the required business logic for a program and the developer’s implementation of that logic. For this reason, unit testing is an excellent way for developers to force themselves to understand and prove to themselves that their program handles inputs and outputs in the ways they expect. Like any lesson in the blockchain world, there is a multimillionaire dollar example of what can go wrong. Last month, the Akutars NFT project had $34 million dollars locked in its contract because developers ignored someone who pointed out to them that their contract’s technical implementation of their business logic was flawed.
Thank you for reading my article! If you liked it or it made you more knowledgeable about blockchain development I'd love to know. Feel free to reach out:
Twitter: twitter.com/jahabeebs LinkedIn: linkedin.com/in/jacob-habib Website: jacobhabib.me GitHub: github.com/jahabeebs