From 5fe9d92159b17e131efa023541e1a70bc4c658ea Mon Sep 17 00:00:00 2001 From: Jeronimo Irazabal Date: Wed, 30 Nov 2022 15:24:08 -0300 Subject: [PATCH 1/3] fix: dual proof verification Signed-off-by: Jeronimo Irazabal --- .../immudb4j/crypto/CryptoUtils.java | 69 ++++++++++++++----- .../codenotary/immudb4j/crypto/DualProof.java | 18 +++-- .../immudb4j/crypto/LinearAdvanceProof.java | 46 +++++++++++++ src/main/proto/schema.proto | 7 ++ 4 files changed, 116 insertions(+), 24 deletions(-) create mode 100644 src/main/java/io/codenotary/immudb4j/crypto/LinearAdvanceProof.java diff --git a/src/main/java/io/codenotary/immudb4j/crypto/CryptoUtils.java b/src/main/java/io/codenotary/immudb4j/crypto/CryptoUtils.java index 4ebfdb5..f591fc4 100644 --- a/src/main/java/io/codenotary/immudb4j/crypto/CryptoUtils.java +++ b/src/main/java/io/codenotary/immudb4j/crypto/CryptoUtils.java @@ -31,14 +31,17 @@ import java.util.Base64; import java.util.List; - public class CryptoUtils { - // FYI: Interesting enough, Go returns a fixed value for sha256.Sum256(nil) and this value is: - // [227 176 196 66 152 252 28 20 154 251 244 200 153 111 185 36 39 174 65 228 100 155 147 76 164 149 153 27 120 82 184 85] + // FYI: Interesting enough, Go returns a fixed value for sha256.Sum256(nil) and + // this value is: + // [227 176 196 66 152 252 28 20 154 251 244 200 153 111 185 36 39 174 65 228 + // 100 155 147 76 164 149 153 27 120 82 184 85] // whose Base64 encoded value is 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=. - // But Java's MessageDigest fails with NPE when providing a null value. So we treat this case as in Go. - private static final byte[] SHA256_SUM_OF_NULL = Base64.getDecoder().decode("47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="); + // But Java's MessageDigest fails with NPE when providing a null value. So we + // treat this case as in Go. + private static final byte[] SHA256_SUM_OF_NULL = Base64.getDecoder() + .decode("47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="); /** * This method returns a SHA256 digest of the provided data. @@ -82,8 +85,8 @@ public static byte[] digestFrom(byte[] digest) { } public static boolean verifyDualProof(DualProof proof, - long sourceTxId, long targetTxId, - byte[] sourceAlh, byte[] targetAlh) { + long sourceTxId, long targetTxId, + byte[] sourceAlh, byte[] targetAlh) { if (proof == null || proof.sourceTxHeader == null || proof.targetTxHeader == null || proof.sourceTxHeader.getId() != sourceTxId || proof.targetTxHeader.getId() != targetTxId) { @@ -94,7 +97,8 @@ public static boolean verifyDualProof(DualProof proof, return false; } - if (!Arrays.equals(sourceAlh, proof.sourceTxHeader.alh()) || !Arrays.equals(targetAlh, proof.targetTxHeader.alh())) { + if (!Arrays.equals(sourceAlh, proof.sourceTxHeader.alh()) + || !Arrays.equals(targetAlh, proof.targetTxHeader.alh())) { return false; } @@ -115,8 +119,7 @@ public static boolean verifyDualProof(DualProof proof, proof.sourceTxHeader.getBlTxId(), proof.targetTxHeader.getBlTxId(), proof.sourceTxHeader.getBlRoot(), - proof.targetTxHeader.getBlRoot() - )) { + proof.targetTxHeader.getBlRoot())) { return false; } } @@ -126,18 +129,50 @@ public static boolean verifyDualProof(DualProof proof, proof.lastInclusionProof, proof.targetTxHeader.getBlTxId(), leafFor(proof.targetBlTxAlh), - proof.targetTxHeader.getBlRoot() - )) { + proof.targetTxHeader.getBlRoot())) { return false; } } if (sourceTxId < proof.targetTxHeader.getBlTxId()) { - return verifyLinearProof(proof.linearProof, - proof.targetTxHeader.getBlTxId(), targetTxId, proof.targetBlTxAlh, targetAlh); + if (!verifyLinearProof(proof.linearProof, + proof.targetTxHeader.getBlTxId(), targetTxId, proof.targetBlTxAlh, targetAlh)) { + return false; + } + + // Verify that the part of the linear proof consumed by the new merkle tree is + // consistent with that Merkle Tree + // In this case, this is the whole chain to the SourceTxID from the previous + // Merkle Tree. + // The sourceTxID consistency is already proven using proof.InclusionProof + return verifyLinearAdvanceProof( + proof.linearAdvanceProof, + proof.sourceTxHeader.getBlTxId(), + sourceTxId, + sourceAlh, + proof.targetTxHeader.getBlRoot(), + proof.targetTxHeader.getBlTxId()); + } + + if (!verifyLinearProof(proof.linearProof, sourceTxId, targetTxId, sourceAlh, targetAlh)) { + return false; } - return verifyLinearProof(proof.linearProof, sourceTxId, targetTxId, sourceAlh, targetAlh); + // Verify that the part of the linear proof consumed by the new merkle tree is + // consistent with that Merkle Tree + // In this case, this is the whole linear chain between the old Merkle Tree and + // the new Merkle Tree. The last entry + // in the new Merkle Tree is already proven through the LastInclusionProof, the + // remaining part of the liner proof + // that goes outside of the target Merkle Tree will be validated in future + // DualProof validations + return verifyLinearAdvanceProof( + proof.linearAdvanceProof, + proof.sourceTxHeader.getBlTxId(), + proof.targetTxHeader.getBlTxId(), + proof.targetBlTxAlh, + proof.targetTxHeader.getBlRoot(), + proof.targetTxHeader.getBlTxId()); } private static byte[] leafFor(byte[] d) { @@ -148,8 +183,8 @@ private static byte[] leafFor(byte[] d) { } private static boolean verifyLinearProof(LinearProof proof, - long sourceTxId, long targetTxId, - byte[] sourceAlh, byte[] targetAlh) { + long sourceTxId, long targetTxId, + byte[] sourceAlh, byte[] targetAlh) { if (proof == null || proof.sourceTxId != sourceTxId || proof.targetTxId != targetTxId) { return false; diff --git a/src/main/java/io/codenotary/immudb4j/crypto/DualProof.java b/src/main/java/io/codenotary/immudb4j/crypto/DualProof.java index b89f1cb..be8b61f 100644 --- a/src/main/java/io/codenotary/immudb4j/crypto/DualProof.java +++ b/src/main/java/io/codenotary/immudb4j/crypto/DualProof.java @@ -28,14 +28,16 @@ public class DualProof { public final byte[] targetBlTxAlh; public final byte[][] lastInclusionProof; public final LinearProof linearProof; + public final LinearAdvanceProof linearAdvanceProof; public DualProof(TxHeader sourceTxHeader, - TxHeader targetTxHeader, - byte[][] inclusionProof, - byte[][] consistencyProof, - byte[] targetBlTxAlh, - byte[][] lastInclusionProof, - LinearProof linearProof) { + TxHeader targetTxHeader, + byte[][] inclusionProof, + byte[][] consistencyProof, + byte[] targetBlTxAlh, + byte[][] lastInclusionProof, + LinearProof linearProof, + LinearAdvanceProof linearAdvanceProof) { this.sourceTxHeader = sourceTxHeader; this.targetTxHeader = targetTxHeader; this.inclusionProof = inclusionProof; @@ -43,6 +45,7 @@ public DualProof(TxHeader sourceTxHeader, this.targetBlTxAlh = targetBlTxAlh; this.lastInclusionProof = lastInclusionProof; this.linearProof = linearProof; + this.linearAdvanceProof = linearAdvanceProof; } public static DualProof valueOf(ImmudbProto.DualProof proof) { @@ -53,7 +56,8 @@ public static DualProof valueOf(ImmudbProto.DualProof proof) { Utils.convertSha256ListToBytesArray(proof.getConsistencyProofList()), proof.getTargetBlTxAlh().toByteArray(), Utils.convertSha256ListToBytesArray(proof.getLastInclusionProofList()), - LinearProof.valueOf(proof.getLinearProof()) + LinearProof.valueOf(proof.getLinearProof()), + LinearAdvanceProof.valueOf(proof.getLinearAdvanceProof()) ); } diff --git a/src/main/java/io/codenotary/immudb4j/crypto/LinearAdvanceProof.java b/src/main/java/io/codenotary/immudb4j/crypto/LinearAdvanceProof.java new file mode 100644 index 0000000..a1d2f9e --- /dev/null +++ b/src/main/java/io/codenotary/immudb4j/crypto/LinearAdvanceProof.java @@ -0,0 +1,46 @@ +/* +Copyright 2022 CodeNotary, Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package io.codenotary.immudb4j.crypto; + +import java.util.ArrayList; +import java.util.List; + +import io.codenotary.immudb.ImmudbProto; +import io.codenotary.immudb4j.Utils; + +public class LinearAdvanceProof { + + public final List inclusionProofs; + public final byte[][] terms; + + public LinearAdvanceProof(List inclusionProofs, byte[][] terms) { + this.inclusionProofs = inclusionProofs; + this.terms = terms; + } + + public static LinearAdvanceProof valueOf(ImmudbProto.LinearAdvanceProof proof) { + final List inclusionProofs = new ArrayList<>(proof.getInclusionProofsCount()); + + for (int i = 0; i < proof.getInclusionProofsCount(); i++) { + inclusionProofs.set(i, InclusionProof.valueOf(proof.getInclusionProofs(i))); + } + + return new LinearAdvanceProof( + inclusionProofs, + Utils.convertSha256ListToBytesArray(proof.getLinearProofTermsList())); + } + +} diff --git a/src/main/proto/schema.proto b/src/main/proto/schema.proto index dfa61f9..26d7828 100644 --- a/src/main/proto/schema.proto +++ b/src/main/proto/schema.proto @@ -197,6 +197,11 @@ message LinearProof { repeated bytes terms = 3; } +message LinearAdvanceProof { + repeated bytes linearProofTerms = 1; + repeated InclusionProof inclusionProofs = 2; +} + message DualProof { TxHeader sourceTxHeader = 1; TxHeader targetTxHeader = 2; @@ -208,6 +213,8 @@ message DualProof { repeated bytes lastInclusionProof = 6; LinearProof linearProof = 7; + + LinearAdvanceProof LinearAdvanceProof = 8; } message Tx { From 86b6a9590fde1f7fe7d824fad9b782753702d71a Mon Sep 17 00:00:00 2001 From: Jeronimo Irazabal Date: Wed, 30 Nov 2022 21:27:47 -0300 Subject: [PATCH 2/3] fix: verify intermediate linear proof Signed-off-by: Jeronimo Irazabal --- .../immudb4j/crypto/CryptoUtils.java | 74 ++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/codenotary/immudb4j/crypto/CryptoUtils.java b/src/main/java/io/codenotary/immudb4j/crypto/CryptoUtils.java index f591fc4..8c264a0 100644 --- a/src/main/java/io/codenotary/immudb4j/crypto/CryptoUtils.java +++ b/src/main/java/io/codenotary/immudb4j/crypto/CryptoUtils.java @@ -59,7 +59,6 @@ public static byte[] sha256Sum(byte[] data) { } public static byte[][] digestsFrom(List terms) { - if (terms == null) { return null; } @@ -84,6 +83,79 @@ public static byte[] digestFrom(byte[] digest) { return d; } + public static byte[] advanceLinearHash(byte[] alh, long txID, byte[] term) { + byte[] bs = new byte[Consts.TX_ID_SIZE + 2 * Consts.SHA256_SIZE]; + Utils.putUint64(txID, bs); + System.arraycopy(alh, 0, bs, Consts.TX_ID_SIZE, Consts.SHA256_SIZE); + // innerHash = hash(ts + mdLen + md + nentries + eH + blTxID + blRoot) + System.arraycopy(term, 0, bs, Consts.TX_ID_SIZE + Consts.SHA256_SIZE, Consts.SHA256_SIZE); + // hash(txID + prevAlh + innerHash) + return sha256Sum(bs); + } + + public static boolean verifyLinearAdvanceProof( + LinearAdvanceProof proof, + long startTxID, + long endTxID, + byte[] endAlh, + byte[] treeRoot, + long treeSize) { + // + // Old + // \ Merkle Tree + // \ + // \ + // \ Additional Inclusion proof + // \ for those nodes + // \ in new Merkle Tree + // \ ...................... + // \ / \ + // \ + // \+--+ +--+ +--+ +--+ +--+ + // -----------| |-----| |-----| |-----| |-----| | + // +--+ +--+ +--+ +--+ +--+ + // + // startTxID endTxID + // + if (endTxID < startTxID) { + // This must not happen - that's an invalid proof + return false; + } + + if (endTxID <= startTxID + 1) { + // Linear Advance Proof is not needed + return true; + } + + if (proof == null || + proof.terms.length != endTxID - startTxID || + proof.inclusionProofs.size() != (endTxID - startTxID) - 1) { + // Check more preconditions that would indicate broken proof + return false; + } + + byte[] calculatedAlh = proof.terms[0]; // alh at startTx+1 + + for (long txID = startTxID + 1; txID < endTxID; txID++) { + // Ensure the node in the chain is included in the target Merkle Tree + if (!verifyInclusion( + proof.inclusionProofs.get((int) (txID - startTxID - 1)).terms, + txID, + treeSize, + leafFor(calculatedAlh), + treeRoot)) { + return false; + } + + // Get the Alh for the next transaction + calculatedAlh = advanceLinearHash(calculatedAlh, txID + 1, proof.terms[(int) (txID - startTxID)]); + } + + // We must end up with the final Alh - that one is also checked for inclusion + // but in different part of the proof + return Arrays.equals(calculatedAlh, endAlh); + } + public static boolean verifyDualProof(DualProof proof, long sourceTxId, long targetTxId, byte[] sourceAlh, byte[] targetAlh) { From 74868e672b7d2dcd5995f41ff25c99cfb9746a79 Mon Sep 17 00:00:00 2001 From: Jeronimo Irazabal Date: Wed, 30 Nov 2022 22:30:00 -0300 Subject: [PATCH 3/3] fix: validate missing intermediate linear proof Signed-off-by: Jeronimo Irazabal --- .../io/codenotary/immudb4j/crypto/CryptoUtils.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/io/codenotary/immudb4j/crypto/CryptoUtils.java b/src/main/java/io/codenotary/immudb4j/crypto/CryptoUtils.java index 8c264a0..5cfff75 100644 --- a/src/main/java/io/codenotary/immudb4j/crypto/CryptoUtils.java +++ b/src/main/java/io/codenotary/immudb4j/crypto/CryptoUtils.java @@ -174,6 +174,17 @@ public static boolean verifyDualProof(DualProof proof, return false; } + if (proof.linearAdvanceProof == null) { + // Find the range startTxID / endTxID to fill with linear inclusion proof + long startTxID = proof.sourceTxHeader.getBlTxId(); + long endTxID = Math.min(proof.sourceTxHeader.getId(), proof.targetTxHeader.getBlTxId()); + + if (endTxID > startTxID + 1) { + // Linear Advance Proof is needed + throw new RuntimeException("Linear Advance Proof is needed"); + } + } + if (sourceTxId < proof.targetTxHeader.getBlTxId()) { if (!CryptoUtils.verifyInclusion( proof.inclusionProof,