From e34930f9a8fba962bfb6d43cb7d07dc1e5888ada Mon Sep 17 00:00:00 2001 From: Ramzi El-Yafi Date: Wed, 20 May 2020 22:07:04 +0100 Subject: [PATCH 1/5] [INFRA-351] Jenkinsfile for code check jobs (#6272) --- .ci/dev/pr-code-checks/Jenkinsfile | 69 ++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 .ci/dev/pr-code-checks/Jenkinsfile diff --git a/.ci/dev/pr-code-checks/Jenkinsfile b/.ci/dev/pr-code-checks/Jenkinsfile new file mode 100644 index 0000000000..0fdd5b0055 --- /dev/null +++ b/.ci/dev/pr-code-checks/Jenkinsfile @@ -0,0 +1,69 @@ +@Library('corda-shared-build-pipeline-steps') +import static com.r3.build.BuildControl.killAllExistingBuildsForJob + +killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) + +pipeline { + agent { label 'k8s' } + options { + timestamps() + timeout(time: 3, unit: 'HOURS') + } + + environment { + PR_CONTEXT_STRING = "PR Code Checks" + } + + stages { + stage('Detekt check') { + steps { + script { + pullRequest.createStatus( + status: 'pending', + context: "${PR_CONTEXT_STRING}", + description: "Running code checks", + targetUrl: "${env.BUILD_URL}") + } + sh "./gradlew --no-daemon clean detekt" + } + } + + stage('Compilation warnings check') { + steps { + sh "./gradlew --no-daemon -Pcompilation.warningsAsErrors=true compileAll" + } + } + + stage('No API change check') { + steps { + sh "./gradlew --no-daemon generateApi" + sh ".ci/check-api-changes.sh" + } + } + } + + post { + success { + script { + pullRequest.createStatus( + status: 'success', + context: "${PR_CONTEXT_STRING}", + description: 'Code checks passed', + targetUrl: "${env.BUILD_URL}") + } + } + + failure { + script { + pullRequest.createStatus( + status: 'failure', + context: "${PR_CONTEXT_STRING}", + description: 'Code checks failed', + targetUrl: "${env.BUILD_URL}") + } + } + cleanup { + deleteDir() /* clean up our workspace */ + } + } +} From 326afacedbbeaf0f24a9507b08836e36c884dacc Mon Sep 17 00:00:00 2001 From: Walter Oggioni <6357328+woggioni@users.noreply.github.com> Date: Thu, 21 May 2020 09:27:13 +0100 Subject: [PATCH 2/5] improved interactive shell flow lookup logic (#6271) the current logic when a user type ``` flow start SomeFlow ``` is to search for the string `SomeFlow` in all registered flow names, if there is at exactly flow that ends with that string, it will be selected, if there is more than one, the first in aphabetical order will be selected. If there are multiple flows that contains the string but no one that ends with it, it is considered ambiguous, if there is exactly one containing the string it is instead selected (it doesn't matter whether it ends or not with it). All other cases are considered errors. The goal of this change is to address the case where the node contains ``` net.corda.cordappp.AnotherSomeFlow net.corda.cordappp.ImprovedSomeFlow net.corda.cordappp.SomeFlow ``` typing ``` flow start SomeFlow ``` currently results in running `net.corda.cordappp.AnotherSomeFlow` because it comes first in alphabetical order, while `net.corda.cordappp.SomeFlow` is a better matches because the input substring matches a bigger portion of the full flow name --- .../src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt b/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt index ee26e6016b..bc00b7b53a 100644 --- a/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt +++ b/tools/shell/src/main/kotlin/net/corda/tools/shell/InteractiveShell.kt @@ -336,7 +336,7 @@ object InteractiveShell { ansiProgressRenderer: ANSIProgressRenderer, inputObjectMapper: ObjectMapper = createYamlInputMapper(rpcOps)) { val matches = try { - rpcOps.registeredFlows().filter { nameFragment in it } + rpcOps.registeredFlows().filter { nameFragment in it }.sortedBy { it.length } } catch (e: PermissionException) { output.println(e.message ?: "Access denied", Decoration.bold, Color.red) return From 57b1a5e0fc4af55dea02d4066c74cd3f01b34480 Mon Sep 17 00:00:00 2001 From: Kyriakos Tharrouniatis Date: Fri, 22 May 2020 10:15:51 +0100 Subject: [PATCH 3/5] ENT-5339 Failing tests against Oracle in VaultObserverExceptionTest (#6275) * Fix erroneous sql statement for oracle; It was failing tests with 'ORA-00933: SQL command not properly ended' * Fixed flaky test; it didn't wait for counter party flow to get hospitalized as the test implied --- .../vault/VaultObserverExceptionTest.kt | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/node/src/integration-test/kotlin/net/corda/node/services/vault/VaultObserverExceptionTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/vault/VaultObserverExceptionTest.kt index 5cd4529c6d..86bb3b2931 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/vault/VaultObserverExceptionTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/vault/VaultObserverExceptionTest.kt @@ -24,6 +24,7 @@ import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.seconds import net.corda.node.services.Permissions import net.corda.node.services.statemachine.StaffedFlowHospital +import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.BOB_NAME import net.corda.testing.core.singleIdentity @@ -598,7 +599,12 @@ class VaultObserverExceptionTest { @Test(timeout=300_000) fun `Throw user error in VaultService rawUpdates during counterparty FinalityFlow blows up the flow but does not break the Observer`() { var observationCounter = 0 - StaffedFlowHospital.onFlowKeptForOvernightObservation.add { _, _ -> ++observationCounter } + // Semaphore is used to wait until [PassErroneousOwnableStateReceiver] gets hospitalized, only after that moment let testing thread assert 'observationCounter' + val counterPartyHospitalized = Semaphore(0) + StaffedFlowHospital.onFlowKeptForOvernightObservation.add { _, _ -> + ++observationCounter + counterPartyHospitalized.release() + } val rawUpdatesCount = ConcurrentHashMap() DbListenerService.onNextVisited = { party -> @@ -644,6 +650,7 @@ class VaultObserverExceptionTest { assertEquals(1, aliceNode.getStatesById(stateId, Vault.StateStatus.CONSUMED).size) assertEquals(0, bobNode.getStatesById(stateId, Vault.StateStatus.UNCONSUMED).size) assertEquals(1, notary.getNotarisedTransactionIds().size) + counterPartyHospitalized.acquire() assertEquals(1, observationCounter) assertEquals(2, rawUpdatesCount[aliceNode.nodeInfo.singleIdentity()]) assertEquals(1, rawUpdatesCount[bobNode.nodeInfo.singleIdentity()]) @@ -653,6 +660,7 @@ class VaultObserverExceptionTest { assertEquals(2, aliceNode.getAllStates(Vault.StateStatus.CONSUMED).size) assertEquals(0, bobNode.getStatesById(stateId2, Vault.StateStatus.UNCONSUMED).size) assertEquals(2, notary.getNotarisedTransactionIds().size) + counterPartyHospitalized.acquire() assertEquals(2, observationCounter) assertEquals(4, rawUpdatesCount[aliceNode.nodeInfo.singleIdentity()]) assertEquals(2, rawUpdatesCount[bobNode.nodeInfo.singleIdentity()]) @@ -833,14 +841,13 @@ class VaultObserverExceptionTest { @StartableByRPC class NotarisedTxs : FlowLogic>() { override fun call(): List { - val session = serviceHub.jdbcSession() - val statement = session.createStatement() - statement.execute("SELECT TRANSACTION_ID FROM NODE_NOTARY_COMMITTED_TXS;") - val result = mutableListOf() - while (statement.resultSet.next()) { - result.add(statement.resultSet.getString(1)) + return serviceHub.withEntityManager { + val criteriaQuery = this.criteriaBuilder.createQuery(String::class.java) + val root = criteriaQuery.from(PersistentUniquenessProvider.CommittedTransaction::class.java) + criteriaQuery.select(root.get(PersistentUniquenessProvider.CommittedTransaction::transactionId.name)) + val query = this.createQuery(criteriaQuery) + query.resultList } - return result } } From 4ed57506c823d5b23658a6a53fb192782e8015a7 Mon Sep 17 00:00:00 2001 From: Ramzi El-Yafi Date: Tue, 26 May 2020 15:26:55 +0100 Subject: [PATCH 4/5] [INFRA-352] Artifactory publication in Jenkins (#6276) * [INFRA-352] Artifactory publication in Jenkins * Address review comments --- .ci/dev/publish-branch/Jenkinsfile.nightly | 69 ++++++++++++++++++++++ .ci/dev/publish-branch/Jenkinsfile.preview | 65 ++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 .ci/dev/publish-branch/Jenkinsfile.nightly create mode 100644 .ci/dev/publish-branch/Jenkinsfile.preview diff --git a/.ci/dev/publish-branch/Jenkinsfile.nightly b/.ci/dev/publish-branch/Jenkinsfile.nightly new file mode 100644 index 0000000000..460117e500 --- /dev/null +++ b/.ci/dev/publish-branch/Jenkinsfile.nightly @@ -0,0 +1,69 @@ +#!groovy +@Library('corda-shared-build-pipeline-steps') +import static com.r3.build.BuildControl.killAllExistingBuildsForJob + +killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) + +pipeline { + agent { label 'k8s' } + + options { + timestamps() + ansiColor('xterm') + overrideIndexTriggers(false) + buildDiscarder(logRotator(daysToKeepStr: '7', artifactDaysToKeepStr: '7')) + timeout(time: 3, unit: 'HOURS') + } + + triggers { + cron '@midnight' + } + + environment { + // Replace / with :: as links from Jenkins to Artifactory are broken if we use slashes + // in the name + ARTIFACTORY_BUILD_NAME = "Corda / Publish / Publish Nightly to Artifactory" + .replaceAll("/", " :: ") + } + + stages { + stage('Publish to Artifactory') { + steps { + rtServer ( + id: 'R3-Artifactory', + url: 'https://software.r3.com/artifactory', + credentialsId: 'artifactory-credentials' + ) + rtGradleDeployer ( + id: 'deployer', + serverId: 'R3-Artifactory', + repo: 'corda-dev', + ) + withCredentials([ + usernamePassword(credentialsId: 'artifactory-credentials', + usernameVariable: 'CORDA_ARTIFACTORY_USERNAME', + passwordVariable: 'CORDA_ARTIFACTORY_PASSWORD')]) { + rtGradleRun ( + usesPlugin: true, + useWrapper: true, + switches: "--no-daemon -s", + tasks: 'artifactoryPublish', + deployerId: 'deployer', + buildName: env.ARTIFACTORY_BUILD_NAME + ) + } + rtPublishBuildInfo ( + serverId: 'R3-Artifactory', + buildName: env.ARTIFACTORY_BUILD_NAME + ) + } + } + } + + + post { + cleanup { + deleteDir() /* clean up our workspace */ + } + } +} diff --git a/.ci/dev/publish-branch/Jenkinsfile.preview b/.ci/dev/publish-branch/Jenkinsfile.preview new file mode 100644 index 0000000000..1b39ae3237 --- /dev/null +++ b/.ci/dev/publish-branch/Jenkinsfile.preview @@ -0,0 +1,65 @@ +#!groovy +@Library('corda-shared-build-pipeline-steps') +import static com.r3.build.BuildControl.killAllExistingBuildsForJob + +killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) + +pipeline { + agent { label 'k8s' } + + options { + timestamps() + ansiColor('xterm') + overrideIndexTriggers(false) + buildDiscarder(logRotator(daysToKeepStr: '7', artifactDaysToKeepStr: '7')) + timeout(time: 3, unit: 'HOURS') + } + + environment { + // Replace / with :: as links from Jenkins to Artifactory are broken if we use slashes + // in the name + ARTIFACTORY_BUILD_NAME = "Corda / Publish / Publish Preview to Artifactory" + .replaceAll("/", " :: ") + } + + stages { + stage('Publish to Artifactory') { + steps { + rtServer ( + id: 'R3-Artifactory', + url: 'https://software.r3.com/artifactory', + credentialsId: 'artifactory-credentials' + ) + rtGradleDeployer ( + id: 'deployer', + serverId: 'R3-Artifactory', + repo: 'corda-dev', + ) + withCredentials([ + usernamePassword(credentialsId: 'artifactory-credentials', + usernameVariable: 'CORDA_ARTIFACTORY_USERNAME', + passwordVariable: 'CORDA_ARTIFACTORY_PASSWORD')]) { + rtGradleRun ( + usesPlugin: true, + useWrapper: true, + switches: "--no-daemon -s -PversionFromGit", + tasks: 'artifactoryPublish', + deployerId: 'deployer', + buildName: env.ARTIFACTORY_BUILD_NAME + ) + } + rtPublishBuildInfo ( + serverId: 'R3-Artifactory', + buildName: env.ARTIFACTORY_BUILD_NAME + ) + } + } + } + + + post { + cleanup { + deleteDir() /* clean up our workspace */ + } + } +} From 6ebc6e9b16575a7afbb781b0d93a8f04b3affba5 Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Tue, 26 May 2020 15:46:29 +0100 Subject: [PATCH 5/5] CORDA-3750: Reimplement Corda's Crypto object for use inside the sandbox. (#6193) * CORDA-3750: Use hand-written sandbox Crypto object that delegates to the node. * CORDA-3750: Add integration test for deterministic CashIssueAndPayment flow. * Tidy up generics for Array instances. * Upgrade to DJVM 1.1-RC04. --- constants.properties | 2 +- node/build.gradle | 5 + .../kotlin/net/corda/node/djvm/LtxFactory.kt | 8 +- .../DeterministicCashIssueAndPaymentTest.kt | 68 +++++ .../java/sandbox/java/lang/CharSequence.java | 8 + .../java/sandbox/java/lang/Comparable.java | 8 + .../main/java/sandbox/java/lang/Number.java | 8 + .../java/sandbox/java/math/BigInteger.java | 8 + .../main/java/sandbox/java/security/Key.java | 13 + .../java/sandbox/java/security/KeyPair.java | 8 + .../sandbox/java/security/PrivateKey.java | 8 + .../java/sandbox/java/security/PublicKey.java | 8 + .../security/spec/AlgorithmParameterSpec.java | 8 + .../java/sandbox/java/util/ArrayList.java | 16 ++ .../src/main/java/sandbox/java/util/List.java | 9 + .../sandbox/net/corda/core/crypto/DJVM.java | 62 +++++ .../net/corda/core/crypto/DJVMPublicKey.java | 61 ++++ .../org/bouncycastle/asn1/ASN1Encodable.java | 9 + .../org/bouncycastle/asn1/ASN1Object.java | 16 ++ .../asn1/x509/AlgorithmIdentifier.java | 14 + .../asn1/x509/SubjectPublicKeyInfo.java | 10 + .../corda/node/internal/djvm/Serializer.kt | 2 +- .../DeterministicVerifierFactoryService.kt | 15 +- .../sandbox/net/corda/core/crypto/Crypto.kt | 261 ++++++++++++++++++ .../net/corda/core/crypto/SecureHash.kt | 8 + .../net/corda/core/crypto/SignatureScheme.kt | 26 ++ .../corda/core/crypto/TransactionSignature.kt | 7 + .../corda/core/crypto/internal/ProviderMap.kt | 8 + .../serializers/SandboxPublicKeySerializer.kt | 3 +- .../djvm/DeserializePublicKeyTest.kt | 20 +- 30 files changed, 694 insertions(+), 13 deletions(-) create mode 100644 node/src/integration-test/kotlin/net/corda/node/services/DeterministicCashIssueAndPaymentTest.kt create mode 100644 node/src/main/java/sandbox/java/lang/CharSequence.java create mode 100644 node/src/main/java/sandbox/java/lang/Comparable.java create mode 100644 node/src/main/java/sandbox/java/lang/Number.java create mode 100644 node/src/main/java/sandbox/java/math/BigInteger.java create mode 100644 node/src/main/java/sandbox/java/security/Key.java create mode 100644 node/src/main/java/sandbox/java/security/KeyPair.java create mode 100644 node/src/main/java/sandbox/java/security/PrivateKey.java create mode 100644 node/src/main/java/sandbox/java/security/PublicKey.java create mode 100644 node/src/main/java/sandbox/java/security/spec/AlgorithmParameterSpec.java create mode 100644 node/src/main/java/sandbox/java/util/ArrayList.java create mode 100644 node/src/main/java/sandbox/java/util/List.java create mode 100644 node/src/main/java/sandbox/net/corda/core/crypto/DJVM.java create mode 100644 node/src/main/java/sandbox/net/corda/core/crypto/DJVMPublicKey.java create mode 100644 node/src/main/java/sandbox/org/bouncycastle/asn1/ASN1Encodable.java create mode 100644 node/src/main/java/sandbox/org/bouncycastle/asn1/ASN1Object.java create mode 100644 node/src/main/java/sandbox/org/bouncycastle/asn1/x509/AlgorithmIdentifier.java create mode 100644 node/src/main/java/sandbox/org/bouncycastle/asn1/x509/SubjectPublicKeyInfo.java create mode 100644 node/src/main/kotlin/sandbox/net/corda/core/crypto/Crypto.kt create mode 100644 node/src/main/kotlin/sandbox/net/corda/core/crypto/SecureHash.kt create mode 100644 node/src/main/kotlin/sandbox/net/corda/core/crypto/SignatureScheme.kt create mode 100644 node/src/main/kotlin/sandbox/net/corda/core/crypto/TransactionSignature.kt create mode 100644 node/src/main/kotlin/sandbox/net/corda/core/crypto/internal/ProviderMap.kt diff --git a/constants.properties b/constants.properties index 6865651962..131f2ae5f8 100644 --- a/constants.properties +++ b/constants.properties @@ -30,7 +30,7 @@ snakeYamlVersion=1.19 caffeineVersion=2.7.0 metricsVersion=4.1.0 metricsNewRelicVersion=1.1.1 -djvmVersion=1.1-RC03 +djvmVersion=1.1-RC04 deterministicRtVersion=1.0-RC02 openSourceBranch=https://github.com/corda/corda/blob/release/os/4.4 openSourceSamplesBranch=https://github.com/corda/samples/blob/release-V4 diff --git a/node/build.gradle b/node/build.gradle index ae98903907..58f7a5498e 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -304,6 +304,11 @@ quasar { jar { baseName 'corda-node' + exclude 'sandbox/java/**' + exclude 'sandbox/org/**' + exclude 'sandbox/net/corda/core/crypto/SecureHash.class' + exclude 'sandbox/net/corda/core/crypto/SignatureScheme.class' + exclude 'sandbox/net/corda/core/crypto/TransactionSignature.class' manifest { attributes('Corda-Deterministic-Runtime': configurations.jdkRt.singleFile.name) attributes('Corda-Deterministic-Classpath': configurations.deterministic.collect { it.name }.join(' ')) 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 dc3affd9e2..31c1b2aaa8 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 @@ -27,12 +27,12 @@ private const val TX_PRIVACY_SALT = 7 private const val TX_NETWORK_PARAMETERS = 8 private const val TX_REFERENCES = 9 -class LtxFactory : Function, LedgerTransaction> { +class LtxFactory : Function, LedgerTransaction> { @Suppress("unchecked_cast") - override fun apply(txArgs: Array): LedgerTransaction { + override fun apply(txArgs: Array): LedgerTransaction { return LedgerTransaction.createForSandbox( - inputs = (txArgs[TX_INPUTS] as Array>).map { it.toStateAndRef() }, + inputs = (txArgs[TX_INPUTS] as Array>).map { it.toStateAndRef() }, outputs = (txArgs[TX_OUTPUTS] as? List>) ?: emptyList(), commands = (txArgs[TX_COMMANDS] as? List>) ?: emptyList(), attachments = (txArgs[TX_ATTACHMENTS] as? List) ?: emptyList(), @@ -41,7 +41,7 @@ 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() } ) } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DeterministicCashIssueAndPaymentTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicCashIssueAndPaymentTest.kt new file mode 100644 index 0000000000..0de1960375 --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicCashIssueAndPaymentTest.kt @@ -0,0 +1,68 @@ +package net.corda.node.services + +import net.corda.core.messaging.startFlow +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.loggerFor +import net.corda.finance.DOLLARS +import net.corda.finance.flows.CashIssueAndPaymentFlow +import net.corda.node.DeterministicSourcesRule +import net.corda.testing.core.ALICE_NAME +import net.corda.testing.core.DUMMY_NOTARY_NAME +import net.corda.testing.core.singleIdentity +import net.corda.testing.driver.DriverParameters +import net.corda.testing.driver.driver +import net.corda.testing.driver.internal.incrementalPortAllocation +import net.corda.testing.node.NotarySpec +import net.corda.testing.node.internal.findCordapp +import org.junit.ClassRule +import org.junit.Test +import org.junit.jupiter.api.assertDoesNotThrow + +@Suppress("FunctionName") +class DeterministicCashIssueAndPaymentTest { + companion object { + val logger = loggerFor() + + @ClassRule + @JvmField + val djvmSources = DeterministicSourcesRule() + + @JvmField + val CASH_AMOUNT = 500.DOLLARS + + fun parametersFor(djvmSources: DeterministicSourcesRule): DriverParameters { + return DriverParameters( + portAllocation = incrementalPortAllocation(), + startNodesInProcess = false, + notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)), + cordappsForAllNodes = listOf( + findCordapp("net.corda.finance.contracts"), + findCordapp("net.corda.finance.workflows") + ), + djvmBootstrapSource = djvmSources.bootstrap, + djvmCordaSource = djvmSources.corda + ) + } + } + + @Test(timeout = 300_000) + fun `test DJVM can issue cash`() { + val reference = OpaqueBytes.of(0x01) + driver(parametersFor(djvmSources)) { + val alice = startNode(providedName = ALICE_NAME).getOrThrow() + val aliceParty = alice.nodeInfo.singleIdentity() + val notaryParty = notaryHandles.single().identity + val txId = assertDoesNotThrow { + alice.rpc.startFlow(::CashIssueAndPaymentFlow, + CASH_AMOUNT, + reference, + aliceParty, + false, + notaryParty + ).returnValue.getOrThrow() + } + logger.info("TX-ID: {}", txId) + } + } +} diff --git a/node/src/main/java/sandbox/java/lang/CharSequence.java b/node/src/main/java/sandbox/java/lang/CharSequence.java new file mode 100644 index 0000000000..9b62762a11 --- /dev/null +++ b/node/src/main/java/sandbox/java/lang/CharSequence.java @@ -0,0 +1,8 @@ +package sandbox.java.lang; + +/** + * This is a dummy class that implements just enough of {@link java.lang.CharSequence} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public interface CharSequence extends java.lang.CharSequence { +} diff --git a/node/src/main/java/sandbox/java/lang/Comparable.java b/node/src/main/java/sandbox/java/lang/Comparable.java new file mode 100644 index 0000000000..5ca4f4871c --- /dev/null +++ b/node/src/main/java/sandbox/java/lang/Comparable.java @@ -0,0 +1,8 @@ +package sandbox.java.lang; + +/** + * This is a dummy class that implements just enough of {@link java.lang.Comparable} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public interface Comparable extends java.lang.Comparable { +} diff --git a/node/src/main/java/sandbox/java/lang/Number.java b/node/src/main/java/sandbox/java/lang/Number.java new file mode 100644 index 0000000000..a98d60dbd6 --- /dev/null +++ b/node/src/main/java/sandbox/java/lang/Number.java @@ -0,0 +1,8 @@ +package sandbox.java.lang; + +/** + * This is a dummy class that implements just enough of {@link java.lang.Number} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public class Number extends Object { +} diff --git a/node/src/main/java/sandbox/java/math/BigInteger.java b/node/src/main/java/sandbox/java/math/BigInteger.java new file mode 100644 index 0000000000..d58328fd3c --- /dev/null +++ b/node/src/main/java/sandbox/java/math/BigInteger.java @@ -0,0 +1,8 @@ +package sandbox.java.math; + +/** + * This is a dummy class that implements just enough of {@link java.math.BigInteger} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public class BigInteger extends sandbox.java.lang.Number { +} diff --git a/node/src/main/java/sandbox/java/security/Key.java b/node/src/main/java/sandbox/java/security/Key.java new file mode 100644 index 0000000000..9c5c952852 --- /dev/null +++ b/node/src/main/java/sandbox/java/security/Key.java @@ -0,0 +1,13 @@ +package sandbox.java.security; + +import sandbox.java.lang.String; + +/** + * This is a dummy class that implements just enough of {@link java.security.Key} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public interface Key { + String getAlgorithm(); + String getFormat(); + byte[] getEncoded(); +} diff --git a/node/src/main/java/sandbox/java/security/KeyPair.java b/node/src/main/java/sandbox/java/security/KeyPair.java new file mode 100644 index 0000000000..653e27de8e --- /dev/null +++ b/node/src/main/java/sandbox/java/security/KeyPair.java @@ -0,0 +1,8 @@ +package sandbox.java.security; + +/** + * This is a dummy class that implements just enough of {@link java.security.KeyPair} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public final class KeyPair extends sandbox.java.lang.Object implements java.io.Serializable { +} \ No newline at end of file diff --git a/node/src/main/java/sandbox/java/security/PrivateKey.java b/node/src/main/java/sandbox/java/security/PrivateKey.java new file mode 100644 index 0000000000..a314aa6234 --- /dev/null +++ b/node/src/main/java/sandbox/java/security/PrivateKey.java @@ -0,0 +1,8 @@ +package sandbox.java.security; + +/** + * This is a dummy class that implements just enough of {@link java.security.PrivateKey} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public interface PrivateKey extends Key { +} diff --git a/node/src/main/java/sandbox/java/security/PublicKey.java b/node/src/main/java/sandbox/java/security/PublicKey.java new file mode 100644 index 0000000000..451f33141a --- /dev/null +++ b/node/src/main/java/sandbox/java/security/PublicKey.java @@ -0,0 +1,8 @@ +package sandbox.java.security; + +/** + * This is a dummy class that implements just enough of {@link java.security.PublicKey} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public interface PublicKey extends Key { +} diff --git a/node/src/main/java/sandbox/java/security/spec/AlgorithmParameterSpec.java b/node/src/main/java/sandbox/java/security/spec/AlgorithmParameterSpec.java new file mode 100644 index 0000000000..7943cf4612 --- /dev/null +++ b/node/src/main/java/sandbox/java/security/spec/AlgorithmParameterSpec.java @@ -0,0 +1,8 @@ +package sandbox.java.security.spec; + +/** + * This is a dummy class that implements just enough of {@link java.security.spec.AlgorithmParameterSpec} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public interface AlgorithmParameterSpec { +} diff --git a/node/src/main/java/sandbox/java/util/ArrayList.java b/node/src/main/java/sandbox/java/util/ArrayList.java new file mode 100644 index 0000000000..7eb5df9f09 --- /dev/null +++ b/node/src/main/java/sandbox/java/util/ArrayList.java @@ -0,0 +1,16 @@ +package sandbox.java.util; + +/** + * This is a dummy class that implements just enough of {@link java.util.ArrayList} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +@SuppressWarnings("unused") +public class ArrayList extends sandbox.java.lang.Object implements List { + public ArrayList(int size) { + } + + @Override + public boolean add(T item) { + throw new UnsupportedOperationException("Dummy class - not implemented"); + } +} diff --git a/node/src/main/java/sandbox/java/util/List.java b/node/src/main/java/sandbox/java/util/List.java new file mode 100644 index 0000000000..0f7dbfe22c --- /dev/null +++ b/node/src/main/java/sandbox/java/util/List.java @@ -0,0 +1,9 @@ +package sandbox.java.util; + +/** + * This is a dummy class that implements just enough of {@link java.util.List} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public interface List { + boolean add(T item); +} diff --git a/node/src/main/java/sandbox/net/corda/core/crypto/DJVM.java b/node/src/main/java/sandbox/net/corda/core/crypto/DJVM.java new file mode 100644 index 0000000000..8004d44666 --- /dev/null +++ b/node/src/main/java/sandbox/net/corda/core/crypto/DJVM.java @@ -0,0 +1,62 @@ +package sandbox.net.corda.core.crypto; + +import org.jetbrains.annotations.NotNull; +import sandbox.java.lang.Integer; +import sandbox.java.lang.String; +import sandbox.java.util.ArrayList; +import sandbox.java.util.List; +import sandbox.org.bouncycastle.asn1.x509.AlgorithmIdentifier; + +import java.io.IOException; + +/** + * Helper class for {@link sandbox.net.corda.core.crypto.Crypto}. + * Deliberately package-private. + */ +final class DJVM { + private DJVM() {} + + @NotNull + static SignatureScheme toDJVM(@NotNull net.corda.core.crypto.SignatureScheme scheme) { + // The AlgorithmParameterSpec is deliberately left as null + // because it is computationally expensive to generate these + // objects inside the sandbox every time. + return new SignatureScheme( + scheme.getSchemeNumberID(), + String.toDJVM(scheme.getSchemeCodeName()), + toDJVM(scheme.getSignatureOID()), + toDJVM(scheme.getAlternativeOIDs()), + String.toDJVM(scheme.getProviderName()), + String.toDJVM(scheme.getAlgorithmName()), + String.toDJVM(scheme.getSignatureName()), + null, + Integer.toDJVM(scheme.getKeySize()), + String.toDJVM(scheme.getDesc()) + ); + } + + static org.bouncycastle.asn1.x509.AlgorithmIdentifier fromDJVM(@NotNull AlgorithmIdentifier oid) { + try { + return org.bouncycastle.asn1.x509.AlgorithmIdentifier.getInstance(oid.getEncoded()); + } catch (IOException e) { + throw sandbox.java.lang.DJVM.toRuleViolationError(e); + } + } + + static AlgorithmIdentifier toDJVM(@NotNull org.bouncycastle.asn1.x509.AlgorithmIdentifier oid) { + try { + return AlgorithmIdentifier.getInstance(oid.getEncoded()); + } catch (IOException e) { + throw sandbox.java.lang.DJVM.toRuleViolationError(e); + } + } + + @NotNull + static List toDJVM(@NotNull java.util.List list) { + ArrayList djvmList = new ArrayList<>(list.size()); + for (org.bouncycastle.asn1.x509.AlgorithmIdentifier oid : list) { + djvmList.add(toDJVM(oid)); + } + return djvmList; + } +} diff --git a/node/src/main/java/sandbox/net/corda/core/crypto/DJVMPublicKey.java b/node/src/main/java/sandbox/net/corda/core/crypto/DJVMPublicKey.java new file mode 100644 index 0000000000..199acc39ce --- /dev/null +++ b/node/src/main/java/sandbox/net/corda/core/crypto/DJVMPublicKey.java @@ -0,0 +1,61 @@ +package sandbox.net.corda.core.crypto; + +import org.jetbrains.annotations.NotNull; +import sandbox.java.lang.Object; +import sandbox.java.lang.String; +import sandbox.java.security.PublicKey; + +/** + * We shall delegate as much cryptography as possible to Corda's + * underlying {@link net.corda.core.crypto.Crypto} object and its + * {@link java.security.Provider} classes. This wrapper only needs + * to implement {@link #equals} and {@link #hashCode}. + */ +final class DJVMPublicKey extends Object implements PublicKey { + private final java.security.PublicKey underlying; + private final String algorithm; + private final String format; + private final int hashCode; + + DJVMPublicKey(@NotNull java.security.PublicKey underlying) { + this.underlying = underlying; + this.algorithm = String.toDJVM(underlying.getAlgorithm()); + this.format = String.toDJVM(underlying.getFormat()); + this.hashCode = underlying.hashCode(); + } + + java.security.PublicKey getUnderlying() { + return underlying; + } + + @Override + public boolean equals(java.lang.Object other) { + if (this == other) { + return true; + } else if (!(other instanceof DJVMPublicKey)) { + return false; + } else { + return underlying.equals(((DJVMPublicKey) other).underlying); + } + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public String getAlgorithm() { + return algorithm; + } + + @Override + public String getFormat() { + return format; + } + + @Override + public byte[] getEncoded() { + return underlying.getEncoded(); + } +} diff --git a/node/src/main/java/sandbox/org/bouncycastle/asn1/ASN1Encodable.java b/node/src/main/java/sandbox/org/bouncycastle/asn1/ASN1Encodable.java new file mode 100644 index 0000000000..e75ac2e6b4 --- /dev/null +++ b/node/src/main/java/sandbox/org/bouncycastle/asn1/ASN1Encodable.java @@ -0,0 +1,9 @@ +package sandbox.org.bouncycastle.asn1; + +/** + * This is a dummy class that implements just enough of {@link org.bouncycastle.asn1.ASN1Encodable} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +@SuppressWarnings("WeakerAccess") +public interface ASN1Encodable { +} diff --git a/node/src/main/java/sandbox/org/bouncycastle/asn1/ASN1Object.java b/node/src/main/java/sandbox/org/bouncycastle/asn1/ASN1Object.java new file mode 100644 index 0000000000..d71661cc71 --- /dev/null +++ b/node/src/main/java/sandbox/org/bouncycastle/asn1/ASN1Object.java @@ -0,0 +1,16 @@ +package sandbox.org.bouncycastle.asn1; + +import sandbox.java.lang.Object; + +import java.io.IOException; + +/** + * This is a dummy class that implements just enough of {@link org.bouncycastle.asn1.ASN1Object} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +@SuppressWarnings("RedundantThrows") +public class ASN1Object extends Object implements ASN1Encodable { + public byte[] getEncoded() throws IOException { + throw new UnsupportedOperationException("Dummy class - not implemented"); + } +} diff --git a/node/src/main/java/sandbox/org/bouncycastle/asn1/x509/AlgorithmIdentifier.java b/node/src/main/java/sandbox/org/bouncycastle/asn1/x509/AlgorithmIdentifier.java new file mode 100644 index 0000000000..144b5f9260 --- /dev/null +++ b/node/src/main/java/sandbox/org/bouncycastle/asn1/x509/AlgorithmIdentifier.java @@ -0,0 +1,14 @@ +package sandbox.org.bouncycastle.asn1.x509; + +import sandbox.org.bouncycastle.asn1.ASN1Object; + +/** + * This is a dummy class that implements just enough of {@link org.bouncycastle.asn1.x509.AlgorithmIdentifier} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +@SuppressWarnings("unused") +public class AlgorithmIdentifier extends ASN1Object { + public static AlgorithmIdentifier getInstance(Object obj) { + throw new UnsupportedOperationException("Dummy class - not implemented"); + } +} diff --git a/node/src/main/java/sandbox/org/bouncycastle/asn1/x509/SubjectPublicKeyInfo.java b/node/src/main/java/sandbox/org/bouncycastle/asn1/x509/SubjectPublicKeyInfo.java new file mode 100644 index 0000000000..a84fb60772 --- /dev/null +++ b/node/src/main/java/sandbox/org/bouncycastle/asn1/x509/SubjectPublicKeyInfo.java @@ -0,0 +1,10 @@ +package sandbox.org.bouncycastle.asn1.x509; + +import sandbox.org.bouncycastle.asn1.ASN1Object; + +/** + * This is a dummy class that implements just enough of {@link org.bouncycastle.asn1.x509.SubjectPublicKeyInfo} + * to allow us to compile {@link sandbox.net.corda.core.crypto.Crypto}. + */ +public class SubjectPublicKeyInfo extends ASN1Object { +} diff --git a/node/src/main/kotlin/net/corda/node/internal/djvm/Serializer.kt b/node/src/main/kotlin/net/corda/node/internal/djvm/Serializer.kt index 1262ae8710..40a5522a28 100644 --- a/node/src/main/kotlin/net/corda/node/internal/djvm/Serializer.kt +++ b/node/src/main/kotlin/net/corda/node/internal/djvm/Serializer.kt @@ -31,7 +31,7 @@ class Serializer( * [net.corda.node.djvm.LtxFactory] to be transformed finally to * a list of [net.corda.core.contracts.StateAndRef] objects, */ - fun deserialize(stateRefs: List): Array> { + fun deserialize(stateRefs: List): Array> { return stateRefs.map { arrayOf(deserialize(it.serializedState), deserialize(it.ref.serialize())) }.toTypedArray() diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/DeterministicVerifierFactoryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/DeterministicVerifierFactoryService.kt index 3485e3af71..d514335c92 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/DeterministicVerifierFactoryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/DeterministicVerifierFactoryService.kt @@ -47,7 +47,20 @@ class DeterministicVerifierFactoryService( ConstructorForDeserialization::class.java, DeprecatedConstructorForDeserialization::class.java ), - bootstrapSource = bootstrapSource + bootstrapSource = bootstrapSource, + overrideClasses = setOf( + /** + * These classes are all duplicated into the sandbox + * without the DJVM modifying their byte-code first. + * The goal is to delegate cryptographic operations + * out to the Node rather than perform them inside + * the sandbox, because this is MUCH FASTER. + */ + sandbox.net.corda.core.crypto.Crypto::class.java.name, + "sandbox.net.corda.core.crypto.DJVM", + "sandbox.net.corda.core.crypto.DJVMPublicKey", + "sandbox.net.corda.core.crypto.internal.ProviderMapKt" + ) ) baseSandboxConfiguration = SandboxConfiguration.createFor( diff --git a/node/src/main/kotlin/sandbox/net/corda/core/crypto/Crypto.kt b/node/src/main/kotlin/sandbox/net/corda/core/crypto/Crypto.kt new file mode 100644 index 0000000000..b51a586c93 --- /dev/null +++ b/node/src/main/kotlin/sandbox/net/corda/core/crypto/Crypto.kt @@ -0,0 +1,261 @@ +package sandbox.net.corda.core.crypto + +import sandbox.net.corda.core.crypto.DJVM.fromDJVM +import sandbox.net.corda.core.crypto.DJVM.toDJVM +import sandbox.java.lang.String +import sandbox.java.lang.doCatch +import sandbox.java.math.BigInteger +import sandbox.java.security.KeyPair +import sandbox.java.security.PrivateKey +import sandbox.java.security.PublicKey +import sandbox.java.util.ArrayList +import sandbox.java.util.List +import sandbox.java.lang.Object +import sandbox.org.bouncycastle.asn1.x509.AlgorithmIdentifier +import sandbox.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo +import java.security.GeneralSecurityException +import java.security.SignatureException +import java.security.spec.InvalidKeySpecException + +/** + * This is a hand-written "drop-in" replacement for the version of + * [net.corda.core.crypto.Crypto] found inside core-deterministic. + * This class is used in the DJVM sandbox instead of transforming + * the byte-code for [net.corda.core.crypto.Crypto], with the goal + * of not needing to instantiate some EXPENSIVE elliptic curve + * cryptography classes inside every single sandbox. + * + * The downside is that this class MUST manually be kept consistent + * with the DJVM's byte-code rewriting rules. + */ +@Suppress("unused", "unused_parameter", "TooManyFunctions") +object Crypto : Object() { + @JvmField + val RSA_SHA256: SignatureScheme = toDJVM(net.corda.core.crypto.Crypto.RSA_SHA256) + + @JvmField + val ECDSA_SECP256K1_SHA256: SignatureScheme = toDJVM(net.corda.core.crypto.Crypto.ECDSA_SECP256K1_SHA256) + + @JvmField + val ECDSA_SECP256R1_SHA256: SignatureScheme = toDJVM(net.corda.core.crypto.Crypto.ECDSA_SECP256R1_SHA256) + + @JvmField + val EDDSA_ED25519_SHA512: SignatureScheme = toDJVM(net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512) + + @JvmField + val SPHINCS256_SHA256: SignatureScheme = toDJVM(net.corda.core.crypto.Crypto.SPHINCS256_SHA256) + + @JvmField + val COMPOSITE_KEY: SignatureScheme = toDJVM(net.corda.core.crypto.Crypto.COMPOSITE_KEY) + + @JvmField + val DEFAULT_SIGNATURE_SCHEME = EDDSA_ED25519_SHA512 + + /** + * We can use the unsandboxed versions of [Map] and [List] here + * because the [underlyingSchemes] and [djvmSchemes] fields are + * private and not exposed to the rest of the sandbox. + */ + private val underlyingSchemes: Map + = net.corda.core.crypto.Crypto.supportedSignatureSchemes() + .associateBy(net.corda.core.crypto.SignatureScheme::schemeCodeName) + private val djvmSchemes: Map = listOf( + RSA_SHA256, + ECDSA_SECP256K1_SHA256, + ECDSA_SECP256R1_SHA256, + EDDSA_ED25519_SHA512, + SPHINCS256_SHA256, + COMPOSITE_KEY + ).associateByTo(LinkedHashMap(), SignatureScheme::schemeCodeName) + + private fun findUnderlyingSignatureScheme(signatureScheme: SignatureScheme): net.corda.core.crypto.SignatureScheme { + return net.corda.core.crypto.Crypto.findSignatureScheme(String.fromDJVM(signatureScheme.schemeCodeName)) + } + + private fun PublicKey.toUnderlyingKey(): java.security.PublicKey { + return (this as? DJVMPublicKey ?: throw sandbox.java.lang.fail("Unsupported key ${this::class.java.name}")).underlying + } + + @JvmStatic + fun supportedSignatureSchemes(): List { + val schemes = ArrayList(djvmSchemes.size) + for (scheme in djvmSchemes.values) { + schemes.add(scheme) + } + return schemes + } + + @JvmStatic + fun isSupportedSignatureScheme(signatureScheme: SignatureScheme): Boolean { + return String.fromDJVM(signatureScheme.schemeCodeName) in underlyingSchemes + } + + @JvmStatic + fun findSignatureScheme(schemeCodeName: String): SignatureScheme { + return djvmSchemes[schemeCodeName] + ?: throw IllegalArgumentException("Unsupported key/algorithm for schemeCodeName: $schemeCodeName") + } + + @JvmStatic + fun findSignatureScheme(schemeNumberID: Int): SignatureScheme { + val underlyingScheme = net.corda.core.crypto.Crypto.findSignatureScheme(schemeNumberID) + return findSignatureScheme(String.toDJVM(underlyingScheme.schemeCodeName)) + } + + @JvmStatic + fun findSignatureScheme(key: PublicKey): SignatureScheme { + val underlyingScheme = net.corda.core.crypto.Crypto.findSignatureScheme(key.toUnderlyingKey()) + return findSignatureScheme(String.toDJVM(underlyingScheme.schemeCodeName)) + } + + @JvmStatic + fun findSignatureScheme(algorithm: AlgorithmIdentifier): SignatureScheme { + val underlyingScheme = net.corda.core.crypto.Crypto.findSignatureScheme(fromDJVM(algorithm)) + return findSignatureScheme(String.toDJVM(underlyingScheme.schemeCodeName)) + } + + @JvmStatic + fun findSignatureScheme(key: PrivateKey): SignatureScheme { + throw sandbox.java.lang.failApi("Crypto.findSignatureScheme(PrivateKey)") + } + + @JvmStatic + fun decodePrivateKey(signatureScheme: SignatureScheme, encodedKey: ByteArray): PrivateKey { + throw sandbox.java.lang.failApi("Crypto.decodePrivateKey(SignatureScheme, byte[])") + } + + @JvmStatic + fun decodePublicKey(encodedKey: ByteArray): PublicKey { + val underlying = try { + net.corda.core.crypto.Crypto.decodePublicKey(encodedKey) + } catch (e: InvalidKeySpecException) { + throw sandbox.java.lang.fromDJVM(doCatch(e)) + } + return DJVMPublicKey(underlying) + } + + @JvmStatic + fun decodePublicKey(schemeCodeName: String, encodedKey: ByteArray): PublicKey { + val underlying = try { + net.corda.core.crypto.Crypto.decodePublicKey(String.fromDJVM(schemeCodeName), encodedKey) + } catch (e: InvalidKeySpecException) { + throw sandbox.java.lang.fromDJVM(doCatch(e)) + } + return DJVMPublicKey(underlying) + } + + @JvmStatic + fun decodePublicKey(signatureScheme: SignatureScheme, encodedKey: ByteArray): PublicKey { + return decodePublicKey(signatureScheme.schemeCodeName, encodedKey) + } + + @JvmStatic + fun deriveKeyPair(signatureScheme: SignatureScheme, privateKey: PrivateKey, seed: ByteArray): KeyPair { + throw sandbox.java.lang.failApi("Crypto.deriveKeyPair(SignatureScheme, PrivateKey, byte[])") + } + + @JvmStatic + fun deriveKeyPair(privateKey: PrivateKey, seed: ByteArray): KeyPair { + throw sandbox.java.lang.failApi("Crypto.deriveKeyPair(PrivateKey, byte[])") + } + + @JvmStatic + fun deriveKeyPairFromEntropy(signatureScheme: SignatureScheme, entropy: BigInteger): KeyPair { + throw sandbox.java.lang.failApi("Crypto.deriveKeyPairFromEntropy(SignatureScheme, BigInteger)") + } + + @JvmStatic + fun deriveKeyPairFromEntropy(entropy: BigInteger): KeyPair { + throw sandbox.java.lang.failApi("Crypto.deriveKeyPairFromEntropy(BigInteger)") + } + + @JvmStatic + fun doVerify(schemeCodeName: String, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray): Boolean { + val underlyingKey = publicKey.toUnderlyingKey() + return try { + net.corda.core.crypto.Crypto.doVerify(String.fromDJVM(schemeCodeName), underlyingKey, signatureData, clearData) + } catch (e: GeneralSecurityException) { + throw sandbox.java.lang.fromDJVM(doCatch(e)) + } + } + + @JvmStatic + fun doVerify(publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray): Boolean { + val underlyingKey = publicKey.toUnderlyingKey() + return try { + net.corda.core.crypto.Crypto.doVerify(underlyingKey, signatureData, clearData) + } catch (e: GeneralSecurityException) { + throw sandbox.java.lang.fromDJVM(doCatch(e)) + } + } + + @JvmStatic + fun doVerify(signatureScheme: SignatureScheme, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray): Boolean { + val underlyingScheme = findUnderlyingSignatureScheme(signatureScheme) + val underlyingKey = publicKey.toUnderlyingKey() + return try { + net.corda.core.crypto.Crypto.doVerify(underlyingScheme, underlyingKey, signatureData, clearData) + } catch (e: GeneralSecurityException) { + throw sandbox.java.lang.fromDJVM(doCatch(e)) + } + } + + @JvmStatic + fun doVerify(txId: SecureHash, transactionSignature: TransactionSignature): Boolean { + throw sandbox.java.lang.failApi("Crypto.doVerify(SecureHash, TransactionSignature)") + } + + @JvmStatic + fun isValid(txId: SecureHash, transactionSignature: TransactionSignature): Boolean { + throw sandbox.java.lang.failApi("Crypto.isValid(SecureHash, TransactionSignature)") + } + + @JvmStatic + fun isValid(publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray): Boolean { + val underlyingKey = publicKey.toUnderlyingKey() + return try { + net.corda.core.crypto.Crypto.isValid(underlyingKey, signatureData, clearData) + } catch (e: SignatureException) { + throw sandbox.java.lang.fromDJVM(doCatch(e)) + } + } + + @JvmStatic + fun isValid(signatureScheme: SignatureScheme, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray): Boolean { + val underlyingScheme = findUnderlyingSignatureScheme(signatureScheme) + val underlyingKey = publicKey.toUnderlyingKey() + return try { + net.corda.core.crypto.Crypto.isValid(underlyingScheme, underlyingKey, signatureData, clearData) + } catch (e: SignatureException) { + throw sandbox.java.lang.fromDJVM(doCatch(e)) + } + } + + @JvmStatic + fun publicKeyOnCurve(signatureScheme: SignatureScheme, publicKey: PublicKey): Boolean { + val underlyingScheme = findUnderlyingSignatureScheme(signatureScheme) + val underlyingKey = publicKey.toUnderlyingKey() + return net.corda.core.crypto.Crypto.publicKeyOnCurve(underlyingScheme, underlyingKey) + } + + @JvmStatic + fun validatePublicKey(key: PublicKey): Boolean { + return net.corda.core.crypto.Crypto.validatePublicKey(key.toUnderlyingKey()) + } + + @JvmStatic + fun toSupportedPublicKey(key: SubjectPublicKeyInfo): PublicKey { + return decodePublicKey(key.encoded) + } + + @JvmStatic + fun toSupportedPublicKey(key: PublicKey): PublicKey { + val underlyingKey = key.toUnderlyingKey() + val supportedKey = net.corda.core.crypto.Crypto.toSupportedPublicKey(underlyingKey) + return if (supportedKey === underlyingKey) { + key + } else { + DJVMPublicKey(supportedKey) + } + } +} diff --git a/node/src/main/kotlin/sandbox/net/corda/core/crypto/SecureHash.kt b/node/src/main/kotlin/sandbox/net/corda/core/crypto/SecureHash.kt new file mode 100644 index 0000000000..ac8f733511 --- /dev/null +++ b/node/src/main/kotlin/sandbox/net/corda/core/crypto/SecureHash.kt @@ -0,0 +1,8 @@ +package sandbox.net.corda.core.crypto + +/** + * This is a dummy class that implements just enough of [net.corda.core.crypto.SecureHash] + * to allow us to compile [sandbox.net.corda.core.crypto.Crypto]. + */ +@Suppress("unused_parameter") +sealed class SecureHash(bytes: ByteArray) : sandbox.java.lang.Object() diff --git a/node/src/main/kotlin/sandbox/net/corda/core/crypto/SignatureScheme.kt b/node/src/main/kotlin/sandbox/net/corda/core/crypto/SignatureScheme.kt new file mode 100644 index 0000000000..dd315252df --- /dev/null +++ b/node/src/main/kotlin/sandbox/net/corda/core/crypto/SignatureScheme.kt @@ -0,0 +1,26 @@ +package sandbox.net.corda.core.crypto + +import sandbox.java.lang.String +import sandbox.java.lang.Integer +import sandbox.java.lang.Object +import sandbox.java.security.spec.AlgorithmParameterSpec +import sandbox.java.util.List +import sandbox.org.bouncycastle.asn1.x509.AlgorithmIdentifier + +/** + * This is a dummy class that implements just enough of [net.corda.core.crypto.SignatureScheme] + * to allow us to compile [sandbox.net.corda.core.crypto.Crypto]. + */ +@Suppress("unused") +class SignatureScheme( + val schemeNumberID: Int, + val schemeCodeName: String, + val signatureOID: AlgorithmIdentifier, + val alternativeOIDs: List, + val providerName: String, + val algorithmName: String, + val signatureName: String, + val algSpec: AlgorithmParameterSpec?, + val keySize: Integer?, + val desc: String +) : Object() diff --git a/node/src/main/kotlin/sandbox/net/corda/core/crypto/TransactionSignature.kt b/node/src/main/kotlin/sandbox/net/corda/core/crypto/TransactionSignature.kt new file mode 100644 index 0000000000..2d3c3e8b90 --- /dev/null +++ b/node/src/main/kotlin/sandbox/net/corda/core/crypto/TransactionSignature.kt @@ -0,0 +1,7 @@ +package sandbox.net.corda.core.crypto + +/** + * This is a dummy class that implements just enough of [net.corda.core.crypto.TransactionSignature] + * to allow us to compile [sandbox.net.corda.core.crypto.Crypto]. + */ +class TransactionSignature : sandbox.java.lang.Object() diff --git a/node/src/main/kotlin/sandbox/net/corda/core/crypto/internal/ProviderMap.kt b/node/src/main/kotlin/sandbox/net/corda/core/crypto/internal/ProviderMap.kt new file mode 100644 index 0000000000..5a0ebb4065 --- /dev/null +++ b/node/src/main/kotlin/sandbox/net/corda/core/crypto/internal/ProviderMap.kt @@ -0,0 +1,8 @@ +package sandbox.net.corda.core.crypto.internal + +/** + * THIS FILE IS DELIBERATELY EMPTY, APART FROM A SINGLE DUMMY VALUE. + * KOTLIN WILL NOT CREATE A CLASS IF THIS FILE IS COMPLETELY EMPTY. + */ +@Suppress("unused") +private const val DUMMY_VALUE = 0 diff --git a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxPublicKeySerializer.kt b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxPublicKeySerializer.kt index ec528667ee..6a22e05da6 100644 --- a/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxPublicKeySerializer.kt +++ b/serialization-djvm/src/main/kotlin/net/corda/serialization/djvm/serializers/SandboxPublicKeySerializer.kt @@ -19,8 +19,7 @@ class SandboxPublicKeySerializer( taskFactory: Function>, out Function> ) : CustomSerializer.Implements(classLoader.toSandboxAnyClass(PublicKey::class.java)) { @Suppress("unchecked_cast") - private val decoder: Function - = taskFactory.apply(PublicKeyDecoder::class.java) as Function + private val decoder = taskFactory.apply(PublicKeyDecoder::class.java) as Function override val schemaForDocumentation: Schema = Schema(emptyList()) diff --git a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializePublicKeyTest.kt b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializePublicKeyTest.kt index d952ff10d6..dc982a8569 100644 --- a/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializePublicKeyTest.kt +++ b/serialization-djvm/src/test/kotlin/net/corda/serialization/djvm/DeserializePublicKeyTest.kt @@ -8,6 +8,7 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.internal._contextSerializationEnv import net.corda.core.serialization.serialize import net.corda.serialization.djvm.SandboxType.KOTLIN +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -69,13 +70,18 @@ class DeserializePublicKeyTest : TestBase(KOTLIN) { sandbox { _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) - val sandboxKey = data.deserializeFor(classLoader) + val sandboxData = data.deserializeFor(classLoader) - val taskFactory = classLoader.createRawTaskFactory() - val showPublicKey = taskFactory.compose(classLoader.createSandboxFunction()).apply(ShowPublicKey::class.java) - val result = showPublicKey.apply(sandboxKey) ?: fail("Result cannot be null") + val taskFactory = classLoader.createRawTaskFactory().compose(classLoader.createSandboxFunction()) + val showPublicKey = taskFactory.apply(ShowPublicKey::class.java) + val result = showPublicKey.apply(sandboxData) ?: fail("Result cannot be null") assertEquals(ShowPublicKey().apply(compositeData), result.toString()) + + val sandboxKey = taskFactory.apply(GetPublicKey::class.java) + .apply(sandboxData) ?: fail("PublicKey cannot be null") + assertThat(sandboxKey::class.java.name) + .isEqualTo("sandbox." + CompositeKey::class.java.name) } } @@ -86,6 +92,12 @@ class DeserializePublicKeyTest : TestBase(KOTLIN) { } } } + + class GetPublicKey : Function { + override fun apply(data: PublicKeyData): PublicKey { + return data.key + } + } } @CordaSerializable