Skip to main content

Module 2: Cryptography and Smart Contracts

Introduction

In blockchain development, cryptography and smart contracts are foundational. They provide the tools for secure communication, data verification, and decentralized computation. On the Midnight Network, these foundations are enhanced to enable privacy-preserving, compliance-aware applications. This module explores the principles behind cryptography and smart contracts and shows how they apply within Midnight’s unique design, preparing developers to write secure, expressive logic using the Compact language.

Learning Objectives

By the end of this module, you will be able to:

  • Explain the role of cryptographic primitives (hashing, public-key cryptography, and digital signatures) in Midnight.

  • Understand the basic structure and lifecycle of a smart contract written in Compact.

  • Describe state management, function logic, event signaling, and access control in smart contracts.

  • Identify common vulnerabilities and follow best practices for secure development in privacy-preserving environments.

Cryptographic Foundations

Cryptography provides the security bedrock of any blockchain. Three fundamental cryptographic concepts are especially important: hashing, public-key cryptography, and digital signatures. Let's break down each in simple terms and see how Midnight leverages them.

Hashing Basics in Blockchain

A hash function takes any input data and produces a fixed-size string of bytes (often represented as a hexadecimal string). A key property is that hashing is one-way: it's easy to compute a hash from input data, but computationally hard to reverse. However, this only holds if the input is sufficiently unpredictable—if the input is simple or guessable (like a name or birthdate), an attacker could potentially brute-force all possibilities and match the hash. To defend against this, hashes are often combined with randomness (sometimes called a salt), such as Hash(data | randomness), making brute-force much harder.

Even a tiny change in the input (like changing one letter in a file) produces a completely different hash. In blockchains, hashing ensures data integrity: each block contains a hash of the previous block’s data. If an attacker tries to tamper with a past transaction, its hash changes, breaking the chain and alerting the network. This makes the blockchain effectively immutable—data, once recorded, cannot be altered without detection. Hashing also plays roles in address generation, proof-of-work puzzles, and more, but at its core, it provides a fingerprint of data that ensures it hasn’t been tampered with.

On Midnight, hashing might protect sensitive metadata. For example, a document’s fingerprint (hash) can be stored on-chain to prove that the document existed at a certain time—without revealing the document itself. To prevent guessing the content from the hash, especially for known formats or names, a salt can be added to the input before hashing.

Public-Key Cryptography (PKC)

Public-key cryptography underpins identity, security, and privacy in blockchain networks. It uses a pair of keys: a private key (kept secret) and a public key (shared openly). The two are mathematically linked. In systems like Midnight, your public key (or an address derived from it) is like your account identifier, and your private key is your secret credential to authorize actions and decrypt private data.

Authentication and Integrity

When you send a transaction (e.g., transferring tokens), you sign it with your private key. Network nodes use your public key to verify the signature. If it checks out, the network knows the transaction came from you and wasn’t tampered with. This ensures that only the legitimate key holder can initiate transactions—possession of the private key = control of the account.

Encryption and Confidentiality

Public-key cryptography also enables encryption. In Midnight, certain sensitive data—such as the recipient’s identity or transaction details—can be encrypted using the recipient’s public key. Only the recipient, with their private key, can decrypt this data and see the full details. This means even though the transaction is recorded on a public ledger, its contents remain confidential to everyone but the intended parties.

Midnight, being privacy-focused, may integrate advanced cryptographic tools (like zero-knowledge proofs) on top of this system, but the foundation remains the same: public-key cryptography ensures authentication, integrity, and confidentiality.

Digital Signatures in Midnight Network

A digital signature is a cryptographic mechanism to prove a message or document was created and sent by a specific person (or their private key), and that it wasn’t tampered with. In blockchain, when you sign a transaction with your private key, you produce a digital signature attached to that transaction. Every node in the Midnight Network will verify that signature using your public key.

