TransactionSignature MetaData support (#1040)

Support signature metadata
This commit is contained in:
Konstantinos Chalkias 2017-08-07 16:21:52 +01:00 committed by GitHub
parent 3a3ead2dfe
commit bd0944e799
55 changed files with 394 additions and 429 deletions

View File

@ -438,7 +438,7 @@ fun JarInputStream.extractFile(path: String, outputTo: OutputStream) {
/**
* A privacy salt is required to compute nonces per transaction component in order to ensure that an adversary cannot
* use brute force techniques and reveal the content of a merkle-leaf hashed value.
* use brute force techniques and reveal the content of a Merkle-leaf hashed value.
* Because this salt serves the role of the seed to compute nonces, its size and entropy should be equal to the
* underlying hash function used for Merkle tree generation, currently [SHA256], which has an output of 32 bytes.
* There are two constructors, one that generates a new 32-bytes random salt, and another that takes a [ByteArray] input.

View File

@ -4,6 +4,7 @@ import net.corda.core.crypto.composite.CompositeKey
import net.corda.core.crypto.composite.CompositeSignature
import net.corda.core.crypto.provider.CordaObjectIdentifier
import net.corda.core.crypto.provider.CordaSecurityProvider
import net.corda.core.serialization.serialize
import net.i2p.crypto.eddsa.EdDSAEngine
import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey
@ -401,23 +402,23 @@ object Crypto {
}
/**
* Generic way to sign [MetaData] objects with a [PrivateKey].
* [MetaData] is a wrapper over the transaction's Merkle root in order to attach extra information, such as a timestamp or partial and blind signature indicators.
* Generic way to sign [SignableData] objects with a [PrivateKey].
* [SignableData] is a wrapper over the transaction's id (Merkle root) in order to attach extra information, such as a timestamp or partial and blind signature indicators.
* @param privateKey the signer's [PrivateKey].
* @param metaData a [MetaData] object that adds extra information to a transaction.
* @return a [TransactionSignature] object than contains the output of a successful signing and the metaData.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key or
* if metaData.schemeCodeName is not aligned with key type.
* @param signableData a [SignableData] object that adds extra information to a transaction.
* @return a [TransactionSignature] object than contains the output of a successful signing, signer's public key and the signature metadata.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key.
* @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
fun doSign(privateKey: PrivateKey, metaData: MetaData): TransactionSignature {
val sigKey: SignatureScheme = findSignatureScheme(privateKey)
val sigMetaData: SignatureScheme = findSignatureScheme(metaData.schemeCodeName)
if (sigKey != sigMetaData) throw IllegalArgumentException("Metadata schemeCodeName: ${metaData.schemeCodeName} is not aligned with the key type.")
val signatureData = doSign(sigKey.schemeCodeName, privateKey, metaData.bytes())
return TransactionSignature(signatureData, metaData)
fun doSign(keyPair: KeyPair, signableData: SignableData): TransactionSignature {
val sigKey: SignatureScheme = findSignatureScheme(keyPair.private)
val sigMetaData: SignatureScheme = findSignatureScheme(keyPair.public)
if (sigKey != sigMetaData) throw IllegalArgumentException("Metadata schemeCodeName: ${sigMetaData.schemeCodeName}" +
" is not aligned with the key type: ${sigKey.schemeCodeName}.")
val signatureBytes = doSign(sigKey.schemeCodeName, keyPair.private, signableData.serialize().bytes)
return TransactionSignature(signatureBytes, keyPair.public, signableData.signatureMetadata)
}
/**
@ -434,7 +435,7 @@ object Crypto {
* if this signatureData scheme is unable to process the input data provided, if the verification is not possible.
* @throws IllegalArgumentException if the signature scheme is not supported or if any of the clear or signature data is empty.
*/
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
@Throws(InvalidKeyException::class, SignatureException::class)
fun doVerify(schemeCodeName: String, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray) = doVerify(findSignatureScheme(schemeCodeName), publicKey, signatureData, clearData)
/**
@ -485,9 +486,9 @@ object Crypto {
/**
* Utility to simplify the act of verifying a [TransactionSignature].
* It returns true if it succeeds, but it always throws an exception if verification fails.
* @param publicKey the signer's [PublicKey].
* @param transactionSignature the signatureData on a message.
* @return true if verification passes or throws an exception if verification fails.
* @param txId transaction's id (Merkle root).
* @param transactionSignature the signature on the transaction.
* @return true if verification passes or throw exception if verification fails.
* @throws InvalidKeyException if the key is invalid.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
@ -495,9 +496,26 @@ object Crypto {
* @throws IllegalArgumentException if the signature scheme is not supported or if any of the clear or signature data is empty.
*/
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
fun doVerify(publicKey: PublicKey, transactionSignature: TransactionSignature): Boolean {
if (publicKey != transactionSignature.metaData.publicKey) IllegalArgumentException("MetaData's publicKey: ${transactionSignature.metaData.publicKey.toStringShort()} does not match")
return Crypto.doVerify(publicKey, transactionSignature.signatureData, transactionSignature.metaData.bytes())
fun doVerify(txId: SecureHash, transactionSignature: TransactionSignature): Boolean {
val signableData = SignableData(txId, transactionSignature.signatureMetadata)
return Crypto.doVerify(transactionSignature.by, transactionSignature.bytes, signableData.serialize().bytes)
}
/**
* Utility to simplify the act of verifying a digital signature by identifying the signature scheme used from the input public key's type.
* It returns true if it succeeds and false if not. In comparison to [doVerify] if the key and signature
* do not match it returns false rather than throwing an exception. Normally you should use the function which throws,
* as it avoids the risk of failing to test the result.
* @param txId transaction's id (Merkle root).
* @param transactionSignature the signature on the transaction.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
* if this signatureData scheme is unable to process the input data provided, if the verification is not possible.
*/
@Throws(SignatureException::class)
fun isValid(txId: SecureHash, transactionSignature: TransactionSignature): Boolean {
val signableData = SignableData(txId, transactionSignature.signatureMetadata)
return isValid(findSignatureScheme(transactionSignature.by), transactionSignature.by, transactionSignature.bytes, signableData.serialize().bytes)
}
/**

View File

@ -34,7 +34,17 @@ fun PrivateKey.sign(bytesToSign: ByteArray, publicKey: PublicKey): DigitalSignat
*/
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
fun KeyPair.sign(bytesToSign: ByteArray) = private.sign(bytesToSign, public)
fun KeyPair.sign(bytesToSign: OpaqueBytes) = private.sign(bytesToSign.bytes, public)
fun KeyPair.sign(bytesToSign: OpaqueBytes) = sign(bytesToSign.bytes)
/**
* Helper function for signing a [SignableData] object.
* @param signableData the object to be signed.
* @return a [TransactionSignature] object.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key.
* @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(InvalidKeyException::class, SignatureException::class)
fun KeyPair.sign(signableData: SignableData): TransactionSignature = Crypto.doSign(this, signableData)
/**
* Utility to simplify the act of verifying a signature.
@ -88,7 +98,7 @@ fun PublicKey.containsAny(otherKeys: Iterable<PublicKey>): Boolean {
}
/** Returns the set of all [PublicKey]s of the signatures */
fun Iterable<DigitalSignature.WithKey>.byKeys() = map { it.by }.toSet()
fun Iterable<TransactionSignature>.byKeys() = map { it.by }.toSet()
// Allow Kotlin destructuring: val (private, public) = keyPair
operator fun KeyPair.component1(): PrivateKey = this.private
@ -105,17 +115,6 @@ fun generateKeyPair(): KeyPair = Crypto.generateKeyPair()
*/
fun entropyToKeyPair(entropy: BigInteger): KeyPair = Crypto.deriveKeyPairFromEntropy(entropy)
/**
* Helper function for signing.
* @param metaData tha attached MetaData object.
* @return a [TransactionSignature ] object.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key.
* @throws InvalidKeyException if the private key is invalid.
* @throws SignatureException if signing is not possible due to malformed data or private key.
*/
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
fun PrivateKey.sign(metaData: MetaData): TransactionSignature = Crypto.doSign(this, metaData)
/**
* Helper function to verify a signature.
* @param signatureData the signature on a message.
@ -129,21 +128,6 @@ fun PrivateKey.sign(metaData: MetaData): TransactionSignature = Crypto.doSign(th
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
fun PublicKey.verify(signatureData: ByteArray, clearData: ByteArray): Boolean = Crypto.doVerify(this, signatureData, clearData)
/**
* Helper function to verify a metadata attached signature. It is noted that the transactionSignature contains
* signatureData and a [MetaData] object that contains the signer's public key and the transaction's Merkle root.
* @param transactionSignature a [TransactionSignature] object that .
* @throws InvalidKeyException if the key is invalid.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
* if this signatureData algorithm is unable to process the input data provided, etc.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key or if any of the clear or signature data is empty.
*/
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
fun PublicKey.verify(transactionSignature: TransactionSignature): Boolean {
return Crypto.doVerify(this, transactionSignature)
}
/**
* Helper function for the signers to verify their own signature.
* @param signatureData the signature on a message.

View File

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

View File

@ -0,0 +1,14 @@
package net.corda.core.crypto
import net.corda.core.serialization.CordaSerializable
/**
* A [SignableData] object is the packet actually signed.
* It works as a wrapper over transaction id and signature metadata.
*
* @param txId transaction's id.
* @param signatureMetadata meta data required.
*/
@CordaSerializable
data class SignableData(val txId: SecureHash, val signatureMetadata: SignatureMetadata)

View File

@ -0,0 +1,15 @@
package net.corda.core.crypto
import net.corda.core.serialization.CordaSerializable
/**
* SignatureMeta is required to add extra meta-data to a transaction's signature.
* It currently supports platformVersion only, but it can be extended to support a universal digital
* signature model enabling partial signatures and attaching extra information, such as a user's timestamp or other
* application-specific fields.
*
* @param platformVersion current DLT version.
* @param schemeNumberID number id of the signature scheme used based on signer's key-pair, see [SignatureScheme.schemeNumberID].
*/
@CordaSerializable
data class SignatureMetadata(val platformVersion: Int, val schemeNumberID: Int)

View File

@ -6,7 +6,7 @@ import java.security.spec.AlgorithmParameterSpec
/**
* This class is used to define a digital signature scheme.
* @param schemeNumberID we assign a number ID for more efficient on-wire serialisation. Please ensure uniqueness between schemes.
* @param schemeNumberID we assign a number ID for better efficiency on-wire serialisation. Please ensure uniqueness between schemes.
* @param schemeCodeName code name for this signature scheme (e.g. RSA_SHA256, ECDSA_SECP256K1_SHA256, ECDSA_SECP256R1_SHA256, EDDSA_ED25519_SHA512, SPHINCS-256_SHA512).
* @param signatureOID ASN.1 algorithm identifier of the signature algorithm (e.g 1.3.101.112 for EdDSA)
* @param alternativeOIDs ASN.1 algorithm identifiers for keys of the signature, where we want to map multiple keys to

View File

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

View File

@ -1,22 +1,57 @@
package net.corda.core.crypto
import net.corda.core.serialization.CordaSerializable
import java.security.InvalidKeyException
import java.security.PublicKey
import java.security.SignatureException
import java.util.*
/**
* A wrapper around a digital signature accompanied with metadata, see [MetaData.Full] and [DigitalSignature].
* The signature protocol works as follows: s = sign(MetaData.hashBytes).
* A wrapper over the signature output accompanied by signer's public key and signature metadata.
* This is similar to [DigitalSignature.WithKey], but targeted to DLT transaction signatures.
*/
open class TransactionSignature(val signatureData: ByteArray, val metaData: MetaData) : DigitalSignature(signatureData) {
@CordaSerializable
class TransactionSignature(bytes: ByteArray, val by: PublicKey, val signatureMetadata: SignatureMetadata): DigitalSignature(bytes) {
/**
* Function to auto-verify a [MetaData] object's signature.
* Note that [MetaData] contains both public key and merkle root of the transaction.
* Function to verify a [SignableData] object's signature.
* Note that [SignableData] contains the id of the transaction and extra metadata, such as DLT's platform version.
*
* @param txId transaction's id (Merkle root), which along with [signatureMetadata] will be used to construct the [SignableData] object to be signed.
* @throws InvalidKeyException if the key is invalid.
* @throws SignatureException if this signatureData object is not initialized properly,
* the passed-in signatureData is improperly encoded or of the wrong type,
* if this signatureData algorithm is unable to process the input data provided, etc.
* @throws IllegalArgumentException if the signature scheme is not supported for this private key or if any of the clear or signature data is empty.
*/
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
fun verify(): Boolean = Crypto.doVerify(metaData.publicKey, signatureData, metaData.bytes())
@Throws(InvalidKeyException::class, SignatureException::class)
fun verify(txId: SecureHash) = Crypto.doVerify(txId, this)
/**
* Utility to simplify the act of verifying a signature. In comparison to [verify] doesn't throw an
* exception, making it more suitable where a boolean is required, but normally you should use the function
* which throws, as it avoids the risk of failing to test the result.
*
* @throws InvalidKeyException if the key to verify the signature with is not valid (i.e. wrong key type for the
* signature).
* @throws SignatureException if the signature is invalid (i.e. damaged).
* @return whether the signature is correct for this key.
*/
@Throws(InvalidKeyException::class, SignatureException::class)
fun isValid(txId: SecureHash) = Crypto.isValid(txId, this)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is TransactionSignature) return false
return (Arrays.equals(bytes, other.bytes)
&& by == other.by
&& signatureMetadata == other.signatureMetadata)
}
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + by.hashCode()
result = 31 * result + signatureMetadata.hashCode()
return result
}
}

View File

@ -1,8 +1,7 @@
package net.corda.core.crypto.composite
import net.corda.core.crypto.SecureHash
import net.corda.core.serialization.deserialize
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
import java.io.ByteArrayOutputStream
import java.security.*
import java.security.spec.AlgorithmParameterSpec
@ -77,7 +76,7 @@ class CompositeSignature : Signature(SIGNATURE_ALGORITHM) {
fun engineVerify(sigBytes: ByteArray): Boolean {
val sig = sigBytes.deserialize<CompositeSignaturesWithKeys>()
return if (verifyKey.isFulfilledBy(sig.sigs.map { it.by })) {
val clearData = buffer.toByteArray()
val clearData = SecureHash.SHA256(buffer.toByteArray())
sig.sigs.all { it.isValid(clearData) }
} else {
false

View File

@ -1,6 +1,6 @@
package net.corda.core.crypto.composite
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.TransactionSignature
import net.corda.core.serialization.CordaSerializable
/**
@ -8,7 +8,7 @@ import net.corda.core.serialization.CordaSerializable
* serialization format (i.e. not Kryo).
*/
@CordaSerializable
data class CompositeSignaturesWithKeys(val sigs: List<DigitalSignature.WithKey>) {
data class CompositeSignaturesWithKeys(val sigs: List<TransactionSignature>) {
companion object {
val EMPTY = CompositeSignaturesWithKeys(emptyList())
}

View File

@ -1,6 +1,7 @@
package net.corda.core.crypto.testing
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.AnonymousParty
import net.corda.core.serialization.CordaSerializable
import java.math.BigInteger
@ -31,5 +32,4 @@ class DummyPublicKey(val s: String) : PublicKey, Comparable<PublicKey> {
}
/** A signature with a key and value of zero. Useful when you want a signature object that you know won't ever be used. */
@CordaSerializable
object NullSignature : DigitalSignature.WithKey(NullPublicKey, ByteArray(32))
val NULL_SIGNATURE = TransactionSignature(ByteArray(32), NullPublicKey, SignatureMetadata(1, -1))

View File

@ -4,7 +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.DigitalSignature
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
@ -95,7 +95,7 @@ abstract class AbstractStateReplacementFlow {
abstract protected fun assembleTx(): UpgradeTx
@Suspendable
private fun collectSignatures(participants: Iterable<PublicKey>, stx: SignedTransaction): List<DigitalSignature.WithKey> {
private fun collectSignatures(participants: Iterable<PublicKey>, stx: SignedTransaction): List<TransactionSignature> {
val parties = participants.map {
val participantNode = serviceHub.networkMapCache.getNodeByLegalIdentityKey(it) ?:
throw IllegalStateException("Participant $it to state $originalState not found on the network")
@ -113,10 +113,10 @@ abstract class AbstractStateReplacementFlow {
}
@Suspendable
private fun getParticipantSignature(party: Party, stx: SignedTransaction): DigitalSignature.WithKey {
private fun getParticipantSignature(party: Party, stx: SignedTransaction): TransactionSignature {
val proposal = Proposal(originalState.ref, modification)
subFlow(SendTransactionFlow(party, stx))
return sendAndReceive<DigitalSignature.WithKey>(party, proposal).unwrap {
return sendAndReceive<TransactionSignature>(party, proposal).unwrap {
check(party.owningKey.isFulfilledBy(it.by)) { "Not signed by the required participant" }
it.verify(stx.id)
it
@ -124,7 +124,7 @@ abstract class AbstractStateReplacementFlow {
}
@Suspendable
private fun getNotarySignatures(stx: SignedTransaction): List<DigitalSignature.WithKey> {
private fun getNotarySignatures(stx: SignedTransaction): List<TransactionSignature> {
progressTracker.currentStep = NOTARY
try {
return subFlow(NotaryFlow.Client(stx))
@ -165,7 +165,7 @@ abstract class AbstractStateReplacementFlow {
progressTracker.currentStep = APPROVING
val mySignature = sign(stx)
val swapSignatures = sendAndReceive<List<DigitalSignature.WithKey>>(otherSide, mySignature)
val swapSignatures = sendAndReceive<List<TransactionSignature>>(otherSide, mySignature)
// TODO: This step should not be necessary, as signatures are re-checked in verifyRequiredSignatures.
val allSignatures = swapSignatures.unwrap { signatures ->
@ -203,7 +203,7 @@ abstract class AbstractStateReplacementFlow {
require(myKey in requiredKeys) { "Party is not a participant for any of the input states of transaction ${stx.id}" }
}
private fun sign(stx: SignedTransaction): DigitalSignature.WithKey {
private fun sign(stx: SignedTransaction): TransactionSignature {
return serviceHub.createSignature(stx)
}
}

View File

@ -1,7 +1,7 @@
package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.crypto.toBase58String
import net.corda.core.identity.Party
@ -124,10 +124,10 @@ class CollectSignaturesFlow(val partiallySignedTx: SignedTransaction,
/**
* Get and check the required signature.
*/
@Suspendable private fun collectSignature(counterparty: Party): DigitalSignature.WithKey {
@Suspendable private fun collectSignature(counterparty: Party): TransactionSignature {
// SendTransactionFlow allows otherParty to access our data to resolve the transaction.
subFlow(SendTransactionFlow(counterparty, partiallySignedTx))
return receive<DigitalSignature.WithKey>(counterparty).unwrap {
return receive<TransactionSignature>(counterparty).unwrap {
require(counterparty.owningKey.isFulfilledBy(it.by)) { "Not signed by the required Party." }
it
}

View File

@ -3,6 +3,9 @@ package net.corda.core.flows
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.identity.Party
import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.SignedTransaction
@ -36,7 +39,8 @@ class NotaryChangeFlow<out T : ContractState>(
val participantKeys = inputs.flatMap { it.state.data.participants }.map { it.owningKey }.toSet()
// TODO: We need a much faster way of finding our key in the transaction
val myKey = serviceHub.keyManagementService.filterMyKeys(participantKeys).single()
val mySignature = serviceHub.keyManagementService.sign(tx.id.bytes, myKey)
val signableData = SignableData(tx.id, SignatureMetadata(serviceHub.myInfo.platformVersion, Crypto.findSignatureScheme(myKey).schemeNumberID))
val mySignature = serviceHub.keyManagementService.sign(signableData, myKey)
val stx = SignedTransaction(tx, listOf(mySignature))
return AbstractStateReplacementFlow.UpgradeTx(stx, participantKeys, myKey)

View File

@ -3,9 +3,9 @@ package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.keys
import net.corda.core.identity.Party
import net.corda.core.internal.FetchDataFlow
@ -32,7 +32,7 @@ object NotaryFlow {
*/
@InitiatingFlow
open class Client(private val stx: SignedTransaction,
override val progressTracker: ProgressTracker) : FlowLogic<List<DigitalSignature.WithKey>>() {
override val progressTracker: ProgressTracker) : FlowLogic<List<TransactionSignature>>() {
constructor(stx: SignedTransaction) : this(stx, tracker())
companion object {
@ -46,7 +46,7 @@ object NotaryFlow {
@Suspendable
@Throws(NotaryException::class)
override fun call(): List<DigitalSignature.WithKey> {
override fun call(): List<TransactionSignature> {
progressTracker.currentStep = REQUESTING
notaryParty = stx.notary ?: throw IllegalStateException("Transaction does not specify a Notary")
@ -67,7 +67,7 @@ object NotaryFlow {
val response = try {
if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) {
subFlow(SendTransactionWithRetry(notaryParty, stx))
receive<List<DigitalSignature.WithKey>>(notaryParty)
receive<List<TransactionSignature>>(notaryParty)
} else {
val tx: Any = if (stx.isNotaryChangeTransaction()) {
stx.notaryChangeTx
@ -84,14 +84,14 @@ object NotaryFlow {
}
return response.unwrap { signatures ->
signatures.forEach { validateSignature(it, stx.id.bytes) }
signatures.forEach { validateSignature(it, stx.id) }
signatures
}
}
private fun validateSignature(sig: DigitalSignature.WithKey, data: ByteArray) {
private fun validateSignature(sig: TransactionSignature, txId: SecureHash) {
check(sig.by in notaryParty.owningKey.keys) { "Invalid signer for the notary result" }
sig.verify(data)
sig.verify(txId)
}
}
@ -124,7 +124,7 @@ object NotaryFlow {
@Suspendable
private fun signAndSendResponse(txId: SecureHash) {
val signature = service.sign(txId.bytes)
val signature = service.sign(txId)
send(otherSide, listOf(signature))
}
}

View File

@ -1,7 +1,10 @@
package net.corda.core.node
import net.corda.core.contracts.*
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.TransactionSignature
import net.corda.core.node.services.*
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.transactions.SignedTransaction
@ -123,7 +126,7 @@ interface ServiceHub : ServicesForResolution {
/**
* Helper property to shorten code for fetching the the [PublicKey] portion of the
* Node's Notary signing identity. It is required that the Node hosts a notary service,
* otherwise an IllegalArgumentException will be thrown.
* otherwise an [IllegalArgumentException] will be thrown.
* Typical use is during signing in flows and for unit test signing.
* When this [PublicKey] is passed into the signing methods below, or on the KeyManagementService
* the matching [java.security.PrivateKey] will be looked up internally and used to sign.
@ -132,9 +135,17 @@ interface ServiceHub : ServicesForResolution {
*/
val notaryIdentityKey: PublicKey get() = this.myInfo.notaryIdentity.owningKey
// Helper method to construct an initial partially signed transaction from a [TransactionBuilder].
private fun signInitialTransaction(builder: TransactionBuilder, publicKey: PublicKey, signatureMetadata: SignatureMetadata): SignedTransaction {
val signableData = SignableData(builder.toWireTransaction().id, signatureMetadata)
val sig = keyManagementService.sign(signableData, publicKey)
builder.addSignatureUnchecked(sig)
return builder.toSignedTransaction(false)
}
/**
* Helper method to construct an initial partially signed transaction from a [TransactionBuilder]
* using keys stored inside the node.
* using keys stored inside the node. Signature metadata is added automatically.
* @param builder The [TransactionBuilder] to seal with the node's signature.
* Any existing signatures on the builder will be preserved.
* @param publicKey The [PublicKey] matched to the internal [java.security.PrivateKey] to use in signing this transaction.
@ -142,15 +153,12 @@ interface ServiceHub : ServicesForResolution {
* to sign with.
* @return Returns a SignedTransaction with the new node signature attached.
*/
fun signInitialTransaction(builder: TransactionBuilder, publicKey: PublicKey): SignedTransaction {
val sig = keyManagementService.sign(builder.toWireTransaction().id.bytes, publicKey)
builder.addSignatureUnchecked(sig)
return builder.toSignedTransaction(false)
}
fun signInitialTransaction(builder: TransactionBuilder, publicKey: PublicKey) =
signInitialTransaction(builder, publicKey, SignatureMetadata(myInfo.platformVersion, Crypto.findSignatureScheme(publicKey).schemeNumberID))
/**
* Helper method to construct an initial partially signed transaction from a TransactionBuilder
* using the default identity key contained in the node.
* using the default identity key contained in the node. The legal Indentity key is used to sign.
* @param builder The TransactionBuilder to seal with the node's signature.
* Any existing signatures on the builder will be preserved.
* @return Returns a SignedTransaction with the new node signature attached.
@ -175,25 +183,30 @@ interface ServiceHub : ServicesForResolution {
return stx
}
// Helper method to create an additional signature for an existing (partially) [SignedTransaction].
private fun createSignature(signedTransaction: SignedTransaction, publicKey: PublicKey, signatureMetadata: SignatureMetadata): TransactionSignature {
val signableData = SignableData(signedTransaction.id, signatureMetadata)
return keyManagementService.sign(signableData, publicKey)
}
/**
* Helper method to create an additional signature for an existing (partially) [SignedTransaction].
* @param signedTransaction The [SignedTransaction] to which the signature will apply.
* @param publicKey The [PublicKey] matching to a signing [java.security.PrivateKey] hosted in the node.
* If the [PublicKey] is actually a [net.corda.core.crypto.CompositeKey] the first leaf key found locally will be used
* for signing.
* @return The [DigitalSignature.WithKey] generated by signing with the internally held [java.security.PrivateKey].
* @return The [TransactionSignature] generated by signing with the internally held [java.security.PrivateKey].
*/
fun createSignature(signedTransaction: SignedTransaction, publicKey: PublicKey): DigitalSignature.WithKey {
return keyManagementService.sign(signedTransaction.id.bytes, publicKey)
}
fun createSignature(signedTransaction: SignedTransaction, publicKey: PublicKey) =
createSignature(signedTransaction, publicKey, SignatureMetadata(myInfo.platformVersion, Crypto.findSignatureScheme(publicKey).schemeNumberID))
/**
* Helper method to create an additional signature for an existing (partially) SignedTransaction
* using the default identity signing key of the node.
* Helper method to create an additional signature for an existing (partially) [SignedTransaction]
* using the default identity signing key of the node. The legal identity key is used to sign.
* @param signedTransaction The SignedTransaction to which the signature will apply.
* @return The DigitalSignature.WithKey generated by signing with the internally held identity PrivateKey.
*/
fun createSignature(signedTransaction: SignedTransaction): DigitalSignature.WithKey {
fun createSignature(signedTransaction: SignedTransaction): TransactionSignature {
return createSignature(signedTransaction, legalIdentityKey)
}
@ -210,7 +223,7 @@ interface ServiceHub : ServicesForResolution {
}
/**
* Helper method to ap-pend an additional signature for an existing (partially) [SignedTransaction]
* Helper method to append an additional signature for an existing (partially) [SignedTransaction]
* using the default identity signing key of the node.
* @param signedTransaction The [SignedTransaction] to which the signature will be added.
* @return A new [SignedTransaction] with the addition of the new signature.

View File

@ -2,6 +2,8 @@ package net.corda.core.node.services
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SignableData
import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.PartyAndCertificate
import java.security.PublicKey
@ -48,9 +50,18 @@ interface KeyManagementService {
* or previously generated via the [freshKey] method.
* If the [PublicKey] is actually a [CompositeKey] the first leaf signing key hosted by the node is used.
* @throws IllegalArgumentException if the input key is not a member of [keys].
* TODO A full [KeyManagementService] implementation needs to record activity to the [AuditService] and to limit signing to
* appropriately authorised contexts and initiating users.
*/
@Suspendable
fun sign(bytes: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey
}
/**
* Using the provided signing [PublicKey] internally looks up the matching [PrivateKey] and signs the [SignableData].
* @param signableData a wrapper over transaction id (Merkle root) and signature metadata.
* @param publicKey The [PublicKey] partner to an internally held [PrivateKey], either derived from the node's primary identity,
* or previously generated via the [freshKey] method.
* If the [PublicKey] is actually a [CompositeKey] the first leaf signing key hosted by the node is used.
* @throws IllegalArgumentException if the input key is not a member of [keys].
*/
@Suspendable
fun sign(signableData: SignableData, publicKey: PublicKey): TransactionSignature
}

View File

@ -2,9 +2,7 @@ package net.corda.core.node.services
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.*
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.NotaryError
import net.corda.core.flows.NotaryException
@ -75,4 +73,9 @@ abstract class TrustedAuthorityNotaryService : NotaryService() {
fun sign(bits: ByteArray): DigitalSignature.WithKey {
return services.keyManagementService.sign(bits, services.notaryIdentityKey)
}
fun sign(txId: SecureHash): TransactionSignature {
val signableData = SignableData(txId, SignatureMetadata(services.myInfo.platformVersion, Crypto.findSignatureScheme(services.notaryIdentityKey).schemeNumberID))
return services.keyManagementService.sign(signableData, services.notaryIdentityKey)
}
}

View File

@ -11,7 +11,6 @@ import de.javakaffee.kryoserializers.ArraysAsListSerializer
import de.javakaffee.kryoserializers.BitSetSerializer
import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer
import de.javakaffee.kryoserializers.guava.*
import net.corda.core.crypto.MetaData
import net.corda.core.crypto.composite.CompositeKey
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.transactions.NotaryChangeWireTransaction
@ -98,7 +97,6 @@ object DefaultKryoCustomizer {
addDefaultSerializer(SerializeAsToken::class.java, SerializeAsTokenSerializer<SerializeAsToken>())
register(MetaData::class.java, MetaDataSerializer)
register(BitSet::class.java, BitSetSerializer())
register(Class::class.java, ClassSerializer)

View File

@ -5,7 +5,9 @@ import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import com.esotericsoftware.kryo.util.MapReferenceResolver
import net.corda.core.contracts.*
import net.corda.core.crypto.*
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.composite.CompositeKey
import net.corda.core.identity.Party
import net.corda.core.internal.VisibleForTesting
@ -30,8 +32,6 @@ import java.security.PrivateKey
import java.security.PublicKey
import java.security.cert.CertPath
import java.security.cert.CertificateFactory
import java.security.spec.InvalidKeySpecException
import java.time.Instant
import java.util.*
import javax.annotation.concurrent.ThreadSafe
import kotlin.reflect.KClass
@ -306,7 +306,7 @@ object SignedTransactionSerializer : Serializer<SignedTransaction>() {
override fun read(kryo: Kryo, input: Input, type: Class<SignedTransaction>): SignedTransaction {
return SignedTransaction(
kryo.readClassAndObject(input) as SerializedBytes<CoreTransaction>,
kryo.readClassAndObject(input) as List<DigitalSignature.WithKey>
kryo.readClassAndObject(input) as List<TransactionSignature>
)
}
}
@ -495,35 +495,6 @@ fun <T> Kryo.withoutReferences(block: () -> T): T {
}
}
/** For serialising a MetaData object. */
@ThreadSafe
object MetaDataSerializer : Serializer<MetaData>() {
override fun write(kryo: Kryo, output: Output, obj: MetaData) {
output.writeString(obj.schemeCodeName)
output.writeString(obj.versionID)
kryo.writeClassAndObject(output, obj.signatureType)
kryo.writeClassAndObject(output, obj.timestamp)
kryo.writeClassAndObject(output, obj.visibleInputs)
kryo.writeClassAndObject(output, obj.signedInputs)
output.writeBytesWithLength(obj.merkleRoot)
output.writeBytesWithLength(obj.publicKey.encoded)
}
@Suppress("UNCHECKED_CAST")
@Throws(IllegalArgumentException::class, InvalidKeySpecException::class)
override fun read(kryo: Kryo, input: Input, type: Class<MetaData>): MetaData {
val schemeCodeName = input.readString()
val versionID = input.readString()
val signatureType = kryo.readClassAndObject(input) as SignatureType
val timestamp = kryo.readClassAndObject(input) as Instant?
val visibleInputs = kryo.readClassAndObject(input) as BitSet?
val signedInputs = kryo.readClassAndObject(input) as BitSet?
val merkleRoot = input.readBytesWithLength()
val publicKey = Crypto.decodePublicKey(schemeCodeName, input.readBytesWithLength())
return MetaData(schemeCodeName, versionID, signatureType, timestamp, visibleInputs, signedInputs, merkleRoot, publicKey)
}
}
/** For serialising a Logger. */
@ThreadSafe
object LoggerSerializer : Serializer<Logger>() {

View File

@ -10,7 +10,7 @@ import java.nio.ByteBuffer
import java.util.function.Predicate
/**
* If a privacy salt is provided, the resulted output (merkle-leaf) is computed as
* If a privacy salt is provided, the resulted output (Merkle-leaf) is computed as
* Hash(serializedObject || Hash(privacy_salt || obj_index_in_merkle_tree)).
*/
fun <T : Any> serializedHash(x: T, privacySalt: PrivacySalt?, index: Int): SecureHash {

View File

@ -1,8 +1,8 @@
package net.corda.core.transactions
import net.corda.core.contracts.*
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.toBase58String
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
@ -36,7 +36,7 @@ data class NotaryChangeWireTransaction(
*/
override val id: SecureHash by lazy { serializedHash(inputs + notary + newNotary) }
fun resolve(services: ServiceHub, sigs: List<DigitalSignature.WithKey>): NotaryChangeLedgerTransaction {
fun resolve(services: ServiceHub, sigs: List<TransactionSignature>): NotaryChangeLedgerTransaction {
val resolvedInputs = inputs.map { ref ->
services.loadState(ref).let { StateAndRef(it, ref) }
}
@ -54,7 +54,7 @@ data class NotaryChangeLedgerTransaction(
override val notary: Party,
val newNotary: Party,
override val id: SecureHash,
override val sigs: List<DigitalSignature.WithKey>
override val sigs: List<TransactionSignature>
) : FullTransaction(), TransactionWithSignatures {
init {
checkEncumbrances()

View File

@ -1,8 +1,8 @@
package net.corda.core.transactions
import net.corda.core.contracts.*
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.CordaSerializable
@ -29,10 +29,10 @@ import java.util.*
*/
// DOCSTART 1
data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
override val sigs: List<DigitalSignature.WithKey>
override val sigs: List<TransactionSignature>
) : TransactionWithSignatures {
// DOCEND 1
constructor(ctx: CoreTransaction, sigs: List<DigitalSignature.WithKey>) : this(ctx.serialize(), sigs) {
constructor(ctx: CoreTransaction, sigs: List<TransactionSignature>) : this(ctx.serialize(), sigs) {
cachedTransaction = ctx
}
@ -75,16 +75,16 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
}
/** Returns the same transaction but with an additional (unchecked) signature. */
fun withAdditionalSignature(sig: DigitalSignature.WithKey) = copyWithCache(listOf(sig))
fun withAdditionalSignature(sig: TransactionSignature) = copyWithCache(listOf(sig))
/** Returns the same transaction but with an additional (unchecked) signatures. */
fun withAdditionalSignatures(sigList: Iterable<DigitalSignature.WithKey>) = copyWithCache(sigList)
fun withAdditionalSignatures(sigList: Iterable<TransactionSignature>) = copyWithCache(sigList)
/**
* Creates a copy of the SignedTransaction that includes the provided [sigList]. Also propagates the [cachedTransaction]
* so the contained transaction does not need to be deserialized again.
*/
private fun copyWithCache(sigList: Iterable<DigitalSignature.WithKey>): SignedTransaction {
private fun copyWithCache(sigList: Iterable<TransactionSignature>): SignedTransaction {
val cached = cachedTransaction
return copy(sigs = sigs + sigList).apply {
cachedTransaction = cached
@ -92,10 +92,10 @@ data class SignedTransaction(val txBits: SerializedBytes<CoreTransaction>,
}
/** Alias for [withAdditionalSignature] to let you use Kotlin operator overloading. */
operator fun plus(sig: DigitalSignature.WithKey) = withAdditionalSignature(sig)
operator fun plus(sig: TransactionSignature) = withAdditionalSignature(sig)
/** Alias for [withAdditionalSignatures] to let you use Kotlin operator overloading. */
operator fun plus(sigList: Collection<DigitalSignature.WithKey>) = withAdditionalSignatures(sigList)
operator fun plus(sigList: Collection<TransactionSignature>) = withAdditionalSignatures(sigList)
/**
* Checks the transaction's signatures are valid, optionally calls [verifyRequiredSignatures] to

View File

@ -151,18 +151,18 @@ open class TransactionBuilder(
/** The signatures that have been collected so far - might be incomplete! */
@Deprecated("Signatures should be gathered on a SignedTransaction instead.")
protected val currentSigs = arrayListOf<DigitalSignature.WithKey>()
protected val currentSigs = arrayListOf<TransactionSignature>()
@Deprecated("Use ServiceHub.signInitialTransaction() instead.")
fun signWith(key: KeyPair): TransactionBuilder {
val data = toWireTransaction().id
addSignatureUnchecked(key.sign(data.bytes))
val signableData = SignableData(toWireTransaction().id, SignatureMetadata(1, Crypto.findSignatureScheme(key.public).schemeNumberID)) // A dummy platformVersion.
addSignatureUnchecked(key.sign(signableData))
return this
}
/** Adds the signature directly to the transaction, without checking it for validity. */
@Deprecated("Use ServiceHub.signInitialTransaction() instead.")
fun addSignatureUnchecked(sig: DigitalSignature.WithKey): TransactionBuilder {
fun addSignatureUnchecked(sig: TransactionSignature): TransactionBuilder {
currentSigs.add(sig)
return this
}
@ -188,7 +188,7 @@ open class TransactionBuilder(
* @throws IllegalArgumentException if the signature key doesn't appear in any command.
*/
@Deprecated("Use WireTransaction.checkSignature() instead.")
fun checkAndAddSignature(sig: DigitalSignature.WithKey) {
fun checkAndAddSignature(sig: TransactionSignature) {
checkSignature(sig)
addSignatureUnchecked(sig)
}
@ -200,7 +200,7 @@ open class TransactionBuilder(
* @throws IllegalArgumentException if the signature key doesn't appear in any command.
*/
@Deprecated("Use WireTransaction.checkSignature() instead.")
fun checkSignature(sig: DigitalSignature.WithKey) {
fun checkSignature(sig: TransactionSignature) {
require(commands.any { it.signers.any { sig.by in it.keys } }) { "Signature key doesn't match any command" }
sig.verify(toWireTransaction().id)
}

View File

@ -1,7 +1,7 @@
package net.corda.core.transactions
import net.corda.core.contracts.NamedByHash
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.transactions.SignedTransaction.SignaturesMissingException
import net.corda.core.utilities.toNonEmptySet
@ -10,7 +10,7 @@ import java.security.SignatureException
/** An interface for transactions containing signatures, with logic for signature verification */
interface TransactionWithSignatures : NamedByHash {
val sigs: List<DigitalSignature.WithKey>
val sigs: List<TransactionSignature>
/** Specifies all the public keys that require signatures for the transaction to be valid */
val requiredSigningKeys: Set<PublicKey>
@ -57,7 +57,7 @@ interface TransactionWithSignatures : NamedByHash {
@Throws(SignatureException::class)
fun checkSignaturesAreValid() {
for (sig in sigs) {
sig.verify(id.bytes)
sig.verify(id)
}
}

View File

@ -1,9 +1,9 @@
package net.corda.core.transactions
import net.corda.core.contracts.*
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.MerkleTree
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.keys
import net.corda.core.identity.Party
import net.corda.core.internal.Emoji
@ -172,7 +172,7 @@ data class WireTransaction(
* @throws SignatureException if the signature didn't match the transaction contents.
* @throws IllegalArgumentException if the signature key doesn't appear in any command.
*/
fun checkSignature(sig: DigitalSignature.WithKey) {
fun checkSignature(sig: TransactionSignature) {
require(commands.any { it.signers.any { sig.by in it.keys } }) { "Signature key doesn't match any command" }
sig.verify(id)
}

View File

@ -1,10 +1,8 @@
package net.corda.core.contracts
import net.corda.contracts.asset.DUMMY_CASH_ISSUER_KEY
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.*
import net.corda.core.crypto.composite.CompositeKey
import net.corda.core.crypto.generateKeyPair
import net.corda.core.crypto.sign
import net.corda.core.identity.Party
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.SignedTransaction
@ -19,8 +17,12 @@ import kotlin.test.assertNotEquals
class TransactionTests : TestDependencyInjectionBase() {
private fun makeSigned(wtx: WireTransaction, vararg keys: KeyPair, notarySig: Boolean = true): SignedTransaction {
val keySigs = keys.map { it.sign(wtx.id.bytes) }
val sigs = if (notarySig) keySigs + DUMMY_NOTARY_KEY.sign(wtx.id.bytes) else keySigs
val keySigs = keys.map { it.sign(SignableData(wtx.id, SignatureMetadata(1, Crypto.findSignatureScheme(it.public).schemeNumberID))) }
val sigs = if (notarySig) {
keySigs + DUMMY_NOTARY_KEY.sign(SignableData(wtx.id, SignatureMetadata(1, Crypto.findSignatureScheme(DUMMY_NOTARY_KEY.public).schemeNumberID)))
} else {
keySigs
}
return SignedTransaction(wtx, sigs)
}

View File

@ -36,13 +36,16 @@ class CompositeKeyTests : TestDependencyInjectionBase() {
val charliePublicKey: PublicKey = charlieKey.public
val message = OpaqueBytes("Transaction".toByteArray())
val secureHash = message.sha256()
val aliceSignature = aliceKey.sign(message)
val bobSignature = bobKey.sign(message)
val charlieSignature = charlieKey.sign(message)
// By lazy is required so that the serialisers are configured before vals initialisation takes place (they internally invoke serialise).
val aliceSignature by lazy { aliceKey.sign(SignableData(secureHash, SignatureMetadata(1, Crypto.findSignatureScheme(alicePublicKey).schemeNumberID))) }
val bobSignature by lazy { bobKey.sign(SignableData(secureHash, SignatureMetadata(1, Crypto.findSignatureScheme(bobPublicKey).schemeNumberID))) }
val charlieSignature by lazy { charlieKey.sign(SignableData(secureHash, SignatureMetadata(1, Crypto.findSignatureScheme(charliePublicKey).schemeNumberID))) }
@Test
fun `(Alice) fulfilled by Alice signature`() {
println(aliceKey.serialize().hash)
assertTrue { alicePublicKey.isFulfilledBy(aliceSignature.by) }
assertFalse { alicePublicKey.isFulfilledBy(charlieSignature.by) }
}
@ -153,11 +156,12 @@ class CompositeKeyTests : TestDependencyInjectionBase() {
* Check that verifying a composite signature using the [CompositeSignature] engine works.
*/
@Test
fun `composite signature verification`() {
fun `composite TransactionSignature verification `() {
val twoOfThree = CompositeKey.Builder().addKeys(alicePublicKey, bobPublicKey, charliePublicKey).build(threshold = 2)
val engine = CompositeSignature()
engine.initVerify(twoOfThree)
engine.update(message.bytes)
engine.update(secureHash.bytes)
assertFalse { engine.verify(CompositeSignaturesWithKeys(listOf(aliceSignature)).serialize().bytes) }
assertFalse { engine.verify(CompositeSignaturesWithKeys(listOf(bobSignature)).serialize().bytes) }
@ -168,7 +172,7 @@ class CompositeKeyTests : TestDependencyInjectionBase() {
assertTrue { engine.verify(CompositeSignaturesWithKeys(listOf(aliceSignature, bobSignature, charlieSignature)).serialize().bytes) }
// Check the underlying signature is validated
val brokenBobSignature = DigitalSignature.WithKey(bobSignature.by, aliceSignature.bytes)
val brokenBobSignature = TransactionSignature(aliceSignature.bytes, bobSignature.by, SignatureMetadata(1, Crypto.findSignatureScheme(bobSignature.by).schemeNumberID))
assertFalse { engine.verify(CompositeSignaturesWithKeys(listOf(aliceSignature, brokenBobSignature)).serialize().bytes) }
}
@ -282,19 +286,19 @@ class CompositeKeyTests : TestDependencyInjectionBase() {
@Test
fun `CompositeKey from multiple signature schemes and signature verification`() {
val (privRSA, pubRSA) = Crypto.generateKeyPair(Crypto.RSA_SHA256)
val (privK1, pubK1) = Crypto.generateKeyPair(Crypto.ECDSA_SECP256K1_SHA256)
val (privR1, pubR1) = Crypto.generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
val (privEd, pubEd) = Crypto.generateKeyPair(Crypto.EDDSA_ED25519_SHA512)
val (privSP, pubSP) = Crypto.generateKeyPair(Crypto.SPHINCS256_SHA256)
val keyPairRSA = Crypto.generateKeyPair(Crypto.RSA_SHA256)
val keyPairK1 = Crypto.generateKeyPair(Crypto.ECDSA_SECP256K1_SHA256)
val keyPairR1 = Crypto.generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
val keyPairEd = Crypto.generateKeyPair(Crypto.EDDSA_ED25519_SHA512)
val keyPairSP = Crypto.generateKeyPair(Crypto.SPHINCS256_SHA256)
val RSASignature = privRSA.sign(message.bytes, pubRSA)
val K1Signature = privK1.sign(message.bytes, pubK1)
val R1Signature = privR1.sign(message.bytes, pubR1)
val EdSignature = privEd.sign(message.bytes, pubEd)
val SPSignature = privSP.sign(message.bytes, pubSP)
val RSASignature = keyPairRSA.sign(SignableData(secureHash, SignatureMetadata(1, Crypto.findSignatureScheme(keyPairRSA.public).schemeNumberID)))
val K1Signature = keyPairK1.sign(SignableData(secureHash, SignatureMetadata(1, Crypto.findSignatureScheme(keyPairK1.public).schemeNumberID)))
val R1Signature = keyPairR1.sign(SignableData(secureHash, SignatureMetadata(1, Crypto.findSignatureScheme(keyPairR1.public).schemeNumberID)))
val EdSignature = keyPairEd.sign(SignableData(secureHash, SignatureMetadata(1, Crypto.findSignatureScheme(keyPairEd.public).schemeNumberID)))
val SPSignature = keyPairSP.sign(SignableData(secureHash, SignatureMetadata(1, Crypto.findSignatureScheme(keyPairSP.public).schemeNumberID)))
val compositeKey = CompositeKey.Builder().addKeys(pubRSA, pubK1, pubR1, pubEd, pubSP).build() as CompositeKey
val compositeKey = CompositeKey.Builder().addKeys(keyPairRSA.public, keyPairK1.public, keyPairR1.public, keyPairEd.public, keyPairSP.public).build() as CompositeKey
val signatures = listOf(RSASignature, K1Signature, R1Signature, EdSignature, SPSignature)
assertTrue { compositeKey.isFulfilledBy(signatures.byKeys()) }
@ -307,19 +311,19 @@ class CompositeKeyTests : TestDependencyInjectionBase() {
@Test
fun `Test save to keystore`() {
// From test case [CompositeKey from multiple signature schemes and signature verification]
val (privRSA, pubRSA) = Crypto.generateKeyPair(Crypto.RSA_SHA256)
val (privK1, pubK1) = Crypto.generateKeyPair(Crypto.ECDSA_SECP256K1_SHA256)
val (privR1, pubR1) = Crypto.generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
val (privEd, pubEd) = Crypto.generateKeyPair(Crypto.EDDSA_ED25519_SHA512)
val (privSP, pubSP) = Crypto.generateKeyPair(Crypto.SPHINCS256_SHA256)
val keyPairRSA = Crypto.generateKeyPair(Crypto.RSA_SHA256)
val keyPairK1 = Crypto.generateKeyPair(Crypto.ECDSA_SECP256K1_SHA256)
val keyPairR1 = Crypto.generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
val keyPairEd = Crypto.generateKeyPair(Crypto.EDDSA_ED25519_SHA512)
val keyPairSP = Crypto.generateKeyPair(Crypto.SPHINCS256_SHA256)
val RSASignature = privRSA.sign(message.bytes, pubRSA)
val K1Signature = privK1.sign(message.bytes, pubK1)
val R1Signature = privR1.sign(message.bytes, pubR1)
val EdSignature = privEd.sign(message.bytes, pubEd)
val SPSignature = privSP.sign(message.bytes, pubSP)
val RSASignature = keyPairRSA.sign(SignableData(secureHash, SignatureMetadata(1, Crypto.findSignatureScheme(keyPairRSA.public).schemeNumberID)))
val K1Signature = keyPairK1.sign(SignableData(secureHash, SignatureMetadata(1, Crypto.findSignatureScheme(keyPairK1.public).schemeNumberID)))
val R1Signature = keyPairR1.sign(SignableData(secureHash, SignatureMetadata(1, Crypto.findSignatureScheme(keyPairR1.public).schemeNumberID)))
val EdSignature = keyPairEd.sign(SignableData(secureHash, SignatureMetadata(1, Crypto.findSignatureScheme(keyPairEd.public).schemeNumberID)))
val SPSignature = keyPairSP.sign(SignableData(secureHash, SignatureMetadata(1, Crypto.findSignatureScheme(keyPairSP.public).schemeNumberID)))
val compositeKey = CompositeKey.Builder().addKeys(pubRSA, pubK1, pubR1, pubEd, pubSP).build() as CompositeKey
val compositeKey = CompositeKey.Builder().addKeys(keyPairRSA.public, keyPairK1.public, keyPairR1.public, keyPairEd.public, keyPairSP.public).build() as CompositeKey
val signatures = listOf(RSASignature, K1Signature, R1Signature, EdSignature, SPSignature)
assertTrue { compositeKey.isFulfilledBy(signatures.byKeys()) }

View File

@ -3,73 +3,39 @@ package net.corda.core.crypto
import net.corda.testing.TestDependencyInjectionBase
import org.junit.Test
import java.security.SignatureException
import java.time.Instant
import kotlin.test.assertTrue
/**
* Digital signature MetaData tests
* Digital signature MetaData tests.
*/
class TransactionSignatureTest : TestDependencyInjectionBase() {
val testBytes = "12345678901234567890123456789012".toByteArray()
/** valid sign and verify. */
/** Valid sign and verify. */
@Test
fun `MetaData Full sign and verify`() {
fun `Signature metadata full sign and verify`() {
val keyPair = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
// create a MetaData.Full object
val meta = MetaData("ECDSA_SECP256K1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes, keyPair.public)
// Create a SignableData object.
val signableData = SignableData(testBytes.sha256(), SignatureMetadata(1, Crypto.findSignatureScheme(keyPair.public).schemeNumberID))
// sign the message
val transactionSignature: TransactionSignature = keyPair.private.sign(meta)
// Sign the meta object.
val transactionSignature: TransactionSignature = keyPair.sign(signableData)
// check auto-verification
assertTrue(transactionSignature.verify())
// Check auto-verification.
assertTrue(transactionSignature.verify(testBytes.sha256()))
// check manual verification
assertTrue(keyPair.public.verify(transactionSignature))
// Check manual verification.
assertTrue(Crypto.doVerify(testBytes.sha256(), transactionSignature))
}
/** Signing should fail, as I sign with a secpK1 key, but set schemeCodeName is set to secpR1. */
@Test(expected = IllegalArgumentException::class)
fun `MetaData Full failure wrong scheme`() {
/** Verification should fail; corrupted metadata - clearData (Merkle root) has changed. */
@Test(expected = SignatureException::class)
fun `Signature metadata full failure clearData has changed`() {
val keyPair = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val meta = MetaData("ECDSA_SECP256R1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes, keyPair.public)
keyPair.private.sign(meta)
}
/** Verification should fail; corrupted metadata - public key has changed. */
@Test(expected = SignatureException::class)
fun `MetaData Full failure public key has changed`() {
val keyPair1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val keyPair2 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val meta = MetaData("ECDSA_SECP256K1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes, keyPair2.public)
val transactionSignature = keyPair1.private.sign(meta)
transactionSignature.verify()
}
/** Verification should fail; corrupted metadata - clearData has changed. */
@Test(expected = SignatureException::class)
fun `MetaData Full failure clearData has changed`() {
val keyPair1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val meta = MetaData("ECDSA_SECP256K1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes, keyPair1.public)
val transactionSignature = keyPair1.private.sign(meta)
val meta2 = MetaData("ECDSA_SECP256K1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes.plus(testBytes), keyPair1.public)
val transactionSignature2 = TransactionSignature(transactionSignature.signatureData, meta2)
keyPair1.public.verify(transactionSignature2)
}
/** Verification should fail; corrupted metadata - schemeCodeName has changed from K1 to R1. */
@Test(expected = SignatureException::class)
fun `MetaData Wrong schemeCodeName has changed`() {
val keyPair1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val meta = MetaData("ECDSA_SECP256K1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes, keyPair1.public)
val transactionSignature = keyPair1.private.sign(meta)
val meta2 = MetaData("ECDSA_SECP256R1_SHA256", "M9", SignatureType.FULL, Instant.now(), null, null, testBytes.plus(testBytes), keyPair1.public)
val transactionSignature2 = TransactionSignature(transactionSignature.signatureData, meta2)
keyPair1.public.verify(transactionSignature2)
val signableData = SignableData(testBytes.sha256(), SignatureMetadata(1, Crypto.findSignatureScheme(keyPair.public).schemeNumberID))
val transactionSignature = keyPair.sign(signableData)
Crypto.doVerify((testBytes + testBytes).sha256(), transactionSignature)
}
}

View File

@ -9,6 +9,7 @@ import net.corda.nodeapi.serialization.KryoHeaderV0_1
import net.corda.nodeapi.serialization.SerializationContextImpl
import net.corda.nodeapi.serialization.SerializationFactoryImpl
import net.corda.testing.ALICE
import net.corda.testing.ALICE_PUBKEY
import net.corda.testing.BOB
import net.corda.testing.BOB_PUBKEY
import org.assertj.core.api.Assertions.assertThat
@ -117,16 +118,13 @@ class KryoTests {
}
@Test
fun `serialize - deserialize MetaData`() {
fun `serialize - deserialize SignableData`() {
val testString = "Hello World"
val testBytes = testString.toByteArray()
val keyPair1 = Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
val bitSet = java.util.BitSet(10)
bitSet.set(3)
val meta = MetaData("ECDSA_SECP256K1_SHA256", "M9", SignatureType.FULL, Instant.now(), bitSet, bitSet, testBytes, keyPair1.public)
val meta = SignableData(testBytes.sha256(), SignatureMetadata(1, Crypto.findSignatureScheme(ALICE_PUBKEY).schemeNumberID))
val serializedMetaData = meta.serialize(factory, context).bytes
val meta2 = serializedMetaData.deserialize<MetaData>(factory, context)
val meta2 = serializedMetaData.deserialize<SignableData>(factory, context)
assertEquals(meta2, meta)
}

View File

@ -93,7 +93,7 @@ Where:
* ``outputs`` is a list of the transaction's outputs'
* ``attachments`` is a list of the transaction's attachments'
* ``commands`` is a list of the transaction's commands, and their associated signatures'
* ``id`` is the transaction's merkle root hash'
* ``id`` is the transaction's Merkle root hash'
* ``notary`` is the transaction's notary. If there are inputs these must have the same notary on their source transactions.
* ``timeWindow`` is the transaction's timestamp and defines the acceptable delay for notarisation.

View File

@ -5,8 +5,8 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import net.corda.contracts.asset.Cash;
import net.corda.core.contracts.*;
import net.corda.core.crypto.DigitalSignature;
import net.corda.core.crypto.SecureHash;
import net.corda.core.crypto.TransactionSignature;
import net.corda.core.flows.*;
import net.corda.core.identity.Party;
import net.corda.core.internal.FetchDataFlow;
@ -30,10 +30,8 @@ import java.security.PublicKey;
import java.security.SignatureException;
import java.time.Duration;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static net.corda.core.contracts.ContractsDSL.requireThat;
import static net.corda.testing.TestConstants.getDUMMY_PUBKEY_1;
@ -383,11 +381,11 @@ public class FlowCookbookJava {
// node does not need to check we haven't changed anything in the
// transaction.
// DOCSTART 40
DigitalSignature.WithKey sig = getServiceHub().createSignature(onceSignedTx);
TransactionSignature sig = getServiceHub().createSignature(onceSignedTx);
// DOCEND 40
// And again, if we wanted to use a different public key:
// DOCSTART 41
DigitalSignature.WithKey sig2 = getServiceHub().createSignature(onceSignedTx, otherKey2);
TransactionSignature sig2 = getServiceHub().createSignature(onceSignedTx, otherKey2);
// DOCEND 41
/*----------------------------

View File

@ -5,8 +5,8 @@ package net.corda.docs
import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.*
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.internal.FetchDataFlow
@ -363,11 +363,11 @@ object FlowCookbook {
// node does not need to check we haven't changed anything in the
// transaction.
// DOCSTART 40
val sig: DigitalSignature.WithKey = serviceHub.createSignature(onceSignedTx)
val sig: TransactionSignature = serviceHub.createSignature(onceSignedTx)
// DOCEND 40
// And again, if we wanted to use a different public key:
// DOCSTART 41
val sig2: DigitalSignature.WithKey = serviceHub.createSignature(onceSignedTx, otherKey2)
val sig2: TransactionSignature = serviceHub.createSignature(onceSignedTx, otherKey2)
// DOCEND 41
// In practice, however, the process of gathering every signature

View File

@ -5,8 +5,11 @@ import net.corda.contracts.asset.Cash
import net.corda.core.contracts.Amount
import net.corda.core.contracts.Issued
import net.corda.core.contracts.StateAndRef
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
@ -154,7 +157,7 @@ class ForeignExchangeFlow(val tradeId: String,
// pass transaction details to the counterparty to revalidate and confirm with a signature
// Allow otherParty to access our data to resolve the transaction.
subFlow(SendTransactionFlow(remoteRequestWithNotary.owner, signedTransaction))
val allPartySignedTx = receive<DigitalSignature.WithKey>(remoteRequestWithNotary.owner).unwrap {
val allPartySignedTx = receive<TransactionSignature>(remoteRequestWithNotary.owner).unwrap {
val withNewSignature = signedTransaction + it
// check all signatures are present except the notary
withNewSignature.verifySignaturesExcept(withNewSignature.tx.notary!!.owningKey)

View File

@ -2,8 +2,8 @@ package net.corda.docs
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.*
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.containsAny
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowLogic
@ -188,7 +188,7 @@ class SubmitCompletionFlow(val ref: StateRef, val verdict: WorkflowState) : Flow
val selfSignedTx = serviceHub.signInitialTransaction(tx)
//DOCEND 2
// Send the signed transaction to the originator and await their signature to confirm
val allPartySignedTx = sendAndReceive<DigitalSignature.WithKey>(newState.source, selfSignedTx).unwrap {
val allPartySignedTx = sendAndReceive<TransactionSignature>(newState.source, selfSignedTx).unwrap {
// Add their signature to our unmodified transaction. To check they signed the same tx.
val agreedTx = selfSignedTx + it
// Receive back their signature and confirm that it is for an unmodified transaction

View File

@ -112,7 +112,7 @@ Here is an extract from the ``NodeInterestRates.Oracle`` class and supporting ty
class Oracle {
fun query(queries: List<FixOf>, deadline: Instant): List<Fix>
fun sign(ftx: FilteredTransaction, merkleRoot: SecureHash): DigitalSignature.WithKey
fun sign(ftx: FilteredTransaction, txId: SecureHash): DigitalSignature.WithKey
}
Because the fix contains a timestamp (the ``forDay`` field), that identifies the version of the data being requested,

View File

@ -8,7 +8,7 @@ import net.corda.contracts.asset.Cash
import net.corda.core.contracts.Command
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.crypto.testing.NullSignature
import net.corda.core.crypto.testing.NULL_SIGNATURE
import net.corda.core.identity.AnonymousParty
import net.corda.core.testing.*
import net.corda.core.transactions.SignedTransaction
@ -82,7 +82,7 @@ class SignedTransactionGenerator : Generator<SignedTransaction>(SignedTransactio
val wireTransaction = WiredTransactionGenerator().generate(random, status)
return SignedTransaction(
ctx = wireTransaction,
sigs = listOf(NullSignature)
sigs = listOf(NULL_SIGNATURE)
)
}
}

View File

@ -1,12 +1,9 @@
package net.corda.node.services.keys
import net.corda.core.internal.ThreadBox
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.generateKeyPair
import net.corda.core.crypto.keys
import net.corda.core.crypto.sign
import net.corda.core.crypto.*
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.ThreadBox
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.KeyManagementService
import net.corda.core.serialization.SingletonSerializeAsToken
@ -75,7 +72,13 @@ class E2ETestKeyManagementService(val identityService: IdentityService,
override fun sign(bytes: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey {
val keyPair = getSigningKeyPair(publicKey)
val signature = keyPair.sign(bytes)
return signature
return keyPair.sign(bytes)
}
// TODO: A full KeyManagementService implementation needs to record activity to the Audit Service and to limit
// signing to appropriately authorised contexts and initiating users.
override fun sign(signableData: SignableData, publicKey: PublicKey): TransactionSignature {
val keyPair = getSigningKeyPair(publicKey)
return keyPair.sign(signableData)
}
}

View File

@ -1,12 +1,9 @@
package net.corda.node.services.keys
import net.corda.core.internal.ThreadBox
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.generateKeyPair
import net.corda.core.crypto.keys
import net.corda.core.crypto.sign
import net.corda.core.crypto.*
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.ThreadBox
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.KeyManagementService
import net.corda.core.serialization.SingletonSerializeAsToken
@ -86,8 +83,13 @@ class PersistentKeyManagementService(val identityService: IdentityService,
override fun sign(bytes: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey {
val keyPair = getSigningKeyPair(publicKey)
val signature = keyPair.sign(bytes)
return signature
return keyPair.sign(bytes)
}
// TODO: A full KeyManagementService implementation needs to record activity to the Audit Service and to limit
// signing to appropriately authorised contexts and initiating users.
override fun sign(signableData: SignableData, publicKey: PublicKey): TransactionSignature {
val keyPair = getSigningKeyPair(publicKey)
return keyPair.sign(signableData)
}
}

View File

@ -2,7 +2,10 @@ package net.corda.node.services.transactions
import co.paralleluniverse.fibers.Suspendable
import com.google.common.util.concurrent.SettableFuture
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.NotaryException
import net.corda.core.identity.Party
@ -109,7 +112,8 @@ class BFTNonValidatingNotaryService(override val services: ServiceHubInternal, c
commitInputStates(inputs, id, callerIdentity)
log.debug { "Inputs committed successfully, signing $id" }
val sig = sign(id.bytes)
val signableData = SignableData(id, SignatureMetadata(services.myInfo.platformVersion, Crypto.findSignatureScheme(services.notaryIdentityKey).schemeNumberID))
val sig = sign(signableData)
BFTSMaRt.ReplicaResponse.Signature(sig)
} catch (e: NotaryException) {
log.debug { "Error processing transaction: ${e.error}" }

View File

@ -14,21 +14,18 @@ import bftsmart.tom.server.defaultservices.DefaultReplier
import bftsmart.tom.util.Extractor
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.sign
import net.corda.core.crypto.*
import net.corda.core.flows.NotaryError
import net.corda.core.flows.NotaryException
import net.corda.core.identity.Party
import net.corda.core.internal.declaredField
import net.corda.core.internal.toTypedArray
import net.corda.core.node.services.TimeWindowChecker
import net.corda.core.node.services.UniquenessProvider
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.internal.toTypedArray
import net.corda.core.transactions.FilteredTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.debug
@ -176,7 +173,7 @@ object BFTSMaRt {
abstract class Replica(config: BFTSMaRtConfig,
replicaId: Int,
tableName: String,
private val services: ServiceHubInternal,
protected val services: ServiceHubInternal,
private val timeWindowChecker: TimeWindowChecker) : DefaultRecoverable() {
companion object {
private val log = loggerFor<Replica>()
@ -253,6 +250,10 @@ object BFTSMaRt {
return services.database.transaction { services.keyManagementService.sign(bytes, services.notaryIdentityKey) }
}
protected fun sign(signableData: SignableData): TransactionSignature {
return services.database.transaction { services.keyManagementService.sign(signableData, services.notaryIdentityKey) }
}
// TODO:
// - Test snapshot functionality with different bft-smart cluster configurations.
// - Add streaming to support large data sets.

View File

@ -8,8 +8,7 @@ import net.corda.contracts.asset.Cash
import net.corda.contracts.asset.`issued by`
import net.corda.contracts.asset.`owned by`
import net.corda.core.contracts.*
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.*
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.InitiatingFlow
@ -604,11 +603,11 @@ class TwoPartyTradeFlowTests {
val signed = wtxToSign.map {
val id = it.id
val sigs = mutableListOf<DigitalSignature.WithKey>()
sigs.add(node.services.keyManagementService.sign(id.bytes, node.services.legalIdentityKey))
sigs.add(notaryNode.services.keyManagementService.sign(id.bytes, notaryNode.services.notaryIdentityKey))
val sigs = mutableListOf<TransactionSignature>()
sigs.add(node.services.keyManagementService.sign(SignableData(id, SignatureMetadata(1, Crypto.findSignatureScheme(node.services.legalIdentityKey).schemeNumberID)), node.services.legalIdentityKey))
sigs.add(notaryNode.services.keyManagementService.sign(SignableData(id, SignatureMetadata(1, Crypto.findSignatureScheme(notaryNode.services.notaryIdentityKey).schemeNumberID)), notaryNode.services.notaryIdentityKey))
extraSigningNodes.forEach { currentNode ->
sigs.add(currentNode.services.keyManagementService.sign(id.bytes, currentNode.info.legalIdentity.owningKey))
sigs.add(currentNode.services.keyManagementService.sign(SignableData(id, SignatureMetadata(1, Crypto.findSignatureScheme(currentNode.info.legalIdentity.owningKey).schemeNumberID)), currentNode.info.legalIdentity.owningKey))
}
SignedTransaction(it, sigs)
}

View File

@ -6,6 +6,7 @@ import net.corda.core.flows.FlowLogic
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.*
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.utilities.NonEmptySet
import net.corda.node.internal.InitiatedFlowFactory
import net.corda.node.serialization.NodeClock
import net.corda.node.services.api.*
@ -16,6 +17,8 @@ import net.corda.node.services.statemachine.FlowStateMachineImpl
import net.corda.node.services.statemachine.StateMachineManager
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.utilities.CordaPersistence
import net.corda.testing.DUMMY_IDENTITY_1
import net.corda.testing.MOCK_HOST_AND_PORT
import net.corda.testing.MOCK_IDENTITY_SERVICE
import net.corda.testing.node.MockAttachmentStorage
import net.corda.testing.node.MockNetworkMapCache
@ -60,7 +63,7 @@ open class MockServiceHubInternal(
override val clock: Clock
get() = overrideClock ?: throw UnsupportedOperationException()
override val myInfo: NodeInfo
get() = throw UnsupportedOperationException()
get() = NodeInfo(listOf(MOCK_HOST_AND_PORT), DUMMY_IDENTITY_1, NonEmptySet.of(DUMMY_IDENTITY_1), 1) // Required to get a dummy platformVersion when required for tests.
override val monitoringService: MonitoringService = MonitoringService(MetricRegistry())
override val rpcFlows: List<Class<out FlowLogic<*>>>
get() = throw UnsupportedOperationException()

View File

@ -4,10 +4,7 @@ import io.requery.Persistable
import io.requery.kotlin.eq
import io.requery.sql.KotlinEntityDataStore
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.testing.NullPublicKey
import net.corda.core.crypto.toBase58String
import net.corda.core.crypto.*
import net.corda.core.identity.AnonymousParty
import net.corda.core.node.services.Vault
import net.corda.core.serialization.serialize
@ -20,6 +17,7 @@ import net.corda.node.services.vault.schemas.requery.VaultSchema
import net.corda.node.services.vault.schemas.requery.VaultStatesEntity
import net.corda.node.utilities.CordaPersistence
import net.corda.node.utilities.configureDatabase
import net.corda.testing.ALICE_PUBKEY
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.DUMMY_PUBKEY_1
import net.corda.testing.TestDependencyInjectionBase
@ -212,6 +210,6 @@ class RequeryConfigurationTest : TestDependencyInjectionBase() {
notary = DUMMY_NOTARY,
timeWindow = null
)
return SignedTransaction(wtx, listOf(DigitalSignature.WithKey(NullPublicKey, ByteArray(1))))
return SignedTransaction(wtx, listOf(TransactionSignature(ByteArray(1), ALICE_PUBKEY, SignatureMetadata(1, Crypto.findSignatureScheme(ALICE_PUBKEY).schemeNumberID))))
}
}

View File

@ -19,7 +19,11 @@ import net.corda.node.services.persistence.DBCheckpointStorage
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
import net.corda.node.services.statemachine.StateMachineManager
import net.corda.node.services.vault.NodeVaultService
import net.corda.node.utilities.*
import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.CordaPersistence
import net.corda.node.utilities.configureDatabase
import net.corda.testing.node.InMemoryMessagingNetwork
import net.corda.testing.node.MockKeyManagementService
import net.corda.testing.getTestX509Name
import net.corda.testing.testNodeConfiguration
import net.corda.testing.initialiseTestSerialization
@ -35,7 +39,6 @@ import java.nio.file.Paths
import java.security.PublicKey
import java.time.Clock
import java.time.Instant
import java.util.*
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

View File

@ -1,15 +1,17 @@
package net.corda.node.services.persistence
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.testing.NullPublicKey
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.TransactionSignature
import net.corda.core.toFuture
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.utilities.CordaPersistence
import net.corda.node.utilities.configureDatabase
import net.corda.testing.ALICE_PUBKEY
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.LogHelper
import net.corda.testing.TestDependencyInjectionBase
@ -150,6 +152,6 @@ class DBTransactionStorageTests : TestDependencyInjectionBase() {
notary = DUMMY_NOTARY,
timeWindow = null
)
return SignedTransaction(wtx, listOf(DigitalSignature.WithKey(NullPublicKey, ByteArray(1))))
return SignedTransaction(wtx, listOf(TransactionSignature(ByteArray(1), ALICE_PUBKEY, SignatureMetadata(1, Crypto.findSignatureScheme(ALICE_PUBKEY).schemeNumberID))))
}
}

View File

@ -3,15 +3,15 @@ package net.corda.node.services.transactions
import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.TransactionSignature
import net.corda.core.flows.NotaryError
import net.corda.core.flows.NotaryException
import net.corda.core.flows.NotaryFlow
import net.corda.core.node.services.ServiceInfo
import net.corda.core.utilities.seconds
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.getOrThrow
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.node.internal.AbstractNode
import net.corda.node.services.network.NetworkMapService
import net.corda.testing.DUMMY_NOTARY
@ -132,7 +132,7 @@ class NotaryServiceTests {
notaryError.conflict.verified()
}
private fun runNotaryClient(stx: SignedTransaction): CordaFuture<List<DigitalSignature.WithKey>> {
private fun runNotaryClient(stx: SignedTransaction): CordaFuture<List<TransactionSignature>> {
val flow = NotaryFlow.Client(stx)
val future = clientNode.services.startFlow(flow).resultFuture
mockNet.runNetwork()

View File

@ -4,7 +4,7 @@ import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.Command
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.TransactionSignature
import net.corda.core.flows.NotaryError
import net.corda.core.flows.NotaryException
import net.corda.core.flows.NotaryFlow
@ -85,7 +85,7 @@ class ValidatingNotaryServiceTests {
assertEquals(setOf(expectedMissingKey), missingKeys)
}
private fun runClient(stx: SignedTransaction): CordaFuture<List<DigitalSignature.WithKey>> {
private fun runClient(stx: SignedTransaction): CordaFuture<List<TransactionSignature>> {
val flow = NotaryFlow.Client(stx)
val future = clientNode.services.startFlow(flow).resultFuture
mockNet.runNetwork()

View File

@ -5,7 +5,6 @@ import net.corda.contracts.asset.DUMMY_CASH_ISSUER
import net.corda.contracts.getCashBalance
import net.corda.core.contracts.*
import net.corda.core.crypto.generateKeyPair
import net.corda.core.crypto.sign
import net.corda.core.identity.AnonymousParty
import net.corda.core.node.services.StatesNotAvailableException
import net.corda.core.node.services.Vault
@ -509,14 +508,14 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
val amount = Amount(1000, Issued(BOC.ref(1), GBP))
// Issue some cash
val issueTx = TransactionBuilder(notary).apply {
val issueTxBuilder = TransactionBuilder(notary).apply {
Cash().generateIssue(this, amount, anonymousIdentity.party, notary)
}.toWireTransaction()
signWith(BOC_KEY)
}
// We need to record the issue transaction so inputs can be resolved for the notary change transaction
val signedIssueTx = SignedTransaction(issueTx, listOf(BOC_KEY.sign(issueTx.id)))
services.validatedTransactions.addTransaction(signedIssueTx)
services.validatedTransactions.addTransaction(issueTxBuilder.toSignedTransaction())
val issueTx = issueTxBuilder.toWireTransaction()
val initialCashState = StateAndRef(issueTx.outputs.single(), StateRef(issueTx.id, 0))
// Change notary

View File

@ -9,9 +9,7 @@ import net.corda.contracts.math.CubicSplineInterpolator
import net.corda.contracts.math.Interpolator
import net.corda.contracts.math.InterpolatorFactory
import net.corda.core.contracts.Command
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.MerkleTreeException
import net.corda.core.crypto.keys
import net.corda.core.crypto.*
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatedBy
@ -146,7 +144,7 @@ object NodeInterestRates {
// Oracle gets signing request for only some of them with a valid partial tree? We sign over a whole transaction.
// It will be fixed by adding partial signatures later.
// DOCSTART 1
fun sign(ftx: FilteredTransaction): DigitalSignature.WithKey {
fun sign(ftx: FilteredTransaction): TransactionSignature {
if (!ftx.verify()) {
throw MerkleTreeException("Rate Fix Oracle: Couldn't verify partial Merkle tree.")
}
@ -177,8 +175,9 @@ object NodeInterestRates {
// Note that we will happily sign an invalid transaction, as we are only being presented with a filtered
// version so we can't resolve or check it ourselves. However, that doesn't matter much, as if we sign
// an invalid transaction the signature is worthless.
val signature = services.keyManagementService.sign(ftx.rootHash.bytes, signingKey)
return DigitalSignature.WithKey(signingKey, signature.bytes)
val signableData = SignableData(ftx.rootHash, SignatureMetadata(services.myInfo.platformVersion, Crypto.findSignatureScheme(signingKey).schemeNumberID))
val signature = services.keyManagementService.sign(signableData, signingKey)
return TransactionSignature(signature.bytes, signingKey, signableData.signatureMetadata)
}
// DOCEND 1

View File

@ -3,7 +3,7 @@ package net.corda.irs.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.Fix
import net.corda.contracts.FixOf
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
@ -112,10 +112,10 @@ open class RatesFixFlow(protected val tx: TransactionBuilder,
@InitiatingFlow
class FixSignFlow(val tx: TransactionBuilder, val oracle: Party,
val partialMerkleTx: FilteredTransaction) : FlowLogic<DigitalSignature.WithKey>() {
val partialMerkleTx: FilteredTransaction) : FlowLogic<TransactionSignature>() {
@Suspendable
override fun call(): DigitalSignature.WithKey {
val resp = sendAndReceive<DigitalSignature.WithKey>(oracle, SignRequest(partialMerkleTx))
override fun call(): TransactionSignature {
val resp = sendAndReceive<TransactionSignature>(oracle, SignRequest(partialMerkleTx))
return resp.unwrap { sig ->
check(oracle.owningKey.isFulfilledBy(listOf(sig.by)))
tx.toWireTransaction().checkSignature(sig)

View File

@ -77,6 +77,9 @@ val DUMMY_CA: CertificateAndKeyPair by lazy {
fun dummyCommand(vararg signers: PublicKey) = Command<TypeOnlyCommandData>(object : TypeOnlyCommandData() {}, signers.toList())
val DUMMY_IDENTITY_1: PartyAndCertificate get() = getTestPartyAndCertificate(DUMMY_PARTY)
val DUMMY_PARTY: Party get() = Party(X500Name("CN=Dummy,O=Dummy,L=Madrid,C=ES"), DUMMY_KEY_1.public)
//
// Extensions to the Driver DSL to auto-manufacture nodes by name.
//

View File

@ -1,12 +1,9 @@
package net.corda.testing
import net.corda.core.contracts.*
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.*
import net.corda.core.crypto.composite.expandedCompositeKeys
import net.corda.core.crypto.sign
import net.corda.core.crypto.testing.NullSignature
import net.corda.core.crypto.toStringShort
import net.corda.core.crypto.testing.NULL_SIGNATURE
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import net.corda.core.transactions.SignedTransaction
@ -289,7 +286,7 @@ data class TestLedgerDSLInterpreter private constructor(
override fun verifies(): EnforceVerifyOrFail {
try {
val usedInputs = mutableSetOf<StateRef>()
services.recordTransactions(transactionsUnverified.map { SignedTransaction(it, listOf(NullSignature)) })
services.recordTransactions(transactionsUnverified.map { SignedTransaction(it, listOf(NULL_SIGNATURE)) })
for ((_, value) in transactionWithLocations) {
val wtx = value.transaction
val ltx = wtx.toLedgerTransaction(services)
@ -301,7 +298,7 @@ data class TestLedgerDSLInterpreter private constructor(
throw DoubleSpentInputs(txIds)
}
usedInputs.addAll(wtx.inputs)
services.recordTransactions(SignedTransaction(wtx, listOf(NullSignature)))
services.recordTransactions(SignedTransaction(wtx, listOf(NULL_SIGNATURE)))
}
return EnforceVerifyOrFail.Token
} catch (exception: TransactionVerificationException) {
@ -335,7 +332,7 @@ data class TestLedgerDSLInterpreter private constructor(
*/
fun signAll(transactionsToSign: List<WireTransaction>, extraKeys: List<KeyPair>) = transactionsToSign.map { wtx ->
check(wtx.requiredSigningKeys.isNotEmpty())
val signatures = ArrayList<DigitalSignature.WithKey>()
val signatures = ArrayList<TransactionSignature>()
val keyLookup = HashMap<PublicKey, KeyPair>()
(ALL_TEST_KEYS + extraKeys).forEach {
@ -343,7 +340,7 @@ fun signAll(transactionsToSign: List<WireTransaction>, extraKeys: List<KeyPair>)
}
wtx.requiredSigningKeys.expandedCompositeKeys.forEach {
val key = keyLookup[it] ?: throw IllegalArgumentException("Missing required key for ${it.toStringShort()}")
signatures += key.sign(wtx.id)
signatures += key.sign(SignableData(wtx.id, SignatureMetadata(1, Crypto.findSignatureScheme(it).schemeNumberID)))
}
SignedTransaction(wtx, signatures)
}

View File

@ -124,8 +124,12 @@ class MockKeyManagementService(val identityService: IdentityService,
override fun sign(bytes: ByteArray, publicKey: PublicKey): DigitalSignature.WithKey {
val keyPair = getSigningKeyPair(publicKey)
val signature = keyPair.sign(bytes)
return signature
return keyPair.sign(bytes)
}
override fun sign(signableData: SignableData, publicKey: PublicKey): TransactionSignature {
val keyPair = getSigningKeyPair(publicKey)
return keyPair.sign(signableData)
}
}