Access Control
The StoffelAccessControl contract provides role-based access control for MPC parties, ensuring only authorized addresses can participate in the computation.
Overview
abstract contract StoffelAccessControl is AccessControl {
bytes32 public constant PARTY_ROLE = keccak256("PARTY_ROLE");
bytes32 public constant DESIGNATED_PARTY_ROLE = keccak256("DESIGNATED_PARTY_ROLE");
}
Inheritance: OpenZeppelin's AccessControl
Roles
PARTY_ROLE
Assigned to MPC compute nodes (servers). Parties can:
- Participate in MPC protocol execution
- Submit computation results
- Access party-restricted functions
DESIGNATED_PARTY_ROLE
Elevated role for orchestration. The designated party can:
- Trigger round transitions
- Initialize input mask buffers
- Coordinate preprocessing and output phases
- All permissions of PARTY_ROLE
Storage
// Number of parties (n)
uint256 public nParties;
// Fault tolerance threshold (t)
uint256 public threshold;
Constructor
constructor(
uint256 n,
uint256 t,
address designatedParty,
address[] memory initialMPCNodes
) {
require(n >= 3 * t + 1, "Invalid n/t configuration");
require(initialMPCNodes.length <= n, "Too many initial nodes");
nParties = n;
threshold = t;
// Grant designated party role
_grantRole(DESIGNATED_PARTY_ROLE, designatedParty);
_grantRole(PARTY_ROLE, designatedParty);
// Grant party role to all MPC nodes
for (uint i = 0; i < initialMPCNodes.length; i++) {
_grantRole(PARTY_ROLE, initialMPCNodes[i]);
}
}
Modifiers
onlyParty
Restricts function to addresses with PARTY_ROLE.
modifier onlyParty() {
require(hasRole(PARTY_ROLE, msg.sender), "Caller is not a party");
_;
}
// Usage
function submitShare(bytes calldata share) external onlyParty {
// Only MPC nodes can submit shares
}
onlyDesignatedParty
Restricts function to the designated party.
modifier onlyDesignatedParty() {
require(hasRole(DESIGNATED_PARTY_ROLE, msg.sender), "Caller is not designated party");
_;
}
// Usage
function startPreprocessing() external onlyDesignatedParty {
// Only designated party can start preprocessing
}
Party Management
Adding Parties
function addParty(address party) external onlyDesignatedParty {
require(!hasRole(PARTY_ROLE, party), "Already a party");
require(getPartyCount() < nParties, "Max parties reached");
_grantRole(PARTY_ROLE, party);
}
Removing Parties
function removeParty(address party) external onlyDesignatedParty {
require(hasRole(PARTY_ROLE, party), "Not a party");
require(getPartyCount() > threshold + 1, "Cannot go below threshold");
_revokeRole(PARTY_ROLE, party);
}
Querying Party Status
function isParty(address account) public view returns (bool) {
return hasRole(PARTY_ROLE, account);
}
function isDesignatedParty(address account) public view returns (bool) {
return hasRole(DESIGNATED_PARTY_ROLE, account);
}
function getPartyCount() public view returns (uint256) {
return getRoleMemberCount(PARTY_ROLE);
}
Constraints
n >= 3t + 1
The HoneyBadger protocol requires n >= 3t + 1:
function validateConfiguration(uint256 n, uint256 t) internal pure {
require(n >= 3 * t + 1, "n must be >= 3t + 1 for Byzantine fault tolerance");
}
Minimum Party Threshold
Parties cannot be removed if it would violate the threshold:
function canRemoveParty() public view returns (bool) {
return getPartyCount() > threshold + 1;
}
Designated Party Transfer
function transferDesignatedParty(address newDesignatedParty) external onlyDesignatedParty {
require(newDesignatedParty != address(0), "Invalid address");
_revokeRole(DESIGNATED_PARTY_ROLE, msg.sender);
_grantRole(DESIGNATED_PARTY_ROLE, newDesignatedParty);
// New designated party also gets PARTY_ROLE
if (!hasRole(PARTY_ROLE, newDesignatedParty)) {
_grantRole(PARTY_ROLE, newDesignatedParty);
}
}
Events
// Inherited from OpenZeppelin AccessControl
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
Example Usage
Deploy with Initial Parties
address[] memory mpcNodes = new address[](5);
mpcNodes[0] = 0x1111...;
mpcNodes[1] = 0x2222...;
mpcNodes[2] = 0x3333...;
mpcNodes[3] = 0x4444...;
mpcNodes[4] = 0x5555...;
MyCoordinator coordinator = new MyCoordinator(
programHash,
5, // n = 5 parties
1, // t = 1 (tolerates 1 faulty)
designatedPartyAddr,
mpcNodes
);
Check Permissions
// Check if address is a party
bool canCompute = coordinator.isParty(someAddress);
// Check if designated party
bool canOrchestrate = coordinator.isDesignatedParty(someAddress);
// Get current party count
uint256 activeParties = coordinator.getPartyCount();
Dynamic Party Management
// Add a new MPC node (designated party only)
coordinator.addParty(newNodeAddress);
// Remove an MPC node (must maintain threshold)
if (coordinator.canRemoveParty()) {
coordinator.removeParty(oldNodeAddress);
}
// Transfer designated party role
coordinator.transferDesignatedParty(newDesignatedPartyAddress);
Security Considerations
- Threshold maintenance: Never allow party count to drop below
t + 1 - Designated party security: Protect the designated party key
- Role separation: Use separate addresses for different roles when possible
- Multi-sig: Consider using a multi-sig for designated party role in production
Next Steps
- StoffelCoordinator: State machine details
- Input Manager: Client input handling
- Overview: Architecture overview