Skip to content

Commit 3b0f256

Browse files
committed
Port EspressoSystems/timeboost#482 into timeboost-contracts
1 parent d367ab3 commit 3b0f256

File tree

6 files changed

+376
-1
lines changed

6 files changed

+376
-1
lines changed

README.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Timeboost Smart Contracts
2+
3+
This directory contains the smart contracts that power the decentralized Timeboost protocol. These contracts run on Ethereum-compatible blockchains and provide the foundation for secure, decentralized time synchronization.
4+
5+
## Background
6+
Smart contracts are executable code, deployed on blockchains that
7+
can be read from / written to anyone with an internet connection.
8+
Transaction data and smart contract storage is public and can be
9+
accessed in blockchain explorers. In decentralized Timeboost,
10+
smart contracts are used to allow anyone to interact with various
11+
of the protocol. This readme is directed at developers who are
12+
contributing to or making use of this decentralized timeboost
13+
implementation.
14+
15+
## What Are These Contracts For?
16+
17+
Timeboost needs a way to coordinate cryptographic keys and committee members across a decentralized network. These smart contracts provide:
18+
19+
- **Key Management**: Store and manage public keys for the protocol
20+
- **Committee Coordination**: Track which nodes are part of the consensus committee
21+
- **Access Control**: Ensure only authorized parties can update critical protocol parameters
22+
- **Transparency**: All changes are recorded on-chain for public verification
23+
24+
These contracts act as the "coordination layer" that allows the Timeboost network to operate without a central authority.
25+
26+
## Handling Upgradeability
27+
28+
### The Upgrade Problem
29+
Once deployed, smart contracts can't be changed. To solve this, we use a proxy solution that performs functionally as upgradeable contracts.
30+
31+
### The Proxy Solution
32+
We use a "proxy pattern" that works like this:
33+
34+
1. **Users always interact with the proxy** - This address never changes
35+
2. **The proxy points to an implementation** - This can be updated
36+
3. **When you upgrade** - Just point the proxy to a new implementation
37+
4. **All data stays safe** - Storage is preserved across upgrades
38+
39+
Think of it like changing the engine in a car - the car (proxy) stays the same, but you can swap out the engine (implementation) for a better one.
40+
41+
### Our Architecture
42+
```
43+
User → Proxy Contract → Implementation Contract
44+
45+
Storage (persistent)
46+
```
47+
48+
## The Contracts
49+
50+
### KeyManager - The Main Contract
51+
52+
**What it stores:**
53+
- **Encryption keys** - The cryptographic keys used by the protocol
54+
- **Committee members** - Who's currently in the consensus committee
55+
- **Manager address** - Who can update the contract
56+
57+
**What it does:**
58+
- **Sets committees** - Updates which nodes are part of the network
59+
- **Manages keys** - Stores the threshold encryption key
60+
- **Controls access** - Only the manager can make changes
61+
- **Logs everything** - All changes are recorded as events
62+
63+
**Key functions:**
64+
- `setNextCommittee()` - Add a new committee with future members
65+
- `currentCommitteeId()` - Find which committee is active right now
66+
- `getCommitteeById()` - Get details about a specific committee
67+
- `setThresholdEncryptionKey()` - Set the encryption key for the protocol
68+
69+
### ERC1967Proxy - The Upgrade Mechanism
70+
This is the "shell" that makes upgrades possible:
71+
72+
- **Never changes** - Users always interact with this address
73+
- **Delegates calls** - Forwards requests to the current implementation
74+
- **Preserves data** - All storage survives upgrades
75+
- **SecOps Precautions** - it's important to call the initialize methods during deploys and upgrades so that those transactions aren't front-run
76+
77+
## Getting Started
78+
79+
### Prerequisites
80+
- **Foundry** - For building and testing contracts
81+
82+
### Building the Contracts
83+
```bash
84+
# Build all contracts
85+
just build-contracts
86+
87+
# Or use forge directly
88+
forge build
89+
```
90+
91+
### Testing
92+
```bash
93+
# Run all tests
94+
just test-contracts
95+
96+
# Run with detailed output
97+
forge test -vvv
98+
99+
# Run specific test
100+
forge test --match-test test_setThresholdEncryptionKey
101+
```
102+
103+
### Integration Testing
104+
For testing contract interactions from Rust code, see the [timeboost-contract README](../timeboost-contract/README.md).
105+
106+
## Deployment
107+
You can deploy a local anvil network (as done in the test), a fork of a real network, a testnet network (e.g. Ethereum Sepolia) or a mainnet (e.g. Ethereum Mainnet).
108+
109+
### Quick Start (Local Testing)
110+
```bash
111+
# 1. Start a local blockchain
112+
anvil
113+
114+
# 2. Deploy the contracts
115+
cp env.example .env
116+
# Edit .env with your values
117+
./script/deploy.sh
118+
```
119+
120+
**📋 For detailed deployment instructions, see the [deployment script README](script/README.md)**
121+
122+
## Security
123+
124+
### Current Status
125+
- **ERC1967Proxy** - Audited by OpenZeppelin, widely used
126+
- **KeyManager** - Not yet audited (audit planned)
127+
128+
### Security Considerations
129+
- **Manager privileges** - The manager can update committees and keys
130+
- **Upgrade authority** - Only the contract owner can upgrade
131+
- **Committee validation** - Contracts validate committee transitions
132+
- **Event logging** - All changes are logged onchain for transparency
133+
134+
### Best Practices
135+
- **Use multisig wallets** - Don't use single-key wallets for manager/owner
136+
- **Test thoroughly** - Always test on testnets first
137+
- **Monitor events** - Watch for unexpected contract changes
138+
- **Keep keys secure** - Store private keys and mnemonics safely
139+
140+
## Getting Help
141+
- Check the [deployment script README](script/README.md) for deployment issues
142+
- Review the [timeboost-contract README](../timeboost-contract/README.md) for integration questions

