mirror of
https://github.com/corda/corda.git
synced 2025-06-23 01:19:00 +00:00
Refactor of CompositeKeys to implement PublicKey interface. (#433)
* Make CompositeKey implement PublicKey The initial implementation of composite keys as their own distinct class separate from PublicKey means that the keys cannot be used on standard classes such as Certificate. This work is a beginning to modifying CompositeKey to being a PublicKey implementation, although significant further work is required to integrate this properly with the standard Java APIs, especially around verifying signatures using the new key type. * First stage of making CompositeKey implement PublicKey interface. Revert to using PublicKey everywhere we expect a key. * Move algorithm and format into companion object (#432) Move algorithm and format into companion object so that they can be referenced from other classes (i.e. the upcoming signature class). * Add simple invariants to construction of CompositeKey. Builder emits CompositeKeys in simplified normalised form. Forbid keys with single child node, force ordering on children and forbid duplicates on the same level. It's not full semantical normalisation. * Make constructor of CompositeKey private, move NodeWeight inside the class. Add utility function for Kryo deserialization to read list with length constraints.
This commit is contained in:
@ -2,8 +2,8 @@
|
||||
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.Party
|
||||
import java.security.PublicKey
|
||||
import java.math.BigDecimal
|
||||
import java.util.*
|
||||
|
||||
@ -69,7 +69,7 @@ inline fun <R> requireThat(body: Requirements.() -> R) = Requirements.body()
|
||||
// TODO: Provide a version of select that interops with Java
|
||||
|
||||
/** Filters the command list by type, party and public key all at once. */
|
||||
inline fun <reified T : CommandData> Collection<AuthenticatedObject<CommandData>>.select(signer: CompositeKey? = null,
|
||||
inline fun <reified T : CommandData> Collection<AuthenticatedObject<CommandData>>.select(signer: PublicKey? = null,
|
||||
party: Party? = null) =
|
||||
filter { it.value is T }.
|
||||
filter { if (signer == null) true else signer in it.signers }.
|
||||
@ -79,7 +79,7 @@ inline fun <reified T : CommandData> Collection<AuthenticatedObject<CommandData>
|
||||
// TODO: Provide a version of select that interops with Java
|
||||
|
||||
/** Filters the command list by type, parties and public keys all at once. */
|
||||
inline fun <reified T : CommandData> Collection<AuthenticatedObject<CommandData>>.select(signers: Collection<CompositeKey>?,
|
||||
inline fun <reified T : CommandData> Collection<AuthenticatedObject<CommandData>>.select(signers: Collection<PublicKey>?,
|
||||
parties: Collection<Party>?) =
|
||||
filter { it.value is T }.
|
||||
filter { if (signers == null) true else it.signers.containsAll(signers) }.
|
||||
|
@ -1,9 +1,9 @@
|
||||
package 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.transactions.TransactionBuilder
|
||||
import java.security.PublicKey
|
||||
|
||||
// The dummy contract doesn't do anything useful. It exists for testing purposes.
|
||||
|
||||
@ -14,12 +14,12 @@ data class DummyContract(override val legalContractReference: SecureHash = Secur
|
||||
val magicNumber: Int
|
||||
}
|
||||
|
||||
data class SingleOwnerState(override val magicNumber: Int = 0, override val owner: CompositeKey) : OwnableState, State {
|
||||
data class SingleOwnerState(override val magicNumber: Int = 0, override val owner: PublicKey) : OwnableState, State {
|
||||
override val contract = DUMMY_PROGRAM_ID
|
||||
override val participants: List<CompositeKey>
|
||||
override val participants: List<PublicKey>
|
||||
get() = listOf(owner)
|
||||
|
||||
override fun withNewOwner(newOwner: CompositeKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -28,9 +28,9 @@ data class DummyContract(override val legalContractReference: SecureHash = Secur
|
||||
* in a different field, however this is a good example of a contract with multiple states.
|
||||
*/
|
||||
data class MultiOwnerState(override val magicNumber: Int = 0,
|
||||
val owners: List<CompositeKey>) : ContractState, State {
|
||||
val owners: List<PublicKey>) : ContractState, State {
|
||||
override val contract = DUMMY_PROGRAM_ID
|
||||
override val participants: List<CompositeKey> get() = owners
|
||||
override val participants: List<PublicKey> get() = owners
|
||||
}
|
||||
|
||||
interface Commands : CommandData {
|
||||
@ -55,8 +55,8 @@ data class DummyContract(override val legalContractReference: SecureHash = Secur
|
||||
}
|
||||
}
|
||||
|
||||
fun move(prior: StateAndRef<DummyContract.SingleOwnerState>, newOwner: CompositeKey) = move(listOf(prior), newOwner)
|
||||
fun move(priors: List<StateAndRef<DummyContract.SingleOwnerState>>, newOwner: CompositeKey): TransactionBuilder {
|
||||
fun move(prior: StateAndRef<DummyContract.SingleOwnerState>, newOwner: PublicKey) = move(listOf(prior), newOwner)
|
||||
fun move(priors: List<StateAndRef<DummyContract.SingleOwnerState>>, newOwner: PublicKey): TransactionBuilder {
|
||||
require(priors.isNotEmpty())
|
||||
val priorState = priors[0].state.data
|
||||
val (cmd, state) = priorState.withNewOwner(newOwner)
|
||||
|
@ -1,9 +1,9 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.flows.ContractUpgradeFlow
|
||||
import java.security.PublicKey
|
||||
|
||||
// The dummy contract doesn't do anything useful. It exists for testing purposes.
|
||||
val DUMMY_V2_PROGRAM_ID = DummyContractV2()
|
||||
@ -11,12 +11,13 @@ val DUMMY_V2_PROGRAM_ID = DummyContractV2()
|
||||
/**
|
||||
* Dummy contract state for testing of the upgrade process.
|
||||
*/
|
||||
// DOCSTART 1
|
||||
class DummyContractV2 : UpgradedContract<DummyContract.State, DummyContractV2.State> {
|
||||
override val legacyContract = DummyContract::class.java
|
||||
|
||||
data class State(val magicNumber: Int = 0, val owners: List<CompositeKey>) : ContractState {
|
||||
data class State(val magicNumber: Int = 0, val owners: List<PublicKey>) : ContractState {
|
||||
override val contract = DUMMY_V2_PROGRAM_ID
|
||||
override val participants: List<CompositeKey> = owners
|
||||
override val participants: List<PublicKey> = owners
|
||||
}
|
||||
|
||||
interface Commands : CommandData {
|
||||
@ -35,7 +36,7 @@ class DummyContractV2 : UpgradedContract<DummyContract.State, DummyContractV2.St
|
||||
|
||||
// The "empty contract"
|
||||
override val legalContractReference: SecureHash = SecureHash.sha256("")
|
||||
|
||||
// DOCEND 1
|
||||
/**
|
||||
* Generate an upgrade transaction from [DummyContract].
|
||||
*
|
||||
@ -43,7 +44,7 @@ class DummyContractV2 : UpgradedContract<DummyContract.State, DummyContractV2.St
|
||||
*
|
||||
* @return a pair of wire transaction, and a set of those who should sign the transaction for it to be valid.
|
||||
*/
|
||||
fun generateUpgradeFromV1(vararg states: StateAndRef<DummyContract.State>): Pair<WireTransaction, Set<CompositeKey>> {
|
||||
fun generateUpgradeFromV1(vararg states: StateAndRef<DummyContract.State>): Pair<WireTransaction, Set<PublicKey>> {
|
||||
val notary = states.map { it.state.notary }.single()
|
||||
require(states.isNotEmpty())
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import java.security.PublicKey
|
||||
|
||||
/**
|
||||
* Dummy state for use in testing. Not part of any contract, not even the [DummyContract].
|
||||
*/
|
||||
data class DummyState(val magicNumber: Int = 0) : ContractState {
|
||||
override val contract = DUMMY_PROGRAM_ID
|
||||
override val participants: List<CompositeKey>
|
||||
override val participants: List<PublicKey>
|
||||
get() = emptyList()
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.flows.FlowException
|
||||
import java.security.PublicKey
|
||||
|
||||
class InsufficientBalanceException(val amountMissing: Amount<*>) : FlowException("Insufficient balance, missing $amountMissing")
|
||||
|
||||
@ -25,11 +25,11 @@ interface FungibleAsset<T : Any> : OwnableState {
|
||||
* There must be an ExitCommand signed by these keys to destroy the amount. While all states require their
|
||||
* owner to sign, some (i.e. cash) also require the issuer.
|
||||
*/
|
||||
val exitKeys: Collection<CompositeKey>
|
||||
val exitKeys: Collection<PublicKey>
|
||||
/** There must be a MoveCommand signed by this key to claim the amount */
|
||||
override val owner: CompositeKey
|
||||
override val owner: PublicKey
|
||||
|
||||
fun move(newAmount: Amount<Issued<T>>, newOwner: CompositeKey): FungibleAsset<T>
|
||||
fun move(newAmount: Amount<Issued<T>>, newOwner: PublicKey): FungibleAsset<T>
|
||||
|
||||
// Just for grouping
|
||||
interface Commands : CommandData {
|
||||
|
@ -2,7 +2,6 @@ package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.contracts.clauses.Clause
|
||||
import net.corda.core.crypto.AnonymousParty
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogicRef
|
||||
@ -116,7 +115,7 @@ interface ContractState {
|
||||
* The participants list should normally be derived from the contents of the state. E.g. for [Cash] the participants
|
||||
* list should just contain the owner.
|
||||
*/
|
||||
val participants: List<CompositeKey>
|
||||
val participants: List<PublicKey>
|
||||
}
|
||||
|
||||
/**
|
||||
@ -189,10 +188,10 @@ fun <T : Any> Amount<Issued<T>>.withoutIssuer(): Amount<T> = Amount(quantity, to
|
||||
*/
|
||||
interface OwnableState : ContractState {
|
||||
/** There must be a MoveCommand signed by this key to claim the amount */
|
||||
val owner: CompositeKey
|
||||
val owner: PublicKey
|
||||
|
||||
/** Copies the underlying data structure, replacing the owner field with this new value and leaving the rest alone */
|
||||
fun withNewOwner(newOwner: CompositeKey): Pair<CommandData, OwnableState>
|
||||
fun withNewOwner(newOwner: PublicKey): Pair<CommandData, OwnableState>
|
||||
}
|
||||
|
||||
/** Something which is scheduled to happen at a point in time */
|
||||
@ -237,7 +236,7 @@ interface LinearState : ContractState {
|
||||
|
||||
/**
|
||||
* True if this should be tracked by our vault(s).
|
||||
* */
|
||||
*/
|
||||
fun isRelevant(ourKeys: Set<PublicKey>): Boolean
|
||||
|
||||
/**
|
||||
@ -376,12 +375,12 @@ 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>) {
|
||||
data class Command(val value: CommandData, val signers: List<PublicKey>) {
|
||||
init {
|
||||
require(signers.isNotEmpty())
|
||||
}
|
||||
|
||||
constructor(data: CommandData, key: CompositeKey) : this(data, listOf(key))
|
||||
constructor(data: CommandData, key: PublicKey) : this(data, listOf(key))
|
||||
|
||||
private fun commandDataToString() = value.toString().let { if (it.contains("@")) it.replace('$', '.').split("@")[0] else it }
|
||||
override fun toString() = "${commandDataToString()} with pubkeys ${signers.joinToString()}"
|
||||
@ -415,7 +414,7 @@ data class UpgradeCommand(val upgradedContractClass: Class<out UpgradedContract<
|
||||
/** 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>,
|
||||
val signers: List<PublicKey>,
|
||||
/** If any public keys were recognised, the looked up institutions are available here */
|
||||
val signingParties: List<Party>,
|
||||
val value: T
|
||||
|
@ -1,10 +1,10 @@
|
||||
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
|
||||
import java.security.PublicKey
|
||||
|
||||
/** Defines transaction build & validation logic for a specific transaction type */
|
||||
@CordaSerializable
|
||||
@ -27,7 +27,7 @@ sealed class TransactionType {
|
||||
}
|
||||
|
||||
/** Check that the list of signers includes all the necessary keys */
|
||||
fun verifySigners(tx: LedgerTransaction): Set<CompositeKey> {
|
||||
fun verifySigners(tx: LedgerTransaction): Set<PublicKey> {
|
||||
val notaryKey = tx.inputs.map { it.state.notary.owningKey }.toSet()
|
||||
if (notaryKey.size > 1) throw TransactionVerificationException.MoreThanOneNotary(tx)
|
||||
|
||||
@ -54,7 +54,7 @@ sealed class TransactionType {
|
||||
* Return the list of public keys that that require signatures for the transaction type.
|
||||
* Note: the notary key is checked separately for all transactions and need not be included.
|
||||
*/
|
||||
abstract fun getRequiredSigners(tx: LedgerTransaction): Set<CompositeKey>
|
||||
abstract fun getRequiredSigners(tx: LedgerTransaction): Set<PublicKey>
|
||||
|
||||
/** Implement type specific transaction validation logic */
|
||||
abstract fun verifyTransaction(tx: LedgerTransaction)
|
||||
|
@ -1,11 +1,11 @@
|
||||
package 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.flows.FlowException
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
|
||||
// TODO: Consider moving this out of the core module and providing a different way for unit tests to test contracts.
|
||||
@ -101,7 +101,7 @@ class TransactionConflictException(val conflictRef: StateRef, val tx1: LedgerTra
|
||||
sealed class TransactionVerificationException(val tx: LedgerTransaction, cause: Throwable?) : FlowException(cause) {
|
||||
class ContractRejection(tx: LedgerTransaction, val contract: Contract, cause: Throwable?) : TransactionVerificationException(tx, cause)
|
||||
class MoreThanOneNotary(tx: LedgerTransaction) : TransactionVerificationException(tx, null)
|
||||
class SignersMissing(tx: LedgerTransaction, val missing: List<CompositeKey>) : TransactionVerificationException(tx, null) {
|
||||
class SignersMissing(tx: LedgerTransaction, val missing: List<PublicKey>) : TransactionVerificationException(tx, null) {
|
||||
override fun toString(): String = "Signers missing: ${missing.joinToString()}"
|
||||
}
|
||||
|
||||
|
@ -10,10 +10,7 @@ import java.security.PublicKey
|
||||
* 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)
|
||||
|
||||
abstract class AbstractParty(val owningKey: PublicKey) {
|
||||
/** 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
|
||||
|
||||
|
@ -8,10 +8,7 @@ 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.
|
||||
*/
|
||||
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)
|
||||
|
||||
class AnonymousParty(owningKey: PublicKey) : AbstractParty(owningKey) {
|
||||
// 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>"
|
||||
|
@ -1,118 +1,146 @@
|
||||
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
|
||||
|
||||
/**
|
||||
* A tree data structure that enables the representation of composite public keys.
|
||||
* Notice that with that implementation CompositeKey extends PublicKey. Leaves are represented by single public keys.
|
||||
*
|
||||
* In the simplest case it may just contain a single node encapsulating a [PublicKey] – a [Leaf].
|
||||
*
|
||||
* For more complex scenarios, such as *"Both Alice and Bob need to sign to consume a state S"*, we can represent
|
||||
* the requirement by creating a tree with a root [Node], and Alice and Bob as children – [Leaf]s.
|
||||
* For complex scenarios, such as *"Both Alice and Bob need to sign to consume a state S"*, we can represent
|
||||
* the requirement by creating a tree with a root [CompositeKey], and Alice and Bob as children.
|
||||
* The root node would specify *weights* for each of its children and a *threshold* – the minimum total weight required
|
||||
* (e.g. the minimum number of child signatures required) to satisfy the tree signature requirement.
|
||||
*
|
||||
* 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"*.
|
||||
*
|
||||
* [CompositeKey] maintains a list of [NodeAndWeight]s which holds child subtree with associated weight carried by child node signatures.
|
||||
*
|
||||
* The [threshold] specifies the minimum total weight required (in the simple case – the minimum number of child
|
||||
* signatures required) to satisfy the sub-tree rooted at this node.
|
||||
*/
|
||||
@CordaSerializable
|
||||
sealed class CompositeKey {
|
||||
/** Checks whether [keys] match a sufficient amount of leaf nodes */
|
||||
abstract fun isFulfilledBy(keys: Iterable<PublicKey>): Boolean
|
||||
|
||||
fun isFulfilledBy(key: PublicKey) = isFulfilledBy(setOf(key))
|
||||
|
||||
/** Returns all [PublicKey]s contained within the tree leaves */
|
||||
abstract val keys: Set<PublicKey>
|
||||
|
||||
/** Checks whether any of the given [keys] matches a leaf on the tree */
|
||||
fun containsAny(otherKeys: Iterable<PublicKey>) = keys.intersect(otherKeys).isNotEmpty()
|
||||
class CompositeKey private constructor (val threshold: Int,
|
||||
children: List<NodeAndWeight>) : PublicKey {
|
||||
val children = children.sorted()
|
||||
init {
|
||||
require (children.size == children.toSet().size) { "Trying to construct CompositeKey with duplicated child nodes." }
|
||||
// If we want PublicKey we only keep one key, otherwise it will lead to semantically equivalent trees but having different structures.
|
||||
require(children.size > 1) { "Cannot construct CompositeKey with only one child node." }
|
||||
}
|
||||
|
||||
/**
|
||||
* This is generated by serializing the composite key with Kryo, and encoding the resulting bytes in base58.
|
||||
* A custom serialization format is being used.
|
||||
*
|
||||
* TODO: follow the crypto-conditions ASN.1 spec, some changes are needed to be compatible with the condition
|
||||
* structure, e.g. mapping a PublicKey to a condition with the specific feature (ED25519).
|
||||
* Holds node - weight pairs for a CompositeKey. Ordered first by weight, then by node's hashCode.
|
||||
*/
|
||||
fun toBase58String(): String = Base58.encode(this.serialize().bytes)
|
||||
@CordaSerializable
|
||||
data class NodeAndWeight(val node: PublicKey, val weight: Int): Comparable<NodeAndWeight> {
|
||||
override fun compareTo(other: NodeAndWeight): Int {
|
||||
if (weight == other.weight) {
|
||||
return node.hashCode().compareTo(other.node.hashCode())
|
||||
}
|
||||
else return weight.compareTo(other.weight)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun parseFromBase58(encoded: String) = Base58.decode(encoded).deserialize<CompositeKey>()
|
||||
}
|
||||
|
||||
/** The leaf node of the tree – a wrapper around a [PublicKey] primitive */
|
||||
data class Leaf(val publicKey: PublicKey) : CompositeKey() {
|
||||
override fun isFulfilledBy(keys: Iterable<PublicKey>) = publicKey in keys
|
||||
|
||||
override val keys: Set<PublicKey> get() = setOf(publicKey)
|
||||
|
||||
override fun toString() = publicKey.toStringShort()
|
||||
// TODO: Get the design standardised and from there define a recognised name
|
||||
val ALGORITHM = "X-Corda-CompositeKey"
|
||||
// TODO: We should be using a well defined format.
|
||||
val FORMAT = "X-Corda-Kryo"
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a node in the key tree. It maintains a list of child nodes – sub-trees, and associated
|
||||
* [weights] carried by child node signatures.
|
||||
*
|
||||
* The [threshold] specifies the minimum total weight required (in the simple case – the minimum number of child
|
||||
* signatures required) to satisfy the sub-tree rooted at this node.
|
||||
* Takes single PublicKey and checks if CompositeKey requirements hold for that key.
|
||||
*/
|
||||
data class Node(val threshold: Int, val children: List<CompositeKey>, val weights: List<Int>) : CompositeKey() {
|
||||
override fun isFulfilledBy(keys: Iterable<PublicKey>): Boolean {
|
||||
val totalWeight = children.mapIndexed { i, childNode ->
|
||||
if (childNode.isFulfilledBy(keys)) weights[i] else 0
|
||||
}.sum()
|
||||
fun isFulfilledBy(key: PublicKey) = isFulfilledBy(setOf(key))
|
||||
|
||||
return totalWeight >= threshold
|
||||
}
|
||||
override fun getAlgorithm() = ALGORITHM
|
||||
override fun getEncoded(): ByteArray = this.serialize().bytes
|
||||
override fun getFormat() = FORMAT
|
||||
|
||||
override val keys: Set<PublicKey> get() = children.flatMap { it.keys }.toSet()
|
||||
|
||||
override fun toString() = "(${children.joinToString()})"
|
||||
/**
|
||||
* Function checks if the public keys corresponding to the signatures are matched against the leaves of the composite
|
||||
* key tree in question, and the total combined weight of all children is calculated for every intermediary node.
|
||||
* If all thresholds are satisfied, the composite key requirement is considered to be met.
|
||||
*/
|
||||
fun isFulfilledBy(keysToCheck: Iterable<PublicKey>): Boolean {
|
||||
if (keysToCheck.any { it is CompositeKey } ) return false
|
||||
val totalWeight = children.map { (node, weight) ->
|
||||
if (node is CompositeKey) {
|
||||
if (node.isFulfilledBy(keysToCheck)) weight else 0
|
||||
} else {
|
||||
if (keysToCheck.contains(node)) weight else 0
|
||||
}
|
||||
}.sum()
|
||||
return totalWeight >= threshold
|
||||
}
|
||||
|
||||
/** A helper class for building a [CompositeKey.Node]. */
|
||||
/**
|
||||
* Set of all leaf keys of that CompositeKey.
|
||||
*/
|
||||
val leavesKeys: Set<PublicKey>
|
||||
get() = children.flatMap { it.node.keys }.toSet() // Uses PublicKey.keys extension.
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is CompositeKey) return false
|
||||
if (threshold != other.threshold) return false
|
||||
if (children != other.children) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = threshold
|
||||
result = 31 * result + children.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString() = "(${children.joinToString()})"
|
||||
|
||||
/** A helper class for building a [CompositeKey]. */
|
||||
class Builder {
|
||||
private val children: MutableList<CompositeKey> = mutableListOf()
|
||||
private val weights: MutableList<Int> = mutableListOf()
|
||||
private val children: MutableList<NodeAndWeight> = mutableListOf()
|
||||
|
||||
/** Adds a child [CompositeKey] node. Specifying a [weight] for the child is optional and will default to 1. */
|
||||
fun addKey(key: CompositeKey, weight: Int = 1): Builder {
|
||||
children.add(key)
|
||||
weights.add(weight)
|
||||
fun addKey(key: PublicKey, weight: Int = 1): Builder {
|
||||
children.add(NodeAndWeight(key, weight))
|
||||
return this
|
||||
}
|
||||
|
||||
fun addKeys(vararg keys: CompositeKey): Builder {
|
||||
fun addKeys(vararg keys: PublicKey): Builder {
|
||||
keys.forEach { addKey(it) }
|
||||
return this
|
||||
}
|
||||
|
||||
fun addKeys(keys: List<CompositeKey>): Builder = addKeys(*keys.toTypedArray())
|
||||
fun addKeys(keys: List<PublicKey>): Builder = addKeys(*keys.toTypedArray())
|
||||
|
||||
/**
|
||||
* Builds the [CompositeKey.Node]. If [threshold] is not specified, it will default to
|
||||
* Builds the [CompositeKey]. If [threshold] is not specified, it will default to
|
||||
* the size of the children, effectively generating an "N of N" requirement.
|
||||
* During process removes single keys wrapped in [CompositeKey] and enforces ordering on child nodes.
|
||||
*/
|
||||
fun build(threshold: Int? = null): CompositeKey.Node {
|
||||
return Node(threshold ?: children.size, children.toList(), weights.toList())
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun build(threshold: Int? = null): PublicKey {
|
||||
val n = children.size
|
||||
if (n > 1)
|
||||
return CompositeKey(threshold ?: n, children)
|
||||
else if (n == 1) {
|
||||
require(threshold == null || threshold == children.first().weight)
|
||||
{ "Trying to build invalid CompositeKey, threshold value different than weight of single child node." }
|
||||
return children.first().node // We can assume that this node is a correct CompositeKey.
|
||||
}
|
||||
else throw IllegalArgumentException("Trying to build CompositeKey without child nodes.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the enclosed [PublicKey] for a [CompositeKey] with a single leaf node
|
||||
*
|
||||
* @throws IllegalArgumentException if the [CompositeKey] contains more than one node
|
||||
*/
|
||||
val singleKey: PublicKey
|
||||
get() = keys.singleOrNull() ?: throw IllegalStateException("The key is composed of more than one PublicKey primitive")
|
||||
}
|
||||
|
||||
/** Returns the set of all [PublicKey]s contained in the leaves of the [CompositeKey]s */
|
||||
val Iterable<CompositeKey>.keys: Set<PublicKey>
|
||||
/**
|
||||
* Expands all [CompositeKey]s present in PublicKey iterable to set of single [PublicKey]s.
|
||||
* If an element of the set is a single PublicKey it gives just that key, if it is a [CompositeKey] it returns all leaf
|
||||
* keys for that composite element.
|
||||
*/
|
||||
val Iterable<PublicKey>.expandedCompositeKeys: Set<PublicKey>
|
||||
get() = flatMap { it.keys }.toSet()
|
@ -4,6 +4,8 @@ package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.OpaqueBytes
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.i2p.crypto.eddsa.EdDSAEngine
|
||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||
@ -12,6 +14,7 @@ import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
|
||||
import java.math.BigInteger
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.KeyPair
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
@ -27,7 +30,7 @@ open class DigitalSignature(bits: ByteArray) : OpaqueBytes(bits) {
|
||||
}
|
||||
|
||||
// TODO: consider removing this as whoever needs to identify the signer should be able to derive it from the public key
|
||||
class LegallyIdentifiable(val signer: Party, bits: ByteArray) : WithKey(signer.owningKey.singleKey, bits)
|
||||
class LegallyIdentifiable(val signer: Party, bits: ByteArray) : WithKey(signer.owningKey, bits)
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
@ -39,7 +42,6 @@ object NullPublicKey : PublicKey, Comparable<PublicKey> {
|
||||
override fun toString() = "NULL_KEY"
|
||||
}
|
||||
|
||||
val NullCompositeKey = NullPublicKey.composite
|
||||
|
||||
// TODO: Clean up this duplication between Null and Dummy public key
|
||||
@CordaSerializable
|
||||
@ -72,22 +74,37 @@ fun PrivateKey.signWithECDSA(bytesToSign: ByteArray, publicKey: PublicKey): Digi
|
||||
|
||||
val ed25519Curve = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)
|
||||
|
||||
fun parsePublicKeyBase58(base58String: String) = EdDSAPublicKey(EdDSAPublicKeySpec(Base58.decode(base58String), ed25519Curve))
|
||||
fun PublicKey.toBase58String() = Base58.encode((this as EdDSAPublicKey).abyte)
|
||||
// TODO We use for both CompositeKeys and EdDSAPublicKey custom Kryo serializers and deserializers. We need to specify encoding.
|
||||
// TODO: follow the crypto-conditions ASN.1 spec, some changes are needed to be compatible with the condition
|
||||
// structure, e.g. mapping a PublicKey to a condition with the specific feature (ED25519).
|
||||
fun parsePublicKeyBase58(base58String: String): PublicKey = Base58.decode(base58String).deserialize<PublicKey>()
|
||||
fun PublicKey.toBase58String(): String = Base58.encode(this.serialize().bytes)
|
||||
|
||||
fun KeyPair.signWithECDSA(bytesToSign: ByteArray) = private.signWithECDSA(bytesToSign, public)
|
||||
fun KeyPair.signWithECDSA(bytesToSign: OpaqueBytes) = private.signWithECDSA(bytesToSign.bytes, public)
|
||||
fun KeyPair.signWithECDSA(bytesToSign: OpaqueBytes, party: Party) = signWithECDSA(bytesToSign.bytes, party)
|
||||
// TODO This case will need more careful thinking, as party owningKey can be a CompositeKey. One way of doing that is
|
||||
// implementation of CompositeSignature.
|
||||
@Throws(InvalidKeyException::class)
|
||||
fun KeyPair.signWithECDSA(bytesToSign: ByteArray, party: Party): DigitalSignature.LegallyIdentifiable {
|
||||
check(public in party.owningKey.keys)
|
||||
val sig = signWithECDSA(bytesToSign)
|
||||
val sigKey = when (party.owningKey) { // Quick workaround when we have CompositeKey as Party owningKey.
|
||||
is CompositeKey -> throw InvalidKeyException("Signing for parties with CompositeKey not supported.")
|
||||
else -> party.owningKey
|
||||
}
|
||||
sigKey.verifyWithECDSA(bytesToSign, sig)
|
||||
return DigitalSignature.LegallyIdentifiable(party, sig.bytes)
|
||||
}
|
||||
|
||||
/** Utility to simplify the act of verifying a signature */
|
||||
@Throws(SignatureException::class, IllegalStateException::class)
|
||||
fun PublicKey.verifyWithECDSA(content: ByteArray, signature: DigitalSignature) {
|
||||
val pubKey = when (this) {
|
||||
is CompositeKey -> throw IllegalStateException("Verification of CompositeKey signatures currently not supported.") // TODO CompositeSignature verification.
|
||||
else -> this
|
||||
}
|
||||
val verifier = EdDSAEngine()
|
||||
verifier.initVerify(this)
|
||||
verifier.initVerify(pubKey)
|
||||
verifier.update(content)
|
||||
if (verifier.verify(signature.bytes) == false)
|
||||
throw SignatureException("Signature did not match")
|
||||
@ -100,8 +117,22 @@ fun PublicKey.toStringShort(): String {
|
||||
} ?: toString()
|
||||
}
|
||||
|
||||
/** Creates a [CompositeKey] with a single leaf node containing the public key */
|
||||
val PublicKey.composite: CompositeKey get() = CompositeKey.Leaf(this)
|
||||
val PublicKey.keys: Set<PublicKey> get() {
|
||||
return if (this is CompositeKey) this.leavesKeys
|
||||
else setOf(this)
|
||||
}
|
||||
|
||||
fun PublicKey.isFulfilledBy(otherKey: PublicKey): Boolean = isFulfilledBy(setOf(otherKey))
|
||||
fun PublicKey.isFulfilledBy(otherKeys: Iterable<PublicKey>): Boolean {
|
||||
return if (this is CompositeKey) this.isFulfilledBy(otherKeys)
|
||||
else this in otherKeys
|
||||
}
|
||||
|
||||
/** Checks whether any of the given [keys] matches a leaf on the CompositeKey tree or a single PublicKey */
|
||||
fun PublicKey.containsAny(otherKeys: Iterable<PublicKey>): Boolean {
|
||||
return if (this is CompositeKey) keys.intersect(otherKeys).isNotEmpty()
|
||||
else this in otherKeys
|
||||
}
|
||||
|
||||
/** Returns the set of all [PublicKey]s of the signatures */
|
||||
fun Iterable<DigitalSignature.WithKey>.byKeys() = map { it.by }.toSet()
|
||||
|
@ -7,7 +7,7 @@ import java.security.PublicKey
|
||||
/**
|
||||
* The [Party] class represents an entity on the network, which is typically identified by a legal [name] and public key
|
||||
* that it can sign transactions under. As parties may use multiple keys for signing and, for example, have offline backup
|
||||
* keys, the "public key" of a party is represented by a composite construct – a [CompositeKey], which combines multiple
|
||||
* keys, the "public key" of a party can be represented by a composite construct – a [CompositeKey], which combines multiple
|
||||
* 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.
|
||||
@ -22,10 +22,7 @@ import java.security.PublicKey
|
||||
*
|
||||
* @see CompositeKey
|
||||
*/
|
||||
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)
|
||||
|
||||
class Party(val name: String, owningKey: PublicKey) : AbstractParty(owningKey) {
|
||||
override fun toAnonymous(): AnonymousParty = AnonymousParty(owningKey)
|
||||
override fun toString() = "${owningKey.toBase58String()} (${name})"
|
||||
override fun nameOrNull(): String? = name
|
||||
|
@ -5,7 +5,6 @@ import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.UpgradedContract
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
@ -18,6 +17,7 @@ import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import rx.Observable
|
||||
import java.io.InputStream
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
@ -158,7 +158,7 @@ interface CordaRPCOps : RPCOps {
|
||||
/**
|
||||
* Returns the [Party] corresponding to the given key, if found.
|
||||
*/
|
||||
fun partyFromKey(key: CompositeKey): Party?
|
||||
fun partyFromKey(key: PublicKey): Party?
|
||||
|
||||
/**
|
||||
* Returns the [Party] with the given name as it's [Party.name]
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.core.node
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.keys
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowStateMachine
|
||||
import net.corda.core.messaging.MessagingService
|
||||
@ -109,6 +110,8 @@ interface ServiceHub : ServicesForResolution {
|
||||
* used in contexts where the Node knows it is hosting a Notary Service. Otherwise, it will throw
|
||||
* an IllegalArgumentException.
|
||||
* Typical use is during signing in flows and for unit test signing.
|
||||
*
|
||||
* TODO: same problem as with legalIdentityKey.
|
||||
*/
|
||||
val notaryIdentityKey: KeyPair get() = this.keyManagementService.toKeyPair(this.myInfo.notaryIdentity.owningKey.keys)
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ package net.corda.core.node.services
|
||||
|
||||
import net.corda.core.contracts.PartyAndReference
|
||||
import net.corda.core.crypto.AnonymousParty
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.Party
|
||||
import java.security.PublicKey
|
||||
|
||||
/**
|
||||
* An identity service maintains an bidirectional map of [Party]s to their associated public keys and thus supports
|
||||
@ -23,7 +23,7 @@ interface IdentityService {
|
||||
// indefinitely. It may be that in the long term we need to drop or archive very old Party information for space,
|
||||
// but for now this is not supported.
|
||||
|
||||
fun partyFromKey(key: CompositeKey): Party?
|
||||
fun partyFromKey(key: PublicKey): Party?
|
||||
fun partyFromName(name: String): Party?
|
||||
|
||||
fun partyFromAnonymous(party: AnonymousParty): Party?
|
||||
|
@ -3,7 +3,6 @@ package net.corda.core.node.services
|
||||
import com.google.common.annotations.VisibleForTesting
|
||||
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.MessagingService
|
||||
import net.corda.core.messaging.SingleMessageRecipient
|
||||
@ -11,6 +10,7 @@ import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.randomOrNull
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import rx.Observable
|
||||
import java.security.PublicKey
|
||||
|
||||
/**
|
||||
* A network map contains lists of nodes on the network along with information about their identity keys, services
|
||||
@ -74,11 +74,11 @@ interface NetworkMapCache {
|
||||
*/
|
||||
|
||||
/** Look up the node info for a specific peer key. */
|
||||
fun getNodeByLegalIdentityKey(compositeKey: CompositeKey): NodeInfo?
|
||||
fun getNodeByLegalIdentityKey(identityKey: PublicKey): 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 } }
|
||||
/** Look up all nodes advertising the service owned by [publicKey] */
|
||||
fun getNodesByAdvertisedServiceIdentityKey(publicKey: PublicKey): List<NodeInfo> {
|
||||
return partyNodes.filter { it.advertisedServices.any { it.identity.owningKey == publicKey } }
|
||||
}
|
||||
|
||||
/** Returns information about the party, which may be a specific node or a service */
|
||||
|
@ -204,8 +204,8 @@ interface VaultService {
|
||||
@Suspendable
|
||||
fun generateSpend(tx: TransactionBuilder,
|
||||
amount: Amount<Currency>,
|
||||
to: CompositeKey,
|
||||
onlyFromParties: Set<AbstractParty>? = null): Pair<TransactionBuilder, List<CompositeKey>>
|
||||
to: PublicKey,
|
||||
onlyFromParties: Set<AbstractParty>? = null): Pair<TransactionBuilder, List<PublicKey>>
|
||||
|
||||
// DOCSTART VaultStatesQuery
|
||||
/**
|
||||
@ -288,11 +288,19 @@ interface KeyManagementService {
|
||||
/** Returns a snapshot of the current pubkey->privkey mapping. */
|
||||
val keys: Map<PublicKey, PrivateKey>
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
fun toPrivate(publicKey: PublicKey) = keys[publicKey] ?: throw IllegalStateException("No private key known for requested public key ${publicKey.toStringShort()}")
|
||||
|
||||
fun toKeyPair(publicKey: PublicKey) = KeyPair(publicKey, toPrivate(publicKey))
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun toKeyPair(publicKey: PublicKey): KeyPair {
|
||||
when (publicKey) {
|
||||
is CompositeKey -> throw IllegalArgumentException("Got CompositeKey when single PublicKey expected.")
|
||||
else -> return KeyPair(publicKey, toPrivate(publicKey))
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the first [KeyPair] matching any of the [publicKeys] */
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun toKeyPair(publicKeys: Iterable<PublicKey>) = publicKeys.first { keys.contains(it) }.let { toKeyPair(it) }
|
||||
|
||||
/** Generates a new random key and adds it to the exposed map. */
|
||||
|
@ -65,8 +65,7 @@ object DefaultKryoCustomizer {
|
||||
register(EdDSAPrivateKey::class.java, Ed25519PrivateKeySerializer)
|
||||
|
||||
// Using a custom serializer for compactness
|
||||
register(CompositeKey.Node::class.java, CompositeKeyNodeSerializer)
|
||||
register(CompositeKey.Leaf::class.java, CompositeKeyLeafSerializer)
|
||||
register(CompositeKey::class.java, CompositeKeySerializer)
|
||||
|
||||
// Exceptions. We don't bother sending the stack traces as the client will fill in its own anyway.
|
||||
register(Array<StackTraceElement>::class, read = { _, _ -> emptyArray() }, write = { _, _, _ -> })
|
||||
|
@ -330,7 +330,7 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
|
||||
val outputs = kryo.readClassAndObject(input) as List<TransactionState<ContractState>>
|
||||
val commands = kryo.readClassAndObject(input) as List<Command>
|
||||
val notary = kryo.readClassAndObject(input) as Party?
|
||||
val signers = kryo.readClassAndObject(input) as List<CompositeKey>
|
||||
val signers = kryo.readClassAndObject(input) as List<PublicKey>
|
||||
val transactionType = kryo.readClassAndObject(input) as TransactionType
|
||||
val timestamp = kryo.readClassAndObject(input) as Timestamp?
|
||||
|
||||
@ -367,41 +367,38 @@ object Ed25519PublicKeySerializer : Serializer<EdDSAPublicKey>() {
|
||||
}
|
||||
}
|
||||
|
||||
/** For serialising composite keys */
|
||||
// TODO Implement standardized serialization of CompositeKeys. See JIRA issue: CORDA-249.
|
||||
@ThreadSafe
|
||||
object CompositeKeyLeafSerializer : Serializer<CompositeKey.Leaf>() {
|
||||
override fun write(kryo: Kryo, output: Output, obj: CompositeKey.Leaf) {
|
||||
val key = obj.publicKey
|
||||
kryo.writeClassAndObject(output, key)
|
||||
}
|
||||
|
||||
override fun read(kryo: Kryo, input: Input, type: Class<CompositeKey.Leaf>): CompositeKey.Leaf {
|
||||
val key = kryo.readClassAndObject(input) as PublicKey
|
||||
return CompositeKey.Leaf(key)
|
||||
}
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
object CompositeKeyNodeSerializer : Serializer<CompositeKey.Node>() {
|
||||
override fun write(kryo: Kryo, output: Output, obj: CompositeKey.Node) {
|
||||
object CompositeKeySerializer : Serializer<CompositeKey>() {
|
||||
override fun write(kryo: Kryo, output: Output, obj: CompositeKey) {
|
||||
output.writeInt(obj.threshold)
|
||||
output.writeInt(obj.children.size)
|
||||
obj.children.forEach { kryo.writeClassAndObject(output, it) }
|
||||
output.writeInts(obj.weights.toIntArray())
|
||||
}
|
||||
|
||||
override fun read(kryo: Kryo, input: Input, type: Class<CompositeKey.Node>): CompositeKey.Node {
|
||||
override fun read(kryo: Kryo, input: Input, type: Class<CompositeKey>): CompositeKey {
|
||||
val threshold = input.readInt()
|
||||
val childCount = input.readInt()
|
||||
val children = (1..childCount).map { kryo.readClassAndObject(input) as CompositeKey }
|
||||
val weights = input.readInts(childCount)
|
||||
|
||||
val children = readListOfLength<CompositeKey.NodeAndWeight>(kryo, input, minLen = 2)
|
||||
val builder = CompositeKey.Builder()
|
||||
weights.zip(children).forEach { builder.addKey(it.second, it.first) }
|
||||
return builder.build(threshold)
|
||||
children.forEach { builder.addKey(it.node, it.weight) }
|
||||
return builder.build(threshold) as CompositeKey
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for reading lists with number of elements at the beginning.
|
||||
* @param minLen minimum number of elements we expect for list to include, defaults to 1
|
||||
* @param expectedLen expected length of the list, defaults to null if arbitrary length list read
|
||||
*/
|
||||
inline fun <reified T> readListOfLength(kryo: Kryo, input: Input, minLen: Int = 1, expectedLen: Int? = null): List<T> {
|
||||
val elemCount = input.readInt()
|
||||
if (elemCount < minLen) throw KryoException("Cannot deserialize list, too little elements. Minimum required: $minLen, got: $elemCount")
|
||||
if (expectedLen != null && elemCount != expectedLen)
|
||||
throw KryoException("Cannot deserialize list, expected length: $expectedLen, got: $elemCount.")
|
||||
val list = (1..elemCount).map { kryo.readClassAndObject(input) as T }
|
||||
return list
|
||||
}
|
||||
|
||||
/** Marker interface for kotlin object definitions so that they are deserialized as the singleton instance. */
|
||||
interface DeserializeAsKotlinObjectDef
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
package net.corda.core.transactions
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.Party
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@ -20,14 +20,14 @@ abstract class BaseTransaction(
|
||||
*/
|
||||
val notary: Party?,
|
||||
/**
|
||||
* Composite keys that need to be fulfilled by signatures in order for the transaction to be valid.
|
||||
* Public keys that need to be fulfilled by signatures in order for the transaction to be valid.
|
||||
* In a [SignedTransaction] this list is used to check whether there are any missing signatures. Note that
|
||||
* there is nothing that forces the list to be the _correct_ list of signers for this transaction until
|
||||
* the transaction is verified by using [LedgerTransaction.verify].
|
||||
*
|
||||
* It includes the notary key, if the notary field is set.
|
||||
*/
|
||||
val mustSign: List<CompositeKey>,
|
||||
val mustSign: List<PublicKey>,
|
||||
/**
|
||||
* Pointer to a class that defines the behaviour of this transaction: either normal, or "notary changing".
|
||||
*/
|
||||
|
@ -1,10 +1,10 @@
|
||||
package net.corda.core.transactions
|
||||
|
||||
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
|
||||
import java.security.PublicKey
|
||||
|
||||
/**
|
||||
* A LedgerTransaction is derived from a [WireTransaction]. It is the result of doing the following operations:
|
||||
@ -29,7 +29,7 @@ class LedgerTransaction(
|
||||
/** The hash of the original serialised WireTransaction. */
|
||||
override val id: SecureHash,
|
||||
notary: Party?,
|
||||
signers: List<CompositeKey>,
|
||||
signers: List<PublicKey>,
|
||||
timestamp: Timestamp?,
|
||||
type: TransactionType
|
||||
) : BaseTransaction(inputs, outputs, notary, signers, type, timestamp) {
|
||||
|
@ -5,6 +5,7 @@ import net.corda.core.crypto.*
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.p2PKryo
|
||||
import net.corda.core.serialization.serialize
|
||||
import java.security.PublicKey
|
||||
import net.corda.core.serialization.withoutReferences
|
||||
|
||||
fun <T : Any> serializedHash(x: T): SecureHash {
|
||||
@ -26,7 +27,7 @@ interface TraversableTransaction {
|
||||
val outputs: List<TransactionState<ContractState>>
|
||||
val commands: List<Command>
|
||||
val notary: Party?
|
||||
val mustSign: List<CompositeKey>
|
||||
val mustSign: List<PublicKey>
|
||||
val type: TransactionType?
|
||||
val timestamp: Timestamp?
|
||||
|
||||
@ -74,7 +75,7 @@ class FilteredLeaves(
|
||||
override val outputs: List<TransactionState<ContractState>>,
|
||||
override val commands: List<Command>,
|
||||
override val notary: Party?,
|
||||
override val mustSign: List<CompositeKey>,
|
||||
override val mustSign: List<PublicKey>,
|
||||
override val type: TransactionType?,
|
||||
override val timestamp: Timestamp?
|
||||
) : TraversableTransaction {
|
||||
|
@ -3,21 +3,22 @@ package net.corda.core.transactions
|
||||
import net.corda.core.contracts.AttachmentResolutionException
|
||||
import net.corda.core.contracts.NamedByHash
|
||||
import net.corda.core.contracts.TransactionResolutionException
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.isFulfilledBy
|
||||
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.PublicKey
|
||||
import java.security.SignatureException
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* SignedTransaction wraps a serialized WireTransaction. It contains one or more signatures, each one for
|
||||
* a public key that is mentioned inside a transaction command. SignedTransaction is the top level transaction type
|
||||
* and the type most frequently passed around the network and stored. The identity of a transaction is the hash
|
||||
* a public key (including composite keys) that is mentioned inside a transaction command. SignedTransaction is the top level transaction type
|
||||
* and the type most frequently passed around the network and stored. The identity of a transaction is the hash of Merkle root
|
||||
* of a WireTransaction, therefore if you are storing data keyed by WT hash be aware that multiple different STs may
|
||||
* map to the same key (and they could be different in important ways, like validity!). The signatures on a
|
||||
* SignedTransaction might be invalid or missing: the type does not imply validity.
|
||||
@ -43,7 +44,7 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
|
||||
override val id: SecureHash get() = tx.id
|
||||
|
||||
@CordaSerializable
|
||||
class SignaturesMissingException(val missing: Set<CompositeKey>, val descriptions: List<String>, override val id: SecureHash) : NamedByHash, SignatureException() {
|
||||
class SignaturesMissingException(val missing: Set<PublicKey>, 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()}"
|
||||
}
|
||||
@ -62,13 +63,13 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
|
||||
* @throws SignaturesMissingException if any signatures should have been present but were not.
|
||||
*/
|
||||
@Throws(SignatureException::class)
|
||||
fun verifySignatures(vararg allowedToBeMissing: CompositeKey): WireTransaction {
|
||||
fun verifySignatures(vararg allowedToBeMissing: PublicKey): WireTransaction {
|
||||
// Embedded WireTransaction is not deserialised until after we check the signatures.
|
||||
checkSignaturesAreValid()
|
||||
|
||||
val missing = getMissingSignatures()
|
||||
if (missing.isNotEmpty()) {
|
||||
val allowed = setOf(*allowedToBeMissing)
|
||||
val allowed = allowedToBeMissing.toSet()
|
||||
val needed = missing - allowed
|
||||
if (needed.isNotEmpty())
|
||||
throw SignaturesMissingException(needed, getMissingKeyDescriptions(needed), id)
|
||||
@ -92,8 +93,10 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMissingSignatures(): Set<CompositeKey> {
|
||||
private fun getMissingSignatures(): Set<PublicKey> {
|
||||
val sigKeys = sigs.map { it.by }.toSet()
|
||||
// TODO Problem is that we can get single PublicKey wrapped as CompositeKey in allowedToBeMissing/mustSign
|
||||
// equals on CompositeKey won't catch this case (do we want to single PublicKey be equal to the same key wrapped in CompositeKey with threshold 1?)
|
||||
val missing = tx.mustSign.filter { !it.isFulfilledBy(sigKeys) }.toSet()
|
||||
return missing
|
||||
}
|
||||
@ -102,7 +105,7 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
|
||||
* Get a human readable description of where signatures are required from, and are missing, to assist in debugging
|
||||
* the underlying cause.
|
||||
*/
|
||||
private fun getMissingKeyDescriptions(missing: Set<CompositeKey>): ArrayList<String> {
|
||||
private fun getMissingKeyDescriptions(missing: Set<PublicKey>): ArrayList<String> {
|
||||
// TODO: We need a much better way of structuring this data
|
||||
val missingElements = ArrayList<String>()
|
||||
this.tx.commands.forEach { command ->
|
||||
|
@ -6,6 +6,7 @@ import net.corda.core.crypto.*
|
||||
import net.corda.core.flows.FlowStateMachine
|
||||
import net.corda.core.serialization.serialize
|
||||
import java.security.KeyPair
|
||||
import java.security.PublicKey
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
@ -34,7 +35,7 @@ open class TransactionBuilder(
|
||||
protected val attachments: MutableList<SecureHash> = arrayListOf(),
|
||||
protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf(),
|
||||
protected val commands: MutableList<Command> = arrayListOf(),
|
||||
protected val signers: MutableSet<CompositeKey> = mutableSetOf(),
|
||||
protected val signers: MutableSet<PublicKey> = mutableSetOf(),
|
||||
protected var timestamp: Timestamp? = null) {
|
||||
|
||||
val time: Timestamp? get() = timestamp
|
||||
@ -135,7 +136,7 @@ open class TransactionBuilder(
|
||||
fun toSignedTransaction(checkSufficientSignatures: Boolean = true): SignedTransaction {
|
||||
if (checkSufficientSignatures) {
|
||||
val gotKeys = currentSigs.map { it.by }.toSet()
|
||||
val missing: Set<CompositeKey> = signers.filter { !it.isFulfilledBy(gotKeys) }.toSet()
|
||||
val missing: Set<PublicKey> = signers.filter { !it.isFulfilledBy(gotKeys) }.toSet()
|
||||
if (missing.isNotEmpty())
|
||||
throw IllegalStateException("Missing signatures on the transaction for the public keys: ${missing.joinToString()}")
|
||||
}
|
||||
@ -178,8 +179,8 @@ open class TransactionBuilder(
|
||||
commands.add(arg)
|
||||
}
|
||||
|
||||
fun addCommand(data: CommandData, vararg keys: CompositeKey) = addCommand(Command(data, listOf(*keys)))
|
||||
fun addCommand(data: CommandData, keys: List<CompositeKey>) = addCommand(Command(data, keys))
|
||||
fun addCommand(data: CommandData, vararg keys: PublicKey) = addCommand(Command(data, listOf(*keys)))
|
||||
fun addCommand(data: CommandData, keys: List<PublicKey>) = addCommand(Command(data, keys))
|
||||
|
||||
// Accessors that yield immutable snapshots.
|
||||
fun inputStates(): List<StateRef> = ArrayList(inputs)
|
||||
|
@ -2,7 +2,6 @@ package net.corda.core.transactions
|
||||
|
||||
import com.esotericsoftware.kryo.pool.KryoPool
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.MerkleTree
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.SecureHash
|
||||
@ -30,7 +29,7 @@ class WireTransaction(
|
||||
/** Ordered list of ([CommandData], [PublicKey]) pairs that instruct the contracts what to do. */
|
||||
override val commands: List<Command>,
|
||||
notary: Party?,
|
||||
signers: List<CompositeKey>,
|
||||
signers: List<PublicKey>,
|
||||
type: TransactionType,
|
||||
timestamp: Timestamp?
|
||||
) : BaseTransaction(inputs, outputs, notary, signers, type, timestamp), TraversableTransaction {
|
||||
@ -87,7 +86,7 @@ class WireTransaction(
|
||||
*/
|
||||
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
|
||||
fun toLedgerTransaction(
|
||||
resolveIdentity: (CompositeKey) -> Party?,
|
||||
resolveIdentity: (PublicKey) -> Party?,
|
||||
resolveAttachment: (SecureHash) -> Attachment?,
|
||||
resolveStateRef: (StateRef) -> TransactionState<*>?
|
||||
): LedgerTransaction {
|
||||
|
@ -1,8 +1,8 @@
|
||||
package net.corda.core.utilities
|
||||
|
||||
import net.corda.core.ErrorOr
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.parsePublicKeyBase58
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import javax.ws.rs.core.Response
|
||||
|
||||
@ -18,7 +18,7 @@ class ApiUtils(val rpc: CordaRPCOps) {
|
||||
*/
|
||||
fun withParty(partyKeyStr: String, notFound: (String) -> Response = defaultNotFound, found: (Party) -> Response): Response {
|
||||
val party = try {
|
||||
val partyKey = CompositeKey.parseFromBase58(partyKeyStr)
|
||||
val partyKey = parsePublicKeyBase58(partyKeyStr)
|
||||
ErrorOr(rpc.partyFromKey(partyKey))
|
||||
} catch (e: IllegalArgumentException) {
|
||||
ErrorOr.of(Exception("Invalid base58 key passed for party key $e"))
|
||||
|
@ -5,13 +5,14 @@ package net.corda.core.utilities
|
||||
import net.corda.core.crypto.*
|
||||
import java.math.BigInteger
|
||||
import java.security.KeyPair
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
|
||||
// A dummy time at which we will be pretending test transactions are created.
|
||||
val TEST_TX_TIME: Instant get() = Instant.parse("2015-04-17T12:00:00.00Z")
|
||||
|
||||
val DUMMY_PUBKEY_1: CompositeKey get() = DummyPublicKey("x1").composite
|
||||
val DUMMY_PUBKEY_2: CompositeKey get() = DummyPublicKey("x2").composite
|
||||
val DUMMY_PUBKEY_1: PublicKey get() = DummyPublicKey("x1")
|
||||
val DUMMY_PUBKEY_2: PublicKey get() = DummyPublicKey("x2")
|
||||
|
||||
val DUMMY_KEY_1: KeyPair by lazy { generateKeyPair() }
|
||||
val DUMMY_KEY_2: KeyPair by lazy { generateKeyPair() }
|
||||
|
@ -4,10 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.signWithECDSA
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.flows.FlowException
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
@ -16,6 +13,7 @@ import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.UntrustworthyData
|
||||
import net.corda.core.utilities.unwrap
|
||||
import java.security.PublicKey
|
||||
|
||||
/**
|
||||
* Abstract flow to be used for replacing one state with another, for example when changing the notary of a state.
|
||||
@ -74,10 +72,10 @@ abstract class AbstractStateReplacementFlow {
|
||||
return finalTx.tx.outRef(0)
|
||||
}
|
||||
|
||||
abstract protected fun assembleTx(): Pair<SignedTransaction, Iterable<CompositeKey>>
|
||||
abstract protected fun assembleTx(): Pair<SignedTransaction, Iterable<PublicKey>>
|
||||
|
||||
@Suspendable
|
||||
private fun collectSignatures(participants: Iterable<CompositeKey>, stx: SignedTransaction): List<DigitalSignature.WithKey> {
|
||||
private fun collectSignatures(participants: Iterable<PublicKey>, stx: SignedTransaction): List<DigitalSignature.WithKey> {
|
||||
val parties = participants.map {
|
||||
val participantNode = serviceHub.networkMapCache.getNodeByLegalIdentityKey(it) ?:
|
||||
throw IllegalStateException("Participant $it to state $originalState not found on the network")
|
||||
|
@ -2,13 +2,13 @@ package net.corda.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.flows.AbstractStateReplacementFlow.Proposal
|
||||
import net.corda.flows.ContractUpgradeFlow.Acceptor
|
||||
import net.corda.flows.ContractUpgradeFlow.Instigator
|
||||
import java.security.PublicKey
|
||||
|
||||
/**
|
||||
* A flow to be used for upgrading state objects of an old contract to a new contract.
|
||||
@ -28,8 +28,8 @@ object ContractUpgradeFlow {
|
||||
@JvmStatic
|
||||
fun verify(input: ContractState, output: ContractState, commandData: Command) {
|
||||
val command = commandData.value as UpgradeCommand
|
||||
val participants: Set<CompositeKey> = input.participants.toSet()
|
||||
val keysThatSigned: Set<CompositeKey> = commandData.signers.toSet()
|
||||
val participants: Set<PublicKey> = input.participants.toSet()
|
||||
val keysThatSigned: Set<PublicKey> = commandData.signers.toSet()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val upgradedContract = command.upgradedContractClass.newInstance() as UpgradedContract<ContractState, *>
|
||||
requireThat {
|
||||
@ -54,7 +54,7 @@ object ContractUpgradeFlow {
|
||||
newContractClass: Class<out UpgradedContract<OldState, NewState>>
|
||||
) : AbstractStateReplacementFlow.Instigator<OldState, NewState, Class<out UpgradedContract<OldState, NewState>>>(originalState, newContractClass) {
|
||||
|
||||
override fun assembleTx(): Pair<SignedTransaction, Iterable<CompositeKey>> {
|
||||
override fun assembleTx(): Pair<SignedTransaction, Iterable<PublicKey>> {
|
||||
val stx = assembleBareTx(originalState, modification)
|
||||
.signWith(serviceHub.legalIdentityKey)
|
||||
.toSignedTransaction(false)
|
||||
|
@ -5,6 +5,7 @@ import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TransactionState
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.isFulfilledBy
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
|
@ -1,13 +1,13 @@
|
||||
package net.corda.flows
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.flows.NotaryChangeFlow.Acceptor
|
||||
import net.corda.flows.NotaryChangeFlow.Instigator
|
||||
import java.security.PublicKey
|
||||
|
||||
/**
|
||||
* A flow to be used for changing a state's Notary. This is required since all input states to a transaction
|
||||
@ -25,11 +25,11 @@ object NotaryChangeFlow : AbstractStateReplacementFlow() {
|
||||
newNotary: Party,
|
||||
progressTracker: ProgressTracker = tracker()) : AbstractStateReplacementFlow.Instigator<T, T, Party>(originalState, newNotary, progressTracker) {
|
||||
|
||||
override fun assembleTx(): Pair<SignedTransaction, Iterable<CompositeKey>> {
|
||||
override fun assembleTx(): Pair<SignedTransaction, Iterable<PublicKey>> {
|
||||
val state = originalState.state
|
||||
val tx = TransactionType.NotaryChange.Builder(originalState.state.notary)
|
||||
|
||||
val participants: Iterable<CompositeKey>
|
||||
val participants: Iterable<PublicKey>
|
||||
|
||||
if (state.encumbrance == null) {
|
||||
val modifiedState = TransactionState(state.data, modification)
|
||||
@ -54,14 +54,14 @@ object NotaryChangeFlow : AbstractStateReplacementFlow() {
|
||||
*
|
||||
* @return union of all added states' participants
|
||||
*/
|
||||
private fun resolveEncumbrances(tx: TransactionBuilder): Iterable<CompositeKey> {
|
||||
private fun resolveEncumbrances(tx: TransactionBuilder): Iterable<PublicKey> {
|
||||
val stateRef = originalState.ref
|
||||
val txId = stateRef.txhash
|
||||
val issuingTx = serviceHub.storageService.validatedTransactions.getTransaction(txId)
|
||||
?: throw StateReplacementException("Transaction $txId not found")
|
||||
val outputs = issuingTx.tx.outputs
|
||||
|
||||
val participants = mutableSetOf<CompositeKey>()
|
||||
val participants = mutableSetOf<PublicKey>()
|
||||
|
||||
var nextStateIndex = stateRef.index
|
||||
var newOutputPosition = tx.outputStates().size
|
||||
|
@ -18,6 +18,7 @@ import net.corda.core.utilities.UntrustworthyData
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.core.utilities.unwrap
|
||||
import java.security.KeyPair
|
||||
import java.security.PublicKey
|
||||
|
||||
/**
|
||||
* Classes for manipulating a two party deal or agreement.
|
||||
@ -43,7 +44,7 @@ object TwoPartyDealFlow {
|
||||
|
||||
// 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)
|
||||
data class Handshake<out T>(val payload: T, val publicKey: PublicKey)
|
||||
|
||||
@CordaSerializable
|
||||
class SignaturesFromPrimary(val sellerSig: DigitalSignature.WithKey, val notarySigs: List<DigitalSignature.WithKey>)
|
||||
@ -92,7 +93,7 @@ object TwoPartyDealFlow {
|
||||
progressTracker.currentStep = AWAITING_PROPOSAL
|
||||
|
||||
// Make the first message we'll send to kick off the flow.
|
||||
val hello = Handshake(payload, myKeyPair.public.composite)
|
||||
val hello = Handshake(payload, myKeyPair.public)
|
||||
val maybeSTX = sendAndReceive<SignedTransaction>(otherParty, hello)
|
||||
|
||||
return maybeSTX
|
||||
@ -106,7 +107,7 @@ object TwoPartyDealFlow {
|
||||
progressTracker.nextStep()
|
||||
|
||||
// Check that the tx proposed by the buyer is valid.
|
||||
val wtx: WireTransaction = stx.verifySignatures(myKeyPair.public.composite, notaryNode.notaryIdentity.owningKey)
|
||||
val wtx: WireTransaction = stx.verifySignatures(myKeyPair.public, notaryNode.notaryIdentity.owningKey)
|
||||
logger.trace { "Received partially signed transaction: ${stx.id}" }
|
||||
|
||||
checkDependencies(stx)
|
||||
@ -253,9 +254,9 @@ object TwoPartyDealFlow {
|
||||
return sendAndReceive<SignaturesFromPrimary>(otherParty, stx).unwrap { it }
|
||||
}
|
||||
|
||||
private fun signWithOurKeys(signingPubKeys: List<CompositeKey>, ptx: TransactionBuilder): SignedTransaction {
|
||||
private fun signWithOurKeys(signingPubKeys: List<PublicKey>, ptx: TransactionBuilder): SignedTransaction {
|
||||
// Now sign the transaction with whatever keys we need to move the cash.
|
||||
for (publicKey in signingPubKeys.keys) {
|
||||
for (publicKey in signingPubKeys.expandedCompositeKeys) {
|
||||
val privateKey = serviceHub.keyManagementService.toPrivate(publicKey)
|
||||
ptx.signWith(KeyPair(publicKey, privateKey))
|
||||
}
|
||||
@ -264,7 +265,7 @@ object TwoPartyDealFlow {
|
||||
}
|
||||
|
||||
@Suspendable protected abstract fun validateHandshake(handshake: Handshake<U>): Handshake<U>
|
||||
@Suspendable protected abstract fun assembleSharedTX(handshake: Handshake<U>): Pair<TransactionBuilder, List<CompositeKey>>
|
||||
@Suspendable protected abstract fun assembleSharedTX(handshake: Handshake<U>): Pair<TransactionBuilder, List<PublicKey>>
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
@ -297,7 +298,7 @@ object TwoPartyDealFlow {
|
||||
return handshake.copy(payload = autoOffer.copy(dealBeingOffered = deal))
|
||||
}
|
||||
|
||||
override fun assembleSharedTX(handshake: Handshake<AutoOffer>): Pair<TransactionBuilder, List<CompositeKey>> {
|
||||
override fun assembleSharedTX(handshake: Handshake<AutoOffer>): Pair<TransactionBuilder, List<PublicKey>> {
|
||||
val deal = handshake.payload.dealBeingOffered
|
||||
val ptx = deal.generateAgreement(handshake.payload.notary)
|
||||
|
||||
|
@ -1,12 +1,11 @@
|
||||
package net.corda.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.composite
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.utilities.unwrap
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.Certificate
|
||||
|
||||
object TxKeyFlowUtilities {
|
||||
@ -15,7 +14,7 @@ object TxKeyFlowUtilities {
|
||||
* process.
|
||||
*/
|
||||
@Suspendable
|
||||
fun receiveKey(flow: FlowLogic<*>, otherSide: Party): Pair<CompositeKey, Certificate?> {
|
||||
fun receiveKey(flow: FlowLogic<*>, otherSide: Party): Pair<PublicKey, Certificate?> {
|
||||
val untrustedKey = flow.receive<ProvidedTransactionKey>(otherSide)
|
||||
return untrustedKey.unwrap {
|
||||
// TODO: Verify the certificate connects the given key to the counterparty, once we have certificates
|
||||
@ -29,8 +28,8 @@ object TxKeyFlowUtilities {
|
||||
* a transaction with the counterparty, in order to avoid a DoS risk.
|
||||
*/
|
||||
@Suspendable
|
||||
fun provideKey(flow: FlowLogic<*>, otherSide: Party): CompositeKey {
|
||||
val key = flow.serviceHub.keyManagementService.freshKey().public.composite
|
||||
fun provideKey(flow: FlowLogic<*>, otherSide: Party): PublicKey {
|
||||
val key = flow.serviceHub.keyManagementService.freshKey().public
|
||||
// TODO: Generate and sign certificate for the key, once we have signing support for composite keys
|
||||
// (in this case the legal identity key)
|
||||
flow.send(otherSide, ProvidedTransactionKey(key, null))
|
||||
@ -38,5 +37,5 @@ object TxKeyFlowUtilities {
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
data class ProvidedTransactionKey(val key: CompositeKey, val certificate: Certificate?)
|
||||
}
|
||||
data class ProvidedTransactionKey(val key: PublicKey, val certificate: Certificate?)
|
||||
}
|
||||
|
Binary file not shown.
@ -1,7 +1,6 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.utilities.DUMMY_PUBKEY_1
|
||||
import net.corda.core.utilities.DUMMY_PUBKEY_2
|
||||
@ -9,6 +8,7 @@ import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.ledger
|
||||
import net.corda.testing.transaction
|
||||
import org.junit.Test
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
@ -40,7 +40,7 @@ class TransactionEncumbranceTests {
|
||||
data class State(
|
||||
val validFrom: Instant
|
||||
) : ContractState {
|
||||
override val participants: List<CompositeKey> = emptyList()
|
||||
override val participants: List<PublicKey> = emptyList()
|
||||
override val contract: Contract = TEST_TIMELOCK_ID
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.crypto.composite
|
||||
import net.corda.core.crypto.newSecureRandom
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
@ -32,7 +31,7 @@ class TransactionGraphSearchTests {
|
||||
fun buildTransactions(command: CommandData, signer: KeyPair): GraphTransactionStorage {
|
||||
val originTx = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||
addOutputState(DummyState(random31BitValue()))
|
||||
addCommand(command, signer.public.composite)
|
||||
addCommand(command, signer.public)
|
||||
signWith(signer)
|
||||
signWith(DUMMY_NOTARY_KEY)
|
||||
}.toSignedTransaction(false)
|
||||
|
@ -1,9 +1,10 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER_KEY
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.composite
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.crypto.signWithECDSA
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
@ -22,6 +23,48 @@ import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class TransactionTests {
|
||||
|
||||
private fun makeSigned(wtx: WireTransaction, vararg keys: KeyPair): SignedTransaction {
|
||||
val bytes: SerializedBytes<WireTransaction> = wtx.serialized
|
||||
return SignedTransaction(bytes, keys.map { it.signWithECDSA(wtx.id.bytes) })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `signed transaction missing signatures - CompositeKey`() {
|
||||
val ak = generateKeyPair()
|
||||
val bk = generateKeyPair()
|
||||
val ck = generateKeyPair()
|
||||
val apub = ak.public
|
||||
val bpub = bk.public
|
||||
val cpub = ck.public
|
||||
val c1 = CompositeKey.Builder().addKeys(apub, bpub).build(2)
|
||||
val compKey = CompositeKey.Builder().addKeys(c1, cpub).build(1)
|
||||
val wtx = WireTransaction(
|
||||
inputs = listOf(StateRef(SecureHash.randomSHA256(), 0)),
|
||||
attachments = emptyList(),
|
||||
outputs = emptyList(),
|
||||
commands = emptyList(),
|
||||
notary = DUMMY_NOTARY,
|
||||
signers = listOf(compKey, DUMMY_KEY_1.public, DUMMY_KEY_2.public),
|
||||
type = TransactionType.General,
|
||||
timestamp = null
|
||||
)
|
||||
assertEquals(
|
||||
setOf(compKey, DUMMY_KEY_2.public),
|
||||
assertFailsWith<SignedTransaction.SignaturesMissingException> { makeSigned(wtx, DUMMY_KEY_1).verifySignatures() }.missing
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
setOf(compKey, DUMMY_KEY_2.public),
|
||||
assertFailsWith<SignedTransaction.SignaturesMissingException> { makeSigned(wtx, DUMMY_KEY_1, ak).verifySignatures() }.missing
|
||||
)
|
||||
makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2, ak, bk).verifySignatures()
|
||||
makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2, ck).verifySignatures()
|
||||
makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2, ak, bk, ck).verifySignatures()
|
||||
makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2, ak).verifySignatures(compKey)
|
||||
makeSigned(wtx, DUMMY_KEY_1, ak).verifySignatures(compKey, DUMMY_KEY_2.public) // Mixed allowed to be missing.
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `signed transaction missing signatures`() {
|
||||
val wtx = WireTransaction(
|
||||
@ -30,31 +73,29 @@ class TransactionTests {
|
||||
outputs = emptyList(),
|
||||
commands = emptyList(),
|
||||
notary = DUMMY_NOTARY,
|
||||
signers = listOf(DUMMY_KEY_1.public.composite, DUMMY_KEY_2.public.composite),
|
||||
signers = listOf(DUMMY_KEY_1.public, DUMMY_KEY_2.public),
|
||||
type = TransactionType.General,
|
||||
timestamp = null
|
||||
)
|
||||
val bytes: SerializedBytes<WireTransaction> = wtx.serialized
|
||||
fun make(vararg keys: KeyPair) = SignedTransaction(bytes, keys.map { it.signWithECDSA(wtx.id.bytes) })
|
||||
assertFailsWith<IllegalArgumentException> { make().verifySignatures() }
|
||||
assertFailsWith<IllegalArgumentException> { makeSigned(wtx).verifySignatures() }
|
||||
|
||||
assertEquals(
|
||||
setOf(DUMMY_KEY_1.public.composite),
|
||||
assertFailsWith<SignedTransaction.SignaturesMissingException> { make(DUMMY_KEY_2).verifySignatures() }.missing
|
||||
setOf(DUMMY_KEY_1.public),
|
||||
assertFailsWith<SignedTransaction.SignaturesMissingException> { makeSigned(wtx, DUMMY_KEY_2).verifySignatures() }.missing
|
||||
)
|
||||
assertEquals(
|
||||
setOf(DUMMY_KEY_2.public.composite),
|
||||
assertFailsWith<SignedTransaction.SignaturesMissingException> { make(DUMMY_KEY_1).verifySignatures() }.missing
|
||||
setOf(DUMMY_KEY_2.public),
|
||||
assertFailsWith<SignedTransaction.SignaturesMissingException> { makeSigned(wtx, DUMMY_KEY_1).verifySignatures() }.missing
|
||||
)
|
||||
assertEquals(
|
||||
setOf(DUMMY_KEY_2.public.composite),
|
||||
assertFailsWith<SignedTransaction.SignaturesMissingException> { make(DUMMY_CASH_ISSUER_KEY).verifySignatures(DUMMY_KEY_1.public.composite) }.missing
|
||||
setOf(DUMMY_KEY_2.public),
|
||||
assertFailsWith<SignedTransaction.SignaturesMissingException> { makeSigned(wtx, DUMMY_CASH_ISSUER_KEY).verifySignatures(DUMMY_KEY_1.public) }.missing
|
||||
)
|
||||
|
||||
make(DUMMY_KEY_1).verifySignatures(DUMMY_KEY_2.public.composite)
|
||||
make(DUMMY_KEY_2).verifySignatures(DUMMY_KEY_1.public.composite)
|
||||
makeSigned(wtx, DUMMY_KEY_1).verifySignatures(DUMMY_KEY_2.public)
|
||||
makeSigned(wtx, DUMMY_KEY_2).verifySignatures(DUMMY_KEY_1.public)
|
||||
|
||||
make(DUMMY_KEY_1, DUMMY_KEY_2).verifySignatures()
|
||||
makeSigned(wtx, DUMMY_KEY_1, DUMMY_KEY_2).verifySignatures()
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -65,7 +106,7 @@ class TransactionTests {
|
||||
val commands = emptyList<AuthenticatedObject<CommandData>>()
|
||||
val attachments = emptyList<Attachment>()
|
||||
val id = SecureHash.randomSHA256()
|
||||
val signers = listOf(DUMMY_NOTARY_KEY.public.composite)
|
||||
val signers = listOf(DUMMY_NOTARY_KEY.public)
|
||||
val timestamp: Timestamp? = null
|
||||
val transaction: LedgerTransaction = LedgerTransaction(
|
||||
inputs,
|
||||
@ -92,7 +133,7 @@ class TransactionTests {
|
||||
val commands = emptyList<AuthenticatedObject<CommandData>>()
|
||||
val attachments = emptyList<Attachment>()
|
||||
val id = SecureHash.randomSHA256()
|
||||
val signers = listOf(DUMMY_NOTARY_KEY.public.composite)
|
||||
val signers = listOf(DUMMY_NOTARY_KEY.public)
|
||||
val timestamp: Timestamp? = null
|
||||
val transaction: LedgerTransaction = LedgerTransaction(
|
||||
inputs,
|
||||
@ -119,7 +160,7 @@ class TransactionTests {
|
||||
val commands = emptyList<AuthenticatedObject<CommandData>>()
|
||||
val attachments = emptyList<Attachment>()
|
||||
val id = SecureHash.randomSHA256()
|
||||
val signers = listOf(DUMMY_NOTARY_KEY.public.composite)
|
||||
val signers = listOf(DUMMY_NOTARY_KEY.public)
|
||||
val timestamp: Timestamp? = null
|
||||
val transaction: LedgerTransaction = LedgerTransaction(
|
||||
inputs,
|
||||
|
@ -3,6 +3,8 @@ package net.corda.core.crypto
|
||||
import net.corda.core.serialization.OpaqueBytes
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class CompositeKeyTests {
|
||||
@ -10,9 +12,9 @@ class CompositeKeyTests {
|
||||
val bobKey = generateKeyPair()
|
||||
val charlieKey = generateKeyPair()
|
||||
|
||||
val alicePublicKey = CompositeKey.Leaf(aliceKey.public)
|
||||
val bobPublicKey = CompositeKey.Leaf(bobKey.public)
|
||||
val charliePublicKey = CompositeKey.Leaf(charlieKey.public)
|
||||
val alicePublicKey = aliceKey.public
|
||||
val bobPublicKey = bobKey.public
|
||||
val charliePublicKey = charlieKey.public
|
||||
|
||||
val message = OpaqueBytes("Transaction".toByteArray())
|
||||
|
||||
@ -54,8 +56,35 @@ class CompositeKeyTests {
|
||||
val aliceAndBobOrCharlie = CompositeKey.Builder().addKeys(aliceAndBob, charliePublicKey).build(threshold = 1)
|
||||
|
||||
val encoded = aliceAndBobOrCharlie.toBase58String()
|
||||
val decoded = CompositeKey.parseFromBase58(encoded)
|
||||
val decoded = parsePublicKeyBase58(encoded)
|
||||
|
||||
assertEquals(decoded, aliceAndBobOrCharlie)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `tree canonical form`() {
|
||||
assertEquals(CompositeKey.Builder().addKeys(alicePublicKey).build(), alicePublicKey)
|
||||
val node1 = CompositeKey.Builder().addKeys(alicePublicKey, bobPublicKey).build(1) // threshold = 1
|
||||
val node2 = CompositeKey.Builder().addKeys(alicePublicKey, bobPublicKey).build(2) // threshold = 2
|
||||
assertFalse(node2.isFulfilledBy(alicePublicKey))
|
||||
// Ordering by weight.
|
||||
val tree1 = CompositeKey.Builder().addKey(node1, 13).addKey(node2, 27).build()
|
||||
val tree2 = CompositeKey.Builder().addKey(node2, 27).addKey(node1, 13).build()
|
||||
assertEquals(tree1, tree2)
|
||||
assertEquals(tree1.hashCode(), tree2.hashCode())
|
||||
|
||||
// Ordering by node, weights the same.
|
||||
val tree3 = CompositeKey.Builder().addKeys(node1, node2).build()
|
||||
val tree4 = CompositeKey.Builder().addKeys(node2, node1).build()
|
||||
assertEquals(tree3, tree4)
|
||||
assertEquals(tree3.hashCode(), tree4.hashCode())
|
||||
|
||||
// Duplicate node cases.
|
||||
val tree5 = CompositeKey.Builder().addKey(node1, 3).addKey(node1, 14).build()
|
||||
val tree6 = CompositeKey.Builder().addKey(node1, 14).addKey(node1, 3).build()
|
||||
assertEquals(tree5, tree6)
|
||||
|
||||
// Chain of single nodes should throw.
|
||||
assertEquals(CompositeKey.Builder().addKeys(tree1).build(), tree1)
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.MEGA_CORP_PUBKEY
|
||||
import net.corda.testing.ledger
|
||||
import org.junit.Test
|
||||
import java.security.PublicKey
|
||||
import kotlin.test.*
|
||||
|
||||
class PartialMerkleTreeTest {
|
||||
@ -99,7 +100,7 @@ class PartialMerkleTreeTest {
|
||||
is TransactionState<*> -> elem.data.participants[0].keys == DUMMY_PUBKEY_1.keys
|
||||
is Command -> MEGA_CORP_PUBKEY in elem.signers
|
||||
is Timestamp -> true
|
||||
is CompositeKey -> elem == MEGA_CORP_PUBKEY
|
||||
is PublicKey -> elem == MEGA_CORP_PUBKEY
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,8 @@ import kotlin.test.assertNotEquals
|
||||
class PartyTest {
|
||||
@Test
|
||||
fun `equality`() {
|
||||
val key = entropyToKeyPair(BigInteger.valueOf(20170207L)).public.composite
|
||||
val differentKey = entropyToKeyPair(BigInteger.valueOf(7201702L)).public.composite
|
||||
val key = entropyToKeyPair(BigInteger.valueOf(20170207L)).public
|
||||
val differentKey = entropyToKeyPair(BigInteger.valueOf(7201702L)).public
|
||||
val anonymousParty = AnonymousParty(key)
|
||||
val party = Party("test key", key)
|
||||
assertEquals<AbstractParty>(party, anonymousParty)
|
||||
|
@ -2,7 +2,6 @@ package net.corda.core.flows
|
||||
|
||||
import net.corda.contracts.asset.Cash
|
||||
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.getOrThrow
|
||||
@ -27,6 +26,7 @@ import java.util.concurrent.ExecutionException
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
import java.security.*
|
||||
|
||||
class ContractUpgradeFlowTest {
|
||||
lateinit var mockNet: MockNetwork
|
||||
@ -175,15 +175,15 @@ class ContractUpgradeFlowTest {
|
||||
class CashV2 : UpgradedContract<Cash.State, CashV2.State> {
|
||||
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()
|
||||
data class State(override val amount: Amount<Issued<Currency>>, val owners: List<PublicKey>) : FungibleAsset<Currency> {
|
||||
override val owner: PublicKey = owners.first()
|
||||
override val exitKeys = (owners + amount.token.issuer.party.owningKey).toSet()
|
||||
override val contract = CashV2()
|
||||
override val participants = owners
|
||||
|
||||
override fun move(newAmount: Amount<Issued<Currency>>, newOwner: CompositeKey) = copy(amount = amount.copy(newAmount.quantity), owners = listOf(newOwner))
|
||||
override fun move(newAmount: Amount<Issued<Currency>>, newOwner: PublicKey) = copy(amount = amount.copy(newAmount.quantity), owners = listOf(newOwner))
|
||||
override fun toString() = "${Emoji.bagOfCash}New Cash($amount at ${amount.token.issuer} owned by $owner)"
|
||||
override fun withNewOwner(newOwner: CompositeKey) = Pair(Cash.Commands.Move(), copy(owners = listOf(newOwner)))
|
||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Cash.Commands.Move(), copy(owners = listOf(newOwner)))
|
||||
}
|
||||
|
||||
override fun upgrade(state: Cash.State) = CashV2.State(state.amount.times(1000), listOf(state.owner))
|
||||
|
@ -1,11 +1,11 @@
|
||||
package net.corda.core.flows
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.flows.TxKeyFlowUtilities
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.Certificate
|
||||
|
||||
/**
|
||||
@ -19,7 +19,7 @@ object TxKeyFlow {
|
||||
}
|
||||
|
||||
class Requester(val otherSide: Party,
|
||||
override val progressTracker: ProgressTracker) : FlowLogic<Pair<CompositeKey, Certificate?>>() {
|
||||
override val progressTracker: ProgressTracker) : FlowLogic<Pair<PublicKey, Certificate?>>() {
|
||||
constructor(otherSide: Party) : this(otherSide, tracker())
|
||||
|
||||
companion object {
|
||||
@ -29,7 +29,7 @@ object TxKeyFlow {
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
override fun call(): Pair<CompositeKey, Certificate?> {
|
||||
override fun call(): Pair<PublicKey, Certificate?> {
|
||||
progressTracker.currentStep = AWAITING_KEY
|
||||
return TxKeyFlowUtilities.receiveKey(this, otherSide)
|
||||
}
|
||||
@ -40,7 +40,7 @@ object TxKeyFlow {
|
||||
* counterparty and as the result from the flow.
|
||||
*/
|
||||
class Provider(val otherSide: Party,
|
||||
override val progressTracker: ProgressTracker) : FlowLogic<CompositeKey>() {
|
||||
override val progressTracker: ProgressTracker) : FlowLogic<PublicKey>() {
|
||||
constructor(otherSide: Party) : this(otherSide, tracker())
|
||||
|
||||
companion object {
|
||||
@ -50,7 +50,7 @@ object TxKeyFlow {
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
override fun call(): CompositeKey {
|
||||
override fun call(): PublicKey {
|
||||
progressTracker.currentStep == SENDING_KEY
|
||||
return TxKeyFlowUtilities.provideKey(this, otherSide)
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.core.flows
|
||||
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.testing.ALICE
|
||||
@ -9,6 +8,7 @@ import net.corda.testing.MOCK_IDENTITY_SERVICE
|
||||
import net.corda.testing.node.MockNetwork
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.security.PublicKey
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
class TxKeyFlowUtilitiesTests {
|
||||
@ -36,7 +36,7 @@ class TxKeyFlowUtilitiesTests {
|
||||
val requesterFlow = aliceNode.services.startFlow(TxKeyFlow.Requester(bobKey))
|
||||
|
||||
// Get the results
|
||||
val actual: CompositeKey = requesterFlow.resultFuture.get().first
|
||||
val actual: PublicKey = requesterFlow.resultFuture.get().first
|
||||
assertNotNull(actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package net.corda.core.node
|
||||
|
||||
import com.esotericsoftware.kryo.Kryo
|
||||
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.node.services.AttachmentStorage
|
||||
@ -18,6 +17,7 @@ import org.junit.Test
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.net.URLClassLoader
|
||||
import java.security.PublicKey
|
||||
import java.util.jar.JarOutputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import kotlin.test.assertEquals
|
||||
@ -39,7 +39,7 @@ class AttachmentClassLoaderTests {
|
||||
class AttachmentDummyContract : Contract {
|
||||
data class State(val magicNumber: Int = 0) : ContractState {
|
||||
override val contract = ATTACHMENT_TEST_PROGRAM_ID
|
||||
override val participants: List<CompositeKey>
|
||||
override val participants: List<PublicKey>
|
||||
get() = listOf()
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
package net.corda.core.node
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import org.junit.Test
|
||||
import java.security.PublicKey
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ class VaultUpdateTests {
|
||||
}
|
||||
|
||||
private class DummyState : ContractState {
|
||||
override val participants: List<CompositeKey>
|
||||
override val participants: List<PublicKey>
|
||||
get() = emptyList()
|
||||
override val contract = VaultUpdateTests.DummyContract
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
package net.corda.core.serialization
|
||||
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.composite
|
||||
import net.corda.core.seconds
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.*
|
||||
@ -11,6 +9,7 @@ import net.corda.testing.MINI_CORP
|
||||
import net.corda.testing.generateStateRef
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.security.PublicKey
|
||||
import java.security.SignatureException
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
@ -28,12 +27,12 @@ class TransactionSerializationTests {
|
||||
data class State(
|
||||
val deposit: PartyAndReference,
|
||||
val amount: Amount<Currency>,
|
||||
override val owner: CompositeKey) : OwnableState {
|
||||
override val owner: PublicKey) : OwnableState {
|
||||
override val contract: Contract = TEST_PROGRAM_ID
|
||||
override val participants: List<CompositeKey>
|
||||
override val participants: List<PublicKey>
|
||||
get() = listOf(owner)
|
||||
|
||||
override fun withNewOwner(newOwner: CompositeKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
override fun withNewOwner(newOwner: PublicKey) = Pair(Commands.Move(), copy(owner = newOwner))
|
||||
}
|
||||
|
||||
interface Commands : CommandData {
|
||||
@ -47,7 +46,7 @@ class TransactionSerializationTests {
|
||||
val fakeStateRef = generateStateRef()
|
||||
val inputState = StateAndRef(TransactionState(TestCash.State(depositRef, 100.POUNDS, DUMMY_PUBKEY_1), DUMMY_NOTARY), fakeStateRef)
|
||||
val outputState = TransactionState(TestCash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1), DUMMY_NOTARY)
|
||||
val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, DUMMY_KEY_1.public.composite), DUMMY_NOTARY)
|
||||
val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, DUMMY_KEY_1.public), DUMMY_NOTARY)
|
||||
|
||||
|
||||
lateinit var tx: TransactionBuilder
|
||||
@ -55,7 +54,7 @@ class TransactionSerializationTests {
|
||||
@Before
|
||||
fun setup() {
|
||||
tx = TransactionType.General.Builder(DUMMY_NOTARY).withItems(
|
||||
inputState, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(DUMMY_KEY_1.public.composite))
|
||||
inputState, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(DUMMY_KEY_1.public))
|
||||
)
|
||||
}
|
||||
|
||||
@ -94,7 +93,7 @@ class TransactionSerializationTests {
|
||||
// If the signature was replaced in transit, we don't like it.
|
||||
assertFailsWith(SignatureException::class) {
|
||||
val tx2 = TransactionType.General.Builder(DUMMY_NOTARY).withItems(inputState, outputState, changeState,
|
||||
Command(TestCash.Commands.Move(), DUMMY_KEY_2.public.composite))
|
||||
Command(TestCash.Commands.Move(), DUMMY_KEY_2.public))
|
||||
tx2.signWith(DUMMY_NOTARY_KEY)
|
||||
tx2.signWith(DUMMY_KEY_2)
|
||||
|
||||
|
@ -33,27 +33,22 @@ class PrivateKeyGenerator : Generator<PrivateKey>(PrivateKey::class.java) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO add CompositeKeyGenerator that actually does something useful.
|
||||
class PublicKeyGenerator : Generator<PublicKey>(PublicKey::class.java) {
|
||||
override fun generate(random: SourceOfRandomness, status: GenerationStatus): PublicKey {
|
||||
return entropyToKeyPair(random.nextBigInteger(32)).public
|
||||
}
|
||||
}
|
||||
|
||||
class CompositeKeyGenerator : Generator<CompositeKey>(CompositeKey::class.java) {
|
||||
override fun generate(random: SourceOfRandomness, status: GenerationStatus): CompositeKey {
|
||||
return entropyToKeyPair(random.nextBigInteger(32)).public.composite
|
||||
}
|
||||
}
|
||||
|
||||
class AnonymousPartyGenerator : Generator<AnonymousParty>(AnonymousParty::class.java) {
|
||||
override fun generate(random: SourceOfRandomness, status: GenerationStatus): AnonymousParty {
|
||||
return AnonymousParty(CompositeKeyGenerator().generate(random, status))
|
||||
return AnonymousParty(PublicKeyGenerator().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))
|
||||
return Party(StringGenerator().generate(random, status), PublicKeyGenerator().generate(random, status))
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user