diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataCombiner.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataCombiner.java index 73b670d6..3a34fbe2 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataCombiner.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataCombiner.java @@ -24,7 +24,7 @@ public class ArbitraryDataCombiner { private Path pathAfter; private byte[] signatureBefore; private Path finalPath; - ArbitraryDataMetadataPatch metadata; + private ArbitraryDataMetadataPatch metadata; public ArbitraryDataCombiner(Path pathBefore, Path pathAfter, byte[] signatureBefore) { this.pathBefore = pathBefore; @@ -39,6 +39,7 @@ public class ArbitraryDataCombiner { this.validatePreviousSignature(); this.validatePreviousHash(); this.process(); + this.validateCurrentHash(); } finally { this.postExecute(); @@ -132,6 +133,22 @@ public class ArbitraryDataCombiner { this.finalPath = merge.getMergePath(); } + private void validateCurrentHash() throws IOException { + byte[] currentHash = this.metadata.getCurrentHash(); + if (currentHash == null) { + throw new IllegalStateException("Unable to extract current hash from patch metadata"); + } + + ArbitraryDataDigest digest = new ArbitraryDataDigest(this.finalPath); + digest.compute(); + boolean valid = digest.isHashValid(currentHash); + if (!valid) { + String currentHash58 = Base58.encode(currentHash); + throw new InvalidObjectException(String.format("Current state hash mismatch. " + + "Patch curHash: %s, actual: %s", currentHash58, digest.getHash58())); + } + } + public Path getFinalPath() { return this.finalPath; } diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataDiff.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataDiff.java index 1eebb395..0d342e8c 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataDiff.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataDiff.java @@ -65,6 +65,7 @@ public class ArbitraryDataDiff { private Path pathAfter; private byte[] previousSignature; private byte[] previousHash; + private byte[] currentHash; private Path diffPath; private String identifier; @@ -92,6 +93,7 @@ public class ArbitraryDataDiff { this.findAddedOrModifiedFiles(); this.findRemovedFiles(); this.validate(); + this.hashCurrentState(); this.writeMetadata(); } finally { @@ -272,6 +274,12 @@ public class ArbitraryDataDiff { } } + private void hashCurrentState() throws IOException { + ArbitraryDataDigest digest = new ArbitraryDataDigest(this.pathAfter); + digest.compute(); + this.currentHash = digest.getHash(); + } + private void writeMetadata() throws IOException { ArbitraryDataMetadataPatch metadata = new ArbitraryDataMetadataPatch(this.diffPath); metadata.setAddedPaths(this.addedPaths); @@ -279,6 +287,7 @@ public class ArbitraryDataDiff { metadata.setRemovedPaths(this.removedPaths); metadata.setPreviousSignature(this.previousSignature); metadata.setPreviousHash(this.previousHash); + metadata.setCurrentHash(this.currentHash); metadata.write(); } diff --git a/src/main/java/org/qortal/arbitrary/metadata/ArbitraryDataMetadataPatch.java b/src/main/java/org/qortal/arbitrary/metadata/ArbitraryDataMetadataPatch.java index e4d75f84..888503ab 100644 --- a/src/main/java/org/qortal/arbitrary/metadata/ArbitraryDataMetadataPatch.java +++ b/src/main/java/org/qortal/arbitrary/metadata/ArbitraryDataMetadataPatch.java @@ -23,6 +23,7 @@ public class ArbitraryDataMetadataPatch extends ArbitraryDataMetadata { private List removedPaths; private byte[] previousSignature; private byte[] previousHash; + private byte[] currentHash; public ArbitraryDataMetadataPatch(Path filePath) { super(filePath); @@ -56,6 +57,12 @@ public class ArbitraryDataMetadataPatch extends ArbitraryDataMetadata { this.previousHash = Base58.decode(prevHash); } } + if (patch.has("curHash")) { + String curHash = patch.getString("curHash"); + if (curHash != null) { + this.currentHash = Base58.decode(curHash); + } + } if (patch.has("added")) { JSONArray added = (JSONArray) patch.get("added"); if (added != null) { @@ -101,6 +108,7 @@ public class ArbitraryDataMetadataPatch extends ArbitraryDataMetadata { patch.put("prevSig", Base58.encode(this.previousSignature)); patch.put("prevHash", Base58.encode(this.previousHash)); + patch.put("curHash", Base58.encode(this.currentHash)); patch.put("added", new JSONArray(this.addedPaths)); patch.put("removed", new JSONArray(this.removedPaths)); @@ -157,4 +165,12 @@ public class ArbitraryDataMetadataPatch extends ArbitraryDataMetadata { return this.previousHash; } + public void setCurrentHash(byte[] currentHash) { + this.currentHash = currentHash; + } + + public byte[] getCurrentHash() { + return this.currentHash; + } + } diff --git a/src/test/java/org/qortal/test/arbitrary/ArbitraryDataMergeTests.java b/src/test/java/org/qortal/test/arbitrary/ArbitraryDataMergeTests.java index 8eab5887..5a62c88e 100644 --- a/src/test/java/org/qortal/test/arbitrary/ArbitraryDataMergeTests.java +++ b/src/test/java/org/qortal/test/arbitrary/ArbitraryDataMergeTests.java @@ -2,9 +2,9 @@ package org.qortal.test.arbitrary; import org.junit.Before; import org.junit.Test; +import org.qortal.arbitrary.ArbitraryDataCombiner; import org.qortal.arbitrary.ArbitraryDataCreatePatch; import org.qortal.arbitrary.ArbitraryDataDigest; -import org.qortal.arbitrary.ArbitraryDataMerge; import org.qortal.crypto.Crypto; import org.qortal.repository.DataException; import org.qortal.test.common.Common; @@ -35,8 +35,12 @@ public class ArbitraryDataMergeTests extends Common { Path path1 = Paths.get("src/test/resources/arbitrary/demo1"); Path path2 = Paths.get("src/test/resources/arbitrary/demo2"); + // Generate random signature for the purposes of validation + byte[] signature = new byte[32]; + new Random().nextBytes(signature); + // Create a patch using the differences in path2 compared with path1 - ArbitraryDataCreatePatch patch = new ArbitraryDataCreatePatch(path1, path2, new byte[16]); + ArbitraryDataCreatePatch patch = new ArbitraryDataCreatePatch(path1, path2, signature); patch.create(); Path patchPath = patch.getFinalPath(); assertTrue(Files.exists(patchPath)); @@ -90,9 +94,9 @@ public class ArbitraryDataMergeTests extends Common { ); // Now merge the patch with the original path - ArbitraryDataMerge merge = new ArbitraryDataMerge(path1, patchPath); - merge.compute(); - Path finalPath = merge.getMergePath(); + ArbitraryDataCombiner combiner = new ArbitraryDataCombiner(path1, patchPath, signature); + combiner.combine(); + Path finalPath = combiner.getFinalPath(); // Ensure that all files exist in the final path (including lorem3) assertTrue(Files.exists(Paths.get(finalPath.toString(), "lorem1.txt"))); @@ -185,8 +189,12 @@ public class ArbitraryDataMergeTests extends Common { assertTrue(Files.exists(file1.toPath())); assertTrue(Files.exists(file2.toPath())); + // Generate random signature for the purposes of validation + byte[] signature = new byte[32]; + new Random().nextBytes(signature); + // Create a patch from the two paths - ArbitraryDataCreatePatch patch = new ArbitraryDataCreatePatch(tempDir1, tempDir2, new byte[16]); + ArbitraryDataCreatePatch patch = new ArbitraryDataCreatePatch(tempDir1, tempDir2, signature); patch.create(); Path patchPath = patch.getFinalPath(); assertTrue(Files.exists(patchPath)); @@ -205,9 +213,9 @@ public class ArbitraryDataMergeTests extends Common { assertFalse(Arrays.equals(patchDigest, file1Digest)); // Now merge the patch with the original path - ArbitraryDataMerge merge = new ArbitraryDataMerge(tempDir1, patchPath); - merge.compute(); - Path finalPath = merge.getMergePath(); + ArbitraryDataCombiner combiner = new ArbitraryDataCombiner(tempDir1, patchPath, signature); + combiner.combine(); + Path finalPath = combiner.getFinalPath(); // Check that the directory digests match ArbitraryDataDigest path2Digest = new ArbitraryDataDigest(tempDir2); @@ -252,8 +260,12 @@ public class ArbitraryDataMergeTests extends Common { assertTrue(Files.exists(file1.toPath())); assertTrue(Files.exists(file2.toPath())); + // Generate random signature for the purposes of validation + byte[] signature = new byte[32]; + new Random().nextBytes(signature); + // Create a patch from the two paths - ArbitraryDataCreatePatch patch = new ArbitraryDataCreatePatch(tempDir1, tempDir2, new byte[16]); + ArbitraryDataCreatePatch patch = new ArbitraryDataCreatePatch(tempDir1, tempDir2, signature); patch.create(); Path patchPath = patch.getFinalPath(); assertTrue(Files.exists(patchPath)); @@ -268,9 +280,9 @@ public class ArbitraryDataMergeTests extends Common { assertFalse(Arrays.equals(patchDigest, file2Digest)); // Now merge the patch with the original path - ArbitraryDataMerge merge = new ArbitraryDataMerge(tempDir1, patchPath); - merge.compute(); - Path finalPath = merge.getMergePath(); + ArbitraryDataCombiner combiner = new ArbitraryDataCombiner(tempDir1, patchPath, signature); + combiner.combine(); + Path finalPath = combiner.getFinalPath(); // Check that the directory digests match ArbitraryDataDigest path2Digest = new ArbitraryDataDigest(tempDir2); @@ -318,8 +330,12 @@ public class ArbitraryDataMergeTests extends Common { assertTrue(Files.exists(file1.toPath())); assertTrue(Files.exists(file2.toPath())); + // Generate random signature for the purposes of validation + byte[] signature = new byte[32]; + new Random().nextBytes(signature); + // Create a patch from the two paths - ArbitraryDataCreatePatch patch = new ArbitraryDataCreatePatch(tempDir1, tempDir2, new byte[16]); + ArbitraryDataCreatePatch patch = new ArbitraryDataCreatePatch(tempDir1, tempDir2, signature); patch.create(); Path patchPath = patch.getFinalPath(); assertTrue(Files.exists(patchPath)); @@ -337,9 +353,9 @@ public class ArbitraryDataMergeTests extends Common { assertFalse(Arrays.equals(patchDigest, file1Digest)); // Now merge the patch with the original path - ArbitraryDataMerge merge = new ArbitraryDataMerge(tempDir1, patchPath); - merge.compute(); - Path finalPath = merge.getMergePath(); + ArbitraryDataCombiner combiner = new ArbitraryDataCombiner(tempDir1, patchPath, signature); + combiner.combine(); + Path finalPath = combiner.getFinalPath(); // Check that the directory digests match ArbitraryDataDigest path2Digest = new ArbitraryDataDigest(tempDir2); @@ -385,8 +401,12 @@ public class ArbitraryDataMergeTests extends Common { assertTrue(Files.exists(file1.toPath())); assertTrue(Files.exists(file2.toPath())); + // Generate random signature for the purposes of validation + byte[] signature = new byte[32]; + new Random().nextBytes(signature); + // Create a patch from the two paths - ArbitraryDataCreatePatch patch = new ArbitraryDataCreatePatch(tempDir1, tempDir2, new byte[16]); + ArbitraryDataCreatePatch patch = new ArbitraryDataCreatePatch(tempDir1, tempDir2, signature); patch.create(); Path patchPath = patch.getFinalPath(); assertTrue(Files.exists(patchPath)); @@ -404,9 +424,9 @@ public class ArbitraryDataMergeTests extends Common { assertFalse(Arrays.equals(patchDigest, file1Digest)); // Now merge the patch with the original path - ArbitraryDataMerge merge = new ArbitraryDataMerge(tempDir1, patchPath); - merge.compute(); - Path finalPath = merge.getMergePath(); + ArbitraryDataCombiner combiner = new ArbitraryDataCombiner(tempDir1, patchPath, signature); + combiner.combine(); + Path finalPath = combiner.getFinalPath(); // Check that the directory digests match ArbitraryDataDigest path2Digest = new ArbitraryDataDigest(tempDir2);