env.example

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Timeboost Contract Deployment Configuration
2+
# Copy this file to .env and fill in your actual values
3+
# DO NOT commit .env files to version control
4+
5+
# Manager address for the KeyManager contract
6+
# This should be the address that will manage the KeyManager contract
7+
# Typically a multisig wallet or governance contract address
8+
# Example: MANAGER_ADDRESS=0x1234567890123456789012345678901234567890
9+
MANAGER_ADDRESS=
10+
11+
# RPC endpoint for the target network
12+
# For local development: http://localhost:8545 (Anvil)
13+
# For testnets: https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
14+
# For mainnet: https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY
15+
RPC_URL=
16+
17+
# Deployment Account Mnemonic and account index
18+
# Your 12 or 24 word BIP39 mnemonic phrase for the deployment account
19+
# This account must have enough ETH to pay for gas fees
20+
# Example: MNEMONIC="abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
21+
MNEMONIC=
22+
23+
# Which account to use from the mnemonic (0 = first account, 1 = second, etc.)
24+
# Example: ACCOUNT_INDEX=0
25+
ACCOUNT_INDEX=
26+
27+
# Etherscan API key for contract verification (optional)
28+
# Get one at: https://etherscan.io/apis
29+
# This is used to verify your contracts on Etherscan after deployment
30+
# Example: ETHERSCAN_API_KEY=YourApiKeyToken
31+
ETHERSCAN_API_KEY=

