// TestAnony.java
//
// This is the main Class of the "Anonymity Engine": a suite of tools to
// Calculate the anonymity of a system.
//
// Tom Chothia T.Chothia@cwi.nl June/2008
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// Copyright 2008 Tom Chothia
package info;
import java.util.Vector;
import java.util.Iterator;
import java.io.*;
import java.util.regex.*;
public class TestAnony {
// Default options that can be override by the commandline.
static boolean readFromChanFile = false;
static boolean readFromObsFile = false;
static boolean miUniform = false;
static int verbose = 5;
static double acceptableError = 0.000000000001;
static int noOfIterations = 5000;
static Observations obs;
static Channel channel;
static double[] inputDist;
static int sampleSize =0;
// Apple
// static String fileLocation = "/Users/tom/Documents/AE/";
// eee
//static String fileLocation = "/home/user/Projects/ae/code/";
// bham
static String fileLocation = "/home/staff/tpc/AE/workspace/Data/";
//static String fileLocation = System.getProperty("user.dir")+"/";
// Win
// static String fileLocation = "C:\\Documents and Settings\\tpc\\workspace\\Data\\";
// A noisy channel with non-overlapping outputs
// static String fileName = fileLocation+"Channels/NoisyNonOverlap.txt";
// Cap = 1
// A binary symmetric channel
// static String fileName = fileLocation+"Channels/home/staff/tpc/AE/workspace/Data/Channels//BinSymChannel.txt";
//
static double p = 0.25;
static double[][] defaultChannelMatrix_W = {{1-p,p},{p,1-p}};
static String[] defaultOutputNames = {"o1","o2"};
static String[] defaultInputNames = {"a1","a2"};
//static double[] defaultInputPMF_Q= {0.75,0.25};
// Cap = 1-H(p) i.e. 0.18872187554086717 for p = 0.25
// A binary erasure channel
//static String fileName = fileLocation+"Channels/BinErasureChannel.txt";
// Cap = 1-a
// Noisy Typewriter with 6 keys
//static String fileName = fileLocation+"Channels/TextBookInfoTheory/NoisyTypeW6.txt";
// Cap = log2(3)
// Noisy Typewriter with 26 keys
//static String fileName = fileLocation+"Channels/NoisyTypeW26.txt";
//Cap = log2(13)
// static String fileName = fileLocation+"Channels/NonSymChannel.txt";
//static String fileName = fileLocation+"Channels/MultiChannel1.txt";
//static String fileName = fileLocation+"Channels/multiChannel.txt";
// Motivating example for D.C.
//static String fileName = fileLocation+"Channels/DiningCryptos/dc4fair.txt";
//static String fileName = fileLocation+"Channels/DiningCryptos/dc4allbais.txt";
//static String fileName = fileLocation+"Channels/DiningCryptos/dc4twoBiased.txt";
// Motivating example for multi-sender channels.
//static String fileName = fileLocation+"Channels/MultiSender/MultiAsSingleChannel.txt";
//static String fileName = fileLocation+"Channels/MultiSender/MultiChannelExample.txt";
//static String fileName = fileLocation+"Channels/MultiSender/MultiChannel1.txt";
// Examples of obs files
//static String fileName = fileLocation+"Tests/dc4cpu1linux40000.txt";
//static String fileName = fileLocation+"Tests/dc4cpu1apple40000.txt";
//static String fileName = "/home/staff/tpc/AE/workspace/Data/Tests/dc4cpu2apple40000.txt";
//static String fileName = "/home/staff/tpc/AE/workspace/Data/Channels/DiningCryptos/dc3allbias4.txt";
// static String fileName = "/home/staff/tpc/AE/workspace/Data/Tests/dc4cpu8win100000.txt";
// static String fileName = "/home/staff/tpc/DCtests/DCtest.txt";
//"/home/staff/tpc/AE/workspace/Data/Channels/DiningCryptos/dc4allbias.txt";
// static String fileName = "C:\\Documents and Settings\\tpc\\workspace\\Data\\Channels\\DiningCryptos\\dc3allbias4.txt";
//static String fileName = "/home/user/apps/mixs/data2/test.txt";
//static String fileName = "/home/user/data2/matrix0904.txt";
//static String fileName = "/home/user/DCtests/SSLcover2800000.txt";
//static String fileName = "/home/user/DCtest.t)xt";
//static String fileName = "/home/user/DCtests/SSLlocalDC.txt";
//static String fileName = fileLocation+"Tests/dc4cpu2apple100000.txt";
//static String fileName = fileLocation+"Tests/dc4cpu1apple200000.txt";
//static String fileName = fileLocation+"Tests/test1.txt";
//static String fileName = fileLocation+"Channels/multiChannel.txt";
//
// Conditional channels
//
//static String fileName = fileLocation+"Channels/DiningCryptos/dc3nopay.txt";
//
//Mixes
//
// Theshold Mixes
//static String fileName = fileLocation+"Channels/Mixes/Thesholds3r3n2.txt";
//static String fileName = fileLocation+"Channels/Mixes/Thesholds3r3n2Conditional.txt";
//static String fileName = fileLocation+"Channels/Mixes/Thesholds3r3n2condSender";
//Pool Mixes
//static String fileName = fileLocation+"Channels/Mixes/Pools3r3n2p1Once.txt";
//static String fileName = fileLocation+"Channels/Mixes/Pools3r3n2p1OnceCondSender.txt";
//static String fileName = "/home/staff/tpc/AE/workspace/Data/Tests/dc4cpu2apple40000.txt";
//static String fileName ="/home/staff/tpc/AE/workspace/Data/Tests/dc4cpu1apple40000.txt";
//static String fileName ="/home/staff/tpc/50000_0";
static String fileName = "unset"; // this is set by the commandline
public static void main(String[] args) {
//
// Read the commandline arguments
//
if ((args.length==0)|| (args.length > 0 && (args[0].equals("help") || args[0].equals("h") || args[0].equals("-h") || args[0].equals("-help"))))
{
System.out.println("Anonymity Engine\n This is a program to calculate the anonymity of a system from\n either a matrix relating the inputs and outputs or a list of\n observations of the systems.");
System.out.println("To run type: java -jar ae.jar <fileName> <options>");
System.out.println(" where <fileName> is a text file of observations or a channel matrix");
System.out.println("Options:\n -h,help print this message \n -f <fileName> or file <fileName>, read input from file <fileName>");
System.out.println(" -v set the level of information shown (0 to 4) e.g. -v 4");
System.out.println(" -o process an observations file");
System.out.println(" -c process a channel file");
System.out.println(" -e set the acceptable error level e.g. -e 0.0000001");
System.out.println(" -i set the maximum number of interations e.g. -i 500");
System.out.println(" -mi calculate mutual information with uniform distribution");
System.out.println(" (rather than capacity)");
System.exit(0);
}
int clc = 0;
while (clc < args.length)
{
if (args[clc].equalsIgnoreCase("-f") || args[clc].equalsIgnoreCase("file"))
{
if (clc < args.length+1) { fileName = args[clc+1]; }
clc = clc +2;
} else if (args[clc].equalsIgnoreCase("-v")) {
verbose = Integer.parseInt(args[clc+1]);;
clc = clc + 2;
} else if (args[clc].equalsIgnoreCase("-o")) {
readFromObsFile = true;
clc = clc + 1;
} else if (args[clc].equalsIgnoreCase("-e")) {
acceptableError = Double.parseDouble(args[clc+1]);
clc = clc + 2;
} else if (args[clc].equalsIgnoreCase("-i")) {
noOfIterations = Integer.parseInt(args[clc+1]);
clc = clc + 2;
} else if (args[clc].equalsIgnoreCase("-c")) {
readFromChanFile = true;
clc = clc + 1;
} else if (args[clc].equalsIgnoreCase("-mi")) {
miUniform = true;
clc = clc + 1;
} else if (fileName.equals("unset")) {
fileName = args[clc];
clc = clc + 1;
} else {
System.out.println("Unrecognised commandline option: "+args[clc]);
System.out.println(" Skipping it. Use -h or help for a list of options");
clc = clc + 1;
}
}
// If it hasn't been specified on the command line
// find out if the file is a matrix or observations.
if (!readFromChanFile && !readFromObsFile) {
try {
BufferedReader reader = new BufferedReader(new FileReader(fileName));
String line = reader.readLine();
while ( (line.trim()).equalsIgnoreCase("") || (line.trim()).startsWith("//"))
{ line = reader.readLine(); }
Pattern patternObs = Pattern.compile("\\([\\s]*([\\w:]+)[\\s]*,[\\s]*([\\w:]+)[\\s]*\\)[\\s]*$");
Matcher matcherObs = patternObs.matcher(line.trim());;
if (matcherObs.find()) {
if (verbose>0) { System.out.println("Reading file: " +fileName+" as an observation file\n"); }
readFromObsFile = true;
} else {
if (verbose>0) { System.out.println("Reading file: " +fileName+" as a matrix file"); }
readFromChanFile = true;
}
reader.close();
} catch (IOException e){ System.out.println(e+ " while trying to read file: "+fileName);System.exit(1); }
}
//
// Read in the channel file
//
// reading the channel from an observations file
if (readFromObsFile)
{
ReadFile obsReader = new ReadFile(fileName);
obsReader.readObservations();
obs = obsReader.getObservations();
channel = obs.generateChannel();
sampleSize = obs.noOfTests;
if (verbose >2) {
System.out.println("These observations lead to the following channel matrix, to 4 decimal places:");
channel.printChannel();
}
//channel.printSaveChannel("/home/user/matrix.txt");
if (verbose >3) {
System.out.println("\nThe largest "+ obs.certainty*100+"% confidence interval for any entry, to 4 decimal places, is "+Stats.round((2*obs.largestInterval()),4));
//System.out.println("\n With certainty: "+ obs.totalCertainty()+" the largest confidence interval is: "+(2*obs.largestInterval()));
//System.out.println("\n Max error ratio is: "+ obs.maxErrorRatio()+" the min error ratio is :"+ obs.minErrorRatio());
}
}
// reading the channel from a channel file
if (readFromChanFile)
{
// Read the channel matrix using the ReadChannelFile Class
ReadFile channelFileReader = new ReadFile(fileName);
channelFileReader.readChannel();
channel = channelFileReader.getChannel();
}
//
// Calculate the Capacity
//
// Switch on the kind of channel and calculate capacity accordingly.
// Channels can be basic, conditional or multi-user.
BlahutArimoto ba = new BlahutArimoto(channel,acceptableError,noOfIterations);
double result = 0.0;
switch (channel.kind)
{
case (Channel.BASIC):
// Find capacity or mutual information of a basic channel
if (miUniform==true) {
result = IT.MIuniformInput(channel.getMatrix());
inputDist = IT.unifromDist(channel.noOfInputs());
} else {
ba.calculateCapacity();
result = ba.getCapacity();
inputDist = ba.getMaxInputDist();
}
// Print the output, depending on the value of verbose
// the higher "verbose" the more details given.
if (readFromObsFile)
{
//if (verbose > 2) {
// System.out.print(" Maximising Input distribution estimated to be:\n ");
// IT.printPMF(ba.inputNames,ba.inputPMF_Q);
// System.out.print("\n");
//}
if (verbose>4) {
System.out.print("\nWith certainty: "+ Stats.round(obs.totalCertainty(),4)+" the attacker learns between ");
System.out.print(Stats.round(Math.max(0,(obs.minErrorRatio() * result) + IT.log2( obs.minErrorRatio() / obs.maxErrorRatio())),4));
System.out.print(" and ");
System.out.print(Stats.round((obs.maxErrorRatio() * result) + IT.log2( obs.maxErrorRatio() / obs.minErrorRatio()),4));
System.out.println(" bits about the users");
}
double correction = (double)(channel.noOfInputs()-1)*(double)(channel.noOfOutputs()-1)/(double)(2*sampleSize)*IT.log2(Math.E);
if (verbose>2) {
System.out.println("\n"+channel.noOfInputs()+" inputs, "+channel.noOfOutputs()+" outputs and "+sampleSize+" samples");
System.out.println(" estimate result = "+Stats.round(result,4));
System.out.println(" correction = log_2(e).(noOfInputs-1)(noOfOutputs-1)/2.sampleSize = "+ Stats.round(correction,4));
System.out.println(" The results are no more accurate that the correction value,\n increase the sample size to decrease the correction.\n Calculations are to 95% confidence.");
}
if (verbose > 5) {
System.out.print("Maximising Input distribution estimated to be:\n ");
IT.printPMF(ba.inputNames,ba.inputPMF_Q);
System.out.print("\n");
}
if (miUniform == false) {
System.out.println(" The estimated capacity is "+Stats.round(result,4)+" - "+Stats.round(correction, 4)+" = "+Stats.round(result-correction,4));
} else {
System.out.println(" The mutual information is "+Stats.round(result,4)+" - "+Stats.round(correction, 4)+" = "+Stats.round(result-correction,4));
}
double zeroUpperBound = Stats.upperBoundForZero( (channel.noOfInputs()-1)*(channel.noOfOutputs()-1),sampleSize );
System.out.println(" The 95% confidence interval for zero information leakage is :"+Stats.round(zeroUpperBound,4));
double mean = result-correction;
double variance = Stats.nonZeroVariance(inputDist, channel.getMatrix(), sampleSize);
if (result<=zeroUpperBound) {
System.out.println(" This is consistent with the chi^2 distribution for zero leakage.");
System.out.println("Capacity is between 0 and "+ Math.max(0,Stats.round(Stats.upperBoundNormal95(mean, variance),4)));
System.out.println("");
} else {
System.out.println(" This is not consistent with the chi^2 distribution for zero leakage.");
System.out.printf(" The estimated leakage is has mean "+ Stats.round(mean,4)+" and variance %1$6.5g",variance);
System.out.println("\nCapacity is between "+Math.max(0,Stats.round(Stats.lowerBoundNormal95(mean, variance),4))+" and "+ Math.max(0,Stats.round(Stats.upperBoundNormal95(mean, variance),4)));
System.out.println("");
}
//if (result == 1)
//{ System.out.println("\nThe attacker learns 1 bit about the users"); }
//else
//{ System.out.println("\nThe attacker learns "+result+" bits about the users"); }
} else { // Print output for a processed matrix, e.g. no error info.
if (miUniform == false) {
if (verbose > 1) {
if (ba.possibleError == 0) {
System.out.println(" Complete, after "+ba.iteration+" iterations");
} else if (ba.possibleError <= ba.acceptableError) {
System.out.println(" Capacity calculated to within acceptable error, in "+ba.iteration+" iterations");
} else if (ba.possibleError > ba.acceptableError) {
System.out.println(" NOT COMPLETE: Performed the maximum number of iterations: "+ba.iteration+"\n"+
" and still not with acceptable error rate\n " +
" increase the maximum number of iterations (with flag -i <int>)\n" +
" or increase the acceptable error (with flag -e <double>");
}
System.out.printf(" The Channel Capacity is: %1$6.5g +/- %2$6.5g",(result+(ba.possibleError/2)),(ba.possibleError/2));
System.out.printf(" and there are "+channel.noOfInputs()+" possible input events\n");
}
if (verbose > 2) {
System.out.print(" Maximising Input distribution estimated to be:\n ");
IT.printPMF(ba.inputNames,ba.inputPMF_Q);
System.out.print("\n\n");
}
} else { // then using MI not capacity.
System.out.println("Calculating the mutual information with the uniform distribution");
}
if (result == 1)
{ System.out.println("The attacker learns 1 bit"); }
else
{ System.out.printf("The attacker learns %6.5g bits",result);}
System.out.printf(", out of a possible %6.5g bits, about the input events.",IT.log2(channel.noOfInputs()));
System.out.print("\n");
}
break;
// The channel has multiple users that can send at the same time
// so we use network information theory to find the worst case
case (Channel.MULTI):
// find the capacity for each of the inputs in turn.
double[] capResults = new double[channel.noOfInputs()];
boolean allAccurate = true;
for (int inputIndex=0;inputIndex<channel.noOfInputs();inputIndex++)
{
boolean accurateResults = true;
double maxCapSoFar = -1;
BlahutArimoto maxResultsSoFar = new BlahutArimoto();
for (int rowCounter =0; rowCounter<channel.noOfRows();rowCounter++)
{
if (( channel.getInputRow(rowCounter)).contains(new Integer(inputIndex)))
{
// row rowCounter contains an input we are currently looking at
// so find the matching row without that input and make a matrix
Vector matchingRow = (Vector)(channel.getInputRow(rowCounter)).clone();
matchingRow.remove(new Integer(inputIndex));
int matchingIndex = 0;
boolean found = false;
while (matchingIndex< channel.noOfRows() && !found)
{
Vector inputRowInfor = channel.getInputRow(matchingIndex);
if (matchingRow.containsAll(inputRowInfor) && inputRowInfor.containsAll(matchingRow))
{
found = true;
}
else
{
matchingIndex++;
}
}
if (!found)
{
System.out.println("Syntax error in multi channel file \n all input combinations must be present");
System.exit(0);
}
// Make a new channel object for just this test
double[][] subChannelMatrix = new double[2][channel.noOfOutputs()];
for (int collNo=0;collNo<channel.noOfOutputs();collNo++)
{
subChannelMatrix[0][collNo] = channel.prob(rowCounter,collNo);
}
for (int collNo=0;collNo<channel.noOfOutputs();collNo++)
{
subChannelMatrix[1][collNo] = channel.prob(matchingIndex,collNo);
}
System.out.println(" \n\n For inputIndex "+inputIndex+" rows "+rowCounter+" and "+matchingIndex+" match \n");
String[] subInputNames = new String[2];
subInputNames[0] = channel.getRowLabel(rowCounter);
subInputNames[1] = channel.getRowLabel(matchingIndex);
Channel subChannel = new Channel(Channel.BASIC,subInputNames,channel.getOutputNames(),subChannelMatrix);
subChannel.printChannel();
//double[] subInputPMF_Q ={0.5, 0.5};
ba = new BlahutArimoto(subChannel,acceptableError,noOfIterations);
if (ba.calculateCapacity() > maxCapSoFar)
{
maxCapSoFar = ba.getCapacity();
maxResultsSoFar = ba;
if (ba.getpossibleError()> acceptableError)
{
accurateResults = false;
System.out.println("This result is not within the acceptable error level");
}
}
System.out.println(" \n has cap: "+ba.getCapacity());
}
}
System.out.println("The max cap. for "+channel.getInputName(inputIndex)+" is "+maxResultsSoFar.getCapacity());
if (!accurateResults)
{
System.out.println("BUT this result is not within the acceptable error level.");
System.out.println("Please increase the number of iterations or raise the acceptable error level");
allAccurate = false;
}
capResults[inputIndex] = maxResultsSoFar.getCapacity();
}
// Print the results for each input.
System.out.println("\n\n");
for (int i = 0;i<capResults.length;i++)
{
if (capResults[i] == 1)
{
System.out.println("The attacker learns 1 bit about user "+channel.getInputName(i));
}
else
{
System.out.println("The attacker learns "+capResults[i]+" bits about user "+channel.getInputName(i));
}
}
if (!allAccurate) { System.out.println("HOWEVER these results are not with the acceptable error level");}
break;
case(Channel.COND):
System.out.println("It's a cond channel with:");
String[] groupNames = channel.getGroupNames();
double[] capForGroup = new double[groupNames.length];
double overallCap = 0;
for (int i=0;i<groupNames.length;i++)
{
System.out.println("\nInput group "+groupNames[i]+" has the submatrix:" );
// For the vector of rows in group i
Vector keyRows = channel.getRowsForGroup()[i];
// Create the submatrix for groupName[i]
String [] subInputNames= new String[keyRows.size()];
double[][] subChannelMatrix = new double[keyRows.size()][channel.noOfOutputs()];
for(int j=0;j<keyRows.size();j++)
{
subInputNames[j] = channel.getInputName(((Integer)keyRows.get(j)).intValue());
for (int cols=0;cols<channel.noOfOutputs();cols++)
{
subChannelMatrix[j][cols] = channel.prob(((Integer)keyRows.get(j)).intValue(), cols);
}
}
double[] subInputPMF_Q = new double[keyRows.size()];
for (int input=0;input<keyRows.size();input++)
{
subInputPMF_Q[input] = (1.0/(double)keyRows.size());
}
Channel subChannel = new Channel(Channel.BASIC,subInputNames,channel.getOutputNames(),subChannelMatrix);
subChannel.printChannel();
ba = new BlahutArimoto(subChannel,acceptableError,noOfIterations);
System.out.println("\n The capacity for group "+groupNames[i]+" is:");
capForGroup[i] = ba.calculateCapacity();
overallCap = Math.max(overallCap, capForGroup[i]);
}
System.out.println("\n\n\n\n\nThe relative loss of anonymity is: "+overallCap);
result = overallCap;
break;
}
//return result;
}
// Take a file name and calculate the mutual information
// for a uniform input distribution.
public static double obsToMIuniformInput(String fileName) {
ReadFile obsReader = new ReadFile(fileName);
obsReader.readObservations();
return IT.MIuniformInput(obsReader.getObservations().generateChannel().getMatrix());
}
// Take a file name and calculate the point estimate of capacity.
public static double obsToCapacity(String fileName) {
return(obsToCapacity(fileName,0.00001,5000));
}
// Take a file name and calculate the point estimate of capacity
// to with some BA error or maximum number of iterations.
public static double obsToCapacity(String fileName, double acceptableError, int noOfIterations) {
ReadFile obsReader = new ReadFile(fileName);
obsReader.readObservations();
BlahutArimoto ba = new BlahutArimoto(obsReader.getObservations().generateChannel(),acceptableError,noOfIterations);
ba.calculateCapacity();
obsReader.getObservations().generateChannel().printSaveChannel("/home/staff/tpc/matrix.txt");
return (ba.getCapacity());
}
@SuppressWarnings("unchecked")
public static Vector removeIndex (int index, Vector combinations)
{
int i = 0;
while(i<combinations.size())
{
if ( ((Vector)combinations.get(i)).contains(new Integer(index)) )
{ combinations.remove(i); }
else
{ i++; }
}
return combinations;
}
@SuppressWarnings("unchecked")
public static Vector<Vector<Integer>> allCombinations (int fromIndex, int noOfInputs)
{
Vector<Vector<Integer>> results = new Vector<Vector<Integer>>();
if (fromIndex == noOfInputs-1)
{
Vector<Integer> trueCase = new Vector<Integer>();
trueCase.add(new Integer(fromIndex));
Vector<Integer> falseCase = new Vector<Integer>();
results.add(trueCase);
results.add(falseCase);
}
if (fromIndex < noOfInputs-1)
{
Vector<Vector<Integer>> resultsSoFar = allCombinations (fromIndex+1, noOfInputs);
for (Iterator<Vector<Integer>> it = resultsSoFar.iterator(); it.hasNext(); )
{
Vector<Integer> result = it.next();
results.add((Vector<Integer>)result.clone());
result.add(new Integer(fromIndex));
results.add((Vector<Integer>)result.clone());
}
}
return results;
}
}