Digital signatures provide authentication, integrity, and non-repudiation for transactions:

  • Authentication: Nodes know the transaction came from the owner of a certain public key (identity is verified).

  • Integrity: If the transaction data were altered after signing, the signature would be invalid. Thus, data can’t be changed unnoticed.

  • Non-repudiation: Once you’ve signed a transaction, you cannot later claim you didn’t send it – only your private key could have produced that signature.

In blockchains, digital signatures are essential. They’re a fundamental building block used mainly to authenticate transactions. For example, on Midnight, if Alice wants to send 5 tokens to Bob, she signs the transaction with her private key. The network validates the signature to confirm Alice is authorized to spend those 5 tokens, and then the transaction is executed. Without digital signatures, anyone could forge transactions, and the network would have no trustable way to verify ownership or permission.

Quiz

Loading...

Smart Contract Basics

Smart contracts are programs that run on the blockchain. They define rules and behaviors for how participants can interact, much like a regular program, but their code is stored and executed decentrally by the blockchain network. In Midnight Network, smart contracts not only enable general logic but also incorporate data protection features to handle sensitive information in a compliant way.

Let's explore the basic structure and components of smart contracts, see a simple example, and then try a hands-on exercise.

Structure and Components of a Smart Contract

A typical smart contract (using a language like Solidity for illustration) contains several key components:

  • State Variables: These are like a contract’s permanent storage. They hold values (numbers, addresses, etc.) that persist on the blockchain. For example, a contract might have a state variable tracking an account balance or an owner’s address. State variables are stored in the blockchain's state and are shared by all nodes.

  • Functions: Functions define the behavior of the contract. They are callable routines that can read or modify state variables and perform computations. Some functions are publicly accessible (anyone can call them, if allowed), while others might be restricted or for internal use. For instance, a transfer() function in a token contract might move tokens from one account to another by adjusting state variables.

  • Events: Events are logging mechanisms. A contract can emit events to record that something happened, which is then stored in the blockchain’s transaction log. External applications (like a front-end or monitoring tool) can listen for events. For example, an event Transfer might log whenever tokens are sent, recording the from address, to address, and amount. Events don’t directly affect the state, but they are crucial for notifying external systems and for transparency.

  • Modifiers: Function modifiers are pieces of code that can be applied to functions to change their behavior. They often impose requirements or setup checks before a function executes. For example, an onlyOwner modifier can check that the caller of a function is the contract’s owner, enforcing access control. Modifiers help avoid repeating common checks across functions and make the code cleaner. (Don't confuse these with view/pure or payable modifiers in Solidity, which are different—here we mean custom modifiers that you define to wrap functions with additional logic.)

In summary, smart contract structure can include state variables, functions, function modifiers, events, as well as other definitions like structs or enums​. These elements work together: state variables store data, functions contain logic (often altering those variables), events log important actions, and modifiers enforce rules on functions.

Quiz

Loading...

Security Considerations

Writing a smart contract is one thing – writing a secure smart contract is another. Blockchain programs are powerful because they manage real value (tokens, sensitive data, etc.) without intermediaries. But if there’s a bug in a contract, it can be exploited by anyone, and blockchain transactions are irreversible. Security is paramount.

In this section, we'll cover some common smart contract vulnerabilities and how to avoid them. We will also look at a simple vulnerable contract example and then an improved version. Being aware of these pitfalls will help you develop on Midnight (or any blockchain) more safely.

Common Vulnerabilities in Smart Contracts

Reentrancy

This is a classic attack pattern. It occurs when a contract calls an external contract (or another address) and that external call invokes back into the original contract in the middle of execution, often in a loop or multiple times. If the original contract hasn't updated its state yet, the attacker can exploit this to perform actions multiple times that should only happen once. For example, a vulnerable contract might subtract a user’s balance after sending them funds. An attacker can craft a malicious fallback function to call back into the withdrawal function before the balance is reduced, allowing them to withdraw the same funds repeatedly. Reentrancy attacks were famously used to drain funds from The DAO in Ethereum in 2016. In summary, a reentrancy attack lets an external contract "re-enter" a function and repeat actions like withdrawals before the victim contract realizes what's happening.

Integer Overflow/Underflow

This vulnerability arises in languages (like older Solidity versions) where integers have a fixed size (e.g., 256-bit) and don’t automatically check for overflow. If you try to increment a maximum value, it wraps around to zero (overflow); if you decrement below zero, it wraps around to the max value (underflow). Attackers could exploit this to, say, wrap a token balance around from 0 to a huge number. For instance, if uint8 x = 255 (max for 8-bit) and you do x += 1, it becomes 0 without error in older compilers. This could allow manipulating balances or bypassing conditional checks. Essentially, overflow/underflow means a number goes outside the range the program expects, causing unexpected behavior​.

Access Control Issues

Sometimes the vulnerability is simply not properly restricting who can call certain functions. If a contract forgets to set an owner-only requirement where it should, anyone might call an administrative function (like minting tokens or changing critical settings). Likewise, hardcoding an owner address and later transferring ownership without updating it can leave a gap. The result: unauthorized users gain control or perform restricted actions.

Others

There are many more issues that can arise (timestamp dependence, denial of service with expensive loops, front-running attacks where miners exploit transaction ordering, etc.). For beginners, the above are the big ones to grasp first. The key is understanding that blockchain code is public and adversarial – you must assume someone will try to break your contract in every possible way.

Best Practices for Secure Development

Now that we’ve outlined some common issues, how do we prevent them? Here are some best practices:

  • Checks-Effects-Interactions Pattern: This is a recommended pattern to avoid reentrancy. It means when your function needs to (a) check conditions, (b) update state, and (c) interact with other contracts (like sending Ether or calling external functions), you should do it in that order: check inputs and preconditions first, then effect the state changes (update balances, etc.), and only then interact with external calls. By updating the state before calling out, you reduce the risk of reentrancy (the external call can’t hijack the logic because the state is already changed). Also, where possible, use built-in functions like transfer in Solidity which have limited gas and prevent reentrancy by design, or use reentrancy guards (more on that in a moment).

  • Use Established Libraries: Don’t reinvent the wheel for things like math or complex operations. Use well-vetted libraries (like OpenZeppelin’s contracts for tokens, math, access control). For example, OpenZeppelin provides an Ownable contract that sets up an owner and an onlyOwner modifier for you, as well as ReentrancyGuard which provides a simple way to prevent reentrant calls to certain functions.

  • Proper Access Control: Identify which functions in your contract should be restricted (only the owner or authorized addresses can call them). Use modifiers like onlyOwner or more complex role-based access control as needed. Always initialize your ownership or critical roles in the constructor, and be careful with functions that set or change ownership (to avoid accidentally leaving the contract without an owner or setting the wrong address).

  • Validate Inputs and Conditions: Use require and assert statements generously to enforce that the state makes sense. For example, in a voting contract, require(now < votingDeadline) before accepting a vote, or in a token transfer, require(amount <= balances[sender]). These checks not only prevent errors but also discourage malicious inputs.

  • Keep It Simple: Complexity is the enemy of security. Try to write simple and straightforward code. The more complex the logic, the higher the chance of unintended bugs. If your contract is doing something complex (like interacting with multiple other contracts or handling lots of different features), consider splitting it into smaller contracts or modules.

  • Testing and Auditing: Always test your contracts thoroughly. Write unit tests for all functions, covering both expected use and edge cases. If possible, get your code reviewed or audited by others, especially for production contracts. Sometimes a fresh pair of eyes can spot an issue you overlooked.

Quiz

Loading...

Summary

In this module, you explored:

  • The cryptographic principles that power trust and privacy in Midnight.
  • How smart contracts function in Compact, Midnight’s privacy-first language.
  • Why strong access control and secure proof validation are essential.
  • How to think about contract structure and design in a privacy-aware setting.