script/DeployKeyManager.s.sol

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.13;
3+
4+
import {Script, console} from "forge-std/Script.sol";
5+
import {KeyManager} from "../src/KeyManager.sol";
6+
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
7+
8+
/// @title DeployKeyManager
9+
/// @notice Simple script to deploy KeyManager implementation and proxy, then initialize
10+
contract DeployKeyManager is Script {
11+
function run() external returns (address, address) {
12+
// Get the manager address from environment variable or use sender as fallback
13+
address manager = vm.envOr("MANAGER_ADDRESS", msg.sender);
14+
15+
vm.startBroadcast();
16+
17+
// Deploy the KeyManager implementation
18+
KeyManager implementation = new KeyManager();
19+
console.log("KeyManager implementation deployed at:", address(implementation));
20+
21+
// Prepare initialization data
22+
bytes memory initData = abi.encodeWithSelector(KeyManager.initialize.selector, manager);
23+
24+
// Deploy the proxy and initialize it
25+
ERC1967Proxy proxy = new ERC1967Proxy(address(implementation), initData);
26+
console.log("ERC1967Proxy deployed at:", address(proxy));
27+
28+
// Verify initialization
29+
KeyManager proxyContract = KeyManager(address(proxy));
30+
require(proxyContract.manager() == manager, "Manager not set correctly");
31+
32+
console.log("Deployment successful!");
33+
console.log("Implementation:", address(implementation));
34+
console.log("Proxy:", address(proxy));
35+
console.log("Manager:", manager);
36+
37+
vm.stopBroadcast();
38+
39+
return (address(proxy), address(implementation));
40+
}
41+
}

script/README.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Deploy KeyManager Contract
2+
3+
This script deploys the KeyManager contract to any Ethereum network.
4+
5+
## Quick Start
6+
7+
1. **Set up your environment:**
8+
```bash
9+
cp env.example .env
10+
# Edit .env with your values
11+
```
12+
13+
2. **Start a local blockchain (if testing locally):**
14+
```bash
15+
anvil
16+
```
17+
18+
3. **Deploy:**
19+
```bash
20+
./script/deploy.sh
21+
```
22+
23+
That's it! The script will deploy your contract and show you the addresses.
24+
25+
## What You Need
26+
27+
- **Manager Address**: Who will manage the contract (usually a multisig wallet)
28+
- **RPC URL**: Where to deploy (localhost for testing, or a real network)
29+
- **Mnemonic**: Your wallet phrase (optional for local testing)
30+
31+
## How It Works
32+
33+
The script deploys two contracts:
34+
1. **KeyManager** - The actual contract with your business logic
35+
2. **Proxy** - Points to the KeyManager, so you can upgrade it later
36+
37+
The proxy gets initialized with your manager address, and that's it!
38+
39+
## Configuration
40+
41+
All settings go in your `.env` file:
42+
43+
```bash
44+
# Required: Who manages the contract
45+
MANAGER_ADDRESS=0x1234567890123456789012345678901234567890
46+
47+
# Optional: Where to deploy (defaults to localhost)
48+
RPC_URL=http://localhost:8545
49+
50+
# Optional: Your wallet phrase (defaults to Anvil's test account)
51+
MNEMONIC="your twelve word mnemonic phrase here"
52+
ACCOUNT_INDEX=0
53+
```
54+
55+
## How it works
56+
The deployment script:
57+
1. **Deploys** the KeyManager implementation contract
58+
2. **Creates** an ERC1967 proxy pointing to the implementation
59+
3. **Initializes** the proxy with your manager address
60+
4. **Verifies** the deployment was successful
61+
5. **Returns** both proxy and implementation addresses
62+
63+
64+
## Troubleshooting
65+
66+
**"Manager address not set"**
67+
- Make sure you have `MANAGER_ADDRESS=0x...` in your `.env` file
68+
69+
**"Deployment failed"**
70+
- Check your RPC URL works
71+
- Make sure your wallet has enough ETH for gas
72+
- Verify your mnemonic is correct
73+
74+
**Need help?**
75+
- Make sure Foundry is installed: `forge --version`
76+
- Check your `.env` file has all required values

