Documentation Index
Fetch the complete documentation index at: https://docs.stoffelmpc.com/llms.txt
Use this file to discover all available pages before exploring further.
Input Manager
The StoffelInputManager contract handles client input submission for MPC computations, including mask reservation and ECDSA authentication.
Overview
abstract contract StoffelInputManager {
// Manages client inputs with privacy-preserving masking
}
Clients don’t submit raw inputs on-chain (that would reveal them). Instead:
- MPC nodes generate input masks during preprocessing
- Clients reserve mask indices on-chain
- Clients submit masked inputs:
masked = input + mask
- MPC nodes unmask during computation using their mask shares
Client Input: 42
Mask: 17
Masked Input: 59 ← This goes on-chain (reveals nothing about 42)
Data Structures
struct MaskedInput {
uint256 index; // Reserved mask index
uint256 maskedInput; // Input XOR/+ mask
}
struct Inputs {
bytes publicInputs; // Optional public parameters
MaskedInput[] maskedInputs; // Array of masked secret inputs
}
Outputs
struct Outputs {
bytes publicOutputs; // Computation results
mapping(address => mapping(address => bool)) sharesReceived;
// Tracks: client => party => received
}
Storage
// Maps index to reserving client
mapping(uint256 => address) public reservedInputIndices;
// Total indices available
uint256 public nTotalIndices;
// Remaining unreserved indices
uint256 public nIndicesLeft;
// Client inputs storage
mapping(address => MaskedInput) public clientInputs;
Functions
Sets up the input mask buffer. Called by designated party during preprocessing.
function initializeInputMaskBuffer(uint256 nIndicesToReserve)
external
onlyDesignatedParty
{
nTotalIndices = nIndicesToReserve;
nIndicesLeft = nIndicesToReserve;
}
Clients call this to reserve an input mask index.
function reserveInputMask(uint256 indexToReserve) external {
require(indexToReserve < nTotalIndices, "Index out of bounds");
require(reservedInputIndices[indexToReserve] == address(0), "Index already reserved");
require(nIndicesLeft > 0, "No indices left");
reservedInputIndices[indexToReserve] = msg.sender;
nIndicesLeft--;
emit InputMaskReserved(msg.sender, indexToReserve);
}
Clients submit their masked input using a reserved index.
function submitMaskedInput(uint256 maskedInput, uint256 reservedIndex) external {
require(reservedInputIndices[reservedIndex] == msg.sender, "Not your reserved index");
clientInputs[msg.sender] = MaskedInput({
index: reservedIndex,
maskedInput: maskedInput
});
// Unreserve the index (one-time use)
reservedInputIndices[reservedIndex] = address(0);
emit MaskedInputSubmitted(msg.sender, reservedIndex);
}
authenticateClient
MPC nodes use this for off-chain client authentication via ECDSA.
function authenticateClient(
uint256 requestIndex,
address clientAddr,
bytes calldata signature
) external view returns (bool) {
// Construct the message hash
bytes32 messageHash = keccak256(abi.encode(requestIndex));
bytes32 ethSignedHash = keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash)
);
// Recover signer from signature
address recovered = recoverSigner(ethSignedHash, signature);
return recovered == clientAddr;
}
Retrieve a client’s submitted input.
function getClientInput(address client)
external
view
returns (MaskedInput memory)
{
return clientInputs[client];
}
hasClientSubmitted
Check if a client has submitted their input.
function hasClientSubmitted(address client) external view returns (bool) {
return clientInputs[client].maskedInput != 0 ||
clientInputs[client].index != 0;
}
Events
event InputMaskReserved(address indexed client, uint256 indexed index);
event MaskedInputSubmitted(address indexed client, uint256 indexed index);
event ClientAuthenticated(address indexed client, uint256 indexed requestIndex);
Client Workflow
1. Reserve a Mask Index
// Client reserves index 5
await coordinator.reserveInputMask(5);
2. Get the Mask (Off-Chain)
// Client contacts MPC nodes to get their mask
// This happens off-chain via the Rust SDK
const mask = await mpcClient.getInputMask(5);
// Client masks their secret input
const secretInput = 42n;
const maskedInput = secretInput + mask; // or XOR depending on protocol
// Client submits on-chain
await coordinator.submitMaskedInput(maskedInput, 5);
5. MPC Nodes Unmask
During computation, MPC nodes:
- Read
maskedInput from contract
- Subtract their mask share
- Proceed with MPC on the unmasked value
Authentication Flow
For off-chain operations, clients prove their identity:
// Client signs a request
const requestIndex = 12345;
const messageHash = ethers.utils.keccak256(
ethers.utils.defaultAbiCoder.encode(['uint256'], [requestIndex])
);
const signature = await wallet.signMessage(ethers.utils.arrayify(messageHash));
// MPC node verifies on-chain
const isValid = await coordinator.authenticateClient(
requestIndex,
clientAddress,
signature
);
contract SecureVoting is StoffelCoordinator {
mapping(address => bool) public hasVoted;
function vote(uint256 maskedVote, uint256 maskIndex) external {
require(!hasVoted[msg.sender], "Already voted");
require(currentRound == Round.CollectingClientInputRound, "Not collecting");
// Verify client reserved this index
require(reservedInputIndices[maskIndex] == msg.sender, "Wrong index");
// Submit the masked vote
clientInputs[msg.sender] = MaskedInput({
index: maskIndex,
maskedInput: maskedVote
});
hasVoted[msg.sender] = true;
reservedInputIndices[maskIndex] = address(0);
emit MaskedInputSubmitted(msg.sender, maskIndex);
}
}
Security Considerations
Index Reservation
- Each index can only be reserved once
- Prevents double-spending of masks
- Clients should reserve early to ensure availability
Mask Uniqueness
- Each mask is used exactly once
- After submission, the index is unreserved
- Prevents mask reuse attacks
Authentication
- ECDSA signatures verify client identity
- Prevents impersonation in off-chain communications
- Message includes unique
requestIndex to prevent replay
- Only masked values appear on-chain
- Raw inputs never touch the blockchain
- Privacy depends on MPC node security (threshold trust)
Next Steps