mirror of
https://github.com/corda/corda.git
synced 2025-01-06 21:18:46 +00:00
Misc fixes:
- Use the OpaqueBytes wrapper (a box for byte[]) inside TimestampedWireTransaction to avoid array comparison issues. - Introduce a few utility functions to make OpaqueBytes less painful. - Make StateAndRef serialisable - Introduce the notion of an OwnedState which abstracts out the owner field.
This commit is contained in:
parent
f4ddbc9e82
commit
73cbd41a09
src
main/kotlin/core
test/kotlin/core/serialization
@ -45,6 +45,7 @@ sealed class SecureHash(bits: ByteArray) : OpaqueBytes(bits) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun ByteArray.sha256(): SecureHash.SHA256 = SecureHash.sha256(this)
|
fun ByteArray.sha256(): SecureHash.SHA256 = SecureHash.sha256(this)
|
||||||
|
fun OpaqueBytes.sha256(): SecureHash.SHA256 = SecureHash.sha256(this.bits)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper around a digital signature. The covering field is a generic tag usable by whatever is interpreting the
|
* A wrapper around a digital signature. The covering field is a generic tag usable by whatever is interpreting the
|
||||||
@ -90,6 +91,7 @@ fun PrivateKey.signWithECDSA(bits: ByteArray): DigitalSignature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun PrivateKey.signWithECDSA(bits: ByteArray, publicKey: PublicKey) = DigitalSignature.WithKey(publicKey, signWithECDSA(bits).bits)
|
fun PrivateKey.signWithECDSA(bits: ByteArray, publicKey: PublicKey) = DigitalSignature.WithKey(publicKey, signWithECDSA(bits).bits)
|
||||||
|
fun KeyPair.signWithECDSA(bits: ByteArray) = private.signWithECDSA(bits, public)
|
||||||
|
|
||||||
/** Utility to simplify the act of verifying a signature */
|
/** Utility to simplify the act of verifying a signature */
|
||||||
fun PublicKey.verifyWithECDSA(content: ByteArray, signature: DigitalSignature) {
|
fun PublicKey.verifyWithECDSA(content: ByteArray, signature: DigitalSignature) {
|
||||||
|
@ -26,6 +26,14 @@ interface ContractState : SerializeableWithKryo {
|
|||||||
val programRef: SecureHash
|
val programRef: SecureHash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface OwnableState : ContractState {
|
||||||
|
/** There must be a MoveCommand signed by this key to claim the amount */
|
||||||
|
val owner: PublicKey
|
||||||
|
|
||||||
|
/** Copies the underlying data structure, replacing the owner field with this new value and leaving the rest alone */
|
||||||
|
fun withNewOwner(newOwner: PublicKey): Pair<Command, OwnableState>
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns the SHA-256 hash of the serialised contents of this state (not cached!) */
|
/** Returns the SHA-256 hash of the serialised contents of this state (not cached!) */
|
||||||
fun ContractState.hash(): SecureHash = SecureHash.sha256((serialize()))
|
fun ContractState.hash(): SecureHash = SecureHash.sha256((serialize()))
|
||||||
|
|
||||||
@ -36,7 +44,7 @@ fun ContractState.hash(): SecureHash = SecureHash.sha256((serialize()))
|
|||||||
data class ContractStateRef(val txhash: SecureHash, val index: Int) : SerializeableWithKryo
|
data class ContractStateRef(val txhash: SecureHash, val index: Int) : SerializeableWithKryo
|
||||||
|
|
||||||
/** A StateAndRef is simply a (state, ref) pair. For instance, a wallet (which holds available assets) contains these. */
|
/** A StateAndRef is simply a (state, ref) pair. For instance, a wallet (which holds available assets) contains these. */
|
||||||
data class StateAndRef<out T : ContractState>(val state: T, val ref: ContractStateRef)
|
data class StateAndRef<out T : ContractState>(val state: T, val ref: ContractStateRef) : SerializeableWithKryo
|
||||||
|
|
||||||
/** A [Party] is well known (name, pubkey) pair. In a real system this would probably be an X.509 certificate. */
|
/** A [Party] is well known (name, pubkey) pair. In a real system this would probably be an X.509 certificate. */
|
||||||
data class Party(val name: String, val owningKey: PublicKey) : SerializeableWithKryo {
|
data class Party(val name: String, val owningKey: PublicKey) : SerializeableWithKryo {
|
||||||
|
@ -102,11 +102,13 @@ class PartialTransaction(private val inputStates: MutableList<ContractStateRef>
|
|||||||
|
|
||||||
fun toWireTransaction() = WireTransaction(inputStates, outputStates, commands)
|
fun toWireTransaction() = WireTransaction(inputStates, outputStates, commands)
|
||||||
|
|
||||||
fun toSignedTransaction(): SignedWireTransaction {
|
fun toSignedTransaction(checkSufficientSignatures: Boolean = true): SignedWireTransaction {
|
||||||
val requiredKeys = commands.flatMap { it.pubkeys }.toSet()
|
if (checkSufficientSignatures) {
|
||||||
val gotKeys = currentSigs.map { it.by }.toSet()
|
val requiredKeys = commands.flatMap { it.pubkeys }.toSet()
|
||||||
check(gotKeys == requiredKeys) { "The set of required signatures isn't equal to the signatures we've got" }
|
val gotKeys = currentSigs.map { it.by }.toSet()
|
||||||
return SignedWireTransaction(toWireTransaction().serialize(), ArrayList(currentSigs))
|
check(gotKeys == requiredKeys) { "The set of required signatures isn't equal to the signatures we've got" }
|
||||||
|
}
|
||||||
|
return SignedWireTransaction(toWireTransaction().serialize().opaque(), ArrayList(currentSigs))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addInputState(ref: ContractStateRef) {
|
fun addInputState(ref: ContractStateRef) {
|
||||||
@ -144,7 +146,7 @@ interface TimestamperService {
|
|||||||
fun verifyTimestamp(hash: SecureHash, signedTimestamp: ByteArray): Instant
|
fun verifyTimestamp(hash: SecureHash, signedTimestamp: ByteArray): Instant
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SignedWireTransaction(val txBits: ByteArray, val sigs: List<DigitalSignature.WithKey>) : SerializeableWithKryo {
|
data class SignedWireTransaction(val txBits: OpaqueBytes, val sigs: List<DigitalSignature.WithKey>) : SerializeableWithKryo {
|
||||||
init {
|
init {
|
||||||
check(sigs.isNotEmpty())
|
check(sigs.isNotEmpty())
|
||||||
}
|
}
|
||||||
@ -158,7 +160,7 @@ data class SignedWireTransaction(val txBits: ByteArray, val sigs: List<DigitalSi
|
|||||||
*/
|
*/
|
||||||
fun verifySignatures() {
|
fun verifySignatures() {
|
||||||
for (sig in sigs)
|
for (sig in sigs)
|
||||||
sig.verifyWithECDSA(txBits)
|
sig.verifyWithECDSA(txBits.bits)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -182,11 +184,11 @@ data class SignedWireTransaction(val txBits: ByteArray, val sigs: List<DigitalSi
|
|||||||
/** Uses the given timestamper service to calculate a signed timestamp and then returns a wrapper for both */
|
/** Uses the given timestamper service to calculate a signed timestamp and then returns a wrapper for both */
|
||||||
fun toTimestampedTransaction(timestamper: TimestamperService): TimestampedWireTransaction {
|
fun toTimestampedTransaction(timestamper: TimestamperService): TimestampedWireTransaction {
|
||||||
val bits = serialize()
|
val bits = serialize()
|
||||||
return TimestampedWireTransaction(bits, timestamper.timestamp(bits.sha256()))
|
return TimestampedWireTransaction(bits.opaque(), timestamper.timestamp(bits.sha256()).opaque())
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a [TimestampedWireTransaction] with an empty byte array as the timestamp: this means, no time was provided. */
|
/** Returns a [TimestampedWireTransaction] with an empty byte array as the timestamp: this means, no time was provided. */
|
||||||
fun toTimestampedTransactionWithoutTime() = TimestampedWireTransaction(serialize(), ByteArray(0))
|
fun toTimestampedTransactionWithoutTime() = TimestampedWireTransaction(serialize().opaque(), null)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -195,17 +197,17 @@ data class SignedWireTransaction(val txBits: ByteArray, val sigs: List<DigitalSi
|
|||||||
*/
|
*/
|
||||||
data class TimestampedWireTransaction(
|
data class TimestampedWireTransaction(
|
||||||
/** A serialised SignedWireTransaction */
|
/** A serialised SignedWireTransaction */
|
||||||
val signedWireTX: ByteArray,
|
val signedWireTX: OpaqueBytes,
|
||||||
|
|
||||||
/** Signature from a timestamping authority. For instance using RFC 3161 */
|
/** Signature from a timestamping authority. For instance using RFC 3161 */
|
||||||
val timestamp: ByteArray
|
val timestamp: OpaqueBytes?
|
||||||
) : SerializeableWithKryo {
|
) : SerializeableWithKryo {
|
||||||
val transactionID: SecureHash = serialize().sha256()
|
val transactionID: SecureHash = serialize().sha256()
|
||||||
|
|
||||||
fun verifyToLedgerTransaction(timestamper: TimestamperService, partyKeyMap: Map<PublicKey, Party>): LedgerTransaction {
|
fun verifyToLedgerTransaction(timestamper: TimestamperService, partyKeyMap: Map<PublicKey, Party>): LedgerTransaction {
|
||||||
val stx: SignedWireTransaction = signedWireTX.deserialize()
|
val stx: SignedWireTransaction = signedWireTX.deserialize()
|
||||||
val wtx: WireTransaction = stx.verify()
|
val wtx: WireTransaction = stx.verify()
|
||||||
val instant: Instant? = if (timestamp.size != 0) timestamper.verifyTimestamp(signedWireTX.sha256(), timestamp) else null
|
val instant: Instant? = if (timestamp != null) timestamper.verifyTimestamp(signedWireTX.sha256(), timestamp.bits) else null
|
||||||
return wtx.toLedgerTransaction(instant, partyKeyMap, transactionID)
|
return wtx.toLedgerTransaction(instant, partyKeyMap, transactionID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,12 @@ open class OpaqueBytes(val bits: ByteArray) : SerializeableWithKryo {
|
|||||||
|
|
||||||
override fun hashCode() = Arrays.hashCode(bits)
|
override fun hashCode() = Arrays.hashCode(bits)
|
||||||
override fun toString() = "[" + BaseEncoding.base16().encode(bits) + "]"
|
override fun toString() = "[" + BaseEncoding.base16().encode(bits) + "]"
|
||||||
|
|
||||||
|
val size: Int get() = bits.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ByteArray.opaque(): OpaqueBytes = OpaqueBytes(this)
|
||||||
|
|
||||||
val Int.days: Duration get() = Duration.ofDays(this.toLong())
|
val Int.days: Duration get() = Duration.ofDays(this.toLong())
|
||||||
val Int.hours: Duration get() = Duration.ofHours(this.toLong())
|
val Int.hours: Duration get() = Duration.ofHours(this.toLong())
|
||||||
val Int.minutes: Duration get() = Duration.ofMinutes(this.toLong())
|
val Int.minutes: Duration get() = Duration.ofMinutes(this.toLong())
|
||||||
|
@ -183,6 +183,7 @@ val THREAD_LOCAL_KRYO = ThreadLocal.withInitial { createKryo() }
|
|||||||
|
|
||||||
inline fun <reified T : SerializeableWithKryo> Kryo.registerDataClass() = register(T::class.java, DataClassSerializer(T::class))
|
inline fun <reified T : SerializeableWithKryo> Kryo.registerDataClass() = register(T::class.java, DataClassSerializer(T::class))
|
||||||
inline fun <reified T : SerializeableWithKryo> ByteArray.deserialize(kryo: Kryo = THREAD_LOCAL_KRYO.get()): T = kryo.readObject(Input(this), T::class.java)
|
inline fun <reified T : SerializeableWithKryo> ByteArray.deserialize(kryo: Kryo = THREAD_LOCAL_KRYO.get()): T = kryo.readObject(Input(this), T::class.java)
|
||||||
|
inline fun <reified T : SerializeableWithKryo> OpaqueBytes.deserialize(kryo: Kryo = THREAD_LOCAL_KRYO.get()): T = kryo.readObject(Input(this.bits), T::class.java)
|
||||||
|
|
||||||
fun SerializeableWithKryo.serialize(kryo: Kryo = THREAD_LOCAL_KRYO.get()): ByteArray {
|
fun SerializeableWithKryo.serialize(kryo: Kryo = THREAD_LOCAL_KRYO.get()): ByteArray {
|
||||||
val stream = ByteArrayOutputStream()
|
val stream = ByteArrayOutputStream()
|
||||||
@ -232,6 +233,7 @@ fun createKryo(): Kryo {
|
|||||||
registerDataClass<WireTransaction>()
|
registerDataClass<WireTransaction>()
|
||||||
registerDataClass<WireCommand>()
|
registerDataClass<WireCommand>()
|
||||||
registerDataClass<TimestampedWireTransaction>()
|
registerDataClass<TimestampedWireTransaction>()
|
||||||
|
registerDataClass<StateAndRef<ContractState>>()
|
||||||
|
|
||||||
// Can't use data classes for this in Kotlin 1.0 due to lack of support for inheritance: must write a manual
|
// Can't use data classes for this in Kotlin 1.0 due to lack of support for inheritance: must write a manual
|
||||||
// serialiser instead :(
|
// serialiser instead :(
|
||||||
|
@ -44,7 +44,7 @@ class TransactionSerializationTests {
|
|||||||
signedTX.verify()
|
signedTX.verify()
|
||||||
|
|
||||||
// Corrupt the data and ensure the signature catches the problem.
|
// Corrupt the data and ensure the signature catches the problem.
|
||||||
signedTX.txBits[5] = 0
|
signedTX.txBits.bits[5] = 0
|
||||||
assertFailsWith(SignatureException::class) {
|
assertFailsWith(SignatureException::class) {
|
||||||
signedTX.verify()
|
signedTX.verify()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user