script/deploy.sh

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/bin/bash
2+
3+
# Simple deployment script for KeyManager contract
4+
5+
set -e
6+
7+
# Load environment variables if .env file exists
8+
if [ -f ".env" ]; then
9+
echo "Loading environment from .env file..."
10+
# Use source to properly handle quoted values
11+
set -a
12+
source .env
13+
set +a
14+
fi
15+
16+
# Check if required environment variables are set
17+
if [ -z "$MANAGER_ADDRESS" ]; then
18+
echo "Error: MANAGER_ADDRESS is required"
19+
echo "Please set MANAGER_ADDRESS in your .env file"
20+
echo ""
21+
echo "Example .env file:"
22+
echo "MANAGER_ADDRESS=0x1234567890123456789012345678901234567890"
23+
echo "RPC_URL=http://localhost:8545"
24+
echo "MNEMONIC=\"your twelve word mnemonic phrase here\""
25+
echo "ACCOUNT_INDEX=0"
26+
exit 1
27+
fi
28+
29+
# Use environment variables with defaults
30+
RPC_URL=${RPC_URL:-"http://localhost:8545"}
31+
ACCOUNT_INDEX=${ACCOUNT_INDEX:-0}
32+
MNEMONIC=${MNEMONIC:-"test test test test test test test test test test test junk"}
33+
34+
echo "Deploying KeyManager contract..."
35+
echo "Manager address: $MANAGER_ADDRESS"
36+
echo "RPC URL: $RPC_URL"
37+
echo "Account index: $ACCOUNT_INDEX"
38+
echo
39+
40+
# Set environment variable for the script
41+
export MANAGER_ADDRESS=$MANAGER_ADDRESS
42+
43+
# Build forge command
44+
FORGE_CMD="forge script script/DeployKeyManager.s.sol:DeployKeyManager --rpc-url $RPC_URL --broadcast --mnemonics \"$MNEMONIC\" --mnemonic-indexes $ACCOUNT_INDEX"
45+
46+
47+
echo "Executing Forge command"
48+
echo
49+
50+
# Deploy the contract and capture output
51+
DEPLOYMENT_OUTPUT=$(eval $FORGE_CMD 2>&1)
52+
DEPLOYMENT_EXIT_CODE=$?
53+
54+
if [ $? -eq 124 ]; then
55+
echo "Forge command timed out - likely invalid flags or connection issue"
56+
exit 1
57+
fi
58+
59+
echo "Forge command completed with exit code: $DEPLOYMENT_EXIT_CODE"
60+
61+
# Display the deployment output
62+
echo "$DEPLOYMENT_OUTPUT"
63+
64+
# Check if deployment was successful
65+
if [ $DEPLOYMENT_EXIT_CODE -eq 0 ]; then
66+
echo
67+
echo "Deployment complete!"
68+
echo "Check the output above for contract addresses."
69+
70+
# Try to extract addresses from the output (optional enhancement)
71+
echo
72+
echo "Extracted contract addresses:"
73+
echo "$DEPLOYMENT_OUTPUT" | grep -E "(KeyManager implementation deployed at:|ERC1967Proxy deployed at:)" || echo "Could not extract addresses from output"
74+
else
75+
echo
76+
echo "Deployment failed with exit code $DEPLOYMENT_EXIT_CODE"
77+
exit $DEPLOYMENT_EXIT_CODE
78+
fi

src/KeyManager.sol

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Own
55
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
66
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
77

8+
/// @title KeyManager
9+
/// @notice The KeyManager contract is responsible for managing the keys for the Timeboost protocol.
10+
/// @notice It is used to set the threshold encryption key, create committees, and prune old committees.
11+
/// @notice It is also used to get the current committee id and the committee by id.
12+
/// @notice Only the admin can set the manager.
13+
/// @notice The manager can set the threshold encryption key, create committees, and prune old committees.
14+
/// @notice The contract is upgradeable and can be upgraded by the admin.
815
contract KeyManager is Initializable, OwnableUpgradeable, UUPSUpgradeable {
916
struct CommitteeMember {
1017
/// @notice public key for consensus votes, also used as the primary label for a node
@@ -106,7 +113,7 @@ contract KeyManager is Initializable, OwnableUpgradeable, UUPSUpgradeable {
106113
* @notice This function is used to initialize the contract.
107114
* @dev Reverts if the manager is the zero address.
108115
* @dev Assumes that the manager is valid.
109-
* @dev This must be called once when the contract is first deployed.
116+
* @dev This must be called once when the contract is first deployed, in the same transaction as the proxy deployment.
110117
* @param initialManager The initial manager.
111118
*/
112119
function initialize(address initialManager) external initializer {

0 commit comments

Comments
 (0)