Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: dual proof verification #47

Merged
merged 3 commits into from Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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