Merge open/master

This commit is contained in:
Andras Slemmer 2017-03-13 11:52:18 +00:00
commit d52accb52c
2466 changed files with 54660 additions and 26330 deletions

4
.gitignore vendored
View File

@ -77,3 +77,7 @@ crashlytics-build.properties
# docs related
docs/virtualenv/
# bft-smart
node/bft-smart-config/currentView
node/config/currentView

View File

@ -1,11 +0,0 @@
<component name="ProjectDictionaryState">
<dictionary name="rossnicoll">
<words>
<w>Corda</w>
<w>Kryo</w>
<w>Quasar</w>
<w>blockchain</w>
<w>counterparty</w>
</words>
</dictionary>
</component>

View File

@ -1,4 +1,4 @@
Copyright 2016, R3 Limited.
Copyright 2016 - 2017, R3 Limited.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -55,6 +55,9 @@ To look at the Corda source and run some sample applications:
* [Documentation](https://docs.corda.net)
* [Slack channel] (https://slack.corda.net/)
* [Forum](https://discourse.corda.net)
* [Meetups](https://www.meetup.com/pro/corda/)
* [Training Course](https://www.corda.net/corda-training/)
## Development State
@ -77,7 +80,7 @@ Please read [here](./CONTRIBUTING.md).
## License
[Apache 2.0](./LICENCE)
[Apache 2.0](./LICENSE)
## Acknowledgements

View File

@ -4,16 +4,16 @@ buildscript {
file("publish.properties").withInputStream { props.load(it) }
// Our version: bump this on release.
ext.corda_version = "0.9-SNAPSHOT"
ext.corda_version = "0.10-SNAPSHOT"
ext.gradle_plugins_version = props.getProperty("gradlePluginsVersion")
// Dependency versions. Can run 'gradle dependencyUpdates' to find new versions of things.
//
// TODO: Sort this alphabetically.
ext.kotlin_version = '1.0.5-2'
ext.kotlin_version = '1.0.6'
ext.quasar_version = '0.7.6' // TODO: Upgrade to 0.7.7+ when Quasar bug 238 is resolved.
ext.asm_version = '0.5.3'
ext.artemis_version = '1.5.1'
ext.artemis_version = '1.5.3'
ext.jackson_version = '2.8.5'
ext.jetty_version = '9.3.9.v20160517'
ext.jersey_version = '2.25'
@ -24,12 +24,15 @@ buildscript {
ext.guava_version = '19.0'
ext.quickcheck_version = '0.7'
ext.okhttp_version = '3.5.0'
ext.netty_version = '4.1.5.Final'
ext.typesafe_config_version = '1.3.1'
ext.junit_version = '4.12'
ext.jopt_simple_version = '5.0.2'
ext.jansi_version = '1.14'
ext.hibernate_version = '5.2.6.Final'
ext.h2_version = '1.4.193'
ext.rxjava_version = '1.2.4'
ext.requery_version = '1.1.1'
ext.dokka_version = '0.9.13'
repositories {
@ -44,7 +47,9 @@ buildscript {
classpath "net.corda.plugins:quasar-utils:$gradle_plugins_version"
classpath "net.corda.plugins:cordformation:$gradle_plugins_version"
classpath 'com.github.ben-manes:gradle-versions-plugin:0.13.0'
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}"
classpath "org.ajoberstar:grgit:1.1.0"
}
}
@ -54,6 +59,10 @@ plugins {
id "us.kirchmeier.capsule" version "1.0.2"
}
ext {
corda_revision = org.ajoberstar.grgit.Grgit.open(file('.')).head().id
}
apply plugin: 'kotlin'
apply plugin: 'project-report'
apply plugin: 'com.github.ben-manes.versions'
@ -84,6 +93,10 @@ allprojects {
group 'net.corda'
version "$corda_version"
repositories {
maven { url 'https://jitpack.io' }
}
}
// Check that we are running on a Java 8 JDK. The source/targetCompatibility values above aren't sufficient to
@ -107,6 +120,7 @@ dependencies {
compile project(':node')
compile "com.google.guava:guava:$guava_version"
runtime project(path: ":node:capsule", configuration: 'runtimeArtifacts')
runtime project(path: ":node:webserver", configuration: 'runtimeArtifacts')
}
task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) {
@ -172,7 +186,7 @@ bintrayConfig {
projectUrl = 'https://github.com/corda/corda'
gpgSign = true
gpgPassphrase = System.getenv('CORDA_BINTRAY_GPG_PASSPHRASE')
publications = ['client', 'core', 'corda', 'finance', 'node', 'test-utils']
publications = ['client', 'core', 'corda', 'corda-webserver', 'finance', 'node', 'node-schemas', 'test-utils', 'jackson']
license {
name = 'Apache-2.0'
url = 'https://www.apache.org/licenses/LICENSE-2.0'
@ -191,7 +205,7 @@ dokka {
moduleName = 'corda'
outputDirectory = 'docs/build/html/api/kotlin'
processConfigurations = ['compile']
sourceDirs = files('core/src/main/kotlin', 'client/src/main/kotlin', 'node/src/main/kotlin', 'finance/src/main/kotlin')
sourceDirs = files('core/src/main/kotlin', 'client/src/main/kotlin', 'node/src/main/kotlin', 'finance/src/main/kotlin', 'client/jackson/src/main/kotlin')
}
task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
@ -199,7 +213,7 @@ task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
outputFormat = "javadoc"
outputDirectory = 'docs/build/html/api/javadoc'
processConfigurations = ['compile']
sourceDirs = files('core/src/main/kotlin', 'client/src/main/kotlin', 'node/src/main/kotlin', 'finance/src/main/kotlin')
sourceDirs = files('core/src/main/kotlin', 'client/src/main/kotlin', 'node/src/main/kotlin', 'finance/src/main/kotlin', 'client/jackson/src/main/kotlin')
}
task apidocs(dependsOn: ['dokka', 'dokkaJavadoc'])

View File

@ -0,0 +1,19 @@
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.publish-utils'
repositories {
mavenLocal()
mavenCentral()
jcenter()
maven {
url 'https://dl.bintray.com/kotlin/exposed'
}
}
dependencies {
compile project(':core')
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile ("com.fasterxml.jackson.module:jackson-module-kotlin:${jackson_version}")
testCompile "junit:junit:$junit_version"
}

View File

@ -1,4 +1,4 @@
package net.corda.node.utilities
package net.corda.jackson
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParseException
@ -13,10 +13,10 @@ import net.corda.core.contracts.BusinessCalendar
import net.corda.core.crypto.*
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.IdentityService
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.i2p.crypto.eddsa.EdDSAPublicKey
import net.corda.core.node.services.IdentityService
import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalDateTime
@ -25,21 +25,27 @@ import java.time.LocalDateTime
* Utilities and serialisers for working with JSON representations of basic types. This adds Jackson support for
* the java.time API, some core types, and Kotlin data classes.
*
* TODO: This does not belong in node. It should be moved to the client module or a dedicated webserver module.
* Note that Jackson can also be used to serialise/deserialise other formats such as Yaml and XML.
*/
object JsonSupport {
object JacksonSupport {
// If you change this API please update the docs in the docsite (json.rst)
interface PartyObjectMapper {
fun partyFromName(partyName: String): Party?
fun partyFromKey(owningKey: CompositeKey): Party?
}
class RpcObjectMapper(val rpc: CordaRPCOps) : PartyObjectMapper, ObjectMapper() {
override fun partyFromName(partyName: String): Party? = rpc.partyFromName(partyName)
override fun partyFromKey(owningKey: CompositeKey): Party? = rpc.partyFromKey(owningKey)
}
class IdentityObjectMapper(val identityService: IdentityService) : PartyObjectMapper, ObjectMapper(){
override fun partyFromName(partyName: String) = identityService.partyFromName(partyName)
override fun partyFromName(partyName: String): Party? = identityService.partyFromName(partyName)
override fun partyFromKey(owningKey: CompositeKey): Party? = identityService.partyFromKey(owningKey)
}
class NoPartyObjectMapper: PartyObjectMapper, ObjectMapper() {
override fun partyFromName(partyName: String) = throw UnsupportedOperationException()
override fun partyFromName(partyName: String): Party? = throw UnsupportedOperationException()
override fun partyFromKey(owningKey: CompositeKey): Party? = throw UnsupportedOperationException()
}
val javaTimeModule: Module by lazy {
@ -53,6 +59,8 @@ object JsonSupport {
val cordaModule: Module by lazy {
SimpleModule("core").apply {
addSerializer(AnonymousParty::class.java, AnonymousPartySerializer)
addDeserializer(AnonymousParty::class.java, AnonymousPartyDeserializer)
addSerializer(Party::class.java, PartySerializer)
addDeserializer(Party::class.java, PartyDeserializer)
addSerializer(BigDecimal::class.java, ToStringSerializer)
@ -78,13 +86,16 @@ object JsonSupport {
}
}
/* Mapper requiring RPC support to deserialise parties from names */
/** Mapper requiring RPC support to deserialise parties from names */
@JvmStatic
fun createDefaultMapper(rpc: CordaRPCOps): ObjectMapper = configureMapper(RpcObjectMapper(rpc))
/* For testing or situations where deserialising parties is not required */
/** For testing or situations where deserialising parties is not required */
@JvmStatic
fun createNonRpcMapper(): ObjectMapper = configureMapper(NoPartyObjectMapper())
/* For testing with an in memory identity service */
/** For testing with an in memory identity service */
@JvmStatic
fun createInMemoryMapper(identityService: IdentityService) = configureMapper(IdentityObjectMapper(identityService))
private fun configureMapper(mapper: ObjectMapper): ObjectMapper = mapper.apply {
@ -120,6 +131,25 @@ object JsonSupport {
}
object AnonymousPartySerializer : JsonSerializer<AnonymousParty>() {
override fun serialize(obj: AnonymousParty, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.owningKey.toBase58String())
}
}
object AnonymousPartyDeserializer : JsonDeserializer<AnonymousParty>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): AnonymousParty {
if (parser.currentToken == JsonToken.FIELD_NAME) {
parser.nextToken()
}
val mapper = parser.codec as PartyObjectMapper
// TODO this needs to use some industry identifier(s) instead of these keys
val key = CompositeKey.parseFromBase58(parser.text)
return AnonymousParty(key)
}
}
object PartySerializer : JsonSerializer<Party>() {
override fun serialize(obj: Party, generator: JsonGenerator, provider: SerializerProvider) {
generator.writeString(obj.name)
@ -224,3 +254,4 @@ object JsonSupport {
}
}
}

View File

@ -1,21 +1,18 @@
package net.corda.node
package net.corda.jackson
import com.pholser.junit.quickcheck.From
import com.pholser.junit.quickcheck.Property
import com.pholser.junit.quickcheck.runner.JUnitQuickcheck
import net.corda.core.testing.PublicKeyGenerator
import net.corda.node.utilities.JsonSupport
import net.corda.testing.node.MockIdentityService
import net.i2p.crypto.eddsa.EdDSAPublicKey
import org.junit.runner.RunWith
import java.security.PublicKey
import kotlin.test.assertEquals
@RunWith(JUnitQuickcheck::class)
class JsonSupportTest {
class JacksonSupportTest {
companion object {
val mapper = JsonSupport.createNonRpcMapper()
val mapper = JacksonSupport.createNonRpcMapper()
}
@Property

View File

@ -176,14 +176,16 @@ fun <A : Any> Generator.Companion.replicatePoisson(meanSize: Double, generator:
fun <A : Any> Generator.Companion.pickOne(list: List<A>) = Generator.intRange(0, list.size - 1).map { list[it] }
fun <A : Any> Generator.Companion.pickN(number: Int, list: List<A>) = Generator<List<A>> {
val mask = BitSet(list.size)
for (i in 0..Math.min(list.size, number) - 1) {
mask[i] = 1
val size = Math.min(list.size, number)
for (i in 0..size - 1) {
// mask[i] = 1 desugars into mask.set(i, 1), which sets a range instead of a bit
mask[i] = true
}
for (i in 0..mask.size() - 1) {
val byte = mask[i]
val swapIndex = i + it.nextInt(mask.size() - i)
for (i in 0..list.size - 1) {
val bit = mask[i]
val swapIndex = i + it.nextInt(size - i)
mask[i] = mask[swapIndex]
mask[swapIndex] = byte
mask[swapIndex] = bit
}
val resultList = ArrayList<A>()
list.forEachIndexed { index, a ->

View File

@ -40,6 +40,7 @@ class NetworkIdentityModel {
return advertisedServices.any { it.info.type == NetworkMapService.type || it.info.type.isNotary() }
}
// TODO: Use Identity Service in service hub instead?
fun lookup(compositeKey: CompositeKey): ObservableValue<NodeInfo?> = parties.firstOrDefault(notaries.firstOrNullObservable { it.notaryIdentity.owningKey == compositeKey }) {
it.legalIdentity.owningKey == compositeKey
}

View File

@ -4,7 +4,7 @@ keyStorePassword : "cordacadevpass"
trustStorePassword : "trustpass"
artemisAddress : "localhost:31337"
webAddress : "localhost:31339"
extraAdvertisedServiceIds: "corda.interest_rates"
extraAdvertisedServiceIds : [ "corda.interest_rates" ]
networkMapService : {
address : "localhost:12345"
legalName : "Network Map Service"

View File

@ -4,7 +4,7 @@ keyStorePassword : "cordacadevpass"
trustStorePassword : "trustpass"
artemisAddress : "localhost:31338"
webAddress : "localhost:31340"
extraAdvertisedServiceIds: "corda.interest_rates"
extraAdvertisedServiceIds : [ "corda.interest_rates" ]
networkMapService : {
address : "localhost:12345"
legalName : "Network Map Service"

View File

@ -6,17 +6,14 @@
<Property name="log-name">node-${hostName}</Property>
<Property name="archive">${sys:log-path}/archive</Property>
<Property name="consoleLogLevel">error</Property>
<Property name="defaultLogLevel">info</Property>
</Properties>
<ThresholdFilter level="trace"/>
<Appenders>
<Console name="Console-Appender" target="SYSTEM_OUT">
<PatternLayout>
<pattern>
%highlight{%level{length=1} %d{HH:mm:ss} %T %c{1}.%M - %msg%n}{INFO=white,WARN=red,FATAL=bright red blink}
</pattern>>
</PatternLayout>
<PatternLayout pattern="%highlight{%level{length=1} %d{HH:mm:ss} [%t] %c{2}.%M - %msg%n}{INFO=white,WARN=red,FATAL=bright red}" />
</Console>
<!-- Will generate up to 10 log files for a given day. During every rollover it will delete
@ -25,7 +22,7 @@
fileName="${sys:log-path}/${log-name}.log"
filePattern="${archive}/${log-name}.%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="[%-5level] %d{ISO8601}{GMT+0} [%t] %c{1} - %msg%n"/>
<PatternLayout pattern="[%-5level] %d{ISO8601}{GMT+0} [%t] %c{2}.%M - %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
@ -47,13 +44,9 @@
</Appenders>
<Loggers>
<Root level="info">
<Root level="${sys:defaultLogLevel}">
<AppenderRef ref="Console-Appender" level="${sys:consoleLogLevel}"/>
<AppenderRef ref="RollingFile-Appender" level="info"/>
<AppenderRef ref="RollingFile-Appender" />
</Root>
<Logger name="net.corda" level="info" additivity="false">
<AppenderRef ref="Console-Appender" level="${sys:consoleLogLevel}"/>
<AppenderRef ref="RollingFile-Appender"/>
</Logger>
</Loggers>
</Configuration>

View File

@ -4,5 +4,5 @@ keyStorePassword : "cordacadevpass"
trustStorePassword : "trustpass"
artemisAddress : "localhost:12345"
webAddress : "localhost:12346"
extraAdvertisedServiceIds: "corda.notary.validating"
extraAdvertisedServiceIds : [ "corda.notary.validating" ]
useHTTPS : false

View File

@ -1,19 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info">
<Properties>
<Property name="defaultLogLevel">info</Property>
</Properties>
<Appenders>
<Console name="Console-Appender" target="SYSTEM_OUT">
<PatternLayout>
<pattern>
[%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n
</pattern>>
</PatternLayout>
<PatternLayout pattern="[%-5level] %d{HH:mm:ss.SSS} [%t] %c{2}.%M - %msg%n" />
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console-Appender"/>
</Root>
<Logger name="net.corda" level="info" additivity="false">
<Logger name="net.corda" level="${sys:defaultLogLevel}" additivity="false">
<AppenderRef ref="Console-Appender"/>
</Logger>
</Loggers>

View File

@ -31,6 +31,7 @@ sourceSets {
}
dependencies {
testCompile "junit:junit:$junit_version"
testCompile "commons-fileupload:commons-fileupload:1.3.2"
@ -56,6 +57,9 @@ dependencies {
// AssertJ: for fluent assertions for testing
testCompile "org.assertj:assertj-core:${assertj_version}"
// TODO: Upgrade to junit-quickcheck 0.8, once it is released,
// because it depends on org.javassist:javassist instead
// of javassist:javassist.
compile "com.pholser:junit-quickcheck-core:$quickcheck_version"
compile "com.pholser:junit-quickcheck-generators:$quickcheck_version"
@ -63,7 +67,7 @@ dependencies {
compile "com.google.guava:guava:$guava_version"
// RxJava: observable streams of events.
compile "io.reactivex:rxjava:1.2.4"
compile "io.reactivex:rxjava:$rxjava_version"
// Kryo: object graph serialization.
compile "com.esotericsoftware:kryo:4.0.0"
@ -88,4 +92,7 @@ dependencies {
// RS API: Response type and codes for ApiUtils.
compile "javax.ws.rs:javax.ws.rs-api:2.0"
// Requery: SQL based query & persistence for Kotlin
compile "io.requery:requery-kotlin:$requery_version"
}

View File

@ -9,6 +9,7 @@ import com.google.common.io.ByteStreams
import com.google.common.util.concurrent.*
import kotlinx.support.jdk7.use
import net.corda.core.crypto.newSecureRandom
import net.corda.core.serialization.CordaSerializable
import org.slf4j.Logger
import rx.Observable
import rx.Observer
@ -145,6 +146,7 @@ inline fun Path.write(createDirs: Boolean = false, vararg options: OpenOption =
}
inline fun <R> Path.readLines(charset: Charset = UTF_8, block: (Stream<String>) -> R): R = Files.lines(this, charset).use(block)
fun Path.readAllLines(charset: Charset = UTF_8): List<String> = Files.readAllLines(this, charset)
fun Path.writeLines(lines: Iterable<CharSequence>, charset: Charset = UTF_8, vararg options: OpenOption): Path = Files.write(this, lines, charset, *options)
fun InputStream.copyTo(target: Path, vararg options: CopyOption): Long = Files.copy(this, target, *options)
@ -203,7 +205,7 @@ inline fun elapsedTime(block: () -> Unit): Duration {
val start = System.nanoTime()
block()
val end = System.nanoTime()
return Duration.ofNanos(end-start)
return Duration.ofNanos(end - start)
}
// TODO: Add inline back when a new Kotlin version is released and check if the java.lang.VerifyError
@ -257,6 +259,7 @@ class ThreadBox<out T>(val content: T, val lock: ReentrantLock = ReentrantLock()
*
* We avoid the use of the word transient here to hopefully reduce confusion with the term in relation to (Java) serialization.
*/
@CordaSerializable
abstract class RetryableException(message: String) : Exception(message)
/**
@ -278,13 +281,16 @@ class TransientProperty<out T>(private val initializer: () -> T) {
/**
* Given a path to a zip file, extracts it to the given directory.
*/
fun extractZipFile(zipFile: Path, toDirectory: Path) {
val normalisedDirectory = toDirectory.normalize().createDirectories()
fun extractZipFile(zipFile: Path, toDirectory: Path) = extractZipFile(Files.newInputStream(zipFile), toDirectory)
zipFile.read {
val zip = ZipInputStream(BufferedInputStream(it))
/**
* Given a zip file input stream, extracts it to the given directory.
*/
fun extractZipFile(inputStream: InputStream, toDirectory: Path) {
val normalisedDirectory = toDirectory.normalize().createDirectories()
ZipInputStream(BufferedInputStream(inputStream)).use {
while (true) {
val e = zip.nextEntry ?: break
val e = it.nextEntry ?: break
val outPath = (normalisedDirectory / e.name).normalize()
// Security checks: we should reject a zip that contains tricksy paths that try to escape toDirectory.
@ -295,9 +301,9 @@ fun extractZipFile(zipFile: Path, toDirectory: Path) {
continue
}
outPath.write { out ->
ByteStreams.copy(zip, out)
ByteStreams.copy(it, out)
}
zip.closeEntry()
it.closeEntry()
}
}
}
@ -307,6 +313,7 @@ fun extractZipFile(zipFile: Path, toDirectory: Path) {
val Throwable.rootCause: Throwable get() = Throwables.getRootCause(this)
/** Representation of an operation that may have thrown an error. */
@CordaSerializable
data class ErrorOr<out A> private constructor(val value: A?, val error: Throwable?) {
// The ErrorOr holds a value iff error == null
constructor(value: A) : this(value, null)
@ -391,15 +398,24 @@ private class ObservableToFuture<T>(observable: Observable<T>) : AbstractFuture<
override fun onNext(value: T) {
set(value)
}
override fun onError(e: Throwable) {
setException(e)
}
override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
subscription.unsubscribe()
return super.cancel(mayInterruptIfRunning)
}
override fun onCompleted() {}
}
/** Return the sum of an Iterable of [BigDecimal]s. */
fun Iterable<BigDecimal>.sum(): BigDecimal = fold(BigDecimal.ZERO) { a, b -> a + b }
fun codePointsString(vararg codePoints: Int): String {
val builder = StringBuilder()
codePoints.forEach { builder.append(Character.toChars(it)) }
return builder.toString()
}

View File

@ -12,7 +12,7 @@ val DUMMY_V2_PROGRAM_ID = DummyContractV2()
* Dummy contract state for testing of the upgrade process.
*/
class DummyContractV2 : UpgradedContract<DummyContract.State, DummyContractV2.State> {
override val legacyContract = DUMMY_PROGRAM_ID
override val legacyContract = DummyContract::class.java
data class State(val magicNumber: Int = 0, val owners: List<CompositeKey>) : ContractState {
override val contract = DUMMY_V2_PROGRAM_ID

View File

@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.google.common.annotations.VisibleForTesting
import net.corda.core.serialization.CordaSerializable
import java.math.BigDecimal
import java.math.BigInteger
import java.time.DayOfWeek
@ -34,6 +35,7 @@ import java.util.*
*
* @param T the type of the token, for example [Currency].
*/
@CordaSerializable
data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> {
companion object {
/**
@ -108,12 +110,14 @@ fun <T> Iterable<Amount<T>>.sumOrZero(currency: T) = if (iterator().hasNext()) s
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/** A [FixOf] identifies the question side of a fix: what day, tenor and type of fix ("LIBOR", "EURIBOR" etc) */
@CordaSerializable
data class FixOf(val name: String, val forDay: LocalDate, val ofTenor: Tenor)
/** A [Fix] represents a named interest rate, on a given day, for a given duration. It can be embedded in a tx. */
data class Fix(val of: FixOf, val value: BigDecimal) : CommandData
/** Represents a textual expression of e.g. a formula */
@CordaSerializable
@JsonDeserialize(using = ExpressionDeserializer::class)
@JsonSerialize(using = ExpressionSerializer::class)
data class Expression(val expr: String)
@ -131,6 +135,7 @@ object ExpressionDeserializer : JsonDeserializer<Expression>() {
}
/** Placeholder class for the Tenor datatype - which is a standardised duration of time until maturity */
@CordaSerializable
data class Tenor(val name: String) {
private val amount: Int
private val unit: TimeUnit
@ -166,6 +171,7 @@ data class Tenor(val name: String) {
override fun toString(): String = name
@CordaSerializable
enum class TimeUnit(val code: String) {
Day("D"), Week("W"), Month("M"), Year("Y")
}
@ -175,6 +181,7 @@ data class Tenor(val name: String) {
* Simple enum for returning accurals adjusted or unadjusted.
* We don't actually do anything with this yet though, so it's ignored for now.
*/
@CordaSerializable
enum class AccrualAdjustment {
Adjusted, Unadjusted
}
@ -183,6 +190,7 @@ enum class AccrualAdjustment {
* This is utilised in the [DateRollConvention] class to determine which way we should initially step when
* finding a business day.
*/
@CordaSerializable
enum class DateRollDirection(val value: Long) { FORWARD(1), BACKWARD(-1) }
/**
@ -190,6 +198,7 @@ enum class DateRollDirection(val value: Long) { FORWARD(1), BACKWARD(-1) }
* Depending on the accounting requirement, we can move forward until we get to a business day, or backwards.
* There are some additional rules which are explained in the individual cases below.
*/
@CordaSerializable
enum class DateRollConvention {
// direction() cannot be a val due to the throw in the Actual instance
@ -235,6 +244,7 @@ enum class DateRollConvention {
* Note that the first character cannot be a number (enum naming constraints), so we drop that
* in the toString lest some people get confused.
*/
@CordaSerializable
enum class DayCountBasisDay {
// We have to prefix 30 etc with a letter due to enum naming constraints.
D30,
@ -246,6 +256,7 @@ enum class DayCountBasisDay {
}
/** This forms the year part of the "Day Count Basis" used for interest calculation. */
@CordaSerializable
enum class DayCountBasisYear {
// Ditto above comment for years.
Y360,
@ -257,6 +268,7 @@ enum class DayCountBasisYear {
}
/** Whether the payment should be made before the due date, or after it. */
@CordaSerializable
enum class PaymentRule {
InAdvance, InArrears,
}
@ -266,6 +278,7 @@ enum class PaymentRule {
* that would divide into (eg annually = 1, semiannual = 2, monthly = 12 etc).
*/
@Suppress("unused") // TODO: Revisit post-Vega and see if annualCompoundCount is still needed.
@CordaSerializable
enum class Frequency(val annualCompoundCount: Int) {
Annual(1) {
override fun offset(d: LocalDate, n: Long) = d.plusYears(1 * n)
@ -304,7 +317,9 @@ fun LocalDate.isWorkingDay(accordingToCalendar: BusinessCalendar): Boolean = acc
* typical feature of financial contracts, in which a business may not want a payment event to fall on a day when
* no staff are around to handle problems.
*/
@CordaSerializable
open class BusinessCalendar private constructor(val holidayDates: List<LocalDate>) {
@CordaSerializable
class UnknownCalendar(name: String) : Exception("$name not found")
companion object {
@ -434,6 +449,7 @@ fun calculateDaysBetween(startDate: LocalDate,
* Enum for the types of netting that can be applied to state objects. Exact behaviour
* for each type of netting is left to the contract to determine.
*/
@CordaSerializable
enum class NetType {
/**
* Close-out netting applies where one party is bankrupt or otherwise defaults (exact terms are contract specific),
@ -461,6 +477,7 @@ enum class NetType {
* @param defaultFractionDigits the number of digits normally after the decimal point when referring to quantities of
* this commodity.
*/
@CordaSerializable
data class Commodity(val commodityCode: String,
val displayName: String,
val defaultFractionDigits: Int = 0) {
@ -487,6 +504,7 @@ data class Commodity(val commodityCode: String,
* So that the first time a state is issued this should be given a new UUID.
* Subsequent copies and evolutions of a state should just copy the [externalId] and [id] fields unmodified.
*/
@CordaSerializable
data class UniqueIdentifier(val externalId: String? = null, val id: UUID = UUID.randomUUID()) : Comparable<UniqueIdentifier> {
override fun toString(): String = if (externalId != null) "${externalId}_$id" else id.toString()

View File

@ -8,6 +8,7 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogicRef
import net.corda.core.flows.FlowLogicRefFactory
import net.corda.core.node.services.ServiceType
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.serialization.serialize
import net.corda.core.transactions.TransactionBuilder
@ -62,6 +63,7 @@ interface NettableState<N : BilateralNettableState<N>, T : Any> : BilateralNetta
* notary is responsible for ensuring there is no "double spending" by only signing a transaction if the input states
* are all free.
*/
@CordaSerializable
interface ContractState {
/**
* An instance of the contract class that will verify this state.
@ -121,6 +123,7 @@ interface ContractState {
* A wrapper for [ContractState] containing additional platform-level state information.
* This is the definitive state that is stored on the ledger and used in transaction outputs.
*/
@CordaSerializable
data class TransactionState<out T : ContractState> @JvmOverloads constructor(
/** The custom contract state */
val data: T,
@ -169,6 +172,7 @@ interface IssuanceDefinition
*
* @param P the type of product underlying the definition, for example [Currency].
*/
@CordaSerializable
data class Issued<out P>(val issuer: PartyAndReference, val product: P) {
override fun toString() = "$product issued by $issuer"
}
@ -239,6 +243,7 @@ interface LinearState : ContractState {
/**
* Standard clause to verify the LinearState safety properties.
*/
@CordaSerializable
class ClauseVerifier<S : LinearState, C : CommandData>() : Clause<S, C, Unit>() {
override fun verify(tx: TransactionForContract,
inputs: List<S>,
@ -290,7 +295,7 @@ interface DealState : LinearState {
* separate process exchange certificates to ascertain identities. Thus decoupling identities from
* [ContractState]s.
* */
val parties: List<Party>
val parties: List<AnonymousParty>
/**
* Generate a partial transaction representing an agreement (command) to this deal, allowing a general
@ -334,11 +339,13 @@ fun ContractState.hash(): SecureHash = SecureHash.sha256(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.
*/
@CordaSerializable
data class StateRef(val txhash: SecureHash, val index: Int) {
override fun toString() = "$txhash($index)"
}
/** A StateAndRef is simply a (state, ref) pair. For instance, a vault (which holds available assets) contains these. */
@CordaSerializable
data class StateAndRef<out T : ContractState>(val state: TransactionState<T>, val ref: StateRef)
/** Filters a list of [StateAndRef] objects according to the type of the states */
@ -350,11 +357,14 @@ inline fun <reified T : ContractState> Iterable<StateAndRef<ContractState>>.filt
* Reference to something being stored or issued by a party e.g. in a vault or (more likely) on their normal
* ledger. The reference is intended to be encrypted so it's meaningless to anyone other than the party.
*/
@CordaSerializable
data class PartyAndReference(val party: AnonymousParty, val reference: OpaqueBytes) {
constructor(party: Party, reference: OpaqueBytes) : this(party.toAnonymous(), reference)
override fun toString() = "${party}$reference"
}
/** Marker interface for classes that represent commands */
@CordaSerializable
interface CommandData
/** Commands that inherit from this are intended to have no data items: it's only their presence that matters. */
@ -364,6 +374,7 @@ abstract class TypeOnlyCommandData : CommandData {
}
/** Command data/content plus pubkey pair: the signature is stored at the end of the serialized bytes */
@CordaSerializable
data class Command(val value: CommandData, val signers: List<CompositeKey>) {
init {
require(signers.isNotEmpty())
@ -398,9 +409,10 @@ interface NetCommand : CommandData {
}
/** Indicates that this transaction replaces the inputs contract state to another contract state */
data class UpgradeCommand(val upgradedContractClass: Class<UpgradedContract<*, *>>) : CommandData
data class UpgradeCommand(val upgradedContractClass: Class<out UpgradedContract<*, *>>) : CommandData
/** Wraps an object that was signed by a public key, which may be a well known/recognised institutional key. */
@CordaSerializable
data class AuthenticatedObject<out T : Any>(
val signers: List<CompositeKey>,
/** If any public keys were recognised, the looked up institutions are available here */
@ -412,6 +424,7 @@ data class AuthenticatedObject<out T : Any>(
* If present in a transaction, contains a time that was verified by the uniqueness service. The true time must be
* between (after, before).
*/
@CordaSerializable
data class Timestamp(val after: Instant?, val before: Instant?) {
init {
if (after == null && before == null)
@ -430,7 +443,10 @@ data class Timestamp(val after: Instant?, val before: Instant?) {
* every [LedgerTransaction] they see on the network, for every input and output state. All contracts must accept the
* transaction for it to be accepted: failure of any aborts the entire thing. The time is taken from a trusted
* timestamp attached to the transaction itself i.e. it is NOT necessarily the current time.
*
* TODO: Contract serialization is likely to change, so the annotation is likely temporary.
*/
@CordaSerializable
interface Contract {
/**
* Takes an object that represents a state transition, and ensures the inputs/outputs/commands make sense.
@ -456,7 +472,7 @@ interface Contract {
* @param NewState the upgraded contract state.
*/
interface UpgradedContract<in OldState : ContractState, out NewState : ContractState> : Contract {
val legacyContract: Contract
val legacyContract: Class<out Contract>
/**
* Upgrade contract's state object to a new state object.
*

View File

@ -2,10 +2,12 @@ package net.corda.core.contracts
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
/** Defines transaction build & validation logic for a specific transaction type */
@CordaSerializable
sealed class TransactionType {
override fun equals(other: Any?) = other?.javaClass == javaClass
override fun hashCode() = javaClass.name.hashCode()

View File

@ -4,6 +4,7 @@ import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowException
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.LedgerTransaction
import java.util.*
@ -94,6 +95,7 @@ class AttachmentResolutionException(val hash : SecureHash) : FlowException() {
override fun toString(): String = "Attachment resolution failure for $hash"
}
@CordaSerializable
class TransactionConflictException(val conflictRef: StateRef, val tx1: LedgerTransaction, val tx2: LedgerTransaction) : Exception()
sealed class TransactionVerificationException(val tx: LedgerTransaction, cause: Throwable?) : FlowException(cause) {
@ -116,6 +118,8 @@ sealed class TransactionVerificationException(val tx: LedgerTransaction, cause:
class TransactionMissingEncumbranceException(tx: LedgerTransaction, val missing: Int, val inOut: Direction) : TransactionVerificationException(tx, null) {
override val message: String get() = "Missing required encumbrance $missing in $inOut"
}
@CordaSerializable
enum class Direction {
INPUT,
OUTPUT

View File

@ -0,0 +1,25 @@
package net.corda.core.crypto
import net.corda.core.contracts.PartyAndReference
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.OpaqueBytes
import java.security.PublicKey
/**
* An [AbstractParty] contains the common elements of [Party] and [AnonymousParty], specifically the owning key of
* the party. In most cases [Party] or [AnonymousParty] should be used, depending on use-case.
*/
@CordaSerializable
abstract class AbstractParty(val owningKey: CompositeKey) {
/** A helper constructor that converts the given [PublicKey] in to a [CompositeKey] with a single node */
constructor(owningKey: PublicKey) : this(owningKey.composite)
/** Anonymised parties do not include any detail apart from owning key, so equality is dependent solely on the key */
override fun equals(other: Any?): Boolean = other is AbstractParty && this.owningKey == other.owningKey
override fun hashCode(): Int = owningKey.hashCode()
abstract fun toAnonymous() : AnonymousParty
abstract fun nameOrNull() : String?
abstract fun ref(bytes: OpaqueBytes): PartyAndReference
fun ref(vararg bytes: Byte) = ref(OpaqueBytes.of(*bytes))
}

View File

@ -8,17 +8,16 @@ import java.security.PublicKey
* The [AnonymousParty] class contains enough information to uniquely identify a [Party] while excluding private
* information such as name. It is intended to represent a party on the distributed ledger.
*/
open class AnonymousParty(val owningKey: CompositeKey) {
class AnonymousParty(owningKey: CompositeKey) : AbstractParty(owningKey) {
/** A helper constructor that converts the given [PublicKey] in to a [CompositeKey] with a single node */
constructor(owningKey: PublicKey) : this(owningKey.composite)
/** Anonymised parties do not include any detail apart from owning key, so equality is dependent solely on the key */
override fun equals(other: Any?): Boolean = other is AnonymousParty && this.owningKey == other.owningKey
override fun hashCode(): Int = owningKey.hashCode()
// Use the key as the bulk of the toString(), but include a human readable identifier as well, so that [Party]
// can put in the key and actual name
override fun toString() = "${owningKey.toBase58String()} <Anonymous>"
fun ref(bytes: OpaqueBytes) = PartyAndReference(this, bytes)
fun ref(vararg bytes: Byte) = ref(OpaqueBytes.of(*bytes))
override fun nameOrNull(): String? = null
override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this, bytes)
override fun toAnonymous() = this
}

View File

@ -2,6 +2,7 @@ package net.corda.core.crypto
import net.corda.core.crypto.CompositeKey.Leaf
import net.corda.core.crypto.CompositeKey.Node
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import java.security.PublicKey
@ -19,6 +20,7 @@ import java.security.PublicKey
* Using these constructs we can express e.g. 1 of N (OR) or N of N (AND) signature requirements. By nesting we can
* create multi-level requirements such as *"either the CEO or 3 of 5 of his assistants need to sign"*.
*/
@CordaSerializable
sealed class CompositeKey {
/** Checks whether [keys] match a sufficient amount of leaf nodes */
abstract fun isFulfilledBy(keys: Iterable<PublicKey>): Boolean

View File

@ -0,0 +1,421 @@
package net.corda.core.crypto
import net.i2p.crypto.eddsa.EdDSAEngine
import net.i2p.crypto.eddsa.EdDSAKey
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.interfaces.ECKey
import org.bouncycastle.pqc.jcajce.spec.SPHINCS256KeyGenParameterSpec
import java.security.*
import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
/**
* This object controls and provides the available and supported signature schemes for Corda.
* Any implemented [SignatureScheme] should be strictly defined here.
* However, only the schemes returned by {@link #listSupportedSignatureSchemes()} are supported.
* Note that Corda currently supports the following signature schemes by their code names:
* <p><ul>
* <li>RSA_SHA256 (RSA using SHA256 as hash algorithm and MGF1 (with SHA256) as mask generation function).
* <li>ECDSA_SECP256K1_SHA256 (ECDSA using the secp256k1 Koblitz curve and SHA256 as hash algorithm).
* <li>ECDSA_SECP256R1_SHA256 (ECDSA using the secp256r1 (NIST P-256) curve and SHA256 as hash algorithm).
* <li>EDDSA_ED25519_SHA512 (EdDSA using the ed255519 twisted Edwards curve and SHA512 as hash algorithm).
* <li>SPHINCS256_SHA512 (SPHINCS-256 hash-based signature scheme using SHA512 as hash algorithm).
* </ul>
*/
object Crypto {
/**
* RSA_SHA256 signature scheme using SHA256 as hash algorithm and MGF1 (with SHA256) as mask generation function.
* Note: Recommended key size >= 3072 bits.
*/
private val RSA_SHA256 = SignatureScheme(
1,
"RSA_SHA256",
"RSA",
Signature.getInstance("SHA256WITHRSAANDMGF1", "BC"),
KeyFactory.getInstance("RSA", "BC"),
KeyPairGenerator.getInstance("RSA", "BC"),
null,
3072,
"RSA_SHA256 signature scheme using SHA256 as hash algorithm and MGF1 (with SHA256) as mask generation function."
)
/** ECDSA signature scheme using the secp256k1 Koblitz curve. */
private val ECDSA_SECP256K1_SHA256 = SignatureScheme(
2,
"ECDSA_SECP256K1_SHA256",
"ECDSA",
Signature.getInstance("SHA256withECDSA", "BC"),
KeyFactory.getInstance("ECDSA", "BC"),
KeyPairGenerator.getInstance("ECDSA", "BC"),
ECNamedCurveTable.getParameterSpec("secp256k1"),
256,
"ECDSA signature scheme using the secp256k1 Koblitz curve."
)
/** ECDSA signature scheme using the secp256r1 (NIST P-256) curve. */
private val ECDSA_SECP256R1_SHA256 = SignatureScheme(
3,
"ECDSA_SECP256R1_SHA256",
"ECDSA",
Signature.getInstance("SHA256withECDSA", "BC"),
KeyFactory.getInstance("ECDSA", "BC"),
KeyPairGenerator.getInstance("ECDSA", "BC"),
ECNamedCurveTable.getParameterSpec("secp256r1"),
256,
"ECDSA signature scheme using the secp256r1 (NIST P-256) curve."
)
/** EdDSA signature scheme using the ed255519 twisted Edwards curve. */
private val EDDSA_ED25519_SHA512 = SignatureScheme(
4,
"EDDSA_ED25519_SHA512",
"EdDSA",
EdDSAEngine(),
EdDSAKeyFactory(),
net.i2p.crypto.eddsa.KeyPairGenerator(), // EdDSA engine uses a custom KeyPairGenerator Vs BouncyCastle.
EdDSANamedCurveTable.getByName("ed25519-sha-512"),
256,
"EdDSA signature scheme using the ed25519 twisted Edwards curve."
)
/**
* SPHINCS-256 hash-based signature scheme. It provides 128bit security against post-quantum attackers
* at the cost of larger key sizes and loss of compatibility.
*/
private val SPHINCS256_SHA256 = SignatureScheme(
5,
"SPHINCS-256_SHA512",
"SPHINCS-256",
Signature.getInstance("SHA512WITHSPHINCS256", "BCPQC"),
KeyFactory.getInstance("SPHINCS256", "BCPQC"),
KeyPairGenerator.getInstance("SPHINCS256", "BCPQC"),
SPHINCS256KeyGenParameterSpec(SPHINCS256KeyGenParameterSpec.SHA512_256),
256,
"SPHINCS-256 hash-based signature scheme. It provides 128bit security against post-quantum attackers " +
"at the cost of larger key sizes and loss of compatibility."
)
/** Our default signature scheme if no algorithm is specified (e.g. for key generation). */
private val DEFAULT_SIGNATURE_SCHEME = EDDSA_ED25519_SHA512
/**
* Supported digital signature schemes.
* Note: Only the schemes added in this map will be supported (see [Crypto]).
* Do not forget to add the DEFAULT_SIGNATURE_SCHEME as well.
*/
private val supportedSignatureSchemes = mapOf(
RSA_SHA256.schemeCodeName to RSA_SHA256,
ECDSA_SECP256K1_SHA256.schemeCodeName to ECDSA_SECP256K1_SHA256,
ECDSA_SECP256R1_SHA256.schemeCodeName to ECDSA_SECP256R1_SHA256,
EDDSA_ED25519_SHA512.schemeCodeName to EDDSA_ED25519_SHA512,
SPHINCS256_SHA256.schemeCodeName to SPHINCS256_SHA256
)
/**
* Factory pattern to retrieve the corresponding [SignatureScheme] based on the type of the [String] input.
* This function is usually called by key generators and verify signature functions.
* In case the input is not a key in the supportedSignatureSchemes map, null will be returned.
* @param schemeCodeName a [String] that should match a supported signature scheme code name (e.g. ECDSA_SECP256K1_SHA256), see [Crypto].
* @return a currently supported SignatureScheme.
* @throws IllegalArgumentException if the requested signature scheme is not supported.
*/
private fun findSignatureScheme(schemeCodeName: String): SignatureScheme = supportedSignatureSchemes[schemeCodeName] ?: throw IllegalArgumentException("Unsupported key/algorithm for metadata schemeCodeName: ${schemeCodeName}")
/**
* Retrieve the corresponding [SignatureScheme] based on the type of the input [KeyPair].
* Note that only the Corda platform standard schemes are supported (see [Crypto]).
* This function is usually called when requiring to sign signatures.
* @param keyPair a cryptographic [KeyPair].
* @return a currently supported SignatureScheme or null.
* @throws IllegalArgumentException if the requested signature scheme is not supported.
*/
private fun findSignatureScheme(keyPair: KeyPair): SignatureScheme = findSignatureScheme(keyPair.private)
/**
* Retrieve the corresponding [SignatureScheme] based on the type of the input [Key].
* This function is usually called when requiring to verify signatures and the signing schemes must be defined.
* Note that only the Corda platform standard schemes are supported (see [Crypto]).
* Note that we always need to add an additional if-else statement when there are signature schemes
* with the same algorithmName, but with different parameters (e.g. now there are two ECDSA schemes, each using its own curve).
* @param key either private or public.
* @return a currently supported SignatureScheme.
* @throws IllegalArgumentException if the requested key type is not supported.
*/
private fun findSignatureScheme(key: Key): SignatureScheme {
for (sig in supportedSignatureSchemes.values) {
val algorithm = key.algorithm
if (algorithm == sig.algorithmName) {
// If more than one ECDSA schemes are supported, we should distinguish between them by checking their curve parameters.
// TODO: change 'continue' to 'break' if only one EdDSA curve will be used.
if (algorithm == "EdDSA") {
if ((key as EdDSAKey).params == sig.algSpec) {
return sig
} else continue
} else if (algorithm == "ECDSA") {
if ((key as ECKey).parameters == sig.algSpec) {
return sig
} else continue
} else return sig // it's either RSA_SHA256 or SPHINCS-256.
}
}
throw IllegalArgumentException("Unsupported key/algorithm for the private key: ${key.encoded.toBase58()}")
}
/**
* Retrieve the corresponding signature scheme code name based on the type of the input [Key].
* See [Crypto] for the supported scheme code names.
* @param key either private or public.
* @return signatureSchemeCodeName for a [Key].
* @throws IllegalArgumentException if the requested key type is not supported.
*/
fun findSignatureSchemeCodeName(key: Key): String = findSignatureScheme(key).schemeCodeName
/**
* Decode a PKCS8 encoded key to its [PrivateKey] object.
* @param encodedKey a PKCS8 encoded private key.
* @throws IllegalArgumentException on not supported scheme or if the given key specification
* is inappropriate for this key factory to produce a private key.
*/
@Throws(IllegalArgumentException::class)
fun decodePrivateKey(encodedKey: ByteArray): PrivateKey {
for (sig in supportedSignatureSchemes.values) {
try {
return sig.keyFactory.generatePrivate(PKCS8EncodedKeySpec(encodedKey))
} catch (ikse: InvalidKeySpecException) {
// ignore it - only used to bypass the scheme that causes an exception.
}
}
throw IllegalArgumentException("This private key cannot be decoded, please ensure it is PKCS8 encoded and the signature scheme is supported.")
}
/**
* Decode a PKCS8 encoded key to its [PrivateKey] object based on the input scheme code name.
* This will be used by Kryo deserialisation.
* @param encodedKey a PKCS8 encoded private key.
* @param schemeCodeName a [String] that should match a key in supportedSignatureSchemes map (e.g. ECDSA_SECP256K1_SHA256).
* @throws IllegalArgumentException on not supported scheme or if the given key specification
* is inappropriate for this key factory to produce a private key.
*/
@Throws(IllegalArgumentException::class, InvalidKeySpecException::class)
fun decodePrivateKey(encodedKey: ByteArray, schemeCodeName: String): PrivateKey {
val sig = findSignatureScheme(schemeCodeName)
try {
return sig.keyFactory.generatePrivate(PKCS8EncodedKeySpec(encodedKey))
} catch (ikse: InvalidKeySpecException) {
throw InvalidKeySpecException("This private key cannot be decoded, please ensure it is PKCS8 encoded and that it corresponds to the input scheme's code name.", ikse)
}
}
/**
* Decode an X509 encoded key to its [PublicKey] object.
* @param encodedKey an X509 encoded public key.
* @throws UnsupportedSchemeException on not supported scheme.
* @throws IllegalArgumentException on not supported scheme or if the given key specification
* is inappropriate for this key factory to produce a private key.
*/
@Throws(IllegalArgumentException::class)
fun decodePublicKey(encodedKey: ByteArray): PublicKey {
for (sig in supportedSignatureSchemes.values) {
try {
return sig.keyFactory.generatePublic(X509EncodedKeySpec(encodedKey))
} catch (ikse: InvalidKeySpecException) {
// ignore it - only used to bypass the scheme that causes an exception.
}
}
throw IllegalArgumentException("This public key cannot be decoded, please ensure it is X509 encoded and the signature scheme is supported.")
}
/**
* Decode an X509 encoded key to its [PrivateKey] object based on the input scheme code name.
* This will be used by Kryo deserialisation.
* @param encodedKey an X509 encoded public key.
* @param schemeCodeName a [String] that should match a key in supportedSignatureSchemes map (e.g. ECDSA_SECP256K1_SHA256).
* @throws IllegalArgumentException if the requested scheme is not supported
* @throws InvalidKeySpecException if the given key specification
* is inappropriate for this key factory to produce a public key.
*/
@Throws(IllegalArgumentException::class, InvalidKeySpecException::class)
fun decodePublicKey(encodedKey: ByteArray, schemeCodeName: String): PublicKey {
val sig = findSignatureScheme(schemeCodeName)
try {
return sig.keyFactory.generatePublic(X509EncodedKeySpec(encodedKey))
} catch (ikse: InvalidKeySpecException) {
throw throw InvalidKeySpecException("This public key cannot be decoded, please ensure it is X509 encoded and that it corresponds to the input scheme's code name.", ikse)
}
}
/**
* Utility to simplify the act of generating keys.
* Normally, we don't expect other errors here, assuming that key generation parameters for every supported signature scheme have been unit-tested.
* @param schemeCodeName a signature scheme's code name (e.g. ECDSA_SECP256K1_SHA256).
* @return a KeyPair for the requested scheme.
* @throws IllegalArgumentException if the requested signature scheme is not supported.
*/
@Throws(IllegalArgumentException::class)
fun generateKeyPair(schemeCodeName: String): KeyPair = findSignatureScheme(schemeCodeName).keyPairGenerator.generateKeyPair()
/**
* Generate a KeyPair using the default signature scheme.
* @return a new KeyPair.
*/
fun generateKeyPair(): KeyPair = DEFAULT_SIGNATURE_SCHEME.keyPairGenerator.generateKeyPair()
/**
* Generic way to sign [ByteArray] data with a [PrivateKey]. Strategy on on identifying the actual signing scheme is based
* on the [PrivateKey] type, but if the schemeCodeName is known, then better use doSign(signatureScheme: String, privateKey: PrivateKey, clearData: ByteArray).
* @param privateKey the signer's [PrivateKey].
* @param clearData the data/message to be signed in [ByteArray] form (usually the Merkle root).
* @return the digital signature (in [ByteArray]) on the input message.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key.
* @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
fun doSign(privateKey: PrivateKey, clearData: ByteArray) = doSign(findSignatureScheme(privateKey).sig, privateKey, clearData)
/**
* Generic way to sign [ByteArray] data with a [PrivateKey] and a known schemeCodeName [String].
* @param schemeCodeName a signature scheme's code name (e.g. ECDSA_SECP256K1_SHA256).
* @param privateKey the signer's [PrivateKey].
* @param clearData the data/message to be signed in [ByteArray] form (usually the Merkle root).
* @return the digital signature (in [ByteArray]) on the input message.
* @throws IllegalArgumentException if the signature scheme is not supported.
* @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
fun doSign(schemeCodeName: String, privateKey: PrivateKey, clearData: ByteArray) = doSign(findSignatureScheme(schemeCodeName).sig, privateKey, clearData)
/**
* Generic way to sign [ByteArray] data with a [PrivateKey] and a known [Signature].
* @param signature a [Signature] object, retrieved from supported signature schemes, see [Crypto].
* @param privateKey the signer's [PrivateKey].
* @param clearData the data/message to be signed in [ByteArray] form (usually the Merkle root).
* @return the digital signature (in [ByteArray]) on the input message.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key.
* @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
private fun doSign(signature: Signature, privateKey: PrivateKey, clearData: ByteArray): ByteArray {
if (clearData.isEmpty()) throw Exception("Signing of an empty array is not permitted!")
signature.initSign(privateKey)
signature.update(clearData)
return signature.sign()
}
/**
* Generic way to sign [MetaData] objects with a [PrivateKey].
* [MetaData] is a wrapper over the transaction's Merkle root in order to attach extra information, such as a timestamp or partial and blind signature indicators.
* @param privateKey the signer's [PrivateKey].
* @param metaData a [MetaData] object that adds extra information to a transaction.
* @return a [TransactionSignature] object than contains the output of a successful signing and the metaData.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key or
* if metaData.schemeCodeName is not aligned with key type.
* @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
fun doSign(privateKey: PrivateKey, metaData: MetaData): TransactionSignature {
val sigKey: SignatureScheme = findSignatureScheme(privateKey)
val sigMetaData: SignatureScheme = findSignatureScheme(metaData.schemeCodeName)
if (sigKey != sigMetaData) throw IllegalArgumentException("Metadata schemeCodeName: ${metaData.schemeCodeName} is not aligned with the key type.")
val signatureData = doSign(sigKey.schemeCodeName, privateKey, metaData.bytes())
return TransactionSignature(signatureData, metaData)
}
/**
* Utility to simplify the act of verifying a digital signature.
* It returns true if it succeeds, but it always throws an exception if verification fails.
* @param publicKey the signer's [PublicKey].
* @param signatureData the signatureData on a message.
* @param clearData the clear data/message that was signed (usually the Merkle root).
* @return true if verification passes or throws an exception if verification fails.
* @throws InvalidKeyException if the key is invalid.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
* if this signatureData scheme is unable to process the input data provided, if the verification is not possible.
* @throws IllegalArgumentException if the signature scheme is not supported or if any of the clear or signature data is empty.
*/
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
fun doVerify(schemeCodeName: String, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray) = doVerify(findSignatureScheme(schemeCodeName).sig, publicKey, signatureData, clearData)
/**
* Utility to simplify the act of verifying a digital signature by identifying the signature scheme used from the input public key's type.
* It returns true if it succeeds, but it always throws an exception if verification fails.
* Strategy on identifying the actual signing scheme is based on the [PublicKey] type, but if the schemeCodeName is known,
* then better use doVerify(schemeCodeName: String, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray).
* @param publicKey the signer's [PublicKey].
* @param signatureData the signatureData on a message.
* @param clearData the clear data/message that was signed (usually the Merkle root).
* @return true if verification passes or throws an exception if verification fails.
* @throws InvalidKeyException if the key is invalid.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
* if this signatureData scheme is unable to process the input data provided, if the verification is not possible.
* @throws IllegalArgumentException if the signature scheme is not supported or if any of the clear or signature data is empty.
*/
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
fun doVerify(publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray) = doVerify(findSignatureScheme(publicKey).sig, publicKey, signatureData, clearData)
/**
* Method to verify a digital signature.
* It returns true if it succeeds, but it always throws an exception if verification fails.
* @param signature a [Signature] object, retrieved from supported signature schemes, see [Crypto].
* @param publicKey the signer's [PublicKey].
* @param signatureData the signatureData on a message.
* @param clearData the clear data/message that was signed (usually the Merkle root).
* @return true if verification passes or throws an exception if verification fails.
* @throws InvalidKeyException if the key is invalid.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
* if this signatureData scheme is unable to process the input data provided, if the verification is not possible.
* @throws IllegalArgumentException if any of the clear or signature data is empty.
*/
private fun doVerify(signature: Signature, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray): Boolean {
if (signatureData.isEmpty()) throw IllegalArgumentException("Signature data is empty!")
if (clearData.isEmpty()) throw IllegalArgumentException("Clear data is empty, nothing to verify!")
signature.initVerify(publicKey)
signature.update(clearData)
val verificationResult = signature.verify(signatureData)
if (verificationResult) {
return true
} else {
throw SignatureException("Signature Verification failed!")
}
}
/**
* Utility to simplify the act of verifying a [TransactionSignature].
* It returns true if it succeeds, but it always throws an exception if verification fails.
* @param publicKey the signer's [PublicKey].
* @param transactionSignature the signatureData on a message.
* @return true if verification passes or throws an exception if verification fails.
* @throws InvalidKeyException if the key is invalid.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
* if this signatureData scheme is unable to process the input data provided, if the verification is not possible.
* @throws IllegalArgumentException if the signature scheme is not supported or if any of the clear or signature data is empty.
*/
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
fun doVerify(publicKey: PublicKey, transactionSignature: TransactionSignature): Boolean {
if (publicKey != transactionSignature.metaData.publicKey) IllegalArgumentException("MetaData's publicKey: ${transactionSignature.metaData.publicKey.encoded.toBase58()} does not match the input clearData: ${publicKey.encoded.toBase58()}")
return Crypto.doVerify(publicKey, transactionSignature.signatureData, transactionSignature.metaData.bytes())
}
/**
* Check if the requested signature scheme is supported by the system.
* @param schemeCodeName a signature scheme's code name (e.g. ECDSA_SECP256K1_SHA256).
* @return true if the signature scheme is supported.
*/
fun isSupportedSignatureScheme(schemeCodeName: String): Boolean = schemeCodeName in supportedSignatureSchemes
/** @return the default signature scheme's code name. */
fun getDefaultSignatureSchemeCodeName(): String = DEFAULT_SIGNATURE_SCHEME.schemeCodeName
/** @return a [List] of Strings with the scheme code names defined in [SignatureScheme] for all of our supported signature schemes, see [Crypto]. */
fun listSupportedSignatureSchemes(): List<String> = supportedSignatureSchemes.keys.toList()
}

View File

@ -2,6 +2,7 @@
package net.corda.core.crypto
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.OpaqueBytes
import net.i2p.crypto.eddsa.EdDSAEngine
import net.i2p.crypto.eddsa.EdDSAPrivateKey
@ -21,11 +22,8 @@ fun newSecureRandom(): SecureRandom {
}
}
/**
* A wrapper around a digital signature. The covering field is a generic tag usable by whatever is interpreting the
* signature. It isn't used currently, but experience from Bitcoin suggests such a feature is useful, especially when
* building partially signed transactions.
*/
/** A wrapper around a digital signature. */
@CordaSerializable
open class DigitalSignature(bits: ByteArray) : OpaqueBytes(bits) {
/** A digital signature that identifies who the public key is owned by. */
open class WithKey(val by: PublicKey, bits: ByteArray) : DigitalSignature(bits) {
@ -37,6 +35,7 @@ open class DigitalSignature(bits: ByteArray) : OpaqueBytes(bits) {
class LegallyIdentifiable(val signer: Party, bits: ByteArray) : WithKey(signer.owningKey.singleKey, bits)
}
@CordaSerializable
object NullPublicKey : PublicKey, Comparable<PublicKey> {
override fun getAlgorithm() = "NULL"
override fun getEncoded() = byteArrayOf(0)
@ -48,6 +47,7 @@ object NullPublicKey : PublicKey, Comparable<PublicKey> {
val NullCompositeKey = NullPublicKey.composite
// TODO: Clean up this duplication between Null and Dummy public key
@CordaSerializable
class DummyPublicKey(val s: String) : PublicKey, Comparable<PublicKey> {
override fun getAlgorithm() = "DUMMY"
override fun getEncoded() = s.toByteArray()
@ -59,6 +59,7 @@ class DummyPublicKey(val s: String) : PublicKey, Comparable<PublicKey> {
}
/** A signature with a key and value of zero. Useful when you want a signature object that you know won't ever be used. */
@CordaSerializable
object NullSignature : DigitalSignature.WithKey(NullPublicKey, ByteArray(32))
/** Utility to simplify the act of signing a byte array */

View File

@ -0,0 +1,117 @@
package net.corda.core.crypto
import java.security.*
/**
* Helper function for signing.
* @param clearData the data/message to be signed in [ByteArray] form (usually the Merkle root).
* @return the digital signature (in [ByteArray]) on the input message.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key.
* @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
fun PrivateKey.sign(clearData: ByteArray): ByteArray = Crypto.doSign(this, clearData)
/**
* Helper function for signing.
* @param metaDataFull tha attached MetaData object.
* @return a [DSWithMetaDataFull] object.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key.
* @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
fun PrivateKey.sign(metaData: MetaData): TransactionSignature = Crypto.doSign(this, metaData)
/**
* Helper function to sign with a key pair.
* @param clearData the data/message to be signed in [ByteArray] form (usually the Merkle root).
* @return the digital signature (in [ByteArray]) on the input message.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key.
* @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
fun KeyPair.sign(clearData: ByteArray): ByteArray = Crypto.doSign(this.private, clearData)
/**
* Helper function to verify a signature.
* @param signatureData the signature on a message.
* @param clearData the clear data/message that was signed (usually the Merkle root).
* @throws InvalidKeyException if the key is invalid.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
* if this signatureData algorithm is unable to process the input data provided, etc.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key or if any of the clear or signature data is empty.
*/
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
fun PublicKey.verify(signatureData: ByteArray, clearData: ByteArray): Boolean = Crypto.doVerify(this, signatureData, clearData)
/**
* Helper function to verify a metadata attached signature. It is noted that the transactionSignature contains
* signatureData and a [MetaData] object that contains the signer's public key and the transaction's Merkle root.
* @param transactionSignature a [TransactionSignature] object that .
* @throws InvalidKeyException if the key is invalid.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
* if this signatureData algorithm is unable to process the input data provided, etc.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key or if any of the clear or signature data is empty.
*/
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
fun PublicKey.verify(transactionSignature: TransactionSignature): Boolean {
return Crypto.doVerify(this, transactionSignature)
}
/**
* Helper function for the signers to verify their own signature.
* @param signature the signature on a message.
* @param clearData the clear data/message that was signed (usually the Merkle root).
* @throws InvalidKeyException if the key is invalid.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
* if this signatureData algorithm is unable to process the input data provided, etc.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key or if any of the clear or signature data is empty.
*/
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
fun KeyPair.verify(signatureData: ByteArray, clearData: ByteArray): Boolean = Crypto.doVerify(this.public, signatureData, clearData)
/**
* Generate a securely random [ByteArray] of requested number of bytes. Usually used for seeds, nonces and keys.
* @param numOfBytes how many random bytes to output.
* @return a random [ByteArray].
* @throws NoSuchAlgorithmException thrown if "NativePRNGNonBlocking" is not supported on the JVM
* or if no strong SecureRandom implementations are available or if Security.getProperty("securerandom.strongAlgorithms") is null or empty,
* which should never happen and suggests an unusual JVM or non-standard Java library.
*/
@Throws(NoSuchAlgorithmException::class)
fun safeRandomBytes(numOfBytes: Int): ByteArray {
return safeRandom().generateSeed(numOfBytes)
}
/**
* Get an instance of [SecureRandom] to avoid blocking, due to waiting for additional entropy, when possible.
* In this version, the NativePRNGNonBlocking is exclusively used on Linux OS to utilize dev/urandom because in high traffic
* /dev/random may wait for a certain amount of "noise" to be generated on the host machine before returning a result.
*
* On Solaris, Linux, and OS X, if the entropy gathering device in java.security is set to file:/dev/urandom
* or file:/dev/random, then NativePRNG is preferred to SHA1PRNG. Otherwise, SHA1PRNG is preferred.
* @see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html#SecureRandomImp">SecureRandom Implementation</a>.
*
* If both dev/random and dev/urandom are available, then dev/random is only preferred over dev/urandom during VM boot
* where it may be possible that OS didn't yet collect enough entropy to fill the randomness pool for the 1st time.
* @see <a href="http://www.2uo.de/myths-about-urandom/">Myths about urandom</a> for a more descriptive explanation on /dev/random Vs /dev/urandom.
* TODO: check default settings per OS and random/urandom availability.
* @return a [SecureRandom] object.
* @throws NoSuchAlgorithmException thrown if "NativePRNGNonBlocking" is not supported on the JVM
* or if no strong SecureRandom implementations are available or if Security.getProperty("securerandom.strongAlgorithms") is null or empty,
* which should never happen and suggests an unusual JVM or non-standard Java library.
*/
@Throws(NoSuchAlgorithmException::class)
fun safeRandom(): SecureRandom {
if (System.getProperty("os.name") == "Linux") {
return SecureRandom.getInstance("NativePRNGNonBlocking")
} else {
return SecureRandom.getInstanceStrong()
}
}

View File

@ -0,0 +1,12 @@
package net.corda.core.crypto
import java.security.KeyFactory
/**
* Custom [KeyFactory] for EdDSA with null security [Provider].
* This is required as a [SignatureScheme] requires a [java.security.KeyFactory] property, but i2p has
* its own KeyFactory for EdDSA, thus this actually a Proxy Pattern over i2p's KeyFactory.
*/
class EdDSAKeyFactory: KeyFactory {
constructor() : super(net.i2p.crypto.eddsa.KeyFactory(), null, "EDDSA_ED25519_SHA512")
}

View File

@ -0,0 +1,60 @@
package net.corda.core.crypto
import java.nio.charset.Charset
import java.util.*
import javax.xml.bind.DatatypeConverter
// This file includes useful encoding methods and extension functions for the most common encoding/decoding operations.
// [ByteArray] encoders
fun ByteArray.toBase58(): String = Base58.encode(this)
fun ByteArray.toBase64(): String = Base64.getEncoder().encodeToString(this)
/** Convert a byte array to a hex (base 16) capitalized encoded string.*/
fun ByteArray.toHex(): String = DatatypeConverter.printHexBinary(this)
// [String] encoders and decoders
/** Base58-String to the actual real [String], i.e. "JxF12TrwUP45BMd" -> "Hello World". */
fun String.base58ToRealString() = String(base58ToByteArray(), Charset.defaultCharset())
/** Base64-String to the actual real [String], i.e. "SGVsbG8gV29ybGQ=" -> "Hello World". */
fun String.base64ToRealString() = String(base64ToByteArray())
/** HEX-String to the actual real [String], i.e. "48656C6C6F20576F726C64" -> "Hello World". */
fun String.hexToRealString() = String(hexToByteArray())
fun String.base58ToByteArray(): ByteArray = Base58.decode(this)
fun String.base64ToByteArray(): ByteArray = Base64.getDecoder().decode(this)
/** Hex-String to [ByteArray]. Accept any hex form (capitalized, lowercase, mixed). */
fun String.hexToByteArray(): ByteArray = DatatypeConverter.parseHexBinary(this);
// Encoding changers
/** Encoding changer. Base58-[String] to Base64-[String], i.e. "SGVsbG8gV29ybGQ=" -> JxF12TrwUP45BMd" */
fun String.base58toBase64(): String = base58ToByteArray().toBase64()
/** Encoding changer. Base58-[String] to Hex-[String], i.e. "SGVsbG8gV29ybGQ=" -> "48656C6C6F20576F726C64" */
fun String.base58toHex(): String = base58ToByteArray().toHex()
/** Encoding changer. Base64-[String] to Base58-[String], i.e. "SGVsbG8gV29ybGQ=" -> JxF12TrwUP45BMd" */
fun String.base64toBase58(): String = base64ToByteArray().toBase58()
/** Encoding changer. Base64-[String] to Hex-[String], i.e. "SGVsbG8gV29ybGQ=" -> "48656C6C6F20576F726C64" */
fun String.base64toHex(): String = base64ToByteArray().toHex()
/** Encoding changer. Hex-[String] to Base58-[String], i.e. "48656C6C6F20576F726C64" -> "JxF12TrwUP45BMd" */
fun String.hexToBase58(): String = hexToByteArray().toBase58()
/** Encoding changer. Hex-[String] to Base64-[String], i.e. "48656C6C6F20576F726C64" -> "SGVsbG8gV29ybGQ=" */
fun String.hexToBase64(): String = hexToByteArray().toBase64()
// Helper vars.
private val HEX_ALPHABET = "0123456789ABCDEF".toCharArray()

View File

@ -0,0 +1,71 @@
package net.corda.core.crypto
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.opaque
import net.corda.core.serialization.serialize
import java.security.PublicKey
import java.time.Instant
import java.util.*
/**
* A [MetaData] object adds extra information to a transaction. MetaData is used to support a universal
* digital signature model enabling full, partial, fully or partially blind and metaData attached signatures,
* (such as an attached timestamp). A MetaData object contains both the merkle root of the transaction and the signer's public key.
* When signatureType is set to FULL, then visibleInputs and signedInputs can be ignored.
* Note: We could omit signatureType as it can always be defined by combining visibleInputs and signedInputs,
* but it helps to speed up the process when FULL is used, and thus we can bypass the extra check on boolean arrays.
*
* @param schemeCodeName a signature scheme's code name (e.g. ECDSA_SECP256K1_SHA256).
* @param versionID DLT's version.
* @param signatureType type of the signature, see [SignatureType] (e.g. FULL, PARTIAL, BLIND, PARTIAL_AND_BLIND).
* @param timestamp the signature's timestamp as provided by the signer.
* @param visibleInputs for partially/fully blind signatures. We use Merkle tree boolean index flags (from left to right)
* indicating what parts of the transaction were visible when the signature was calculated.
* @param signedInputs for partial signatures. We use Merkle tree boolean index flags (from left to right)
* indicating what parts of the Merkle tree are actually signed.
* @param merkleRoot the Merkle root of the transaction.
* @param publicKey the signer's public key.
*/
@CordaSerializable
open class MetaData(
val schemeCodeName: String,
val versionID: String,
val signatureType: SignatureType = SignatureType.FULL,
val timestamp: Instant?,
val visibleInputs: BitSet?,
val signedInputs: BitSet?,
val merkleRoot: ByteArray,
val publicKey: PublicKey) {
fun bytes() = this.serialize().bytes
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as MetaData
if (schemeCodeName != other.schemeCodeName) return false
if (versionID != other.versionID) return false
if (signatureType != other.signatureType) return false
if (timestamp != other.timestamp) return false
if (visibleInputs != other.visibleInputs) return false
if (signedInputs != other.signedInputs) return false
if (merkleRoot.opaque() != other.merkleRoot.opaque()) return false
if (publicKey != other.publicKey) return false
return true
}
override fun hashCode(): Int {
var result = schemeCodeName.hashCode()
result = 31 * result + versionID.hashCode()
result = 31 * result + signatureType.hashCode()
result = 31 * result + (timestamp?.hashCode() ?: 0)
result = 31 * result + (visibleInputs?.hashCode() ?: 0)
result = 31 * result + (signedInputs?.hashCode() ?: 0)
result = 31 * result + Arrays.hashCode(merkleRoot)
result = 31 * result + publicKey.hashCode()
return result
}
}

View File

@ -1,10 +1,10 @@
package net.corda.core.crypto
import net.corda.core.crypto.MerkleTree
import net.corda.core.crypto.SecureHash.Companion.zeroHash
import net.corda.core.serialization.CordaSerializable
import java.util.*
@CordaSerializable
class MerkleTreeException(val reason: String) : Exception() {
override fun toString() = "Partial Merkle Tree exception. Reason: $reason"
}
@ -43,7 +43,7 @@ class MerkleTreeException(val reason: String) : Exception() {
* (there can be a difference in obtained leaves ordering - that's why it's a set comparison not hashing leaves into a tree).
* If both equalities hold, we can assume that l3 and l5 belong to the transaction with root h15.
*/
@CordaSerializable
class PartialMerkleTree(val root: PartialTree) {
/**
* The structure is a little different than that of Merkle Tree.
@ -52,6 +52,7 @@ class PartialMerkleTree(val root: PartialTree) {
* transaction and leaves that just keep hashes needed for calculation. Reason for this approach: during verification
* it's easier to extract hashes used as a base for this tree.
*/
@CordaSerializable
sealed class PartialTree {
class IncludedLeaf(val hash: SecureHash) : PartialTree()
class Leaf(val hash: SecureHash) : PartialTree()

View File

@ -1,5 +1,7 @@
package net.corda.core.crypto
import net.corda.core.contracts.PartyAndReference
import net.corda.core.serialization.OpaqueBytes
import java.security.PublicKey
/**
@ -9,7 +11,7 @@ import java.security.PublicKey
* cryptographic public key primitives into a tree structure.
*
* For example: Alice has two key pairs (pub1/priv1 and pub2/priv2), and wants to be able to sign transactions with either of them.
* Her advertised [Party] then has a legal [name] "Alice" and an [owingKey] "pub1 or pub2".
* Her advertised [Party] then has a legal [name] "Alice" and an [owningKey] "pub1 or pub2".
*
* [Party] is also used for service identities. E.g. Alice may also be running an interest rate oracle on her Corda node,
* which requires a separate signing key (and an identifying name). Services can also be distributed run by a coordinated
@ -20,8 +22,12 @@ import java.security.PublicKey
*
* @see CompositeKey
*/
class Party(val name: String, owningKey: CompositeKey) : AnonymousParty(owningKey) {
class Party(val name: String, owningKey: CompositeKey) : AbstractParty(owningKey) {
/** A helper constructor that converts the given [PublicKey] in to a [CompositeKey] with a single node */
constructor(name: String, owningKey: PublicKey) : this(name, owningKey.composite)
override fun toString() = "${owningKey.toBase58String()} (name)"
override fun toAnonymous(): AnonymousParty = AnonymousParty(owningKey)
override fun toString() = "${owningKey.toBase58String()} (${name})"
override fun nameOrNull(): String? = name
override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this.toAnonymous(), bytes)
}

View File

@ -1,6 +1,7 @@
package net.corda.core.crypto
import com.google.common.io.BaseEncoding
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.OpaqueBytes
import java.security.MessageDigest
@ -8,6 +9,7 @@ import java.security.MessageDigest
* Container for a cryptographically secure hash value.
* Provides utilities for generating a cryptographic hash using different algorithms (currently only SHA-256 supported).
*/
@CordaSerializable
sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
/** SHA-256 is part of the SHA-2 hash function family. Generated hash is fixed size, 256-bits (32-bytes) */
class SHA256(bytes: ByteArray) : SecureHash(bytes) {

View File

@ -0,0 +1,42 @@
package net.corda.core.crypto
import java.security.*
import java.security.spec.AlgorithmParameterSpec
/**
* This class is used to define a digital signature scheme.
* @param schemeNumberID we assign a number ID for more efficient on-wire serialisation. Please ensure uniqueness between schemes.
* @param schemeCodeName code name for this signature scheme (e.g. RSA_SHA256, ECDSA_SECP256K1_SHA256, ECDSA_SECP256R1_SHA256, EDDSA_ED25519_SHA512, SPHINCS-256_SHA512).
* @param algorithmName which signature algorithm is used (e.g. RSA, ECDSA. EdDSA, SPHINCS-256).
* @param sig the [Signature] class that provides the functionality of a digital signature scheme.
* eg. Signature.getInstance("SHA256withECDSA", "BC").
* @param keyFactory the KeyFactory for this scheme (e.g. KeyFactory.getInstance("RSA", "BC")).
* @param keyPairGenerator defines the <i>Service Provider Interface</i> (<b>SPI</b>) for the {@code KeyPairGenerator} class.
* e.g. KeyPairGenerator.getInstance("ECDSA", "BC").
* @param algSpec parameter specs for the underlying algorithm. Note that RSA is defined by the key size rather than algSpec.
* eg. ECGenParameterSpec("secp256k1").
* @param keySize the private key size (currently used for RSA only).
* @param desc a human-readable description for this scheme.
*/
data class SignatureScheme(
val schemeNumberID: Int,
val schemeCodeName: String,
val algorithmName: String,
val sig: Signature,
val keyFactory: KeyFactory,
val keyPairGenerator: KeyPairGeneratorSpi,
val algSpec: AlgorithmParameterSpec?,
val keySize: Int,
val desc: String) {
/**
* KeyPair generators are always initialized once we create them, as no re-initialization is required.
* Note that RSA is the sole algorithm initialized specifically by its supported keySize.
*/
init {
if (algSpec != null)
keyPairGenerator.initialize(algSpec, safeRandom())
else
keyPairGenerator.initialize(keySize, safeRandom())
}
}

View File

@ -0,0 +1,17 @@
package net.corda.core.crypto
import net.corda.core.serialization.CordaSerializable
/**
* Supported Signature types:
* <p><ul>
* <li>FULL = signature covers whole transaction, by the convention that signing the Merkle root, it is equivalent to signing all parts of the transaction.
* <li>PARTIAL = signature covers only a part of the transaction, see [MetaData].
* <li>BLIND = when an entity blindly signs without having full knowledge on the content, see [MetaData].
* <li>PARTIAL_AND_BLIND = combined PARTIAL and BLIND in the same time.
* </ul>
*/
@CordaSerializable
enum class SignatureType {
FULL, PARTIAL, BLIND, PARTIAL_AND_BLIND
}

View File

@ -1,5 +1,6 @@
package net.corda.core.crypto
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import java.security.SignatureException
@ -11,6 +12,7 @@ import java.security.SignatureException
* @param raw the raw serialized data.
* @param sig the (unverified) signature for the data.
*/
@CordaSerializable
open class SignedData<T : Any>(val raw: SerializedBytes<T>, val sig: DigitalSignature.WithKey) {
/**
* Return the deserialized data if the signature can be verified.

View File

@ -0,0 +1,24 @@
package net.corda.core.crypto
import net.corda.core.serialization.opaque
import java.security.InvalidKeyException
import java.security.PublicKey
import java.security.SignatureException
/**
* A wrapper around a digital signature accompanied with metadata, see [MetaData.Full] and [DigitalSignature].
* The signature protocol works as follows: s = sign(MetaData.hashBytes).
*/
open class TransactionSignature(val signatureData: ByteArray, val metaData: MetaData) : DigitalSignature(signatureData) {
/**
* Function to auto-verify a [MetaData] object's signature.
* Note that [MetaData] contains both public key and merkle root of the transaction.
* @throws InvalidKeyException if the key is invalid.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
* if this signatureData algorithm is unable to process the input data provided, etc.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key or if any of the clear or signature data is empty.
*/
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
fun verify(): Boolean = Crypto.doVerify(metaData.publicKey, signatureData, metaData.bytes())
}

View File

@ -1,5 +1,7 @@
package net.corda.core.flows
import net.corda.core.serialization.CordaSerializable
/**
* Exception which can be thrown by a [FlowLogic] at any point in its logic to unexpectedly bring it to a permanent end.
* The exception will propagate to all counterparty flows and will be thrown on their end the next time they wait on a
@ -9,6 +11,7 @@ package net.corda.core.flows
* [FlowException] (or a subclass) can be a valid expected response from a flow, particularly ones which act as a service.
* It is recommended a [FlowLogic] document the [FlowException] types it can throw.
*/
@CordaSerializable
open class FlowException(override val message: String?, override val cause: Throwable?) : Exception() {
constructor(message: String?) : this(message, null)
constructor(cause: Throwable?) : this(cause?.toString(), cause)

View File

@ -7,6 +7,7 @@ import net.corda.core.node.ServiceHub
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.debug
import org.slf4j.Logger
import rx.Observable
@ -135,7 +136,9 @@ abstract class FlowLogic<out T> {
if (shareParentSessions) {
subLogic.sessionFlow = this
}
logger.debug { "Calling subflow: $subLogic" }
val result = subLogic.call()
logger.debug { "Subflow finished with result $result" }
// It's easy to forget this when writing flows so we just step it to the DONE state when it completes.
subLogic.progressTracker?.currentStep = ProgressTracker.DONE
return result

View File

@ -2,6 +2,7 @@ package net.corda.core.flows
import com.google.common.primitives.Primitives
import net.corda.core.crypto.SecureHash
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SingletonSerializeAsToken
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
@ -23,8 +24,7 @@ import kotlin.reflect.primaryConstructor
* TODO: Align with API related logic for passing in FlowLogic references (FlowRef)
* TODO: Actual support for AppContext / AttachmentsClassLoader
*/
class FlowLogicRefFactory(private val flowWhitelist: Map<String, Set<String>>) : SingletonSerializeAsToken() {
class FlowLogicRefFactory(val flowWhitelist: Map<String, Set<String>>) : SingletonSerializeAsToken() {
constructor() : this(mapOf())
// Pending real dependence on AppContext for class loading etc
@ -186,6 +186,7 @@ class FlowLogicRefFactory(private val flowWhitelist: Map<String, Set<String>>) :
}
}
@CordaSerializable
class IllegalFlowLogicException(type: Class<*>, msg: String) : IllegalArgumentException("${FlowLogicRef::class.java.simpleName} cannot be constructed for ${FlowLogic::class.java.simpleName} of type ${type.name} $msg")
/**
@ -194,12 +195,14 @@ class IllegalFlowLogicException(type: Class<*>, msg: String) : IllegalArgumentEx
* Only allows a String reference to the FlowLogic class, and only allows restricted argument types as per [FlowLogicRefFactory].
*/
// TODO: align this with the existing [FlowRef] in the bank-side API (probably replace some of the API classes)
@CordaSerializable
data class FlowLogicRef internal constructor(val flowLogicClassName: String, val appContext: AppContext, val args: Map<String, Any?>)
/**
* This is just some way to track what attachments need to be in the class loader, but may later include some app
* properties loaded from the attachments. And perhaps the authenticated user for an API call?
*/
@CordaSerializable
data class AppContext(val attachments: List<SecureHash>) {
// TODO: build a real [AttachmentsClassLoader] etc
val classLoader: ClassLoader

View File

@ -5,6 +5,7 @@ import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.UntrustworthyData
import org.slf4j.Logger
@ -14,6 +15,7 @@ import java.util.*
* A unique identifier for a single state machine run, valid across node restarts. Note that a single run always
* has at least one flow, but that flow may also invoke sub-flows: they all share the same run id.
*/
@CordaSerializable
data class StateMachineRunId private constructor(val uuid: UUID) {
companion object {
fun createRandom(): StateMachineRunId = StateMachineRunId(UUID.randomUUID())

View File

@ -14,22 +14,26 @@ import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.StateMachineTransactionMapping
import net.corda.core.node.services.Vault
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import rx.Observable
import java.io.InputStream
import java.time.Instant
import java.util.*
@CordaSerializable
data class StateMachineInfo(
val id: StateMachineRunId,
val flowLogicClassName: String,
val progressTrackerStepAndUpdates: Pair<String, Observable<String>>?
)
@CordaSerializable
sealed class StateMachineUpdate(val id: StateMachineRunId) {
class Added(val stateMachineInfo: StateMachineInfo) : StateMachineUpdate(stateMachineInfo.id) {
override fun toString() = "Added($id, ${stateMachineInfo.flowLogicClassName})"
}
class Removed(id: StateMachineRunId) : StateMachineUpdate(id) {
override fun toString() = "Removed($id)"
}
@ -107,12 +111,17 @@ interface CordaRPCOps : RPCOps {
*/
fun attachmentExists(id: SecureHash): Boolean
/**
* Download an attachment JAR by ID
*/
fun openAttachment(id: SecureHash): InputStream
/**
* Uploads a jar to the node, returns it's hash.
*/
fun uploadAttachment(jar: InputStream): SecureHash
@Suppress("DEPRECATION")
// TODO: Remove this from the interface
@Deprecated("This service will be removed in a future milestone")
fun uploadFile(dataType: String, name: String?, file: InputStream): String
@ -122,7 +131,7 @@ interface CordaRPCOps : RPCOps {
* Invoking this method indicate the node is willing to upgrade the [state] using the [upgradedContractClass].
* This method will NOT initiate the upgrade process. To start the upgrade process, see [ContractUpgradeFlow.Instigator].
*/
fun authoriseContractUpgrade(state: StateAndRef<*>, upgradedContractClass: Class<UpgradedContract<*, *>>)
fun authoriseContractUpgrade(state: StateAndRef<*>, upgradedContractClass: Class<out UpgradedContract<*, *>>)
/**
* Authorise a contract state upgrade.
@ -153,6 +162,9 @@ interface CordaRPCOps : RPCOps {
* Returns the [Party] with the given name as it's [Party.name]
*/
fun partyFromName(name: String): Party?
/** Enumerates the class names of the flows that this node knows about. */
fun registeredFlows(): List<String>
}
/**
@ -206,6 +218,7 @@ inline fun <T : Any, A, B, C, D, reified R : FlowLogic<T>> CordaRPCOps.startFlow
* @param progress The stream of progress tracker events.
* @param returnValue A [ListenableFuture] of the flow's return value.
*/
@CordaSerializable
data class FlowHandle<A>(
val id: StateMachineRunId,
val progress: Observable<String>,

View File

@ -5,6 +5,7 @@ import com.google.common.util.concurrent.SettableFuture
import net.corda.core.catch
import net.corda.core.node.services.DEFAULT_SESSION_ID
import net.corda.core.node.services.PartyInfo
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeserializeAsKotlinObjectDef
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
@ -160,6 +161,7 @@ interface MessageHandlerRegistration
* @param sessionID identifier for the session the message is part of. For services listening before
* a session is established, use [DEFAULT_SESSION_ID].
*/
@CordaSerializable
data class TopicSession(val topic: String, val sessionID: Long = DEFAULT_SESSION_ID) {
fun isBlank() = topic.isBlank() && sessionID == DEFAULT_SESSION_ID
override fun toString(): String = "$topic.$sessionID"
@ -213,4 +215,5 @@ interface AllPossibleRecipients : MessageRecipients
* A general Ack message that conveys no content other than it's presence for use when you want an acknowledgement
* from a recipient. Using [Unit] can be ambiguous as it is similar to [Void] and so could mean no response.
*/
@CordaSerializable
object Ack : DeserializeAsKotlinObjectDef

View File

@ -2,6 +2,7 @@ package net.corda.core.node
import net.corda.core.contracts.Attachment
import net.corda.core.crypto.SecureHash
import net.corda.core.serialization.CordaSerializable
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.FileNotFoundException
@ -24,6 +25,7 @@ class AttachmentsClassLoader(attachments: List<Attachment>, parent: ClassLoader
private val pathsToAttachments = HashMap<String, Attachment>()
private val idsToAttachments = HashMap<SecureHash, Attachment>()
@CordaSerializable
class OverlappingAttachments(val path: String) : Exception() {
override fun toString() = "Multiple attachments define a file at path $path"
}

View File

@ -1,7 +1,7 @@
package net.corda.core.node
import com.esotericsoftware.kryo.Kryo
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.serialization.SerializationCustomization
import java.util.function.Function
/**
@ -40,14 +40,12 @@ abstract class CordaPluginRegistry(
open val servicePlugins: List<Function<PluginServiceHub, out Any>> = emptyList()
) {
/**
* Optionally register types with [Kryo] for use over RPC, as we lock down the types that can be serialised in this
* particular use case.
* For example, if you add an RPC interface that carries some contract states back and forth, you need to register
* those classes here using the [register] method on Kryo.
*
* TODO: Kryo and likely the requirement to register classes here will go away when we replace the serialization implementation.
* Optionally whitelist types for use in object serialization, as we lock down the types that can be serialized.
*
* For example, if you add a new [ContractState] it needs to be whitelisted. You can do that either by
* adding the @CordaSerializable annotation or via this method.
**
* @return true if you register types, otherwise you will be filtered out of the list of plugins considered in future.
*/
open fun registerRPCKryoTypes(kryo: Kryo): Boolean = false
open fun customizeSerialization(custom: SerializationCustomization): Boolean = false
}

View File

@ -4,18 +4,22 @@ import net.corda.core.crypto.Party
import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType
import net.corda.core.serialization.CordaSerializable
/**
* Information for an advertised service including the service specific identity information.
* The identity can be used in flows and is distinct from the Node's legalIdentity
*/
@CordaSerializable
data class ServiceEntry(val info: ServiceInfo, val identity: Party)
/**
* Info about a network node that acts on behalf of some form of contract party.
*/
@CordaSerializable
data class NodeInfo(val address: SingleMessageRecipient,
val legalIdentity: Party,
val version: Version,
var advertisedServices: List<ServiceEntry> = emptyList(),
val physicalLocation: PhysicalLocation? = null) {
init {

View File

@ -1,8 +1,10 @@
package net.corda.core.node
import net.corda.core.serialization.CordaSerializable
import java.util.*
/** A latitude/longitude pair. */
@CordaSerializable
data class WorldCoordinate(val latitude: Double, val longitude: Double) {
init {
require(latitude in -90..90)
@ -39,6 +41,7 @@ data class WorldCoordinate(val latitude: Double, val longitude: Double) {
* A labelled [WorldCoordinate], where the label is human meaningful. For example, the name of the nearest city.
* Labels should not refer to non-landmarks, for example, they should not contain the names of organisations.
*/
@CordaSerializable
data class PhysicalLocation(val coordinate: WorldCoordinate, val description: String)
/**

View File

@ -9,6 +9,25 @@ import net.corda.core.transactions.SignedTransaction
import java.security.KeyPair
import java.time.Clock
/**
* Subset of node services that are used for loading transactions from the wire into fully resolved, looked up
* forms ready for verification.
*
* @see ServiceHub
*/
interface ServicesForResolution {
val identityService: IdentityService
val storageService: AttachmentsStorageService
/**
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
*
* @throws TransactionResolutionException if the [StateRef] points to a non-existent transaction.
*/
@Throws(TransactionResolutionException::class)
fun loadState(stateRef: StateRef): TransactionState<*>
}
/**
* A service hub simply vends references to the other services a node has. Some of those services may be missing or
* mocked out. This class is useful to pass to chunks of pluggable code that might have need of many different kinds of
@ -17,12 +36,11 @@ import java.time.Clock
* Any services exposed to flows (public view) need to implement [SerializeAsToken] or similar to avoid their internal
* state from being serialized in checkpoints.
*/
interface ServiceHub {
interface ServiceHub : ServicesForResolution {
val vaultService: VaultService
val keyManagementService: KeyManagementService
val identityService: IdentityService
val storageService: StorageService
val networkService: MessagingService
override val storageService: StorageService
val networkMapCache: NetworkMapCache
val schedulerService: SchedulerService
val clock: Clock
@ -37,20 +55,29 @@ interface ServiceHub {
// TODO: Make this take a single tx.
fun recordTransactions(txs: Iterable<SignedTransaction>)
/**
* Given some [SignedTransaction]s, writes them to the local storage for validated transactions and then
* sends them to the vault for further processing.
*
* @param txs The transactions to record.
*/
fun recordTransactions(vararg txs: SignedTransaction) = recordTransactions(txs.toList())
/**
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
*
* @throws TransactionResolutionException if the [StateRef] points to a non-existent transaction.
*/
fun loadState(stateRef: StateRef): TransactionState<*> {
@Throws(TransactionResolutionException::class)
override fun loadState(stateRef: StateRef): TransactionState<*> {
val definingTx = storageService.validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
return definingTx.tx.outputs[stateRef.index]
}
/**
* Given a [StateRef] loads the referenced transaction and returns a [StateAndRef<T>]
* Will check [logicType] and [args] against a whitelist and if acceptable then construct and initiate the protocol.
*
* @throws TransactionResolutionException if the [StateRef] points to a non-existent transaction.
* @throws IllegalProtocolLogicException or IllegalArgumentException if there are problems with the [logicType] or [args].
*/
fun <T : ContractState> toStateAndRef(ref: StateRef): StateAndRef<T> {
val definingTx = storageService.validatedTransactions.getTransaction(ref.txhash) ?: throw TransactionResolutionException(ref.txhash)

View File

@ -0,0 +1,28 @@
package net.corda.core.node
import net.corda.core.serialization.CordaSerializable
import java.util.regex.Pattern
/**
* Versions of the same [major] version but with different [minor] versions are considered compatible with each other. One
* exception to this is when the major version is 0 - each different minor version should be considered incompatible.
*
* If two [Version]s are equal (i.e. [equals] returns true) but they are both [snapshot] then they may refer to different
* builds of the node. [NodeVersionInfo.revision] would be required to differentiate the two.
*/
@CordaSerializable
data class Version(val major: Int, val minor: Int, val snapshot: Boolean) {
companion object {
private val pattern = Pattern.compile("""(\d+)\.(\d+)(-SNAPSHOT)?""")
fun parse(string: String): Version {
val matcher = pattern.matcher(string)
require(matcher.matches())
return Version(matcher.group(1).toInt(), matcher.group(2).toInt(), matcher.group(3) != null)
}
}
override fun toString(): String = if (snapshot) "$major.$minor-SNAPSHOT" else "$major.$minor"
}
data class NodeVersionInfo(val version: Version, val revision: String, val vendor: String)

View File

@ -3,11 +3,20 @@ package net.corda.core.node.services
import net.corda.core.contracts.Attachment
import net.corda.core.crypto.SecureHash
import java.io.InputStream
import java.nio.file.Path
/**
* An attachment store records potentially large binary objects, identified by their hash.
*/
interface AttachmentStorage {
/**
* If true, newly inserted attachments will be unzipped to a subdirectory of the [storePath]. This is intended for
* human browsing convenience: the attachment itself will still be the file (that is, edits to the extracted directory
* will not have any effect).
*/
var automaticallyExtractAttachments : Boolean
var storePath : Path
/**
* Returns a handle to a locally stored attachment, or null if it's not known. The handle can be used to open
* a stream for the data, which will be a zip/jar file.

View File

@ -5,12 +5,11 @@ import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.contracts.Contract
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.messaging.MessageRecipients
import net.corda.core.messaging.MessagingService
import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceEntry
import net.corda.core.randomOrNull
import net.corda.core.serialization.CordaSerializable
import rx.Observable
/**
@ -21,6 +20,7 @@ import rx.Observable
*/
interface NetworkMapCache {
@CordaSerializable
sealed class MapChange(val node: NodeInfo) {
class Added(node: NodeInfo) : MapChange(node)
class Removed(node: NodeInfo) : MapChange(node)
@ -73,6 +73,7 @@ interface NetworkMapCache {
/** Look up the node info for a specific peer key. */
fun getNodeByLegalIdentityKey(compositeKey: CompositeKey): NodeInfo?
/** Look up all nodes advertising the service owned by [compositeKey] */
fun getNodesByAdvertisedServiceIdentityKey(compositeKey: CompositeKey): List<NodeInfo> {
return partyNodes.filter { it.advertisedServices.any { it.identity.owningKey == compositeKey } }
@ -108,6 +109,11 @@ interface NetworkMapCache {
/** Checks whether a given party is an advertised notary identity */
fun isNotary(party: Party): Boolean = notaryNodes.any { it.notaryIdentity == party }
/** Checks whether a given party is an advertised validating notary identity */
fun isValidatingNotary(party: Party): Boolean {
return notaryNodes.any { it.notaryIdentity == party && it.advertisedServices.any { it.info.type.isValidatingNotary() }}
}
/**
* Add a network map service; fetches a copy of the latest map from the service and subscribes to any further
* updates.
@ -138,6 +144,7 @@ interface NetworkMapCache {
fun runWithoutMapService()
}
@CordaSerializable
sealed class NetworkCacheError : Exception() {
/** Indicates a failure to deregister, because of a rejected request from the remote node */
class DeregistrationFailed : NetworkCacheError()

View File

@ -1,5 +1,7 @@
package net.corda.core.node.services
import net.corda.core.serialization.CordaSerializable
/**
* A container for additional information for an advertised service.
*
@ -7,6 +9,7 @@ package net.corda.core.node.services
* @param name the service name, used for differentiating multiple services of the same type. Can also be used as a
* grouping identifier for nodes collectively running a distributed service.
*/
@CordaSerializable
data class ServiceInfo(val type: ServiceType, val name: String? = null) {
companion object {
fun parse(encoded: String): ServiceInfo {

View File

@ -1,10 +1,13 @@
package net.corda.core.node.services
import net.corda.core.serialization.CordaSerializable
/**
* Identifier for service types a node can expose over the network to other peers. These types are placed into network
* map advertisements. Services that are purely local and are not providing functionality to other parts of the network
* don't need a declared service type.
*/
@CordaSerializable
sealed class ServiceType(val id: String) {
init {
// Enforce:
@ -43,6 +46,7 @@ sealed class ServiceType(val id: String) {
fun isSubTypeOf(superType: ServiceType) = (id == superType.id) || id.startsWith(superType.id + ".")
fun isNotary() = isSubTypeOf(notary)
fun isValidatingNotary() = isNotary() && id.contains(".validating")
override fun hashCode(): Int = id.hashCode()
override fun toString(): String = id.toString()

View File

@ -3,6 +3,7 @@ package net.corda.core.node.services
import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.contracts.*
import net.corda.core.crypto.*
import net.corda.core.serialization.CordaSerializable
import net.corda.core.toFuture
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction
@ -32,13 +33,12 @@ val DEFAULT_SESSION_ID = 0L
*
* This abstract class has no references to Cash contracts.
*
* [states] Holds the states that are *active* and *relevant*.
* [states] Holds a [VaultService] queried subset of states that are *active* and *relevant*.
* Active means they haven't been consumed yet (or we don't know about it).
* Relevant means they contain at least one of our pubkeys.
*/
class Vault(val states: List<StateAndRef<ContractState>>) {
@Suppress("UNCHECKED_CAST")
inline fun <reified T : ContractState> statesOfType() = states.filter { it.state.data is T } as List<StateAndRef<T>>
@CordaSerializable
class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
/**
* Represents an update observed by the vault that will be notified to observers. Include the [StateRef]s of
@ -48,6 +48,7 @@ class Vault(val states: List<StateAndRef<ContractState>>) {
* If the vault observes multiple transactions simultaneously, where some transactions consume the outputs of some of the
* other transactions observed, then the changes are observed "net" of those.
*/
@CordaSerializable
data class Update(val consumed: Set<StateAndRef<ContractState>>, val produced: Set<StateAndRef<ContractState>>) {
/** Checks whether the update contains a state of the specified type. */
inline fun <reified T : ContractState> containsType() = consumed.any { it.state.data is T } || produced.any { it.state.data is T }
@ -81,6 +82,10 @@ class Vault(val states: List<StateAndRef<ContractState>>) {
companion object {
val NoUpdate = Update(emptySet(), emptySet())
}
enum class StateStatus {
UNCONSUMED, CONSUMED
}
}
/**
@ -92,11 +97,6 @@ class Vault(val states: List<StateAndRef<ContractState>>) {
* Note that transactions we've seen are held by the storage service, not the vault.
*/
interface VaultService {
/**
* Returns a read-only snapshot of the vault at the time the call is made. Note that if you consume states or
* keys in this vault, you must inform the vault service so it can update its internal state.
*/
val currentVault: Vault
/**
* Prefer the use of [updates] unless you know why you want to use this instead.
@ -125,25 +125,13 @@ interface VaultService {
* Atomically get the current vault and a stream of updates. Note that the Observable buffers updates until the
* first subscriber is registered so as to avoid racing with early updates.
*/
fun track(): Pair<Vault, Observable<Vault.Update>>
fun track(): Pair<Vault<ContractState>, Observable<Vault.Update>>
/**
* Returns a snapshot of the heads of LinearStates.
* Return unconsumed [ContractState]s for a given set of [StateRef]s
* TODO: revisit and generalize this exposed API function.
*/
val linearHeads: Map<UniqueIdentifier, StateAndRef<LinearState>>
// TODO: When KT-10399 is fixed, rename this and remove the inline version below.
/** Returns the [linearHeads] only when the type of the state would be considered an 'instanceof' the given type. */
@Suppress("UNCHECKED_CAST")
fun <T : LinearState> linearHeadsOfType_(stateType: Class<T>): Map<UniqueIdentifier, StateAndRef<T>> {
return linearHeads.filterValues { stateType.isInstance(it.state.data) }.mapValues { StateAndRef(it.value.state as TransactionState<T>, it.value.ref) }
}
fun statesForRefs(refs: List<StateRef>): Map<StateRef, TransactionState<*>?> {
val refsToStates = currentVault.states.associateBy { it.ref }
return refs.associateBy({ it }) { refsToStates[it]?.state }
}
fun statesForRefs(refs: List<StateRef>): Map<StateRef, TransactionState<*>?>
/**
* Possibly update the vault by marking as spent states that these transactions consume, and adding any relevant
@ -165,7 +153,7 @@ interface VaultService {
/** Get contracts we would be willing to upgrade the suggested contract to. */
// TODO: We need a better place to put business logic functions
fun getAuthorisedContractUpgrade(ref: StateRef): Class<UpgradedContract<*, *>>?
fun getAuthorisedContractUpgrade(ref: StateRef): Class<out UpgradedContract<*, *>>?
/**
* Authorise a contract state upgrade.
@ -173,7 +161,7 @@ interface VaultService {
* Invoking this method indicate the node is willing to upgrade the [state] using the [upgradedContractClass].
* This method will NOT initiate the upgrade process. To start the upgrade process, see [ContractUpgradeFlow.Instigator].
*/
fun authoriseContractUpgrade(stateAndRef: StateAndRef<*>, upgradedContractClass: Class<UpgradedContract<*, *>>)
fun authoriseContractUpgrade(stateAndRef: StateAndRef<*>, upgradedContractClass: Class<out UpgradedContract<*, *>>)
/**
* Authorise a contract state upgrade.
@ -212,11 +200,26 @@ interface VaultService {
fun generateSpend(tx: TransactionBuilder,
amount: Amount<Currency>,
to: CompositeKey,
onlyFromParties: Set<AnonymousParty>? = null): Pair<TransactionBuilder, List<CompositeKey>>
onlyFromParties: Set<AbstractParty>? = null): Pair<TransactionBuilder, List<CompositeKey>>
/**
* Return [ContractState]s of a given [Contract] type and list of [Vault.StateStatus]
*/
fun <T : ContractState> states(clazzes: Set<Class<T>>, statuses: EnumSet<Vault.StateStatus>): List<StateAndRef<T>>
}
inline fun <reified T : LinearState> VaultService.linearHeadsOfType() = linearHeadsOfType_(T::class.java)
inline fun <reified T : DealState> VaultService.dealsWith(party: Party) = linearHeadsOfType<T>().values.filter {
inline fun <reified T: ContractState> VaultService.unconsumedStates(): List<StateAndRef<T>> =
states(setOf(T::class.java), EnumSet.of(Vault.StateStatus.UNCONSUMED))
inline fun <reified T: ContractState> VaultService.consumedStates(): List<StateAndRef<T>> =
states(setOf(T::class.java), EnumSet.of(Vault.StateStatus.CONSUMED))
/** Returns the [linearState] heads only when the type of the state would be considered an 'instanceof' the given type. */
inline fun <reified T : LinearState> VaultService.linearHeadsOfType() =
states(setOf(T::class.java), EnumSet.of(Vault.StateStatus.UNCONSUMED))
.associateBy { it.state.data.linearId }.mapValues { it.value }
inline fun <reified T : DealState> VaultService.dealsWith(party: AbstractParty) = linearHeadsOfType<T>().values.filter {
it.state.data.parties.any { it == party }
}
@ -261,12 +264,17 @@ interface FileUploader {
fun accepts(type: String): Boolean
}
interface AttachmentsStorageService {
/** Provides access to storage of arbitrary JAR files (which may contain only data, no code). */
val attachments: AttachmentStorage
}
/**
* A sketch of an interface to a simple key/value storage system. Intended for persistence of simple blobs like
* transactions, serialised flow state machines and so on. Again, this isn't intended to imply lack of SQL or
* anything like that, this interface is only big enough to support the prototyping work.
*/
interface StorageService {
interface StorageService : AttachmentsStorageService {
/**
* A map of hash->tx where tx has been signature/contract validated and the states are known to be correct.
* The signatures aren't technically needed after that point, but we keep them around so that we can relay
@ -274,9 +282,6 @@ interface StorageService {
*/
val validatedTransactions: ReadOnlyTransactionStorage
/** Provides access to storage of arbitrary JAR files (which may contain only data, no code). */
val attachments: AttachmentStorage
@Suppress("DEPRECATION")
@Deprecated("This service will be removed in a future milestone")
val uploaders: List<FileUploader>

View File

@ -2,8 +2,10 @@ package net.corda.core.node.services
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.StateMachineRunId
import net.corda.core.serialization.CordaSerializable
import rx.Observable
@CordaSerializable
data class StateMachineTransactionMapping(val stateMachineRunId: StateMachineRunId, val transactionId: SecureHash)
/**

View File

@ -3,6 +3,7 @@ package net.corda.core.node.services
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.serialization.CordaSerializable
/**
* A service that records input states of the given transaction and provides conflict information
@ -15,6 +16,7 @@ interface UniquenessProvider {
fun commit(states: List<StateRef>, txId: SecureHash, callerIdentity: Party)
/** Specifies the consuming transaction for every conflicting state */
@CordaSerializable
data class Conflict(val stateHistory: Map<StateRef, ConsumingTx>)
/**
@ -26,7 +28,9 @@ interface UniquenessProvider {
* This allows a party to just submit invalid transactions with outputs it was aware of and
* find out where exactly they were spent.
*/
@CordaSerializable
data class ConsumingTx(val id: SecureHash, val inputIndex: Int, val requestingParty: Party)
}
@CordaSerializable
class UniquenessException(val error: UniquenessProvider.Conflict) : Exception()

View File

@ -1,5 +1,6 @@
package net.corda.core.schemas
import io.requery.Persistable
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateRef
import net.corda.core.serialization.toHexString
@ -48,7 +49,7 @@ abstract class MappedSchema(schemaFamily: Class<*>,
* A super class for all mapped states exported to a schema that ensures the [StateRef] appears on the database row. The
* [StateRef] will be set to the correct value by the framework (there's no need to set during mapping generation by the state itself).
*/
@MappedSuperclass open class PersistentState(@EmbeddedId var stateRef: PersistentStateRef? = null)
@MappedSuperclass open class PersistentState(@EmbeddedId var stateRef: PersistentStateRef? = null) : Persistable
/**
* Embedded [StateRef] representation used in state mapping.
@ -62,5 +63,9 @@ data class PersistentStateRef(
var index: Int?
) : Serializable {
constructor(stateRef: StateRef) : this(stateRef.txhash.bytes.toHexString(), stateRef.index)
/*
JPA Query requirement:
@Entity classes should have a default (non-arg) constructor to instantiate the objects when retrieving them from the database.
*/
constructor() : this(null, null)
}

View File

@ -0,0 +1,26 @@
package net.corda.core.schemas.requery
import io.requery.Key
import io.requery.Persistable
import io.requery.Superclass
import net.corda.core.contracts.StateRef
import javax.persistence.Column
object Requery {
/**
* A super class for all mapped states exported to a schema that ensures the [StateRef] appears on the database row. The
* [StateRef] will be set to the correct value by the framework (there's no need to set during mapping generation by the state itself).
*/
// TODO: this interface will supercede the existing [PersistentState] interface defined in PersistentTypes.kt
// once we cut-over all existing Hibernate ContractState persistence to Requery
@Superclass interface PersistentState : Persistable {
@get:Key
@get:Column(name = "transaction_id", length = 64)
var txId: String
@get:Key
@get:Column(name = "output_index")
var index: Int
}
}

View File

@ -0,0 +1,28 @@
package net.corda.core.schemas.requery.converters
import io.requery.Converter
import java.sql.Blob
import javax.sql.rowset.serial.SerialBlob
/**
* Converts from a [ByteArray] to a [Blob].
*/
class BlobConverter : Converter<ByteArray, Blob> {
override fun getMappedType(): Class<ByteArray> = ByteArray::class.java
override fun getPersistedType(): Class<Blob> = Blob::class.java
/**
* creates BLOB(INT.MAX) = 2 GB
*/
override fun getPersistedSize(): Int? = null
override fun convertToPersisted(value: ByteArray?): Blob? {
return value?.let { SerialBlob(value) }
}
override fun convertToMapped(type: Class<out ByteArray>?, value: Blob?): ByteArray? {
return value?.getBytes(1, value.length().toInt())
}
}

View File

@ -0,0 +1,30 @@
package net.corda.core.schemas.requery.converters
import io.requery.Converter
import java.sql.*
import java.time.*
/**
* Converts from a [Instant] to a [java.sql.Timestamp] for Java 8. Note that
* when converting between the time type and the database type all times will be converted to the
* UTC zone offset.
*/
class InstantConverter : Converter<Instant, Timestamp> {
override fun getMappedType(): Class<Instant> { return Instant::class.java }
override fun getPersistedType(): Class<Timestamp> { return Timestamp::class.java }
override fun getPersistedSize(): Int? { return null }
override fun convertToPersisted(value: Instant?): Timestamp? {
if (value == null) { return null }
return Timestamp.from(value)
}
override fun convertToMapped(type: Class<out Instant>, value: Timestamp?): Instant? {
if (value == null) { return null }
return value.toInstant()
}
}

View File

@ -0,0 +1,28 @@
package net.corda.core.schemas.requery.converters
import io.requery.Converter
import net.corda.core.crypto.SecureHash
/**
* Convert from a [SecureHash] to a [String]
*/
class SecureHashConverter : Converter<SecureHash, String> {
override fun getMappedType(): Class<SecureHash> = SecureHash::class.java
override fun getPersistedType(): Class<String> = String::class.java
/**
* SecureHash consists of 32 bytes which need VARCHAR(64) in hex
* TODO: think about other hash widths
*/
override fun getPersistedSize(): Int? = 64
override fun convertToPersisted(value: SecureHash?): String? {
return value?.toString()
}
override fun convertToMapped(type: Class<out SecureHash>, value: String?): SecureHash? {
return value?.let { SecureHash.parse(value) }
}
}

View File

@ -0,0 +1,28 @@
package net.corda.core.schemas.requery.converters
import io.requery.Converter
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.SecureHash
/**
* Converts from a [StateRef] to a Composite Key defined by a [String] txnHash and an [Int] index
*/
class StateRefConverter : Converter<StateRef, Pair<String, Int>> {
override fun getMappedType(): Class<StateRef> { return StateRef::class.java }
@Suppress("UNCHECKED_CAST")
override fun getPersistedType(): Class<Pair<String,Int>> { return Pair::class.java as Class<Pair<String,Int>> }
override fun getPersistedSize(): Int? { return null }
override fun convertToPersisted(value: StateRef?): Pair<String,Int>? {
if (value == null) { return null }
return Pair(value.txhash.toString(), value.index)
}
override fun convertToMapped(type: Class<out StateRef>, value: Pair<String,Int>?): StateRef? {
if (value == null) { return null }
return StateRef(SecureHash.parse(value.first), value.second)
}
}

View File

@ -0,0 +1,15 @@
package net.corda.core.schemas.requery.converters
import io.requery.Converter
import io.requery.converter.EnumOrdinalConverter
import io.requery.sql.Mapping
import net.corda.core.contracts.ContractState
import net.corda.core.node.services.Vault
import java.sql.*
import java.time.*
/**
* Converter which persists a [Vault.StateStatus] enum using its enum ordinal representation
*/
class VaultStateStatusConverter() : EnumOrdinalConverter<Vault.StateStatus>(Vault.StateStatus::class.java)

View File

@ -9,6 +9,7 @@ import java.util.*
* In an ideal JVM this would be a value type and be completely overhead free. Project Valhalla is adding such
* functionality to Java, but it won't arrive for a few years yet!
*/
@CordaSerializable
open class OpaqueBytes(val bytes: ByteArray) {
init {
check(bytes.isNotEmpty())

View File

@ -0,0 +1,178 @@
package net.corda.core.serialization
import com.esotericsoftware.kryo.*
import com.esotericsoftware.kryo.util.DefaultClassResolver
import com.esotericsoftware.kryo.util.Util
import net.corda.core.node.AttachmentsClassLoader
import net.corda.core.utilities.loggerFor
import java.io.PrintWriter
import java.lang.reflect.Modifier
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.StandardOpenOption
import java.util.*
fun Kryo.addToWhitelist(type: Class<*>) {
((classResolver as? CordaClassResolver)?.whitelist as? MutableClassWhitelist)?.add(type)
}
fun makeStandardClassResolver(): ClassResolver {
return CordaClassResolver(GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()))
}
fun makeNoWhitelistClassResolver(): ClassResolver {
return CordaClassResolver(AllWhitelist)
}
class CordaClassResolver(val whitelist: ClassWhitelist) : DefaultClassResolver() {
companion object {
private val logger = loggerFor<CordaClassResolver>()
}
/** Returns the registration for the specified class, or null if the class is not registered. */
override fun getRegistration(type: Class<*>): Registration? {
return super.getRegistration(type) ?: checkClass(type)
}
private var whitelistEnabled = true
fun disableWhitelist() {
whitelistEnabled = false
}
fun enableWhitelist() {
whitelistEnabled = true
}
private fun checkClass(type: Class<*>): Registration? {
/** If call path has disabled whitelisting (see [CordaKryo.register]), just return without checking. */
if(!whitelistEnabled) return null
// Allow primitives, abstracts and interfaces
if (type.isPrimitive || type == Any::class.java || Modifier.isAbstract(type.modifiers) || type==String::class.java) return null
// If array, recurse on element type
if (type.isArray) {
return checkClass(type.componentType)
}
if (!type.isEnum && Enum::class.java.isAssignableFrom(type)) {
// Specialised enum entry, so just resolve the parent Enum type since cannot annotate the specialised entry.
return checkClass(type.superclass)
}
// It's safe to have the Class already, since Kryo loads it with initialisation off.
val hasAnnotation = checkForAnnotation(type)
if (!hasAnnotation && !whitelist.hasListed(type)) {
throw KryoException("Class ${Util.className(type)} is not annotated or on the whitelist, so cannot be used in serialization")
}
return null
}
override fun registerImplicit(type: Class<*>): Registration {
// We have to set reference to true, since the flag influences how String fields are treated and we want it to be consistent.
val references = kryo.references
try {
kryo.references = true
return register(Registration(type, kryo.getDefaultSerializer(type), NAME.toInt()))
} finally {
kryo.references = references
}
}
// We don't allow the annotation for classes in attachments for now. The class will be on the main classpath if we have the CorDapp installed.
// We also do not allow extension of KryoSerializable for annotated classes, or combination with @DefaultSerializer for custom serialisation.
// TODO: Later we can support annotations on attachment classes and spin up a proxy via bytecode that we know is harmless.
private fun checkForAnnotation(type: Class<*>): Boolean {
return (type.classLoader !is AttachmentsClassLoader)
&& !KryoSerializable::class.java.isAssignableFrom(type)
&& !type.isAnnotationPresent(DefaultSerializer::class.java)
&& (type.isAnnotationPresent(CordaSerializable::class.java) || hasAnnotationOnInterface(type))
}
// Recursively check interfaces for our annotation.
private fun hasAnnotationOnInterface(type: Class<*>): Boolean {
return type.interfaces.any { it.isAnnotationPresent(CordaSerializable::class.java) || hasAnnotationOnInterface(it) }
|| (type.superclass != null && hasAnnotationOnInterface(type.superclass))
}
}
interface ClassWhitelist {
fun hasListed(type: Class<*>): Boolean
}
interface MutableClassWhitelist : ClassWhitelist {
fun add(entry: Class<*>)
}
object EmptyWhitelist : ClassWhitelist {
override fun hasListed(type: Class<*>): Boolean = false
}
class BuiltInExceptionsWhitelist : ClassWhitelist {
override fun hasListed(type: Class<*>): Boolean = Throwable::class.java.isAssignableFrom(type) && type.`package`.name.startsWith("java.")
}
object AllWhitelist : ClassWhitelist {
override fun hasListed(type: Class<*>): Boolean = true
}
// TODO: Need some concept of from which class loader
class GlobalTransientClassWhiteList(val delegate: ClassWhitelist) : MutableClassWhitelist, ClassWhitelist by delegate {
companion object {
val whitelist: MutableSet<String> = Collections.synchronizedSet(mutableSetOf())
}
override fun hasListed(type: Class<*>): Boolean {
return (type.name in whitelist) || delegate.hasListed(type)
}
override fun add(entry: Class<*>) {
whitelist += entry.name
}
}
/**
* This class is not currently used, but can be installed to log a large number of missing entries from the whitelist
* and was used to track down the initial set.
*/
class LoggingWhitelist(val delegate: ClassWhitelist, val global: Boolean = true) : MutableClassWhitelist {
companion object {
val log = loggerFor<LoggingWhitelist>()
val globallySeen: MutableSet<String> = Collections.synchronizedSet(mutableSetOf())
val journalWriter: PrintWriter? = openOptionalDynamicWhitelistJournal()
private fun openOptionalDynamicWhitelistJournal(): PrintWriter? {
val fileName = System.getenv("WHITELIST_FILE")
if (fileName != null && fileName.isNotEmpty()) {
try {
return PrintWriter(Files.newBufferedWriter(Paths.get(fileName), StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND, StandardOpenOption.WRITE), true)
} catch(ioEx: Exception) {
log.error("Could not open/create whitelist journal file for append: $fileName", ioEx)
}
}
return null
}
}
private val locallySeen: MutableSet<String> = mutableSetOf()
private val alreadySeen: MutableSet<String> get() = if (global) globallySeen else locallySeen
override fun hasListed(type: Class<*>): Boolean {
if (type.name !in alreadySeen && !delegate.hasListed(type)) {
alreadySeen += type.name
val className = Util.className(type)
log.warn("Dynamically whitelisted class $className")
if (journalWriter != null) {
journalWriter.println(className)
}
}
return true
}
override fun add(entry: Class<*>) {
if (delegate is MutableClassWhitelist) {
delegate.add(entry)
} else {
throw UnsupportedOperationException("Cannot add to whitelist since delegate whitelist is not mutable.")
}
}
}

View File

@ -0,0 +1,19 @@
package net.corda.core.serialization
import java.lang.annotation.Inherited
/**
* This annotation is a marker to indicate that a class is permitted and intended to be serialized as part of Node messaging.
*
* Strictly speaking, it is critical to identifying that a class is intended to be deserialized by the node, to avoid
* a security compromise later when a vulnerability is discovered in the deserialisation of a class that just happens to
* be on the classpath, perhaps from a 3rd party library, as has been witnessed elsewhere.
*
* It also makes it possible for a code reviewer to clearly identify the classes that can be passed on the wire.
*
* TODO: As we approach a long term wire format, this annotation will only be permitted on classes that meet certain criteria.
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@Inherited
annotation class CordaSerializable

View File

@ -0,0 +1,85 @@
package net.corda.core.serialization
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer
import com.esotericsoftware.kryo.serializers.FieldSerializer
import com.esotericsoftware.kryo.util.MapReferenceResolver
import de.javakaffee.kryoserializers.ArraysAsListSerializer
import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer
import de.javakaffee.kryoserializers.guava.*
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.MetaData
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.NonEmptySet
import net.corda.core.utilities.NonEmptySetSerializer
import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey
import org.objenesis.strategy.StdInstantiatorStrategy
import java.io.BufferedInputStream
import java.util.*
object DefaultKryoCustomizer {
private val pluginRegistries: List<CordaPluginRegistry> by lazy {
// No ClassResolver only constructor. MapReferenceResolver is the default as used by Kryo in other constructors.
val unusedKryo = Kryo(makeStandardClassResolver(), MapReferenceResolver())
val customization = KryoSerializationCustomization(unusedKryo)
ServiceLoader.load(CordaPluginRegistry::class.java).toList().filter { it.customizeSerialization(customization) }
}
fun customize(kryo: Kryo): Kryo {
return kryo.apply {
// Store a little schema of field names in the stream the first time a class is used which increases tolerance
// for change to a class.
setDefaultSerializer(CompatibleFieldSerializer::class.java)
// Take the safest route here and allow subclasses to have fields named the same as super classes.
fieldSerializerConfig.setCachedFieldNameStrategy(FieldSerializer.CachedFieldNameStrategy.EXTENDED)
// Allow construction of objects using a JVM backdoor that skips invoking the constructors, if there is no
// no-arg constructor available.
instantiatorStrategy = Kryo.DefaultInstantiatorStrategy(StdInstantiatorStrategy())
register(Arrays.asList("").javaClass, ArraysAsListSerializer())
register(SignedTransaction::class.java, ImmutableClassSerializer(SignedTransaction::class))
register(WireTransaction::class.java, WireTransactionSerializer)
register(SerializedBytes::class.java, SerializedBytesSerializer)
UnmodifiableCollectionsSerializer.registerSerializers(this)
ImmutableListSerializer.registerSerializers(this)
ImmutableSetSerializer.registerSerializers(this)
ImmutableSortedSetSerializer.registerSerializers(this)
ImmutableMapSerializer.registerSerializers(this)
ImmutableMultimapSerializer.registerSerializers(this)
register(BufferedInputStream::class.java, InputStreamSerializer)
register(Class.forName("sun.net.www.protocol.jar.JarURLConnection\$JarURLInputStream"), InputStreamSerializer)
noReferencesWithin<WireTransaction>()
register(EdDSAPublicKey::class.java, Ed25519PublicKeySerializer)
register(EdDSAPrivateKey::class.java, Ed25519PrivateKeySerializer)
// Using a custom serializer for compactness
register(CompositeKey.Node::class.java, CompositeKeyNodeSerializer)
register(CompositeKey.Leaf::class.java, CompositeKeyLeafSerializer)
// Exceptions. We don't bother sending the stack traces as the client will fill in its own anyway.
register(Array<StackTraceElement>::class, read = { kryo, input -> emptyArray() }, write = { kryo, output, obj -> })
// This ensures a NonEmptySetSerializer is constructed with an initial value.
register(NonEmptySet::class.java, NonEmptySetSerializer)
/** This ensures any kotlin objects that implement [DeserializeAsKotlinObjectDef] are read back in as singletons. */
addDefaultSerializer(DeserializeAsKotlinObjectDef::class.java, KotlinObjectSerializer)
addDefaultSerializer(SerializeAsToken::class.java, SerializeAsTokenSerializer<SerializeAsToken>())
register(MetaData::class.java, MetaDataSerializer)
register(BitSet::class.java, ReferencesAwareJavaSerializer)
val customization = KryoSerializationCustomization(this)
pluginRegistries.forEach { it.customizeSerialization(customization) }
}
}
}

View File

@ -1,37 +1,26 @@
package net.corda.core.serialization
import co.paralleluniverse.fibers.Fiber
import co.paralleluniverse.io.serialization.kryo.KryoSerializer
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.Kryo.DefaultInstantiatorStrategy
import com.esotericsoftware.kryo.KryoException
import com.esotericsoftware.kryo.Registration
import com.esotericsoftware.kryo.Serializer
import com.esotericsoftware.kryo.*
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import com.esotericsoftware.kryo.serializers.JavaSerializer
import com.esotericsoftware.kryo.serializers.MapSerializer
import de.javakaffee.kryoserializers.ArraysAsListSerializer
import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer
import de.javakaffee.kryoserializers.guava.*
import com.esotericsoftware.kryo.util.MapReferenceResolver
import net.corda.core.contracts.*
import net.corda.core.crypto.*
import net.corda.core.node.AttachmentsClassLoader
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.NonEmptySet
import net.corda.core.utilities.NonEmptySetSerializer
import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
import org.objenesis.strategy.StdInstantiatorStrategy
import java.io.*
import java.lang.reflect.InvocationTargetException
import java.nio.file.Files
import java.nio.file.Path
import java.security.PublicKey
import java.security.spec.InvalidKeySpecException
import java.time.Instant
import java.util.*
import javax.annotation.concurrent.ThreadSafe
@ -64,37 +53,55 @@ import kotlin.reflect.jvm.javaType
* in invalid states, thus violating system invariants. It isn't designed to handle malicious streams and therefore,
* isn't usable beyond the prototyping stage. But that's fine: we can revisit serialisation technologies later after
* a formal evaluation process.
*
* We now distinguish between internal, storage related Kryo and external, network facing Kryo. We presently use
* some non-whitelisted classes as part of internal storage.
* TODO: eliminate internal, storage related whitelist issues, such as private keys in blob storage.
*/
// A convenient instance of Kryo pre-configured with some useful things. Used as a default by various functions.
val THREAD_LOCAL_KRYO: ThreadLocal<Kryo> = ThreadLocal.withInitial { createKryo() }
private val THREAD_LOCAL_KRYO: ThreadLocal<Kryo> = ThreadLocal.withInitial { createKryo() }
// Same again, but this has whitelisting turned off for internal storage use only.
private val INTERNAL_THREAD_LOCAL_KRYO: ThreadLocal<Kryo> = ThreadLocal.withInitial { createInternalKryo() }
fun threadLocalP2PKryo(): Kryo = THREAD_LOCAL_KRYO.get()
fun threadLocalStorageKryo(): Kryo = INTERNAL_THREAD_LOCAL_KRYO.get()
/**
* A type safe wrapper around a byte array that contains a serialised object. You can call [SerializedBytes.deserialize]
* to get the original object back.
*/
class SerializedBytes<T : Any>(bytes: ByteArray) : OpaqueBytes(bytes) {
class SerializedBytes<T : Any>(bytes: ByteArray, val internalOnly: Boolean = false) : OpaqueBytes(bytes) {
// It's OK to use lazy here because SerializedBytes is configured to use the ImmutableClassSerializer.
val hash: SecureHash by lazy { bytes.sha256() }
fun writeToFile(path: Path): Path = Files.write(path, bytes)
}
// "corda" + majorVersionByte + minorVersionMSB + minorVersionLSB
private val KryoHeaderV0_1: OpaqueBytes = OpaqueBytes("corda\u0000\u0000\u0001".toByteArray())
// Some extension functions that make deserialisation convenient and provide auto-casting of the result.
fun <T : Any> ByteArray.deserialize(kryo: Kryo = THREAD_LOCAL_KRYO.get()): T {
@Suppress("UNCHECKED_CAST")
return kryo.readClassAndObject(Input(this)) as T
fun <T : Any> ByteArray.deserialize(kryo: Kryo = threadLocalP2PKryo()): T {
Input(this).use {
val header = OpaqueBytes(it.readBytes(8))
if (header != KryoHeaderV0_1) {
throw KryoException("Serialized bytes header does not match any known format.")
}
@Suppress("UNCHECKED_CAST")
return kryo.readClassAndObject(it) as T
}
}
fun <T : Any> OpaqueBytes.deserialize(kryo: Kryo = THREAD_LOCAL_KRYO.get()): T {
fun <T : Any> OpaqueBytes.deserialize(kryo: Kryo = threadLocalP2PKryo()): T {
return this.bytes.deserialize(kryo)
}
// The more specific deserialize version results in the bytes being cached, which is faster.
@JvmName("SerializedBytesWireTransaction")
fun SerializedBytes<WireTransaction>.deserialize(kryo: Kryo = THREAD_LOCAL_KRYO.get()): WireTransaction = WireTransaction.deserialize(this, kryo)
fun SerializedBytes<WireTransaction>.deserialize(kryo: Kryo = threadLocalP2PKryo()): WireTransaction = WireTransaction.deserialize(this, kryo)
fun <T : Any> SerializedBytes<T>.deserialize(kryo: Kryo = THREAD_LOCAL_KRYO.get()): T = bytes.deserialize(kryo)
fun <T : Any> SerializedBytes<T>.deserialize(kryo: Kryo = if (internalOnly) threadLocalStorageKryo() else threadLocalP2PKryo()): T = bytes.deserialize(kryo)
/**
* A serialiser that avoids writing the wrapper class to the byte stream, thus ensuring [SerializedBytes] is a pure
@ -115,12 +122,13 @@ object SerializedBytesSerializer : Serializer<SerializedBytes<Any>>() {
* Can be called on any object to convert it to a byte array (wrapped by [SerializedBytes]), regardless of whether
* the type is marked as serializable or was designed for it (so be careful!).
*/
fun <T : Any> T.serialize(kryo: Kryo = THREAD_LOCAL_KRYO.get()): SerializedBytes<T> {
fun <T : Any> T.serialize(kryo: Kryo = threadLocalP2PKryo(), internalOnly: Boolean = false): SerializedBytes<T> {
val stream = ByteArrayOutputStream()
Output(stream).use {
it.writeBytes(KryoHeaderV0_1.bytes)
kryo.writeClassAndObject(it, this)
}
return SerializedBytes(stream.toByteArray())
return SerializedBytes(stream.toByteArray(), internalOnly)
}
/**
@ -261,6 +269,7 @@ fun Input.readBytesWithLength(): ByteArray {
}
/** Thrown during deserialisation to indicate that an attachment needed to construct the [WireTransaction] is not found */
@CordaSerializable
class MissingAttachmentsException(val ids: List<SecureHash>) : Exception()
/** A serialisation engine that knows how to deserialise code inside a sandbox */
@ -389,65 +398,55 @@ object KotlinObjectSerializer : Serializer<DeserializeAsKotlinObjectDef>() {
override fun write(kryo: Kryo, output: Output, obj: DeserializeAsKotlinObjectDef) {}
}
fun createKryo(k: Kryo = Kryo()): Kryo {
return k.apply {
// Allow any class to be deserialized (this is insecure but for prototyping we don't care)
isRegistrationRequired = false
// Allow construction of objects using a JVM backdoor that skips invoking the constructors, if there is no
// no-arg constructor available.
instantiatorStrategy = DefaultInstantiatorStrategy(StdInstantiatorStrategy())
// No ClassResolver only constructor. MapReferenceResolver is the default as used by Kryo in other constructors.
fun createInternalKryo(k: Kryo = CordaKryo(makeNoWhitelistClassResolver())): Kryo {
return DefaultKryoCustomizer.customize(k)
}
register(Arrays.asList("").javaClass, ArraysAsListSerializer())
// No ClassResolver only constructor. MapReferenceResolver is the default as used by Kryo in other constructors.
fun createKryo(k: Kryo = CordaKryo(makeStandardClassResolver())): Kryo {
return DefaultKryoCustomizer.customize(k)
}
// Because we like to stick a Kryo object in a ThreadLocal to speed things up a bit, we can end up trying to
// serialise the Kryo object itself when suspending a fiber. That's dumb, useless AND can cause crashes, so
// we avoid it here.
register(Kryo::class,
read = { kryo, input -> createKryo((Fiber.getFiberSerializer() as KryoSerializer).kryo) },
write = { kryo, output, obj -> }
)
/**
* We need to disable whitelist checking during calls from our Kryo code to register a serializer, since it checks
* for existing registrations and then will enter our [CordaClassResolver.getRegistration] method.
*/
open class CordaKryo(classResolver: ClassResolver) : Kryo(classResolver, MapReferenceResolver()) {
override fun register(type: Class<*>?): Registration {
(classResolver as? CordaClassResolver)?.disableWhitelist()
try {
return super.register(type)
} finally {
(classResolver as? CordaClassResolver)?.enableWhitelist()
}
}
register(EdDSAPublicKey::class.java, Ed25519PublicKeySerializer)
register(EdDSAPrivateKey::class.java, Ed25519PrivateKeySerializer)
register(Instant::class.java, ReferencesAwareJavaSerializer)
override fun register(type: Class<*>?, id: Int): Registration {
(classResolver as? CordaClassResolver)?.disableWhitelist()
try {
return super.register(type, id)
} finally {
(classResolver as? CordaClassResolver)?.enableWhitelist()
}
}
// Using a custom serializer for compactness
register(CompositeKey.Node::class.java, CompositeKeyNodeSerializer)
register(CompositeKey.Leaf::class.java, CompositeKeyLeafSerializer)
override fun register(type: Class<*>?, serializer: Serializer<*>?): Registration {
(classResolver as? CordaClassResolver)?.disableWhitelist()
try {
return super.register(type, serializer)
} finally {
(classResolver as? CordaClassResolver)?.enableWhitelist()
}
}
// Some classes have to be handled with the ImmutableClassSerializer because they need to have their
// constructors be invoked (typically for lazy members).
register(SignedTransaction::class.java, ImmutableClassSerializer(SignedTransaction::class))
// This class has special handling.
register(WireTransaction::class.java, WireTransactionSerializer)
// This ensures a SerializedBytes<Foo> wrapper is written out as just a byte array.
register(SerializedBytes::class.java, SerializedBytesSerializer)
addDefaultSerializer(SerializeAsToken::class.java, SerializeAsTokenSerializer<SerializeAsToken>())
// This is required to make all the unit tests pass
register(Party::class.java)
// This ensures a NonEmptySetSerializer is constructed with an initial value.
register(NonEmptySet::class.java, NonEmptySetSerializer)
register(Array<StackTraceElement>::class, read = { kryo, input -> emptyArray() }, write = { kryo, output, o -> })
/** This ensures any kotlin objects that implement [DeserializeAsKotlinObjectDef] are read back in as singletons. */
addDefaultSerializer(DeserializeAsKotlinObjectDef::class.java, KotlinObjectSerializer)
addDefaultSerializer(BufferedInputStream::class.java, InputStreamSerializer)
UnmodifiableCollectionsSerializer.registerSerializers(k)
ImmutableListSerializer.registerSerializers(k)
ImmutableSetSerializer.registerSerializers(k)
ImmutableSortedSetSerializer.registerSerializers(k)
ImmutableMapSerializer.registerSerializers(k)
ImmutableMultimapSerializer.registerSerializers(k)
noReferencesWithin<WireTransaction>()
override fun register(registration: Registration?): Registration {
(classResolver as? CordaClassResolver)?.disableWhitelist()
try {
return super.register(registration)
} finally {
(classResolver as? CordaClassResolver)?.enableWhitelist()
}
}
}
@ -554,3 +553,32 @@ object OrderedSerializer : Serializer<HashMap<Any, Any>>() {
return hm
}
}
/** For serialising a MetaData object. */
@ThreadSafe
object MetaDataSerializer : Serializer<MetaData>() {
override fun write(kryo: Kryo, output: Output, obj: MetaData) {
output.writeString(obj.schemeCodeName)
output.writeString(obj.versionID)
kryo.writeClassAndObject(output, obj.signatureType)
kryo.writeClassAndObject(output, obj.timestamp)
kryo.writeClassAndObject(output, obj.visibleInputs)
kryo.writeClassAndObject(output, obj.signedInputs)
output.writeBytesWithLength(obj.merkleRoot)
output.writeBytesWithLength(obj.publicKey.encoded)
}
@Suppress("UNCHECKED_CAST")
@Throws(IllegalArgumentException::class, InvalidKeySpecException::class)
override fun read(kryo: Kryo, input: Input, type: Class<MetaData>): MetaData {
val schemeCodeName = input.readString()
val versionID = input.readString()
val signatureType = kryo.readClassAndObject(input) as SignatureType
val timestamp = kryo.readClassAndObject(input) as Instant?
val visibleInputs = kryo.readClassAndObject(input) as BitSet?
val signedInputs = kryo.readClassAndObject(input) as BitSet?
val merkleRoot = input.readBytesWithLength()
val publicKey = Crypto.decodePublicKey(input.readBytesWithLength(), schemeCodeName)
return MetaData(schemeCodeName, versionID, signatureType, timestamp, visibleInputs, signedInputs, merkleRoot, publicKey)
}
}

View File

@ -0,0 +1,13 @@
package net.corda.core.serialization
import com.esotericsoftware.kryo.Kryo
interface SerializationCustomization {
fun addToWhitelist(type: Class<*>)
}
class KryoSerializationCustomization(val kryo: Kryo) : SerializationCustomization {
override fun addToWhitelist(type: Class<*>) {
kryo.addToWhitelist(type)
}
}

View File

@ -22,6 +22,7 @@ import java.util.*
*
* This models a similar pattern to the readReplace/writeReplace methods in Java serialization.
*/
@CordaSerializable
interface SerializeAsToken {
fun toToken(context: SerializeAsTokenContext): SerializationToken
}
@ -100,6 +101,7 @@ class SerializeAsTokenContext(toBeTokenized: Any, kryo: Kryo = createKryo()) {
* A class representing a [SerializationToken] for some object that is not serializable but can be looked up
* (when deserialized) via just the class name.
*/
@CordaSerializable
data class SingletonSerializationToken private constructor(private val className: String) : SerializationToken {
constructor(toBeTokenized: SerializeAsToken) : this(toBeTokenized.javaClass.name)

View File

@ -45,6 +45,12 @@ class CompositeKeyGenerator : Generator<CompositeKey>(CompositeKey::class.java)
}
}
class AnonymousPartyGenerator : Generator<AnonymousParty>(AnonymousParty::class.java) {
override fun generate(random: SourceOfRandomness, status: GenerationStatus): AnonymousParty {
return AnonymousParty(CompositeKeyGenerator().generate(random, status))
}
}
class PartyGenerator : Generator<Party>(Party::class.java) {
override fun generate(random: SourceOfRandomness, status: GenerationStatus): Party {
return Party(StringGenerator().generate(random, status), CompositeKeyGenerator().generate(random, status))
@ -53,7 +59,7 @@ class PartyGenerator : Generator<Party>(Party::class.java) {
class PartyAndReferenceGenerator : Generator<PartyAndReference>(PartyAndReference::class.java) {
override fun generate(random: SourceOfRandomness, status: GenerationStatus): PartyAndReference {
return PartyAndReference(PartyGenerator().generate(random, status), OpaqueBytes(random.nextBytes(16)))
return PartyAndReference(AnonymousPartyGenerator().generate(random, status), OpaqueBytes(random.nextBytes(16)))
}
}

View File

@ -44,12 +44,16 @@ abstract class BaseTransaction(
if (timestamp != null) check(notary != null) { "If a timestamp is provided, there must be a notary." }
}
override fun equals(other: Any?) =
other is BaseTransaction &&
notary == other.notary &&
mustSign == other.mustSign &&
type == other.type &&
timestamp == other.timestamp
override fun equals(other: Any?): Boolean {
if (other === this) return true
return other is BaseTransaction &&
notary == other.notary &&
mustSign == other.mustSign &&
type == other.type &&
timestamp == other.timestamp
}
override fun hashCode() = Objects.hash(notary, mustSign, type, timestamp)
override fun toString(): String = "${javaClass.simpleName}(id=$id)"
}

View File

@ -4,6 +4,7 @@ import net.corda.core.contracts.*
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.serialization.CordaSerializable
/**
* A LedgerTransaction is derived from a [WireTransaction]. It is the result of doing the following operations:
@ -16,6 +17,7 @@ import net.corda.core.crypto.SecureHash
*
* All the above refer to inputs using a (txhash, output index) pair.
*/
@CordaSerializable
class LedgerTransaction(
/** The resolved input states which will be consumed/invalidated by the execution of this transaction. */
override val inputs: List<StateAndRef<*>>,

View File

@ -2,11 +2,10 @@ package net.corda.core.transactions
import net.corda.core.contracts.*
import net.corda.core.crypto.*
import net.corda.core.crypto.SecureHash.Companion.zeroHash
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.createKryo
import net.corda.core.serialization.extendKryoHash
import net.corda.core.serialization.serialize
import java.util.*
fun <T : Any> serializedHash(x: T): SecureHash {
val kryo = extendKryoHash(createKryo()) // Dealing with HashMaps inside states.
@ -14,9 +13,13 @@ fun <T : Any> serializedHash(x: T): SecureHash {
}
/**
* Interface implemented by WireTransaction and FilteredLeaves.
* Property traversableList assures that we always calculate hashes in the same order, lets us define which
* fields of WireTransaction will be included in id calculation or partial merkle tree building.
* Implemented by [WireTransaction] and [FilteredLeaves]. A TraversableTransaction allows you to iterate
* over the flattened components of the underlying transaction structure, taking into account that some
* 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.
*
* The [availableComponents] property is used for calculation of the transaction's [MerkleTree], which is in
* turn used to derive the ID hash.
*/
interface TraversableTransaction {
val inputs: List<StateRef>
@ -29,30 +32,43 @@ interface TraversableTransaction {
val timestamp: Timestamp?
/**
* Traversing transaction fields with a list function over transaction contents. Used for leaves hashes calculation
* and user provided filtering and checking of filtered transaction.
* Returns a flattened list of all the components that are present in the transaction, in the following order:
*
* - Each input that is present
* - Each attachment that is present
* - Each output that is present
* - Each command that is present
* - The notary [Party], if present
* - Each required signer ([mustSign]) that is present
* - The type of the transaction, if present
* - The timestamp of the transaction, if present
*/
// We may want to specify our own behaviour on certain tx fields.
// Like if we include them at all, what to do with null values, if we treat list as one or not etc. for building
// torn-off transaction and id calculation.
val traversableList: List<Any>
val availableComponents: List<Any>
get() {
val traverseList = mutableListOf(inputs, attachments, outputs, commands).flatten().toMutableList()
if (notary != null) traverseList.add(notary!!)
traverseList.addAll(mustSign)
if (type != null) traverseList.add(type!!)
if (timestamp != null) traverseList.add(timestamp!!)
return traverseList
// We may want to specify our own behaviour on certain tx fields.
// Like if we include them at all, what to do with null values, if we treat list as one or not etc. for building
// torn-off transaction and id calculation.
val result = mutableListOf(inputs, attachments, outputs, commands).flatten().toMutableList()
notary?.let { result += it }
result.addAll(mustSign)
type?.let { result += it }
timestamp?.let { result += it }
return result
}
// Calculation of all leaves hashes that are needed for calculation of transaction id and partial Merkle branches.
fun calculateLeavesHashes(): List<SecureHash> = traversableList.map { serializedHash(it) }
/**
* Calculate the hashes of the sub-components of the transaction, that are used to build its Merkle tree.
* The root of the tree is the transaction identifier. The tree structure is helpful for privacy, please
* see the user-guide section "Transaction tear-offs" to learn more about this topic.
*/
val availableComponentHashes: List<SecureHash> get() = availableComponents.map { serializedHash(it) }
}
/**
* Class that holds filtered leaves for a partial Merkle transaction. We assume mixed leaf types, notice that every
* field from WireTransaction can be used in PartialMerkleTree calculation.
* field from [WireTransaction] can be used in [PartialMerkleTree] calculation.
*/
@CordaSerializable
class FilteredLeaves(
override val inputs: List<StateRef>,
override val attachments: List<SecureHash>,
@ -73,7 +89,7 @@ class FilteredLeaves(
* @returns false if no elements were matched on a structure or checkingFun returned false.
*/
fun checkWithFun(checkingFun: (Any) -> Boolean): Boolean {
val checkList = traversableList.map { checkingFun(it) }
val checkList = availableComponents.map { checkingFun(it) }
return (!checkList.isEmpty()) && checkList.all { true }
}
}
@ -84,6 +100,7 @@ class FilteredLeaves(
* @param filteredLeaves Leaves included in a filtered transaction.
* @param partialMerkleTree Merkle branch needed to verify filteredLeaves.
*/
@CordaSerializable
class FilteredTransaction private constructor(
val rootHash: SecureHash,
val filteredLeaves: FilteredLeaves,
@ -99,8 +116,8 @@ class FilteredTransaction private constructor(
filtering: (Any) -> Boolean
): FilteredTransaction {
val filteredLeaves = wtx.filterWithFun(filtering)
val merkleTree = wtx.getMerkleTree()
val pmt = PartialMerkleTree.build(merkleTree, filteredLeaves.calculateLeavesHashes())
val merkleTree = wtx.merkleTree
val pmt = PartialMerkleTree.build(merkleTree, filteredLeaves.availableComponentHashes)
return FilteredTransaction(merkleTree.hash, filteredLeaves, pmt)
}
}
@ -110,17 +127,9 @@ class FilteredTransaction private constructor(
*/
@Throws(MerkleTreeException::class)
fun verify(): Boolean {
val hashes: List<SecureHash> = filteredLeaves.calculateLeavesHashes()
val hashes: List<SecureHash> = filteredLeaves.availableComponentHashes
if (hashes.isEmpty())
throw MerkleTreeException("Transaction without included leaves.")
return partialMerkleTree.verify(rootHash, hashes)
}
/**
* Runs verification of Partial Merkle Branch against [rootHash]. Checks filteredLeaves with provided checkingFun.
*/
@Throws(MerkleTreeException::class)
fun verifyWithFunction(checkingFun: (Any) -> Boolean): Boolean {
return verify() && filteredLeaves.checkWithFun { checkingFun(it) }
}
}

View File

@ -8,6 +8,7 @@ import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.signWithECDSA
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializedBytes
import java.security.KeyPair
import java.security.SignatureException
@ -23,8 +24,7 @@ import java.util.*
* A transaction ID should be the hash of the [WireTransaction] Merkle tree root. Thus adding or removing a signature does not change it.
*/
data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
val sigs: List<DigitalSignature.WithKey>,
override val id: SecureHash
val sigs: List<DigitalSignature.WithKey>
) : NamedByHash {
init {
require(sigs.isNotEmpty())
@ -33,12 +33,16 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
// TODO: This needs to be reworked to ensure that the inner WireTransaction is only ever deserialised sandboxed.
/** Lazily calculated access to the deserialised/hashed transaction data. */
val tx: WireTransaction by lazy {
val temp = WireTransaction.deserialize(txBits)
check(temp.id == id) { "Supplied transaction ID does not match deserialized transaction's ID - this is probably a problem in serialization/deserialization" }
temp
}
val tx: WireTransaction by lazy { WireTransaction.deserialize(txBits) }
/**
* The Merkle root of the inner [WireTransaction]. Note that this is _not_ the same as the simple hash of
* [txBits], which would not use the Merkle tree structure. If the difference isn't clear, please consult
* the user guide section "Transaction tear-offs" to learn more about Merkle trees.
*/
override val id: SecureHash get() = tx.id
@CordaSerializable
class SignaturesMissingException(val missing: Set<CompositeKey>, val descriptions: List<String>, override val id: SecureHash) : NamedByHash, SignatureException() {
override fun toString(): String {
return "Missing signatures for $descriptions on transaction ${id.prefixChars()} for ${missing.joinToString()}"
@ -143,4 +147,6 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
* @return a digital signature of the transaction.
*/
fun signWithECDSA(keyPair: KeyPair) = keyPair.signWithECDSA(this.id.bytes)
override fun toString(): String = "${javaClass.simpleName}(id=$id)"
}

View File

@ -137,7 +137,7 @@ open class TransactionBuilder(
throw IllegalStateException("Missing signatures on the transaction for the public keys: ${missing.joinToString()}")
}
val wtx = toWireTransaction()
return SignedTransaction(wtx.serialize(), ArrayList(currentSigs), wtx.id)
return SignedTransaction(wtx.serialize(), ArrayList(currentSigs))
}
open fun addInputState(stateAndRef: StateAndRef<*>) {

View File

@ -7,11 +7,11 @@ import net.corda.core.crypto.MerkleTree
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.indexOfOrThrow
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.THREAD_LOCAL_KRYO
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.serialization.threadLocalP2PKryo
import net.corda.core.utilities.Emoji
import java.security.PublicKey
@ -42,10 +42,10 @@ class WireTransaction(
@Volatile @Transient private var cachedBytes: SerializedBytes<WireTransaction>? = null
val serialized: SerializedBytes<WireTransaction> get() = cachedBytes ?: serialize().apply { cachedBytes = this }
override val id: SecureHash by lazy { getMerkleTree().hash }
override val id: SecureHash by lazy { merkleTree.hash }
companion object {
fun deserialize(data: SerializedBytes<WireTransaction>, kryo: Kryo = THREAD_LOCAL_KRYO.get()): WireTransaction {
fun deserialize(data: SerializedBytes<WireTransaction>, kryo: Kryo = threadLocalP2PKryo()): WireTransaction {
val wtx = data.bytes.deserialize<WireTransaction>(kryo)
wtx.cachedBytes = data
return wtx
@ -70,7 +70,7 @@ class WireTransaction(
* @throws TransactionResolutionException if an input points to a transaction not found in storage.
*/
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServiceHub): LedgerTransaction {
fun toLedgerTransaction(services: ServicesForResolution): LedgerTransaction {
// Look up public keys to authenticated identities. This is just a stub placeholder and will all change in future.
val authenticatedArgs = commands.map {
val parties = it.signers.mapNotNull { pk -> services.identityService.partyFromKey(pk) }
@ -94,9 +94,7 @@ class WireTransaction(
/**
* Builds whole Merkle tree for a transaction.
*/
fun getMerkleTree(): MerkleTree {
return MerkleTree.getMerkleTree(calculateLeavesHashes())
}
val merkleTree: MerkleTree by lazy { MerkleTree.getMerkleTree(availableComponentHashes) }
/**
* Construction of partial transaction from WireTransaction based on filtering.
@ -119,9 +117,9 @@ class WireTransaction(
override fun toString(): String {
val buf = StringBuilder()
buf.appendln("Transaction $id:")
buf.appendln("Transaction:")
for (input in inputs) buf.appendln("${Emoji.rightArrow}INPUT: $input")
for (output in outputs) buf.appendln("${Emoji.leftArrow}OUTPUT: $output")
for (output in outputs) buf.appendln("${Emoji.leftArrow}OUTPUT: ${output.data}")
for (command in commands) buf.appendln("${Emoji.diamond}COMMAND: $command")
for (attachment in attachments) buf.appendln("${Emoji.paperclip}ATTACHMENT: $attachment")
return buf.toString()

View File

@ -1,5 +1,7 @@
package net.corda.core.utilities
import net.corda.core.codePointsString
/**
* A simple wrapper class that contains icons and support for printing them only when we're connected to a terminal.
*/
@ -7,15 +9,18 @@ object Emoji {
// Unfortunately only Apple has a terminal that can do colour emoji AND an emoji font installed by default.
val hasEmojiTerminal by lazy { listOf("Apple_Terminal", "iTerm.app").contains(System.getenv("TERM_PROGRAM")) }
const val CODE_SANTA_CLAUS = "\ud83c\udf85"
const val CODE_DIAMOND = "\ud83d\udd37"
const val CODE_BAG_OF_CASH = "\ud83d\udcb0"
const val CODE_NEWSPAPER = "\ud83d\udcf0"
const val CODE_RIGHT_ARROW = "\u27a1\ufe0f"
const val CODE_LEFT_ARROW = "\u2b05\ufe0f"
const val CODE_GREEN_TICK = "\u2705"
const val CODE_PAPERCLIP = "\ud83d\udcce"
const val CODE_COOL_GUY = "\ud83d\ude0e"
@JvmStatic val CODE_SANTA_CLAUS: String = codePointsString(0x1F385)
@JvmStatic val CODE_DIAMOND: String = codePointsString(0x1F537)
@JvmStatic val CODE_BAG_OF_CASH: String = codePointsString(0x1F4B0)
@JvmStatic val CODE_NEWSPAPER: String = codePointsString(0x1F4F0)
@JvmStatic val CODE_RIGHT_ARROW: String = codePointsString(0x27A1, 0xFE0F)
@JvmStatic val CODE_LEFT_ARROW: String = codePointsString(0x2B05, 0xFE0F)
@JvmStatic val CODE_GREEN_TICK: String = codePointsString(0x2705)
@JvmStatic val CODE_PAPERCLIP: String = codePointsString(0x1F4CE)
@JvmStatic val CODE_COOL_GUY: String = codePointsString(0x1F60E)
@JvmStatic val CODE_NO_ENTRY: String = codePointsString(0x1F6AB)
@JvmStatic val CODE_SKULL_AND_CROSSBONES: String = codePointsString(0x2620)
@JvmStatic val CODE_BOOKS: String = codePointsString(0x1F4DA)
/**
* When non-null, toString() methods are allowed to use emoji in the output as we're going to render them to a
@ -31,6 +36,7 @@ object Emoji {
val leftArrow: String get() = if (emojiMode.get() != null) "$CODE_LEFT_ARROW " else ""
val paperclip: String get() = if (emojiMode.get() != null) "$CODE_PAPERCLIP " else ""
val coolGuy: String get() = if (emojiMode.get() != null) "$CODE_COOL_GUY " else ""
val books: String get() = if (emojiMode.get() != null) "$CODE_BOOKS " else ""
inline fun <T> renderIfSupported(body: () -> T): T {
emojiMode.set(this) // Could be any object.
@ -55,4 +61,5 @@ object Emoji {
emojiMode.set(null)
}
}
}

View File

@ -1,6 +1,7 @@
package net.corda.core.utilities
import net.corda.core.TransientProperty
import net.corda.core.serialization.CordaSerializable
import rx.Observable
import rx.Subscription
import rx.subjects.BehaviorSubject
@ -32,7 +33,9 @@ import java.util.*
* A progress tracker is *not* thread safe. You may move events from the thread making progress to another thread by
* using the [Observable] subscribeOn call.
*/
@CordaSerializable
class ProgressTracker(vararg steps: Step) {
@CordaSerializable
sealed class Change {
class Position(val tracker: ProgressTracker, val newStep: Step) : Change() {
override fun toString() = newStep.label
@ -48,6 +51,7 @@ class ProgressTracker(vararg steps: Step) {
}
/** The superclass of all step objects. */
@CordaSerializable
open class Step(open val label: String) {
open val changes: Observable<Change> get() = Observable.empty()
open fun childProgressTracker(): ProgressTracker? = null
@ -55,7 +59,7 @@ class ProgressTracker(vararg steps: Step) {
/** This class makes it easier to relabel a step on the fly, to provide transient information. */
open inner class RelabelableStep(currentLabel: String) : Step(currentLabel) {
override val changes = BehaviorSubject.create<Change>()
override val changes: BehaviorSubject<Change> = BehaviorSubject.create()
var currentLabel: String = currentLabel
set(value) {
@ -81,6 +85,7 @@ class ProgressTracker(vararg steps: Step) {
// This field won't be serialized.
private val _changes by TransientProperty { PublishSubject.create<Change>() }
@CordaSerializable
private data class Child(val tracker: ProgressTracker, @Transient val subscription: Subscription?)
private val childProgressTrackers = HashMap<Step, Child>()
@ -105,27 +110,26 @@ class ProgressTracker(vararg steps: Step) {
var currentStep: Step
get() = steps[stepIndex]
set(value) {
if (currentStep != value) {
check(currentStep != DONE) { "Cannot rewind a progress tracker once it reaches the done state" }
check(!hasEnded) { "Cannot rewind a progress tracker once it has ended" }
if (currentStep == value) return
val index = steps.indexOf(value)
require(index != -1)
val index = steps.indexOf(value)
require(index != -1)
if (index < stepIndex) {
// We are going backwards: unlink and unsubscribe from any child nodes that we're rolling back
// through, in preparation for moving through them again.
for (i in stepIndex downTo index) {
removeChildProgressTracker(steps[i])
}
if (index < stepIndex) {
// We are going backwards: unlink and unsubscribe from any child nodes that we're rolling back
// through, in preparation for moving through them again.
for (i in stepIndex downTo index) {
removeChildProgressTracker(steps[i])
}
curChangeSubscription?.unsubscribe()
stepIndex = index
_changes.onNext(Change.Position(this, steps[index]))
curChangeSubscription = currentStep.changes.subscribe { _changes.onNext(it) }
if (currentStep == DONE) _changes.onCompleted()
}
curChangeSubscription?.unsubscribe()
stepIndex = index
_changes.onNext(Change.Position(this, steps[index]))
curChangeSubscription = currentStep.changes.subscribe( { _changes.onNext(it) }, { _changes.onError(it) })
if (currentStep == DONE) _changes.onCompleted()
}
/** Returns the current step, descending into children to find the deepest step we are up to. */
@ -149,6 +153,15 @@ class ProgressTracker(vararg steps: Step) {
_changes.onNext(Change.Structural(this, step))
}
/**
* Ends the progress tracker with the given error, bypassing any remaining steps. [changes] will emit the exception
* as an error.
*/
fun endWithError(error: Throwable) {
check(!hasEnded) { "Progress tracker has already ended" }
_changes.onError(error)
}
/** The parent of this tracker: set automatically by the parent when a tracker is added as a child */
var parent: ProgressTracker? = null
private set
@ -195,6 +208,9 @@ class ProgressTracker(vararg steps: Step) {
* if a step changed its label or rendering).
*/
val changes: Observable<Change> get() = _changes
/** Returns true if the progress tracker has ended, either by reaching the [DONE] step or prematurely with an error */
val hasEnded: Boolean get() = _changes.hasCompleted() || _changes.hasThrowable()
}

View File

@ -11,6 +11,7 @@ import net.corda.core.crypto.signWithECDSA
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.node.recordTransactions
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.ProgressTracker
@ -29,6 +30,7 @@ abstract class AbstractStateReplacementFlow {
*
* @param M the type of a class representing proposed modification by the instigator.
*/
@CordaSerializable
data class Proposal<out M>(val stateRef: StateRef, val modification: M, val stx: SignedTransaction)
/**
@ -63,7 +65,7 @@ abstract class AbstractStateReplacementFlow {
val me = listOf(myKey)
val signatures = if (participants == me) {
listOf(getNotarySignature(stx))
getNotarySignatures(stx)
} else {
collectSignatures(participants - me, stx)
}
@ -87,7 +89,7 @@ abstract class AbstractStateReplacementFlow {
val allPartySignedTx = stx + participantSignatures
val allSignatures = participantSignatures + getNotarySignature(allPartySignedTx)
val allSignatures = participantSignatures + getNotarySignatures(allPartySignedTx)
parties.forEach { send(it, allSignatures) }
return allSignatures
@ -105,7 +107,7 @@ abstract class AbstractStateReplacementFlow {
}
@Suspendable
private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.WithKey {
private fun getNotarySignatures(stx: SignedTransaction): List<DigitalSignature.WithKey> {
progressTracker.currentStep = NOTARY
try {
return subFlow(NotaryFlow.Client(stx))
@ -115,8 +117,10 @@ abstract class AbstractStateReplacementFlow {
}
}
// Type parameter should ideally be Unit but that prevents Java code from subclassing it (https://youtrack.jetbrains.com/issue/KT-15964).
// We use Void? instead of Unit? as that's what you'd use in Java.
abstract class Acceptor<in T>(val otherSide: Party,
override val progressTracker: ProgressTracker = tracker()) : FlowLogic<Unit>() {
override val progressTracker: ProgressTracker = tracker()) : FlowLogic<Void?>() {
companion object {
object VERIFYING : ProgressTracker.Step("Verifying state replacement proposal")
object APPROVING : ProgressTracker.Step("State replacement approved")
@ -126,7 +130,7 @@ abstract class AbstractStateReplacementFlow {
@Suspendable
@Throws(StateReplacementException::class)
override fun call() {
override fun call(): Void? {
progressTracker.currentStep = VERIFYING
val maybeProposal: UntrustworthyData<Proposal<T>> = receive(otherSide)
val stx: SignedTransaction = maybeProposal.unwrap {
@ -135,6 +139,7 @@ abstract class AbstractStateReplacementFlow {
it.stx
}
approve(stx)
return null
}
@Suspendable

View File

@ -3,6 +3,7 @@ package net.corda.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.Party
import net.corda.core.flows.FlowLogic
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
@ -17,6 +18,7 @@ import net.corda.core.transactions.SignedTransaction
*/
class BroadcastTransactionFlow(val notarisedTransaction: SignedTransaction,
val participants: Set<Party>) : FlowLogic<Unit>() {
@CordaSerializable
data class NotifyTxRequest(val tx: SignedTransaction)
@Suspendable

View File

@ -30,10 +30,11 @@ object ContractUpgradeFlow {
val command = commandData.value as UpgradeCommand
val participants: Set<CompositeKey> = input.participants.toSet()
val keysThatSigned: Set<CompositeKey> = commandData.signers.toSet()
@Suppress("UNCHECKED_CAST")
val upgradedContract = command.upgradedContractClass.newInstance() as UpgradedContract<ContractState, *>
requireThat {
"The signing keys include all participant keys" by keysThatSigned.containsAll(participants)
"Inputs state reference the legacy contract" by (input.contract.javaClass == upgradedContract.legacyContract.javaClass)
"Inputs state reference the legacy contract" by (input.contract.javaClass == upgradedContract.legacyContract)
"Outputs state reference the upgraded contract" by (output.contract.javaClass == command.upgradedContractClass)
"Output state must be an upgraded version of the input state" by (output == upgradedContract.upgrade(input))
}
@ -41,17 +42,17 @@ object ContractUpgradeFlow {
private fun <OldState : ContractState, NewState : ContractState> assembleBareTx(
stateRef: StateAndRef<OldState>,
upgradedContractClass: Class<UpgradedContract<OldState, NewState>>
upgradedContractClass: Class<out UpgradedContract<OldState, NewState>>
): TransactionBuilder {
val contractUpgrade = upgradedContractClass.newInstance()
return TransactionType.General.Builder(stateRef.state.notary)
.withItems(stateRef, contractUpgrade.upgrade(stateRef.state.data), Command(UpgradeCommand(contractUpgrade.javaClass), stateRef.state.data.participants))
.withItems(stateRef, contractUpgrade.upgrade(stateRef.state.data), Command(UpgradeCommand(upgradedContractClass), stateRef.state.data.participants))
}
class Instigator<OldState : ContractState, NewState : ContractState>(
class Instigator<OldState : ContractState, out NewState : ContractState>(
originalState: StateAndRef<OldState>,
newContractClass: Class<UpgradedContract<OldState, NewState>>
) : AbstractStateReplacementFlow.Instigator<OldState, NewState, Class<UpgradedContract<OldState, NewState>>>(originalState, newContractClass) {
newContractClass: Class<out UpgradedContract<OldState, NewState>>
) : AbstractStateReplacementFlow.Instigator<OldState, NewState, Class<out UpgradedContract<OldState, NewState>>>(originalState, newContractClass) {
override fun assembleTx(): Pair<SignedTransaction, Iterable<CompositeKey>> {
val stx = assembleBareTx(originalState, modification)
@ -61,10 +62,10 @@ object ContractUpgradeFlow {
}
}
class Acceptor(otherSide: Party) : AbstractStateReplacementFlow.Acceptor<Class<UpgradedContract<ContractState, *>>>(otherSide) {
class Acceptor(otherSide: Party) : AbstractStateReplacementFlow.Acceptor<Class<out UpgradedContract<ContractState, *>>>(otherSide) {
@Suspendable
@Throws(StateReplacementException::class)
override fun verifyProposal(proposal: Proposal<Class<UpgradedContract<ContractState, *>>>) {
override fun verifyProposal(proposal: Proposal<Class<out UpgradedContract<ContractState, *>>>) {
// Retrieve signed transaction from our side, we will apply the upgrade logic to the transaction on our side, and verify outputs matches the proposed upgrade.
val stx = subFlow(FetchTransactionsFlow(setOf(proposal.stateRef.txhash), otherSide)).fromDisk.singleOrNull()
requireNotNull(stx) { "We don't have a copy of the referenced state" }

View File

@ -4,7 +4,6 @@ import net.corda.core.contracts.Attachment
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import java.io.ByteArrayInputStream
import java.io.InputStream
/**
@ -16,18 +15,19 @@ class FetchAttachmentsFlow(requests: Set<SecureHash>,
override fun load(txid: SecureHash): Attachment? = serviceHub.storageService.attachments.openAttachment(txid)
override fun convert(wire: ByteArray): Attachment {
return object : Attachment {
override fun open(): InputStream = ByteArrayInputStream(wire)
override val id: SecureHash = wire.sha256()
override fun equals(other: Any?) = (other is Attachment) && other.id == id
override fun hashCode(): Int = id.hashCode()
}
}
override fun convert(wire: ByteArray): Attachment = ByteArrayAttachment(wire)
override fun maybeWriteToDisk(downloaded: List<Attachment>) {
for (attachment in downloaded) {
serviceHub.storageService.attachments.importAttachment(attachment.open())
}
}
private class ByteArrayAttachment(private val wire : ByteArray) : Attachment {
override val id: SecureHash by lazy { wire.sha256() }
override fun open(): InputStream = wire.inputStream()
override fun equals(other: Any?) = other === this || other is Attachment && other.id == this.id
override fun hashCode(): Int = id.hashCode()
override fun toString(): String = "${javaClass.simpleName}(id=$id)"
}
}

View File

@ -6,6 +6,7 @@ import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.unwrap
import net.corda.flows.FetchDataFlow.DownloadedVsRequestedDataMismatch
@ -32,11 +33,18 @@ abstract class FetchDataFlow<T : NamedByHash, in W : Any>(
protected val requests: Set<SecureHash>,
protected val otherSide: Party) : FlowLogic<FetchDataFlow.Result<T>>() {
@CordaSerializable
class DownloadedVsRequestedDataMismatch(val requested: SecureHash, val got: SecureHash) : IllegalArgumentException()
@CordaSerializable
class DownloadedVsRequestedSizeMismatch(val requested: Int, val got: Int) : IllegalArgumentException()
class HashNotFound(val requested: SecureHash) : FlowException()
@CordaSerializable
data class Request(val hashes: List<SecureHash>)
@CordaSerializable
data class Result<out T : NamedByHash>(val fromDisk: List<T>, val downloaded: List<T>)
@Suspendable

View File

@ -74,8 +74,8 @@ class FinalityFlow(val transactions: Iterable<SignedTransaction>,
return stxnsAndParties.map { pair ->
val stx = pair.first
val notarised = if (needsNotarySignature(stx)) {
val notarySig = subFlow(NotaryFlow.Client(stx))
stx + notarySig
val notarySignatures = subFlow(NotaryFlow.Client(stx))
stx + notarySignatures
} else {
stx
}
@ -84,7 +84,12 @@ class FinalityFlow(val transactions: Iterable<SignedTransaction>,
}
}
private fun needsNotarySignature(stx: SignedTransaction) = stx.tx.notary != null && hasNoNotarySignature(stx)
private fun needsNotarySignature(stx: SignedTransaction): Boolean {
val wtx = stx.tx
val needsNotarisation = wtx.inputs.isNotEmpty() || wtx.timestamp != null
return needsNotarisation && hasNoNotarySignature(stx)
}
private fun hasNoNotarySignature(stx: SignedTransaction): Boolean {
val notaryKey = stx.tx.notary?.owningKey
val signers = stx.sigs.map { it.by }.toSet()
@ -117,7 +122,7 @@ class FinalityFlow(val transactions: Iterable<SignedTransaction>,
// Load and verify each transaction.
return sorted.map { stx ->
val notary = stx.tx.notary
// The notary signature is allowed to be missing but no others.
// The notary signature(s) are allowed to be missing but no others.
val wtx = if (notary != null) stx.verifySignatures(notary.owningKey) else stx.verifySignatures()
val ltx = wtx.toLedgerTransaction(augmentedLookup)
ltx.verify()

View File

@ -0,0 +1,29 @@
package net.corda.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.Party
import net.corda.core.node.services.TimestampChecker
import net.corda.core.node.services.UniquenessProvider
import net.corda.core.transactions.FilteredTransaction
import net.corda.core.utilities.unwrap
class NonValidatingNotaryFlow(otherSide: Party,
timestampChecker: TimestampChecker,
uniquenessProvider: UniquenessProvider) : NotaryFlow.Service(otherSide, timestampChecker, uniquenessProvider) {
/**
* The received transaction is not checked for contract-validity, as that would require fully
* resolving it into a [TransactionForVerification], for which the caller would have to reveal the whole transaction
* history chain.
* As a result, the Notary _will commit invalid transactions_ as well, but as it also records the identity of
* the caller, it is possible to raise a dispute and verify the validity of the transaction and subsequently
* undo the commit of the input states (the exact mechanism still needs to be worked out).
*/
@Suspendable
override fun receiveAndVerifyTx(): TransactionParts {
val ftx = receive<FilteredTransaction>(otherSide).unwrap {
it.verify()
it
}
return TransactionParts(ftx.rootHash, ftx.filteredLeaves.inputs, ftx.filteredLeaves.timestamp)
}
}

View File

@ -1,31 +1,33 @@
package net.corda.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.Party
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.signWithECDSA
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.Timestamp
import net.corda.core.crypto.*
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.node.services.TimestampChecker
import net.corda.core.node.services.UniquenessException
import net.corda.core.node.services.UniquenessProvider
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.serialize
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap
object NotaryFlow {
/**
* A flow to be used for obtaining a signature from a [NotaryService] ascertaining the transaction
* A flow to be used by a party for obtaining signature(s) from a [NotaryService] ascertaining the transaction
* timestamp is correct and none of its inputs have been used in another completed transaction.
*
* In case of a single-node or Raft notary, the flow will return a single signature. For the BFT notary multiple
* signatures will be returned one from each replica that accepted the input state commit.
*
* @throws NotaryException in case the any of the inputs to the transaction have been consumed
* by another transaction or the timestamp is invalid.
*/
open class Client(private val stx: SignedTransaction,
override val progressTracker: ProgressTracker) : FlowLogic<DigitalSignature.WithKey>() {
override val progressTracker: ProgressTracker) : FlowLogic<List<DigitalSignature.WithKey>>() {
constructor(stx: SignedTransaction) : this(stx, Client.tracker())
companion object {
@ -39,7 +41,7 @@ object NotaryFlow {
@Suspendable
@Throws(NotaryException::class)
override fun call(): DigitalSignature.WithKey {
override fun call(): List<DigitalSignature.WithKey> {
progressTracker.currentStep = REQUESTING
val wtx = stx.tx
notaryParty = wtx.notary ?: throw IllegalStateException("Transaction does not specify a Notary")
@ -52,8 +54,14 @@ object NotaryFlow {
throw NotaryException(NotaryError.SignaturesMissing(ex))
}
val payload: Any = if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) {
stx
} else {
wtx.buildFilteredTransaction { it is StateRef || it is Timestamp }
}
val response = try {
sendAndReceive<DigitalSignature.WithKey>(notaryParty, SignRequest(stx))
sendAndReceive<List<DigitalSignature.WithKey>>(notaryParty, payload)
} catch (e: NotaryException) {
if (e.error is NotaryError.Conflict) {
e.error.conflict.verified()
@ -61,9 +69,9 @@ object NotaryFlow {
throw e
}
return response.unwrap { sig ->
validateSignature(sig, stx.id.bytes)
sig
return response.unwrap { signatures ->
signatures.forEach { validateSignature(it, stx.id.bytes) }
signatures
}
}
@ -73,64 +81,60 @@ object NotaryFlow {
}
}
/**
* Checks that the timestamp command is valid (if present) and commits the input state, or returns a conflict
* A flow run by a notary service that handles notarisation requests.
*
* It checks that the timestamp command is valid (if present) and commits the input state, or returns a conflict
* if any of the input states have been previously committed.
*
* Extend this class, overriding _beforeCommit_ to add custom transaction processing/validation logic.
*
* TODO: the notary service should only be able to see timestamp commands and inputs
* Additional transaction validation logic can be added when implementing [receiveAndVerifyTx].
*/
open class Service(val otherSide: Party,
val timestampChecker: TimestampChecker,
val uniquenessProvider: UniquenessProvider) : FlowLogic<Unit>() {
// See AbstractStateReplacementFlow.Acceptor for why it's Void?
abstract class Service(val otherSide: Party,
val timestampChecker: TimestampChecker,
val uniquenessProvider: UniquenessProvider) : FlowLogic<Void?>() {
@Suspendable
override fun call() {
val stx = receive<SignRequest>(otherSide).unwrap { it.tx }
val wtx = stx.tx
validateTimestamp(wtx)
beforeCommit(stx)
commitInputStates(wtx)
val sig = sign(stx.id.bytes)
send(otherSide, sig)
}
private fun validateTimestamp(tx: WireTransaction) {
if (tx.timestamp != null
&& !timestampChecker.isValid(tx.timestamp))
throw NotaryException(NotaryError.TimestampInvalid())
override fun call(): Void? {
val (id, inputs, timestamp) = receiveAndVerifyTx()
validateTimestamp(timestamp)
commitInputStates(inputs, id)
signAndSendResponse(id)
return null
}
/**
* No pre-commit processing is done. Transaction is not checked for contract-validity, as that would require fully
* resolving it into a [TransactionForVerification], for which the caller would have to reveal the whole transaction
* history chain.
* As a result, the Notary _will commit invalid transactions_ as well, but as it also records the identity of
* the caller, it is possible to raise a dispute and verify the validity of the transaction and subsequently
* undo the commit of the input states (the exact mechanism still needs to be worked out).
* Implement custom logic to receive the transaction to notarise, and perform verification based on validity and
* privacy requirements.
*/
@Suspendable
open fun beforeCommit(stx: SignedTransaction) {
abstract fun receiveAndVerifyTx(): TransactionParts
@Suspendable
private fun signAndSendResponse(txId: SecureHash) {
val signature = sign(txId.bytes)
send(otherSide, listOf(signature))
}
private fun validateTimestamp(t: Timestamp?) {
if (t != null && !timestampChecker.isValid(t))
throw NotaryException(NotaryError.TimestampInvalid())
}
/**
* A NotaryException is thrown if any of the states have been consumed by a different transaction. Note that
* this method does not throw an exception when input states are present multiple times within the transaction.
*/
private fun commitInputStates(tx: WireTransaction) {
private fun commitInputStates(inputs: List<StateRef>, txId: SecureHash) {
try {
uniquenessProvider.commit(tx.inputs, tx.id, otherSide)
uniquenessProvider.commit(inputs, txId, otherSide)
} catch (e: UniquenessException) {
val conflicts = tx.inputs.filterIndexed { i, stateRef ->
val conflicts = inputs.filterIndexed { i, stateRef ->
val consumingTx = e.error.stateHistory[stateRef]
consumingTx != null && consumingTx != UniquenessProvider.ConsumingTx(tx.id, i, otherSide)
consumingTx != null && consumingTx != UniquenessProvider.ConsumingTx(txId, i, otherSide)
}
if (conflicts.isNotEmpty()) {
// TODO: Create a new UniquenessException that only contains the conflicts filtered above.
throw notaryException(tx, e)
throw notaryException(txId, e)
}
}
}
@ -140,30 +144,35 @@ object NotaryFlow {
return mySigningKey.signWithECDSA(bits)
}
private fun notaryException(tx: WireTransaction, e: UniquenessException): NotaryException {
private fun notaryException(txId: SecureHash, e: UniquenessException): NotaryException {
val conflictData = e.error.serialize()
val signedConflict = SignedData(conflictData, sign(conflictData.bytes))
return NotaryException(NotaryError.Conflict(tx, signedConflict))
return NotaryException(NotaryError.Conflict(txId, signedConflict))
}
}
data class SignRequest(val tx: SignedTransaction)
}
/**
* The minimum amount of information needed to notarise a transaction. Note that this does not include
* any sensitive transaction details.
*/
data class TransactionParts(val id: SecureHash, val inputs: List<StateRef>, val timestamp: Timestamp?)
class NotaryException(val error: NotaryError) : FlowException() {
override fun toString() = "${super.toString()}: Error response from Notary - $error"
}
@CordaSerializable
sealed class NotaryError {
class Conflict(val tx: WireTransaction, val conflict: SignedData<UniquenessProvider.Conflict>) : NotaryError() {
override fun toString() = "One or more input states for transaction ${tx.id} have been used in another transaction"
class Conflict(val txId: SecureHash, val conflict: SignedData<UniquenessProvider.Conflict>) : NotaryError() {
override fun toString() = "One or more input states for transaction $txId have been used in another transaction"
}
/** Thrown if the time specified in the timestamp command is outside the allowed tolerance */
class TimestampInvalid : NotaryError()
class TransactionInvalid(val msg: String) : NotaryError()
class SignaturesInvalid(val msg: String): NotaryError()
class SignaturesInvalid(val msg: String) : NotaryError()
class SignaturesMissing(val cause: SignedTransaction.SignaturesMissingException) : NotaryError() {
override fun toString() = cause.toString()

View File

@ -6,6 +6,7 @@ import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.node.recordTransactions
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
@ -67,6 +68,7 @@ class ResolveTransactionsFlow(private val txHashes: Set<SecureHash>,
}
@CordaSerializable
class ExcessivelyLargeTransactionGraph() : Exception()
// Transactions to verify after the dependencies.

View File

@ -3,10 +3,12 @@ package net.corda.flows
import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.messaging.*
import net.corda.core.node.services.DEFAULT_SESSION_ID
import net.corda.core.serialization.CordaSerializable
/**
* Abstract superclass for request messages sent to services which expect a reply.
*/
@CordaSerializable
interface ServiceRequestMessage {
val sessionID: Long
val replyTo: SingleMessageRecipient

View File

@ -10,6 +10,7 @@ import net.corda.core.node.NodeInfo
import net.corda.core.node.recordTransactions
import net.corda.core.node.services.ServiceType
import net.corda.core.seconds
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction
@ -31,18 +32,22 @@ import java.security.KeyPair
*/
object TwoPartyDealFlow {
@CordaSerializable
class DealMismatchException(val expectedDeal: ContractState, val actualDeal: ContractState) : Exception() {
override fun toString() = "The submitted deal didn't match the expected: $expectedDeal vs $actualDeal"
}
@CordaSerializable
class DealRefMismatchException(val expectedDeal: StateRef, val actualDeal: StateRef) : Exception() {
override fun toString() = "The submitted deal didn't match the expected: $expectedDeal vs $actualDeal"
}
// This object is serialised to the network and is the first flow message the seller sends to the buyer.
@CordaSerializable
data class Handshake<out T>(val payload: T, val publicKey: CompositeKey)
class SignaturesFromPrimary(val sellerSig: DigitalSignature.WithKey, val notarySig: DigitalSignature.WithKey)
@CordaSerializable
class SignaturesFromPrimary(val sellerSig: DigitalSignature.WithKey, val notarySigs: List<DigitalSignature.WithKey>)
/**
* [Primary] at the end sends the signed tx to all the regulator parties. This a seperate workflow which needs a
@ -139,9 +144,9 @@ object TwoPartyDealFlow {
// These two steps could be done in parallel, in theory. Our framework doesn't support that yet though.
val ourSignature = computeOurSignature(stx)
val allPartySignedTx = stx + ourSignature
val notarySignature = getNotarySignature(allPartySignedTx)
val notarySignatures = getNotarySignatures(allPartySignedTx)
val fullySigned = sendSignatures(allPartySignedTx, ourSignature, notarySignature)
val fullySigned = sendSignatures(allPartySignedTx, ourSignature, notarySignatures)
progressTracker.currentStep = RECORDING
@ -161,7 +166,7 @@ object TwoPartyDealFlow {
}
@Suspendable
private fun getNotarySignature(stx: SignedTransaction): DigitalSignature.WithKey {
private fun getNotarySignatures(stx: SignedTransaction): List<DigitalSignature.WithKey> {
progressTracker.currentStep = NOTARY
return subFlow(NotaryFlow.Client(stx))
}
@ -173,13 +178,13 @@ object TwoPartyDealFlow {
@Suspendable
private fun sendSignatures(allPartySignedTx: SignedTransaction, ourSignature: DigitalSignature.WithKey,
notarySignature: DigitalSignature.WithKey): SignedTransaction {
notarySignatures: List<DigitalSignature.WithKey>): SignedTransaction {
progressTracker.currentStep = SENDING_SIGS
val fullySigned = allPartySignedTx + notarySignature
val fullySigned = allPartySignedTx + notarySignatures
logger.trace { "Built finished transaction, sending back to other party!" }
send(otherParty, SignaturesFromPrimary(ourSignature, notarySignature))
send(otherParty, SignaturesFromPrimary(ourSignature, notarySignatures))
return fullySigned
}
}
@ -217,7 +222,7 @@ object TwoPartyDealFlow {
logger.trace { "Got signatures from other party, verifying ... " }
val fullySigned = stx + signatures.sellerSig + signatures.notarySig
val fullySigned = stx + signatures.sellerSig + signatures.notarySigs
fullySigned.verifySignatures()
logger.trace { "Signatures received are valid. Deal transaction complete! :-)" }
@ -263,7 +268,7 @@ object TwoPartyDealFlow {
@Suspendable protected abstract fun assembleSharedTX(handshake: Handshake<U>): Pair<TransactionBuilder, List<CompositeKey>>
}
@CordaSerializable
data class AutoOffer(val notary: Party, val dealBeingOffered: DealState)
@ -300,7 +305,7 @@ object TwoPartyDealFlow {
// And add a request for timestamping: it may be that none of the contracts need this! But it can't hurt
// to have one.
ptx.setTime(serviceHub.clock.instant(), 30.seconds)
return Pair(ptx, arrayListOf(deal.parties.single { it.name == serviceHub.myInfo.legalIdentity.name }.owningKey))
return Pair(ptx, arrayListOf(deal.parties.single { it == serviceHub.myInfo.legalIdentity as AbstractParty }.owningKey))
}
}

View File

@ -7,6 +7,7 @@ import net.corda.core.node.services.TimestampChecker
import net.corda.core.node.services.UniquenessProvider
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.unwrap
import java.security.SignatureException
/**
@ -19,21 +20,18 @@ class ValidatingNotaryFlow(otherSide: Party,
timestampChecker: TimestampChecker,
uniquenessProvider: UniquenessProvider) :
NotaryFlow.Service(otherSide, timestampChecker, uniquenessProvider) {
/**
* The received transaction is checked for contract-validity, which requires fully resolving it into a
* [TransactionForVerification], for which the caller also has to to reveal the whole transaction
* dependency chain.
*/
@Suspendable
override fun beforeCommit(stx: SignedTransaction) {
try {
checkSignatures(stx)
val wtx = stx.tx
resolveTransaction(wtx)
wtx.toLedgerTransaction(serviceHub).verify()
} catch (e: Exception) {
when (e) {
is TransactionVerificationException -> NotaryException(NotaryError.TransactionInvalid(e.toString()))
is SignatureException -> throw NotaryException(NotaryError.SignaturesInvalid(e.toString()))
else -> throw e
}
}
override fun receiveAndVerifyTx(): TransactionParts {
val stx = receive<SignedTransaction>(otherSide).unwrap { it }
checkSignatures(stx)
val wtx = stx.tx
validateTransaction(wtx)
return TransactionParts(wtx.id, wtx.inputs, wtx.timestamp)
}
private fun checkSignatures(stx: SignedTransaction) {
@ -45,7 +43,19 @@ class ValidatingNotaryFlow(otherSide: Party,
}
@Suspendable
private fun resolveTransaction(wtx: WireTransaction) {
subFlow(ResolveTransactionsFlow(wtx, otherSide))
fun validateTransaction(wtx: WireTransaction) {
try {
resolveTransaction(wtx)
wtx.toLedgerTransaction(serviceHub).verify()
} catch (e: Exception) {
throw when (e) {
is TransactionVerificationException -> NotaryException(NotaryError.TransactionInvalid(e.toString()))
is SignatureException -> NotaryException(NotaryError.SignaturesInvalid(e.toString()))
else -> e
}
}
}
@Suspendable
private fun resolveTransaction(wtx: WireTransaction) = subFlow(ResolveTransactionsFlow(wtx, otherSide))
}

View File

@ -754,3 +754,4 @@ Nuremberg 11.05 49.45
Santa Fe -60.69 -31.6
Joinville -48.84 -26.32
Zurich 8.55 47.36
Hong Kong 114.16 22.28

View File

@ -35,7 +35,7 @@ class TransactionTests {
timestamp = null
)
val bytes: SerializedBytes<WireTransaction> = wtx.serialized
fun make(vararg keys: KeyPair) = SignedTransaction(bytes, keys.map { it.signWithECDSA(wtx.id.bytes) }, wtx.id)
fun make(vararg keys: KeyPair) = SignedTransaction(bytes, keys.map { it.signWithECDSA(wtx.id.bytes) })
assertFailsWith<IllegalArgumentException> { make().verifySignatures() }
assertEquals(

View File

@ -0,0 +1,656 @@
package net.corda.core.crypto
import com.google.common.collect.Sets
import net.i2p.crypto.eddsa.EdDSAKey
import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.interfaces.ECKey
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider
import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PrivateKey
import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey
import org.junit.Assert.assertNotEquals
import org.junit.Test
import java.security.KeyFactory
import java.security.Security
import java.util.*
import java.security.spec.*
import kotlin.test.*
/**
* Run tests for cryptographic algorithms
*/
class CryptoUtilsTest {
init {
Security.addProvider(BouncyCastleProvider())
Security.addProvider(BouncyCastlePQCProvider())
}
val testString = "Hello World"
val testBytes = testString.toByteArray()
// key generation test
@Test
fun `Generate key pairs`() {
// testing supported algorithms
val rsaKeyPair = Crypto.generateKeyPair("RSA_SHA256")
val ecdsaKKeyPair = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val ecdsaRKeyPair = Crypto.generateKeyPair("ECDSA_SECP256R1_SHA256")
val eddsaKeyPair = Crypto.generateKeyPair("EDDSA_ED25519_SHA512")
val sphincsKeyPair = Crypto.generateKeyPair("SPHINCS-256_SHA512")
// not null private keys
assertNotNull(rsaKeyPair.private);
assertNotNull(ecdsaKKeyPair.private);
assertNotNull(ecdsaRKeyPair.private);
assertNotNull(eddsaKeyPair.private);
assertNotNull(sphincsKeyPair.private);
// not null public keys
assertNotNull(rsaKeyPair.public);
assertNotNull(ecdsaKKeyPair.public);
assertNotNull(ecdsaRKeyPair.public);
assertNotNull(eddsaKeyPair.public);
assertNotNull(sphincsKeyPair.public);
// fail on unsupported algorithm
try {
val wrongKeyPair = Crypto.generateKeyPair("WRONG_ALG")
fail()
} catch (e: Exception) {
// expected
}
}
// full process tests
@Test
fun `RSA full process keygen-sign-verify`() {
val keyPair = Crypto.generateKeyPair("RSA_SHA256")
// test for some data
val signedData = keyPair.sign(testBytes)
val verification = keyPair.verify(signedData, testBytes)
assertTrue(verification)
// test for empty data signing
try {
keyPair.sign(ByteArray(0))
fail()
} catch (e: Exception) {
// expected
}
// test for empty source data when verifying
try {
keyPair.verify(testBytes, ByteArray(0))
fail()
} catch (e: Exception) {
// expected
}
// test for empty signed data when verifying
try {
keyPair.verify(ByteArray(0), testBytes)
fail()
} catch (e: Exception) {
// expected
}
// test for zero bytes data
val signedDataZeros = keyPair.sign(ByteArray(100))
val verificationZeros = keyPair.verify(signedDataZeros, ByteArray(100))
assertTrue(verificationZeros)
// test for 1MB of data (I successfully tested it locally for 1GB as well)
val MBbyte = ByteArray(1000000) // 1.000.000
Random().nextBytes(MBbyte)
val signedDataBig = keyPair.sign(MBbyte)
val verificationBig = keyPair.verify(signedDataBig, MBbyte)
assertTrue(verificationBig)
// test on malformed signatures (even if they change for 1 bit)
for (i in 0..signedData.size - 1) {
val b = signedData.get(i)
signedData.set(i,b.inc())
try {
keyPair.verify(signedData, testBytes)
fail()
} catch (e: Exception) {
// expected
}
signedData.set(i,b.dec())
}
}
@Test
fun `ECDSA secp256k1 full process keygen-sign-verify`() {
val keyPair = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
// test for some data
val signedData = keyPair.sign(testBytes)
val verification = keyPair.verify(signedData, testBytes)
assertTrue(verification)
// test for empty data signing
try {
keyPair.sign(ByteArray(0))
fail()
} catch (e: Exception) {
// expected
}
// test for empty source data when verifying
try {
keyPair.verify(testBytes, ByteArray(0))
fail()
} catch (e: Exception) {
// expected
}
// test for empty signed data when verifying
try {
keyPair.verify(ByteArray(0), testBytes)
fail()
} catch (e: Exception) {
// expected
}
// test for zero bytes data
val signedDataZeros = keyPair.sign(ByteArray(100))
val verificationZeros = keyPair.verify(signedDataZeros, ByteArray(100))
assertTrue(verificationZeros)
// test for 1MB of data (I successfully tested it locally for 1GB as well)
val MBbyte = ByteArray(1000000) // 1.000.000
Random().nextBytes(MBbyte)
val signedDataBig = keyPair.sign(MBbyte)
val verificationBig = keyPair.verify(signedDataBig, MBbyte)
assertTrue(verificationBig)
// test on malformed signatures (even if they change for 1 bit)
signedData.set(0,signedData[0].inc())
try {
keyPair.verify(signedData, testBytes)
fail()
} catch (e: Exception) {
// expected
}
}
@Test
fun `ECDSA secp256r1 full process keygen-sign-verify`() {
val keyPair = Crypto.generateKeyPair("ECDSA_SECP256R1_SHA256")
// test for some data
val signedData = keyPair.sign(testBytes)
val verification = keyPair.verify(signedData, testBytes)
assertTrue(verification)
// test for empty data signing
try {
keyPair.sign(ByteArray(0))
fail()
} catch (e: Exception) {
// expected
}
// test for empty source data when verifying
try {
keyPair.verify(testBytes, ByteArray(0))
fail()
} catch (e: Exception) {
// expected
}
// test for empty signed data when verifying
try {
keyPair.verify(ByteArray(0), testBytes)
fail()
} catch (e: Exception) {
// expected
}
// test for zero bytes data
val signedDataZeros = keyPair.sign(ByteArray(100))
val verificationZeros = keyPair.verify(signedDataZeros, ByteArray(100))
assertTrue(verificationZeros)
// test for 1MB of data (I successfully tested it locally for 1GB as well)
val MBbyte = ByteArray(1000000) // 1.000.000
Random().nextBytes(MBbyte)
val signedDataBig = keyPair.sign(MBbyte)
val verificationBig = keyPair.verify(signedDataBig, MBbyte)
assertTrue(verificationBig)
// test on malformed signatures (even if they change for 1 bit)
signedData.set(0, signedData[0].inc())
try {
keyPair.verify(signedData, testBytes)
fail()
} catch (e: Exception) {
// expected
}
}
@Test
fun `EDDSA ed25519 full process keygen-sign-verify`() {
val keyPair = Crypto.generateKeyPair("EDDSA_ED25519_SHA512")
// test for some data
val signedData = keyPair.sign(testBytes)
val verification = keyPair.verify(signedData, testBytes)
assertTrue(verification)
// test for empty data signing
try {
keyPair.sign(ByteArray(0))
fail()
} catch (e: Exception) {
// expected
}
// test for empty source data when verifying
try {
keyPair.verify(testBytes, ByteArray(0))
fail()
} catch (e: Exception) {
// expected
}
// test for empty signed data when verifying
try {
keyPair.verify(ByteArray(0), testBytes)
fail()
} catch (e: Exception) {
// expected
}
// test for zero bytes data
val signedDataZeros = keyPair.sign(ByteArray(100))
val verificationZeros = keyPair.verify(signedDataZeros, ByteArray(100))
assertTrue(verificationZeros)
// test for 1MB of data (I successfully tested it locally for 1GB as well)
val MBbyte = ByteArray(1000000) // 1.000.000
Random().nextBytes(MBbyte)
val signedDataBig = keyPair.sign(MBbyte)
val verificationBig = keyPair.verify(signedDataBig, MBbyte)
assertTrue(verificationBig)
// test on malformed signatures (even if they change for 1 bit)
signedData.set(0, signedData[0].inc())
try {
keyPair.verify(signedData, testBytes)
fail()
} catch (e: Exception) {
// expected
}
}
@Test
fun `SPHINCS-256 full process keygen-sign-verify`() {
val keyPair = Crypto.generateKeyPair("SPHINCS-256_SHA512")
// test for some data
val signedData = keyPair.sign(testBytes)
val verification = keyPair.verify(signedData, testBytes)
assertTrue(verification)
// test for empty data signing
try {
keyPair.sign(ByteArray(0))
fail()
} catch (e: Exception) {
// expected
}
// test for empty source data when verifying
try {
keyPair.verify(testBytes, ByteArray(0))
fail()
} catch (e: Exception) {
// expected
}
// test for empty signed data when verifying
try {
keyPair.verify(ByteArray(0), testBytes)
fail()
} catch (e: Exception) {
// expected
}
// test for zero bytes data
val signedDataZeros = keyPair.sign(ByteArray(100))
val verificationZeros = keyPair.verify(signedDataZeros, ByteArray(100))
assertTrue(verificationZeros)
// test for 1MB of data (I successfully tested it locally for 1GB as well)
val MBbyte = ByteArray(1000000) // 1.000.000
Random().nextBytes(MBbyte)
val signedDataBig = keyPair.sign(MBbyte)
val verificationBig = keyPair.verify(signedDataBig, MBbyte)
assertTrue(verificationBig)
// test on malformed signatures (even if they change for 1 bit)
signedData.set(0, signedData[0].inc())
try {
keyPair.verify(signedData, testBytes)
fail()
} catch (e: Exception) {
// expected
}
}
// test list of supported algorithms
@Test
fun `Check supported algorithms`() {
val algList : List<String> = Crypto.listSupportedSignatureSchemes()
val expectedAlgSet = setOf<String>("RSA_SHA256","ECDSA_SECP256K1_SHA256", "ECDSA_SECP256R1_SHA256", "EDDSA_ED25519_SHA512","SPHINCS-256_SHA512")
assertTrue { Sets.symmetricDifference(expectedAlgSet,algList.toSet()).isEmpty(); }
}
// Unfortunately, there isn't a standard way to encode/decode keys, so we need to test per case
@Test
fun `RSA encode decode keys - required for serialization`() {
// Generate key pair.
val keyPair = Crypto.generateKeyPair("RSA_SHA256")
val (privKey, pubKey) = keyPair
val keyFactory = KeyFactory.getInstance("RSA", "BC")
// Encode and decode private key.
val privKey2 = keyFactory.generatePrivate(PKCS8EncodedKeySpec(privKey.encoded))
assertEquals(privKey2, privKey)
// Encode and decode public key.
val pubKey2 = keyFactory.generatePublic(X509EncodedKeySpec(pubKey.encoded))
assertEquals(pubKey2, pubKey)
}
@Test
fun `ECDSA secp256k1 encode decode keys - required for serialization`() {
// Generate key pair.
val keyPair = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val (privKey, pubKey) = keyPair
val kf = KeyFactory.getInstance("ECDSA", "BC")
// Encode and decode private key.
val privKey2 = kf.generatePrivate(PKCS8EncodedKeySpec(privKey.encoded))
assertEquals(privKey2, privKey)
// Encode and decode public key.
val pubKey2 = kf.generatePublic(X509EncodedKeySpec(pubKey.encoded))
assertEquals(pubKey2, pubKey)
}
@Test
fun `ECDSA secp256r1 encode decode keys - required for serialization`() {
// Generate key pair.
val keyPair = Crypto.generateKeyPair("ECDSA_SECP256R1_SHA256")
val (privKey, pubKey) = keyPair
val kf = KeyFactory.getInstance("ECDSA", "BC")
// Encode and decode private key.
val privKey2 = kf.generatePrivate(PKCS8EncodedKeySpec(privKey.encoded))
assertEquals(privKey2, privKey)
// Encode and decode public key.
val pubKey2 = kf.generatePublic(X509EncodedKeySpec(pubKey.encoded))
assertEquals(pubKey2, pubKey)
}
@Test
fun `EdDSA encode decode keys - required for serialization`() {
// Generate key pair.
val keyPair = Crypto.generateKeyPair("EDDSA_ED25519_SHA512")
val privKey: EdDSAPrivateKey = keyPair.private as EdDSAPrivateKey
val pubKey: EdDSAPublicKey = keyPair.public as EdDSAPublicKey
val kf = EdDSAKeyFactory()
// Encode and decode private key.
val privKey2 = kf.generatePrivate(PKCS8EncodedKeySpec(privKey.encoded))
assertEquals(privKey2, privKey)
// Encode and decode public key.
val pubKey2 = kf.generatePublic(X509EncodedKeySpec(pubKey.encoded))
assertEquals(pubKey2, pubKey)
}
@Test
fun `SPHINCS-256 encode decode keys - required for serialization`() {
// Generate key pair.
val keyPair = Crypto.generateKeyPair("SPHINCS-256_SHA512")
val privKey: BCSphincs256PrivateKey = keyPair.private as BCSphincs256PrivateKey
val pubKey: BCSphincs256PublicKey = keyPair.public as BCSphincs256PublicKey
//1st method for encoding/decoding
val keyFactory = KeyFactory.getInstance("SPHINCS256", "BCPQC")
// Encode and decode private key.
val privKey2 = keyFactory.generatePrivate(PKCS8EncodedKeySpec(privKey.encoded))
assertEquals(privKey2, privKey)
// Encode and decode public key.
val pubKey2 = keyFactory.generatePublic(X509EncodedKeySpec(pubKey.encoded))
assertEquals(pubKey2, pubKey)
//2nd method for encoding/decoding
// Encode and decode private key.
val privKeyInfo : PrivateKeyInfo = PrivateKeyInfo.getInstance(privKey.encoded)
val decodedPrivKey = BCSphincs256PrivateKey(privKeyInfo)
// Check that decoded private key is equal to the initial one.
assertEquals(decodedPrivKey, privKey)
// Encode and decode public key.
val pubKeyInfo : SubjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(pubKey.encoded)
val extractedPubKey = BCSphincs256PublicKey(pubKeyInfo)
// Check that decoded private key is equal to the initial one.
assertEquals(extractedPubKey, pubKey)
}
@Test
fun `RSA scheme finder by key type`() {
val keyPairRSA = Crypto.generateKeyPair("RSA_SHA256")
val (privRSA, pubRSA) = keyPairRSA
assertEquals(privRSA.algorithm, "RSA")
assertEquals(pubRSA.algorithm, "RSA")
}
@Test
fun `ECDSA secp256k1 scheme finder by key type`() {
val keyPairK1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val (privK1, pubK1) = keyPairK1
// Encode and decode keys as they would be transferred.
val kf = KeyFactory.getInstance("ECDSA", "BC")
val privK1Decoded = kf.generatePrivate(PKCS8EncodedKeySpec(privK1.encoded))
val pubK1Decoded = kf.generatePublic(X509EncodedKeySpec(pubK1.encoded))
assertEquals(privK1Decoded.algorithm, "ECDSA")
assertEquals((privK1Decoded as ECKey).parameters, ECNamedCurveTable.getParameterSpec("secp256k1"))
assertEquals(pubK1Decoded.algorithm, "ECDSA")
assertEquals((pubK1Decoded as ECKey).parameters, ECNamedCurveTable.getParameterSpec("secp256k1"))
}
@Test
fun `ECDSA secp256r1 scheme finder by key type`() {
val keyPairR1 = Crypto.generateKeyPair("ECDSA_SECP256R1_SHA256")
val (privR1, pubR1) = keyPairR1
assertEquals(privR1.algorithm, "ECDSA")
assertEquals((privR1 as ECKey).parameters, ECNamedCurveTable.getParameterSpec("secp256r1"))
assertEquals(pubR1.algorithm, "ECDSA")
assertEquals((pubR1 as ECKey).parameters, ECNamedCurveTable.getParameterSpec("secp256r1"))
}
@Test
fun `EdDSA scheme finder by key type`() {
val keyPairEd = Crypto.generateKeyPair("EDDSA_ED25519_SHA512")
val (privEd, pubEd) = keyPairEd
assertEquals(privEd.algorithm, "EdDSA")
assertEquals((privEd as EdDSAKey).params, EdDSANamedCurveTable.getByName("ed25519-sha-512"))
assertEquals(pubEd.algorithm, "EdDSA")
assertEquals((pubEd as EdDSAKey).params, EdDSANamedCurveTable.getByName("ed25519-sha-512"))
}
@Test
fun `SPHINCS-256 scheme finder by key type`() {
val keyPairSP = Crypto.generateKeyPair("SPHINCS-256_SHA512")
val (privSP, pubSP) = keyPairSP
assertEquals(privSP.algorithm, "SPHINCS-256")
assertEquals(pubSP.algorithm, "SPHINCS-256")
}
@Test
fun `Automatic EdDSA key-type detection and decoding`() {
val keyPairEd = Crypto.generateKeyPair("EDDSA_ED25519_SHA512")
val (privEd, pubEd) = keyPairEd
val encodedPrivEd = privEd.encoded
val encodedPubEd = pubEd.encoded
val decodedPrivEd = Crypto.decodePrivateKey(encodedPrivEd)
assertEquals(decodedPrivEd.algorithm, "EdDSA")
assertEquals(decodedPrivEd, privEd)
val decodedPubEd = Crypto.decodePublicKey(encodedPubEd)
assertEquals(decodedPubEd.algorithm, "EdDSA")
assertEquals(decodedPubEd, pubEd)
}
@Test
fun `Automatic ECDSA secp256k1 key-type detection and decoding`() {
val keyPairK1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val (privK1, pubK1) = keyPairK1
val encodedPrivK1 = privK1.encoded
val encodedPubK1 = pubK1.encoded
val decodedPrivK1 = Crypto.decodePrivateKey(encodedPrivK1)
assertEquals(decodedPrivK1.algorithm, "ECDSA")
assertEquals(decodedPrivK1, privK1)
val decodedPubK1 = Crypto.decodePublicKey(encodedPubK1)
assertEquals(decodedPubK1.algorithm, "ECDSA")
assertEquals(decodedPubK1, pubK1)
}
@Test
fun `Automatic ECDSA secp256r1 key-type detection and decoding`() {
val keyPairR1 = Crypto.generateKeyPair("ECDSA_SECP256R1_SHA256")
val (privR1, pubR1) = keyPairR1
val encodedPrivR1 = privR1.encoded
val encodedPubR1 = pubR1.encoded
val decodedPrivR1 = Crypto.decodePrivateKey(encodedPrivR1)
assertEquals(decodedPrivR1.algorithm, "ECDSA")
assertEquals(decodedPrivR1, privR1)
val decodedPubR1 = Crypto.decodePublicKey(encodedPubR1)
assertEquals(decodedPubR1.algorithm, "ECDSA")
assertEquals(decodedPubR1, pubR1)
}
@Test
fun `Automatic RSA key-type detection and decoding`() {
val keyPairRSA = Crypto.generateKeyPair("RSA_SHA256")
val (privRSA, pubRSA) = keyPairRSA
val encodedPrivRSA = privRSA.encoded
val encodedPubRSA = pubRSA.encoded
val decodedPrivRSA = Crypto.decodePrivateKey(encodedPrivRSA)
assertEquals(decodedPrivRSA.algorithm, "RSA")
assertEquals(decodedPrivRSA, privRSA)
val decodedPubRSA = Crypto.decodePublicKey(encodedPubRSA)
assertEquals(decodedPubRSA.algorithm, "RSA")
assertEquals(decodedPubRSA, pubRSA)
}
@Test
fun `Automatic SPHINCS-256 key-type detection and decoding`() {
val keyPairSP = Crypto.generateKeyPair("SPHINCS-256_SHA512")
val (privSP, pubSP) = keyPairSP
val encodedPrivSP = privSP.encoded
val encodedPubSP = pubSP.encoded
val decodedPrivSP = Crypto.decodePrivateKey(encodedPrivSP)
assertEquals(decodedPrivSP.algorithm, "SPHINCS-256")
assertEquals(decodedPrivSP, privSP)
val decodedPubSP = Crypto.decodePublicKey(encodedPubSP)
assertEquals(decodedPubSP.algorithm, "SPHINCS-256")
assertEquals(decodedPubSP, pubSP)
}
@Test
fun `Failure test between K1 and R1 keys`() {
val keyPairK1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val (privK1, pubK1) = keyPairK1
val encodedPrivK1 = privK1.encoded
val decodedPrivK1 = Crypto.decodePrivateKey(encodedPrivK1)
val keyPairR1 = Crypto.generateKeyPair("ECDSA_SECP256R1_SHA256")
val (privR1, pubR1) = keyPairR1
val encodedPrivR1 = privR1.encoded
val decodedPrivR1 = Crypto.decodePrivateKey(encodedPrivR1)
assertNotEquals(decodedPrivK1, decodedPrivR1)
}
@Test
fun `Decoding Failure on randomdata as key`() {
val keyPairK1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val (privK1, pubK1) = keyPairK1
val encodedPrivK1 = privK1.encoded
// Test on random encoded bytes.
val fakeEncodedKey = ByteArray(encodedPrivK1.size)
val r = Random()
r.nextBytes(fakeEncodedKey)
// fail on fake key.
try {
val decodedFake = Crypto.decodePrivateKey(fakeEncodedKey)
fail()
} catch (e: Exception) {
// expected
}
}
@Test
fun `Decoding Failure on malformed keys`() {
val keyPairK1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val (privK1, pubK1) = keyPairK1
val encodedPrivK1 = privK1.encoded
// fail on malformed key.
for (i in 0..encodedPrivK1.size - 1) {
val b = encodedPrivK1.get(i)
encodedPrivK1.set(i,b.inc())
try {
val decodedFake = Crypto.decodePrivateKey(encodedPrivK1)
println("OK")
fail()
} catch (e: Exception) {
// expected
}
encodedPrivK1.set(i,b.dec())
}
}
}

View File

@ -0,0 +1,103 @@
package net.corda.core.crypto
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.fail
class EncodingUtilsTest {
val testString = "Hello World"
val testBytes = testString.toByteArray()
val testBase58String = "JxF12TrwUP45BMd" // Base58 format for Hello World.
val testBase64String = "SGVsbG8gV29ybGQ=" // Base64 format for Hello World.
val testHexString = "48656C6C6F20576F726C64" // HEX format for Hello World.
// Encoding tests
@Test
fun `encoding Hello World`() {
assertEquals(testBase58String, testBytes.toBase58())
assertEquals(testBase64String, testBytes.toBase64())
assertEquals(testHexString, testBytes.toHex())
}
@Test
fun `empty encoding`() {
val emptyByteArray = ByteArray(0)
assertEquals("", emptyByteArray.toBase58())
assertEquals("", emptyByteArray.toBase64())
assertEquals("", emptyByteArray.toHex())
}
@Test
fun `encoding 7 zero bytes`() {
val sevenZeroByteArray = ByteArray(7)
assertEquals("1111111", sevenZeroByteArray.toBase58())
assertEquals("AAAAAAAAAA==", sevenZeroByteArray.toBase64())
assertEquals("00000000000000", sevenZeroByteArray.toHex())
}
//Decoding tests
@Test
fun `decoding to real String`() {
assertEquals(testString, testBase58String.base58ToRealString())
assertEquals(testString, testBase64String.base64ToRealString())
assertEquals(testString, testHexString.hexToRealString())
}
@Test
fun `decoding empty Strings`() {
assertEquals("", "".base58ToRealString())
assertEquals("", "".base64ToRealString())
assertEquals("", "".hexToRealString())
}
@Test
fun `decoding lowercase and mixed HEX`() {
val testHexStringLowercase = testHexString.toLowerCase()
assertEquals(testHexString.hexToRealString(), testHexStringLowercase.hexToRealString())
val testHexStringMixed = testHexString.replace('C', 'c')
assertEquals(testHexString.hexToRealString(), testHexStringMixed.hexToRealString())
}
@Test
fun `decoding on wrong format`() {
// the String "Hello World" is not a valid Base58 or Base64 or HEX format
try {
testString.base58ToRealString()
fail()
} catch (e: AddressFormatException) {
// expected.
}
try {
testString.base64ToRealString()
fail()
} catch (e: IllegalArgumentException) {
// expected.
}
try {
testString.hexToRealString()
fail()
} catch (e: IllegalArgumentException) {
// expected.
}
}
//Encoding changers tests
@Test
fun `change encoding between base58, base64, hex`() {
// base58 to base64
assertEquals(testBase64String, testBase58String.base58toBase64())
// base58 to hex
assertEquals(testHexString, testBase58String.base58toHex())
// base64 to base58
assertEquals(testBase58String, testBase64String.base64toBase58())
// base64 to hex
assertEquals(testHexString, testBase64String.base64toHex())
// hex to base58
assertEquals(testBase58String, testHexString.hexToBase58())
// hex to base64
assertEquals(testBase64String, testHexString.hexToBase64())
}
}

View File

@ -12,9 +12,9 @@ class PartyTest {
val differentKey = entropyToKeyPair(BigInteger.valueOf(7201702L)).public.composite
val anonymousParty = AnonymousParty(key)
val party = Party("test key", key)
assertEquals(party, anonymousParty)
assertEquals(anonymousParty, party)
assertNotEquals(AnonymousParty(differentKey), anonymousParty)
assertNotEquals(AnonymousParty(differentKey), party)
assertEquals<AbstractParty>(party, anonymousParty)
assertEquals<AbstractParty>(anonymousParty, party)
assertNotEquals<AbstractParty>(AnonymousParty(differentKey), anonymousParty)
assertNotEquals<AbstractParty>(AnonymousParty(differentKey), party)
}
}

View File

@ -0,0 +1,83 @@
package net.corda.core.crypto
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider
import org.junit.Test
import java.security.Security
import java.security.SignatureException
import java.time.Instant
import kotlin.test.assertTrue
import kotlin.test.fail
/**
* Digital signature MetaData tests
*/
class TransactionSignatureTest {
init {
Security.addProvider(BouncyCastleProvider())
Security.addProvider(BouncyCastlePQCProvider())
}
val testBytes = "12345678901234567890123456789012".toByteArray()
/** valid sign and verify. */
@Test
fun `MetaData Full sign and verify`() {
val keyPair = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
// create a MetaData.Full object
val meta = MetaData("ECDSA_SECP256K1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes, keyPair.public)
// sign the message
val transactionSignature: TransactionSignature = keyPair.private.sign(meta)
// check auto-verification
assertTrue(transactionSignature.verify())
// check manual verification
assertTrue(keyPair.public.verify(transactionSignature))
}
/** Signing should fail, as I sign with a secpK1 key, but set schemeCodeName is set to secpR1. */
@Test(expected = IllegalArgumentException::class)
fun `MetaData Full failure wrong scheme`() {
val keyPair = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val meta = MetaData("ECDSA_SECP256R1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes, keyPair.public)
keyPair.private.sign(meta)
}
/** Verification should fail; corrupted metadata - public key has changed. */
@Test(expected = SignatureException::class)
fun `MetaData Full failure public key has changed`() {
val keyPair1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val keyPair2 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val meta = MetaData("ECDSA_SECP256K1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes, keyPair2.public)
val transactionSignature = keyPair1.private.sign(meta)
transactionSignature.verify()
}
/** Verification should fail; corrupted metadata - clearData has changed. */
@Test(expected = SignatureException::class)
fun `MetaData Full failure clearData has changed`() {
val keyPair1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val meta = MetaData("ECDSA_SECP256K1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes, keyPair1.public)
val transactionSignature = keyPair1.private.sign(meta)
val meta2 = MetaData("ECDSA_SECP256K1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes.plus(testBytes), keyPair1.public)
val transactionSignature2 = TransactionSignature(transactionSignature.signatureData, meta2)
keyPair1.public.verify(transactionSignature2)
}
/** Verification should fail; corrupted metadata - schemeCodeName has changed from K1 to R1. */
@Test(expected = SignatureException::class)
fun `MetaData Wrong schemeCodeName has changed`() {
val keyPair1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val meta = MetaData("ECDSA_SECP256K1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes, keyPair1.public)
val transactionSignature = keyPair1.private.sign(meta)
val meta2 = MetaData("ECDSA_SECP256R1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes.plus(testBytes), keyPair1.public)
val transactionSignature2 = TransactionSignature(transactionSignature.signatureData, meta2)
keyPair1.public.verify(transactionSignature2)
}
}

View File

@ -1,6 +1,5 @@
package net.corda.core.flows
import com.esotericsoftware.kryo.io.Input
import com.pholser.junit.quickcheck.From
import com.pholser.junit.quickcheck.Property
import com.pholser.junit.quickcheck.generator.GenerationStatus
@ -8,7 +7,7 @@ import com.pholser.junit.quickcheck.generator.Generator
import com.pholser.junit.quickcheck.random.SourceOfRandomness
import com.pholser.junit.quickcheck.runner.JUnitQuickcheck
import net.corda.contracts.testing.SignedTransactionGenerator
import net.corda.core.serialization.createKryo
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.flows.BroadcastTransactionFlow.NotifyTxRequest
import org.junit.runner.RunWith
@ -25,9 +24,8 @@ class BroadcastTransactionFlowTest {
@Property
fun serialiseDeserialiseOfNotifyMessageWorks(@From(NotifyTxRequestMessageGenerator::class) message: NotifyTxRequest) {
val kryo = createKryo()
val serialized = message.serialize().bytes
val deserialized = kryo.readClassAndObject(Input(serialized))
val deserialized = serialized.deserialize<NotifyTxRequest>()
assertEquals(deserialized, message)
}
}

View File

@ -6,11 +6,17 @@ import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.getOrThrow
import net.corda.core.messaging.startFlow
import net.corda.core.node.services.unconsumedStates
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.utilities.Emoji
import net.corda.flows.CashIssueFlow
import net.corda.flows.ContractUpgradeFlow
import net.corda.flows.FinalityFlow
import net.corda.node.internal.CordaRPCOpsImpl
import net.corda.node.services.User
import net.corda.node.services.messaging.CURRENT_RPC_USER
import net.corda.node.services.startFlowPermission
import net.corda.node.utilities.databaseTransaction
import net.corda.testing.node.MockNetwork
import org.junit.After
@ -60,19 +66,82 @@ class ContractUpgradeFlowTest {
requireNotNull(btx)
// The request is expected to be rejected because party B haven't authorise the upgrade yet.
val rejectedFuture = a.services.startFlow(ContractUpgradeFlow.Instigator<DummyContract.State, DummyContractV2.State>(atx!!.tx.outRef(0), DUMMY_V2_PROGRAM_ID.javaClass)).resultFuture
val rejectedFuture = a.services.startFlow(ContractUpgradeFlow.Instigator(atx!!.tx.outRef(0), DummyContractV2::class.java)).resultFuture
mockNet.runNetwork()
assertFailsWith(ExecutionException::class) { rejectedFuture.get() }
// Party B authorise the contract state upgrade.
b.services.vaultService.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DUMMY_V2_PROGRAM_ID.javaClass)
b.services.vaultService.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
// Party A initiates contract upgrade flow, expected to succeed this time.
val resultFuture = a.services.startFlow(ContractUpgradeFlow.Instigator<DummyContract.State, DummyContractV2.State>(atx.tx.outRef(0), DUMMY_V2_PROGRAM_ID.javaClass)).resultFuture
val resultFuture = a.services.startFlow(ContractUpgradeFlow.Instigator(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture
mockNet.runNetwork()
val result = resultFuture.get()
fun check(node: MockNetwork.MockNode) {
val nodeStx = databaseTransaction(node.database) {
node.services.storageService.validatedTransactions.getTransaction(result.ref.txhash)
}
requireNotNull(nodeStx)
// Verify inputs.
val input = databaseTransaction(node.database) {
node.services.storageService.validatedTransactions.getTransaction(nodeStx!!.tx.inputs.single().txhash)
}
requireNotNull(input)
assertTrue(input!!.tx.outputs.single().data is DummyContract.State)
// Verify outputs.
assertTrue(nodeStx!!.tx.outputs.single().data is DummyContractV2.State)
}
check(a)
check(b)
}
@Test
fun `2 parties contract upgrade using RPC`() {
// Create dummy contract.
val twoPartyDummyContract = DummyContract.generateInitial(0, notary, a.info.legalIdentity.ref(1), b.info.legalIdentity.ref(1))
val stx = twoPartyDummyContract.signWith(a.services.legalIdentityKey)
.signWith(b.services.legalIdentityKey)
.toSignedTransaction()
a.services.startFlow(FinalityFlow(stx, setOf(a.info.legalIdentity, b.info.legalIdentity)))
mockNet.runNetwork()
val atx = databaseTransaction(a.database) { a.services.storageService.validatedTransactions.getTransaction(stx.id) }
val btx = databaseTransaction(b.database) { b.services.storageService.validatedTransactions.getTransaction(stx.id) }
requireNotNull(atx)
requireNotNull(btx)
// The request is expected to be rejected because party B haven't authorise the upgrade yet.
val rpcA = CordaRPCOpsImpl(a.services, a.smm, a.database)
val rpcB = CordaRPCOpsImpl(b.services, b.smm, b.database)
CURRENT_RPC_USER.set(User("user", "pwd", permissions = setOf(
startFlowPermission<ContractUpgradeFlow.Instigator<*, *>>()
)))
val rejectedFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Instigator(stateAndRef, upgrade) },
atx!!.tx.outRef<DummyContract.State>(0),
DummyContractV2::class.java).returnValue
mockNet.runNetwork()
assertFailsWith(ExecutionException::class) { rejectedFuture.get() }
// Party B authorise the contract state upgrade.
rpcB.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
// Party A initiates contract upgrade flow, expected to succeed this time.
val resultFuture = rpcA.startFlow({ stateAndRef, upgrade -> ContractUpgradeFlow.Instigator(stateAndRef, upgrade) },
atx.tx.outRef<DummyContract.State>(0),
DummyContractV2::class.java).returnValue
mockNet.runNetwork()
val result = resultFuture.get()
// Check results.
listOf(a, b).forEach {
val stx = databaseTransaction(a.database) { a.services.storageService.validatedTransactions.getTransaction(result.ref.txhash) }
requireNotNull(stx)
@ -94,17 +163,17 @@ class ContractUpgradeFlowTest {
mockNet.runNetwork()
val stateAndRef = result.getOrThrow().tx.outRef<Cash.State>(0)
// Starts contract upgrade flow.
a.services.startFlow(ContractUpgradeFlow.Instigator<Cash.State, CashV2.State>(stateAndRef, CashV2().javaClass))
a.services.startFlow(ContractUpgradeFlow.Instigator(stateAndRef, CashV2::class.java))
mockNet.runNetwork()
// Get contract state form the vault.
val state = databaseTransaction(a.database) { a.vault.currentVault.states }
val state = databaseTransaction(a.database) { a.vault.unconsumedStates<ContractState>() }
assertTrue(state.single().state.data is CashV2.State, "Contract state is upgraded to the new version.")
assertEquals(Amount(1000000, USD).`issued by`(a.info.legalIdentity.ref(1)), (state.first().state.data as CashV2.State).amount, "Upgraded cash contain the correct amount.")
assertEquals(listOf(a.info.legalIdentity.owningKey), (state.first().state.data as CashV2.State).owners, "Upgraded cash belongs to the right owner.")
}
class CashV2 : UpgradedContract<Cash.State, CashV2.State> {
override val legacyContract = Cash()
override val legacyContract = Cash::class.java
data class State(override val amount: Amount<Issued<Currency>>, val owners: List<CompositeKey>) : FungibleAsset<Currency> {
override val owner: CompositeKey = owners.first()

Some files were not shown because too many files have changed in this diff Show More