diff --git a/node/src/integration-test/kotlin/net/corda/contracts/djvm/crypto/DeterministicCryptoContract.kt b/node/src/integration-test/kotlin/net/corda/contracts/djvm/crypto/DeterministicCryptoContract.kt new file mode 100644 index 0000000000..f57f77fc1d --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/contracts/djvm/crypto/DeterministicCryptoContract.kt @@ -0,0 +1,39 @@ +package net.corda.contracts.djvm.crypto + +import net.corda.core.contracts.CommandData +import net.corda.core.contracts.Contract +import net.corda.core.contracts.ContractState +import net.corda.core.crypto.Crypto +import net.corda.core.identity.AbstractParty +import net.corda.core.transactions.LedgerTransaction +import net.corda.core.utilities.OpaqueBytes +import java.security.PublicKey + +class DeterministicCryptoContract : Contract { + override fun verify(tx: LedgerTransaction) { + val cryptoData = tx.outputsOfType(CryptoState::class.java) + val validators = tx.commandsOfType(Validate::class.java) + + val isValid = validators.all { validate -> + with (validate.value) { + cryptoData.all { crypto -> + Crypto.doVerify(schemeCodeName, publicKey, crypto.signature.bytes, crypto.original.bytes) + } + } + } + + if (cryptoData.isEmpty() || validators.isEmpty() || !isValid) { + throw IllegalStateException("Failed to validate signatures in command data") + } + } + + @Suppress("CanBeParameter", "MemberVisibilityCanBePrivate") + class CryptoState(val owner: AbstractParty, val original: OpaqueBytes, val signature: OpaqueBytes) : ContractState { + override val participants: List = listOf(owner) + } + + class Validate( + val schemeCodeName: String, + val publicKey: PublicKey + ) : CommandData +} \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/flows/djvm/crypto/DeterministicCryptoFlow.kt b/node/src/integration-test/kotlin/net/corda/flows/djvm/crypto/DeterministicCryptoFlow.kt new file mode 100644 index 0000000000..18fb73c380 --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/flows/djvm/crypto/DeterministicCryptoFlow.kt @@ -0,0 +1,32 @@ +package net.corda.flows.djvm.crypto + +import co.paralleluniverse.fibers.Suspendable +import net.corda.contracts.djvm.crypto.DeterministicCryptoContract +import net.corda.core.contracts.Command +import net.corda.core.contracts.CommandData +import net.corda.core.crypto.SecureHash +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.InitiatingFlow +import net.corda.core.flows.StartableByRPC +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.OpaqueBytes + +@InitiatingFlow +@StartableByRPC +class DeterministicCryptoFlow( + private val command: CommandData, + private val original: OpaqueBytes, + private val signature: OpaqueBytes +) : FlowLogic() { + @Suspendable + override fun call(): SecureHash { + val notary = serviceHub.networkMapCache.notaryIdentities[0] + val stx = serviceHub.signInitialTransaction( + TransactionBuilder(notary) + .addOutputState(DeterministicCryptoContract.CryptoState(ourIdentity, original, signature)) + .addCommand(Command(command, ourIdentity.owningKey)) + ) + stx.verify(serviceHub, checkSufficientSignatures = false) + return stx.id + } +} diff --git a/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractCryptoTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractCryptoTest.kt new file mode 100644 index 0000000000..108b3271fc --- /dev/null +++ b/node/src/integration-test/kotlin/net/corda/node/services/DeterministicContractCryptoTest.kt @@ -0,0 +1,68 @@ +package net.corda.node.services + +import net.corda.contracts.djvm.crypto.DeterministicCryptoContract.* +import net.corda.core.crypto.Crypto +import net.corda.core.crypto.Crypto.DEFAULT_SIGNATURE_SCHEME +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.flows.djvm.crypto.DeterministicCryptoFlow +import net.corda.node.DeterministicSourcesRule +import net.corda.testing.core.ALICE_NAME +import net.corda.testing.core.DUMMY_NOTARY_NAME +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.cordappsForPackages +import org.junit.ClassRule +import org.junit.Test +import org.junit.jupiter.api.assertDoesNotThrow +import java.security.KeyPairGenerator + +class DeterministicContractCryptoTest { + companion object { + const val MESSAGE = "Very Important Data! Do Not Change!" + val logger = loggerFor() + + @ClassRule + @JvmField + val djvmSources = DeterministicSourcesRule() + + fun parametersFor(djvmSources: DeterministicSourcesRule): DriverParameters { + return DriverParameters( + portAllocation = incrementalPortAllocation(), + startNodesInProcess = false, + notarySpecs = listOf(NotarySpec(DUMMY_NOTARY_NAME, validating = true)), + cordappsForAllNodes = cordappsForPackages( + "net.corda.contracts.djvm.crypto", + "net.corda.flows.djvm.crypto" + ), + djvmBootstrapSource = djvmSources.bootstrap, + djvmCordaSource = djvmSources.corda + ) + } + } + + @Test + fun `test DJVM can verify using crypto`() { + val keyPair = KeyPairGenerator.getInstance(DEFAULT_SIGNATURE_SCHEME.algorithmName).genKeyPair() + val importantData = OpaqueBytes(MESSAGE.toByteArray()) + val signature = OpaqueBytes(Crypto.doSign(DEFAULT_SIGNATURE_SCHEME, keyPair.`private`, importantData.bytes)) + + val validate = Validate( + schemeCodeName = DEFAULT_SIGNATURE_SCHEME.schemeCodeName, + publicKey = keyPair.`public` + ) + + driver(parametersFor(djvmSources)) { + val alice = startNode(providedName = ALICE_NAME).getOrThrow() + val txId = assertDoesNotThrow { + alice.rpc.startFlow(::DeterministicCryptoFlow, validate, importantData, signature) + .returnValue.getOrThrow() + } + logger.info("TX-ID: {}", txId) + } + } +}