Skip to content

Commit

Permalink
Merge pull request #47 from codenotary/fix_proof
Browse files Browse the repository at this point in the history
fix: dual proof verification
  • Loading branch information
jeroiraz committed Dec 1, 2022
2 parents c4996d9 + 74868e6 commit d84aa6e
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 25 deletions.
154 changes: 136 additions & 18 deletions src/main/java/io/codenotary/immudb4j/crypto/CryptoUtils.java
Expand Up @@ -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.
Expand All @@ -56,7 +59,6 @@ public static byte[] sha256Sum(byte[] data) {
}

public static byte[][] digestsFrom(List<ByteString> terms) {

if (terms == null) {
return null;
}
Expand All @@ -81,9 +83,82 @@ 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) {
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) {
Expand All @@ -94,10 +169,22 @@ 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;
}

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,
Expand All @@ -115,8 +202,7 @@ public static boolean verifyDualProof(DualProof proof,
proof.sourceTxHeader.getBlTxId(),
proof.targetTxHeader.getBlTxId(),
proof.sourceTxHeader.getBlRoot(),
proof.targetTxHeader.getBlRoot()
)) {
proof.targetTxHeader.getBlRoot())) {
return false;
}
}
Expand All @@ -126,18 +212,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) {
Expand All @@ -148,8 +266,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;
Expand Down
18 changes: 11 additions & 7 deletions src/main/java/io/codenotary/immudb4j/crypto/DualProof.java
Expand Up @@ -28,21 +28,24 @@ 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;
this.consistencyProof = consistencyProof;
this.targetBlTxAlh = targetBlTxAlh;
this.lastInclusionProof = lastInclusionProof;
this.linearProof = linearProof;
this.linearAdvanceProof = linearAdvanceProof;
}

public static DualProof valueOf(ImmudbProto.DualProof proof) {
Expand All @@ -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())
);
}

Expand Down
@@ -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<InclusionProof> inclusionProofs;
public final byte[][] terms;

public LinearAdvanceProof(List<InclusionProof> inclusionProofs, byte[][] terms) {
this.inclusionProofs = inclusionProofs;
this.terms = terms;
}

public static LinearAdvanceProof valueOf(ImmudbProto.LinearAdvanceProof proof) {
final List<InclusionProof> 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()));
}

}
7 changes: 7 additions & 0 deletions src/main/proto/schema.proto
Expand Up @@ -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;
Expand All @@ -208,6 +213,8 @@ message DualProof {
repeated bytes lastInclusionProof = 6;

LinearProof linearProof = 7;

LinearAdvanceProof LinearAdvanceProof = 8;
}

message Tx {
Expand Down

0 comments on commit d84aa6e

Please sign in to comment.