From 14e545826c1ff868ff47097402d558deaec17ad7 Mon Sep 17 00:00:00 2001 From: Denis Rekalov Date: Thu, 12 Nov 2020 09:39:57 +0000 Subject: [PATCH] CORDA-4076: Fix SecureHash compatibility with previous versions (#6801) --- .../net/corda/core/crypto/SecureHash.kt | 19 +++++--- .../corda/core/internal/TransactionUtils.kt | 1 + .../crypto/Blake2s256DigestServiceTest.kt | 7 ++- .../net/corda/notary/common/BatchSigning.kt | 1 + .../internal/SecureHashSerializationTest.kt | 45 +++++++++++++++++++ .../tools/shell/HashLookupShellCommand.java | 3 +- 6 files changed, 66 insertions(+), 10 deletions(-) create mode 100644 serialization-tests/src/test/kotlin/net/corda/serialization/internal/SecureHashSerializationTest.kt diff --git a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt index fc1e34e4da..abee5a22f5 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt @@ -22,9 +22,7 @@ import java.util.function.Supplier */ @KeepForDJVM @CordaSerializable -sealed class SecureHash(val algorithm: String, bytes: ByteArray) : OpaqueBytes(bytes) { - constructor(bytes: ByteArray) : this(SHA2_256, bytes) - +sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) { /** SHA-256 is part of the SHA-2 hash function family. Generated hash is fixed size, 256-bits (32-bytes). */ class SHA256(bytes: ByteArray) : SecureHash(bytes) { init { @@ -52,7 +50,7 @@ sealed class SecureHash(val algorithm: String, bytes: ByteArray) : OpaqueBytes(b } } - class HASH(algorithm: String, bytes: ByteArray) : SecureHash(algorithm, bytes) { + class HASH(val algorithm: String, bytes: ByteArray) : SecureHash(bytes) { override fun equals(other: Any?): Boolean { return when { this === other -> true @@ -63,6 +61,10 @@ sealed class SecureHash(val algorithm: String, bytes: ByteArray) : OpaqueBytes(b override fun hashCode() = ByteBuffer.wrap(bytes).int + override fun toString(): String { + return "$algorithm$DELIMITER${toHexString()}" + } + override fun generate(data: ByteArray): SecureHash { return HASH(algorithm, digestAs(algorithm, data)) } @@ -70,9 +72,7 @@ sealed class SecureHash(val algorithm: String, bytes: ByteArray) : OpaqueBytes(b fun toHexString(): String = bytes.toHexString() - override fun toString(): String { - return "$algorithm$DELIMITER${toHexString()}" - } + override fun toString(): String = bytes.toHexString() /** * Returns the first [prefixLen] hexadecimal digits of the [SecureHash] value. @@ -354,6 +354,11 @@ fun ByteArray.hashAs(algorithm: String): SecureHash = SecureHash.hashAs(algorith */ fun OpaqueBytes.hashAs(algorithm: String): SecureHash = SecureHash.hashAs(algorithm, bytes) +/** + * Hash algorithm. + */ +val SecureHash.algorithm: String get() = if (this is SecureHash.HASH) algorithm else SecureHash.SHA2_256 + /** * Hide the [FastThreadLocal] class behind a [Supplier] interface * so that we can remove it for core-deterministic. diff --git a/core/src/main/kotlin/net/corda/core/internal/TransactionUtils.kt b/core/src/main/kotlin/net/corda/core/internal/TransactionUtils.kt index b963e50841..d9ba9e8903 100644 --- a/core/src/main/kotlin/net/corda/core/internal/TransactionUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/TransactionUtils.kt @@ -4,6 +4,7 @@ import net.corda.core.KeepForDJVM import net.corda.core.contracts.* import net.corda.core.crypto.DigestService import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.algorithm import net.corda.core.crypto.hashAs import net.corda.core.crypto.internal.DigestAlgorithmFactory import net.corda.core.flows.FlowLogic diff --git a/core/src/test/kotlin/net/corda/core/crypto/Blake2s256DigestServiceTest.kt b/core/src/test/kotlin/net/corda/core/crypto/Blake2s256DigestServiceTest.kt index 368b261746..d2d51f3761 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/Blake2s256DigestServiceTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/Blake2s256DigestServiceTest.kt @@ -33,20 +33,23 @@ class Blake2s256DigestServiceTest { @Test(timeout = 300_000) fun testBlankHash() { assertEquals( - "C59F682376D137F3F255E671E207D1F2374EBE504E9314208A52D9F88D69E8C8", - service.hash(byteArrayOf()).toHexString() + "BLAKE_TEST:C59F682376D137F3F255E671E207D1F2374EBE504E9314208A52D9F88D69E8C8", + service.hash(byteArrayOf()).toString() ) + assertEquals("C59F682376D137F3F255E671E207D1F2374EBE504E9314208A52D9F88D69E8C8", service.hash(byteArrayOf()).toHexString()) } @Test(timeout = 300_000) fun testHashBytes() { val hash = service.hash(byteArrayOf(0x64, -0x13, 0x42, 0x3a)) + assertEquals("BLAKE_TEST:9EEA14092257E759ADAA56539A7A88DA1F68F03ABE3D9552A21D4731F4E6ECA0", hash.toString()) assertEquals("9EEA14092257E759ADAA56539A7A88DA1F68F03ABE3D9552A21D4731F4E6ECA0", hash.toHexString()) } @Test(timeout = 300_000) fun testHashString() { val hash = service.hash("test") + assertEquals("BLAKE_TEST:AB76E8F7EEA1968C183D343B756EC812E47D4BC7A3F061F4DDE8948B3E05DAF2", hash.toString()) assertEquals("AB76E8F7EEA1968C183D343B756EC812E47D4BC7A3F061F4DDE8948B3E05DAF2", hash.toHexString()) } diff --git a/node/src/main/kotlin/net/corda/notary/common/BatchSigning.kt b/node/src/main/kotlin/net/corda/notary/common/BatchSigning.kt index 43dad3459a..ad25833db8 100644 --- a/node/src/main/kotlin/net/corda/notary/common/BatchSigning.kt +++ b/node/src/main/kotlin/net/corda/notary/common/BatchSigning.kt @@ -7,6 +7,7 @@ import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SignableData import net.corda.core.crypto.SignatureMetadata import net.corda.core.crypto.TransactionSignature +import net.corda.core.crypto.algorithm import net.corda.core.flows.NotaryError import net.corda.core.internal.digestService import net.corda.core.node.ServiceHub diff --git a/serialization-tests/src/test/kotlin/net/corda/serialization/internal/SecureHashSerializationTest.kt b/serialization-tests/src/test/kotlin/net/corda/serialization/internal/SecureHashSerializationTest.kt new file mode 100644 index 0000000000..39152e4c0a --- /dev/null +++ b/serialization-tests/src/test/kotlin/net/corda/serialization/internal/SecureHashSerializationTest.kt @@ -0,0 +1,45 @@ +package net.corda.serialization.internal + +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.SecureHash.Companion.SHA2_256 +import net.corda.core.crypto.SecureHash.Companion.SHA2_512 +import net.corda.core.crypto.algorithm +import net.corda.core.serialization.SerializationDefaults +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import net.corda.testing.core.SerializationEnvironmentRule +import org.junit.Assert.assertArrayEquals +import org.junit.Rule +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class SecureHashSerializationTest { + @Rule + @JvmField + val testSerialization = SerializationEnvironmentRule() + + @Test(timeout = 300_000) + fun `serialize and deserialize SHA-256`() { + val before = SecureHash.randomSHA256() + val bytes = before.serialize(context = SerializationDefaults.P2P_CONTEXT.withoutReferences()).bytes + val after = bytes.deserialize() + assertEquals(before, after) + assertArrayEquals(before.bytes, after.bytes) + assertEquals(before.algorithm, after.algorithm) + assertEquals(after.algorithm, SHA2_256) + assertTrue(after is SecureHash.SHA256) + } + + @Test(timeout = 300_000) + fun `serialize and deserialize SHA-512`() { + val before = SecureHash.random(SHA2_512) + val bytes = before.serialize(context = SerializationDefaults.P2P_CONTEXT.withoutReferences()).bytes + val after = bytes.deserialize() + assertEquals(before, after) + assertArrayEquals(before.bytes, after.bytes) + assertEquals(before.algorithm, after.algorithm) + assertEquals(after.algorithm, SHA2_512) + assertTrue(after is SecureHash.HASH) + } +} diff --git a/tools/shell/src/main/java/net/corda/tools/shell/HashLookupShellCommand.java b/tools/shell/src/main/java/net/corda/tools/shell/HashLookupShellCommand.java index 74e3513104..b5e72f82f6 100644 --- a/tools/shell/src/main/java/net/corda/tools/shell/HashLookupShellCommand.java +++ b/tools/shell/src/main/java/net/corda/tools/shell/HashLookupShellCommand.java @@ -1,6 +1,7 @@ package net.corda.tools.shell; import net.corda.core.crypto.SecureHash; +import net.corda.core.crypto.SecureHashKt; import net.corda.core.internal.VisibleForTesting; import net.corda.core.messaging.CordaRPCOps; import net.corda.core.messaging.StateMachineTransactionMapping; @@ -60,7 +61,7 @@ public class HashLookupShellCommand extends CordaRpcOpsShellCommand { Optional match = mapping.stream() .map(StateMachineTransactionMapping::getTransactionId) .filter( - txId -> txId.equals(txIdHashParsed) || SecureHash.hashAs(txIdHashParsed.getAlgorithm(), txId.getBytes()).equals(txIdHashParsed) + txId -> txId.equals(txIdHashParsed) || SecureHash.hashAs(SecureHashKt.getAlgorithm(txIdHashParsed), txId.getBytes()).equals(txIdHashParsed) ) .findFirst();