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.
@ -10,4 +10,4 @@
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
limitations under 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'])
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

@ -753,4 +753,5 @@ Cuautitlan Izcalli -99.25 19.65
Nuremberg 11.05 49.45
Santa Fe -60.69 -31.6
Joinville -48.84 -26.32
Zurich 8.55 47.36
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