diff --git a/assignment1/test/IsValidTest.java b/assignment1/test/IsValidTest.java new file mode 100644 index 0000000..de6a461 --- /dev/null +++ b/assignment1/test/IsValidTest.java @@ -0,0 +1,191 @@ +// Copyright (C) 2016-2017 Enrique Albertos +// Distributed under the GNU GPL v2 software license + + +import static org.junit.Assert.assertEquals; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; + +import org.junit.Test; +/** + * Unit tests for {@link TxHandler#isValidTx(Transaction)} + *

+ * Test Strategy: + * Test 1: test isValidTx() with valid transactions + * Test 2: test isValidTx() with transactions containing signatures of incorrect data + * Test 3: test isValidTx() with transactions containing signatures using incorrect private keys + * Test 4: test isValidTx() with transactions whose total output value exceeds total input value + * Test 5: test isValidTx() with transactions that claim outputs not in the current utxoPool + * Test 6: test isValidTx() with transactions that claim the same UTXO multiple times + * Test 7: test isValidTx() with transactions that contain a negative output value + * + * @author ealbertos + * + */ + +public class IsValidTest { + + private static void assertTestSetIsValid(final UtxoTestSet utxoTestSet) { + final ValidationLists trxsValidation = utxoTestSet.getValidationLists(); + + // Instantiate student solution + final TxHandler txHandler = new TxHandler(utxoTestSet.getUtxoPool()); + + // Check validation of all the transactions in the set + for (Transaction tx: trxsValidation.allElements()) { + assertEquals(txHandler.isValidTx(tx), trxsValidation.isValid(tx) ); + } + } + + // Test 1: test isValidTx() with valid transactions + @Test + public void testIsValidWithValidTransactions() + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + // Create a new set of transactions for testing + final UtxoTestSet utxoTestSet = UtxoTestSet.builder() + .setPeopleSize(10) + .setUtxoTxNumber(10) + .setMaxUtxoTxOutput(10) + .setMaxValue(200) + .setTxPerTest(10) + .setMaxInput(10) + .setMaxOutput(10) + .setCorruptedPercentage(0) // All valid transactions + .build(); + // check against student solution + assertTestSetIsValid(utxoTestSet); + + } + + + // Test 2: test isValidTx() with transactions containing signatures of incorrect data + @Test + public void testIsValidWithInvalidSignatures() throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + // Create a new set of transactions for testing + final UtxoTestSet utxoTestSet = UtxoTestSet.builder() + .setPeopleSize(10) + .setUtxoTxNumber(10) + .setMaxUtxoTxOutput(10) + .setMaxValue(200) + .setTxPerTest(10) + .setMaxInput(10) + .setMaxOutput(10) + .setForceCorruptedSignature(true) + .setCorruptedPercentage(.20) // probability of 20% of invalid transactions + .build(); + + // check against student solution + assertTestSetIsValid(utxoTestSet); + + } + + // Test 3: test isValidTx() with transactions containing signatures using incorrect private keys + @Test + public void testIsValidSignaturesWithInvalidPrivateKeys() throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + // Create a new set of transactions for testing + final UtxoTestSet utxoTestSet = UtxoTestSet.builder() + .setPeopleSize(10) + .setUtxoTxNumber(10) + .setMaxUtxoTxOutput(10) + .setMaxValue(200) + .setTxPerTest(10) + .setMaxInput(10) + .setMaxOutput(10) + .setInvalidPrivateKeys(true) // corrupt the private key that signs + .setCorruptedPercentage(.20) // probability of 20% of invalid transactions + .build(); + + // check against student solution + assertTestSetIsValid(utxoTestSet); + + } + + // Test 4: test isValidTx() with transactions whose total output value exceeds total input value + @Test + public void testIsValidTotalOutputExceedsTotalInput() throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + // Create a new set of transactions for testing + final UtxoTestSet utxoTestSet = UtxoTestSet.builder() + .setPeopleSize(10) + .setUtxoTxNumber(10) + .setMaxUtxoTxOutput(10) + .setMaxValue(200) + .setTxPerTest(10) + .setMaxInput(10) + .setMaxOutput(10) + .setInvalidTotals(true) // create transactions with invalid total value + .setCorruptedPercentage(.20) // probability of 20% of invalid transactions + .build(); + + // check against student solution + assertTestSetIsValid(utxoTestSet); + + } + + + // Test 5: test isValidTx() with transactions that claim outputs not in the current utxoPool + @Test + public void testIsValidTransactionsClamingOuputsNotInThePool() throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + // Create a new set of transactions for testing + final UtxoTestSet utxoTestSet = UtxoTestSet.builder() + .setPeopleSize(10) + .setUtxoTxNumber(10) + .setMaxUtxoTxOutput(10) + .setMaxValue(200) + .setTxPerTest(10) + .setMaxInput(10) + .setMaxOutput(10) + .setClaimingOutputsNotInPool(true) // create transactions claiming outputs not in the pool + .setCorruptedPercentage(.20) // probability of 20% of invalid transactions + .build(); + + // check against student solution + assertTestSetIsValid(utxoTestSet); + + } + + // Test 6: test isValidTx() with transactions that claim the same UTXO multiple times + @Test + public void testIsValidTransactionsClaimingTheSameUTXOSeveralTimes() throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + // Create a new set of transactions for testing + final UtxoTestSet utxoTestSet = UtxoTestSet.builder() + .setPeopleSize(10) + .setUtxoTxNumber(10) + .setMaxUtxoTxOutput(10) + .setMaxValue(200) + .setTxPerTest(10) + .setMaxInput(10) + .setMaxOutput(10) + .setClaimingUtxoSeveralTimes(true) // create transactions claiming the same output several times + .setCorruptedPercentage(.20) // probability of 20% of invalid transactions + .build(); + + assertTestSetIsValid(utxoTestSet); + + } + + // Test 7: test isValidTx() with transactions that contain a negative output value + @Test + public void testIsValidTransactionsWithNegativeOutput() throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + // Create a new set of transactions for testing + final UtxoTestSet utxoTestSet = UtxoTestSet.builder() + .setPeopleSize(10) + .setUtxoTxNumber(10) + .setMaxUtxoTxOutput(10) + .setMaxValue(200) + .setTxPerTest(10) + .setMaxInput(10) + .setMaxOutput(10) + .setNegativeOutputs(true) // create transactions with negative values + .setCorruptedPercentage(.20) // probability of 20% of invalid transactions + .build(); + + assertTestSetIsValid(utxoTestSet); + + } + + + + +} diff --git a/assignment1/test/UtxoTestSet.java b/assignment1/test/UtxoTestSet.java new file mode 100644 index 0000000..b5319aa --- /dev/null +++ b/assignment1/test/UtxoTestSet.java @@ -0,0 +1,511 @@ +// Copyright (C) 2016-2017 Enrique Albertos +// Distributed under the GNU GPL v2 software license + +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; + +/** + * UtxoTestSet represent a random created data test set for testing isValid and txHandler methods. + * Creates an UtxPool ans several transactions, valid, invalid or conflicted + * Several flags force different types of transactions + * + * A UtxoTestSet can be + * constructed wit by means of a builder: + * + *

+ * UtxoTestSet.builder()
+ * 	.setPeopleSize(10)
+ * 	.setUtxoTxNumber(10)
+ * 	.setMaxUtxoTxOutput(10)
+ * 	.setMaxValue(200)
+ * 	.setTxPerTest(10)
+ * 	.setMaxInput(10)
+ * 	.setMaxOutput(10)
+ * 	.setCorruptedPercentage(0)
+ * 	.build();
+ * 
+ * + * @author ealbertos + * + */ +public class UtxoTestSet { + + /** + * Factory method that constructs a new builder of UtxoTestSet + * @return a new builder + */ + public static UtxoTestSetBuilder builder(){ + return new UtxoTestSetBuilder(); + } + + /** + * Validation list contains list with valid transactions, invalid transactions and conflicted transactions + * @return the validationLists + */ + public ValidationLists getValidationLists() { + return new ValidationLists<>(validationLists); + } + + /** + * Return the created Utxo Pool + * @return a copy of the pool + */ + public UTXOPool getUtxoPool() { + return new UTXOPool(utxoPool); + } + + + /** + * Builder for UtxoTestSet + * @author ealbertos + * + */ + static class UtxoTestSetBuilder { + private int peopleSize; + private int utxoTxNumber; + private int maxUtxoTxOutput; + private double maxValue; + private int txNumberPerTest; + private int maxInputs; + private int maxOutputs; + private double corruptedPercentage = 0D; + private boolean isForceInvalidPrivateKeys = false; + private boolean isForceInvalidTotals = false; + private boolean isClaimingOutputsNotInPool = false; + private boolean isForceCorruptedSignature = false; + private boolean isClaimingUtxoSeveralTimes = false; + private boolean isForceNegativeOutputs = false; + + /** + * Number of different people address in the test set + * @param peopleNumber the number of different people address in the test set + * @return this builder + */ + public UtxoTestSetBuilder setPeopleSize(final int peopleNumber) { + this.peopleSize = peopleNumber; + return this; + } + + /** + * Number of utxo in the pool to create for the set, the same number is created for the extraPool set + * Extra Pool set is used to create transactions tha use UTXO no in the actual pool + * @param utxoTxNumber number of utxo in the pool to create for the set + * @return this builder + */ + public UtxoTestSetBuilder setUtxoTxNumber(final int utxoTxNumber) { + this.utxoTxNumber = utxoTxNumber; + return this; + } + + /** + * Max Number of utxo per output in trx created + * @param maxUtxoTxOutput + * @return this builder + */ + public UtxoTestSetBuilder setMaxUtxoTxOutput(final int maxUtxoTxOutput) { + this.maxUtxoTxOutput = maxUtxoTxOutput; + return this; + } + + /** + * Max coin value per transaction, can be exceed in invalid transactions + * @param maxValue + * @return this builder + */ + public UtxoTestSetBuilder setMaxValue(final double maxValue) { + this.maxValue = maxValue; + return this; + } + + /** + * number of tx per test + * @param txNumberPerTest number of tx per test + * @return this builder + */ + public UtxoTestSetBuilder setTxPerTest(final int txNumberPerTest) { + this.txNumberPerTest = txNumberPerTest; + return this; + } + + /** + * Max number of inputs + * @param maxInputs max number of inputs in transactions created + * @return + */ + public UtxoTestSetBuilder setMaxInput(int maxInputs) { + this.maxInputs = maxInputs; + return this; + } + + /** + * set the max number of outputs in transactions created + * @param maxOutputs max number of outputs in transactions created + * @return this builder + */ + public UtxoTestSetBuilder setMaxOutput(int maxOutputs) { + this.maxOutputs = maxOutputs; + return this; + } + + /** + * Set the percentage of corrupted transactions + * @param corruptedPercentage 0 to 1 + * @return this builder + */ + public UtxoTestSetBuilder setCorruptedPercentage(double corruptedPercentage) { + if(corruptedPercentage > 1 || corruptedPercentage < 0) { + throw new IllegalArgumentException("Percentage value must be in the range (0-1)"); + } + this.corruptedPercentage = corruptedPercentage; + return this; + } + + /** + * Create transactions containing signatures using incorrect private keys + * @param value + * @return this builder + */ + public UtxoTestSetBuilder setInvalidPrivateKeys(boolean value) { + isForceInvalidPrivateKeys = value; + return this; + } + + /** + * Create transactions whose total output value exceeds total input value + * @param value True force creation + * @return this builder + */ + public UtxoTestSetBuilder setInvalidTotals(boolean value) { + isForceInvalidTotals = value; + return this; + } + + /** + * Create transactions that claim outputs not in the current utxoPool + * @param value True force creation + * @return this builder + */ + public UtxoTestSetBuilder setClaimingOutputsNotInPool(boolean value) { + isClaimingOutputsNotInPool = value; + return this; + } + + /** + * Create transactions containing signatures of incorrect data + * @param value True force creation + * @return this builder + */ + public UtxoTestSetBuilder setForceCorruptedSignature(boolean value) { + isForceCorruptedSignature = value; + return this; + } + + /** + * Create transactions that claim the same UTXO multiple times + * @param value True force creation + * @return this builder + */ + public UtxoTestSetBuilder setClaimingUtxoSeveralTimes(boolean value) { + isClaimingUtxoSeveralTimes = value; + return this; + } + + /** + * Create transactions that contain a negative output value + * @param value True force creation + * @return this builder + */ + public UtxoTestSetBuilder setNegativeOutputs(boolean value) { + isForceNegativeOutputs = value; + return this; + } + + public UtxoTestSet build() throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + return new UtxoTestSet(peopleSize, + utxoTxNumber, + maxUtxoTxOutput, + maxValue, + txNumberPerTest, + maxInputs, + maxOutputs, + corruptedPercentage, + isForceInvalidPrivateKeys, + isForceInvalidTotals, + isClaimingOutputsNotInPool, + isForceCorruptedSignature, + isClaimingUtxoSeveralTimes, + isForceNegativeOutputs); + } + + + } + + private final UTXOPool utxoPool; + private final List people; + private final Map utxoToKeyPair; + + private final UTXOPool utxoExtraPool; + private final List peopleExtra; + + private final int maxInputs; + private final int maxOutputs; + private final int txNumberPerTest; + private final double corruptedPercentage; + private final boolean isForceInvalidPrivateKeys; + private final boolean isForceInvalidTotals; + private final boolean isClaimingOutputsNotInPool; + private final boolean isForceCorruptedSignature; + private final boolean isClaimingUtxoSeveralTimes; + private final double maxValue; + private final ValidationLists validationLists; + private final ThreadLocalRandom random; + private final boolean isForceNegativeOutputs; + + + /** + * Private construct, force the creation of set with the builder + * @param peopleSize + * @param utxoTxNumber + * @param maxUtxoTxOutput + * @param maxValue + * @param txNumberPerTest + * @param maxInputs + * @param maxOutputs + * @param corruptedPercentage + * @param isForceInvalidPrivateKeys + * @param isForceInvalidTotals + * @param isClaimingOutputsNotInPool + * @param isForceCorruptedSignature + * @param isClaimingUtxoSeveralTimes + * @param isForceNegativeOutputs + * @throws NoSuchAlgorithmException + * @throws InvalidKeyException + * @throws SignatureException + */ + private UtxoTestSet(int peopleSize, int utxoTxNumber, int maxUtxoTxOutput, double maxValue, int txNumberPerTest, + int maxInputs, int maxOutputs, double corruptedPercentage, boolean isForceInvalidPrivateKeys, + boolean isForceInvalidTotals, boolean isClaimingOutputsNotInPool, boolean isForceCorruptedSignature, + boolean isClaimingUtxoSeveralTimes, boolean isForceNegativeOutputs) + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + this.txNumberPerTest = txNumberPerTest; + this.maxInputs = maxInputs; + this.maxOutputs = maxOutputs; + this.corruptedPercentage = corruptedPercentage; + this.isForceInvalidPrivateKeys = isForceInvalidPrivateKeys; + this.isForceInvalidTotals = isForceInvalidTotals; + this.isClaimingOutputsNotInPool = isClaimingOutputsNotInPool; + this.isForceCorruptedSignature = isForceCorruptedSignature; + this.isClaimingUtxoSeveralTimes = isClaimingUtxoSeveralTimes; + this.isForceNegativeOutputs = isForceNegativeOutputs; + this.maxValue = maxValue; + this.random = ThreadLocalRandom.current(); + + people = createPeopleAddresses(peopleSize); + utxoToKeyPair = new HashMap<>(); + utxoPool = createUtxoPool(people, utxoTxNumber, maxUtxoTxOutput, maxValue, utxoToKeyPair); + + peopleExtra = createPeopleAddresses(peopleSize); + utxoExtraPool = createUtxoPool(peopleExtra, utxoTxNumber, maxUtxoTxOutput, maxValue, utxoToKeyPair); + + validationLists = generateTrxWithCorruptedSignaturePercentage(); + + } + + private UTXOPool createUtxoPool(List people, int utxoTxNumber, int maxUtxoTxOutput, double maxValue, + Map utxoToKeyPair) { + final UTXOPool utxoPool = new UTXOPool(); + Map keyPairAtIndex = new HashMap<>(); + + for (int i = 0; i < utxoTxNumber; i++) { + int num = maxUtxoTxOutput; + Transaction tx = createTxWithOutputs(people, maxValue, keyPairAtIndex, num); + // add all tx outputs to utxo pool + addTxOutputsToPool(utxoPool, keyPairAtIndex, utxoToKeyPair, num, tx); + } + return utxoPool; + } + + private Transaction createTxWithOutputs(List people, double maxValue, Map keyPairAtIndex, int num) { + final Transaction tx = new Transaction(); + for (int j = 0; j < num; j++) { + // pick a random public address + int rIndex = random.nextInt(people.size()); + PublicKey addr = people.get(rIndex).getPublic(); + double value = random.nextDouble(maxValue); + tx.addOutput(value, addr); + keyPairAtIndex.put(j, people.get(rIndex)); + } + tx.finalize(); + return tx; + } + + private void addTxOutputsToPool(UTXOPool utxoPool, Map keyPairAtIndex, + Map utxoToKeyPair, + int numTx, Transaction tx) { + for (int j = 0; j < numTx; j++) { + UTXO ut = new UTXO(tx.getHash(), j); + utxoPool.addUTXO(ut, tx.getOutput(j)); + utxoToKeyPair.put(ut, keyPairAtIndex.get(j)); + } + } + + private List createPeopleAddresses(int peopleSize) throws NoSuchAlgorithmException { + final List people = new ArrayList<>(); + for (int i = 0; i < peopleSize; i++) + people.add(KeyPairGenerator.getInstance("RSA").generateKeyPair()); + return Collections.unmodifiableList(people); + } + + private PublicKey getAddress(int rIndex) { + return people.get(rIndex).getPublic(); + } + + private PrivateKey getPrivate(UTXO utxo) { + return utxoToKeyPair.get(utxo).getPrivate(); + } + + private KeyPair getKeyPair(UTXO utxo) { + return utxoToKeyPair.get(utxo); + } + + private ArrayList getAllUTXO(UTXOPool pool) { + return pool.getAllUTXO(); + } + + + private byte[] sign(PrivateKey privateKey, byte[] rawDataToSign) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + Signature sig = Signature.getInstance("SHA256withRSA"); + sig.initSign(privateKey); + sig.update(rawDataToSign); + return sig.sign(); + } + + + + private ValidationLists generateTrxWithCorruptedSignaturePercentage() + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + final ArrayList utxoList = getAllUTXO(utxoPool); + final ArrayList utxoExtraList = getAllUTXO(utxoExtraPool); + //final UTXOPool utxoPool = new UTXOPool(); + final Map utxoAtIndex = new HashMap<>(); + final Set utxosSeen = new HashSet<>(); + final Set utxosToRepeat = new HashSet<>(); + + // create validationLists + final List valid = new ArrayList<>(); + final List invalid = new ArrayList<>(); + final List conflicted = new ArrayList<>(); + for (int i = 0; i < txNumberPerTest; i++) { + boolean corrupted = false; + Transaction tx = new Transaction(); + + final int nInputs = random.nextInt(maxInputs) + 1; + final int nOutputs = random.nextInt(maxOutputs) + 1; + + // create inputs + double inputValue = 0; + for (int j = 0; j < nInputs; j++) { + UTXO utxo = null; + if (isClaimingOutputsNotInPool && isRandomSelection()) { + do { + utxo = utxoExtraList.get(random.nextInt(utxoExtraList.size())); + } while (!utxosSeen.add(utxo)); + inputValue += utxoExtraPool.getTxOutput(utxo).value; + corrupted = true; + } else { + do { + utxo = utxoList.get(random.nextInt(utxoList.size())); + } while (!utxosSeen.add(utxo)); + inputValue += utxoPool.getTxOutput(utxo).value; + if (isClaimingUtxoSeveralTimes && isRandomSelection()) { + utxosToRepeat.add(utxo); + corrupted = true; + } + } + tx.addInput(utxo.getTxHash(), utxo.getIndex()); + utxoAtIndex.put(j, utxo); + } + + int count = 0; + for (UTXO utxo : utxosToRepeat) { + tx.addInput(utxo.getTxHash(), utxo.getIndex()); + inputValue += utxoPool.getTxOutput(utxo).value; + utxoAtIndex.put(nInputs + count, utxo); + count++; + } + + // create outpus + double outputValue = 0; + for (int j = 0; j < nOutputs; j++) { + double value; + if ((isForceInvalidTotals && isRandomSelection()) + || outputValue > inputValue) { + value = random.nextDouble(maxValue); + } else { + value = random.nextDouble(inputValue - outputValue); + if (isForceNegativeOutputs && isRandomSelection()) { + value = -value; + corrupted = true; + } + } + + tx.addOutput(value, getAddress(random.nextInt(people.size()))); + outputValue += value; + } + corrupted |= (outputValue > inputValue); + + // sign transaction + for (int j = 0; j < nInputs + utxosToRepeat.size(); j++) { + byte[] rawData = tx.getRawDataToSign(j); + PrivateKey privateKey = getPrivate(utxoAtIndex.get(j)); + if (isRandomSelection()) { + if (isForceInvalidPrivateKeys && isRandomSelection()) { + // corrupt private key, change for other people + int index = people.indexOf(getKeyPair(utxoAtIndex.get(j))); + privateKey = people.get((index + 1) % people.size()).getPrivate(); + corrupted = true; + } else if (isForceCorruptedSignature && isRandomSelection()) { + // corrupt data + rawData[0]++; + corrupted = true; + } + + } + tx.addSignature(sign(privateKey, rawData), j); + } + tx.finalize(); + + if (corrupted){ + invalid.add(tx); + } else{ + valid.add(tx); + } + + } + + return ValidationLists.builder(Transaction.class) + .setValid(valid) + .setInvalid(invalid) + .setConflicted(conflicted) + .build(); + + } + + private boolean isRandomSelection() { + return Math.abs(random.nextGaussian()) < corruptedPercentage; + } +} diff --git a/assignment1/test/ValidationLists.java b/assignment1/test/ValidationLists.java new file mode 100644 index 0000000..709fb6d --- /dev/null +++ b/assignment1/test/ValidationLists.java @@ -0,0 +1,73 @@ +// Copyright (C) 2016-2017 Enrique Albertos +// Distributed under the GNU GPL v2 software license + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Validation Lists is a container for several lists of elements calssified in: + * Valid, Invalid or Conflicted + * @author ealbertos + * + * @param Type of the elements + */ +public class ValidationLists { + private final List valid; + private final List invalid; + private final List conflicted; + + private ValidationLists(final List valid, final List invalid, final List conflicted) { + super(); + this.valid = Collections.unmodifiableList( new ArrayList<>(valid)); + this.invalid = Collections.unmodifiableList(new ArrayList<>(invalid)); + this.conflicted = Collections.unmodifiableList(new ArrayList<>(conflicted)); + } + + public ValidationLists(ValidationLists original) { + this(original.valid, original.invalid, original.conflicted); + } + + public boolean isValid(final E e) { + return valid.contains(e) && !invalid.contains(e); + } + + public List allElements() { + final ArrayList list = new ArrayList<>(valid); + list.addAll(invalid); + return list; + } + + public static Builder builder(Class c){ + return new Builder(); + } + + + public static final class Builder{ + private List valid; + private List invalid; + private List conflicted; + + Builder setValid(List valid){ + this.valid = valid; + return this; + } + + Builder setInvalid(List invalid){ + this.invalid = invalid; + return this; + } + + Builder setConflicted(List conflicted){ + this.conflicted = conflicted; + return this; + } + + ValidationLists build() { + return new ValidationLists<>(valid, invalid, conflicted); + } + + } + + +}