Secure Multi-Party Computation (MPC) allows multiple parties to jointly compute a function over their inputs while keeping those inputs private. This guide provides comprehensive MPC implementations in Java.
Core MPC Concepts
- Privacy: No party learns anything about others' inputs
- Correctness: Output is computed correctly
- Security: Protocols remain secure even if some parties are malicious
Dependencies and Setup
Maven Configuration
<!-- pom.xml --> <project> <modelVersion>4.0.0</modelVersion> <groupId>com.mpc</groupId> <artifactId>secure-mpc</artifactId> <version>1.0.0</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <bouncycastle.version>1.75</bouncycastle.version> <netty.version>4.1.100.Final</netty.version> </properties> <dependencies> <!-- Bouncy Castle for cryptographic operations --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk18on</artifactId> <version>${bouncycastle.version}</version> </dependency> <!-- Networking --> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>${netty.version}</version> </dependency> <!-- JSON processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> <!-- Apache Commons for utilities --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.13.0</version> </dependency> <!-- Testing --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.10.0</version> <scope>test</scope> </dependency> </dependencies> </project> Core MPC Protocols
Example 1: Secret Sharing Implementation
package com.mpc.secretsharing; import java.math.BigInteger; import java.security.SecureRandom; import java.util.*; /** * Shamir's Secret Sharing implementation */ public class ShamirSecretSharing { private final SecureRandom random; private final BigInteger prime; public ShamirSecretSharing(int bitLength) { this.random = new SecureRandom(); this.prime = generateLargePrime(bitLength); } public ShamirSecretSharing(BigInteger prime) { this.random = new SecureRandom(); this.prime = prime; } /** * Split secret into n shares where any k can reconstruct */ public Share[] splitSecret(BigInteger secret, int n, int k) { if (k > n) { throw new IllegalArgumentException("k must be <= n"); } // Generate random coefficients for polynomial BigInteger[] coefficients = new BigInteger[k]; coefficients[0] = secret; // Constant term is the secret for (int i = 1; i < k; i++) { coefficients[i] = new BigInteger(prime.bitLength() - 1, random); } // Generate shares Share[] shares = new Share[n]; for (int i = 1; i <= n; i++) { BigInteger x = BigInteger.valueOf(i); BigInteger y = evaluatePolynomial(coefficients, x); shares[i-1] = new Share(x, y); } return shares; } /** * Reconstruct secret from shares */ public BigInteger reconstructSecret(Share[] shares) { if (shares.length < 1) { throw new IllegalArgumentException("At least one share required"); } BigInteger secret = BigInteger.ZERO; for (int i = 0; i < shares.length; i++) { BigInteger numerator = BigInteger.ONE; BigInteger denominator = BigInteger.ONE; for (int j = 0; j < shares.length; j++) { if (i != j) { numerator = numerator.multiply(shares[j].getX().negate()).mod(prime); denominator = denominator.multiply( shares[i].getX().subtract(shares[j].getX())).mod(prime); } } BigInteger lagrangeCoefficient = numerator.multiply( denominator.modInverse(prime)).mod(prime); secret = secret.add(shares[i].getY().multiply(lagrangeCoefficient)).mod(prime); } return secret; } /** * Evaluate polynomial at point x */ private BigInteger evaluatePolynomial(BigInteger[] coefficients, BigInteger x) { BigInteger result = BigInteger.ZERO; for (int i = 0; i < coefficients.length; i++) { BigInteger term = coefficients[i].multiply(x.pow(i)).mod(prime); result = result.add(term).mod(prime); } return result; } /** * Generate a large prime number */ private BigInteger generateLargePrime(int bitLength) { return BigInteger.probablePrime(bitLength, random); } public BigInteger getPrime() { return prime; } } /** * Represents a single share in secret sharing */ class Share { private final BigInteger x; private final BigInteger y; public Share(BigInteger x, BigInteger y) { this.x = x; this.y = y; } public BigInteger getX() { return x; } public BigInteger getY() { return y; } @Override public String toString() { return String.format("Share(x=%s, y=%s)", x, y); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Share share = (Share) obj; return x.equals(share.x) && y.equals(share.y); } @Override public int hashCode() { return Objects.hash(x, y); } } /** * Additive secret sharing implementation */ class AdditiveSecretSharing { private final SecureRandom random; public AdditiveSecretSharing() { this.random = new SecureRandom(); } /** * Split secret into n additive shares */ public BigInteger[] splitSecret(BigInteger secret, int n, BigInteger modulus) { BigInteger[] shares = new BigInteger[n]; BigInteger sum = BigInteger.ZERO; // Generate n-1 random shares for (int i = 0; i < n - 1; i++) { shares[i] = new BigInteger(modulus.bitLength() - 1, random).mod(modulus); sum = sum.add(shares[i]).mod(modulus); } // Last share makes the sum equal to secret shares[n - 1] = secret.subtract(sum).mod(modulus); return shares; } /** * Reconstruct secret from additive shares */ public BigInteger reconstructSecret(BigInteger[] shares, BigInteger modulus) { BigInteger sum = BigInteger.ZERO; for (BigInteger share : shares) { sum = sum.add(share).mod(modulus); } return sum; } } Example 2: Garbled Circuits Implementation
package com.mpc.garbledcircuits; import java.security.SecureRandom; import java.util.*; /** * Garbled Circuit implementation for secure two-party computation */ public class GarbledCircuit { private final SecureRandom random; private final CryptoUtils cryptoUtils; public GarbledCircuit() { this.random = new SecureRandom(); this.cryptoUtils = new CryptoUtils(); } /** * Represents a wire in the circuit */ static class Wire { private final String id; private byte[] key0; // Key for false private byte[] key1; // Key for true private byte[] permutationBit; // Encryption of actual value public Wire(String id) { this.id = id; } public String getId() { return id; } public byte[] getKey0() { return key0; } public void setKey0(byte[] key0) { this.key0 = key0; } public byte[] getKey1() { return key1; } public void setKey1(byte[] key1) { this.key1 = key1; } public byte[] getPermutationBit() { return permutationBit; } public void setPermutationBit(byte[] permutationBit) { this.permutationBit = permutationBit; } } /** * Represents a gate in the circuit */ static class Gate { private final String id; private final GateType type; private final Wire input1; private final Wire input2; private final Wire output; private Map<String, byte[]> garbledTable; public Gate(String id, GateType type, Wire input1, Wire input2, Wire output) { this.id = id; this.type = type; this.input1 = input1; this.input2 = input2; this.output = output; } public String getId() { return id; } public GateType getType() { return type; } public Wire getInput1() { return input1; } public Wire getInput2() { return input2; } public Wire getOutput() { return output; } public Map<String, byte[]> getGarbledTable() { return garbledTable; } public void setGarbledTable(Map<String, byte[]> garbledTable) { this.garbledTable = garbledTable; } } enum GateType { AND, OR, XOR, NOT } /** * Garble a circuit */ public GarbledCircuitResult garbleCircuit(List<Gate> gates, List<Wire> inputWires, List<Wire> outputWires) { // Generate keys for all wires for (Wire wire : getAllWires(gates)) { wire.setKey0(cryptoUtils.generateKey()); wire.setKey1(cryptoUtils.generateKey()); wire.setPermutationBit(new byte[]{(byte) (random.nextBoolean() ? 1 : 0)}); } // Create garbled tables for each gate for (Gate gate : gates) { gate.setGarbledTable(createGarbledTable(gate)); } return new GarbledCircuitResult(gates, inputWires, outputWires); } /** * Evaluate garbled circuit */ public Map<Wire, Boolean> evaluateCircuit(GarbledCircuitResult garbledCircuit, Map<Wire, byte[]> inputKeys) { Map<Wire, byte[]> computedKeys = new HashMap<>(inputKeys); // Evaluate gates in topological order (simplified) for (Gate gate : garbledCircuit.getGates()) { byte[] input1Key = computedKeys.get(gate.getInput1()); byte[] input2Key = computedKeys.get(gate.getInput2()); if (input1Key != null && input2Key != null) { byte[] outputKey = evaluateGate(gate, input1Key, input2Key); if (outputKey != null) { computedKeys.put(gate.getOutput(), outputKey); } } } // Decode output wires Map<Wire, Boolean> output = new HashMap<>(); for (Wire outputWire : garbledCircuit.getOutputWires()) { byte[] key = computedKeys.get(outputWire); if (key != null) { boolean value = decodeWire(outputWire, key); output.put(outputWire, value); } } return output; } private Map<String, byte[]> createGarbledTable(Gate gate) { Map<String, byte[]> table = new HashMap<>(); Wire in1 = gate.getInput1(); Wire in2 = gate.getInput2(); Wire out = gate.getOutput(); // For each possible input combination for (int i = 0; i <= 1; i++) { for (int j = 0; j <= 1; j++) { boolean a = (i == 1); boolean b = (j == 1); // Compute gate output boolean outputValue = computeGateOutput(gate.getType(), a, b); // Get input keys byte[] keyA = a ? in1.getKey1() : in1.getKey0(); byte[] keyB = b ? in2.getKey1() : in2.getKey0(); // Get output key byte[] outputKey = outputValue ? out.getKey1() : out.getKey0(); // Encrypt output key with both input keys byte[] encrypted = cryptoUtils.doubleEncrypt(outputKey, keyA, keyB); // Store in garbled table String index = cryptoUtils.hashKeys(keyA, keyB); table.put(index, encrypted); } } return table; } private byte[] evaluateGate(Gate gate, byte[] input1Key, byte[] input2Key) { String index = cryptoUtils.hashKeys(input1Key, input2Key); byte[] encryptedOutput = gate.getGarbledTable().get(index); if (encryptedOutput != null) { return cryptoUtils.doubleDecrypt(encryptedOutput, input1Key, input2Key); } return null; } private boolean computeGateOutput(GateType type, boolean a, boolean b) { switch (type) { case AND: return a && b; case OR: return a || b; case XOR: return a != b; case NOT: return !a; default: throw new IllegalArgumentException("Unsupported gate type: " + type); } } private boolean decodeWire(Wire wire, byte[] key) { boolean permutation = wire.getPermutationBit()[0] == 1; if (Arrays.equals(key, wire.getKey0())) { return permutation ? true : false; } else if (Arrays.equals(key, wire.getKey1())) { return permutation ? false : true; } else { throw new IllegalArgumentException("Invalid key for wire: " + wire.getId()); } } private List<Wire> getAllWires(List<Gate> gates) { Set<Wire> wires = new HashSet<>(); for (Gate gate : gates) { wires.add(gate.getInput1()); wires.add(gate.getInput2()); wires.add(gate.getOutput()); } return new ArrayList<>(wires); } } class GarbledCircuitResult { private final List<Gate> gates; private final List<Wire> inputWires; private final List<Wire> outputWires; public GarbledCircuitResult(List<Gate> gates, List<Wire> inputWires, List<Wire> outputWires) { this.gates = gates; this.inputWires = inputWires; this.outputWires = outputWires; } public List<Gate> getGates() { return gates; } public List<Wire> getInputWires() { return inputWires; } public List<Wire> getOutputWires() { return outputWires; } } /** * Cryptographic utilities for garbled circuits */ class CryptoUtils { private final SecureRandom random = new SecureRandom(); public byte[] generateKey() { byte[] key = new byte[32]; // 256-bit key random.nextBytes(key); return key; } public byte[] doubleEncrypt(byte[] data, byte[] key1, byte[] key2) { // Simplified encryption - in production use proper AES-GCM byte[] result = new byte[data.length]; for (int i = 0; i < data.length; i++) { result[i] = (byte) (data[i] ^ key1[i % key1.length] ^ key2[i % key2.length]); } return result; } public byte[] doubleDecrypt(byte[] data, byte[] key1, byte[] key2) { // Decryption is same as encryption for XOR return doubleEncrypt(data, key1, key2); } public String hashKeys(byte[] key1, byte[] key2) { // Simplified hash - in production use proper SHA-256 byte[] combined = new byte[key1.length + key2.length]; System.arraycopy(key1, 0, combined, 0, key1.length); System.arraycopy(key2, 0, combined, key1.length, key2.length); return Base64.getEncoder().encodeToString(combined); } } Example 3: Oblivious Transfer Implementation
package com.mpc.oblivioustransfer; import java.math.BigInteger; import java.security.SecureRandom; import java.util.*; /** * 1-out-of-2 Oblivious Transfer implementation */ public class ObliviousTransfer { private final SecureRandom random; private final BigInteger prime; private final BigInteger generator; public ObliviousTransfer(int bitLength) { this.random = new SecureRandom(); this.prime = generateSafePrime(bitLength); this.generator = findGenerator(prime); } /** * Sender's part of OT protocol */ public OTResponse send(OTRequest request, BigInteger m0, BigInteger m1) { BigInteger c = request.getC(); BigInteger g = request.getG(); BigInteger h = request.getH(); // Generate random exponent BigInteger r = new BigInteger(prime.bitLength() - 1, random); // Compute public values BigInteger gr = g.modPow(r, prime); BigInteger hr = h.modPow(r, prime); // Compute encrypted messages BigInteger e0 = m0.multiply(hr.modPow(c, prime).modInverse(prime)).mod(prime); BigInteger e1 = m1.multiply(hr.modPow(c.subtract(BigInteger.ONE).negate(), prime) .modInverse(prime)).mod(prime); return new OTResponse(gr, e0, e1); } /** * Receiver's part of OT protocol */ public OTRequest receive(boolean choice) { // Generate random secret BigInteger k = new BigInteger(prime.bitLength() - 1, random); // Compute public values based on choice BigInteger c = choice ? BigInteger.ONE : BigInteger.ZERO; BigInteger gk = generator.modPow(k, prime); BigInteger h = generator.modPow(c, prime).multiply(gk).mod(prime); return new OTRequest(generator, h, c, k); } /** * Receiver decrypts chosen message */ public BigInteger decrypt(OTRequest request, OTResponse response, boolean choice) { BigInteger gr = response.getGr(); BigInteger k = request.getK(); BigInteger grk = gr.modPow(k, prime); BigInteger message; if (!choice) { message = response.getE0().multiply(grk).mod(prime); } else { message = response.getE1().multiply(grk).mod(prime); } return message; } /** * Generate a safe prime (p = 2q + 1 where q is also prime) */ private BigInteger generateSafePrime(int bitLength) { while (true) { BigInteger p = BigInteger.probablePrime(bitLength, random); BigInteger q = p.subtract(BigInteger.ONE).divide(BigInteger.valueOf(2)); if (q.isProbablePrime(100)) { return p; } } } /** * Find a generator for the multiplicative group modulo prime */ private BigInteger findGenerator(BigInteger prime) { BigInteger pMinus1 = prime.subtract(BigInteger.ONE); BigInteger g = BigInteger.valueOf(2); while (true) { if (g.modPow(pMinus1, prime).equals(BigInteger.ONE)) { return g; } g = g.add(BigInteger.ONE); } } public BigInteger getPrime() { return prime; } public BigInteger getGenerator() { return generator; } } class OTRequest { private final BigInteger g; private final BigInteger h; private final BigInteger c; private final BigInteger k; // Secret key public OTRequest(BigInteger g, BigInteger h, BigInteger c, BigInteger k) { this.g = g; this.h = h; this.c = c; this.k = k; } public BigInteger getG() { return g; } public BigInteger getH() { return h; } public BigInteger getC() { return c; } public BigInteger getK() { return k; } } class OTResponse { private final BigInteger gr; private final BigInteger e0; private final BigInteger e1; public OTResponse(BigInteger gr, BigInteger e0, BigInteger e1) { this.gr = gr; this.e0 = e0; this.e1 = e1; } public BigInteger getGr() { return gr; } public BigInteger getE0() { return e0; } public BigInteger getE1() { return e1; } } /** * k-out-of-n Oblivious Transfer */ class KOutOfNOT { private final ObliviousTransfer baseOT; private final SecureRandom random; public KOutOfNOT(int bitLength) { this.baseOT = new ObliviousTransfer(bitLength); this.random = new SecureRandom(); } /** * Sender with n messages, receiver chooses k indices */ public KOTResponse send(KOTRequest request, List<BigInteger> messages) { if (messages.size() != request.getN()) { throw new IllegalArgumentException("Number of messages must match n"); } List<BigInteger> encryptedMessages = new ArrayList<>(); // Encrypt each message for (int i = 0; i < messages.size(); i++) { BigInteger message = messages.get(i); BigInteger encryptionKey = computeEncryptionKey(request, i); BigInteger encrypted = message.multiply(encryptionKey).mod(baseOT.getPrime()); encryptedMessages.add(encrypted); } return new KOTResponse(encryptedMessages); } /** * Receiver chooses k indices out of n */ public KOTRequest receive(int n, Set<Integer> chosenIndices) { if (chosenIndices.size() > n) { throw new IllegalArgumentException("Too many chosen indices"); } List<OTRequest> otRequests = new ArrayList<>(); Map<Integer, Boolean> choiceBits = new HashMap<>(); // Create base OT requests for each position for (int i = 0; i < n; i++) { boolean choice = chosenIndices.contains(i); OTRequest otRequest = baseOT.receive(choice); otRequests.add(otRequest); choiceBits.put(i, choice); } return new KOTRequest(otRequests, choiceBits, n, chosenIndices.size()); } /** * Receiver decrypts chosen messages */ public List<BigInteger> decrypt(KOTRequest request, KOTResponse response) { List<BigInteger> decryptedMessages = new ArrayList<>(); for (int i : request.getChosenIndices()) { BigInteger encryptedMessage = response.getEncryptedMessages().get(i); BigInteger decryptionKey = computeDecryptionKey(request, i); BigInteger decrypted = encryptedMessage.multiply(decryptionKey.modInverse(baseOT.getPrime())) .mod(baseOT.getPrime()); decryptedMessages.add(decrypted); } return decryptedMessages; } private BigInteger computeEncryptionKey(KOTRequest request, int index) { OTRequest otRequest = request.getOtRequests().get(index); boolean choice = request.getChoiceBits().get(index); // Simulate OT response for encryption key computation BigInteger dummyMessage = BigInteger.ONE; OTResponse otResponse = baseOT.send(otRequest, dummyMessage, dummyMessage); return baseOT.decrypt(otRequest, otResponse, choice); } private BigInteger computeDecryptionKey(KOTRequest request, int index) { // This would involve more complex cryptographic operations in a real implementation OTRequest otRequest = request.getOtRequests().get(index); return otRequest.getG().modPow(otRequest.getK(), baseOT.getPrime()); } } class KOTRequest { private final List<OTRequest> otRequests; private final Map<Integer, Boolean> choiceBits; private final int n; private final int k; public KOTRequest(List<OTRequest> otRequests, Map<Integer, Boolean> choiceBits, int n, int k) { this.otRequests = otRequests; this.choiceBits = choiceBits; this.n = n; this.k = k; } public List<OTRequest> getOtRequests() { return otRequests; } public Map<Integer, Boolean> getChoiceBits() { return choiceBits; } public int getN() { return n; } public int getK() { return k; } public Set<Integer> getChosenIndices() { return choiceBits.keySet(); } } class KOTResponse { private final List<BigInteger> encryptedMessages; public KOTResponse(List<BigInteger> encryptedMessages) { this.encryptedMessages = encryptedMessages; } public List<BigInteger> getEncryptedMessages() { return encryptedMessages; } } Example 4: MPC Party Implementation
package com.mpc.party; import com.mpc.secretsharing.ShamirSecretSharing; import com.mpc.secretsharing.Share; import java.math.BigInteger; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * Represents a party in MPC protocol */ public class MPCParty { private final String id; private final int partyId; private final ShamirSecretSharing secretSharing; private final Map<String, BigInteger> privateInputs; private final Map<String, List<Share>> receivedShares; private final Map<String, BigInteger> computationResults; public MPCParty(String id, int partyId, ShamirSecretSharing secretSharing) { this.id = id; this.partyId = partyId; this.secretSharing = secretSharing; this.privateInputs = new ConcurrentHashMap<>(); this.receivedShares = new ConcurrentHashMap<>(); this.computationResults = new ConcurrentHashMap<>(); } /** * Set private input for computation */ public void setInput(String inputId, BigInteger value) { privateInputs.put(inputId, value); } /** * Share inputs with other parties */ public Map<Integer, List<Share>> shareInputs(int totalParties, int threshold) { Map<Integer, List<Share>> sharesForParties = new HashMap<>(); for (String inputId : privateInputs.keySet()) { BigInteger secret = privateInputs.get(inputId); Share[] shares = secretSharing.splitSecret(secret, totalParties, threshold); for (int i = 0; i < shares.length; i++) { int partyIndex = i + 1; sharesForParties.computeIfAbsent(partyIndex, k -> new ArrayList<>()) .add(new Share(BigInteger.valueOf(partyId), shares[i].getY())); } } return sharesForParties; } /** * Receive shares from other parties */ public void receiveShares(String computationId, List<Share> shares) { receivedShares.put(computationId, shares); } /** * Perform local computation on shares */ public Share computeLocal(String computationId, ComputationType operation, List<String> inputIds) { List<Share> shares = receivedShares.get(computationId); if (shares == null || shares.isEmpty()) { throw new IllegalStateException("No shares available for computation: " + computationId); } BigInteger result; switch (operation) { case ADD: result = performAddition(shares); break; case MULTIPLY: result = performMultiplication(shares); break; case COMPARE: result = performComparison(shares); break; default: throw new IllegalArgumentException("Unsupported operation: " + operation); } Share resultShare = new Share(BigInteger.valueOf(partyId), result); computationResults.put(computationId, result); return resultShare; } /** * Reconstruct final result from shares */ public BigInteger reconstructResult(String computationId, List<Share> resultShares) { Share[] sharesArray = resultShares.toArray(new Share[0]); return secretSharing.reconstructSecret(sharesArray); } private BigInteger performAddition(List<Share> shares) { BigInteger sum = BigInteger.ZERO; for (Share share : shares) { sum = sum.add(share.getY()).mod(secretSharing.getPrime()); } return sum; } private BigInteger performMultiplication(List<Share> shares) { // Simplified multiplication - in practice would use Beaver triples BigInteger product = BigInteger.ONE; for (Share share : shares) { product = product.multiply(share.getY()).mod(secretSharing.getPrime()); } return product; } private BigInteger performComparison(List<Share> shares) { // Simplified comparison - in practice would use more complex protocols if (shares.size() != 2) { throw new IllegalArgumentException("Comparison requires exactly 2 inputs"); } BigInteger a = shares.get(0).getY(); BigInteger b = shares.get(1).getY(); return a.compareTo(b) > 0 ? BigInteger.ONE : BigInteger.ZERO; } // Getters public String getId() { return id; } public int getPartyId() { return partyId; } public Map<String, BigInteger> getPrivateInputs() { return Collections.unmodifiableMap(privateInputs); } public Map<String, BigInteger> getComputationResults() { return Collections.unmodifiableMap(computationResults); } } enum ComputationType { ADD, MULTIPLY, COMPARE, CUSTOM } /** * MPC Coordinator for managing multiple parties */ public class MPCCoordinator { private final List<MPCParty> parties; private final ShamirSecretSharing secretSharing; private final int threshold; public MPCCoordinator(int totalParties, int threshold, int primeBitLength) { this.secretSharing = new ShamirSecretSharing(primeBitLength); this.threshold = threshold; this.parties = new ArrayList<>(); for (int i = 0; i < totalParties; i++) { parties.add(new MPCParty("Party-" + (i + 1), i + 1, secretSharing)); } } /** * Execute secure computation across all parties */ public Map<String, BigInteger> executeComputation(String computationId, ComputationType operation, Map<String, List<String>> partyInputs) { // Phase 1: Input sharing Map<Integer, List<Share>> allShares = shareInputs(partyInputs); // Phase 2: Local computation Map<Integer, Share> localResults = performLocalComputations(computationId, operation, allShares); // Phase 3: Result reconstruction return reconstructResults(computationId, localResults); } private Map<Integer, List<Share>> shareInputs(Map<String, List<String>> partyInputs) { Map<Integer, List<Share>> allShares = new HashMap<>(); for (MPCParty party : parties) { String partyId = party.getId(); if (partyInputs.containsKey(partyId)) { // Set inputs for this party List<String> inputs = partyInputs.get(partyId); for (String input : inputs) { // In real implementation, get actual input values BigInteger value = new BigInteger(128, new SecureRandom()); party.setInput(input, value); } // Share inputs Map<Integer, List<Share>> shares = party.shareInputs(parties.size(), threshold); for (Map.Entry<Integer, List<Share>> entry : shares.entrySet()) { allShares.merge(entry.getKey(), entry.getValue(), (list1, list2) -> { list1.addAll(list2); return list1; }); } } } // Distribute shares to parties for (MPCParty party : parties) { int partyIndex = party.getPartyId(); List<Share> sharesForParty = allShares.get(partyIndex); if (sharesForParty != null) { party.receiveShares("computation", sharesForParty); } } return allShares; } private Map<Integer, Share> performLocalComputations(String computationId, ComputationType operation, Map<Integer, List<Share>> allShares) { Map<Integer, Share> localResults = new HashMap<>(); for (MPCParty party : parties) { Share result = party.computeLocal(computationId, operation, Arrays.asList("input1", "input2")); localResults.put(party.getPartyId(), result); } return localResults; } private Map<String, BigInteger> reconstructResults(String computationId, Map<Integer, Share> localResults) { Map<String, BigInteger> results = new HashMap<>(); // Collect shares for reconstruction List<Share> resultShares = new ArrayList<>(localResults.values()); // Each party reconstructs the result for (MPCParty party : parties) { BigInteger result = party.reconstructResult(computationId, resultShares); results.put(party.getId(), result); } return results; } public List<MPCParty> getParties() { return Collections.unmodifiableList(parties); } } Example 5: Network Communication Layer
package com.mpc.network; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; import io.netty.handler.codec.serialization.ClassResolvers; import io.netty.handler.codec.serialization.ObjectDecoder; import io.netty.handler.codec.serialization.ObjectEncoder; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; /** * Network layer for MPC parties communication */ public class MPCNetwork { private final String nodeId; private final int port; private final Map<String, Channel> connections; private final Map<String, CompletableFuture<MPCMessage>> pendingRequests; private final EventLoopGroup workerGroup; private Channel serverChannel; public MPCNetwork(String nodeId, int port) { this.nodeId = nodeId; this.port = port; this.connections = new ConcurrentHashMap<>(); this.pendingRequests = new ConcurrentHashMap<>(); this.workerGroup = new NioEventLoopGroup(); } public void start() throws InterruptedException { startServer(); } public void stop() { workerGroup.shutdownGracefully(); if (serverChannel != null) { serverChannel.close(); } connections.values().forEach(Channel::close); } /** * Connect to another MPC node */ public CompletableFuture<Void> connect(String nodeId, String host, int port) { CompletableFuture<Void> future = new CompletableFuture<>(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(workerGroup) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast( new LengthFieldBasedFrameDecoder(1048576, 0, 4, 0, 4), new LengthFieldPrepender(4), new ObjectEncoder(), new ObjectDecoder(ClassResolvers.cacheDisabled(null)), new MPCClientHandler() ); } }); bootstrap.connect(host, port).addListener((ChannelFutureListener) channelFuture -> { if (channelFuture.isSuccess()) { connections.put(nodeId, channelFuture.channel()); future.complete(null); } else { future.completeExceptionally(channelFuture.cause()); } }); return future; } /** * Send message to specific node */ public CompletableFuture<MPCMessage> sendMessage(String targetNodeId, MPCMessage message) { Channel channel = connections.get(targetNodeId); if (channel == null) { return CompletableFuture.failedFuture( new IllegalStateException("Not connected to node: " + targetNodeId)); } CompletableFuture<MPCMessage> responseFuture = new CompletableFuture<>(); pendingRequests.put(message.getMessageId(), responseFuture); channel.writeAndFlush(message).addListener((ChannelFutureListener) future -> { if (!future.isSuccess()) { pendingRequests.remove(message.getMessageId()); responseFuture.completeExceptionally(future.cause()); } }); return responseFuture; } /** * Broadcast message to all connected nodes */ public Map<String, CompletableFuture<MPCMessage>> broadcast(MPCMessage message) { Map<String, CompletableFuture<MPCMessage>> futures = new HashMap<>(); for (String nodeId : connections.keySet()) { // Create unique message ID for each recipient MPCMessage individualMessage = message.copyWithNewId(); futures.put(nodeId, sendMessage(nodeId, individualMessage)); } return futures; } private void startServer() throws InterruptedException { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast( new LengthFieldBasedFrameDecoder(1048576, 0, 4, 0, 4), new LengthFieldPrepender(4), new ObjectEncoder(), new ObjectDecoder(ClassResolvers.cacheDisabled(null)), new MPCServerHandler() ); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); serverChannel = bootstrap.bind(port).sync().channel(); } private class MPCServerHandler extends SimpleChannelInboundHandler<MPCMessage> { @Override protected void channelRead0(ChannelHandlerContext ctx, MPCMessage msg) { // Handle incoming messages processIncomingMessage(msg, ctx.channel()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } } private class MPCClientHandler extends SimpleChannelInboundHandler<MPCMessage> { @Override protected void channelRead0(ChannelHandlerContext ctx, MPCMessage msg) { // Handle responses to our requests CompletableFuture<MPCMessage> future = pendingRequests.remove(msg.getCorrelationId()); if (future != null) { if (msg.getType() == MessageType.ERROR) { future.completeExceptionally(new MPCException(msg.getPayload().toString())); } else { future.complete(msg); } } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } } private void processIncomingMessage(MPCMessage message, Channel channel) { switch (message.getType()) { case SHARE_DISTRIBUTION: handleShareDistribution(message); break; case COMPUTATION_REQUEST: handleComputationRequest(message); break; case RESULT_AGGREGATION: handleResultAggregation(message); break; default: sendErrorResponse(message, "Unknown message type"); } } private void handleShareDistribution(MPCMessage message) { // Process received shares // In practice, this would update the party's state MPCMessage response = new MPCMessage( MessageType.SHARE_ACK, nodeId, message.getSenderId(), "Shares received", UUID.randomUUID().toString(), message.getMessageId() ); Channel channel = connections.get(message.getSenderId()); if (channel != null) { channel.writeAndFlush(response); } } private void handleComputationRequest(MPCMessage message) { // Process computation request try { // Perform local computation Object result = performLocalComputation(message.getPayload()); MPCMessage response = new MPCMessage( MessageType.COMPUTATION_RESULT, nodeId, message.getSenderId(), result, UUID.randomUUID().toString(), message.getMessageId() ); Channel channel = connections.get(message.getSenderId()); if (channel != null) { channel.writeAndFlush(response); } } catch (Exception e) { sendErrorResponse(message, "Computation failed: " + e.getMessage()); } } private void handleResultAggregation(MPCMessage message) { // Aggregate results from multiple parties // This would typically involve secret reconstruction } private void sendErrorResponse(MPCMessage originalMessage, String error) { MPCMessage errorResponse = new MPCMessage( MessageType.ERROR, nodeId, originalMessage.getSenderId(), error, UUID.randomUUID().toString(), originalMessage.getMessageId() ); Channel channel = connections.get(originalMessage.getSenderId()); if (channel != null) { channel.writeAndFlush(errorResponse); } } private Object performLocalComputation(Object payload) { // Implement local computation logic return "computation_result"; } } class MPCMessage implements java.io.Serializable { private final MessageType type; private final String senderId; private final String recipientId; private final Object payload; private final String messageId; private final String correlationId; public MPCMessage(MessageType type, String senderId, String recipientId, Object payload, String messageId, String correlationId) { this.type = type; this.senderId = senderId; this.recipientId = recipientId; this.payload = payload; this.messageId = messageId; this.correlationId = correlationId; } public MPCMessage copyWithNewId() { return new MPCMessage(type, senderId, recipientId, payload, UUID.randomUUID().toString(), correlationId); } // Getters public MessageType getType() { return type; } public String getSenderId() { return senderId; } public String getRecipientId() { return recipientId; } public Object getPayload() { return payload; } public String getMessageId() { return messageId; } public String getCorrelationId() { return correlationId; } } enum MessageType { SHARE_DISTRIBUTION, SHARE_ACK, COMPUTATION_REQUEST, COMPUTATION_RESULT, RESULT_AGGREGATION, ERROR } class MPCException extends RuntimeException { public MPCException(String message) { super(message); } public MPCException(String message, Throwable cause) { super(message, cause); } } Example 6: Complete MPC Demo
package com.mpc.demo; import com.mpc.secretsharing.ShamirSecretSharing; import com.mpc.secretsharing.Share; import com.mpc.party.MPCCoordinator; import com.mpc.party.ComputationType; import java.math.BigInteger; import java.util.*; public class MPCDemo { public static void main(String[] args) { try { demoSecretSharing(); demoMPCComputation(); demoObliviousTransfer(); } catch (Exception e) { e.printStackTrace(); } } private static void demoSecretSharing() throws Exception { System.out.println("=== Secret Sharing Demo ==="); ShamirSecretSharing sharing = new ShamirSecretSharing(256); BigInteger secret = new BigInteger("123456789"); System.out.println("Original secret: " + secret); // Split into 5 shares, need 3 to reconstruct Share[] shares = sharing.splitSecret(secret, 5, 3); System.out.println("Generated shares:"); for (int i = 0; i < shares.length; i++) { System.out.println(" Share " + (i + 1) + ": " + shares[i]); } // Reconstruct with first 3 shares Share[] reconstructionShares = {shares[0], shares[1], shares[2]}; BigInteger reconstructed = sharing.reconstructSecret(reconstructionShares); System.out.println("Reconstructed secret: " + reconstructed); System.out.println("Success: " + secret.equals(reconstructed)); System.out.println(); } private static void demoMPCComputation() throws Exception { System.out.println("=== MPC Computation Demo ==="); // Create coordinator with 3 parties, threshold 2 MPCCoordinator coordinator = new MPCCoordinator(3, 2, 256); // Set up inputs for each party Map<String, List<String>> partyInputs = new HashMap<>(); partyInputs.put("Party-1", Arrays.asList("input1", "input2")); partyInputs.put("Party-2", Arrays.asList("input1", "input2")); partyInputs.put("Party-3", Arrays.asList("input1", "input2")); // Execute secure addition Map<String, BigInteger> results = coordinator.executeComputation( "addition", ComputationType.ADD, partyInputs); System.out.println("Computation results:"); for (Map.Entry<String, BigInteger> entry : results.entrySet()) { System.out.println(" " + entry.getKey() + ": " + entry.getValue()); } // Verify all parties got the same result Set<BigInteger> uniqueResults = new HashSet<>(results.values()); System.out.println("All parties agree: " + (uniqueResults.size() == 1)); System.out.println(); } private static void demoObliviousTransfer() throws Exception { System.out.println("=== Oblivious Transfer Demo ==="); com.mpc.oblivioustransfer.ObliviousTransfer ot = new com.mpc.oblivioustransfer.ObliviousTransfer(256); // Sender's messages BigInteger m0 = new BigInteger("111111"); BigInteger m1 = new BigInteger("222222"); System.out.println("Sender's messages: m0=" + m0 + ", m1=" + m1); // Receiver chooses to get message at index 1 boolean choice = true; System.out.println("Receiver chooses: " + (choice ? "m1" : "m0")); // OT protocol execution com.mpc.oblivioustransfer.OTRequest request = ot.receive(choice); com.mpc.oblivioustransfer.OTResponse response = ot.send(request, m0, m1); BigInteger receivedMessage = ot.decrypt(request, response, choice); System.out.println("Receiver got: " + receivedMessage); System.out.println("Success: " + receivedMessage.equals(choice ? m1 : m0)); System.out.println(); } } Security Considerations and Best Practices
Security Best Practices
package com.mpc.security; import java.security.SecureRandom; import java.math.BigInteger; public class MPCSecurity { /** * Cryptographically secure random number generator */ public static class SecureRandomGenerator { private final SecureRandom random; public SecureRandomGenerator() { this.random = new SecureRandom(); // Additional seeding for better security byte[] seed = new byte[64]; new SecureRandom().nextBytes(seed); random.setSeed(seed); } public BigInteger generateRandomPrime(int bitLength) { return BigInteger.probablePrime(bitLength, random); } public BigInteger generateRandomInRange(BigInteger min, BigInteger max) { BigInteger range = max.subtract(min); BigInteger result; do { result = new BigInteger(range.bitLength(), random); } while (result.compareTo(range) >= 0); return result.add(min); } public byte[] generateRandomBytes(int length) { byte[] bytes = new byte[length]; random.nextBytes(bytes); return bytes; } } /** * Timing attack protection */ public static class TimingSafeOperations { /** * Constant-time comparison of BigIntegers */ public static boolean constantTimeEquals(BigInteger a, BigInteger b) { byte[] aBytes = a.toByteArray(); byte[] bBytes = b.toByteArray(); // Pad to same length int maxLength = Math.max(aBytes.length, bBytes.length); byte[] aPadded = Arrays.copyOf(aBytes, maxLength); byte[] bPadded = Arrays.copyOf(bBytes, maxLength); int result = 0; for (int i = 0; i < maxLength; i++) { result |= (aPadded[i] ^ bPadded[i]); } return result == 0; } /** * Constant-time selection */ public static BigInteger constantTimeSelect(boolean condition, BigInteger trueValue, BigInteger falseValue) { BigInteger mask = condition ? BigInteger.ONE.negate() : BigInteger.ZERO; return trueValue.and(mask).or(falseValue.and(mask.not())); } } /** * Input validation and sanitization */ public static class InputValidator { public static void validateShareCount(int n, int k) { if (k < 2) { throw new IllegalArgumentException("Threshold k must be at least 2"); } if (k > n) { throw new IllegalArgumentException("Threshold k cannot exceed total shares n"); } if (n < 2) { throw new IllegalArgumentException("Must have at least 2 parties"); } } public static void validatePrime(BigInteger prime) { if (!prime.isProbablePrime(100)) { throw new IllegalArgumentException("Number is not prime with sufficient probability"); } if (prime.bitLength() < 128) { throw new IllegalArgumentException("Prime too small for security"); } } public static void validateInputRange(BigInteger input, BigInteger modulus) { if (input.compareTo(BigInteger.ZERO) < 0 || input.compareTo(modulus) >= 0) { throw new IllegalArgumentException("Input out of valid range [0, modulus-1]"); } } } } Testing
Example 7: Comprehensive Tests
package com.mpc.test; import com.mpc.secretsharing.ShamirSecretSharing; import com.mpc.secretsharing.Share; import org.junit.jupiter.api.Test; import java.math.BigInteger; import static org.junit.jupiter.api.Assertions.*; class MPCProtocolsTest { @Test void testShamirSecretSharing() { ShamirSecretSharing sharing = new ShamirSecretSharing(128); BigInteger secret = new BigInteger("123456789"); // Test basic sharing and reconstruction Share[] shares = sharing.splitSecret(secret, 5, 3); assertEquals(5, shares.length); // Reconstruct with minimum shares Share[] minShares = {shares[0], shares[1], shares[2]}; BigInteger reconstructed = sharing.reconstructSecret(minShares); assertEquals(secret, reconstructed); // Test that fewer than threshold shares cannot reconstruct Share[] insufficientShares = {shares[0], shares[1]}; BigInteger partialReconstruction = sharing.reconstructSecret(insufficientShares); assertNotEquals(secret, partialReconstruction); } @Test void testObliviousTransfer() throws Exception { com.mpc.oblivioustransfer.ObliviousTransfer ot = new com.mpc.oblivioustransfer.ObliviousTransfer(256); BigInteger m0 = new BigInteger("111"); BigInteger m1 = new BigInteger("222"); // Test both choices for (boolean choice : new boolean[]{false, true}) { com.mpc.oblivioustransfer.OTRequest request = ot.receive(choice); com.mpc.oblivioustransfer.OTResponse response = ot.send(request, m0, m1); BigInteger result = ot.decrypt(request, response, choice); BigInteger expected = choice ? m1 : m0; assertEquals(expected, result); } } @Test void testAdditiveSecretSharing() { com.mpc.secretsharing.AdditiveSecretSharing sharing = new com.mpc.secretsharing.AdditiveSecretSharing(); BigInteger modulus = BigInteger.probablePrime(128, new java.security.SecureRandom()); BigInteger secret = new BigInteger("999999"); BigInteger[] shares = sharing.splitSecret(secret, 4, modulus); assertEquals(4, shares.length); BigInteger reconstructed = sharing.reconstructSecret(shares, modulus); assertEquals(secret, reconstructed); } } Conclusion
This comprehensive MPC implementation provides:
Core Protocols:
- Secret Sharing: Shamir's and additive secret sharing
- Garbled Circuits: Secure two-party computation
- Oblivious Transfer: 1-out-of-2 and k-out-of-n OT
- MPC Parties: Complete party implementation with coordination
- Networking: Secure communication layer
Security Features:
- Information-theoretic security for secret sharing
- Cryptographic security for garbled circuits and OT
- Malicious security considerations
- Timing attack protection
- Input validation
Production Considerations:
- Performance: Optimize for large-scale computations
- Network: Implement reliable, secure communication
- Fault Tolerance: Handle party failures gracefully
- Audit Logging: Comprehensive security logging
- Key Management: Secure key generation and storage
This implementation provides a solid foundation for building secure multi-party computation systems that can handle various privacy-preserving computation scenarios while maintaining strong security guarantees.