From 82a114a329fee1a249f9c0786d5c2f8593759863 Mon Sep 17 00:00:00 2001 From: Edoardo Ierina Date: Thu, 5 Nov 2020 23:05:29 +0100 Subject: [PATCH] [DRAFT] feat/CORDA-3823-hash-agility-qa-ready (#6789) * wip * wip * wip (need to review IEE comments) * wip * wip * Small refactoring, fixed network-verifier's TestNotaryFlow * Added command line option to explicitly enable hash agility support * wip-do-not-push * wip * wip * wip * aligned merkletree/transaction hash algorithms * wip * Added mixed algorithm support for nodes vs leaves and corrected mixed algorithm tests * moved global computeNonce and componentHash to DigestService * added comment for failing test to fix * wip * Minor cleanups, added deprecated componentHash/computeNonce * restored exploratory changes to failing SignedTransaction test * cleaned up and minor rafactoring * Fixed some tests with hardcoded hash algorithm * some changes and cleanups following code review * WIP commit before large change * WIP Fixed 3 tests * WIP removed direct references to randomSHA256() and sha256() * Updated/added liquibase migrations to support larger hash algorithms * Reviewed, cleanups, comments, fixes * removing direct references to sha256() * WIP verifying obligations test errors * reviewing obligation/attachment issues with sha3_256 * Full review before PR - intermediate commits * Reviewed and cleaned up * Futher cleanup * Fixed partial tree backward compatible json and cleanups * all tests passing * Removed couple of unused imports * Reworked global componentHash function to avoid deprecated warnings * replaced SHA3s with some alternate SHA2s * Removed SHA3-256 and SHA3-512 references * fixed some tests using non ubiquitous hash algorithms * Fixed ABI compatibility (not for TransactionBuilder) * Fixed ABI compatibility to TransactionBuilder * couple of fixes * fixed DigestService's randomHash * Removed constructor with loosely typed args for private constructor of LedgerTransaction class (API removal) * re-introduced LedgerTransaction deprecated ctor for deserialization * Add possibility to load CustomMessageDigest bypassing JCA (#6798) * Change api-current for DigestAlgorithm * disable flaky tests Co-authored-by: Denis Rekalov --- .ci/api-current.txt | 10 +- build.gradle | 2 +- .../corda/client/jackson/JacksonSupport.kt | 2 +- .../client/jackson/internal/CordaModule.kt | 41 +- .../client/jackson/JacksonSupportTest.kt | 64 ++- .../jackson/StringToMethodCallParserTest.kt | 2 +- core-deterministic/build.gradle | 2 +- .../net/corda/core/crypto/DigestSupplier.kt | 10 + .../corda/core/crypto/SHA256DigestSupplier.kt | 9 - .../contracts/PrivacySaltTest.kt | 12 +- .../deterministic/crypto/MerkleTreeTest.kt | 40 +- .../deterministic/crypto/SecureHashTest.kt | 2 +- .../crypto/TransactionSignatureTest.kt | 4 +- .../contracts/ConstraintsPropagationTests.kt | 2 + ...VerificationExceptionSerialisationTests.kt | 12 + .../coretests/crypto/PartialMerkleTreeTest.kt | 90 ++-- ...MerkleTreeWithNamedHashMultiAlgTreeTest.kt | 384 ++++++++++++++++++ .../PartialMerkleTreeWithNamedHashTest.kt | 384 ++++++++++++++++++ .../crypto/TransactionSignatureTest.kt | 7 +- .../CompatibleTransactionTests.kt | 26 +- .../transactions/TransactionBuilderTest.kt | 50 +++ .../transactions/TransactionTests.kt | 41 +- .../net/corda/core/contracts/Structures.kt | 26 +- .../TransactionVerificationException.kt | 4 + .../kotlin/net/corda/core/crypto/Crypto.kt | 2 +- .../net/corda/core/crypto/CryptoUtils.kt | 7 +- .../net/corda/core/crypto/DigestAlgorithm.kt | 32 ++ .../net/corda/core/crypto/DigestService.kt | 121 ++++++ .../net/corda/core/crypto/MerkleTree.kt | 27 +- .../corda/core/crypto/PartialMerkleTree.kt | 28 +- .../net/corda/core/crypto/SecureHash.kt | 246 ++++++++++- .../crypto/internal/DigestAlgorithmFactory.kt | 70 ++++ .../net/corda/core/flows/NotaryChangeFlow.kt | 4 +- .../kotlin/net/corda/core/flows/NotaryFlow.kt | 2 +- .../core/internal/ContractUpgradeUtils.kt | 3 +- .../corda/core/internal/TransactionUtils.kt | 80 +++- .../TransactionVerifierServiceInternal.kt | 1 + .../corda/core/node/services/VaultService.kt | 2 +- .../net/corda/core/schemas/PersistentTypes.kt | 5 +- .../ContractUpgradeTransactions.kt | 53 ++- .../core/transactions/LedgerTransaction.kt | 63 ++- .../core/transactions/MerkleTransaction.kt | 38 +- .../transactions/NotaryChangeTransactions.kt | 31 +- .../core/transactions/TransactionBuilder.kt | 4 +- .../core/transactions/WireTransaction.kt | 29 +- .../crypto/Blake2s256DigestServiceTest.kt | 67 +++ .../core/crypto/SHA2256DigestServiceTest.kt | 46 +++ .../core/crypto/SHA2384DigestServiceTest.kt | 46 +++ .../net/corda/core/crypto/SecureHashTest.kt | 67 +++ .../corda/core/internal/InternalUtilsTest.kt | 2 +- .../internal/internalAccessTestHelpers.kt | 6 +- detekt-baseline.xml | 7 - .../contracts/asset/ObligationTests.kt | 3 +- .../asset/selection/AbstractCashSelection.kt | 2 +- .../migration/cash.changelog-master.xml | 1 + .../resources/migration/cash.changelog-v1.xml | 5 +- .../resources/migration/cash.changelog-v2.xml | 11 + .../commercial-paper.changelog-master.xml | 1 + .../commercial-paper.changelog-v2.xml | 11 + .../nodeapi/internal/KeyStoreConfigHelpers.kt | 2 +- .../internal/serialization/kryo/Kryo.kt | 17 +- .../kotlin/net/corda/node/djvm/LtxFactory.kt | 5 +- .../net/corda/node/internal/AbstractNode.kt | 4 +- .../internal/DBNetworkParametersStorage.kt | 2 +- .../net/corda/node/internal/NodeStartup.kt | 5 + .../internal/cordapp/CordappProviderImpl.kt | 4 +- .../cordapp/JarScanningCordappLoader.kt | 6 +- .../node/internal/djvm/ComponentFactory.kt | 6 +- .../internal/djvm/DeterministicVerifier.kt | 6 +- .../node/internal/schemas/NodeInfoSchema.kt | 5 + .../node/migration/VaultStateMigration.kt | 2 +- .../node/services/api/ServiceHubInternal.kt | 3 +- .../PersistentScheduledFlowRepository.kt | 2 +- .../network/PersistentNetworkMapCache.kt | 2 +- .../DBTransactionMappingStorage.kt | 2 +- .../persistence/DBTransactionStorage.kt | 4 +- .../persistence/NodeAttachmentService.kt | 18 +- .../PersistentUniquenessProvider.kt | 12 +- .../node/services/vault/NodeVaultService.kt | 6 +- .../corda/node/services/vault/VaultSchema.kt | 2 +- .../net/corda/notary/common/BatchSigning.kt | 19 +- .../notary/experimental/bftsmart/BFTSmart.kt | 4 +- .../bftsmart/BFTSmartNotaryService.kt | 6 +- .../raft/RaftTransactionCommitLog.kt | 5 +- .../raft/RaftUniquenessProvider.kt | 10 +- .../corda/notary/jpa/JPAUniquenessProvider.kt | 21 +- .../migration/node-core.changelog-master.xml | 2 + .../migration/node-core.changelog-v17.xml | 14 + .../node-notary.changelog-master.xml | 1 + .../migration/node-notary.changelog-v100.xml | 20 + .../notary-bft-smart.changelog-master.xml | 1 + .../notary-bft-smart.changelog-v2.xml | 17 + .../notary-raft.changelog-master.xml | 1 + .../migration/notary-raft.changelog-v2.xml | 19 + .../vault-schema.changelog-master.xml | 1 + .../migration/vault-schema.changelog-v12.xml | 29 ++ .../events/NodeSchedulerServiceTest.kt | 4 +- .../persistence/HibernateConfigurationTest.kt | 4 +- .../persistence/NodeAttachmentServiceTest.kt | 4 +- .../NonValidatingNotaryServiceTests.kt | 4 +- .../transactions/UniquenessProviderTests.kt | 124 +++--- .../ValidatingNotaryServiceTests.kt | 4 +- .../node/services/vault/VaultQueryTests.kt | 4 +- .../bftsmart/BFTNotaryServiceTests.kt | 2 +- .../raft/RaftTransactionCommitLogTests.kt | 12 +- .../vega/contracts/CordappDependencies.kt | 2 +- .../flow/CommercialPaperIssueFlow.kt | 2 +- .../net/corda/traderdemo/flow/SellerFlow.kt | 2 +- .../migration/dbfailure.changelog-master.xml | 1 + .../migration/dbfailure.changelog-v2.xml | 11 + .../net/corda/testing/node/MockServices.kt | 2 + .../node/internal/network/NetworkMapServer.kt | 4 +- .../testing/internal/InternalTestUtils.kt | 7 +- .../services/InternalMockAttachmentStorage.kt | 2 +- .../testing/services/MockAttachmentStorage.kt | 2 +- .../servlets/AttachmentDownloadServlet.kt | 2 +- .../corda/explorer/views/TransactionViewer.kt | 2 +- .../tools/shell/HashLookupShellCommand.java | 6 +- 118 files changed, 2470 insertions(+), 371 deletions(-) create mode 100644 core-deterministic/src/main/kotlin/net/corda/core/crypto/DigestSupplier.kt delete mode 100644 core-deterministic/src/main/kotlin/net/corda/core/crypto/SHA256DigestSupplier.kt create mode 100644 core-tests/src/test/kotlin/net/corda/coretests/crypto/PartialMerkleTreeWithNamedHashMultiAlgTreeTest.kt create mode 100644 core-tests/src/test/kotlin/net/corda/coretests/crypto/PartialMerkleTreeWithNamedHashTest.kt create mode 100644 core/src/main/kotlin/net/corda/core/crypto/DigestAlgorithm.kt create mode 100644 core/src/main/kotlin/net/corda/core/crypto/DigestService.kt create mode 100644 core/src/main/kotlin/net/corda/core/crypto/internal/DigestAlgorithmFactory.kt create mode 100644 core/src/test/kotlin/net/corda/core/crypto/Blake2s256DigestServiceTest.kt create mode 100644 core/src/test/kotlin/net/corda/core/crypto/SHA2256DigestServiceTest.kt create mode 100644 core/src/test/kotlin/net/corda/core/crypto/SHA2384DigestServiceTest.kt create mode 100644 finance/workflows/src/main/resources/migration/cash.changelog-v2.xml create mode 100644 finance/workflows/src/main/resources/migration/commercial-paper.changelog-v2.xml create mode 100644 node/src/main/resources/migration/node-core.changelog-v17.xml create mode 100644 node/src/main/resources/migration/node-notary.changelog-v100.xml create mode 100644 node/src/main/resources/migration/notary-bft-smart.changelog-v2.xml create mode 100644 node/src/main/resources/migration/notary-raft.changelog-v2.xml create mode 100644 node/src/main/resources/migration/vault-schema.changelog-v12.xml create mode 100644 testing/cordapps/dbfailure/dbfcontracts/src/main/resources/migration/dbfailure.changelog-v2.xml diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 7b43eb624d..947baa9ba7 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -1849,6 +1849,15 @@ public final class net.corda.core.crypto.CryptoUtils extends java.lang.Object public static final boolean verify(java.security.PublicKey, byte[], net.corda.core.crypto.DigitalSignature) public static final boolean verify(java.security.PublicKey, byte[], byte[]) ## +public interface net.corda.core.crypto.DigestAlgorithm + @NotNull + public abstract byte[] digest(byte[]) + @NotNull + public abstract String getAlgorithm() + public abstract int getDigestLength() + @NotNull + public abstract byte[] preImageResistantDigest(byte[]) +## @CordaSerializable public class net.corda.core.crypto.DigitalSignature extends net.corda.core.utilities.OpaqueBytes public (byte[]) @@ -6408,7 +6417,6 @@ public abstract class net.corda.core.transactions.FullTransaction extends net.co public final class net.corda.core.transactions.LedgerTransaction extends net.corda.core.transactions.FullTransaction public (java.util.List>, java.util.List>, java.util.List>, java.util.List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt) public (java.util.List>, java.util.List>, java.util.List>, java.util.List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt, net.corda.core.node.NetworkParameters) - public (java.util.List, java.util.List, java.util.List, java.util.List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt, net.corda.core.node.NetworkParameters, java.util.List, java.util.List, java.util.List, java.util.List, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function2, net.corda.core.serialization.internal.AttachmentsClassLoaderCache, kotlin.jvm.internal.DefaultConstructorMarker) @NotNull public final java.util.List> commandsOfType(Class) @NotNull diff --git a/build.gradle b/build.gradle index a46fabf303..02a0ca4336 100644 --- a/build.gradle +++ b/build.gradle @@ -544,7 +544,7 @@ task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) { fileTree(dir: it, // these exclusions are necessary because jacoco gets confused by same class names // which occur due to deterministic versions of non deterministic classes - exclude: ['**/net/corda/core/crypto/SHA256DigestSupplier**', + exclude: ['**/net/corda/core/crypto/DigestSupplier**', '**/net/corda/core/crypto/DelegatingSecureRandomService', '**/net/corda/core/internal/ThreadLocalToggleField**', '**/net/corda/core/internal/InheritableThreadLocalToggleField**', diff --git a/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt index 5cd84f1ad0..95f6f3e03c 100644 --- a/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt +++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt @@ -396,7 +396,7 @@ object JacksonSupport { class SecureHashDeserializer : JsonDeserializer() { override fun deserialize(parser: JsonParser, context: DeserializationContext): T { try { - return uncheckedCast(SecureHash.parse(parser.text)) + return uncheckedCast(SecureHash.create(parser.text)) } catch (e: Exception) { throw JsonParseException(parser, "Invalid hash ${parser.text}: ${e.message}") } diff --git a/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt b/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt index 2adbccc93f..17e98ffd22 100644 --- a/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt +++ b/client/jackson/src/main/kotlin/net/corda/client/jackson/internal/CordaModule.kt @@ -34,6 +34,7 @@ import net.corda.client.jackson.JacksonSupport import net.corda.core.contracts.* import net.corda.core.crypto.* import net.corda.core.crypto.PartialMerkleTree.PartialTree +import net.corda.core.crypto.SecureHash.Companion.SHA2_256 import net.corda.core.flows.StateMachineRunId import net.corda.core.identity.* import net.corda.core.internal.DigitalSignatureWithCert @@ -80,8 +81,9 @@ class CordaModule : SimpleModule("corda-core") { context.setMixInAnnotations(Party::class.java, PartyMixin::class.java) context.setMixInAnnotations(PublicKey::class.java, PublicKeyMixin::class.java) context.setMixInAnnotations(ByteSequence::class.java, ByteSequenceMixin::class.java) - context.setMixInAnnotations(SecureHash.SHA256::class.java, SecureHashSHA256Mixin::class.java) - context.setMixInAnnotations(SecureHash::class.java, SecureHashSHA256Mixin::class.java) + context.setMixInAnnotations(SecureHash.SHA256::class.java, SecureHashMixin::class.java) + context.setMixInAnnotations(SecureHash.HASH::class.java, SecureHashMixin::class.java) + context.setMixInAnnotations(SecureHash::class.java, SecureHashMixin::class.java) context.setMixInAnnotations(SerializedBytes::class.java, SerializedBytesMixin::class.java) context.setMixInAnnotations(DigitalSignature.WithKey::class.java, ByteSequenceWithPropertiesMixin::class.java) context.setMixInAnnotations(DigitalSignatureWithCert::class.java, ByteSequenceWithPropertiesMixin::class.java) @@ -97,6 +99,7 @@ class CordaModule : SimpleModule("corda-core") { context.setMixInAnnotations(PartialTree::class.java, PartialTreeMixin::class.java) context.setMixInAnnotations(NodeInfo::class.java, NodeInfoMixin::class.java) context.setMixInAnnotations(StateMachineRunId::class.java, StateMachineRunIdMixin::class.java) + context.setMixInAnnotations(DigestService::class.java, DigestServiceMixin::class.java) } } @@ -209,6 +212,7 @@ private interface WireTransactionMixin private class WireTransactionSerializer : JsonSerializer() { override fun serialize(value: WireTransaction, gen: JsonGenerator, serializers: SerializerProvider) { gen.writeObject(WireTransactionJson( + value.digestService, value.id, value.notary, value.inputs, @@ -236,11 +240,12 @@ private class WireTransactionDeserializer : JsonDeserializer() wrapper.references, wrapper.networkParametersHash ) - return WireTransaction(componentGroups, wrapper.privacySalt) + return WireTransaction(componentGroups, wrapper.privacySalt, wrapper.digestService ?: DigestService.sha2_256) } } -private class WireTransactionJson(val id: SecureHash, +private class WireTransactionJson(@get:JsonInclude(Include.NON_NULL) val digestService: DigestService?, + val id: SecureHash, val notary: Party?, val inputs: List, val outputs: List>, @@ -335,7 +340,7 @@ private class PartialTreeSerializer : JsonSerializer() { return when (tree) { is PartialTree.IncludedLeaf -> PartialTreeJson(includedLeaf = tree.hash) is PartialTree.Leaf -> PartialTreeJson(leaf = tree.hash) - is PartialTree.Node -> PartialTreeJson(left = convert(tree.left), right = convert(tree.right)) + is PartialTree.Node -> PartialTreeJson(left = convert(tree.left), right = convert(tree.right), hashAlgorithm = tree.hashAlgorithm) else -> throw IllegalArgumentException("Don't know how to serialize $tree") } } @@ -351,7 +356,7 @@ private class PartialTreeDeserializer : JsonDeserializer() { when { includedLeaf != null -> PartialTree.IncludedLeaf(includedLeaf) leaf != null -> PartialTree.Leaf(leaf) - else -> PartialTree.Node(convert(left!!), convert(right!!)) + else -> PartialTree.Node(convert(left!!), convert(right!!), hashAlgorithm ?: SHA2_256) } } } @@ -361,7 +366,8 @@ private class PartialTreeDeserializer : JsonDeserializer() { private class PartialTreeJson(val includedLeaf: SecureHash? = null, val leaf: SecureHash? = null, val left: PartialTreeJson? = null, - val right: PartialTreeJson? = null) { + val right: PartialTreeJson? = null, + val hashAlgorithm: String? = null) { init { if (includedLeaf != null) { require(leaf == null && left == null && right == null) { "Invalid JSON structure" } @@ -440,7 +446,7 @@ private interface NodeInfoMixin @ToStringSerialize @JsonDeserialize(using = JacksonSupport.SecureHashDeserializer::class) -private interface SecureHashSHA256Mixin +private interface SecureHashMixin @JsonSerialize(using = JacksonSupport.PublicKeySerializer::class) @JsonDeserialize(using = JacksonSupport.PublicKeyDeserializer::class) @@ -541,6 +547,25 @@ private class AmountDeserializer(delegate: JsonDeserializer<*>) : DelegatingDese } } +@JsonSerialize(using = DigestServiceSerializer::class) +@JsonDeserialize(using = DigestServiceDeserializer::class) +private interface DigestServiceMixin + +private class DigestServiceSerializer : JsonSerializer() { + override fun serialize(value: DigestService, gen: JsonGenerator, serializers: SerializerProvider) { + gen.writeObject(DigestServiceJson(value.hashAlgorithm)) + } +} + +private class DigestServiceDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): DigestService { + val wrapper = parser.readValueAs() + return DigestService(wrapper.hashAlgorithm) + } +} + +private class DigestServiceJson(val hashAlgorithm: String) + @JsonDeserialize(using = JacksonSupport.OpaqueBytesDeserializer::class) private interface ByteSequenceMixin { @Suppress("unused") diff --git a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt index 5edd886de5..26efe9b567 100644 --- a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt +++ b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt @@ -50,6 +50,7 @@ import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Before import org.junit.Rule import org.junit.Test +import org.junit.jupiter.api.TestFactory import org.junit.runner.RunWith import org.junit.runners.Parameterized import org.junit.runners.Parameterized.Parameters @@ -236,6 +237,29 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory: assertThat(mapper.convertValue(json)).isEqualTo(transactionSignature) } + @Test(timeout=300_000) + fun `TransactionSignature with SHA3`() { + val signatureMetadata = SignatureMetadata(1, 1) + val partialMerkleTree = PartialMerkleTree(PartialTree.Node( + left = PartialTree.Leaf(SecureHash.random(SecureHash.SHA2_384)), + right = PartialTree.IncludedLeaf(SecureHash.random(SecureHash.SHA2_384)), + hashAlgorithm = SecureHash.SHA2_384 + )) + val transactionSignature = TransactionSignature(secureRandomBytes(128), BOB_PUBKEY, signatureMetadata, partialMerkleTree) + val json = mapper.valueToTree(transactionSignature) + val (bytesJson, byJson, signatureMetadataJson, partialMerkleTreeJson) = json.assertHasOnlyFields( + "bytes", + "by", + "signatureMetadata", + "partialMerkleTree" + ) + assertThat(bytesJson.binaryValue()).isEqualTo(transactionSignature.bytes) + assertThat(byJson.valueAs(mapper)).isEqualTo(BOB_PUBKEY) + assertThat(signatureMetadataJson.valueAs(mapper)).isEqualTo(signatureMetadata) + assertThat(partialMerkleTreeJson.valueAs(mapper).root).isEqualTo(partialMerkleTree.root) + assertThat(mapper.convertValue(json)).isEqualTo(transactionSignature) + } + @Test(timeout=300_000) fun `SignedTransaction (WireTransaction)`() { val attachmentId = SecureHash.randomSHA256() @@ -267,7 +291,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory: println(mapper.writeValueAsString(json)) val (wtxJson, signaturesJson) = json.assertHasOnlyFields("wire", "signatures") assertThat(signaturesJson.childrenAs(mapper)).isEqualTo(stx.sigs) - val wtxFields = wtxJson.assertHasOnlyFields("id", "notary", "inputs", "attachments", "outputs", "commands", "timeWindow", "references", "privacySalt", "networkParametersHash") + val wtxFields = wtxJson.assertHasOnlyFields("id", "notary", "inputs", "attachments", "outputs", "commands", "timeWindow", "references", "privacySalt", "networkParametersHash", "digestService") assertThat(wtxFields[0].valueAs(mapper)).isEqualTo(wtx.id) assertThat(wtxFields[1].valueAs(mapper)).isEqualTo(wtx.notary) assertThat(wtxFields[2].childrenAs(mapper)).isEqualTo(wtx.inputs) @@ -277,6 +301,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory: assertThat(wtxFields[6].valueAs(mapper)).isEqualTo(wtx.timeWindow) assertThat(wtxFields[7].childrenAs(mapper)).isEqualTo(wtx.references) assertThat(wtxFields[8].valueAs(mapper)).isEqualTo(wtx.privacySalt) + assertThat(wtxFields[10].valueAs(mapper)).isEqualTo(wtx.digestService) assertThat(mapper.convertValue(wtxJson)).isEqualTo(wtx) assertThat(mapper.convertValue(json)).isEqualTo(stx) } @@ -379,14 +404,47 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory: right = PartialTree.IncludedLeaf(SecureHash.randomSHA256()) ) val json = mapper.valueToTree(node) - println(mapper.writeValueAsString(json)) - val (leftJson, rightJson) = json.assertHasOnlyFields("left", "right") + val (leftJson, rightJson, algorithm) = json.assertHasOnlyFields("left", "right", "hashAlgorithm") assertThat(leftJson.valueAs(mapper)).isEqualTo(node.left) assertThat(rightJson.valueAs(mapper)).isEqualTo(node.right) + assertThat(algorithm.valueAs(mapper)).isEqualTo(node.hashAlgorithm) assertThat(mapper.convertValue(json)).isEqualTo(node) } @Test(timeout=300_000) + fun `simple PartialTree Node with SHA3`() { + val node = PartialTree.Node( + left = PartialTree.Leaf(SecureHash.random(SecureHash.SHA2_384)), + right = PartialTree.IncludedLeaf(SecureHash.random(SecureHash.SHA2_384)), + hashAlgorithm = SecureHash.SHA2_384 + ) + val json = mapper.valueToTree(node) + val (leftJson, rightJson, algorithm) = json.assertHasOnlyFields("left", "right", "hashAlgorithm") + assertThat(leftJson.valueAs(mapper)).isEqualTo(node.left) + assertThat(rightJson.valueAs(mapper)).isEqualTo(node.right) + assertThat(algorithm.valueAs(mapper)).isEqualTo(node.hashAlgorithm) + assertThat(mapper.convertValue(json)).isEqualTo(node) + } + + @Test(timeout=300_000) + fun `PartialTree Node backward compatible deserialize`() { + val left = SecureHash.randomSHA256() + val right = SecureHash.randomSHA256() + val node = PartialTree.Node( + left = PartialTree.Leaf(left), + right = PartialTree.IncludedLeaf(right), + hashAlgorithm = SecureHash.SHA2_256 + ) + val legacyJson = mapper.readTree("{\"left\":{\"leaf\":\"$left\"},\"right\":{\"includedLeaf\":\"$right\"}}") + val (leftJson, rightJson) = legacyJson.assertHasOnlyFields("left", "right") + + assertThat(leftJson.valueAs(mapper)).isEqualTo(node.left) + assertThat(rightJson.valueAs(mapper)).isEqualTo(node.right) + assertThat(mapper.convertValue(legacyJson)).isEqualTo(node) + } + + @Test(timeout=300_000) + @TestFactory() fun `complex PartialTree Node`() { val node = PartialTree.Node( left = PartialTree.IncludedLeaf(SecureHash.randomSHA256()), diff --git a/client/jackson/src/test/kotlin/net/corda/client/jackson/StringToMethodCallParserTest.kt b/client/jackson/src/test/kotlin/net/corda/client/jackson/StringToMethodCallParserTest.kt index 0a105b6f5f..2b27c4c2b0 100644 --- a/client/jackson/src/test/kotlin/net/corda/client/jackson/StringToMethodCallParserTest.kt +++ b/client/jackson/src/test/kotlin/net/corda/client/jackson/StringToMethodCallParserTest.kt @@ -13,7 +13,7 @@ class StringToMethodCallParserTest { fun simple() = "simple" fun string(noteTextWord: String) = noteTextWord fun twoStrings(a: String, b: String) = a + b - fun simpleObject(hash: SecureHash.SHA256) = hash.toString() + fun simpleObject(hash: SecureHash) = hash.toString() fun complexObject(pair: Pair) = pair fun complexNestedObject(pairs: Pair>) = pairs diff --git a/core-deterministic/build.gradle b/core-deterministic/build.gradle index 5be42d8084..71ba8d4efa 100644 --- a/core-deterministic/build.gradle +++ b/core-deterministic/build.gradle @@ -68,7 +68,7 @@ def patchCore = tasks.register('patchCore', Zip) { from(processResources) from(zipTree(originalJar)) { exclude 'net/corda/core/crypto/DelegatingSecureRandomService*.class' - exclude 'net/corda/core/crypto/SHA256DigestSupplier.class' + exclude 'net/corda/core/crypto/DigestSupplier.class' exclude 'net/corda/core/internal/*ToggleField*.class' exclude 'net/corda/core/serialization/*SerializationFactory*.class' exclude 'net/corda/core/serialization/internal/AttachmentsHolderImpl.class' diff --git a/core-deterministic/src/main/kotlin/net/corda/core/crypto/DigestSupplier.kt b/core-deterministic/src/main/kotlin/net/corda/core/crypto/DigestSupplier.kt new file mode 100644 index 0000000000..18d60effa4 --- /dev/null +++ b/core-deterministic/src/main/kotlin/net/corda/core/crypto/DigestSupplier.kt @@ -0,0 +1,10 @@ +package net.corda.core.crypto + +import net.corda.core.crypto.internal.DigestAlgorithmFactory +import java.util.function.Supplier + +@Suppress("unused") +private class DigestSupplier(private val algorithm: String) : Supplier { + override fun get(): DigestAlgorithm = DigestAlgorithmFactory.create(algorithm) + val digestLength: Int by lazy { get().digestLength } +} diff --git a/core-deterministic/src/main/kotlin/net/corda/core/crypto/SHA256DigestSupplier.kt b/core-deterministic/src/main/kotlin/net/corda/core/crypto/SHA256DigestSupplier.kt deleted file mode 100644 index 4eda6be984..0000000000 --- a/core-deterministic/src/main/kotlin/net/corda/core/crypto/SHA256DigestSupplier.kt +++ /dev/null @@ -1,9 +0,0 @@ -package net.corda.core.crypto - -import java.security.MessageDigest -import java.util.function.Supplier - -@Suppress("unused") -private class SHA256DigestSupplier : Supplier { - override fun get(): MessageDigest = MessageDigest.getInstance("SHA-256") -} diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/PrivacySaltTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/PrivacySaltTest.kt index a7912eea63..3b9854d63f 100644 --- a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/PrivacySaltTest.kt +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/contracts/PrivacySaltTest.kt @@ -21,14 +21,14 @@ class PrivacySaltTest { } @Test(timeout=300_000) - fun testTooShortPrivacySalt() { + fun testTooShortPrivacySaltForSHA256() { val ex = assertFailsWith { PrivacySalt(ByteArray(SALT_SIZE - 1) { 0x7f }) } - assertEquals("Privacy salt should be 32 bytes.", ex.message) + assertEquals("Privacy salt should be at least 32 bytes.", ex.message) } @Test(timeout=300_000) - fun testTooLongPrivacySalt() { - val ex = assertFailsWith { PrivacySalt(ByteArray(SALT_SIZE + 1) { 0x7f }) } - assertEquals("Privacy salt should be 32 bytes.", ex.message) + fun testTooShortPrivacySaltForSHA512() { + val ex = assertFailsWith { PrivacySalt(ByteArray(SALT_SIZE) { 0x7f }).apply { validateFor("SHA-512") } } + assertEquals("Privacy salt should be at least 64 bytes for SHA-512.", ex.message) } -} \ No newline at end of file +} diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/MerkleTreeTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/MerkleTreeTest.kt index 0b4d340e73..e2fcf99860 100644 --- a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/MerkleTreeTest.kt +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/MerkleTreeTest.kt @@ -1,14 +1,50 @@ package net.corda.deterministic.crypto +import net.corda.core.crypto.DigestService import net.corda.core.crypto.MerkleTree import net.corda.core.crypto.SecureHash import org.junit.Assert.assertEquals import org.junit.Test class MerkleTreeTest { + private fun leafs(algorithm : String) : List = + listOf(SecureHash.allOnesHashFor(algorithm), SecureHash.zeroHashFor(algorithm)) + @Test(timeout=300_000) fun testCreate() { - val merkle = MerkleTree.getMerkleTree(listOf(SecureHash.allOnesHash, SecureHash.zeroHash)) - assertEquals(SecureHash.parse("A5DE9B714ACCD8AFAAABF1CBD6E1014C9D07FF95C2AE154D91EC68485B31E7B5"), merkle.hash) + val merkle = MerkleTree.getMerkleTree(leafs(SecureHash.SHA2_256), DigestService.sha2_256) + assertEquals(SecureHash.create("A5DE9B714ACCD8AFAAABF1CBD6E1014C9D07FF95C2AE154D91EC68485B31E7B5"), merkle.hash) } + + @Test(timeout=300_000) + fun `test create SHA2-384`() { + val merkle = MerkleTree.getMerkleTree(leafs(SecureHash.SHA2_384), DigestService.sha2_384) + assertEquals(SecureHash.create("SHA-384:2B83D37859E3665D7C239964D769CF950EE6478C13E4CA2D6643C23B6C4EAE035C88F654D22E0D65E7CA40BAE4F3718F"), merkle.hash) + } + + @Test(timeout=300_000) + fun `test create SHA2-256 to SHA2-384`() { + val merkle = MerkleTree.getMerkleTree(leafs(SecureHash.SHA2_256), DigestService.sha2_384) + assertEquals(SecureHash.create("SHA-384:02A4E8EA5AA4BBAFE80C0E7127B15994B84030BE8616EA2A0127D85203CF34221403635C08084A6BDDB1DB06333F0A49"), merkle.hash) + } + +// @Test(timeout=300_000) +// fun testCreateSHA3256() { +// val merkle = MerkleTree.getMerkleTree(listOf(SecureHash.allOnesHashFor(SecureHash.SHA3_256), +// SecureHash.zeroHashFor(SecureHash.SHA3_256)), DigestService.sha3_256) +// assertEquals(SecureHash.create("SHA3-256:80673DBEEC8F6761ACBB121E7E45F61D4279CCD8B8E2231741ECD0716F4C9EDC"), merkle.hash) +// } +// +// @Test(timeout=300_000) +// fun testCreateSHA2256toSHA3256() { +// val merkle = MerkleTree.getMerkleTree(listOf(SecureHash.allOnesHash, SecureHash.zeroHash), DigestService.sha3_256) +// assertEquals(SecureHash.create("SHA3-256:80673DBEEC8F6761ACBB121E7E45F61D4279CCD8B8E2231741ECD0716F4C9EDC"), merkle.hash) +// } +// +// @Test(timeout=300_000) +// fun testCreateSHA3256toSHA2256() { +// val merkle = MerkleTree.getMerkleTree(listOf(SecureHash.allOnesHashFor(SecureHash.SHA3_256), +// SecureHash.zeroHashFor(SecureHash.SHA3_256)), DigestService.sha2_256) +// assertEquals(SecureHash.create("A5DE9B714ACCD8AFAAABF1CBD6E1014C9D07FF95C2AE154D91EC68485B31E7B5"), merkle.hash) +// } } \ No newline at end of file diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/SecureHashTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/SecureHashTest.kt index c51367ffc5..162b7cb488 100644 --- a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/SecureHashTest.kt +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/SecureHashTest.kt @@ -10,7 +10,7 @@ class SecureHashTest { @Test(timeout=300_000) fun testSHA256() { val hash = SecureHash.sha256(byteArrayOf(0x64, -0x13, 0x42, 0x3a)) - assertEquals(SecureHash.parse("6D1687C143DF792A011A1E80670A4E4E0C25D0D87A39514409B1ABFC2043581F"), hash) + assertEquals(SecureHash.create("6D1687C143DF792A011A1E80670A4E4E0C25D0D87A39514409B1ABFC2043581F"), hash) assertEquals("6D1687C143DF792A011A1E80670A4E4E0C25D0D87A39514409B1ABFC2043581F", hash.toString()) } diff --git a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/TransactionSignatureTest.kt b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/TransactionSignatureTest.kt index f233ce89a9..a775acf8e2 100644 --- a/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/TransactionSignatureTest.kt +++ b/core-deterministic/testing/src/test/kotlin/net/corda/deterministic/crypto/TransactionSignatureTest.kt @@ -65,7 +65,7 @@ class TransactionSignatureTest { val txSignature = signMultipleTx(txIds, keyPair) // The hash of all txIds are used as leaves. - val merkleTree = MerkleTree.getMerkleTree(txIds.map { it.sha256() }) + val merkleTree = MerkleTree.getMerkleTree(txIds.map { it.sha256() }, DigestService.default) // We haven't added the partial tree yet. assertNull(txSignature.partialMerkleTree) @@ -128,7 +128,7 @@ class TransactionSignatureTest { // Returns a TransactionSignature over the Merkle root, but the partial tree is null. private fun signMultipleTx(txIds: List, keyPair: KeyPair): TransactionSignature { - val merkleTreeRoot = MerkleTree.getMerkleTree(txIds.map { it.sha256() }).hash + val merkleTreeRoot = MerkleTree.getMerkleTree(txIds.map { it.sha256() }, DigestService.default).hash return signOneTx(merkleTreeRoot, keyPair) } diff --git a/core-tests/src/test/kotlin/net/corda/coretests/contracts/ConstraintsPropagationTests.kt b/core-tests/src/test/kotlin/net/corda/coretests/contracts/ConstraintsPropagationTests.kt index 82397ff1cd..0e77bcbed1 100644 --- a/core-tests/src/test/kotlin/net/corda/coretests/contracts/ConstraintsPropagationTests.kt +++ b/core-tests/src/test/kotlin/net/corda/coretests/contracts/ConstraintsPropagationTests.kt @@ -14,6 +14,7 @@ import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.internal.canBeTransitionedFrom import net.corda.core.internal.inputStream +import net.corda.core.internal.requireSupportedHashType import net.corda.core.internal.toPath import net.corda.core.node.NotaryInfo import net.corda.core.node.services.IdentityService @@ -372,6 +373,7 @@ class ConstraintsPropagationTests { } private fun MockServices.recordTransaction(wireTransaction: WireTransaction) { + requireSupportedHashType(wireTransaction) val nodeKey = ALICE_PUBKEY val sigs = listOf(keyManagementService.sign( SignableData(wireTransaction.id, SignatureMetadata(4, Crypto.findSignatureScheme(nodeKey).schemeNumberID)), nodeKey)) diff --git a/core-tests/src/test/kotlin/net/corda/coretests/contracts/TransactionVerificationExceptionSerialisationTests.kt b/core-tests/src/test/kotlin/net/corda/coretests/contracts/TransactionVerificationExceptionSerialisationTests.kt index eca6e03c97..aca1d670dd 100644 --- a/core-tests/src/test/kotlin/net/corda/coretests/contracts/TransactionVerificationExceptionSerialisationTests.kt +++ b/core-tests/src/test/kotlin/net/corda/coretests/contracts/TransactionVerificationExceptionSerialisationTests.kt @@ -204,6 +204,18 @@ class TransactionVerificationExceptionSerialisationTests { assertEquals(exc.message, exc2.message) } + + @Test(timeout=300_000) + fun unsupportedHashTypeExceptionTest() { + val exc = TransactionVerificationException.UnsupportedHashTypeException(txid) + + val exc2 = DeserializationInput(factory).deserialize( + SerializationOutput(factory).serialize(exc, context), + context) + + assertEquals(exc.message, exc2.message) + } + @Test(timeout=300_000) fun transactionNetworkParameterOrderingExceptionTest() { val exception = TransactionVerificationException.TransactionNetworkParameterOrderingException( diff --git a/core-tests/src/test/kotlin/net/corda/coretests/crypto/PartialMerkleTreeTest.kt b/core-tests/src/test/kotlin/net/corda/coretests/crypto/PartialMerkleTreeTest.kt index edcca8e214..654d1cd684 100644 --- a/core-tests/src/test/kotlin/net/corda/coretests/crypto/PartialMerkleTreeTest.kt +++ b/core-tests/src/test/kotlin/net/corda/coretests/crypto/PartialMerkleTreeTest.kt @@ -5,7 +5,6 @@ import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.* import net.corda.core.crypto.* -import net.corda.core.crypto.SecureHash.Companion.zeroHash import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.node.NotaryInfo @@ -31,13 +30,16 @@ import net.corda.testing.node.ledger import org.junit.Before import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized import java.security.PublicKey import java.util.function.Predicate import java.util.stream.IntStream import kotlin.streams.toList import kotlin.test.* -class PartialMerkleTreeTest { +@RunWith(Parameterized::class) +class PartialMerkleTreeTest(private var digestService: DigestService) { private companion object { val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")) @@ -46,6 +48,14 @@ class PartialMerkleTreeTest { val MEGA_CORP_PUBKEY get() = megaCorp.publicKey val MINI_CORP get() = miniCorp.party val MINI_CORP_PUBKEY get() = miniCorp.publicKey + + @JvmStatic + @Parameterized.Parameters + fun data(): Collection = listOf( + DigestService.sha2_256, + DigestService.sha2_384, + DigestService.sha2_512 + ) } @Rule @@ -53,7 +63,7 @@ class PartialMerkleTreeTest { val testSerialization = SerializationEnvironmentRule() private val nodes = "abcdef" - private lateinit var hashed: List + private lateinit var hashed: List private lateinit var expectedRoot: SecureHash private lateinit var merkleTree: MerkleTree private lateinit var testLedger: LedgerDSL @@ -62,9 +72,10 @@ class PartialMerkleTreeTest { @Before fun init() { - hashed = nodes.map { it.serialize().sha256() } - expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash)).hash - merkleTree = MerkleTree.getMerkleTree(hashed) + digestService = DigestService.default + hashed = nodes.map { digestService.hash(it.serialize().bytes) } + expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(digestService.zeroHash, digestService.zeroHash), digestService).hash + merkleTree = MerkleTree.getMerkleTree(hashed, digestService) testLedger = MockServices( cordappPackages = emptyList(), @@ -107,29 +118,29 @@ class PartialMerkleTreeTest { @Test(timeout=300_000) fun `building Merkle tree - no hashes`() { - assertFailsWith { MerkleTree.getMerkleTree(emptyList()) } + assertFailsWith { MerkleTree.getMerkleTree(emptyList(), digestService) } } @Test(timeout=300_000) fun `building Merkle tree one node`() { val node = 'a'.serialize().sha256() - val mt = MerkleTree.getMerkleTree(listOf(node)) + val mt = MerkleTree.getMerkleTree(listOf(node), digestService) assertEquals(node, mt.hash) } @Test(timeout=300_000) fun `building Merkle tree odd number of nodes`() { val odd = hashed.subList(0, 3) - val h1 = hashed[0].hashConcat(hashed[1]) - val h2 = hashed[2].hashConcat(zeroHash) - val expected = h1.hashConcat(h2) - val mt = MerkleTree.getMerkleTree(odd) + val h1 = hashed[0].concatenate(hashed[1]) + val h2 = hashed[2].concatenate(digestService.zeroHash) + val expected = h1.concatenate(h2) + val mt = MerkleTree.getMerkleTree(odd, digestService) assertEquals(mt.hash, expected) } @Test(timeout=300_000) fun `check full tree`() { - val h = SecureHash.randomSHA256() + val h = digestService.randomHash() val left = MerkleTree.Node(h, MerkleTree.Node(h, MerkleTree.Leaf(h), MerkleTree.Leaf(h)), MerkleTree.Node(h, MerkleTree.Leaf(h), MerkleTree.Leaf(h))) val right = MerkleTree.Node(h, MerkleTree.Leaf(h), MerkleTree.Leaf(h)) @@ -226,7 +237,7 @@ class PartialMerkleTreeTest { fun `build Partial Merkle Tree - only duplicate leaves, less included failure`() { val leaves = "aaa" val hashes = leaves.map { it.serialize().hash } - val mt = MerkleTree.getMerkleTree(hashes) + val mt = MerkleTree.getMerkleTree(hashes, digestService) assertFailsWith { PartialMerkleTree.build(mt, hashes.subList(0, 1)) } } @@ -248,7 +259,7 @@ class PartialMerkleTreeTest { @Test(timeout=300_000) fun `verify Partial Merkle Tree - duplicate leaves failure`() { - val mt = MerkleTree.getMerkleTree(hashed.subList(0, 5)) // Odd number of leaves. Last one is duplicated. + val mt = MerkleTree.getMerkleTree(hashed.subList(0, 5), digestService) // Odd number of leaves. Last one is duplicated. val inclHashes = arrayListOf(hashed[3], hashed[4]) val pmt = PartialMerkleTree.build(mt, inclHashes) inclHashes.add(hashed[4]) @@ -266,7 +277,7 @@ class PartialMerkleTreeTest { fun `verify Partial Merkle Tree - wrong root`() { val inclHashes = listOf(hashed[3], hashed[5]) val pmt = PartialMerkleTree.build(merkleTree, inclHashes) - val wrongRoot = hashed[3].hashConcat(hashed[5]) + val wrongRoot = hashed[3].concatenate(hashed[5]) assertFalse(pmt.verify(wrongRoot, inclHashes)) } @@ -289,54 +300,55 @@ class PartialMerkleTreeTest { commands = testTx.commands, notary = notary, timeWindow = timeWindow, - privacySalt = privacySalt + privacySalt = privacySalt, + digestService = digestService ) } @Test(timeout=300_000) fun `Find leaf index`() { // A Merkle tree with 20 leaves. - val sampleLeaves = IntStream.rangeClosed(0, 19).toList().map { SecureHash.sha256(it.toString()) } - val merkleTree = MerkleTree.getMerkleTree(sampleLeaves) + val sampleLeaves = IntStream.rangeClosed(0, 19).toList().map { digestService.hash(it.toString()) } + val merkleTree = MerkleTree.getMerkleTree(sampleLeaves, digestService) // Provided hashes are not in the tree. - assertFailsWith { PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("20"))) } + assertFailsWith { PartialMerkleTree.build(merkleTree, listOf(digestService.hash("20"))) } // One of the provided hashes is not in the tree. - assertFailsWith { PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("20"), SecureHash.sha256("1"), SecureHash.sha256("5"))) } + assertFailsWith { PartialMerkleTree.build(merkleTree, listOf(digestService.hash("20"), digestService.hash("1"), digestService.hash("5"))) } - val pmt = PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("1"), SecureHash.sha256("5"), SecureHash.sha256("0"), SecureHash.sha256("19"))) + val pmt = PartialMerkleTree.build(merkleTree, listOf(digestService.hash("1"), digestService.hash("5"), digestService.hash("0"), digestService.hash("19"))) // First leaf. - assertEquals(0, pmt.leafIndex(SecureHash.sha256("0"))) + assertEquals(0, pmt.leafIndex(digestService.hash("0"))) // Second leaf. - assertEquals(1, pmt.leafIndex(SecureHash.sha256("1"))) + assertEquals(1, pmt.leafIndex(digestService.hash("1"))) // A random leaf. - assertEquals(5, pmt.leafIndex(SecureHash.sha256("5"))) + assertEquals(5, pmt.leafIndex(digestService.hash("5"))) // The last leaf. - assertEquals(19, pmt.leafIndex(SecureHash.sha256("19"))) + assertEquals(19, pmt.leafIndex(digestService.hash("19"))) // The provided hash is not in the tree. - assertFailsWith { pmt.leafIndex(SecureHash.sha256("10")) } + assertFailsWith { pmt.leafIndex(digestService.hash("10")) } // The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree). - assertFailsWith { pmt.leafIndex(SecureHash.sha256("30")) } + assertFailsWith { pmt.leafIndex(digestService.hash("30")) } - val pmtFirstElementOnly = PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("0"))) - assertEquals(0, pmtFirstElementOnly.leafIndex(SecureHash.sha256("0"))) + val pmtFirstElementOnly = PartialMerkleTree.build(merkleTree, listOf(digestService.hash("0"))) + assertEquals(0, pmtFirstElementOnly.leafIndex(digestService.hash("0"))) // The provided hash is not in the tree. - assertFailsWith { pmtFirstElementOnly.leafIndex(SecureHash.sha256("10")) } + assertFailsWith { pmtFirstElementOnly.leafIndex(digestService.hash("10")) } - val pmtLastElementOnly = PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("19"))) - assertEquals(19, pmtLastElementOnly.leafIndex(SecureHash.sha256("19"))) + val pmtLastElementOnly = PartialMerkleTree.build(merkleTree, listOf(digestService.hash("19"))) + assertEquals(19, pmtLastElementOnly.leafIndex(digestService.hash("19"))) // The provided hash is not in the tree. - assertFailsWith { pmtLastElementOnly.leafIndex(SecureHash.sha256("10")) } + assertFailsWith { pmtLastElementOnly.leafIndex(digestService.hash("10")) } - val pmtOneElement = PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("5"))) - assertEquals(5, pmtOneElement.leafIndex(SecureHash.sha256("5"))) + val pmtOneElement = PartialMerkleTree.build(merkleTree, listOf(digestService.hash("5"))) + assertEquals(5, pmtOneElement.leafIndex(digestService.hash("5"))) // The provided hash is not in the tree. - assertFailsWith { pmtOneElement.leafIndex(SecureHash.sha256("10")) } + assertFailsWith { pmtOneElement.leafIndex(digestService.hash("10")) } val pmtAllIncluded = PartialMerkleTree.build(merkleTree, sampleLeaves) - for (i in 0..19) assertEquals(i, pmtAllIncluded.leafIndex(SecureHash.sha256(i.toString()))) + for (i in 0..19) assertEquals(i, pmtAllIncluded.leafIndex(digestService.hash(i.toString()))) // The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree). - assertFailsWith { pmtAllIncluded.leafIndex(SecureHash.sha256("30")) } + assertFailsWith { pmtAllIncluded.leafIndex(digestService.hash("30")) } } @Test(timeout=300_000) diff --git a/core-tests/src/test/kotlin/net/corda/coretests/crypto/PartialMerkleTreeWithNamedHashMultiAlgTreeTest.kt b/core-tests/src/test/kotlin/net/corda/coretests/crypto/PartialMerkleTreeWithNamedHashMultiAlgTreeTest.kt new file mode 100644 index 0000000000..c131f2b0b1 --- /dev/null +++ b/core-tests/src/test/kotlin/net/corda/coretests/crypto/PartialMerkleTreeWithNamedHashMultiAlgTreeTest.kt @@ -0,0 +1,384 @@ +package net.corda.coretests.crypto + +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.whenever +import net.corda.core.contracts.Command +import net.corda.core.contracts.PrivacySalt +import net.corda.core.contracts.StateRef +import net.corda.core.contracts.TimeWindow +import net.corda.core.contracts.TransactionState +import net.corda.core.crypto.MerkleTree +import net.corda.core.crypto.MerkleTreeException +import net.corda.core.crypto.PartialMerkleTree +import net.corda.core.crypto.DigestService +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.SecureHash.Companion.SHA2_384 +import net.corda.core.crypto.SecureHash.Companion.hashAs +import net.corda.core.crypto.hashAs +import net.corda.core.crypto.keys +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party +import net.corda.core.node.NotaryInfo +import net.corda.core.node.services.IdentityService +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import net.corda.core.transactions.ReferenceStateRef +import net.corda.core.transactions.WireTransaction +import net.corda.core.utilities.OpaqueBytes +import net.corda.finance.DOLLARS +import net.corda.finance.`issued by` +import net.corda.finance.contracts.asset.Cash +import net.corda.testing.common.internal.testNetworkParameters +import net.corda.testing.core.DUMMY_NOTARY_NAME +import net.corda.testing.core.SerializationEnvironmentRule +import net.corda.testing.core.TestIdentity +import net.corda.testing.dsl.LedgerDSL +import net.corda.testing.dsl.TestLedgerDSLInterpreter +import net.corda.testing.dsl.TestTransactionDSLInterpreter +import net.corda.coretesting.internal.TEST_TX_TIME +import net.corda.testing.internal.createWireTransaction +import net.corda.testing.node.MockServices +import net.corda.testing.node.ledger +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.jupiter.api.Assertions.assertEquals +import java.security.PublicKey +import java.util.function.Predicate +import java.util.stream.IntStream +import kotlin.streams.toList +import kotlin.test.assertFailsWith + +@Suppress("FunctionNaming") +class PartialMerkleTreeWithNamedHashMultiAlgTreeTest { + private companion object { + val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party + val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")) + val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")) + val MEGA_CORP get() = megaCorp.party + val MEGA_CORP_PUBKEY get() = megaCorp.publicKey + val MINI_CORP get() = miniCorp.party + val MINI_CORP_PUBKEY get() = miniCorp.publicKey + + fun sha2_384(str: String): SecureHash { + return hashAs(SHA2_384, str.toByteArray()) + } + + fun OpaqueBytes.sha2_384(): SecureHash = hashAs(SHA2_384) + } + + @Rule + @JvmField + val testSerialization = SerializationEnvironmentRule() + + private val nodes = "abcdef" + private lateinit var hashed: List + private lateinit var expectedRoot: SecureHash + private lateinit var merkleTree: MerkleTree + private lateinit var testLedger: LedgerDSL + private lateinit var txs: List + private lateinit var testTx: WireTransaction + + @Before + fun init() { + hashed = nodes.map { it.serialize().sha2_384() } + val zeroHash = SecureHash.zeroHashFor(SHA2_384) + expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash), DigestService.sha2_256).hash + merkleTree = MerkleTree.getMerkleTree(hashed, DigestService.sha2_256) + + testLedger = MockServices( + cordappPackages = emptyList(), + initialIdentity = TestIdentity(MEGA_CORP.name), + identityService = mock().also { + doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) + }, + networkParameters = testNetworkParameters(minimumPlatformVersion = 4, notaries = listOf(NotaryInfo(DUMMY_NOTARY, true))) + ).ledger(DUMMY_NOTARY) { + unverifiedTransaction { + attachments(Cash.PROGRAM_ID) + output(Cash.PROGRAM_ID, "MEGA_CORP cash", + Cash.State( + amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1), + owner = MEGA_CORP)) + output(Cash.PROGRAM_ID, "dummy cash 1", + Cash.State( + amount = 900.DOLLARS `issued by` MEGA_CORP.ref(1, 1), + owner = MINI_CORP)) + } + transaction { + attachments(Cash.PROGRAM_ID) + input("MEGA_CORP cash") + reference("dummy cash 1") + output(Cash.PROGRAM_ID, "MEGA_CORP cash".output().copy(owner = MINI_CORP)) + command(MEGA_CORP_PUBKEY, Cash.Commands.Move()) + timeWindow(TEST_TX_TIME) + this.verifies() + } + } + txs = testLedger.interpreter.transactionsToVerify + testTx = txs[0] + } + + // Building full Merkle Tree tests. + @Test(timeout=300_000) + fun `building Merkle tree with 6 nodes - no rightmost nodes`() { + assertEquals(expectedRoot, merkleTree.hash) + } + + @Test(timeout=300_000) + fun `building Merkle tree - no hashes`() { + assertFailsWith { MerkleTree.getMerkleTree(emptyList(), DigestService.sha2_256) } + } + + @Test(timeout=300_000) + fun `building Merkle tree one node`() { + val node = 'a'.serialize().sha2_384() + val mt = MerkleTree.getMerkleTree(listOf(node), DigestService.sha2_256) + assertEquals(node, mt.hash) + } + + @Test(timeout=300_000) + fun `building Merkle tree odd number of nodes`() { + val odd = hashed.subList(0, 3) + val h1 = hashed[0].concatenateAs(SecureHash.SHA2_256, hashed[1]) + val h2 = hashed[2].concatenateAs(SecureHash.SHA2_256, SecureHash.zeroHashFor(SHA2_384)) + val expected = h1.concatenateAs(SecureHash.SHA2_256, h2) + val mt = MerkleTree.getMerkleTree(odd, DigestService.sha2_256) + assertEquals(mt.hash, expected) + } + + @Test(timeout=300_000) + fun `check full tree`() { + val h = SecureHash.random(SHA2_384) + val left = MerkleTree.Node(h, MerkleTree.Node(h, MerkleTree.Leaf(h), MerkleTree.Leaf(h)), + MerkleTree.Node(h, MerkleTree.Leaf(h), MerkleTree.Leaf(h))) + val right = MerkleTree.Node(h, MerkleTree.Leaf(h), MerkleTree.Leaf(h)) + val tree = MerkleTree.Node(h, left, right) + assertFailsWith { PartialMerkleTree.build(tree, listOf(h)) } + PartialMerkleTree.build(right, listOf(h, h)) // Node and two leaves. + PartialMerkleTree.build(MerkleTree.Leaf(h), listOf(h)) // Just a leaf. + } + + @Test(timeout=300_000) + fun `building Merkle tree for a tx and nonce test`() { + fun filtering(elem: Any): Boolean { + return when (elem) { + is StateRef -> true + is TransactionState<*> -> elem.data.participants[0].owningKey.keys == MINI_CORP_PUBKEY.keys + is Command<*> -> MEGA_CORP_PUBKEY in elem.signers + is TimeWindow -> true + is PublicKey -> elem == MEGA_CORP_PUBKEY + else -> false + } + } + + val d = testTx.serialize().deserialize() + assertEquals(testTx.id, d.id) + + val ftx = testTx.buildFilteredTransaction(Predicate(::filtering)) + + // We expect 5 and not 4 component groups, because there is at least one command in the ftx and thus, + // the signers component is also sent (required for visibility purposes). + assertEquals(5, ftx.filteredComponentGroups.size) + assertEquals(1, ftx.inputs.size) + assertEquals(0, ftx.references.size) + assertEquals(0, ftx.attachments.size) + assertEquals(1, ftx.outputs.size) + assertEquals(1, ftx.commands.size) + assertNull(ftx.notary) + assertNotNull(ftx.timeWindow) + assertNull(ftx.networkParametersHash) + ftx.verify() + } + + @Test(timeout=300_000) + fun `same transactions with different notaries have different ids`() { + // We even use the same privacySalt, and thus the only difference between the two transactions is the notary party. + val privacySalt = PrivacySalt() + val wtx1 = makeSimpleCashWtx(DUMMY_NOTARY, privacySalt) + val wtx2 = makeSimpleCashWtx(MEGA_CORP, privacySalt) + assertEquals(wtx1.privacySalt, wtx2.privacySalt) + assertNotEquals(wtx1.id, wtx2.id) + } + + @Test(timeout=300_000) + fun `nothing filtered`() { + val ftxNothing = testTx.buildFilteredTransaction(Predicate { false }) + assertTrue(ftxNothing.componentGroups.isEmpty()) + assertTrue(ftxNothing.attachments.isEmpty()) + assertTrue(ftxNothing.commands.isEmpty()) + assertTrue(ftxNothing.inputs.isEmpty()) + assertTrue(ftxNothing.references.isEmpty()) + assertTrue(ftxNothing.outputs.isEmpty()) + assertNull(ftxNothing.timeWindow) + assertTrue(ftxNothing.availableComponentGroups.flatten().isEmpty()) + assertNull(ftxNothing.networkParametersHash) + ftxNothing.verify() // We allow empty ftx transactions (eg from a timestamp authority that blindly signs). + } + + // Partial Merkle Tree building tests. + @Test(timeout=300_000) + fun `build Partial Merkle Tree, only left nodes branch`() { + val inclHashes = listOf(hashed[3], hashed[5]) + val pmt = PartialMerkleTree.build(merkleTree, inclHashes) + assertTrue(pmt.verify(merkleTree.hash, inclHashes)) + } + + @Test(timeout=300_000) + fun `build Partial Merkle Tree, include zero leaves`() { + val pmt = PartialMerkleTree.build(merkleTree, emptyList()) + assertTrue(pmt.verify(merkleTree.hash, emptyList())) + } + + @Test(timeout=300_000) + fun `build Partial Merkle Tree, include all leaves`() { + val pmt = PartialMerkleTree.build(merkleTree, hashed) + assertTrue(pmt.verify(merkleTree.hash, hashed)) + } + + @Test(timeout=300_000) + fun `build Partial Merkle Tree - duplicate leaves failure`() { + val inclHashes = arrayListOf(hashed[3], hashed[5], hashed[3], hashed[5]) + assertFailsWith { PartialMerkleTree.build(merkleTree, inclHashes) } + } + + @Test(timeout=300_000) + fun `build Partial Merkle Tree - only duplicate leaves, less included failure`() { + val leaves = "aaa" + val hashes = leaves.map { it.serialize().hash } + val mt = MerkleTree.getMerkleTree(hashes, DigestService.sha2_256) + assertFailsWith { PartialMerkleTree.build(mt, hashes.subList(0, 1)) } + } + + @Test(timeout=300_000) + fun `verify Partial Merkle Tree - too many leaves failure`() { + val inclHashes = arrayListOf(hashed[3], hashed[5]) + val pmt = PartialMerkleTree.build(merkleTree, inclHashes) + inclHashes.add(hashed[0]) + assertFalse(pmt.verify(merkleTree.hash, inclHashes)) + } + + @Test(timeout=300_000) + fun `verify Partial Merkle Tree - too little leaves failure`() { + val inclHashes = arrayListOf(hashed[3], hashed[5], hashed[0]) + val pmt = PartialMerkleTree.build(merkleTree, inclHashes) + inclHashes.remove(hashed[0]) + assertFalse(pmt.verify(merkleTree.hash, inclHashes)) + } + + @Test(timeout=300_000) + fun `verify Partial Merkle Tree - duplicate leaves failure`() { + val mt = MerkleTree.getMerkleTree(hashed.subList(0, 5), DigestService.sha2_256) // Odd number of leaves. Last one is duplicated. + val inclHashes = arrayListOf(hashed[3], hashed[4]) + val pmt = PartialMerkleTree.build(mt, inclHashes) + inclHashes.add(hashed[4]) + assertFalse(pmt.verify(mt.hash, inclHashes)) + } + + @Test(timeout=300_000) + fun `verify Partial Merkle Tree - different leaves failure`() { + val inclHashes = arrayListOf(hashed[3], hashed[5]) + val pmt = PartialMerkleTree.build(merkleTree, inclHashes) + assertFalse(pmt.verify(merkleTree.hash, listOf(hashed[2], hashed[4]))) + } + + @Test(timeout=300_000) + fun `verify Partial Merkle Tree - wrong root`() { + val inclHashes = listOf(hashed[3], hashed[5]) + val pmt = PartialMerkleTree.build(merkleTree, inclHashes) + val wrongRoot = hashed[3].concatenate(hashed[5]) + assertFalse(pmt.verify(wrongRoot, inclHashes)) + } + + @Test(expected = Exception::class, timeout=300_000) + fun `hash map serialization not allowed`() { + val hm1 = hashMapOf("a" to 1, "b" to 2, "c" to 3, "e" to 4) + hm1.serialize() + } + + private fun makeSimpleCashWtx( + notary: Party, + privacySalt: PrivacySalt = PrivacySalt(), + timeWindow: TimeWindow? = null, + attachments: List = emptyList() + ): WireTransaction { + return createWireTransaction( + inputs = testTx.inputs, + attachments = attachments, + outputs = testTx.outputs, + commands = testTx.commands, + notary = notary, + timeWindow = timeWindow, + privacySalt = privacySalt + ) + } + + @Test(timeout=300_000) + fun `Find leaf index`() { + // A Merkle tree with 20 leaves. + val sampleLeaves = IntStream.rangeClosed(0, 19).toList().map { sha2_384(it.toString()) } + val merkleTree = MerkleTree.getMerkleTree(sampleLeaves, DigestService.sha2_256) + + // Provided hashes are not in the tree. + assertFailsWith { PartialMerkleTree.build(merkleTree, listOf(sha2_384("20"))) } + // One of the provided hashes is not in the tree. + assertFailsWith { PartialMerkleTree.build(merkleTree, listOf(sha2_384("20"), sha2_384("1"), sha2_384("5"))) } + + val pmt = PartialMerkleTree.build(merkleTree, listOf(sha2_384("1"), sha2_384("5"), sha2_384("0"), sha2_384("19"))) + // First leaf. + assertEquals(0, pmt.leafIndex(sha2_384("0"))) + // Second leaf. + assertEquals(1, pmt.leafIndex(sha2_384("1"))) + // A random leaf. + assertEquals(5, pmt.leafIndex(sha2_384("5"))) + // The last leaf. + assertEquals(19, pmt.leafIndex(sha2_384("19"))) + // The provided hash is not in the tree. + assertFailsWith { pmt.leafIndex(sha2_384("10")) } + // The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree). + assertFailsWith { pmt.leafIndex(sha2_384("30")) } + + val pmtFirstElementOnly = PartialMerkleTree.build(merkleTree, listOf(sha2_384("0"))) + assertEquals(0, pmtFirstElementOnly.leafIndex(sha2_384("0"))) + // The provided hash is not in the tree. + assertFailsWith { pmtFirstElementOnly.leafIndex(sha2_384("10")) } + + val pmtLastElementOnly = PartialMerkleTree.build(merkleTree, listOf(sha2_384("19"))) + assertEquals(19, pmtLastElementOnly.leafIndex(sha2_384("19"))) + // The provided hash is not in the tree. + assertFailsWith { pmtLastElementOnly.leafIndex(sha2_384("10")) } + + val pmtOneElement = PartialMerkleTree.build(merkleTree, listOf(sha2_384("5"))) + assertEquals(5, pmtOneElement.leafIndex(sha2_384("5"))) + // The provided hash is not in the tree. + assertFailsWith { pmtOneElement.leafIndex(sha2_384("10")) } + + val pmtAllIncluded = PartialMerkleTree.build(merkleTree, sampleLeaves) + for (i in 0..19) assertEquals(i, pmtAllIncluded.leafIndex(sha2_384(i.toString()))) + // The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree). + assertFailsWith { pmtAllIncluded.leafIndex(sha2_384("30")) } + } + + @Test(timeout=300_000) + fun `building Merkle for reference states only`() { + fun filtering(elem: Any): Boolean { + return when (elem) { + is ReferenceStateRef -> true + else -> false + } + } + + val ftx = testTx.buildFilteredTransaction(Predicate(::filtering)) + + assertEquals(1, ftx.filteredComponentGroups.size) + assertEquals(0, ftx.inputs.size) + assertEquals(1, ftx.references.size) + ftx.verify() + } +} \ No newline at end of file diff --git a/core-tests/src/test/kotlin/net/corda/coretests/crypto/PartialMerkleTreeWithNamedHashTest.kt b/core-tests/src/test/kotlin/net/corda/coretests/crypto/PartialMerkleTreeWithNamedHashTest.kt new file mode 100644 index 0000000000..524dec4bb0 --- /dev/null +++ b/core-tests/src/test/kotlin/net/corda/coretests/crypto/PartialMerkleTreeWithNamedHashTest.kt @@ -0,0 +1,384 @@ +package net.corda.coretests.crypto + +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.whenever +import net.corda.core.contracts.Command +import net.corda.core.contracts.PrivacySalt +import net.corda.core.contracts.StateRef +import net.corda.core.contracts.TimeWindow +import net.corda.core.contracts.TransactionState +import net.corda.core.crypto.MerkleTree +import net.corda.core.crypto.MerkleTreeException +import net.corda.core.crypto.PartialMerkleTree +import net.corda.core.crypto.DigestService +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.SecureHash.Companion.SHA2_384 +import net.corda.core.crypto.SecureHash.Companion.hashAs +import net.corda.core.crypto.hashAs +import net.corda.core.crypto.keys +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party +import net.corda.core.node.NotaryInfo +import net.corda.core.node.services.IdentityService +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import net.corda.core.transactions.ReferenceStateRef +import net.corda.core.transactions.WireTransaction +import net.corda.core.utilities.OpaqueBytes +import net.corda.finance.DOLLARS +import net.corda.finance.`issued by` +import net.corda.finance.contracts.asset.Cash +import net.corda.testing.common.internal.testNetworkParameters +import net.corda.testing.core.DUMMY_NOTARY_NAME +import net.corda.testing.core.SerializationEnvironmentRule +import net.corda.testing.core.TestIdentity +import net.corda.testing.dsl.LedgerDSL +import net.corda.testing.dsl.TestLedgerDSLInterpreter +import net.corda.testing.dsl.TestTransactionDSLInterpreter +import net.corda.coretesting.internal.TEST_TX_TIME +import net.corda.testing.internal.createWireTransaction +import net.corda.testing.node.MockServices +import net.corda.testing.node.ledger +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.jupiter.api.Assertions.assertEquals +import java.security.PublicKey +import java.util.function.Predicate +import java.util.stream.IntStream +import kotlin.streams.toList +import kotlin.test.assertFailsWith + +@Suppress("FunctionNaming") +class PartialMerkleTreeWithNamedHashTest { + private companion object { + val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party + val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")) + val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB")) + val MEGA_CORP get() = megaCorp.party + val MEGA_CORP_PUBKEY get() = megaCorp.publicKey + val MINI_CORP get() = miniCorp.party + val MINI_CORP_PUBKEY get() = miniCorp.publicKey + + fun sha2_384(str: String): SecureHash { + return hashAs(SHA2_384, str.toByteArray()) + } + + fun OpaqueBytes.sha2_384(): SecureHash = hashAs(SHA2_384) + } + + @Rule + @JvmField + val testSerialization = SerializationEnvironmentRule() + + private val nodes = "abcdef" + private lateinit var hashed: List + private lateinit var expectedRoot: SecureHash + private lateinit var merkleTree: MerkleTree + private lateinit var testLedger: LedgerDSL + private lateinit var txs: List + private lateinit var testTx: WireTransaction + + @Before + fun init() { + hashed = nodes.map { it.serialize().sha2_384() } + val zeroHash = SecureHash.zeroHashFor(SHA2_384) + expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash), DigestService.sha2_384).hash + merkleTree = MerkleTree.getMerkleTree(hashed, DigestService.sha2_384) + + testLedger = MockServices( + cordappPackages = emptyList(), + initialIdentity = TestIdentity(MEGA_CORP.name), + identityService = mock().also { + doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY) + }, + networkParameters = testNetworkParameters(minimumPlatformVersion = 4, notaries = listOf(NotaryInfo(DUMMY_NOTARY, true))) + ).ledger(DUMMY_NOTARY) { + unverifiedTransaction { + attachments(Cash.PROGRAM_ID) + output(Cash.PROGRAM_ID, "MEGA_CORP cash", + Cash.State( + amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1), + owner = MEGA_CORP)) + output(Cash.PROGRAM_ID, "dummy cash 1", + Cash.State( + amount = 900.DOLLARS `issued by` MEGA_CORP.ref(1, 1), + owner = MINI_CORP)) + } + transaction { + attachments(Cash.PROGRAM_ID) + input("MEGA_CORP cash") + reference("dummy cash 1") + output(Cash.PROGRAM_ID, "MEGA_CORP cash".output().copy(owner = MINI_CORP)) + command(MEGA_CORP_PUBKEY, Cash.Commands.Move()) + timeWindow(TEST_TX_TIME) + this.verifies() + } + } + txs = testLedger.interpreter.transactionsToVerify + testTx = txs[0] + } + + // Building full Merkle Tree tests. + @Test(timeout=300_000) + fun `building Merkle tree with 6 nodes - no rightmost nodes`() { + assertEquals(expectedRoot, merkleTree.hash) + } + + @Test(timeout=300_000) + fun `building Merkle tree - no hashes`() { + assertFailsWith { MerkleTree.getMerkleTree(emptyList(), DigestService.sha2_384) } + } + + @Test(timeout=300_000) + fun `building Merkle tree one node`() { + val node = 'a'.serialize().sha2_384() + val mt = MerkleTree.getMerkleTree(listOf(node), DigestService.sha2_384) + assertEquals(node, mt.hash) + } + + @Test(timeout=300_000) + fun `building Merkle tree odd number of nodes`() { + val odd = hashed.subList(0, 3) + val h1 = hashed[0].concatenate(hashed[1]) + val h2 = hashed[2].concatenate(SecureHash.zeroHashFor(SHA2_384)) + val expected = h1.concatenate(h2) + val mt = MerkleTree.getMerkleTree(odd, DigestService.sha2_384) + assertEquals(mt.hash, expected) + } + + @Test(timeout=300_000) + fun `check full tree`() { + val h = SecureHash.random(SHA2_384) + val left = MerkleTree.Node(h, MerkleTree.Node(h, MerkleTree.Leaf(h), MerkleTree.Leaf(h)), + MerkleTree.Node(h, MerkleTree.Leaf(h), MerkleTree.Leaf(h))) + val right = MerkleTree.Node(h, MerkleTree.Leaf(h), MerkleTree.Leaf(h)) + val tree = MerkleTree.Node(h, left, right) + assertFailsWith { PartialMerkleTree.build(tree, listOf(h)) } + PartialMerkleTree.build(right, listOf(h, h)) // Node and two leaves. + PartialMerkleTree.build(MerkleTree.Leaf(h), listOf(h)) // Just a leaf. + } + + @Test(timeout=300_000) + fun `building Merkle tree for a tx and nonce test`() { + fun filtering(elem: Any): Boolean { + return when (elem) { + is StateRef -> true + is TransactionState<*> -> elem.data.participants[0].owningKey.keys == MINI_CORP_PUBKEY.keys + is Command<*> -> MEGA_CORP_PUBKEY in elem.signers + is TimeWindow -> true + is PublicKey -> elem == MEGA_CORP_PUBKEY + else -> false + } + } + + val d = testTx.serialize().deserialize() + assertEquals(testTx.id, d.id) + + val ftx = testTx.buildFilteredTransaction(Predicate(::filtering)) + + // We expect 5 and not 4 component groups, because there is at least one command in the ftx and thus, + // the signers component is also sent (required for visibility purposes). + assertEquals(5, ftx.filteredComponentGroups.size) + assertEquals(1, ftx.inputs.size) + assertEquals(0, ftx.references.size) + assertEquals(0, ftx.attachments.size) + assertEquals(1, ftx.outputs.size) + assertEquals(1, ftx.commands.size) + assertNull(ftx.notary) + assertNotNull(ftx.timeWindow) + assertNull(ftx.networkParametersHash) + ftx.verify() + } + + @Test(timeout=300_000) + fun `same transactions with different notaries have different ids`() { + // We even use the same privacySalt, and thus the only difference between the two transactions is the notary party. + val privacySalt = PrivacySalt() + val wtx1 = makeSimpleCashWtx(DUMMY_NOTARY, privacySalt) + val wtx2 = makeSimpleCashWtx(MEGA_CORP, privacySalt) + assertEquals(wtx1.privacySalt, wtx2.privacySalt) + assertNotEquals(wtx1.id, wtx2.id) + } + + @Test(timeout=300_000) + fun `nothing filtered`() { + val ftxNothing = testTx.buildFilteredTransaction(Predicate { false }) + assertTrue(ftxNothing.componentGroups.isEmpty()) + assertTrue(ftxNothing.attachments.isEmpty()) + assertTrue(ftxNothing.commands.isEmpty()) + assertTrue(ftxNothing.inputs.isEmpty()) + assertTrue(ftxNothing.references.isEmpty()) + assertTrue(ftxNothing.outputs.isEmpty()) + assertNull(ftxNothing.timeWindow) + assertTrue(ftxNothing.availableComponentGroups.flatten().isEmpty()) + assertNull(ftxNothing.networkParametersHash) + ftxNothing.verify() // We allow empty ftx transactions (eg from a timestamp authority that blindly signs). + } + + // Partial Merkle Tree building tests. + @Test(timeout=300_000) + fun `build Partial Merkle Tree, only left nodes branch`() { + val inclHashes = listOf(hashed[3], hashed[5]) + val pmt = PartialMerkleTree.build(merkleTree, inclHashes) + assertTrue(pmt.verify(merkleTree.hash, inclHashes)) + } + + @Test(timeout=300_000) + fun `build Partial Merkle Tree, include zero leaves`() { + val pmt = PartialMerkleTree.build(merkleTree, emptyList()) + assertTrue(pmt.verify(merkleTree.hash, emptyList())) + } + + @Test(timeout=300_000) + fun `build Partial Merkle Tree, include all leaves`() { + val pmt = PartialMerkleTree.build(merkleTree, hashed) + assertTrue(pmt.verify(merkleTree.hash, hashed)) + } + + @Test(timeout=300_000) + fun `build Partial Merkle Tree - duplicate leaves failure`() { + val inclHashes = arrayListOf(hashed[3], hashed[5], hashed[3], hashed[5]) + assertFailsWith { PartialMerkleTree.build(merkleTree, inclHashes) } + } + + @Test(timeout=300_000) + fun `build Partial Merkle Tree - only duplicate leaves, less included failure`() { + val leaves = "aaa" + val hashes = leaves.map { it.serialize().hash } + val mt = MerkleTree.getMerkleTree(hashes, DigestService.sha2_384) + assertFailsWith { PartialMerkleTree.build(mt, hashes.subList(0, 1)) } + } + + @Test(timeout=300_000) + fun `verify Partial Merkle Tree - too many leaves failure`() { + val inclHashes = arrayListOf(hashed[3], hashed[5]) + val pmt = PartialMerkleTree.build(merkleTree, inclHashes) + inclHashes.add(hashed[0]) + assertFalse(pmt.verify(merkleTree.hash, inclHashes)) + } + + @Test(timeout=300_000) + fun `verify Partial Merkle Tree - too little leaves failure`() { + val inclHashes = arrayListOf(hashed[3], hashed[5], hashed[0]) + val pmt = PartialMerkleTree.build(merkleTree, inclHashes) + inclHashes.remove(hashed[0]) + assertFalse(pmt.verify(merkleTree.hash, inclHashes)) + } + + @Test(timeout=300_000) + fun `verify Partial Merkle Tree - duplicate leaves failure`() { + val mt = MerkleTree.getMerkleTree(hashed.subList(0, 5), DigestService.sha2_384) // Odd number of leaves. Last one is duplicated. + val inclHashes = arrayListOf(hashed[3], hashed[4]) + val pmt = PartialMerkleTree.build(mt, inclHashes) + inclHashes.add(hashed[4]) + assertFalse(pmt.verify(mt.hash, inclHashes)) + } + + @Test(timeout=300_000) + fun `verify Partial Merkle Tree - different leaves failure`() { + val inclHashes = arrayListOf(hashed[3], hashed[5]) + val pmt = PartialMerkleTree.build(merkleTree, inclHashes) + assertFalse(pmt.verify(merkleTree.hash, listOf(hashed[2], hashed[4]))) + } + + @Test(timeout=300_000) + fun `verify Partial Merkle Tree - wrong root`() { + val inclHashes = listOf(hashed[3], hashed[5]) + val pmt = PartialMerkleTree.build(merkleTree, inclHashes) + val wrongRoot = hashed[3].concatenate(hashed[5]) + assertFalse(pmt.verify(wrongRoot, inclHashes)) + } + + @Test(expected = Exception::class, timeout=300_000) + fun `hash map serialization not allowed`() { + val hm1 = hashMapOf("a" to 1, "b" to 2, "c" to 3, "e" to 4) + hm1.serialize() + } + + private fun makeSimpleCashWtx( + notary: Party, + privacySalt: PrivacySalt = PrivacySalt(), + timeWindow: TimeWindow? = null, + attachments: List = emptyList() + ): WireTransaction { + return createWireTransaction( + inputs = testTx.inputs, + attachments = attachments, + outputs = testTx.outputs, + commands = testTx.commands, + notary = notary, + timeWindow = timeWindow, + privacySalt = privacySalt + ) + } + + @Test(timeout=300_000) + fun `Find leaf index`() { + // A Merkle tree with 20 leaves. + val sampleLeaves = IntStream.rangeClosed(0, 19).toList().map { sha2_384(it.toString()) } + val merkleTree = MerkleTree.getMerkleTree(sampleLeaves, DigestService.sha2_384) + + // Provided hashes are not in the tree. + assertFailsWith { PartialMerkleTree.build(merkleTree, listOf(sha2_384("20"))) } + // One of the provided hashes is not in the tree. + assertFailsWith { PartialMerkleTree.build(merkleTree, listOf(sha2_384("20"), sha2_384("1"), sha2_384("5"))) } + + val pmt = PartialMerkleTree.build(merkleTree, listOf(sha2_384("1"), sha2_384("5"), sha2_384("0"), sha2_384("19"))) + // First leaf. + assertEquals(0, pmt.leafIndex(sha2_384("0"))) + // Second leaf. + assertEquals(1, pmt.leafIndex(sha2_384("1"))) + // A random leaf. + assertEquals(5, pmt.leafIndex(sha2_384("5"))) + // The last leaf. + assertEquals(19, pmt.leafIndex(sha2_384("19"))) + // The provided hash is not in the tree. + assertFailsWith { pmt.leafIndex(sha2_384("10")) } + // The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree). + assertFailsWith { pmt.leafIndex(sha2_384("30")) } + + val pmtFirstElementOnly = PartialMerkleTree.build(merkleTree, listOf(sha2_384("0"))) + assertEquals(0, pmtFirstElementOnly.leafIndex(sha2_384("0"))) + // The provided hash is not in the tree. + assertFailsWith { pmtFirstElementOnly.leafIndex(sha2_384("10")) } + + val pmtLastElementOnly = PartialMerkleTree.build(merkleTree, listOf(sha2_384("19"))) + assertEquals(19, pmtLastElementOnly.leafIndex(sha2_384("19"))) + // The provided hash is not in the tree. + assertFailsWith { pmtLastElementOnly.leafIndex(sha2_384("10")) } + + val pmtOneElement = PartialMerkleTree.build(merkleTree, listOf(sha2_384("5"))) + assertEquals(5, pmtOneElement.leafIndex(sha2_384("5"))) + // The provided hash is not in the tree. + assertFailsWith { pmtOneElement.leafIndex(sha2_384("10")) } + + val pmtAllIncluded = PartialMerkleTree.build(merkleTree, sampleLeaves) + for (i in 0..19) assertEquals(i, pmtAllIncluded.leafIndex(sha2_384(i.toString()))) + // The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree). + assertFailsWith { pmtAllIncluded.leafIndex(sha2_384("30")) } + } + + @Test(timeout=300_000) + fun `building Merkle for reference states only`() { + fun filtering(elem: Any): Boolean { + return when (elem) { + is ReferenceStateRef -> true + else -> false + } + } + + val ftx = testTx.buildFilteredTransaction(Predicate(::filtering)) + + assertEquals(1, ftx.filteredComponentGroups.size) + assertEquals(0, ftx.inputs.size) + assertEquals(1, ftx.references.size) + ftx.verify() + } +} \ No newline at end of file diff --git a/core-tests/src/test/kotlin/net/corda/coretests/crypto/TransactionSignatureTest.kt b/core-tests/src/test/kotlin/net/corda/coretests/crypto/TransactionSignatureTest.kt index b2f7d01e61..734c0c0d10 100644 --- a/core-tests/src/test/kotlin/net/corda/coretests/crypto/TransactionSignatureTest.kt +++ b/core-tests/src/test/kotlin/net/corda/coretests/crypto/TransactionSignatureTest.kt @@ -50,13 +50,14 @@ class TransactionSignatureTest { @Test(timeout=300_000) fun `Verify multi-tx signature`() { val keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger.valueOf(1234567890L)) + // Deterministically create 5 txIds. val txIds: List = IntRange(0, 4).map { byteArrayOf(it.toByte()).sha256() } // Multi-tx signature. val txSignature = signMultipleTx(txIds, keyPair) // The hash of all txIds are used as leaves. - val merkleTree = MerkleTree.getMerkleTree(txIds.map { it.sha256() }) + val merkleTree = MerkleTree.getMerkleTree(txIds.map { it.reHash() }) // We haven't added the partial tree yet. assertNull(txSignature.partialMerkleTree) @@ -64,7 +65,7 @@ class TransactionSignatureTest { assertFailsWith { Crypto.doVerify(txIds[3], txSignature) } // Create a partial tree for one tx. - val pmt = PartialMerkleTree.build(merkleTree, listOf(txIds[0].sha256())) + val pmt = PartialMerkleTree.build(merkleTree, listOf(txIds[0].reHash())) // Add the partial Merkle tree to the tx signature. val txSignatureWithTree = TransactionSignature(txSignature.bytes, txSignature.by, txSignature.signatureMetadata, pmt) @@ -84,7 +85,7 @@ class TransactionSignatureTest { // What if we send the Full tree. This could be used if notaries didn't want to create a per tx partial tree. // Create a partial tree for all txs, thus all leaves are included. - val pmtFull = PartialMerkleTree.build(merkleTree, txIds.map { it.sha256() }) + val pmtFull = PartialMerkleTree.build(merkleTree, txIds.map { it.reHash() }) // Add the partial Merkle tree to the tx. val txSignatureWithFullTree = TransactionSignature(txSignature.bytes, txSignature.by, txSignature.signatureMetadata, pmtFull) diff --git a/core-tests/src/test/kotlin/net/corda/coretests/transactions/CompatibleTransactionTests.kt b/core-tests/src/test/kotlin/net/corda/coretests/transactions/CompatibleTransactionTests.kt index 4fd185b5e0..d3a9ac43cb 100644 --- a/core-tests/src/test/kotlin/net/corda/coretests/transactions/CompatibleTransactionTests.kt +++ b/core-tests/src/test/kotlin/net/corda/coretests/transactions/CompatibleTransactionTests.kt @@ -417,7 +417,9 @@ class CompatibleTransactionTests { @Test(timeout=300_000) fun `FilteredTransaction signer manipulation tests`() { // Required to call the private constructor. - val ftxConstructor = FilteredTransaction::class.constructors.first() + //val ftxConstructor = FilteredTransaction::class.constructors.first() + // TODO(iee): verify if it was a hard requirement that the constructor must be the first() + val ftxConstructor = FilteredTransaction::class.constructors.last() // 1st and 3rd commands require a signature from KEY_1. val twoCommandsforKey1 = listOf(dummyCommand(DUMMY_KEY_1.public, DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_1.public)) @@ -429,7 +431,7 @@ class CompatibleTransactionTests { timeWindowGroup, ComponentGroup(SIGNERS_GROUP.ordinal, twoCommandsforKey1.map { it.signers.serialize() }) ) - val wtx = WireTransaction(componentGroups = componentGroups, privacySalt = PrivacySalt()) + val wtx = WireTransaction(componentGroups = componentGroups, privacySalt = PrivacySalt(), digestService = DigestService.default) // Filter KEY_1 commands (commands 1 and 3). fun filterKEY1Commands(elem: Any): Boolean { @@ -453,7 +455,7 @@ class CompatibleTransactionTests { // val commandDataComponents = key1CommandsFtx.filteredComponentGroups[0].components val commandDataHashes = wtx.accessAvailableComponentHashes()[ComponentGroupEnum.COMMANDS_GROUP.ordinal]!! val noLastCommandDataPMT = PartialMerkleTree.build( - MerkleTree.getMerkleTree(commandDataHashes), + MerkleTree.getMerkleTree(commandDataHashes, wtx.digestService), commandDataHashes.subList(0, 1) ) val noLastCommandDataComponents = key1CommandsFtx.filteredComponentGroups[0].components.subList(0, 1) @@ -468,7 +470,7 @@ class CompatibleTransactionTests { val signerComponents = key1CommandsFtx.filteredComponentGroups[1].components val signerHashes = wtx.accessAvailableComponentHashes()[ComponentGroupEnum.SIGNERS_GROUP.ordinal]!! val noLastSignerPMT = PartialMerkleTree.build( - MerkleTree.getMerkleTree(signerHashes), + MerkleTree.getMerkleTree(signerHashes, wtx.digestService), signerHashes.subList(0, 2) ) val noLastSignerComponents = key1CommandsFtx.filteredComponentGroups[1].components.subList(0, 2) @@ -496,12 +498,12 @@ class CompatibleTransactionTests { // A command with no corresponding signer detected // because the pointer of CommandData (3rd leaf) cannot find a corresponding (3rd) signer. val updatedFilteredComponentsNoSignersKey1SamePMT = listOf(key1CommandsFtx.filteredComponentGroups[0], noLastSignerGroupSamePartialTree) - assertFails { ftxConstructor.call(key1CommandsFtx.id, updatedFilteredComponentsNoSignersKey1SamePMT, key1CommandsFtx.groupHashes) } + assertFails { ftxConstructor.call(key1CommandsFtx.id, updatedFilteredComponentsNoSignersKey1SamePMT, key1CommandsFtx.groupHashes, wtx.digestService) } // Remove both last signer (KEY1) and related command. // Update partial Merkle tree for signers. val updatedFilteredComponentsNoLastCommandAndSigners = listOf(noLastCommandDataGroup, noLastSignerGroup) - val ftxNoLastCommandAndSigners = ftxConstructor.call(key1CommandsFtx.id, updatedFilteredComponentsNoLastCommandAndSigners, key1CommandsFtx.groupHashes) + val ftxNoLastCommandAndSigners = ftxConstructor.call(key1CommandsFtx.id, updatedFilteredComponentsNoLastCommandAndSigners, key1CommandsFtx.groupHashes, wtx.digestService) // verify() will pass as the transaction is well-formed. ftxNoLastCommandAndSigners.verify() // checkCommandVisibility() will not pass, because checkAllComponentsVisible(ComponentGroupEnum.SIGNERS_GROUP) will fail. @@ -510,7 +512,7 @@ class CompatibleTransactionTests { // Remove last signer for which there is no pointer from a visible commandData. This is the case of Key2. // Do not change partial Merkle tree for signers. // This time the object can be constructed as there is no pointer mismatch. - val ftxNoLastSigner = ftxConstructor.call(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2SamePMT, key2CommandsFtx.groupHashes) + val ftxNoLastSigner = ftxConstructor.call(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2SamePMT, key2CommandsFtx.groupHashes, wtx.digestService) // verify() will fail as we didn't change the partial Merkle tree. assertFailsWith { ftxNoLastSigner.verify() } // checkCommandVisibility() will not pass. @@ -518,7 +520,7 @@ class CompatibleTransactionTests { // Remove last signer for which there is no pointer from a visible commandData. This is the case of Key2. // Update partial Merkle tree for signers. - val ftxNoLastSignerB = ftxConstructor.call(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2, key2CommandsFtx.groupHashes) + val ftxNoLastSignerB = ftxConstructor.call(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2, key2CommandsFtx.groupHashes, wtx.digestService) // verify() will pass, the transaction is well-formed. ftxNoLastSignerB.verify() // But, checkAllComponentsVisible() will not pass. @@ -527,8 +529,8 @@ class CompatibleTransactionTests { // Modify last signer (we have a pointer from commandData). // Update partial Merkle tree for signers. val alterSignerComponents = signerComponents.subList(0, 2) + signerComponents[1] // Third one is removed and the 2nd command is added twice. - val alterSignersHashes = wtx.accessAvailableComponentHashes()[ComponentGroupEnum.SIGNERS_GROUP.ordinal]!!.subList(0, 2) + componentHash(key1CommandsFtx.filteredComponentGroups[1].nonces[2], alterSignerComponents[2]) - val alterMTree = MerkleTree.getMerkleTree(alterSignersHashes) + val alterSignersHashes = wtx.accessAvailableComponentHashes()[ComponentGroupEnum.SIGNERS_GROUP.ordinal]!!.subList(0, 2) + wtx.digestService.componentHash(key1CommandsFtx.filteredComponentGroups[1].nonces[2], alterSignerComponents[2]) + val alterMTree = MerkleTree.getMerkleTree(alterSignersHashes, wtx.digestService) val alterSignerPMTK = PartialMerkleTree.build( alterMTree, alterSignersHashes @@ -543,14 +545,14 @@ class CompatibleTransactionTests { val alterFilteredComponents = listOf(key1CommandsFtx.filteredComponentGroups[0], alterSignerGroup) // Do not update groupHashes. - val ftxAlterSigner = ftxConstructor.call(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes) + val ftxAlterSigner = ftxConstructor.call(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes, wtx.digestService) // Visible components in signers group cannot be verified against their partial Merkle tree. assertFailsWith { ftxAlterSigner.verify() } // Also, checkAllComponentsVisible() will not pass (groupHash matching will fail). assertFailsWith { ftxAlterSigner.checkCommandVisibility(DUMMY_KEY_1.public) } // Update groupHashes. - val ftxAlterSignerB = ftxConstructor.call(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes.subList(0, 6) + alterMTree.hash) + val ftxAlterSignerB = ftxConstructor.call(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes.subList(0, 6) + alterMTree.hash, wtx.digestService) // Visible components in signers group cannot be verified against their partial Merkle tree. assertFailsWith { ftxAlterSignerB.verify() } // Also, checkAllComponentsVisible() will not pass (top level Merkle tree cannot be verified against transaction's id). diff --git a/core-tests/src/test/kotlin/net/corda/coretests/transactions/TransactionBuilderTest.kt b/core-tests/src/test/kotlin/net/corda/coretests/transactions/TransactionBuilderTest.kt index 1f3442e2a8..882466a059 100644 --- a/core-tests/src/test/kotlin/net/corda/coretests/transactions/TransactionBuilderTest.kt +++ b/core-tests/src/test/kotlin/net/corda/coretests/transactions/TransactionBuilderTest.kt @@ -6,10 +6,13 @@ import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.* import net.corda.core.cordapp.CordappProvider import net.corda.core.crypto.CompositeKey +import net.corda.core.crypto.DigestService import net.corda.core.crypto.SecureHash import net.corda.core.identity.Party import net.corda.core.internal.AbstractAttachment +import net.corda.core.internal.HashAgility import net.corda.core.internal.PLATFORM_VERSION +import net.corda.core.internal.digestService import net.corda.core.node.ServicesForResolution import net.corda.core.node.ZoneVersionTooLowException import net.corda.core.node.services.AttachmentStorage @@ -27,6 +30,7 @@ import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test import java.security.PublicKey @@ -249,4 +253,50 @@ class TransactionBuilderTest { assertThat(builder.commands()).hasSize(1) assertThat(builder.referenceStates()).hasSize(1) } + + @Ignore + @Test(timeout=300_000, expected = TransactionVerificationException.UnsupportedHashTypeException::class) + fun `throws with non-default hash algorithm`() { + HashAgility.init() + try { + val outputState = TransactionState( + data = DummyState(), + contract = DummyContract.PROGRAM_ID, + notary = notary, + constraint = HashAttachmentConstraint(contractAttachmentId) + ) + val builder = TransactionBuilder( + //privacySalt = DigestService.sha2_384.privacySalt, + privacySalt = PrivacySalt.createFor(DigestService.sha2_384.hashAlgorithm)) + .addOutputState(outputState) + .addCommand(DummyCommandData, notary.owningKey) + + builder.toWireTransaction(services) + } finally { + HashAgility.init() + } + } + + @Test(timeout=300_000, expected = Test.None::class) + fun `allows non-default hash algorithm`() { + HashAgility.init(txHashAlgoName = DigestService.sha2_384.hashAlgorithm) + assertThat(services.digestService).isEqualTo(DigestService.sha2_384) + try { + val outputState = TransactionState( + data = DummyState(), + contract = DummyContract.PROGRAM_ID, + notary = notary, + constraint = HashAttachmentConstraint(contractAttachmentId) + ) + val builder = TransactionBuilder( + //privacySalt = DigestService.sha2_384.privacySalt, + privacySalt = PrivacySalt.createFor(DigestService.sha2_384.hashAlgorithm)) + .addOutputState(outputState) + .addCommand(DummyCommandData, notary.owningKey) + + assertThat(builder.toWireTransaction(services).digestService).isEqualTo(DigestService.sha2_384) + } finally { + HashAgility.init() + } + } } diff --git a/core-tests/src/test/kotlin/net/corda/coretests/transactions/TransactionTests.kt b/core-tests/src/test/kotlin/net/corda/coretests/transactions/TransactionTests.kt index 500cfa9816..47d4171c1d 100644 --- a/core-tests/src/test/kotlin/net/corda/coretests/transactions/TransactionTests.kt +++ b/core-tests/src/test/kotlin/net/corda/coretests/transactions/TransactionTests.kt @@ -7,6 +7,7 @@ import net.corda.core.crypto.* import net.corda.core.crypto.CompositeKey import net.corda.core.identity.Party import net.corda.core.internal.AbstractAttachment +import net.corda.core.internal.HashAgility import net.corda.core.internal.TESTDSL_UPLOADER import net.corda.core.internal.createLedgerTransaction import net.corda.core.node.NotaryInfo @@ -20,8 +21,11 @@ import net.corda.testing.internal.createWireTransaction import net.corda.testing.internal.fakeAttachment import net.corda.coretesting.internal.rigorousMock import net.corda.testing.internal.TestingNamedCacheFactory +import org.junit.Before import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized import java.math.BigInteger import java.security.KeyPair import java.security.PublicKey @@ -29,7 +33,8 @@ import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertNotEquals -class TransactionTests { +@RunWith(Parameterized::class) +class TransactionTests(private val digestService : DigestService) { private companion object { val DUMMY_KEY_1 = generateKeyPair() val DUMMY_KEY_2 = generateKeyPair() @@ -39,12 +44,30 @@ class TransactionTests { val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20) val DUMMY_NOTARY get() = dummyNotary.party val DUMMY_NOTARY_KEY get() = dummyNotary.keyPair + + @JvmStatic + @Parameterized.Parameters + fun data(): Collection = listOf( + DigestService.sha2_256 +// DigestService.sha2_384, +// DigestService.sha2_512 + ) } @Rule @JvmField val testSerialization = SerializationEnvironmentRule() + @Before + fun before() { + HashAgility.init(txHashAlgoName = digestService.hashAlgorithm) + } + + @Before + fun after() { + HashAgility.init() + } + private fun makeSigned(wtx: WireTransaction, vararg keys: KeyPair, notarySig: Boolean = true): SignedTransaction { val keySigs = keys.map { it.sign(SignableData(wtx.id, SignatureMetadata(1, Crypto.findSignatureScheme(it.public).schemeNumberID))) } val sigs = if (notarySig) { @@ -130,7 +153,7 @@ class TransactionTests { doReturn(SecureHash.zeroHash).whenever(it).id doReturn(fakeAttachment("nothing", "nada").inputStream()).whenever(it).open() }, DummyContract.PROGRAM_ID, uploader = "app")) - val id = SecureHash.randomSHA256() + val id = digestService.randomHash() val timeWindow: TimeWindow? = null val privacySalt = PrivacySalt() val attachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory()) @@ -146,7 +169,8 @@ class TransactionTests { testNetworkParameters(), emptyList(), isAttachmentTrusted = { true }, - attachmentsClassLoaderCache = attachmentsClassLoaderCache + attachmentsClassLoaderCache = attachmentsClassLoaderCache, + digestService = digestService ) transaction.verify() @@ -173,6 +197,7 @@ class TransactionTests { val inState = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DummyContract.PROGRAM_ID, notary) val outState = inState.copy(notary = ALICE) val inputs = listOf(StateAndRef(inState, StateRef(SecureHash.randomSHA256(), 0))) + val outputs = listOf(outState) val commands = emptyList>() val attachments = listOf(object : AbstractAttachment({ @@ -184,9 +209,9 @@ class TransactionTests { override val size: Int = 1234 override val id: SecureHash = SecureHash.zeroHash }) - val id = SecureHash.randomSHA256() + val id = digestService.randomHash() val timeWindow: TimeWindow? = null - val privacySalt = PrivacySalt() + val privacySalt = PrivacySalt(digestService.digestLength) val attachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory()) fun buildTransaction() = createLedgerTransaction( @@ -201,7 +226,8 @@ class TransactionTests { testNetworkParameters(notaries = listOf(NotaryInfo(DUMMY_NOTARY, true))), emptyList(), isAttachmentTrusted = { true }, - attachmentsClassLoaderCache = attachmentsClassLoaderCache + attachmentsClassLoaderCache = attachmentsClassLoaderCache, + digestService = digestService ) assertFailsWith { buildTransaction().verify() } @@ -217,7 +243,8 @@ class TransactionTests { commands = listOf(dummyCommand(DUMMY_KEY_1.public, DUMMY_KEY_2.public)), notary = null, timeWindow = null, - privacySalt = PrivacySalt() // Randomly-generated – used for calculating the id + privacySalt = PrivacySalt(digestService.digestLength), // Randomly-generated – used for calculating the id + digestService = digestService ) val issueTx1 = buildTransaction() diff --git a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt index bfae2f8900..1bafcbb619 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/Structures.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/Structures.kt @@ -153,6 +153,9 @@ interface SchedulableState : ContractState { /** Returns the SHA-256 hash of the serialised contents of this state (not cached!) */ fun ContractState.hash(): SecureHash = SecureHash.sha256(serialize().bytes) +/** Returns the hash of the serialised contents of this state (not cached!) */ +fun ContractState.hash(algorithm: String): SecureHash = SecureHash.hashAs(algorithm, serialize().bytes) + /** * A stateref is a pointer (reference) to a state, this is an equivalent of an "outpoint" in Bitcoin. It records which * transaction defined the state and where in that transaction it was. @@ -324,13 +327,32 @@ interface UpgradedContractWithLegacyConstraint= MINIMUM_SIZE) { "Privacy salt should be at least $MINIMUM_SIZE bytes." } + } + + fun validateFor(algorithm: String) { + val digestLength = SecureHash.digestLengthFor(algorithm) + require(bytes.size >= digestLength) { "Privacy salt should be at least $digestLength bytes for $algorithm." } + } + + companion object { + private const val MINIMUM_SIZE = 32 + + @DeleteForDJVM + @JvmStatic + fun createFor(algorithm: String): PrivacySalt { + return PrivacySalt(SecureHash.digestLengthFor(algorithm)) + } } } diff --git a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt index 9e243277bc..ff43e2d138 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt @@ -342,6 +342,10 @@ abstract class TransactionVerificationException(val txId: SecureHash, message: S "At this time these are not loadable because the DJVM sandbox has not yet been integrated. " + "You will need to manually install the CorDapp to whitelist it for use.") + // TODO(iee): verify if this is an acceptable exception + @KeepForDJVM + class UnsupportedHashTypeException(txId: SecureHash) : TransactionVerificationException(txId, "The transaction Id is defined by an unsupported hash type", null); + /* If you add a new class extending [TransactionVerificationException], please add a test in `TransactionVerificationExceptionSerializationTests` proving that it can actually be serialised. As a rule, exceptions intended to be serialised _must_ have a corresponding readable property diff --git a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt index 0e2ea7be43..5dd55b34f6 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt @@ -1067,7 +1067,7 @@ object Crypto { return if (partialMerkleTree != null) { val usedHashes = mutableListOf() val root = PartialMerkleTree.rootAndUsedHashes(partialMerkleTree.root, usedHashes) - require(txId.sha256() in usedHashes) { "Transaction with id:$txId is not a leaf in the provided partial Merkle tree" } + require(txId.reHash() in usedHashes) { "Transaction with id:$txId is not a leaf in the provided partial Merkle tree" } root } else { txId diff --git a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt index 11aaa4cc3d..c1c0d88cc0 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt @@ -265,10 +265,12 @@ fun random63BitValue(): Long { * calculated using the SHA256d algorithm, thus SHA256(SHA256(nonce || serializedComponent)), where nonce is computed * from [computeNonce]. */ +@Deprecated("This has been moved to DigestService") fun componentHash(opaqueBytes: OpaqueBytes, privacySalt: PrivacySalt, componentGroupIndex: Int, internalIndex: Int): SecureHash = - componentHash(computeNonce(privacySalt, componentGroupIndex, internalIndex), opaqueBytes) + @Suppress("DEPRECATION") componentHash(computeNonce(privacySalt, componentGroupIndex, internalIndex), opaqueBytes) /** Return the SHA256(SHA256(nonce || serializedComponent)). */ +@Deprecated("This has been moved to DigestService") fun componentHash(nonce: SecureHash, opaqueBytes: OpaqueBytes): SecureHash = SecureHash.sha256Twice(nonce.bytes + opaqueBytes.bytes) /** @@ -276,6 +278,7 @@ fun componentHash(nonce: SecureHash, opaqueBytes: OpaqueBytes): SecureHash = Sec * across platform versions: serialization can produce different values if any of the types being serialized have changed, * or if the version of serialization specified by the context changes. */ +@Deprecated("This has been moved to DigestService") fun serializedHash(x: T): SecureHash = x.serialize(context = SerializationDefaults.P2P_CONTEXT.withoutReferences()).bytes.sha256() /** @@ -286,5 +289,5 @@ fun serializedHash(x: T): SecureHash = x.serialize(context = Serializa * @param internalIndex the internal index of this object in its corresponding components list. * @return SHA256(SHA256(privacySalt || groupIndex || internalIndex)) */ +@Deprecated("This has been moved to DigestService") fun computeNonce(privacySalt: PrivacySalt, groupIndex: Int, internalIndex: Int) = SecureHash.sha256Twice(privacySalt.bytes + ByteBuffer.allocate(8).putInt(groupIndex).putInt(internalIndex).array()) - diff --git a/core/src/main/kotlin/net/corda/core/crypto/DigestAlgorithm.kt b/core/src/main/kotlin/net/corda/core/crypto/DigestAlgorithm.kt new file mode 100644 index 0000000000..ef6b6971b7 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/crypto/DigestAlgorithm.kt @@ -0,0 +1,32 @@ +package net.corda.core.crypto + +import net.corda.core.KeepForDJVM + +/** + * Interface for injecting custom digest implementation bypassing JCA. + */ +@KeepForDJVM +interface DigestAlgorithm { + /** + * Algorithm identifier. + */ + val algorithm: String + + /** + * The length of the digest in bytes. + */ + val digestLength: Int + + /** + * Computes the digest of the [ByteArray]. + * + * @param bytes The [ByteArray] to hash. + */ + fun digest(bytes: ByteArray): ByteArray + + /** + * Computes the digest of the [ByteArray] which is resistant to pre-image attacks. + * Default implementation provides double hashing, but can it be changed to single hashing or something else for better performance. + */ + fun preImageResistantDigest(bytes: ByteArray): ByteArray = digest(digest(bytes)) +} diff --git a/core/src/main/kotlin/net/corda/core/crypto/DigestService.kt b/core/src/main/kotlin/net/corda/core/crypto/DigestService.kt new file mode 100644 index 0000000000..2f88e9d959 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/crypto/DigestService.kt @@ -0,0 +1,121 @@ +package net.corda.core.crypto + +import net.corda.core.DeleteForDJVM +import net.corda.core.KeepForDJVM +import net.corda.core.contracts.PrivacySalt +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.SerializationDefaults +import net.corda.core.serialization.serialize +import net.corda.core.utilities.OpaqueBytes + +import java.nio.ByteBuffer +import java.security.MessageDigest + +/** + * The DigestService class is a service that offers the main crypto methods for calculating transaction hashes and + * building Merkle trees. The [default] instance is passed by default to instances of classes like TransactionBuilder + * and as a parameter to MerkleTree.getMerkleTree(...) method. In future the [default] instance can be parametrized + * to initialize with the network default hash algorithm or just a more secure algorithm (e.g. SHA3_256). While the + * SHA2_256 is vulnerable to pre-image attacks, the computeNonce and componentHash methods behaviour is defined by + * the hashTwiceNonce and hashTwiceComponent; with SHA2_256 they both must be set to true to ensure pre-image attack + * won't work (and for backward compatibility), but for other algorithms like SHA3_256 that are not affected, they + * can and should be set to false as hashing twice would not improve security but affect performance. + * + * @param hashAlgorithm the name of the hash algorithm to be used for the instance + */ +@CordaSerializable +@KeepForDJVM +data class DigestService(val hashAlgorithm: String) { + init { + require(hashAlgorithm.isNotEmpty()) { "Hash algorithm name unavailable or not specified" } + } + + @KeepForDJVM + companion object { + private const val NONCE_SIZE = 8 + /** + * The [default] instance will be parametrized and initialized at runtime. It would be probably useful to assume an override + * priority order. + */ + val default : DigestService by lazy { sha2_256 } + val sha2_256: DigestService by lazy { DigestService(SecureHash.SHA2_256) } + val sha2_384: DigestService by lazy { DigestService(SecureHash.SHA2_384) } + val sha2_512: DigestService by lazy { DigestService(SecureHash.SHA2_512) } + } + + /** + * Specifies the WORD size for the given hash algorithm. + */ + val digestLength: Int + get() = SecureHash.digestLengthFor(hashAlgorithm) + + /** + * Computes the digest of the [ByteArray]. + * + * @param bytes The [ByteArray] to hash. + */ + fun hash(bytes: ByteArray): SecureHash = SecureHash.hashAs(hashAlgorithm, bytes) + + /** + * Computes the digest of the [String]'s UTF-8 byte contents. + * + * @param str [String] whose UTF-8 contents will be hashed. + */ + fun hash(str: String): SecureHash = hash(str.toByteArray()) + + /** + * A digest value consisting of 0xFF bytes. + */ + val allOnesHash: SecureHash + get() = SecureHash.allOnesHashFor(hashAlgorithm) + + /** + * A hash value consisting of 0x00 bytes. + */ + val zeroHash: SecureHash + get() = SecureHash.zeroHashFor(hashAlgorithm) + +// val privacySalt: PrivacySalt +// get() = PrivacySalt.createFor(hashAlgorithm) + + /** + * Compute the hash of each serialised component so as to be used as Merkle tree leaf. The resultant output (leaf) is + * calculated using the service's hash algorithm, thus HASH(HASH(nonce || serializedComponent)) for SHA2-256 and other + * algorithms loaded via JCA [MessageDigest], or DigestAlgorithm.preImageResistantDigest(nonce || serializedComponent) + * otherwise, where nonce is computed from [computeNonce]. + */ + fun componentHash(opaqueBytes: OpaqueBytes, privacySalt: PrivacySalt, componentGroupIndex: Int, internalIndex: Int): SecureHash = + componentHash(computeNonce(privacySalt, componentGroupIndex, internalIndex), opaqueBytes) + + /** Return the HASH(HASH(nonce || serializedComponent)) for SHA2-256 and other algorithms loaded via JCA [MessageDigest], + * otherwise it's defined by DigestAlgorithm.preImageResistantDigest(nonce || serializedComponent). */ + fun componentHash(nonce: SecureHash, opaqueBytes: OpaqueBytes): SecureHash { + val data = nonce.bytes + opaqueBytes.bytes + return SecureHash.preImageResistantHashAs(hashAlgorithm, data) + } + + /** + * Serialise the object and return the hash of the serialized bytes. Note that the resulting hash may not be deterministic + * across platform versions: serialization can produce different values if any of the types being serialized have changed, + * or if the version of serialization specified by the context changes. + */ + fun serializedHash(x: T): SecureHash = + SecureHash.hashAs(hashAlgorithm, x.serialize(context = SerializationDefaults.P2P_CONTEXT.withoutReferences()).bytes) + + /** + * Method to compute a nonce based on privacySalt, component group index and component internal index. + * SHA256d (double SHA256) is used to prevent length extension attacks. + * @param privacySalt a [PrivacySalt]. + * @param groupIndex the fixed index (ordinal) of this component group. + * @param internalIndex the internal index of this object in its corresponding components list. + * @return HASH(HASH(privacySalt || groupIndex || internalIndex)) for SHA2-256 and other algorithms loaded via JCA [MessageDigest], + * otherwise it's defined by DigestAlgorithm.preImageResistantDigest(privacySalt || groupIndex || internalIndex). + */ + fun computeNonce(privacySalt: PrivacySalt, groupIndex: Int, internalIndex: Int) : SecureHash { + val data = (privacySalt.bytes + ByteBuffer.allocate(NONCE_SIZE).putInt(groupIndex).putInt(internalIndex).array()) + return SecureHash.preImageResistantHashAs(hashAlgorithm, data) + } +} + +@DeleteForDJVM +fun DigestService.randomHash(): SecureHash = SecureHash.random(this.hashAlgorithm) diff --git a/core/src/main/kotlin/net/corda/core/crypto/MerkleTree.kt b/core/src/main/kotlin/net/corda/core/crypto/MerkleTree.kt index 8f32a0e8c3..f3dda598b4 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/MerkleTree.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/MerkleTree.kt @@ -21,24 +21,34 @@ sealed class MerkleTree { companion object { private fun isPow2(num: Int): Boolean = num and (num - 1) == 0 + @Throws(MerkleTreeException::class) + fun getMerkleTree(allLeavesHashes: List): MerkleTree { + return getMerkleTree(allLeavesHashes, DigestService.sha2_256); + } + /** * Merkle tree building using hashes, with zero hash padding to full power of 2. */ @Throws(MerkleTreeException::class) - fun getMerkleTree(allLeavesHashes: List): MerkleTree { + fun getMerkleTree(allLeavesHashes: List, nodeDigestService: DigestService): MerkleTree { if (allLeavesHashes.isEmpty()) throw MerkleTreeException("Cannot calculate Merkle root on empty hash list.") + val algorithms = allLeavesHashes.mapTo(HashSet(), SecureHash::algorithm) + require(algorithms.size == 1) { + "Cannot build Merkle tree with multiple hash algorithms: $algorithms" + } val leaves = padWithZeros(allLeavesHashes).map { Leaf(it) } - return buildMerkleTree(leaves) + return buildMerkleTree(leaves, nodeDigestService) } // If number of leaves in the tree is not a power of 2, we need to pad it with zero hashes. private fun padWithZeros(allLeavesHashes: List): List { var n = allLeavesHashes.size if (isPow2(n)) return allLeavesHashes - val paddedHashes = ArrayList(allLeavesHashes) + val paddedHashes = ArrayList(allLeavesHashes) + val zeroHash = SecureHash.zeroHashFor(paddedHashes[0].algorithm) while (!isPow2(n++)) { - paddedHashes.add(SecureHash.zeroHash) + paddedHashes.add(zeroHash) } return paddedHashes } @@ -48,7 +58,7 @@ sealed class MerkleTree { * @param lastNodesList MerkleTree nodes from previous level. * @return Tree root. */ - private tailrec fun buildMerkleTree(lastNodesList: List): MerkleTree { + private tailrec fun buildMerkleTree(lastNodesList: List, nodeDigestService: DigestService): MerkleTree { return if (lastNodesList.size == 1) { lastNodesList[0] // Root reached. } else { @@ -58,11 +68,10 @@ sealed class MerkleTree { for (i in 0..n - 2 step 2) { val left = lastNodesList[i] val right = lastNodesList[i + 1] - val newHash = left.hash.hashConcat(right.hash) - val combined = Node(newHash, left, right) - newLevelHashes.add(combined) + val node = Node(nodeDigestService.hash(left.hash.bytes + right.hash.bytes), left, right) + newLevelHashes.add(node) } - buildMerkleTree(newLevelHashes) + buildMerkleTree(newLevelHashes, nodeDigestService) } } } diff --git a/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt b/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt index f57d02d800..f3aab735ff 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt @@ -3,8 +3,8 @@ package net.corda.core.crypto import net.corda.core.CordaException import net.corda.core.CordaInternal import net.corda.core.KeepForDJVM -import net.corda.core.crypto.SecureHash.Companion.zeroHash import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.DeprecatedConstructorForDeserialization import java.util.* @KeepForDJVM @@ -59,7 +59,20 @@ class PartialMerkleTree(val root: PartialTree) { sealed class PartialTree { @KeepForDJVM data class IncludedLeaf(val hash: SecureHash) : PartialTree() @KeepForDJVM data class Leaf(val hash: SecureHash) : PartialTree() - @KeepForDJVM data class Node(val left: PartialTree, val right: PartialTree) : PartialTree() + @KeepForDJVM data class Node(val left: PartialTree, val right: PartialTree, val hashAlgorithm: String? = SecureHash.SHA2_256) : PartialTree(){ + /** + * Old version of [PartialTree.Node] constructor for ABI compatibility. + */ + @DeprecatedConstructorForDeserialization(1) + constructor(left: PartialTree, right: PartialTree) : this(left, right, SecureHash.SHA2_256) + + /** + * Old version of [PartialTree.Node.copy] for ABI compatibility. + */ + fun copy(left: PartialTree, right: PartialTree) : Node { + return Node(left, right, SecureHash.SHA2_256) + } + } } companion object { @@ -70,13 +83,14 @@ class PartialMerkleTree(val root: PartialTree) { */ @Throws(IllegalArgumentException::class, MerkleTreeException::class) fun build(merkleRoot: MerkleTree, includeHashes: List): PartialMerkleTree { - val usedHashes = ArrayList() - require(zeroHash !in includeHashes) { "Zero hashes shouldn't be included in partial tree." } + require(includeHashes.none(SecureHash::isZero)) { "Zero hashes shouldn't be included in partial tree." } checkFull(merkleRoot) // Throws MerkleTreeException if it is not a full binary tree. + val usedHashes = ArrayList() val tree = buildPartialTree(merkleRoot, includeHashes, usedHashes) // Too many included hashes or different ones. - if (includeHashes.size != usedHashes.size) + if (includeHashes.size != usedHashes.size) { throw MerkleTreeException("Some of the provided hashes are not in the tree.") + } return PartialMerkleTree(tree.second) } @@ -116,7 +130,7 @@ class PartialMerkleTree(val root: PartialTree) { val rightNode = buildPartialTree(root.right, includeHashes, usedHashes) if (leftNode.first or rightNode.first) { // This node is on a path to some included leaves. Don't store hash. - val newTree = PartialTree.Node(leftNode.second, rightNode.second) + val newTree = PartialTree.Node(leftNode.second, rightNode.second, root.hash.algorithm) Pair(true, newTree) } else { // This node has no included leaves below. Cut the tree here and store a hash as a Leaf. @@ -144,7 +158,7 @@ class PartialMerkleTree(val root: PartialTree) { is PartialTree.Node -> { val leftHash = rootAndUsedHashes(node.left, usedHashes) val rightHash = rootAndUsedHashes(node.right, usedHashes) - leftHash.hashConcat(rightHash) + leftHash.concatenateAs(node.hashAlgorithm!!, rightHash) } } } 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 02721a1771..fc1e34e4da 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt @@ -1,15 +1,19 @@ +@file:Suppress("TooManyFunctions", "MagicNumber") @file:KeepForDJVM package net.corda.core.crypto import io.netty.util.concurrent.FastThreadLocal import net.corda.core.DeleteForDJVM import net.corda.core.KeepForDJVM +import net.corda.core.crypto.internal.DigestAlgorithmFactory import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.parseAsHex import net.corda.core.utilities.toHexString import java.nio.ByteBuffer import java.security.MessageDigest +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentMap import java.util.function.Supplier /** @@ -18,7 +22,9 @@ import java.util.function.Supplier */ @KeepForDJVM @CordaSerializable -sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) { +sealed class SecureHash(val algorithm: String, bytes: ByteArray) : OpaqueBytes(bytes) { + constructor(bytes: ByteArray) : this(SHA2_256, 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 { @@ -27,7 +33,7 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) { override fun equals(other: Any?): Boolean { if (this === other) return true - if (javaClass != other?.javaClass) return false + if (other !is SHA256) return false if (!super.equals(other)) return false return true } @@ -35,18 +41,44 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) { // This is an efficient hashCode, because there is no point in performing a hash calculation on a cryptographic hash. // It just takes the first 4 bytes and transforms them into an Int. override fun hashCode() = ByteBuffer.wrap(bytes).int + + /** + * Convert the hash value to an uppercase hexadecimal [String]. + */ + override fun toString() = toHexString() + + override fun generate(data: ByteArray): SecureHash { + return data.sha256() + } } - /** - * Convert the hash value to an uppercase hexadecimal [String]. - */ - override fun toString(): String = bytes.toHexString() + class HASH(algorithm: String, bytes: ByteArray) : SecureHash(algorithm, bytes) { + override fun equals(other: Any?): Boolean { + return when { + this === other -> true + other !is HASH -> false + else -> algorithm == other.algorithm && super.equals(other) + } + } + + override fun hashCode() = ByteBuffer.wrap(bytes).int + + override fun generate(data: ByteArray): SecureHash { + return HASH(algorithm, digestAs(algorithm, data)) + } + } + + fun toHexString(): String = bytes.toHexString() + + override fun toString(): String { + return "$algorithm$DELIMITER${toHexString()}" + } /** * Returns the first [prefixLen] hexadecimal digits of the [SecureHash] value. * @param prefixLen The number of characters in the prefix. */ - fun prefixChars(prefixLen: Int = 6) = toString().substring(0, prefixLen) + fun prefixChars(prefixLen: Int = 6) = toHexString().substring(0, prefixLen) /** * Append a second hash value to this hash value, and then compute the SHA-256 hash of the result. @@ -54,8 +86,82 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) { */ fun hashConcat(other: SecureHash) = (this.bytes + other.bytes).sha256() + /** + * Append a second hash value to this hash value, and then compute the hash of the result. + * @param other The hash to append to this one. + */ + fun concatenate(other: SecureHash): SecureHash { + require(algorithm == other.algorithm) { + "Cannot concatenate $algorithm with ${other.algorithm}" + } + return generate(this.bytes + other.bytes) + } + + /** + * Append a second hash value to this hash value, and then compute the hash of the result using the specified algorithm. + * @param other The hash to append to this one. + * @param concatAlgorithm The hash algorithm to use for the resulting hash. + */ + fun concatenateAs(concatAlgorithm: String, other: SecureHash): SecureHash { + require(algorithm == other.algorithm) { + "Cannot concatenate $algorithm with ${other.algorithm}" + } + val concatBytes = this.bytes + other.bytes + return if(concatAlgorithm == SHA2_256) { + concatBytes.sha256() + } else { + HASH(concatAlgorithm, digestAs(concatAlgorithm, concatBytes)) + } + } + + protected open fun generate(data: ByteArray): SecureHash { + throw UnsupportedOperationException("Not implemented for $algorithm") + } + + fun reHash() : SecureHash = hashAs(algorithm, bytes) + // Like static methods in Java, except the 'companion' is a singleton that can have state. companion object { + const val SHA2_256 = "SHA-256" + const val SHA2_384 = "SHA-384" + const val SHA2_512 = "SHA-512" + const val DELIMITER = ':' + + /** + * Converts a SecureHash hash value represented as a {algorithm:}hexadecimal [String] into a [SecureHash]. + * @param str An optional algorithm id followed by a delimiter and the sequence of hexadecimal digits that represents a hash value. + * @throws IllegalArgumentException The input string does not contain the expected number of hexadecimal digits, or it contains incorrectly-encoded characters. + */ + @JvmStatic + fun create(str: String?): SecureHash { + val txt = str ?: throw IllegalArgumentException("Provided string is null") + val idx = txt.indexOf(DELIMITER) + return if (idx == -1) { + parse(txt) + } else { + val algorithm = txt.substring(0, idx) + val value = txt.substring(idx + 1) + if (algorithm == SHA2_256) { + parse(value) + } else { + decode(algorithm, value) + } + } + } + + /** + * @param algorithm [MessageDigest] algorithm name, in uppercase. + * @param value Hash value as a hexadecimal string. + */ + private fun decode(algorithm: String, value: String): SecureHash { + val digestLength = digestFor(algorithm).digestLength + val data = value.parseAsHex() + return when (data.size) { + digestLength -> HASH(algorithm, data) + else -> throw IllegalArgumentException("Provided string is ${data.size} bytes not $digestLength bytes in hex: $value") + } + } + /** * Converts a SHA-256 hash value represented as a hexadecimal [String] into a [SecureHash]. * @param str A sequence of 64 hexadecimal digits that represents a SHA-256 hash value. @@ -71,14 +177,61 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) { } ?: throw IllegalArgumentException("Provided string is null") } - private val sha256MessageDigest = SHA256DigestSupplier() + private val messageDigests: ConcurrentMap = ConcurrentHashMap() + + private fun digestFor(algorithm: String): DigestSupplier { + return messageDigests.getOrPut(algorithm) { DigestSupplier(algorithm) } + } + + private fun digestAs(algorithm: String, bytes: ByteArray): ByteArray = digestFor(algorithm).get().digest(bytes) + + /** + * @param algorithm The [MessageDigest] algorithm to query. + * @return The length in bytes of this [MessageDigest]. + */ + fun digestLengthFor(algorithm: String): Int { + return digestFor(algorithm).digestLength + } + + /** + * Computes the hash value of the [ByteArray]. + * @param algorithm Java provider name of the digest algorithm. + * @param bytes The [ByteArray] to hash. + */ + @JvmStatic + fun hashAs(algorithm: String, bytes: ByteArray): SecureHash { + val hashBytes = digestAs(algorithm, bytes) + return if (algorithm == SHA2_256) { + SHA256(hashBytes) + } else { + HASH(algorithm, hashBytes) + } + } + + /** + * Computes the digest of the [ByteArray] which is resistant to pre-image attacks. + * It computes the hash of the hash for SHA2-256 and other algorithms loaded via JCA [MessageDigest]. + * For custom algorithms the strategy can be modified via [DigestAlgorithm]. + * @param algorithm The [MessageDigest] algorithm to use. + * @param bytes The [ByteArray] to hash. + */ + @JvmStatic + fun preImageResistantHashAs(algorithm: String, bytes: ByteArray): SecureHash { + return if (algorithm == SHA2_256) { + sha256Twice(bytes) + } else { + val digest = digestFor(algorithm).get() + val firstHash = digest.preImageResistantDigest(bytes) + HASH(algorithm, digest.digest(firstHash)) + } + } /** * Computes the SHA-256 hash value of the [ByteArray]. * @param bytes The [ByteArray] to hash. */ @JvmStatic - fun sha256(bytes: ByteArray) = SHA256(sha256MessageDigest.get().digest(bytes)) + fun sha256(bytes: ByteArray) = SHA256(digestAs(SHA2_256, bytes)) /** * Computes the SHA-256 hash of the [ByteArray], and then computes the SHA-256 hash of the hash. @@ -101,12 +254,26 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) { @JvmStatic fun randomSHA256() = sha256(secureRandomBytes(32)) + /** + * Generates a random hash value. + */ + @DeleteForDJVM + @JvmStatic + fun random(algorithm: String): SecureHash { + return if (algorithm == SHA2_256) { + randomSHA256() + } else { + val digest = digestFor(algorithm) + HASH(algorithm, digest.get().digest(secureRandomBytes(digest.digestLength))) + } + } + /** * A SHA-256 hash value consisting of 32 0x00 bytes. * This field provides more intuitive access from Java. */ @JvmField - val zeroHash: SHA256 = SecureHash.SHA256(ByteArray(32) { 0.toByte() }) + val zeroHash: SHA256 = SHA256(ByteArray(32) { 0.toByte() }) /** * A SHA-256 hash value consisting of 32 0x00 bytes. @@ -120,7 +287,7 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) { * This field provides more intuitive access from Java. */ @JvmField - val allOnesHash: SHA256 = SecureHash.SHA256(ByteArray(32) { 255.toByte() }) + val allOnesHash: SHA256 = SHA256(ByteArray(32) { 255.toByte() }) /** * A SHA-256 hash value consisting of 32 0xFF bytes. @@ -128,11 +295,45 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) { */ @Suppress("Unused") fun getAllOnesHash(): SHA256 = allOnesHash + + private val hashConstants: ConcurrentMap = ConcurrentHashMap() + init { + hashConstants[SHA2_256] = HashConstants(zeroHash, allOnesHash) + } + + private fun getConstantsFor(algorithm: String): HashConstants { + return hashConstants.getOrPut(algorithm) { + val digestLength = digestFor(algorithm).digestLength + HashConstants( + zero = HASH(algorithm, ByteArray(digestLength)), + allOnes = HASH(algorithm, ByteArray(digestLength) { 255.toByte() }) + ) + } + } + + @JvmStatic + fun zeroHashFor(algorithm: String): SecureHash { + return getConstantsFor(algorithm).zero + } + + @JvmStatic + fun allOnesHashFor(algorithm: String): SecureHash { + return getConstantsFor(algorithm).allOnes + } } // In future, maybe SHA3, truncated hashes etc. } +val OpaqueBytes.isZero: Boolean get() { + for (b in bytes) { + if (b != 0.toByte()) { + return false + } + } + return true +} + /** * Compute the SHA-256 hash for the contents of the [ByteArray]. */ @@ -143,17 +344,30 @@ fun ByteArray.sha256(): SecureHash.SHA256 = SecureHash.sha256(this) */ fun OpaqueBytes.sha256(): SecureHash.SHA256 = SecureHash.sha256(this.bytes) +/** + * Compute the [algorithm] hash for the contents of the [ByteArray]. + */ +fun ByteArray.hashAs(algorithm: String): SecureHash = SecureHash.hashAs(algorithm, this) + +/** + * Compute the [algorithm] hash for the contents of the [OpaqueBytes]. + */ +fun OpaqueBytes.hashAs(algorithm: String): SecureHash = SecureHash.hashAs(algorithm, bytes) + /** * Hide the [FastThreadLocal] class behind a [Supplier] interface * so that we can remove it for core-deterministic. */ -private class SHA256DigestSupplier : Supplier { - private val threadLocalSha256MessageDigest = LocalSHA256Digest() - override fun get(): MessageDigest = threadLocalSha256MessageDigest.get() +private class DigestSupplier(algorithm: String) : Supplier { + private val threadLocalMessageDigest = LocalDigest(algorithm) + override fun get(): DigestAlgorithm = threadLocalMessageDigest.get() + val digestLength: Int = get().digestLength } // Declaring this as "object : FastThreadLocal<>" would have // created an extra public class in the API definition. -private class LocalSHA256Digest : FastThreadLocal() { - override fun initialValue(): MessageDigest = MessageDigest.getInstance("SHA-256") +private class LocalDigest(private val algorithm: String) : FastThreadLocal() { + override fun initialValue() = DigestAlgorithmFactory.create(algorithm) } + +private class HashConstants(val zero: SecureHash, val allOnes: SecureHash) diff --git a/core/src/main/kotlin/net/corda/core/crypto/internal/DigestAlgorithmFactory.kt b/core/src/main/kotlin/net/corda/core/crypto/internal/DigestAlgorithmFactory.kt new file mode 100644 index 0000000000..532b95f4c1 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/crypto/internal/DigestAlgorithmFactory.kt @@ -0,0 +1,70 @@ +package net.corda.core.crypto.internal + +import net.corda.core.crypto.DigestAlgorithm +import java.lang.reflect.Constructor +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException +import java.util.* +import java.util.concurrent.ConcurrentHashMap + +sealed class DigestAlgorithmFactory { + abstract fun create(): DigestAlgorithm + abstract val algorithm: String + + private class MessageDigestFactory(override val algorithm: String) : DigestAlgorithmFactory() { + override fun create(): DigestAlgorithm { + try { + val messageDigest = MessageDigest.getInstance(algorithm) + return MessageDigestWrapper(messageDigest, algorithm) + } catch (e: NoSuchAlgorithmException) { + throw IllegalArgumentException("Unknown hash algorithm $algorithm") + } + } + + private class MessageDigestWrapper(val messageDigest: MessageDigest, override val algorithm: String) : DigestAlgorithm { + override val digestLength = messageDigest.digestLength + override fun digest(bytes: ByteArray): ByteArray = messageDigest.digest(bytes) + } + } + + private class CustomAlgorithmFactory(className: String) : DigestAlgorithmFactory() { + val constructor: Constructor = javaClass + .classLoader + .loadClass(className) + .asSubclass(DigestAlgorithm::class.java) + .getConstructor() + override val algorithm: String = constructor.newInstance().algorithm + + override fun create(): DigestAlgorithm { + return constructor.newInstance() + } + } + + companion object { + private const val SHA2_256 = "SHA-256" + private val BANNED: Set = Collections.unmodifiableSet(setOf("MD5", "MD2", "SHA-1")) + private val sha256Factory = MessageDigestFactory(SHA2_256) + private val factories = ConcurrentHashMap() + + private fun check(algorithm: String) { + require(algorithm.toUpperCase() == algorithm) { "Hash algorithm name $this must be in the upper case" } + require(algorithm !in BANNED) { "$algorithm is forbidden!" } + } + + fun registerClass(className: String): String { + val factory = CustomAlgorithmFactory(className) + check(factory.algorithm) + require(factory.algorithm != SHA2_256) { "Standard algorithm name is not allowed in $className" } + factories.putIfAbsent(factory.algorithm, factory) + return factory.algorithm + } + + fun create(algorithm: String): DigestAlgorithm { + check(algorithm) + return when (algorithm) { + SHA2_256 -> sha256Factory.create() + else -> factories[algorithm]?.create()?: MessageDigestFactory(algorithm).create() + } + } + } +} diff --git a/core/src/main/kotlin/net/corda/core/flows/NotaryChangeFlow.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryChangeFlow.kt index 20072e3b8f..41a25e8830 100644 --- a/core/src/main/kotlin/net/corda/core/flows/NotaryChangeFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryChangeFlow.kt @@ -8,6 +8,7 @@ import net.corda.core.crypto.SignableData import net.corda.core.crypto.SignatureMetadata import net.corda.core.identity.Party import net.corda.core.internal.NotaryChangeTransactionBuilder +import net.corda.core.internal.digestService import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.ProgressTracker @@ -34,7 +35,8 @@ class NotaryChangeFlow( inputs.map { it.ref }, originalState.state.notary, modification, - serviceHub.networkParametersService.currentHash + serviceHub.networkParametersService.currentHash, + serviceHub.digestService ).build() val participantKeys = inputs.flatMap { it.state.data.participants }.map { it.owningKey }.toSet() diff --git a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt index 93e866c62f..4e988a6e48 100644 --- a/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/NotaryFlow.kt @@ -175,7 +175,7 @@ class NotaryFlow { */ private fun generateRequestSignature(): NotarisationRequestSignature { // TODO: This is not required any more once our AMQP serialization supports turning off object referencing. - val notarisationRequest = NotarisationRequest(stx.inputs.map { it.copy(txhash = SecureHash.parse(it.txhash.toString())) }, stx.id) + val notarisationRequest = NotarisationRequest(stx.inputs.map { it.copy(txhash = SecureHash.create(it.txhash.toString())) }, stx.id) return notarisationRequest.generateSignature(serviceHub) } } diff --git a/core/src/main/kotlin/net/corda/core/internal/ContractUpgradeUtils.kt b/core/src/main/kotlin/net/corda/core/internal/ContractUpgradeUtils.kt index be68c19c28..31c09c3e97 100644 --- a/core/src/main/kotlin/net/corda/core/internal/ContractUpgradeUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/ContractUpgradeUtils.kt @@ -31,7 +31,8 @@ object ContractUpgradeUtils { upgradedContractClass.name, upgradedContractAttachmentId, privacySalt, - networkParametersHash + networkParametersHash, + services.digestService ).build() } 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 d23a2e4169..b963e50841 100644 --- a/core/src/main/kotlin/net/corda/core/internal/TransactionUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/TransactionUtils.kt @@ -2,11 +2,13 @@ package net.corda.core.internal 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.componentHash -import net.corda.core.crypto.sha256 +import net.corda.core.crypto.hashAs +import net.corda.core.crypto.internal.DigestAlgorithmFactory import net.corda.core.flows.FlowLogic import net.corda.core.identity.Party +import net.corda.core.node.ServicesForResolution import net.corda.core.serialization.* import net.corda.core.transactions.* import net.corda.core.utilities.OpaqueBytes @@ -18,10 +20,12 @@ import kotlin.reflect.KClass class NotaryChangeTransactionBuilder(val inputs: List, val notary: Party, val newNotary: Party, - val networkParametersHash: SecureHash) { + val networkParametersHash: SecureHash, + val digestService: DigestService = DigestService.sha2_256) { + fun build(): NotaryChangeWireTransaction { val components = listOf(inputs, notary, newNotary, networkParametersHash).map { it.serialize() } - return NotaryChangeWireTransaction(components) + return NotaryChangeWireTransaction(components, digestService) } } @@ -32,21 +36,32 @@ class ContractUpgradeTransactionBuilder( val legacyContractAttachmentId: SecureHash, val upgradedContractClassName: ContractClassName, val upgradedContractAttachmentId: SecureHash, - val privacySalt: PrivacySalt = PrivacySalt(), - val networkParametersHash: SecureHash) { + privacySalt: PrivacySalt = PrivacySalt(), + val networkParametersHash: SecureHash, + val digestService: DigestService = DigestService.sha2_256) { + var privacySalt: PrivacySalt = privacySalt + private set + fun build(): ContractUpgradeWireTransaction { val components = listOf(inputs, notary, legacyContractAttachmentId, upgradedContractClassName, upgradedContractAttachmentId, networkParametersHash).map { it.serialize() } - return ContractUpgradeWireTransaction(components, privacySalt) + return ContractUpgradeWireTransaction(components, privacySalt, digestService) } } /** Concatenates the hash components into a single [ByteArray] and returns its hash. */ -fun combinedHash(components: Iterable): SecureHash { +fun combinedHash(components: Iterable/*, digestService: DigestService = DigestService.default*/): SecureHash { val stream = ByteArrayOutputStream() components.forEach { stream.write(it.bytes) } - return stream.toByteArray().sha256() + // TODO(iee): need to re-visit and review this code to understand which is the right + // way to combine the hashes. Is this meant to match a pre-existing tx id, + // or create a new tx id with the [default] hash algorithm from whatever + // components are passed in and independently from their algorithm? + // This is used to build the tx id of ContractUpgradeFilteredTransaction and + // ContractUpgradeWireTransaction + return stream.toByteArray().hashAs(components.first().algorithm) + //return digestService.hash(stream.toByteArray()); } /** @@ -106,7 +121,8 @@ fun deserialiseCommands( componentGroups: List, forceDeserialize: Boolean = false, factory: SerializationFactory = SerializationFactory.defaultFactory, - @Suppress("UNUSED_PARAMETER") context: SerializationContext = factory.defaultContext + @Suppress("UNUSED_PARAMETER") context: SerializationContext = factory.defaultContext, + digestService: DigestService = DigestService.sha2_256 ): List> { // TODO: we could avoid deserialising unrelated signers. // However, current approach ensures the transaction is not malformed @@ -118,7 +134,7 @@ fun deserialiseCommands( check(commandDataList.size <= signersList.size) { "Invalid Transaction. Less Signers (${signersList.size}) than CommandData (${commandDataList.size}) objects" } - val componentHashes = group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) } + val componentHashes = group.components.mapIndexed { index, component -> digestService.componentHash(group.nonces[index], component) } val leafIndices = componentHashes.map { group.partialMerkleTree.leafIndex(it) } if (leafIndices.isNotEmpty()) check(leafIndices.max()!! < signersList.size) { "Invalid Transaction. A command with no corresponding signer detected" } @@ -191,3 +207,45 @@ fun FlowLogic<*>.checkParameterHash(networkParametersHash: SecureHash?) { val SignedTransaction.dependencies: Set get() = (inputs.asSequence() + references.asSequence()).map { it.txhash }.toSet() +class HashAgility { + companion object { + @Volatile + internal var digestService = DigestService.sha2_256 + private set + + fun init(txHashAlgoName: String? = null, txHashAlgoClass: String? = null) { + digestService = DigestService.sha2_256 + txHashAlgoName?.let { + // Verify that algorithm exists. + DigestAlgorithmFactory.create(it) + digestService = DigestService(it) + } + txHashAlgoClass?.let { + val algorithm = DigestAlgorithmFactory.registerClass(it) + digestService = DigestService(algorithm) + } + } + + internal fun isAlgorithmSupported(algorithm: String): Boolean { + return algorithm == SecureHash.SHA2_256 || algorithm == digestService.hashAlgorithm + } + } +} + +/** + * The configured instance of DigestService which is passed by default to instances of classes like TransactionBuilder + * and as a parameter to MerkleTree.getMerkleTree(...) method. Default: SHA2_256. + */ +val ServicesForResolution.digestService get() = HashAgility.digestService + +fun ServicesForResolution.requireSupportedHashType(hash: NamedByHash) { + require(HashAgility.isAlgorithmSupported(hash.id.algorithm)) { + "Tried to record a transaction with non-standard hash algorithm ${hash.id.algorithm} (experimental mode off)" + } +} + +internal fun BaseTransaction.checkSupportedHashType() { + if (!HashAgility.isAlgorithmSupported(id.algorithm)) { + throw TransactionVerificationException.UnsupportedHashTypeException(id) + } +} diff --git a/core/src/main/kotlin/net/corda/core/internal/TransactionVerifierServiceInternal.kt b/core/src/main/kotlin/net/corda/core/internal/TransactionVerifierServiceInternal.kt index 4b307947a9..9a8d794c35 100644 --- a/core/src/main/kotlin/net/corda/core/internal/TransactionVerifierServiceInternal.kt +++ b/core/src/main/kotlin/net/corda/core/internal/TransactionVerifierServiceInternal.kt @@ -46,6 +46,7 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla // list, the contents of which need to be deserialized under the correct classloader. checkNoNotaryChange() checkEncumbrancesValid() + ltx.checkSupportedHashType() // The following checks ensure the integrity of the current transaction and also of the future chain. // See: https://docs.corda.net/head/api-contract-constraints.html diff --git a/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt b/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt index 4e4a501069..3b6db58dcd 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt @@ -174,7 +174,7 @@ class Vault(val states: Iterable>) { fun constraintInfo(type: Type, data: ByteArray?): ConstraintInfo { return when (type) { Type.ALWAYS_ACCEPT -> ConstraintInfo(AlwaysAcceptAttachmentConstraint) - Type.HASH -> ConstraintInfo(HashAttachmentConstraint(SecureHash.parse(data!!.toHexString()))) + Type.HASH -> ConstraintInfo(HashAttachmentConstraint(SecureHash.create(data!!.toHexString()))) Type.CZ_WHITELISTED -> ConstraintInfo(WhitelistedByZoneAttachmentConstraint) Type.SIGNATURE -> ConstraintInfo(SignatureAttachmentConstraint(Crypto.decodePublicKey(data!!))) } diff --git a/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt b/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt index 105046e1c8..4a7a10b21e 100644 --- a/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt +++ b/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt @@ -4,7 +4,6 @@ import net.corda.core.KeepForDJVM import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateRef import net.corda.core.serialization.CordaSerializable -import net.corda.core.utilities.toHexString import org.hibernate.annotations.Immutable import java.io.Serializable import javax.persistence.Column @@ -93,13 +92,13 @@ class PersistentState(@EmbeddedId override var stateRef: PersistentStateRef? = n data class PersistentStateRef( @Suppress("MagicNumber") // column width - @Column(name = "transaction_id", length = 64, nullable = false) + @Column(name = "transaction_id", length = 144, nullable = false) var txId: String, @Column(name = "output_index", nullable = false) var index: Int ) : Serializable { - constructor(stateRef: StateRef) : this(stateRef.txhash.bytes.toHexString(), stateRef.index) + constructor(stateRef: StateRef) : this(stateRef.txhash.toString(), stateRef.index) } /** diff --git a/core/src/main/kotlin/net/corda/core/transactions/ContractUpgradeTransactions.kt b/core/src/main/kotlin/net/corda/core/transactions/ContractUpgradeTransactions.kt index 277dccc1d2..9897bca5d1 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/ContractUpgradeTransactions.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/ContractUpgradeTransactions.kt @@ -3,10 +3,9 @@ package net.corda.core.transactions import net.corda.core.CordaInternal 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.TransactionSignature -import net.corda.core.crypto.componentHash -import net.corda.core.crypto.computeNonce import net.corda.core.identity.Party import net.corda.core.internal.AttachmentWithContext import net.corda.core.internal.ServiceHubCoreInternal @@ -14,6 +13,7 @@ import net.corda.core.internal.combinedHash import net.corda.core.node.NetworkParameters import net.corda.core.node.ServicesForResolution import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.deserialize import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder @@ -42,8 +42,13 @@ data class ContractUpgradeWireTransaction( */ val serializedComponents: List, /** Required for hiding components in [ContractUpgradeFilteredTransaction]. */ - val privacySalt: PrivacySalt = PrivacySalt() + val privacySalt: PrivacySalt, + val digestService: DigestService ) : CoreTransaction() { + @DeprecatedConstructorForDeserialization(1) + constructor(serializedComponents: List, privacySalt: PrivacySalt = PrivacySalt()) + : this(serializedComponents, privacySalt, DigestService.sha2_256) + companion object { /** * Runs the explicit upgrade logic. @@ -83,6 +88,14 @@ data class ContractUpgradeWireTransaction( init { check(inputs.isNotEmpty()) { "A contract upgrade transaction must have inputs" } checkBaseInvariants() + privacySalt.validateFor(digestService.hashAlgorithm) + } + + /** + * Old version of [ContractUpgradeWireTransaction.copy] for sake of ABI compatibility. + */ + fun copy(serializedComponents: List, privacySalt: PrivacySalt): ContractUpgradeWireTransaction { + return ContractUpgradeWireTransaction(serializedComponents, privacySalt, digestService) } /** @@ -99,14 +112,14 @@ data class ContractUpgradeWireTransaction( override val id: SecureHash by lazy { val componentHashes = serializedComponents.mapIndexed { index, component -> - componentHash(nonces[index], component) + digestService.componentHash(nonces[index], component) } - combinedHash(componentHashes) + combinedHash(componentHashes/*, digestService*/) } /** Required for filtering transaction components. */ - private val nonces = (0 until serializedComponents.size).map { - computeNonce(privacySalt, it, 0) + private val nonces = serializedComponents.indices.map { + digestService.computeNonce(privacySalt, it, 0) } /** Resolves input states and contract attachments, and builds a ContractUpgradeLedgerTransaction. */ @@ -171,11 +184,11 @@ data class ContractUpgradeWireTransaction( PARAMETERS_HASH.ordinal to FilteredComponent(serializedComponents[PARAMETERS_HASH.ordinal], nonces[PARAMETERS_HASH.ordinal]) ) val hiddenComponents = (totalComponents - visibleComponents.keys).map { index -> - val hash = componentHash(nonces[index], serializedComponents[index]) + val hash = digestService.componentHash(nonces[index], serializedComponents[index]) index to hash }.toMap() - return ContractUpgradeFilteredTransaction(visibleComponents, hiddenComponents) + return ContractUpgradeFilteredTransaction(visibleComponents, hiddenComponents, digestService) } enum class Component { @@ -197,8 +210,24 @@ data class ContractUpgradeFilteredTransaction( * Hashes of the transaction components that are not revealed in this transaction. * Required for computing the transaction id. */ - val hiddenComponents: Map + val hiddenComponents: Map, + val digestService: DigestService = DigestService.sha2_256 ) : CoreTransaction() { + + /** + * Old version of [ContractUpgradeFilteredTransaction] constructor for ABI compatibility. + */ + @DeprecatedConstructorForDeserialization(1) + constructor(visibleComponents: Map, hiddenComponents: Map) + : this(visibleComponents, hiddenComponents, DigestService.sha2_256) + + /** + * Old version of [ContractUpgradeFilteredTransaction.copy] for ABI compatibility. + */ + fun copy(visibleComponents: Map, hiddenComponents: Map) : ContractUpgradeFilteredTransaction { + return ContractUpgradeFilteredTransaction(visibleComponents, hiddenComponents, DigestService.sha2_256) + } + override val inputs: List by lazy { visibleComponents[INPUTS.ordinal]?.component?.deserialize>() ?: throw IllegalArgumentException("Inputs not specified") @@ -215,13 +244,13 @@ data class ContractUpgradeFilteredTransaction( val hashList = (0 until totalComponents).map { i -> when { visibleComponents.containsKey(i) -> { - componentHash(visibleComponents[i]!!.nonce, visibleComponents[i]!!.component) + digestService.componentHash(visibleComponents[i]!!.nonce, visibleComponents[i]!!.component) } hiddenComponents.containsKey(i) -> hiddenComponents[i]!! else -> throw IllegalStateException("Missing component hashes") } } - combinedHash(hashList) + combinedHash(hashList/*, digestService*/) } override val outputs: List> get() = emptyList() override val references: List get() = emptyList() diff --git a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt index 6c73c299c2..4e75a7567d 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt @@ -14,6 +14,7 @@ import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.TimeWindow import net.corda.core.contracts.TransactionState import net.corda.core.contracts.TransactionVerificationException +import net.corda.core.crypto.DigestService import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowLogic import net.corda.core.identity.Party @@ -26,6 +27,7 @@ import net.corda.core.internal.deserialiseComponentGroup import net.corda.core.internal.isUploaderTrusted import net.corda.core.internal.uncheckedCast import net.corda.core.node.NetworkParameters +import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.internal.AttachmentsClassLoaderCache import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder import net.corda.core.utilities.contextLogger @@ -89,9 +91,42 @@ private constructor( private val serializedReferences: List?, private val isAttachmentTrusted: (Attachment) -> Boolean, private val verifierFactory: (LedgerTransaction, ClassLoader) -> Verifier, - private val attachmentsClassLoaderCache: AttachmentsClassLoaderCache? + private val attachmentsClassLoaderCache: AttachmentsClassLoaderCache?, + val digestService: DigestService = DigestService.sha2_256 ) : FullTransaction() { + /** + * Old version of [LedgerTransaction] constructor for ABI compatibility. + */ + @DeprecatedConstructorForDeserialization(1) + private constructor( + inputs: List>, + outputs: List>, + commands: List>, + attachments: List, + id: SecureHash, + notary: Party?, + timeWindow: TimeWindow?, + privacySalt: PrivacySalt, + networkParameters: NetworkParameters?, + references: List>, + componentGroups: List?, + serializedInputs: List?, + serializedReferences: List?, + isAttachmentTrusted: (Attachment) -> Boolean, + verifierFactory: (LedgerTransaction, ClassLoader) -> Verifier, + attachmentsClassLoaderCache: AttachmentsClassLoaderCache?) : this( + inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, + networkParameters, references, componentGroups, serializedInputs, serializedReferences, + isAttachmentTrusted, verifierFactory, attachmentsClassLoaderCache, DigestService.sha2_256) + + // TODO(iee): add missing => Removed from API txt + // public (java.util.List, java.util.List, java.util.List, java.util.List, net.corda.core.crypto.SecureHash, + // net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt, + // net.corda.core.node.NetworkParameters, java.util.List, java.util.List, java.util.List, java.util.List, + // kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function2, + // net.corda.core.serialization.internal.AttachmentsClassLoaderCache, kotlin.jvm.internal.DefaultConstructorMarker) + init { if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" } checkNotaryWhitelisted() @@ -127,7 +162,8 @@ private constructor( serializedInputs: List? = null, serializedReferences: List? = null, isAttachmentTrusted: (Attachment) -> Boolean, - attachmentsClassLoaderCache: AttachmentsClassLoaderCache? + attachmentsClassLoaderCache: AttachmentsClassLoaderCache?, + digestService: DigestService ): LedgerTransaction { return LedgerTransaction( inputs = inputs, @@ -145,7 +181,8 @@ private constructor( serializedReferences = protect(serializedReferences), isAttachmentTrusted = isAttachmentTrusted, verifierFactory = ::BasicVerifier, - attachmentsClassLoaderCache = attachmentsClassLoaderCache + attachmentsClassLoaderCache = attachmentsClassLoaderCache, + digestService = digestService ) } @@ -164,7 +201,8 @@ private constructor( timeWindow: TimeWindow?, privacySalt: PrivacySalt, networkParameters: NetworkParameters, - references: List>): LedgerTransaction { + references: List>, + digestService: DigestService): LedgerTransaction { return LedgerTransaction( inputs = inputs, outputs = outputs, @@ -181,7 +219,8 @@ private constructor( serializedReferences = null, isAttachmentTrusted = { true }, verifierFactory = ::BasicVerifier, - attachmentsClassLoaderCache = null + attachmentsClassLoaderCache = null, + digestService = digestService ) } } @@ -261,7 +300,8 @@ private constructor( serializedReferences = serializedReferences, isAttachmentTrusted = isAttachmentTrusted, verifierFactory = alternateVerifier, - attachmentsClassLoaderCache = attachmentsClassLoaderCache + attachmentsClassLoaderCache = attachmentsClassLoaderCache, + digestService = digestService ) // Read network parameters with backwards compatibility goo. @@ -305,7 +345,7 @@ private constructor( val deserializedInputs = serializedInputs.map { it.toStateAndRef() } val deserializedReferences = serializedReferences.map { it.toStateAndRef() } val deserializedOutputs = deserialiseComponentGroup(componentGroups, TransactionState::class, ComponentGroupEnum.OUTPUTS_GROUP, forceDeserialize = true) - val deserializedCommands = deserialiseCommands(componentGroups, forceDeserialize = true) + val deserializedCommands = deserialiseCommands(componentGroups, forceDeserialize = true, digestService = digestService) val authenticatedDeserializedCommands = deserializedCommands.map { cmd -> @Suppress("DEPRECATION") // Deprecated feature. val parties = commands.find { it.value.javaClass.name == cmd.value.javaClass.name }!!.signingParties @@ -328,7 +368,8 @@ private constructor( serializedReferences = serializedReferences, isAttachmentTrusted = isAttachmentTrusted, verifierFactory = verifierFactory, - attachmentsClassLoaderCache = attachmentsClassLoaderCache + attachmentsClassLoaderCache = attachmentsClassLoaderCache, + digestService = digestService ) } else { // This branch is only present for backwards compatibility. @@ -772,7 +813,8 @@ private constructor( serializedReferences = serializedReferences, isAttachmentTrusted = isAttachmentTrusted, verifierFactory = verifierFactory, - attachmentsClassLoaderCache = attachmentsClassLoaderCache + attachmentsClassLoaderCache = attachmentsClassLoaderCache, + digestService = digestService ) } @@ -803,7 +845,8 @@ private constructor( serializedReferences = serializedReferences, isAttachmentTrusted = isAttachmentTrusted, verifierFactory = verifierFactory, - attachmentsClassLoaderCache = attachmentsClassLoaderCache + attachmentsClassLoaderCache = attachmentsClassLoaderCache, + digestService = digestService ) } } diff --git a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt index b3ea830914..e5076d4d26 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt @@ -9,6 +9,7 @@ import net.corda.core.identity.Party import net.corda.core.internal.deserialiseCommands import net.corda.core.internal.deserialiseComponentGroup import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.deserialize import net.corda.core.utilities.OpaqueBytes @@ -21,7 +22,14 @@ import java.util.function.Predicate * may be missing in the case of this representing a "torn" transaction. Please see the user guide section * "Transaction tear-offs" to learn more about this feature. */ -abstract class TraversableTransaction(open val componentGroups: List) : CoreTransaction() { +abstract class TraversableTransaction(open val componentGroups: List, val digestService: DigestService) : CoreTransaction() { + + /** + * Old version of [TraversableTransaction] constructor for ABI compatibility. + */ + @DeprecatedConstructorForDeserialization(1) + constructor(componentGroups: List) : this(componentGroups, DigestService.sha2_256) + /** Hashes of the ZIP/JAR files that are needed to interpret the contents of this wire transaction. */ val attachments: List = deserialiseComponentGroup(componentGroups, SecureHash::class, ATTACHMENTS_GROUP) @@ -34,7 +42,7 @@ abstract class TraversableTransaction(open val componentGroups: List> = deserialiseComponentGroup(componentGroups, TransactionState::class, OUTPUTS_GROUP) /** Ordered list of ([CommandData], [PublicKey]) pairs that instruct the contracts what to do. */ - val commands: List> = deserialiseCommands(componentGroups) + val commands: List> = deserialiseCommands(componentGroups, digestService = digestService) override val notary: Party? = let { val notaries: List = deserialiseComponentGroup(componentGroups, Party::class, NOTARY_GROUP) @@ -87,8 +95,16 @@ abstract class TraversableTransaction(open val componentGroups: List, - val groupHashes: List -) : TraversableTransaction(filteredComponentGroups) { + val groupHashes: List, + digestService: DigestService +) : TraversableTransaction(filteredComponentGroups, digestService) { + + /** + * Old version of [FilteredTransaction] constructor for ABI compatibility. + */ + @DeprecatedConstructorForDeserialization(1) + internal constructor(id: SecureHash, filteredComponentGroups: List, groupHashes: List) + : this(id, filteredComponentGroups, groupHashes, DigestService.sha2_256) companion object { /** @@ -99,7 +115,7 @@ class FilteredTransaction internal constructor( @JvmStatic fun buildFilteredTransaction(wtx: WireTransaction, filtering: Predicate): FilteredTransaction { val filteredComponentGroups = filterWithFun(wtx, filtering) - return FilteredTransaction(wtx.id, filteredComponentGroups, wtx.groupHashes) + return FilteredTransaction(wtx.id, filteredComponentGroups, wtx.groupHashes, wtx.digestService) } /** @@ -176,7 +192,7 @@ class FilteredTransaction internal constructor( fun createPartialMerkleTree(componentGroupIndex: Int): PartialMerkleTree { return PartialMerkleTree.build( - MerkleTree.getMerkleTree(wtx.availableComponentHashes[componentGroupIndex]!!), + MerkleTree.getMerkleTree(wtx.availableComponentHashes[componentGroupIndex]!!, wtx.digestService), filteredComponentHashes[componentGroupIndex]!! ) } @@ -206,7 +222,7 @@ class FilteredTransaction internal constructor( verificationCheck(groupHashes.isNotEmpty()) { "At least one component group hash is required" } // Verify the top level Merkle tree (group hashes are its leaves, including allOnesHash for empty list or null // components in WireTransaction). - verificationCheck(MerkleTree.getMerkleTree(groupHashes).hash == id) { + verificationCheck(MerkleTree.getMerkleTree(groupHashes, digestService).hash == id) { "Top level Merkle tree cannot be verified against transaction's id" } @@ -220,7 +236,7 @@ class FilteredTransaction internal constructor( verificationCheck(groupMerkleRoot == PartialMerkleTree.rootAndUsedHashes(groupPartialTree.root, mutableListOf())) { "Partial Merkle tree root and advertised full Merkle tree root for component group $groupIndex do not match" } - verificationCheck(groupPartialTree.verify(groupMerkleRoot, components.mapIndexed { index, component -> componentHash(nonces[index], component) })) { + verificationCheck(groupPartialTree.verify(groupMerkleRoot, components.mapIndexed { index, component -> digestService.componentHash(nonces[index], component) })) { "Visible components in group $groupIndex cannot be verified against their partial Merkle tree" } } @@ -260,16 +276,16 @@ class FilteredTransaction internal constructor( // If we don't receive elements of a particular component, check if its ordinal is bigger that the // groupHashes.size or if the group hash is allOnesHash, // to ensure there were indeed no elements in the original wire transaction. - visibilityCheck(componentGroupEnum.ordinal >= groupHashes.size || groupHashes[componentGroupEnum.ordinal] == SecureHash.allOnesHash) { + visibilityCheck(componentGroupEnum.ordinal >= groupHashes.size || groupHashes[componentGroupEnum.ordinal] == SecureHash.allOnesHashFor(id.algorithm)) { "Did not receive components for group ${componentGroupEnum.ordinal} and cannot verify they didn't exist in the original wire transaction" } } else { visibilityCheck(group.groupIndex < groupHashes.size) { "There is no matching component group hash for group ${group.groupIndex}" } val groupPartialRoot = groupHashes[group.groupIndex] - val groupFullRoot = MerkleTree.getMerkleTree(group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) }).hash + val groupFullRoot = MerkleTree.getMerkleTree(group.components.mapIndexed { index, component -> digestService.componentHash(group.nonces[index], component) }, digestService).hash visibilityCheck(groupPartialRoot == groupFullRoot) { "Some components for group ${group.groupIndex} are not visible" } // Verify the top level Merkle tree from groupHashes. - visibilityCheck(MerkleTree.getMerkleTree(groupHashes).hash == id) { + visibilityCheck(MerkleTree.getMerkleTree(groupHashes, digestService).hash == id) { "Transaction is malformed. Top level Merkle tree cannot be verified against transaction's id" } } diff --git a/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt b/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt index de3a8d30f9..625a2febc6 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/NotaryChangeTransactions.kt @@ -4,14 +4,15 @@ import net.corda.core.CordaInternal import net.corda.core.DeleteForDJVM 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.TransactionSignature -import net.corda.core.crypto.sha256 import net.corda.core.identity.Party import net.corda.core.node.NetworkParameters import net.corda.core.node.ServiceHub import net.corda.core.node.ServicesForResolution import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize @@ -33,8 +34,30 @@ data class NotaryChangeWireTransaction( * This is used for calculating the transaction id in a deterministic fashion, since re-serializing properties * may result in a different byte sequence depending on the serialization context. */ - val serializedComponents: List + val serializedComponents: List, + val digestService: DigestService ) : CoreTransaction() { + /** + * Old version of [NotaryChangeWireTransaction] constructor for ABI compatibility. + */ + @DeprecatedConstructorForDeserialization(1) + constructor(serializedComponents: List) : this(serializedComponents, DigestService.sha2_256) + + /** + * Old version of [NotaryChangeWireTransaction.copy] for ABI compatibility. + */ + fun copy(serializedComponents: List): NotaryChangeWireTransaction { + return NotaryChangeWireTransaction(serializedComponents, DigestService.sha2_256) + } + + // TODO(iee): add missing: + // public (net.corda.core.identity.Party, java.util.UUID, java.util.List, + // java.util.List, + // java.util.List>, + // java.util.List>, net.corda.core.contracts.TimeWindow, + // net.corda.core.contracts.PrivacySalt, java.util.List, + // net.corda.core.node.ServiceHub) + override val inputs: List = serializedComponents[INPUTS.ordinal].deserialize() override val references: List = emptyList() override val notary: Party = serializedComponents[NOTARY.ordinal].deserialize() @@ -68,9 +91,9 @@ data class NotaryChangeWireTransaction( */ override val id: SecureHash by lazy { serializedComponents.map { component -> - component.bytes.sha256() + digestService.hash(component.bytes) }.reduce { combinedHash, componentHash -> - combinedHash.hashConcat(componentHash) + combinedHash.concatenate(componentHash) } } diff --git a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt index 23d829030d..2b2f9655c2 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt @@ -138,6 +138,7 @@ open class TransactionBuilder( */ @Throws(MissingContractAttachments::class) fun toWireTransaction(services: ServicesForResolution): WireTransaction = toWireTransactionWithContext(services, null) + .apply { checkSupportedHashType() } @CordaInternal internal fun toWireTransactionWithContext( @@ -176,7 +177,8 @@ open class TransactionBuilder( window, referenceStates, services.networkParametersService.currentHash), - privacySalt + privacySalt, + services.digestService ) } diff --git a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt index 73b286276d..fac84e73b9 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -14,6 +14,7 @@ import net.corda.core.node.ServiceHub import net.corda.core.node.ServicesForResolution import net.corda.core.node.services.AttachmentId import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.internal.AttachmentsClassLoaderCache import net.corda.core.serialization.serialize @@ -48,10 +49,16 @@ import java.util.function.Predicate */ @CordaSerializable @KeepForDJVM -class WireTransaction(componentGroups: List, val privacySalt: PrivacySalt = PrivacySalt()) : TraversableTransaction(componentGroups) { +class WireTransaction(componentGroups: List, val privacySalt: PrivacySalt, digestService: DigestService) : TraversableTransaction(componentGroups, digestService) { @DeleteForDJVM constructor(componentGroups: List) : this(componentGroups, PrivacySalt()) + /** + * Old version of [WireTransaction] constructor for ABI compatibility. + */ + @DeprecatedConstructorForDeserialization(1) + constructor(componentGroups: List, privacySalt: PrivacySalt = PrivacySalt()) : this(componentGroups, privacySalt, DigestService.sha2_256) + @Deprecated("Required only in some unit-tests and for backwards compatibility purposes.", ReplaceWith("WireTransaction(val componentGroups: List, override val privacySalt: PrivacySalt)"), DeprecationLevel.WARNING) @DeleteForDJVM @@ -64,7 +71,7 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr notary: Party?, timeWindow: TimeWindow?, privacySalt: PrivacySalt = PrivacySalt() - ) : this(createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList(), null), privacySalt) + ) : this(createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList(), null), privacySalt, DigestService.sha2_256) init { check(componentGroups.all { it.components.isNotEmpty() }) { "Empty component groups are not allowed" } @@ -73,6 +80,8 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr check(inputs.isNotEmpty() || outputs.isNotEmpty()) { "A transaction must contain at least one input or output state" } check(commands.isNotEmpty()) { "A transaction must contain at least one command" } if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" } + // TODO(iee): review salt validation - Should we just warn when privacy bytes already >= 32 but <= digestLength? + privacySalt.validateFor(digestService.hashAlgorithm) } /** The transaction id is represented by the root hash of Merkle tree over the transaction components. */ @@ -213,7 +222,8 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr serializedResolvedInputs, serializedResolvedReferences, isAttachmentTrusted, - attachmentsClassLoaderCache + attachmentsClassLoaderCache, + digestService ) checkTransactionSize(ltx, resolvedNetworkParameters.maxTransactionSize, serializedResolvedInputs, serializedResolvedReferences) @@ -269,7 +279,7 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr * If any of the groups is an empty list or a null object, then [SecureHash.allOnesHash] is used as its hash. * Also, [privacySalt] is not a Merkle tree leaf, because it is already "inherently" included via the component nonces. */ - val merkleTree: MerkleTree by lazy { MerkleTree.getMerkleTree(groupHashes) } + val merkleTree: MerkleTree by lazy { MerkleTree.getMerkleTree(groupHashes, digestService) } /** * The leaves (group hashes) of the top level Merkle tree. @@ -280,8 +290,9 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr val listOfLeaves = mutableListOf() // Even if empty and not used, we should at least send oneHashes for each known // or received but unknown (thus, bigger than known ordinal) component groups. + val allOnesHash = digestService.allOnesHash for (i in 0..componentGroups.map { it.groupIndex }.max()!!) { - val root = groupsMerkleRoots[i] ?: SecureHash.allOnesHash + val root = groupsMerkleRoots[i] ?: allOnesHash listOfLeaves.add(root) } listOfLeaves @@ -296,7 +307,7 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr * see the user-guide section "Transaction tear-offs" to learn more about this topic. */ internal val groupsMerkleRoots: Map by lazy { - availableComponentHashes.map { Pair(it.key, MerkleTree.getMerkleTree(it.value).hash) }.toMap() + availableComponentHashes.entries.associate { it.key to MerkleTree.getMerkleTree(it.value, digestService).hash } } /** @@ -309,7 +320,7 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr * nothing about the rest. */ internal val availableComponentNonces: Map> by lazy { - componentGroups.map { Pair(it.groupIndex, it.components.mapIndexed { internalIndex, internalIt -> componentHash(internalIt, privacySalt, it.groupIndex, internalIndex) }) }.toMap() + componentGroups.associate { it.groupIndex to it.components.mapIndexed { internalIndex, internalIt -> digestService.componentHash(internalIt, privacySalt, it.groupIndex, internalIndex) } } } /** @@ -318,7 +329,7 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr * see the user-guide section "Transaction tear-offs" to learn more about this topic. */ internal val availableComponentHashes: Map> by lazy { - componentGroups.map { Pair(it.groupIndex, it.components.mapIndexed { internalIndex, internalIt -> componentHash(availableComponentNonces[it.groupIndex]!![internalIndex], internalIt) }) }.toMap() + componentGroups.associate { it.groupIndex to it.components.mapIndexed { internalIndex, internalIt -> digestService.componentHash(availableComponentNonces[it.groupIndex]!![internalIndex], internalIt) } } } /** @@ -362,7 +373,7 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr @Suppress("UNCHECKED_CAST") when (coreTransaction) { is WireTransaction -> coreTransaction.componentGroups - .firstOrNull { it.groupIndex == ComponentGroupEnum.OUTPUTS_GROUP.ordinal } + .firstOrNull { it.groupIndex == OUTPUTS_GROUP.ordinal } ?.components ?.get(stateRef.index) as SerializedBytes>? is ContractUpgradeWireTransaction -> coreTransaction.resolveOutputComponent(services, stateRef, params) diff --git a/core/src/test/kotlin/net/corda/core/crypto/Blake2s256DigestServiceTest.kt b/core/src/test/kotlin/net/corda/core/crypto/Blake2s256DigestServiceTest.kt new file mode 100644 index 0000000000..368b261746 --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/crypto/Blake2s256DigestServiceTest.kt @@ -0,0 +1,67 @@ +package net.corda.core.crypto + +import net.corda.core.crypto.internal.DigestAlgorithmFactory +import org.bouncycastle.crypto.digests.Blake2sDigest +import org.junit.Assert.assertArrayEquals +import org.junit.Before +import org.junit.Test +import kotlin.test.assertEquals + +class Blake2s256DigestServiceTest { + class BLAKE2s256DigestService : DigestAlgorithm { + override val algorithm = "BLAKE_TEST" + + override val digestLength = 32 + + override fun digest(bytes: ByteArray): ByteArray { + val blake2s256 = Blake2sDigest(null, digestLength, null, "12345678".toByteArray()) + blake2s256.reset() + blake2s256.update(bytes, 0, bytes.size) + val hash = ByteArray(digestLength) + blake2s256.doFinal(hash, 0) + return hash + } + } + + private val service = DigestService("BLAKE_TEST") + + @Before + fun before() { + DigestAlgorithmFactory.registerClass(BLAKE2s256DigestService::class.java.name) + } + + @Test(timeout = 300_000) + fun testBlankHash() { + assertEquals( + "C59F682376D137F3F255E671E207D1F2374EBE504E9314208A52D9F88D69E8C8", + service.hash(byteArrayOf()).toHexString() + ) + } + + @Test(timeout = 300_000) + fun testHashBytes() { + val hash = service.hash(byteArrayOf(0x64, -0x13, 0x42, 0x3a)) + assertEquals("9EEA14092257E759ADAA56539A7A88DA1F68F03ABE3D9552A21D4731F4E6ECA0", hash.toHexString()) + } + + @Test(timeout = 300_000) + fun testHashString() { + val hash = service.hash("test") + assertEquals("AB76E8F7EEA1968C183D343B756EC812E47D4BC7A3F061F4DDE8948B3E05DAF2", hash.toHexString()) + } + + @Test(timeout = 300_000) + fun testGetAllOnesHash() { + assertArrayEquals(service.allOnesHash.bytes, ByteArray(32) { 0xFF.toByte() }) + } + + @Test(timeout = 300_000) + fun testGetZeroHash() { + assertArrayEquals(service.zeroHash.bytes, ByteArray(32)) + } + + @Test(timeout = 300_000) + fun `Blake2s256 does not retain state between same-thread invocations`() { + assertEquals(service.hash("abc"), service.hash("abc")) + } +} \ No newline at end of file diff --git a/core/src/test/kotlin/net/corda/core/crypto/SHA2256DigestServiceTest.kt b/core/src/test/kotlin/net/corda/core/crypto/SHA2256DigestServiceTest.kt new file mode 100644 index 0000000000..9b68963a5e --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/crypto/SHA2256DigestServiceTest.kt @@ -0,0 +1,46 @@ +package net.corda.core.crypto + +import org.junit.Assert +import org.junit.Test + +class SHA2256DigestServiceTest { + private val service: DigestService = DigestService.sha2_256 + + @Test(timeout = 300_000) + fun `test digest service hash and no prefix`() { + Assert.assertEquals( + "6D1687C143DF792A011A1E80670A4E4E0C25D0D87A39514409B1ABFC2043581F", + service.hash(byteArrayOf(0x64, -0x13, 0x42, 0x3a)).toString() + ) + } + + @Test(timeout = 300_000) + fun `test digest service hash blank and no prefix`() { + Assert.assertEquals( + "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", + service.hash(byteArrayOf()).toString() + ) + } + + @Test(timeout = 300_000) + fun `test sha2-256 digest service hash length`() { + Assert.assertEquals(32, service.digestLength) + } + + @Test(timeout = 300_000) + fun `test sha2-256 digest service hash name`() { + Assert.assertEquals("SHA-256", service.hashAlgorithm) + } + + @Test(timeout = 300_000) + fun `test sha2-256 digest service zero hash and no prefix`() { + Assert.assertEquals("0000000000000000000000000000000000000000000000000000000000000000", + service.zeroHash.toString()) + } + + @Test(timeout = 300_000) + fun `test sha2-256 digest service ones hash and no prefix`() { + Assert.assertEquals("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + service.allOnesHash.toString()) + } +} \ No newline at end of file diff --git a/core/src/test/kotlin/net/corda/core/crypto/SHA2384DigestServiceTest.kt b/core/src/test/kotlin/net/corda/core/crypto/SHA2384DigestServiceTest.kt new file mode 100644 index 0000000000..e9f018df46 --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/crypto/SHA2384DigestServiceTest.kt @@ -0,0 +1,46 @@ +package net.corda.core.crypto + +import org.junit.Assert +import org.junit.Test + +class SHA2384DigestServiceTest { + private val service: DigestService = DigestService.sha2_384 + + @Test(timeout = 300_000) + fun `test digest service hash and prefix`() { + Assert.assertEquals( + "SHA-384:5E3DBD33BEC467F625E28D4C5DF90CAACEA722F2DBB2AE9EF9C59EF4FB0FA31A070F5911156713F6AA0FCB09186B78FF", + service.hash(byteArrayOf(0x64, -0x13, 0x42, 0x3a)).toString() + ) + } + + @Test(timeout = 300_000) + fun `test digest service hash blank and prefix`() { + Assert.assertEquals( + "SHA-384:38B060A751AC96384CD9327EB1B1E36A21FDB71114BE07434C0CC7BF63F6E1DA274EDEBFE76F65FBD51AD2F14898B95B", + service.hash(byteArrayOf()).toString() + ) + } + + @Test(timeout = 300_000) + fun `test sha3-256 digest service hash length`() { + Assert.assertEquals(48, service.digestLength) + } + + @Test(timeout = 300_000) + fun `test sha3-256 digest service hash name`() { + Assert.assertEquals("SHA-384", service.hashAlgorithm) + } + + @Test(timeout = 300_000) + fun `test sha3-256 digest service zero hash and prefix`() { + Assert.assertEquals("SHA-384:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + service.zeroHash.toString()) + } + + @Test(timeout = 300_000) + fun `test sha3-256 digest service ones hash and prefix`() { + Assert.assertEquals("SHA-384:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + service.allOnesHash.toString()) + } +} \ No newline at end of file diff --git a/core/src/test/kotlin/net/corda/core/crypto/SecureHashTest.kt b/core/src/test/kotlin/net/corda/core/crypto/SecureHashTest.kt index 908300d016..ae29915d0d 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/SecureHashTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/SecureHashTest.kt @@ -1,6 +1,12 @@ package net.corda.core.crypto +import net.corda.core.crypto.SecureHash.Companion.SHA2_256 +import net.corda.core.internal.JavaVersion +import org.assertj.core.api.Assertions.assertThat +import org.junit.Assume import org.junit.Test +import org.junit.jupiter.api.assertThrows +import java.lang.IllegalArgumentException import kotlin.test.assertEquals class SecureHashTest { @@ -8,4 +14,65 @@ class SecureHashTest { fun `sha256 does not retain state between same-thread invocations`() { assertEquals(SecureHash.sha256("abc"), SecureHash.sha256("abc")) } + + @Test(timeout=300_000) + fun `new sha256 does not retain state between same-thread invocations`() { + assertEquals(SecureHash.hashAs("SHA-256", "abc".toByteArray()), SecureHash.hashAs("SHA-256", "abc".toByteArray())) + } + + @Test(timeout = 300_000) + fun `test new sha256 secure hash`() { + val hash = SecureHash.hashAs("SHA-256", byteArrayOf(0x64, -0x13, 0x42, 0x3a)) + assertEquals(SecureHash.create("SHA-256:6D1687C143DF792A011A1E80670A4E4E0C25D0D87A39514409B1ABFC2043581F"), hash) + assertEquals("6D1687C143DF792A011A1E80670A4E4E0C25D0D87A39514409B1ABFC2043581F", hash.toString()) + } + + @Test(timeout = 300_000) + fun `test new sha3-256 secure hash`() { + Assume.assumeTrue(JavaVersion.isVersionAtLeast(JavaVersion.Java_11)) + val hash = SecureHash.hashAs("SHA3-256", byteArrayOf(0x64, -0x13, 0x42, 0x3a)) + assertEquals(SecureHash.create("SHA3-256:A243D53F7273F4C92ED901A14F11B372FDF6FF69583149AFD4AFA24BF17A8880"), hash) + assertEquals("SHA3-256:A243D53F7273F4C92ED901A14F11B372FDF6FF69583149AFD4AFA24BF17A8880", hash.toString()) + } + + @Test(timeout = 300_000) + fun `test sha2-256 equivalence`() { + val data = byteArrayOf(0x64, -0x13, 0x42, 0x3a) + val oldHash = SecureHash.sha256(data) + val newHash = SecureHash.hashAs("SHA-256", data) + assertEquals(oldHash.hashCode(), newHash.hashCode()) + assertEquals(oldHash, newHash) + } + + @Test(timeout = 300_000) + fun `test unsafe sha-1 secure hash is banned`() { + val ex = assertThrows { + SecureHash.hashAs("SHA-1", byteArrayOf(0x64, -0x13, 0x42, 0x3a)) + } + assertThat(ex).hasMessage("SHA-1 is forbidden!") + } + + @Test(timeout = 300_000) + fun `test hash concatenation is equivalent`() { + val data = byteArrayOf(0x45, 0x33, -0x63, 0x2a, 0x76, -0x64, 0x01, 0x5f) + val oldHash = data.sha256() + val newHash = data.hashAs(SHA2_256) + val expectedHash = oldHash.hashConcat(oldHash) + assertEquals(expectedHash, newHash.concatenate(newHash)) + assertEquals(expectedHash, newHash.concatenate(oldHash)) + assertEquals(expectedHash, oldHash.concatenate(newHash)) + } + + @Test(timeout = 300_000) + fun `test lowercase letters are prohibited in the algorithm name`() { + assertThrows { + SecureHash.hashAs("sha-256", "abc".toByteArray()) + } + assertThrows { + SecureHash.hashAs("Sha-256", "abc".toByteArray()) + } + assertThrows { + SecureHash.hashAs("sha3-256", "abc".toByteArray()) + } + } } diff --git a/core/src/test/kotlin/net/corda/core/internal/InternalUtilsTest.kt b/core/src/test/kotlin/net/corda/core/internal/InternalUtilsTest.kt index 3afc31c8b5..ac2333cabd 100644 --- a/core/src/test/kotlin/net/corda/core/internal/InternalUtilsTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/InternalUtilsTest.kt @@ -127,7 +127,7 @@ open class InternalUtilsTest { fun `test SHA-256 hash for InputStream`() { val contents = arrayOfJunk(DEFAULT_BUFFER_SIZE * 2 + DEFAULT_BUFFER_SIZE / 2) assertThat(contents.inputStream().hash()) - .isEqualTo(SecureHash.parse("A4759E7AA20338328866A2EA17EAF8C7FE4EC6BBE3BB71CEE7DF7C0461B3C22F")) + .isEqualTo(SecureHash.create("A4759E7AA20338328866A2EA17EAF8C7FE4EC6BBE3BB71CEE7DF7C0461B3C22F")) } @Test(timeout=300_000) diff --git a/core/src/test/kotlin/net/corda/core/internal/internalAccessTestHelpers.kt b/core/src/test/kotlin/net/corda/core/internal/internalAccessTestHelpers.kt index c325c805e3..16ce7444ad 100644 --- a/core/src/test/kotlin/net/corda/core/internal/internalAccessTestHelpers.kt +++ b/core/src/test/kotlin/net/corda/core/internal/internalAccessTestHelpers.kt @@ -1,6 +1,7 @@ package net.corda.core.internal import net.corda.core.contracts.* +import net.corda.core.crypto.DigestService import net.corda.core.crypto.SecureHash import net.corda.core.identity.Party import net.corda.core.node.NetworkParameters @@ -34,8 +35,9 @@ fun createLedgerTransaction( serializedInputs: List? = null, serializedReferences: List? = null, isAttachmentTrusted: (Attachment) -> Boolean, - attachmentsClassLoaderCache: AttachmentsClassLoaderCache -): LedgerTransaction = LedgerTransaction.create(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references, componentGroups, serializedInputs, serializedReferences, isAttachmentTrusted, attachmentsClassLoaderCache) + attachmentsClassLoaderCache: AttachmentsClassLoaderCache, + digestService: DigestService = DigestService.default +): LedgerTransaction = LedgerTransaction.create(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references, componentGroups, serializedInputs, serializedReferences, isAttachmentTrusted, attachmentsClassLoaderCache, digestService) fun createContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable) = TransactionVerificationException.ContractCreationError(txId, contractClass, cause) fun createContractRejection(txId: SecureHash, contract: Contract, cause: Throwable) = TransactionVerificationException.ContractRejection(txId, contract, cause) diff --git a/detekt-baseline.xml b/detekt-baseline.xml index fdf411174a..4398ca38b0 100644 --- a/detekt-baseline.xml +++ b/detekt-baseline.xml @@ -184,7 +184,6 @@ EmptyCatchBlock:PersistentUniquenessProvider.kt$PersistentUniquenessProvider${ } EmptyCatchBlock:RPCClientProxyHandler.kt$RPCClientProxyHandler${} EmptyCatchBlock:RPCStabilityTests.kt$RPCStabilityTests${} - EmptyCatchBlock:ScheduledFlowIntegrationTests.kt$ScheduledFlowIntegrationTests${ } EmptyCatchBlock:TransactionCallbackTest.kt$TransactionCallbackTest${ } EmptyCatchBlock:WebServer.kt$WebServer${ } EmptyClassBlock:CordaRPCClient.kt$CordaRPCClient$Companion @@ -192,7 +191,6 @@ EmptyDefaultConstructor:FlowRetryTest.kt$RetryFlow$() EmptyDefaultConstructor:FlowRetryTest.kt$ThrowingFlow$() EmptyIfBlock:ContentSignerBuilder.kt$ContentSignerBuilder.SignatureOutputStream$if (alreadySigned) throw IllegalStateException("Cannot write to already signed object") - EmptyIfBlock:InMemoryIdentityService.kt$InMemoryIdentityService${ } EmptyKtFile:KryoHook.kt$.KryoHook.kt EmptyKtFile:ValidatingNotaryService.kt$.ValidatingNotaryService.kt EnumNaming:LoginView.kt$LoginView.LoginStatus$exception @@ -645,7 +643,6 @@ LongParameterList:IdenticonRenderer.kt$IdenticonRenderer$(g: GraphicsContext, x: Double, y: Double, patchIndex: Int, turn: Int, patchSize: Double, _invert: Boolean, color: PatchColor) LongParameterList:Injectors.kt$( metricRegistry: MetricRegistry, parallelism: Int, overallDuration: Duration, injectionRate: Rate, queueSizeMetricName: String = "QueueSize", workDurationMetricName: String = "WorkDuration", work: () -> Unit ) LongParameterList:InteractiveShell.kt$InteractiveShell$(nameFragment: String, inputData: String, output: RenderPrintWriter, rpcOps: CordaRPCOps, ansiProgressRenderer: ANSIProgressRenderer, inputObjectMapper: ObjectMapper = createYamlInputMapper(rpcOps)) - LongParameterList:InternalTestUtils.kt$(inputs: List<StateRef>, attachments: List<SecureHash>, outputs: List<TransactionState<*>>, commands: List<Command<*>>, notary: Party?, timeWindow: TimeWindow?, privacySalt: PrivacySalt = PrivacySalt()) LongParameterList:JarSignatureTestUtils.kt$JarSignatureTestUtils$(alias: String = "Test", storePassword: String = "secret!", name: String = CODE_SIGNER.toString(), keyalg: String = "RSA", keyPassword: String = storePassword, storeName: String = "_teststore") LongParameterList:MockServices.kt$MockServices.Companion$( cordappLoader: CordappLoader, identityService: IdentityService, networkParameters: NetworkParameters, initialIdentity: TestIdentity, moreKeys: Set<KeyPair>, keyManagementService: KeyManagementService, schemaService: SchemaService, persistence: CordaPersistence ) LongParameterList:MockServices.kt$MockServices.Companion$( cordappPackages: List<String>, initialIdentity: TestIdentity, networkParameters: NetworkParameters = testNetworkParameters(modifiedTime = Instant.MIN), moreKeys: Set<KeyPair>, moreIdentities: Set<PartyAndCertificate>, cacheFactory: TestingNamedCacheFactory = TestingNamedCacheFactory() ) @@ -1095,8 +1092,6 @@ MagicNumber:SearchField.kt$SearchField$5.0 MagicNumber:SecureArtemisConfiguration.kt$SecureArtemisConfiguration$128 MagicNumber:SecureArtemisConfiguration.kt$SecureArtemisConfiguration$16 - MagicNumber:SecureHash.kt$SecureHash.Companion$32 - MagicNumber:SecureHash.kt$SecureHash.SHA256$32 MagicNumber:SerializationEnvironmentHelper.kt$SerializationEnvironmentHelper$128 MagicNumber:ShutdownManager.kt$ShutdownManager$5 MagicNumber:ShutdownManager.kt$ShutdownManager$60 @@ -1106,7 +1101,6 @@ MagicNumber:StaffedFlowHospital.kt$StaffedFlowHospital$10 MagicNumber:StandaloneShell.kt$StandaloneShell$7 MagicNumber:StateRevisionFlow.kt$StateRevisionFlow.Requester$30 - MagicNumber:Structures.kt$PrivacySalt$32 MagicNumber:TestNodeInfoBuilder.kt$TestNodeInfoBuilder$1234 MagicNumber:TestUtils.kt$10000 MagicNumber:TestUtils.kt$30000 @@ -1541,7 +1535,6 @@ TooGenericExceptionCaught:ReconnectingObservable.kt$ReconnectingObservable.ReconnectingSubscriber$e: Exception TooGenericExceptionCaught:RpcServerObservableSerializerTests.kt$RpcServerObservableSerializerTests$e: Exception TooGenericExceptionCaught:SSLHelper.kt$ex: Exception - TooGenericExceptionCaught:ScheduledFlowIntegrationTests.kt$ScheduledFlowIntegrationTests$ex: Exception TooGenericExceptionCaught:SerializationOutputTests.kt$SerializationOutputTests$t: Throwable TooGenericExceptionCaught:ShutdownManager.kt$ShutdownManager$t: Throwable TooGenericExceptionCaught:SimpleAMQPClient.kt$SimpleAMQPClient$e: Exception diff --git a/finance/contracts/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt b/finance/contracts/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt index e2be35de43..28b02a29cd 100644 --- a/finance/contracts/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt +++ b/finance/contracts/src/test/kotlin/net/corda/finance/contracts/asset/ObligationTests.kt @@ -58,6 +58,8 @@ class ObligationTests { val MEGA_CORP_PUBKEY get() = megaCorp.publicKey val MINI_CORP get() = miniCorp.party val MINI_CORP_PUBKEY get() = miniCorp.publicKey + // TODO(iee): Obligations fail to find attachments if we use anything else than sha2_256. All attachment logic is still + // using direct calls to .sha256() which still needs to be replaced with DigestService.default } @Rule @@ -324,7 +326,6 @@ class ObligationTests { private inline fun getStateAndRef(state: T, contractClassName: ContractClassName): StateAndRef { val txState = TransactionState(state, contractClassName, DUMMY_NOTARY, constraint = AlwaysAcceptAttachmentConstraint) return StateAndRef(txState, StateRef(SecureHash.randomSHA256(), 0)) - } /** Test generating a transaction to mark outputs as having defaulted. */ diff --git a/finance/workflows/src/main/kotlin/net/corda/finance/workflows/asset/selection/AbstractCashSelection.kt b/finance/workflows/src/main/kotlin/net/corda/finance/workflows/asset/selection/AbstractCashSelection.kt index 1a2d6ca5b2..403c80e287 100644 --- a/finance/workflows/src/main/kotlin/net/corda/finance/workflows/asset/selection/AbstractCashSelection.kt +++ b/finance/workflows/src/main/kotlin/net/corda/finance/workflows/asset/selection/AbstractCashSelection.kt @@ -128,7 +128,7 @@ abstract class AbstractCashSelection(private val maxRetries : Int = 8, private v var totalPennies = 0L val stateRefs = mutableSetOf() while (rs.next()) { - val txHash = SecureHash.parse(rs.getString(1)) + val txHash = SecureHash.create(rs.getString(1)) val index = rs.getInt(2) val pennies = rs.getLong(3) totalPennies = rs.getLong(4) diff --git a/finance/workflows/src/main/resources/migration/cash.changelog-master.xml b/finance/workflows/src/main/resources/migration/cash.changelog-master.xml index 75b78444bd..4c04c59c5a 100644 --- a/finance/workflows/src/main/resources/migration/cash.changelog-master.xml +++ b/finance/workflows/src/main/resources/migration/cash.changelog-master.xml @@ -6,5 +6,6 @@ + diff --git a/finance/workflows/src/main/resources/migration/cash.changelog-v1.xml b/finance/workflows/src/main/resources/migration/cash.changelog-v1.xml index a657a9f075..1f8e2c6635 100644 --- a/finance/workflows/src/main/resources/migration/cash.changelog-v1.xml +++ b/finance/workflows/src/main/resources/migration/cash.changelog-v1.xml @@ -2,8 +2,7 @@ + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"> @@ -24,4 +23,4 @@ - \ No newline at end of file + diff --git a/finance/workflows/src/main/resources/migration/cash.changelog-v2.xml b/finance/workflows/src/main/resources/migration/cash.changelog-v2.xml new file mode 100644 index 0000000000..dfbf5ad97c --- /dev/null +++ b/finance/workflows/src/main/resources/migration/cash.changelog-v2.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/finance/workflows/src/main/resources/migration/commercial-paper.changelog-master.xml b/finance/workflows/src/main/resources/migration/commercial-paper.changelog-master.xml index 41c6d30d9f..1020b28913 100644 --- a/finance/workflows/src/main/resources/migration/commercial-paper.changelog-master.xml +++ b/finance/workflows/src/main/resources/migration/commercial-paper.changelog-master.xml @@ -6,5 +6,6 @@ + diff --git a/finance/workflows/src/main/resources/migration/commercial-paper.changelog-v2.xml b/finance/workflows/src/main/resources/migration/commercial-paper.changelog-v2.xml new file mode 100644 index 0000000000..4ada1f2c04 --- /dev/null +++ b/finance/workflows/src/main/resources/migration/commercial-paper.changelog-v2.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt index 03133c780a..e422fee9a5 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt @@ -130,7 +130,7 @@ const val DEV_CA_TRUST_STORE_PRIVATE_KEY_PASS: String = "trustpasskeypass" // https://github.com/corda/corda-gradle-plugins/blob/master/cordapp/src/main/resources/certificates/cordadevcodesign.jks const val DEV_CORDAPP_CODE_SIGNING_STR = "AA59D829F2CA8FDDF5ABEA40D815F937E3E54E572B65B93B5C216AE6594E7D6B" -val DEV_PUB_KEY_HASHES: List get() = listOf(DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate).map { it.publicKey.hash.sha256() } + SecureHash.parse(DEV_CORDAPP_CODE_SIGNING_STR).sha256() +val DEV_PUB_KEY_HASHES: List get() = listOf(DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate).map { it.publicKey.hash.sha256() } + SecureHash.create(DEV_CORDAPP_CODE_SIGNING_STR).sha256() // We need a class so that we can get hold of the class loader internal object DevCaHelper { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt index 346a22a207..b08866495b 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/kryo/Kryo.kt @@ -10,6 +10,7 @@ import com.esotericsoftware.kryo.util.MapReferenceResolver import net.corda.core.DeleteForDJVM import net.corda.core.contracts.PrivacySalt import net.corda.core.crypto.Crypto +import net.corda.core.crypto.DigestService import net.corda.core.crypto.SecureHash import net.corda.core.crypto.TransactionSignature import net.corda.core.internal.LazyMappedList @@ -214,12 +215,15 @@ object WireTransactionSerializer : Serializer() { override fun write(kryo: Kryo, output: Output, obj: WireTransaction) { kryo.writeClassAndObject(output, obj.componentGroups) kryo.writeClassAndObject(output, obj.privacySalt) + kryo.writeClassAndObject(output, obj.digestService) } override fun read(kryo: Kryo, input: Input, type: Class): WireTransaction { val componentGroups: List = uncheckedCast(kryo.readClassAndObject(input)) val privacySalt = kryo.readClassAndObject(input) as PrivacySalt - return WireTransaction(componentGroups, privacySalt) + // TODO(iee): handle backward compatibility when deserializing old version of WTX + val digestService = kryo.readClassAndObject(input) as? DigestService + return WireTransaction(componentGroups, privacySalt, digestService ?: DigestService.sha2_256) } } @@ -227,11 +231,14 @@ object WireTransactionSerializer : Serializer() { object NotaryChangeWireTransactionSerializer : Serializer() { override fun write(kryo: Kryo, output: Output, obj: NotaryChangeWireTransaction) { kryo.writeClassAndObject(output, obj.serializedComponents) + kryo.writeClassAndObject(output, obj.digestService) } override fun read(kryo: Kryo, input: Input, type: Class): NotaryChangeWireTransaction { val components: List = uncheckedCast(kryo.readClassAndObject(input)) - return NotaryChangeWireTransaction(components) + // TODO(iee): handle backward compatibility when deserializing old version of NCWTX + val digestService = kryo.readClassAndObject(input) as? DigestService + return NotaryChangeWireTransaction(components, digestService ?: DigestService.sha2_256) } } @@ -240,13 +247,15 @@ object ContractUpgradeWireTransactionSerializer : Serializer): ContractUpgradeWireTransaction { val components: List = uncheckedCast(kryo.readClassAndObject(input)) val privacySalt = kryo.readClassAndObject(input) as PrivacySalt - - return ContractUpgradeWireTransaction(components, privacySalt) + // TODO(iee): handle backward compatibility when deserializing old version of WTX + val digestService = kryo.readClassAndObject(input) as? DigestService + return ContractUpgradeWireTransaction(components, privacySalt, digestService ?: DigestService.sha2_256) } } diff --git a/node/djvm/src/main/kotlin/net/corda/node/djvm/LtxFactory.kt b/node/djvm/src/main/kotlin/net/corda/node/djvm/LtxFactory.kt index 31c1b2aaa8..f12f0cb108 100644 --- a/node/djvm/src/main/kotlin/net/corda/node/djvm/LtxFactory.kt +++ b/node/djvm/src/main/kotlin/net/corda/node/djvm/LtxFactory.kt @@ -10,6 +10,7 @@ import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef import net.corda.core.contracts.TimeWindow import net.corda.core.contracts.TransactionState +import net.corda.core.crypto.DigestService import net.corda.core.crypto.SecureHash import net.corda.core.identity.Party import net.corda.core.node.NetworkParameters @@ -26,6 +27,7 @@ private const val TX_TIME_WINDOW = 6 private const val TX_PRIVACY_SALT = 7 private const val TX_NETWORK_PARAMETERS = 8 private const val TX_REFERENCES = 9 +private const val TX_DIGEST_SERVICE = 10 class LtxFactory : Function, LedgerTransaction> { @@ -41,7 +43,8 @@ class LtxFactory : Function, LedgerTransaction> { timeWindow = txArgs[TX_TIME_WINDOW] as? TimeWindow, privacySalt = txArgs[TX_PRIVACY_SALT] as PrivacySalt, networkParameters = txArgs[TX_NETWORK_PARAMETERS] as NetworkParameters, - references = (txArgs[TX_REFERENCES] as Array>).map { it.toStateAndRef() } + references = (txArgs[TX_REFERENCES] as Array>).map { it.toStateAndRef() }, + digestService = if (txArgs.size > TX_DIGEST_SERVICE) (txArgs[TX_DIGEST_SERVICE] as DigestService) else DigestService.sha2_256 ) } diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 10eecb5673..589709abd1 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -850,10 +850,10 @@ abstract class AbstractNode(val configuration: NodeConfiguration, ) } - private fun parseSecureHashConfiguration(unparsedConfig: List, errorMessage: (String) -> String): List { + private fun parseSecureHashConfiguration(unparsedConfig: List, errorMessage: (String) -> String): List { return unparsedConfig.map { try { - SecureHash.parse(it) + SecureHash.create(it) } catch (e: IllegalArgumentException) { log.error("${errorMessage(it)} due to - ${e.message}", e) throw e diff --git a/node/src/main/kotlin/net/corda/node/internal/DBNetworkParametersStorage.kt b/node/src/main/kotlin/net/corda/node/internal/DBNetworkParametersStorage.kt index cb38734848..634225e0c1 100644 --- a/node/src/main/kotlin/net/corda/node/internal/DBNetworkParametersStorage.kt +++ b/node/src/main/kotlin/net/corda/node/internal/DBNetworkParametersStorage.kt @@ -45,7 +45,7 @@ class DBNetworkParametersStorage( toPersistentEntityKey = { it.toString() }, fromPersistentEntity = { Pair( - SecureHash.parse(it.hash), + SecureHash.create(it.hash), it.signedNetworkParameters ) }, diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index 93353de002..5f81969305 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -175,6 +175,11 @@ open class NodeStartup : NodeStartupLogging { // This needs to go after initLogging(netty clashes with our logging) Crypto.registerProviders() + // Temp Step. Enable experimental hash agility feature allowing to override default transaction hash algorithm. + val txHashAlgoName = System.getProperty("corda.experimental.txHashAlgoName") + val txHashAlgoClass = System.getProperty("corda.experimental.txHashAlgoClass") + HashAgility.init(txHashAlgoName, txHashAlgoClass) + // Step 4. Print banner and basic node info. val versionInfo = getVersionInfo() drawBanner(versionInfo) diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt index 0a01cea940..79a657b0ad 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappProviderImpl.kt @@ -100,7 +100,7 @@ open class CordappProviderImpl(val cordappLoader: CordappLoader, ) } } catch (faee: java.nio.file.FileAlreadyExistsException) { - AttachmentId.parse(faee.message!!) + AttachmentId.create(faee.message!!) } } to cordapp.jarPath }.toMap() @@ -151,7 +151,7 @@ open class CordappProviderImpl(val cordappLoader: CordappLoader, private fun parseIds(ids: String): Set { return ids.split(",").map(String::trim) .filterNot(String::isEmpty) - .mapTo(LinkedHashSet(), SecureHash.Companion::parse) + .mapTo(LinkedHashSet(), SecureHash.Companion::create) } /** diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt index 4098576b03..ef008b252c 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt @@ -49,7 +49,7 @@ import kotlin.streams.toList class JarScanningCordappLoader private constructor(private val cordappJarPaths: List, private val versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List, - private val signerKeyFingerprintBlacklist: List = emptyList()) : CordappLoaderTemplate() { + private val signerKeyFingerprintBlacklist: List = emptyList()) : CordappLoaderTemplate() { init { if (cordappJarPaths.isEmpty()) { logger.info("No CorDapp paths provided") @@ -75,7 +75,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths: fun fromDirectories(cordappDirs: Collection, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List = emptyList(), - signerKeyFingerprintBlacklist: List = emptyList()): JarScanningCordappLoader { + signerKeyFingerprintBlacklist: List = emptyList()): JarScanningCordappLoader { logger.info("Looking for CorDapps in ${cordappDirs.distinct().joinToString(", ", "[", "]")}") val paths = cordappDirs.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() } return JarScanningCordappLoader(paths, versionInfo, extraCordapps, signerKeyFingerprintBlacklist) @@ -87,7 +87,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths: * @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection. */ fun fromJarUrls(scanJars: List, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List = emptyList(), - cordappsSignerKeyFingerprintBlacklist: List = emptyList()): JarScanningCordappLoader { + cordappsSignerKeyFingerprintBlacklist: List = emptyList()): JarScanningCordappLoader { val paths = scanJars.map { it.restricted() } return JarScanningCordappLoader(paths, versionInfo, extraCordapps, cordappsSignerKeyFingerprintBlacklist) } diff --git a/node/src/main/kotlin/net/corda/node/internal/djvm/ComponentFactory.kt b/node/src/main/kotlin/net/corda/node/internal/djvm/ComponentFactory.kt index e75da8eb70..adba2daa13 100644 --- a/node/src/main/kotlin/net/corda/node/internal/djvm/ComponentFactory.kt +++ b/node/src/main/kotlin/net/corda/node/internal/djvm/ComponentFactory.kt @@ -2,7 +2,7 @@ package net.corda.node.internal.djvm import net.corda.core.contracts.ComponentGroupEnum -import net.corda.core.crypto.componentHash +import net.corda.core.crypto.DigestService import net.corda.core.transactions.ComponentGroup import net.corda.core.transactions.FilteredComponentGroup import net.corda.djvm.rewiring.SandboxClassLoader @@ -29,10 +29,10 @@ class ComponentFactory( )) } - fun calculateLeafIndicesFor(groupType: ComponentGroupEnum): IntArray? { + fun calculateLeafIndicesFor(groupType: ComponentGroupEnum, digestService: DigestService): IntArray? { val componentGroup = componentGroups.firstOrNull(groupType::isSameType) as? FilteredComponentGroup ?: return null val componentHashes = componentGroup.components.mapIndexed { index, component -> - componentHash(componentGroup.nonces[index], component) + digestService.componentHash(componentGroup.nonces[index], component) } return componentHashes.map { componentGroup.partialMerkleTree.leafIndex(it) }.toIntArray() } diff --git a/node/src/main/kotlin/net/corda/node/internal/djvm/DeterministicVerifier.kt b/node/src/main/kotlin/net/corda/node/internal/djvm/DeterministicVerifier.kt index bbca77ce47..654b218aee 100644 --- a/node/src/main/kotlin/net/corda/node/internal/djvm/DeterministicVerifier.kt +++ b/node/src/main/kotlin/net/corda/node/internal/djvm/DeterministicVerifier.kt @@ -91,6 +91,7 @@ class DeterministicVerifier( val timeWindowData = ltx.timeWindow?.serialize() val privacySaltData = ltx.privacySalt.serialize() val networkingParametersData = ltx.networkParameters?.serialize() + val digestServiceData = ltx.digestService.serialize() val createSandboxTx = taskFactory.apply(LtxFactory::class.java) createSandboxTx.apply(arrayOf( @@ -99,7 +100,7 @@ class DeterministicVerifier( CommandFactory(taskFactory).toSandbox( componentFactory.toSandbox(SIGNERS_GROUP, List::class.java), componentFactory.toSandbox(COMMANDS_GROUP, CommandData::class.java), - componentFactory.calculateLeafIndicesFor(COMMANDS_GROUP) + componentFactory.calculateLeafIndicesFor(COMMANDS_GROUP, digestService = ltx.digestService) ), attachmentFactory.toSandbox(ltx.attachments), serializer.deserialize(idData), @@ -107,7 +108,8 @@ class DeterministicVerifier( serializer.deserialize(timeWindowData), serializer.deserialize(privacySaltData), serializer.deserialize(networkingParametersData), - serializer.deserialize(serializedReferences) + serializer.deserialize(serializedReferences), + serializer.deserialize(digestServiceData) )) } diff --git a/node/src/main/kotlin/net/corda/node/internal/schemas/NodeInfoSchema.kt b/node/src/main/kotlin/net/corda/node/internal/schemas/NodeInfoSchema.kt index c7640267ab..da1c80d2c6 100644 --- a/node/src/main/kotlin/net/corda/node/internal/schemas/NodeInfoSchema.kt +++ b/node/src/main/kotlin/net/corda/node/internal/schemas/NodeInfoSchema.kt @@ -29,6 +29,11 @@ object NodeInfoSchemaV1 : MappedSchema( @Column(name = "node_info_id", nullable = false) var id: Int, + // TODO(iee): this field receives a hardcoded SHA2_256 string that comes from SerializationAPI's + // SerializedBytes' hash method that calls directly to .sha256(). It appears to be used + // only for serialized NodeInfo instances. Requires review and discussion to determine if + // it should follow the node/system hash algorithm or remain sha256 (in case it does change, + // it will be necessary to change length from 64 to 144. @Suppress("MagicNumber") // database column width @Column(name = "node_info_hash", length = 64, nullable = false) val hash: String, diff --git a/node/src/main/kotlin/net/corda/node/migration/VaultStateMigration.kt b/node/src/main/kotlin/net/corda/node/migration/VaultStateMigration.kt index b776e21a84..f33418c28e 100644 --- a/node/src/main/kotlin/net/corda/node/migration/VaultStateMigration.kt +++ b/node/src/main/kotlin/net/corda/node/migration/VaultStateMigration.kt @@ -61,7 +61,7 @@ class VaultStateMigration : CordaMigration() { private fun getStateAndRef(persistentState: VaultSchemaV1.VaultStates): StateAndRef { val persistentStateRef = persistentState.stateRef ?: throw VaultStateMigrationException("Persistent state ref missing from state") - val txHash = SecureHash.parse(persistentStateRef.txId) + val txHash = SecureHash.create(persistentStateRef.txId) val stateRef = StateRef(txHash, persistentStateRef.index) val state = try { servicesForResolution.loadState(stateRef) diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt index 273a95dfaa..8139b3b0a4 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -156,6 +156,7 @@ interface ServiceHubInternal : ServiceHubCoreInternal { val cacheFactory: NamedCacheFactory override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { + txs.forEach { requireSupportedHashType(it) } recordTransactions( statesToRecord, txs as? Collection ?: txs.toList(), // We can't change txs to a Collection as it's now part of the public API @@ -247,7 +248,7 @@ interface WritableTransactionStorage : TransactionStorage { /** * Add a new *verified* transaction to the store, or convert the existing unverified transaction into a verified one. * @param transaction The transaction to be recorded. - * @return true if the transaction was recorded as a *new verified* transcation, false if the transaction already exists. + * @return true if the transaction was recorded as a *new verified* transaction, false if the transaction already exists. */ // TODO: Throw an exception if trying to add a transaction with fewer signatures than an existing entry. fun addTransaction(transaction: SignedTransaction): Boolean diff --git a/node/src/main/kotlin/net/corda/node/services/events/PersistentScheduledFlowRepository.kt b/node/src/main/kotlin/net/corda/node/services/events/PersistentScheduledFlowRepository.kt index cff3ac39cb..2208eef88f 100644 --- a/node/src/main/kotlin/net/corda/node/services/events/PersistentScheduledFlowRepository.kt +++ b/node/src/main/kotlin/net/corda/node/services/events/PersistentScheduledFlowRepository.kt @@ -27,7 +27,7 @@ class PersistentScheduledFlowRepository(val database: CordaPersistence) : Schedu private fun fromPersistentEntity(scheduledStateRecord: NodeSchedulerService.PersistentScheduledState): Pair { val txId = scheduledStateRecord.output.txId val index = scheduledStateRecord.output.index - return Pair(StateRef(SecureHash.parse(txId), index), ScheduledStateRef(StateRef(SecureHash.parse(txId), index), scheduledStateRecord.scheduledAt)) + return Pair(StateRef(SecureHash.create(txId), index), ScheduledStateRef(StateRef(SecureHash.create(txId), index), scheduledStateRecord.scheduledAt)) } override fun delete(key: StateRef): Boolean { diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index d1863fbc2f..a186241f64 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -75,7 +75,7 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory, select(get(NodeInfoSchemaV1.PersistentNodeInfo::hash.name)) } } - session.createQuery(query).resultList.map { SecureHash.parse(it) } + session.createQuery(query).resultList.map { SecureHash.create(it) } } } diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt index d3bec19d88..32dbed2f24 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionMappingStorage.kt @@ -37,7 +37,7 @@ class DBTransactionMappingStorage(private val database: CordaPersistence) : Stat val from = cq.from(DBTransactionStorage.DBTransaction::class.java) cq.multiselect(from.get(DBTransactionStorage.DBTransaction::stateMachineRunId.name), from.get(DBTransactionStorage.DBTransaction::txId.name)) cq.where(cb.isNotNull(from.get(DBTransactionStorage.DBTransaction::stateMachineRunId.name))) - val flowIds = session.createQuery(cq).resultList.map { StateMachineTransactionMapping(StateMachineRunId(UUID.fromString(it[0] as String)), SecureHash.parse(it[1] as String)) } + val flowIds = session.createQuery(cq).resultList.map { StateMachineTransactionMapping(StateMachineRunId(UUID.fromString(it[0] as String)), SecureHash.create(it[1] as String)) } DataFeed(flowIds, updates.bufferUntilSubscribed().wrapWithDatabaseTransaction()) } } diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt index 7bafadc44e..aeeea1dba8 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/DBTransactionStorage.kt @@ -38,7 +38,7 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory: @Table(name = "${NODE_DATABASE_PREFIX}transactions") class DBTransaction( @Id - @Column(name = "tx_id", length = 64, nullable = false) + @Column(name = "tx_id", length = 144, nullable = false) val txId: String, @Column(name = "state_machine_run_id", length = 36, nullable = true) @@ -120,7 +120,7 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory: name = "DBTransactionStorage_transactions", toPersistentEntityKey = SecureHash::toString, fromPersistentEntity = { - SecureHash.parse(it.txId) to TxCacheValue( + SecureHash.create(it.txId) to TxCacheValue( it.transaction.deserialize(context = contextToUse()), it.status) }, diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt index cebe0f7809..dbf36b9bae 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/NodeAttachmentService.kt @@ -102,7 +102,7 @@ class NodeAttachmentService @JvmOverloads constructor( throw SecurityException("Signed jar has been tampered with. Files ${allManifestEntries} have been removed.") } val extraSignableFiles = extraFilesNotFoundInEntries.filterNot { JarSignatureCollector.isNotSignable(it) } - if (extraSignableFiles.size > 0) { + if (extraSignableFiles.isNotEmpty()) { throw SecurityException("Signed jar has been tampered with. Files ${extraSignableFiles} have been added to the JAR.") } } @@ -173,7 +173,7 @@ class NodeAttachmentService @JvmOverloads constructor( * this will provide an additional safety check against user error. */ @VisibleForTesting - class HashCheckingStream(val expected: SecureHash.SHA256, + class HashCheckingStream(val expected: SecureHash, val expectedSize: Int, input: InputStream, private val counter: CountingInputStream = CountingInputStream(input), @@ -288,7 +288,7 @@ class NodeAttachmentService @JvmOverloads constructor( private fun createAttachmentFromDatabase(attachment: DBAttachment): Attachment { val attachmentImpl = AttachmentImpl( - id = SecureHash.parse(attachment.attId), + id = SecureHash.create(attachment.attId), dataLoader = { attachment.content }, checkOnLoad = checkAttachmentsOnLoad, uploader = attachment.uploader, @@ -358,7 +358,7 @@ class NodeAttachmentService @JvmOverloads constructor( return try { import(jar, uploader, filename) } catch (faee: java.nio.file.FileAlreadyExistsException) { - AttachmentId.parse(faee.message!!) + AttachmentId.create(faee.message!!) } } @@ -454,7 +454,7 @@ class NodeAttachmentService @JvmOverloads constructor( return try { import(jar, UNKNOWN_UPLOADER, null) } catch (faee: java.nio.file.FileAlreadyExistsException) { - AttachmentId.parse(faee.message!!) + AttachmentId.create(faee.message!!) } } @@ -464,7 +464,7 @@ class NodeAttachmentService @JvmOverloads constructor( createAttachmentsIdsQuery( criteria, sorting - ).resultList.map { AttachmentId.parse(it) } + ).resultList.map { AttachmentId.create(it) } } } @@ -477,7 +477,7 @@ class NodeAttachmentService @JvmOverloads constructor( val criteriaQuery = criteriaBuilder.createQuery(String::class.java) val root = criteriaQuery.from(DBAttachment::class.java) - criteriaQuery.select(root.get("${DBAttachment::attId.name}")) + criteriaQuery.select(root.get(DBAttachment::attId.name)) val criteriaParser = HibernateAttachmentQueryCriteriaParser( criteriaBuilder, @@ -562,12 +562,12 @@ class NodeAttachmentService @JvmOverloads constructor( } private fun makeAttachmentIds(it: Map.Entry>, contractClassName: String): Pair { - val signed = it.value.filter { it.signers?.isNotEmpty() ?: false }.map { AttachmentId.parse(it.attId) } + val signed = it.value.filter { it.signers?.isNotEmpty() ?: false }.map { AttachmentId.create(it.attId) } if (!devMode) check(signed.size <= 1) //sanity check else log.warn("(Dev Mode) Multiple signed attachments ${signed.map { it.toString() }} for contract $contractClassName version '${it.key}'.") - val unsigned = it.value.filter { it.signers?.isEmpty() ?: true }.map { AttachmentId.parse(it.attId) } + val unsigned = it.value.filter { it.signers?.isEmpty() ?: true }.map { AttachmentId.create(it.attId) } if (unsigned.size > 1) log.warn("Selecting attachment ${unsigned.first()} from duplicated, unsigned attachments ${unsigned.map { it.toString() }} for contract $contractClassName version '${it.key}'.") return it.key to AttachmentIds(signed.firstOrNull(), unsigned.firstOrNull()) diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt index 6b4b3743b8..8d0b6e1bf7 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt @@ -5,7 +5,6 @@ import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.StateRef import net.corda.core.contracts.TimeWindow import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.sha256 import net.corda.core.flows.NotarisationRequestSignature import net.corda.core.flows.NotaryError import net.corda.core.flows.StateConsumptionDetails @@ -88,7 +87,7 @@ class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersiste @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}notary_committed_txs") class CommittedTransaction( @Id - @Column(name = "transaction_id", nullable = false, length = 64) + @Column(name = "transaction_id", nullable = false, length = 144) val transactionId: String ) @@ -161,8 +160,8 @@ class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersiste val txId = it.id.txId val index = it.id.index Pair( - StateRef(txhash = SecureHash.parse(txId), index = index), - SecureHash.parse(it.consumingTxHash) + StateRef(txhash = SecureHash.create(txId), index = index), + SecureHash.create(it.consumingTxHash) ) }, @@ -218,7 +217,7 @@ class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersiste fun checkConflicts(toCheck: List, type: StateConsumptionDetails.ConsumedStateType) { return toCheck.forEach { stateRef -> val consumingTx = commitLog[stateRef] - if (consumingTx != null) conflictingStates[stateRef] = StateConsumptionDetails(consumingTx.sha256(), type) + if (consumingTx != null) conflictingStates[stateRef] = StateConsumptionDetails(consumingTx.reHash(), type) } } @@ -266,7 +265,8 @@ class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersiste } private fun handleConflicts(txId: SecureHash, conflictingStates: LinkedHashMap) { - if (isConsumedByTheSameTx(txId.sha256(), conflictingStates)) { + // TODO(iee): is the use of reHash correct here? Check backward/forward compatibility + if (isConsumedByTheSameTx(txId.reHash(), conflictingStates)) { log.info("Transaction $txId already notarised. TxId: $txId") return } else { diff --git a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt index ad7d80059d..e7846b2821 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt @@ -473,7 +473,7 @@ class NodeVaultService( val session = currentDBSession() val criteriaBuilder = session.criteriaBuilder fun execute(configure: Root<*>.(CriteriaUpdate<*>, Array) -> Any?) = criteriaBuilder.executeUpdate(session, null) { update, _ -> - val persistentStateRefs = stateRefs.map { PersistentStateRef(it.txhash.bytes.toHexString(), it.index) } + val persistentStateRefs = stateRefs.map { PersistentStateRef(it.txhash.toString(), it.index) } val compositeKey = get(VaultSchemaV1.VaultStates::stateRef.name) val stateRefsPredicate = criteriaBuilder.and(compositeKey.`in`(persistentStateRefs)) configure(update, arrayOf(stateRefsPredicate)) @@ -715,7 +715,7 @@ class NodeVaultService( if (!paging.isDefault && index == paging.pageSize) // skip last result if paged return@forEachIndexed val vaultState = result[0] as VaultSchemaV1.VaultStates - val stateRef = StateRef(SecureHash.parse(vaultState.stateRef!!.txId), vaultState.stateRef!!.index) + val stateRef = StateRef(SecureHash.create(vaultState.stateRef!!.txId), vaultState.stateRef!!.index) stateRefs.add(stateRef) statesMeta.add(Vault.StateMetadata(stateRef, vaultState.contractStateClassName, @@ -869,7 +869,7 @@ private fun CriteriaBuilder.executeUpdate( // Increase SQL server performance by, processing updates in chunks allowing the database's optimizer to make use of the index. var updatedRows = 0 it.asSequence() - .map { stateRef -> PersistentStateRef(stateRef.txhash.bytes.toHexString(), stateRef.index) } + .map { stateRef -> PersistentStateRef(stateRef.txhash.toString(), stateRef.index) } .chunked(NodeVaultService.DEFAULT_SOFT_LOCKING_SQL_IN_CLAUSE_SIZE) .forEach { persistentStateRefs -> updatedRows += doUpdate(persistentStateRefs) diff --git a/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt b/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt index 6b5b5fe0c5..06844d40d0 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt @@ -151,7 +151,7 @@ object VaultSchemaV1 : MappedSchema( @Column(name = "seq_no", nullable = false) var seqNo: Int, - @Column(name = "transaction_id", length = 64, nullable = true) + @Column(name = "transaction_id", length = 144, nullable = true) var txId: String?, @Column(name = "note", nullable = true) 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 4fc3bb4e25..43dad3459a 100644 --- a/node/src/main/kotlin/net/corda/notary/common/BatchSigning.kt +++ b/node/src/main/kotlin/net/corda/notary/common/BatchSigning.kt @@ -7,8 +7,8 @@ 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.sha256 import net.corda.core.flows.NotaryError +import net.corda.core.internal.digestService import net.corda.core.node.ServiceHub import java.security.PublicKey @@ -20,7 +20,17 @@ fun signBatch( notaryIdentityKey: PublicKey, services: ServiceHub ): BatchSignature { - val merkleTree = MerkleTree.getMerkleTree(txIds.map { it.sha256() }) + val algorithms = txIds.mapTo(HashSet(), SecureHash::algorithm) + require(algorithms.size > 0) { + "Cannot sign an empty batch" + } + // TODO(iee): too strict? will be valid in the future? + require(algorithms.size == 1) { + "Cannot sign a batch with multiple hash algorithms: $algorithms" + } + // TODO(iee): assuming this is running on a notary node, and therefore using only the default + // hash algorithm. Review and discuss. + val merkleTree = MerkleTree.getMerkleTree(txIds.map { it.reHash() }, services.digestService) val merkleTreeRoot = merkleTree.hash val signableData = SignableData( merkleTreeRoot, @@ -44,11 +54,14 @@ data class BatchSignature( val fullMerkleTree: MerkleTree) { /** Extracts a signature with a partial Merkle tree for the specified leaf in the batch signature. */ fun forParticipant(txId: SecureHash): TransactionSignature { + require(fullMerkleTree.hash.algorithm == txId.algorithm) { + "The leaf hash algorithm ${txId.algorithm} does not match the Merkle tree hash algorithm ${fullMerkleTree.hash.algorithm}" + } return TransactionSignature( rootSignature.bytes, rootSignature.by, rootSignature.signatureMetadata, - PartialMerkleTree.build(fullMerkleTree, listOf(txId.sha256())) + PartialMerkleTree.build(fullMerkleTree, listOf(txId.reHash())) ) } } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/notary/experimental/bftsmart/BFTSmart.kt b/node/src/main/kotlin/net/corda/notary/experimental/bftsmart/BFTSmart.kt index 03865fed1a..df8cf87857 100644 --- a/node/src/main/kotlin/net/corda/notary/experimental/bftsmart/BFTSmart.kt +++ b/node/src/main/kotlin/net/corda/notary/experimental/bftsmart/BFTSmart.kt @@ -230,7 +230,7 @@ object BFTSmart { type: StateConsumptionDetails.ConsumedStateType ) { states.forEach { stateRef -> - commitLog[stateRef]?.let { conflictingStates[stateRef] = StateConsumptionDetails(it.sha256(), type) } + commitLog[stateRef]?.let { conflictingStates[stateRef] = StateConsumptionDetails(it.reHash(), type) } } } @@ -277,7 +277,7 @@ object BFTSmart { } private fun handleConflicts(txId: SecureHash, conflictingStates: LinkedHashMap) { - if (isConsumedByTheSameTx(txId.sha256(), conflictingStates)) { + if (isConsumedByTheSameTx(txId.reHash(), conflictingStates)) { log.debug { "Transaction $txId already notarised" } return } else { diff --git a/node/src/main/kotlin/net/corda/notary/experimental/bftsmart/BFTSmartNotaryService.kt b/node/src/main/kotlin/net/corda/notary/experimental/bftsmart/BFTSmartNotaryService.kt index f663af3fd1..a570ccd7b5 100644 --- a/node/src/main/kotlin/net/corda/notary/experimental/bftsmart/BFTSmartNotaryService.kt +++ b/node/src/main/kotlin/net/corda/notary/experimental/bftsmart/BFTSmartNotaryService.kt @@ -132,7 +132,7 @@ class BFTSmartNotaryService( @Table(name = "${NODE_DATABASE_PREFIX}bft_committed_txs") class CommittedTransaction( @Id - @Column(name = "transaction_id", nullable = false, length = 64) + @Column(name = "transaction_id", nullable = false, length = 144) val transactionId: String ) @@ -150,8 +150,8 @@ class BFTSmartNotaryService( val txId = it.id.txId val index = it.id.index Pair( - StateRef(txhash = SecureHash.parse(txId), index = index), - SecureHash.parse(it.consumingTxHash) + StateRef(txhash = SecureHash.create(txId), index = index), + SecureHash.create(it.consumingTxHash) ) }, toPersistentEntity = { (txHash, index): StateRef, id: SecureHash -> diff --git a/node/src/main/kotlin/net/corda/notary/experimental/raft/RaftTransactionCommitLog.kt b/node/src/main/kotlin/net/corda/notary/experimental/raft/RaftTransactionCommitLog.kt index 5b08a9c6b8..af98cb2a51 100644 --- a/node/src/main/kotlin/net/corda/notary/experimental/raft/RaftTransactionCommitLog.kt +++ b/node/src/main/kotlin/net/corda/notary/experimental/raft/RaftTransactionCommitLog.kt @@ -14,7 +14,6 @@ import io.atomix.copycat.server.storage.snapshot.SnapshotWriter import net.corda.core.contracts.StateRef import net.corda.core.contracts.TimeWindow import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.sha256 import net.corda.core.flows.NotaryError import net.corda.core.flows.StateConsumptionDetails import net.corda.core.internal.VisibleForTesting @@ -79,7 +78,7 @@ class RaftTransactionCommitLog( val conflictingStates = LinkedHashMap() fun checkConflict(states: List, type: StateConsumptionDetails.ConsumedStateType) = states.forEach { stateRef -> - map[stateRef]?.let { conflictingStates[stateRef] = StateConsumptionDetails(it.second.sha256(), type) } + map[stateRef]?.let { conflictingStates[stateRef] = StateConsumptionDetails(it.second.reHash(), type) } } raftCommit.use { @@ -116,7 +115,7 @@ class RaftTransactionCommitLog( } private fun handleConflicts(txId: SecureHash, conflictingStates: java.util.LinkedHashMap): NotaryError? { - return if (isConsumedByTheSameTx(txId.sha256(), conflictingStates)) { + return if (isConsumedByTheSameTx(txId.reHash(), conflictingStates)) { log.debug { "Transaction $txId already notarised" } null } else { diff --git a/node/src/main/kotlin/net/corda/notary/experimental/raft/RaftUniquenessProvider.kt b/node/src/main/kotlin/net/corda/notary/experimental/raft/RaftUniquenessProvider.kt index 5969882b0c..b63394f426 100644 --- a/node/src/main/kotlin/net/corda/notary/experimental/raft/RaftUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/notary/experimental/raft/RaftUniquenessProvider.kt @@ -92,7 +92,13 @@ class RaftUniquenessProvider( ) fun StateRef.encoded() = "$txhash:$index" - fun String.parseStateRef() = split(":").let { StateRef(SecureHash.parse(it[0]), it[1].toInt()) } + fun String.parseStateRef(): StateRef { + val idx = lastIndexOf(':') + require(idx != -1) { + "Encoding error for StateRef '$this'" + } + return StateRef(SecureHash.create(substring(0, idx)), substring(idx + 1).toInt()) + } } @Entity @@ -115,7 +121,7 @@ class RaftUniquenessProvider( @Table(name = "${NODE_DATABASE_PREFIX}raft_committed_txs") class CommittedTransaction( @Id - @Column(name = "transaction_id", nullable = false, length = 64) + @Column(name = "transaction_id", nullable = false, length = 144) val transactionId: String ) diff --git a/node/src/main/kotlin/net/corda/notary/jpa/JPAUniquenessProvider.kt b/node/src/main/kotlin/net/corda/notary/jpa/JPAUniquenessProvider.kt index 53ea1749fd..d38a3f35b7 100644 --- a/node/src/main/kotlin/net/corda/notary/jpa/JPAUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/notary/jpa/JPAUniquenessProvider.kt @@ -5,7 +5,6 @@ import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.StateRef import net.corda.core.contracts.TimeWindow import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.sha256 import net.corda.core.flows.NotarisationRequestSignature import net.corda.core.flows.NotaryError import net.corda.core.flows.StateConsumptionDetails @@ -68,7 +67,7 @@ class JPAUniquenessProvider( @Column(nullable = true, length = 76) var id: String? = null, - @Column(name = "consuming_transaction_id", nullable = true, length = 64) + @Column(name = "consuming_transaction_id", nullable = true, length = 144) val consumingTxHash: String?, @Column(name = "requesting_party_name", nullable = true, length = 255) @@ -102,14 +101,14 @@ class JPAUniquenessProvider( class CommittedState( @EmbeddedId val id: PersistentStateRef, - @Column(name = "consuming_transaction_id", nullable = false, length = 64) + @Column(name = "consuming_transaction_id", nullable = false, length = 144) val consumingTxHash: String) @Entity @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}notary_committed_txs") class CommittedTransaction( @Id - @Column(name = "transaction_id", nullable = false, length = 64) + @Column(name = "transaction_id", nullable = false, length = 144) val transactionId: String ) @@ -145,7 +144,7 @@ class JPAUniquenessProvider( } fun decodeStateRef(s: PersistentStateRef): StateRef { - return StateRef(txhash = SecureHash.parse(s.txId), index = s.index) + return StateRef(txhash = SecureHash.create(s.txId), index = s.index) } } @@ -217,12 +216,12 @@ class JPAUniquenessProvider( } return committedStates.map { - val stateRef = StateRef(txhash = SecureHash.parse(it.id.txId), index = it.id.index) - val consumingTxId = SecureHash.parse(it.consumingTxHash) + val stateRef = StateRef(txhash = SecureHash.create(it.id.txId), index = it.id.index) + val consumingTxId = SecureHash.create(it.consumingTxHash) if (stateRef in references) { - stateRef to StateConsumptionDetails(consumingTxId.sha256(), type = StateConsumptionDetails.ConsumedStateType.REFERENCE_INPUT_STATE) + stateRef to StateConsumptionDetails(consumingTxId.reHash(), type = StateConsumptionDetails.ConsumedStateType.REFERENCE_INPUT_STATE) } else { - stateRef to StateConsumptionDetails(consumingTxId.sha256()) + stateRef to StateConsumptionDetails(consumingTxId.reHash()) } }.toMap() } @@ -286,7 +285,7 @@ class JPAUniquenessProvider( session: Session ): InternalResult { return when { - isConsumedByTheSameTx(request.txId.sha256(), stateConflicts) -> { + isConsumedByTheSameTx(request.txId.reHash(), stateConflicts) -> { InternalResult.Success } request.states.isEmpty() && isPreviouslyNotarised(session, request.txId) -> { @@ -326,7 +325,7 @@ class JPAUniquenessProvider( } else { // Mark states as consumed to capture conflicting transactions in the same batch request.states.forEach { - consumedStates[it] = StateConsumptionDetails(request.txId.sha256()) + consumedStates[it] = StateConsumptionDetails(request.txId.reHash()) } toCommit.add(request) InternalResult.Success diff --git a/node/src/main/resources/migration/node-core.changelog-master.xml b/node/src/main/resources/migration/node-core.changelog-master.xml index 28aa0cb95e..a7639218a1 100644 --- a/node/src/main/resources/migration/node-core.changelog-master.xml +++ b/node/src/main/resources/migration/node-core.changelog-master.xml @@ -31,6 +31,8 @@ + + diff --git a/node/src/main/resources/migration/node-core.changelog-v17.xml b/node/src/main/resources/migration/node-core.changelog-v17.xml new file mode 100644 index 0000000000..7773bc7f91 --- /dev/null +++ b/node/src/main/resources/migration/node-core.changelog-v17.xml @@ -0,0 +1,14 @@ + + + + + + + + \ No newline at end of file diff --git a/node/src/main/resources/migration/node-notary.changelog-master.xml b/node/src/main/resources/migration/node-notary.changelog-master.xml index 506cd551ad..6d154f21de 100644 --- a/node/src/main/resources/migration/node-notary.changelog-master.xml +++ b/node/src/main/resources/migration/node-notary.changelog-master.xml @@ -11,5 +11,6 @@ + diff --git a/node/src/main/resources/migration/node-notary.changelog-v100.xml b/node/src/main/resources/migration/node-notary.changelog-v100.xml new file mode 100644 index 0000000000..c2a15576ec --- /dev/null +++ b/node/src/main/resources/migration/node-notary.changelog-v100.xml @@ -0,0 +1,20 @@ + + + + + + + + + + \ No newline at end of file diff --git a/node/src/main/resources/migration/notary-bft-smart.changelog-master.xml b/node/src/main/resources/migration/notary-bft-smart.changelog-master.xml index 520ef606e4..306dc402f4 100644 --- a/node/src/main/resources/migration/notary-bft-smart.changelog-master.xml +++ b/node/src/main/resources/migration/notary-bft-smart.changelog-master.xml @@ -7,4 +7,5 @@ + diff --git a/node/src/main/resources/migration/notary-bft-smart.changelog-v2.xml b/node/src/main/resources/migration/notary-bft-smart.changelog-v2.xml new file mode 100644 index 0000000000..a63bc827ca --- /dev/null +++ b/node/src/main/resources/migration/notary-bft-smart.changelog-v2.xml @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/node/src/main/resources/migration/notary-raft.changelog-master.xml b/node/src/main/resources/migration/notary-raft.changelog-master.xml index 08141d79fd..3cb51c0e51 100644 --- a/node/src/main/resources/migration/notary-raft.changelog-master.xml +++ b/node/src/main/resources/migration/notary-raft.changelog-master.xml @@ -6,4 +6,5 @@ + diff --git a/node/src/main/resources/migration/notary-raft.changelog-v2.xml b/node/src/main/resources/migration/notary-raft.changelog-v2.xml new file mode 100644 index 0000000000..ddb0db5742 --- /dev/null +++ b/node/src/main/resources/migration/notary-raft.changelog-v2.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/node/src/main/resources/migration/vault-schema.changelog-master.xml b/node/src/main/resources/migration/vault-schema.changelog-master.xml index 3ba9d52575..d3616c5c0c 100644 --- a/node/src/main/resources/migration/vault-schema.changelog-master.xml +++ b/node/src/main/resources/migration/vault-schema.changelog-master.xml @@ -12,4 +12,5 @@ + diff --git a/node/src/main/resources/migration/vault-schema.changelog-v12.xml b/node/src/main/resources/migration/vault-schema.changelog-v12.xml new file mode 100644 index 0000000000..8dc736a149 --- /dev/null +++ b/node/src/main/resources/migration/vault-schema.changelog-v12.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt index 9517f22bda..08f17a2b32 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt @@ -2,7 +2,9 @@ package net.corda.node.services.events import com.nhaarman.mockito_kotlin.* import net.corda.core.contracts.* +import net.corda.core.crypto.DigestService import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.randomHash import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogicRef import net.corda.core.flows.FlowLogicRefFactory @@ -283,7 +285,7 @@ class NodeSchedulerPersistenceTest : NodeSchedulerServiceTestBase() { val database = configureDatabase(dataSourceProps, databaseConfig, { null }, { null }) database.transaction { val repo = PersistentScheduledFlowRepository(database) - val stateRef = StateRef(SecureHash.randomSHA256(), 0) + val stateRef = StateRef(DigestService.default.randomHash(), 0) val ssr = ScheduledStateRef(stateRef, mark) repo.merge(ssr) diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt index dfdc5530ad..1efb349ca0 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt @@ -953,7 +953,7 @@ class HibernateConfigurationTest { // DOCEND JdbcSession var count = 0 while (rs.next()) { - val stateRef = StateRef(SecureHash.parse(rs.getString(1)), rs.getInt(2)) + val stateRef = StateRef(SecureHash.create(rs.getString(1)), rs.getInt(2)) Assert.assertTrue(cashStates.map { it.ref }.contains(stateRef)) count++ } @@ -962,7 +962,7 @@ class HibernateConfigurationTest { } private fun toStateRef(pStateRef: PersistentStateRef): StateRef { - return StateRef(SecureHash.parse(pStateRef.txId), pStateRef.index) + return StateRef(SecureHash.create(pStateRef.txId), pStateRef.index) } @Test(timeout=300_000) diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt index 4c1e8d2bac..e419df5d01 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt @@ -8,7 +8,9 @@ import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.ContractAttachment import net.corda.core.crypto.Crypto +import net.corda.core.crypto.DigestService import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.randomHash import net.corda.core.crypto.sha256 import net.corda.core.flows.FlowLogic import net.corda.core.internal.* @@ -128,7 +130,7 @@ class NodeAttachmentServiceTest { val id = testJar.read { storage.importAttachment(it, "test", null) } assertEquals(expectedHash, id) - assertNull(storage.openAttachment(SecureHash.randomSHA256())) + assertNull(storage.openAttachment(DigestService.default.randomHash())) val stream = storage.openAttachment(expectedHash)!!.openAsJAR() val e1 = stream.nextJarEntry!! assertEquals("test1.txt", e1.name) diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/NonValidatingNotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/NonValidatingNotaryServiceTests.kt index b368a51bf2..1ae392fc65 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/NonValidatingNotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/NonValidatingNotaryServiceTests.kt @@ -213,8 +213,8 @@ class NonValidatingNotaryServiceTests { assertEquals(notaryError.txId, doubleSpendTx.id) with(notaryError) { assertEquals(consumedStates.size, 2) - assertEquals(consumedStates[firstState.ref]!!.hashOfTransactionId, firstSpendTx.id.sha256()) - assertEquals(consumedStates[secondState.ref]!!.hashOfTransactionId, secondSpendTx.id.sha256()) + assertEquals(consumedStates[firstState.ref]!!.hashOfTransactionId, firstSpendTx.id.reHash()) + assertEquals(consumedStates[secondState.ref]!!.hashOfTransactionId, secondSpendTx.id.reHash()) } } diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/UniquenessProviderTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/UniquenessProviderTests.kt index 29d347b427..b94023b305 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/UniquenessProviderTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/UniquenessProviderTests.kt @@ -3,17 +3,19 @@ package net.corda.node.services.transactions import com.codahale.metrics.MetricRegistry import net.corda.core.contracts.TimeWindow import net.corda.core.crypto.Crypto +import net.corda.core.crypto.DigestService import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.MerkleTree import net.corda.core.crypto.NullKeys import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SignableData import net.corda.core.crypto.SignatureMetadata -import net.corda.core.crypto.sha256 +import net.corda.core.crypto.randomHash import net.corda.core.flows.NotarisationRequestSignature import net.corda.core.flows.NotaryError import net.corda.core.flows.StateConsumptionDetails import net.corda.core.identity.CordaX500Name +import net.corda.core.internal.HashAgility import net.corda.core.internal.notary.UniquenessProvider import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.minutes @@ -51,14 +53,17 @@ import kotlin.test.assertEquals @RunWith(Parameterized::class) class UniquenessProviderTests( - private val uniquenessProviderFactory: UniquenessProviderFactory + private val uniquenessProviderFactory: UniquenessProviderFactory, + private val digestService: DigestService ) { companion object { @JvmStatic - @Parameterized.Parameters(name = "{0}") - fun data(): Collection = listOf( - JPAUniquenessProviderFactory(), - RaftUniquenessProviderFactory() + @Parameterized.Parameters + fun data(): Collection> = listOf( + arrayOf(JPAUniquenessProviderFactory(DigestService.sha2_256), DigestService.sha2_256), + arrayOf(RaftUniquenessProviderFactory(), DigestService.sha2_256) +// arrayOf(JPAUniquenessProviderFactory(DigestService.sha2_512), DigestService.sha2_512), +// arrayOf(RaftUniquenessProviderFactory(), DigestService.sha2_512) ) } @@ -66,7 +71,7 @@ class UniquenessProviderTests( @JvmField val testSerialization = SerializationEnvironmentRule(inheritable = true) private val identity = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).party - private val txID = SecureHash.randomSHA256() + private val txID = digestService.randomHash() private val requestSignature = NotarisationRequestSignature(DigitalSignature.WithKey(NullKeys.NullPublicKey, ByteArray(32)), 0) private lateinit var testClock: TestClock private lateinit var uniquenessProvider: UniquenessProvider @@ -76,10 +81,12 @@ class UniquenessProviderTests( testClock = TestClock(Clock.systemUTC()) uniquenessProvider = uniquenessProviderFactory.create(testClock) LogHelper.setLevel(uniquenessProvider::class) + HashAgility.init(txHashAlgoName = digestService.hashAlgorithm) } @After fun tearDown() { + HashAgility.init() uniquenessProviderFactory.cleanUp() LogHelper.reset(uniquenessProvider::class) } @@ -101,7 +108,7 @@ class UniquenessProviderTests( @Test(timeout=300_000) fun `rejects transaction before time window is valid`() { - val firstTxId = SecureHash.randomSHA256() + val firstTxId = digestService.randomHash() val timeWindow = TimeWindow.between( Clock.systemUTC().instant().plus(30.minutes), Clock.systemUTC().instant().plus(60.minutes)) @@ -126,7 +133,7 @@ class UniquenessProviderTests( @Test(timeout=300_000) fun `commits transaction within time window`() { - val firstTxId = SecureHash.randomSHA256() + val firstTxId = digestService.randomHash() val timeWindow = TimeWindow.untilOnly(Clock.systemUTC().instant().plus(30.minutes)) val result = uniquenessProvider.commit( emptyList(), firstTxId, identity, requestSignature, timeWindow).get() @@ -147,7 +154,7 @@ class UniquenessProviderTests( @Test(timeout=300_000) fun `rejects transaction after time window has expired`() { - val firstTxId = SecureHash.randomSHA256() + val firstTxId = digestService.randomHash() val timeWindow = TimeWindow.untilOnly(Clock.systemUTC().instant().minus(30.minutes)) val result = uniquenessProvider.commit( emptyList(), firstTxId, identity, requestSignature, timeWindow).get() @@ -166,8 +173,8 @@ class UniquenessProviderTests( @Test(timeout=300_000) fun `time window only transactions are processed correctly when duplicate requests occur in succession`() { - val firstTxId = SecureHash.randomSHA256() - val secondTxId = SecureHash.randomSHA256() + val firstTxId = digestService.randomHash() + val secondTxId = digestService.randomHash() val timeWindow = TimeWindow.untilOnly(Clock.systemUTC().instant().plus(30.minutes)) val invalidTimeWindow = TimeWindow.untilOnly(Clock.systemUTC().instant().minus(30.minutes)) @@ -191,7 +198,7 @@ class UniquenessProviderTests( @Test(timeout=300_000) fun `commits transaction with unused reference states`() { - val firstTxId = SecureHash.randomSHA256() + val firstTxId = digestService.randomHash() val referenceState = generateStateRef() val result = uniquenessProvider.commit(emptyList(), firstTxId, identity, requestSignature, references = listOf(referenceState)) @@ -206,7 +213,7 @@ class UniquenessProviderTests( @Test(timeout=300_000) fun `rejects transaction with previously used reference states`() { - val firstTxId = SecureHash.randomSHA256() + val firstTxId = digestService.randomHash() val referenceState = generateStateRef() val result = uniquenessProvider.commit(listOf(referenceState), firstTxId, identity, requestSignature, references = emptyList()) @@ -214,18 +221,18 @@ class UniquenessProviderTests( assert(result is UniquenessProvider.Result.Success) // Transaction referencing the spent sate fails. - val secondTxId = SecureHash.randomSHA256() + val secondTxId = digestService.randomHash() val result2 = uniquenessProvider.commit(emptyList(), secondTxId, identity, requestSignature, references = listOf(referenceState)) .get() val error = (result2 as UniquenessProvider.Result.Failure).error as NotaryError.Conflict val conflictCause = error.consumedStates[referenceState]!! - assertEquals(conflictCause.hashOfTransactionId, firstTxId.sha256()) + assertEquals(conflictCause.hashOfTransactionId, firstTxId.reHash(), "${conflictCause.hashOfTransactionId} != ${firstTxId.reHash()}") assertEquals(StateConsumptionDetails.ConsumedStateType.REFERENCE_INPUT_STATE, conflictCause.type) } @Test(timeout=300_000) fun `commits retry transaction when reference states were spent since initial transaction`() { - val firstTxId = SecureHash.randomSHA256() + val firstTxId = digestService.randomHash() val referenceState = generateStateRef() val result = uniquenessProvider.commit( @@ -234,7 +241,7 @@ class UniquenessProviderTests( assert(result is UniquenessProvider.Result.Success) // Spend reference state - val secondTxId = SecureHash.randomSHA256() + val secondTxId = digestService.randomHash() val result2 = uniquenessProvider.commit( listOf(referenceState), secondTxId, identity, requestSignature, references = emptyList()) .get() @@ -249,8 +256,8 @@ class UniquenessProviderTests( @Test(timeout=300_000) fun `reference state only transactions are processed correctly when duplicate requests occur in succession`() { - val firstTxId = SecureHash.randomSHA256() - val secondTxId = SecureHash.randomSHA256() + val firstTxId = digestService.randomHash() + val secondTxId = digestService.randomHash() val referenceState = generateStateRef() val validFuture3 = uniquenessProvider.commit( @@ -273,7 +280,7 @@ class UniquenessProviderTests( @Test(timeout=300_000) fun `commits transaction with unused reference states and valid time window`() { - val firstTxId = SecureHash.randomSHA256() + val firstTxId = digestService.randomHash() val referenceState = generateStateRef() val timeWindow = TimeWindow.untilOnly(Clock.systemUTC().instant().plus(30.minutes)) @@ -282,7 +289,7 @@ class UniquenessProviderTests( assert(result is UniquenessProvider.Result.Success) // The reference state gets consumed. - val result2 = uniquenessProvider.commit(listOf(referenceState), SecureHash.randomSHA256(), identity, requestSignature, timeWindow) + val result2 = uniquenessProvider.commit(listOf(referenceState), digestService.randomHash(), identity, requestSignature, timeWindow) .get() assert(result2 is UniquenessProvider.Result.Success) @@ -295,7 +302,7 @@ class UniquenessProviderTests( @Test(timeout=300_000) fun `rejects transaction with unused reference states and invalid time window`() { - val firstTxId = SecureHash.randomSHA256() + val firstTxId = digestService.randomHash() val referenceState = generateStateRef() val invalidTimeWindow = TimeWindow.untilOnly(Clock.systemUTC().instant().minus(30.minutes)) @@ -307,7 +314,7 @@ class UniquenessProviderTests( @Test(timeout=300_000) fun `rejects transaction with previously used reference states and valid time window`() { - val firstTxId = SecureHash.randomSHA256() + val firstTxId = digestService.randomHash() val referenceState = generateStateRef() val result = uniquenessProvider.commit(listOf(referenceState), firstTxId, identity, requestSignature, references = emptyList()) @@ -315,19 +322,19 @@ class UniquenessProviderTests( assert(result is UniquenessProvider.Result.Success) // Transaction referencing the spent sate fails. - val secondTxId = SecureHash.randomSHA256() + val secondTxId = digestService.randomHash() val timeWindow = TimeWindow.untilOnly(Clock.systemUTC().instant().plus(30.minutes)) val result2 = uniquenessProvider.commit(emptyList(), secondTxId, identity, requestSignature, timeWindow, references = listOf(referenceState)) .get() val error = (result2 as UniquenessProvider.Result.Failure).error as NotaryError.Conflict val conflictCause = error.consumedStates[referenceState]!! - assertEquals(conflictCause.hashOfTransactionId, firstTxId.sha256()) + assertEquals(conflictCause.hashOfTransactionId, firstTxId.reHash()) assertEquals(StateConsumptionDetails.ConsumedStateType.REFERENCE_INPUT_STATE, conflictCause.type) } @Test(timeout=300_000) fun `rejects transaction with previously used reference states and invalid time window`() { - val firstTxId = SecureHash.randomSHA256() + val firstTxId = digestService.randomHash() val referenceState = generateStateRef() val result = uniquenessProvider.commit(listOf(referenceState), firstTxId, identity, requestSignature, references = emptyList()) @@ -335,13 +342,13 @@ class UniquenessProviderTests( assert(result is UniquenessProvider.Result.Success) // Transaction referencing the spent sate fails. - val secondTxId = SecureHash.randomSHA256() + val secondTxId = digestService.randomHash() val invalidTimeWindow = TimeWindow.untilOnly(Clock.systemUTC().instant().minus(30.minutes)) val result2 = uniquenessProvider.commit(emptyList(), secondTxId, identity, requestSignature, invalidTimeWindow, references = listOf(referenceState)) .get() val error = (result2 as UniquenessProvider.Result.Failure).error as NotaryError.Conflict val conflictCause = error.consumedStates[referenceState]!! - assertEquals(conflictCause.hashOfTransactionId, firstTxId.sha256()) + assertEquals(conflictCause.hashOfTransactionId, firstTxId.reHash()) assertEquals(StateConsumptionDetails.ConsumedStateType.REFERENCE_INPUT_STATE, conflictCause.type) } @@ -368,19 +375,19 @@ class UniquenessProviderTests( val result = uniquenessProvider.commit(inputs, firstTxId, identity, requestSignature).get() assert(result is UniquenessProvider.Result.Success) - val secondTxId = SecureHash.randomSHA256() + val secondTxId = digestService.randomHash() val response: UniquenessProvider.Result = uniquenessProvider.commit(inputs, secondTxId, identity, requestSignature).get() val error = (response as UniquenessProvider.Result.Failure).error as NotaryError.Conflict val conflictCause = error.consumedStates[inputState]!! - assertEquals(firstTxId.sha256(), conflictCause.hashOfTransactionId) + assertEquals(firstTxId.reHash(), conflictCause.hashOfTransactionId) } @Test(timeout=300_000) fun `input state only transactions are processed correctly when duplicate requests occur in succession`() { - val firstTxId = SecureHash.randomSHA256() - val secondTxId = SecureHash.randomSHA256() + val firstTxId = digestService.randomHash() + val secondTxId = digestService.randomHash() val inputState = generateStateRef() val validFuture1 = uniquenessProvider.commit( @@ -433,7 +440,7 @@ class UniquenessProviderTests( val result = uniquenessProvider.commit(inputs, firstTxId, identity, requestSignature).get() assert(result is UniquenessProvider.Result.Success) - val secondTxId = SecureHash.randomSHA256() + val secondTxId = digestService.randomHash() val timeWindow = TimeWindow.untilOnly(Clock.systemUTC().instant().plus(30.minutes)) val response: UniquenessProvider.Result = uniquenessProvider.commit(inputs, secondTxId, identity, requestSignature, timeWindow) @@ -441,7 +448,7 @@ class UniquenessProviderTests( val error = (response as UniquenessProvider.Result.Failure).error as NotaryError.Conflict val conflictCause = error.consumedStates[inputState]!! - assertEquals(firstTxId.sha256(), conflictCause.hashOfTransactionId) + assertEquals(firstTxId.reHash(), conflictCause.hashOfTransactionId) } @Test(timeout=300_000) @@ -452,7 +459,7 @@ class UniquenessProviderTests( val result = uniquenessProvider.commit(inputs, firstTxId, identity, requestSignature).get() assert(result is UniquenessProvider.Result.Success) - val secondTxId = SecureHash.randomSHA256() + val secondTxId = digestService.randomHash() val invalidTimeWindow = TimeWindow.untilOnly(Clock.systemUTC().instant().minus(30.minutes)) val response: UniquenessProvider.Result = uniquenessProvider.commit(inputs, secondTxId, identity, requestSignature, invalidTimeWindow) @@ -460,14 +467,14 @@ class UniquenessProviderTests( val error = (response as UniquenessProvider.Result.Failure).error as NotaryError.Conflict val conflictCause = error.consumedStates[inputState]!! - assertEquals(firstTxId.sha256(), conflictCause.hashOfTransactionId) + assertEquals(firstTxId.reHash(), conflictCause.hashOfTransactionId) } /* Group F: input & reference states */ @Test(timeout=300_000) fun `commits transaction with unused input & reference states`() { - val firstTxId = SecureHash.randomSHA256() + val firstTxId = digestService.randomHash() val inputState = generateStateRef() val referenceState = generateStateRef() val timeWindow = TimeWindow.untilOnly(Clock.systemUTC().instant().plus(30.minutes)) @@ -485,7 +492,7 @@ class UniquenessProviderTests( @Test(timeout=300_000) fun `re-notarise after reference state is spent`() { - val firstTxId = SecureHash.randomSHA256() + val firstTxId = digestService.randomHash() val inputState = generateStateRef() val referenceState = generateStateRef() val timeWindow = TimeWindow.untilOnly(Clock.systemUTC().instant().plus(30.minutes)) @@ -498,7 +505,7 @@ class UniquenessProviderTests( // Spend the reference state. val referenceSpend = uniquenessProvider.commit( listOf(referenceState), - SecureHash.randomSHA256(), + digestService.randomHash(), identity, requestSignature, timeWindow, @@ -516,7 +523,7 @@ class UniquenessProviderTests( @Test(timeout=300_000) fun `rejects transaction with unused reference states and used input states`() { - val firstTxId = SecureHash.randomSHA256() + val firstTxId = digestService.randomHash() val inputState = generateStateRef() val referenceState = generateStateRef() @@ -524,19 +531,19 @@ class UniquenessProviderTests( assert(result is UniquenessProvider.Result.Success) // Transaction referencing the spent sate fails. - val secondTxId = SecureHash.randomSHA256() + val secondTxId = digestService.randomHash() val timeWindow = TimeWindow.untilOnly(Clock.systemUTC().instant().plus(30.minutes)) val result2 = uniquenessProvider.commit(listOf(inputState), secondTxId, identity, requestSignature, timeWindow, references = listOf(referenceState)) .get() val error = (result2 as UniquenessProvider.Result.Failure).error as NotaryError.Conflict val conflictCause = error.consumedStates[inputState]!! - assertEquals(conflictCause.hashOfTransactionId, firstTxId.sha256()) + assertEquals(conflictCause.hashOfTransactionId, firstTxId.reHash()) assertEquals(StateConsumptionDetails.ConsumedStateType.INPUT_STATE, conflictCause.type) } @Test(timeout=300_000) fun `rejects transaction with used reference states and unused input states`() { - val firstTxId = SecureHash.randomSHA256() + val firstTxId = digestService.randomHash() val inputState = generateStateRef() val referenceState = generateStateRef() @@ -545,20 +552,20 @@ class UniquenessProviderTests( assert(result is UniquenessProvider.Result.Success) // Transaction referencing the spent sate fails. - val secondTxId = SecureHash.randomSHA256() + val secondTxId = digestService.randomHash() val timeWindow = TimeWindow.untilOnly(Clock.systemUTC().instant().plus(30.minutes)) val result2 = uniquenessProvider.commit(listOf(inputState), secondTxId, identity, requestSignature, timeWindow, references = listOf(referenceState)) .get() val error = (result2 as UniquenessProvider.Result.Failure).error as NotaryError.Conflict val conflictCause = error.consumedStates[referenceState]!! - assertEquals(conflictCause.hashOfTransactionId, firstTxId.sha256()) + assertEquals(conflictCause.hashOfTransactionId, firstTxId.reHash()) assertEquals(StateConsumptionDetails.ConsumedStateType.REFERENCE_INPUT_STATE, conflictCause.type) } @Test(timeout=300_000) fun `input and reference state transactions are processed correctly when duplicate requests occur in succession`() { - val firstTxId = SecureHash.randomSHA256() - val secondTxId = SecureHash.randomSHA256() + val firstTxId = digestService.randomHash() + val secondTxId = digestService.randomHash() val referenceState = generateStateRef() // Ensure batch contains duplicates @@ -571,7 +578,7 @@ class UniquenessProviderTests( // Attempt to use the reference state after it has been consumed val validFuture4 = uniquenessProvider.commit( - emptyList(), SecureHash.randomSHA256(), identity, requestSignature, references = listOf(referenceState)) + emptyList(), digestService.randomHash(), identity, requestSignature, references = listOf(referenceState)) // Ensure that transactions are processed correctly and duplicates get the same responses to original assert(validFuture1.get() is UniquenessProvider.Result.Success) @@ -587,7 +594,7 @@ class UniquenessProviderTests( fun `signs transactions correctly`() { (1..10).map { val inputState1 = generateStateRef() - val firstTxId = SecureHash.randomSHA256() + val firstTxId = digestService.randomHash() val timeWindow = TimeWindow.untilOnly(Clock.systemUTC().instant().plus(30.minutes)) Pair(firstTxId, uniquenessProvider.commit(listOf(inputState1), firstTxId, identity, requestSignature, timeWindow)) }.forEach { @@ -636,15 +643,8 @@ class RaftUniquenessProviderFactory : UniquenessProviderFactory { } } -fun signBatch(it: Iterable): BatchSignature { - val root = MerkleTree.getMerkleTree(it.map { it.sha256() }) - val signableMetadata = SignatureMetadata(4, Crypto.findSignatureScheme(pubKey).schemeNumberID) - val signature = keyService.sign(SignableData(root.hash, signableMetadata), pubKey) - return BatchSignature(signature, root) -} - -class JPAUniquenessProviderFactory : UniquenessProviderFactory { +class JPAUniquenessProviderFactory(val digestService: DigestService) : UniquenessProviderFactory { private var database: CordaPersistence? = null private val notaryConfig = JPANotaryConfiguration(maxInputStates = 10) private val notaryWorkerName = CordaX500Name.parse("CN=NotaryWorker, O=Corda, L=London, C=GB") @@ -664,6 +664,14 @@ class JPAUniquenessProviderFactory : UniquenessProviderFactory { override fun cleanUp() { database?.close() } + + fun signBatch(it: Iterable): BatchSignature { + val root = MerkleTree.getMerkleTree(it.map { it.reHash() }, digestService) + + val signableMetadata = SignatureMetadata(4, Crypto.findSignatureScheme(pubKey).schemeNumberID) + val signature = keyService.sign(SignableData(root.hash, signableMetadata), pubKey) + return BatchSignature(signature, root) + } } var ourKeyPair: KeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt index cb971a43e2..2e8f382cbe 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt @@ -274,8 +274,8 @@ class ValidatingNotaryServiceTests { assertEquals(notaryError.txId, doubleSpendTx.id) with(notaryError) { assertEquals(consumedStates.size, 2) - assertEquals(consumedStates[firstState.ref]!!.hashOfTransactionId, firstSpendTx.id.sha256()) - assertEquals(consumedStates[secondState.ref]!!.hashOfTransactionId, secondSpendTx.id.sha256()) + assertEquals(consumedStates[firstState.ref]!!.hashOfTransactionId, firstSpendTx.id.reHash()) + assertEquals(consumedStates[secondState.ref]!!.hashOfTransactionId, secondSpendTx.id.reHash()) } } diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt index e07a26b1b2..09964b6602 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt @@ -647,7 +647,7 @@ abstract class VaultQueryTestsBase : VaultQueryParties { database.transaction { vaultFiller.fillWithSomeTestLinearStates(8) val issuedStates = vaultFiller.fillWithSomeTestLinearStates(2) - val stateRefs = issuedStates.states.map { it.ref }.toList() + val stateRefs = issuedStates.states.map { it.ref } // DOCSTART VaultQueryExample2 val sortAttribute = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_TXN_ID) @@ -657,7 +657,7 @@ abstract class VaultQueryTestsBase : VaultQueryParties { assertThat(results.states).hasSize(2) - val sortedStateRefs = stateRefs.sortedBy { it.txhash.bytes.toHexString() } + val sortedStateRefs = stateRefs.sortedBy { it.txhash.toString() } assertThat(results.states.first().ref).isEqualTo(sortedStateRefs.first()) assertThat(results.states.last().ref).isEqualTo(sortedStateRefs.last()) } diff --git a/node/src/test/kotlin/net/corda/notary/experimental/bftsmart/BFTNotaryServiceTests.kt b/node/src/test/kotlin/net/corda/notary/experimental/bftsmart/BFTNotaryServiceTests.kt index a80575c5ee..ce18b11dd2 100644 --- a/node/src/test/kotlin/net/corda/notary/experimental/bftsmart/BFTNotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/notary/experimental/bftsmart/BFTNotaryServiceTests.kt @@ -141,7 +141,7 @@ class BFTNotaryServiceTests { assertEquals(tx.id, error.txId) val (stateRef, cause) = error.consumedStates.entries.single() assertEquals(StateRef(issueTx.id, 0), stateRef) - assertEquals(spendTxs[successfulIndex].id.sha256(), cause.hashOfTransactionId) + assertEquals(spendTxs[successfulIndex].id.reHash(), cause.hashOfTransactionId) } } } diff --git a/node/src/test/kotlin/net/corda/notary/experimental/raft/RaftTransactionCommitLogTests.kt b/node/src/test/kotlin/net/corda/notary/experimental/raft/RaftTransactionCommitLogTests.kt index a03f72308e..8dc0f61056 100644 --- a/node/src/test/kotlin/net/corda/notary/experimental/raft/RaftTransactionCommitLogTests.kt +++ b/node/src/test/kotlin/net/corda/notary/experimental/raft/RaftTransactionCommitLogTests.kt @@ -8,7 +8,9 @@ import io.atomix.copycat.server.storage.Storage import io.atomix.copycat.server.storage.StorageLevel import net.corda.core.contracts.StateRef import net.corda.core.contracts.TimeWindow +import net.corda.core.crypto.DigestService import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.randomHash import net.corda.core.flows.NotaryError import net.corda.core.internal.concurrent.asCordaFuture import net.corda.core.internal.concurrent.transpose @@ -68,7 +70,7 @@ class RaftTransactionCommitLogTests { val client = cluster.last().client val states = listOf(StateRef(SecureHash.randomSHA256(), 0), StateRef(SecureHash.randomSHA256(), 0)) - val txId: SecureHash = SecureHash.randomSHA256() + val txId: SecureHash = DigestService.default.randomHash() val requestingPartyName = ALICE_NAME val requestSignature = ByteArray(1024) @@ -88,8 +90,8 @@ class RaftTransactionCommitLogTests { val client = cluster.last().client val states = listOf(StateRef(SecureHash.randomSHA256(), 0), StateRef(SecureHash.randomSHA256(), 0)) - val txIdFirst = SecureHash.randomSHA256() - val txIdSecond = SecureHash.randomSHA256() + val txIdFirst = DigestService.default.randomHash() + val txIdSecond = DigestService.default.randomHash() val requestingPartyName = ALICE_NAME val requestSignature = ByteArray(1024) @@ -108,7 +110,7 @@ class RaftTransactionCommitLogTests { val client = cluster.last().client val states = listOf(StateRef(SecureHash.randomSHA256(), 0), StateRef(SecureHash.randomSHA256(), 0)) - val txId: SecureHash = SecureHash.randomSHA256() + val txId: SecureHash = DigestService.default.randomHash() val requestingPartyName = ALICE_NAME val requestSignature = ByteArray(1024) val timeWindow = TimeWindow.fromOnly(Instant.MAX) @@ -125,7 +127,7 @@ class RaftTransactionCommitLogTests { val client = cluster.last().client val states = listOf(StateRef(SecureHash.randomSHA256(), 0), StateRef(SecureHash.randomSHA256(), 0)) - val txId: SecureHash = SecureHash.randomSHA256() + val txId: SecureHash = DigestService.default.randomHash() val requestingPartyName = ALICE_NAME val requestSignature = ByteArray(1024) val timeWindow = TimeWindow.fromOnly(Instant.MIN) diff --git a/samples/simm-valuation-demo/contracts-states/src/main/kotlin/net/corda/vega/contracts/CordappDependencies.kt b/samples/simm-valuation-demo/contracts-states/src/main/kotlin/net/corda/vega/contracts/CordappDependencies.kt index 9b2d01da36..0ecd17d988 100644 --- a/samples/simm-valuation-demo/contracts-states/src/main/kotlin/net/corda/vega/contracts/CordappDependencies.kt +++ b/samples/simm-valuation-demo/contracts-states/src/main/kotlin/net/corda/vega/contracts/CordappDependencies.kt @@ -21,7 +21,7 @@ private fun loadDependencies(): List { return URLClassLoader(arrayOf(cordappURL), null).use { cl -> val deps = cl.getResource("META-INF/Cordapp-Dependencies") ?: return emptyList() deps.openStream().bufferedReader().useLines { lines -> - lines.filterNot(String::isBlank).map(SecureHash.Companion::parse).toList() + lines.filterNot(String::isBlank).map(SecureHash.Companion::create).toList() } } } diff --git a/samples/trader-demo/workflows-trader/src/main/kotlin/net/corda/traderdemo/flow/CommercialPaperIssueFlow.kt b/samples/trader-demo/workflows-trader/src/main/kotlin/net/corda/traderdemo/flow/CommercialPaperIssueFlow.kt index fa46802a38..80d34c65a1 100644 --- a/samples/trader-demo/workflows-trader/src/main/kotlin/net/corda/traderdemo/flow/CommercialPaperIssueFlow.kt +++ b/samples/trader-demo/workflows-trader/src/main/kotlin/net/corda/traderdemo/flow/CommercialPaperIssueFlow.kt @@ -29,7 +29,7 @@ class CommercialPaperIssueFlow(private val amount: Amount, constructor(amount: Amount, issueRef: OpaqueBytes, recipient: Party, notary: Party) : this(amount, issueRef, recipient, notary, tracker()) companion object { - val PROSPECTUS_HASH = SecureHash.parse("decd098666b9657314870e192ced0c3519c2c9d395507a238338f8d003929de9") + val PROSPECTUS_HASH = SecureHash.create("decd098666b9657314870e192ced0c3519c2c9d395507a238338f8d003929de9") object ISSUING : ProgressTracker.Step("Issuing and timestamping some commercial paper") diff --git a/samples/trader-demo/workflows-trader/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt b/samples/trader-demo/workflows-trader/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt index 3e91639000..b09795b963 100644 --- a/samples/trader-demo/workflows-trader/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt +++ b/samples/trader-demo/workflows-trader/src/main/kotlin/net/corda/traderdemo/flow/SellerFlow.kt @@ -21,7 +21,7 @@ class SellerFlow(private val otherParty: Party, constructor(otherParty: Party, amount: Amount) : this(otherParty, amount, tracker()) companion object { - val PROSPECTUS_HASH = SecureHash.parse("decd098666b9657314870e192ced0c3519c2c9d395507a238338f8d003929de9") + val PROSPECTUS_HASH = SecureHash.create("decd098666b9657314870e192ced0c3519c2c9d395507a238338f8d003929de9") object SELF_ISSUING : ProgressTracker.Step("Got session ID back, issuing and timestamping some commercial paper") diff --git a/testing/cordapps/dbfailure/dbfcontracts/src/main/resources/migration/dbfailure.changelog-master.xml b/testing/cordapps/dbfailure/dbfcontracts/src/main/resources/migration/dbfailure.changelog-master.xml index 22b9ffda33..a1b6ce0377 100644 --- a/testing/cordapps/dbfailure/dbfcontracts/src/main/resources/migration/dbfailure.changelog-master.xml +++ b/testing/cordapps/dbfailure/dbfcontracts/src/main/resources/migration/dbfailure.changelog-master.xml @@ -5,4 +5,5 @@ + diff --git a/testing/cordapps/dbfailure/dbfcontracts/src/main/resources/migration/dbfailure.changelog-v2.xml b/testing/cordapps/dbfailure/dbfcontracts/src/main/resources/migration/dbfailure.changelog-v2.xml new file mode 100644 index 0000000000..83b8f2e294 --- /dev/null +++ b/testing/cordapps/dbfailure/dbfcontracts/src/main/resources/migration/dbfailure.changelog-v2.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index b9837f7d79..899d36cdc3 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -12,6 +12,7 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.PLATFORM_VERSION +import net.corda.core.internal.requireSupportedHashType import net.corda.core.messaging.DataFeed import net.corda.core.messaging.FlowHandle import net.corda.core.messaging.FlowProgressHandle @@ -236,6 +237,7 @@ open class MockServices private constructor( override var networkParametersService: NetworkParametersService = MockNetworkParametersStorage(networkParameters) override val vaultService: VaultService = makeVaultService(schemaService, persistence, cordappLoader) override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { + txs.forEach { requireSupportedHashType(it) } ServiceHubInternal.recordTransactions( statesToRecord, txs as? Collection ?: txs.toList(), diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt index 6e3f5797a2..fe24c77248 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/network/NetworkMapServer.kt @@ -201,7 +201,7 @@ class NetworkMapServer(private val pollInterval: Duration, @Path("node-info/{var}") @Produces(MediaType.APPLICATION_OCTET_STREAM) fun getNodeInfo(@PathParam("var") nodeInfoHash: String): Response { - val hash = SecureHash.parse(nodeInfoHash) + val hash = SecureHash.create(nodeInfoHash) val signedNodeInfo = nodeInfoMap[hash] return if (signedNodeInfo != null) { Response.ok(signedNodeInfo.serialize().bytes) @@ -223,7 +223,7 @@ class NetworkMapServer(private val pollInterval: Duration, @Path("network-parameters/{var}") @Produces(MediaType.APPLICATION_OCTET_STREAM) fun getNetworkParameter(@PathParam("var") hash: String): Response { - val requestedHash = SecureHash.parse(hash) + val requestedHash = SecureHash.create(hash) val requestedParameters = if (requestedHash == signedNetParams.raw.hash) { signedNetParams } else if (requestedHash == nextNetworkParameters?.serialize()?.hash) { diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt index c45be72902..1dbf7249cb 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/InternalTestUtils.kt @@ -8,6 +8,7 @@ import net.corda.core.contracts.TimeWindow import net.corda.core.contracts.TransactionState import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto.generateKeyPair +import net.corda.core.crypto.DigestService import net.corda.core.crypto.SecureHash import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name @@ -147,15 +148,17 @@ fun p2pSslOptions(path: Path, name: CordaX500Name = CordaX500Name("MegaCorp", "L } /** This is the same as the deprecated [WireTransaction] c'tor but avoids the deprecation warning. */ +@SuppressWarnings("LongParameterList") fun createWireTransaction(inputs: List, attachments: List, outputs: List>, commands: List>, notary: Party?, timeWindow: TimeWindow?, - privacySalt: PrivacySalt = PrivacySalt()): WireTransaction { + privacySalt: PrivacySalt = PrivacySalt(), + digestService: DigestService = DigestService.default): WireTransaction { val componentGroups = createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList(), null) - return WireTransaction(componentGroups, privacySalt) + return WireTransaction(componentGroups, privacySalt, digestService) } /** diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/services/InternalMockAttachmentStorage.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/services/InternalMockAttachmentStorage.kt index 1eb8eb108f..26471237ec 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/services/InternalMockAttachmentStorage.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/services/InternalMockAttachmentStorage.kt @@ -30,7 +30,7 @@ class InternalMockAttachmentStorage(storage: MockAttachmentStorage) : Attachment return try { importAttachment(jar, uploader, filename) } catch (faee: java.nio.file.FileAlreadyExistsException) { - AttachmentId.parse(faee.message!!) + AttachmentId.create(faee.message!!) } } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/services/MockAttachmentStorage.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/services/MockAttachmentStorage.kt index f830421a3f..c220bcfb1b 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/services/MockAttachmentStorage.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/services/MockAttachmentStorage.kt @@ -83,7 +83,7 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() { return try { importAttachment(jar, UNKNOWN_UPLOADER, null) } catch (e: java.nio.file.FileAlreadyExistsException) { - AttachmentId.parse(e.message!!) + AttachmentId.create(e.message!!) } } diff --git a/testing/testserver/src/main/kotlin/net/corda/webserver/servlets/AttachmentDownloadServlet.kt b/testing/testserver/src/main/kotlin/net/corda/webserver/servlets/AttachmentDownloadServlet.kt index e76d6633d2..9bfc69b641 100644 --- a/testing/testserver/src/main/kotlin/net/corda/webserver/servlets/AttachmentDownloadServlet.kt +++ b/testing/testserver/src/main/kotlin/net/corda/webserver/servlets/AttachmentDownloadServlet.kt @@ -38,7 +38,7 @@ class AttachmentDownloadServlet : HttpServlet() { } try { - val hash = SecureHash.parse(reqPath.substringBefore('/')) + val hash = SecureHash.create(reqPath.substringBefore('/')) val rpc = servletContext.getAttribute("rpc") as CordaRPCOps val attachment = rpc.openAttachment(hash) diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/views/TransactionViewer.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/views/TransactionViewer.kt index 3ba8b4897d..da9cf4c585 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/views/TransactionViewer.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/views/TransactionViewer.kt @@ -235,7 +235,7 @@ class TransactionViewer : CordaView("Transactions") { } override fun computeValue(): SecureHash { return if (hashList.isEmpty()) SecureHash.zeroHash - else hashList.fold(hashList[0], { one, another -> one.hashConcat(another) }) + else hashList.fold(hashList[0], { one, another -> one.concatenate(another) }) } } graphicProperty().bind(hashBinding.map { identicon(it, 30.0) }) 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 bcd10b09cb..74e3513104 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 @@ -51,16 +51,16 @@ public class HashLookupShellCommand extends CordaRpcOpsShellCommand { SecureHash txIdHashParsed; try { - txIdHashParsed = SecureHash.parse(txIdHash); + txIdHashParsed = SecureHash.create(txIdHash); } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("The provided string is not a valid hexadecimal SHA-256 hash value"); + throw new IllegalArgumentException("The provided string is neither a valid SHA-256 hash value or a supported hash algorithm"); } List mapping = proxy.stateMachineRecordedTransactionMappingSnapshot(); Optional match = mapping.stream() .map(StateMachineTransactionMapping::getTransactionId) .filter( - txId -> txId.equals(txIdHashParsed) || SecureHash.sha256(txId.getBytes()).equals(txIdHashParsed) + txId -> txId.equals(txIdHashParsed) || SecureHash.hashAs(txIdHashParsed.getAlgorithm(), txId.getBytes()).equals(txIdHashParsed) ) .findFirst();