mirror of
https://github.com/corda/corda.git
synced 2024-12-21 05:53:23 +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
@ -45,6 +45,7 @@ sealed class SecureHash(bits: ByteArray) : OpaqueBytes(bits) {
|
||||
}
|
||||
|
||||
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
|
||||
@ -90,6 +91,7 @@ fun PrivateKey.signWithECDSA(bits: ByteArray): DigitalSignature {
|
||||
}
|
||||
|
||||
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 */
|
||||
fun PublicKey.verifyWithECDSA(content: ByteArray, signature: DigitalSignature) {
|
||||
|
@ -26,6 +26,14 @@ interface ContractState : SerializeableWithKryo {
|
||||
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!) */
|
||||
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
|
||||
|
||||
/** 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. */
|
||||
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 toSignedTransaction(): SignedWireTransaction {
|
||||
val requiredKeys = commands.flatMap { it.pubkeys }.toSet()
|
||||
val gotKeys = currentSigs.map { it.by }.toSet()
|
||||
check(gotKeys == requiredKeys) { "The set of required signatures isn't equal to the signatures we've got" }
|
||||
return SignedWireTransaction(toWireTransaction().serialize(), ArrayList(currentSigs))
|
||||
fun toSignedTransaction(checkSufficientSignatures: Boolean = true): SignedWireTransaction {
|
||||
if (checkSufficientSignatures) {
|
||||
val requiredKeys = commands.flatMap { it.pubkeys }.toSet()
|
||||
val gotKeys = currentSigs.map { it.by }.toSet()
|
||||
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) {
|
||||
@ -144,7 +146,7 @@ interface TimestamperService {
|
||||
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 {
|
||||
check(sigs.isNotEmpty())
|
||||
}
|
||||
@ -158,7 +160,7 @@ data class SignedWireTransaction(val txBits: ByteArray, val sigs: List<DigitalSi
|
||||
*/
|
||||
fun verifySignatures() {
|
||||
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 */
|
||||
fun toTimestampedTransaction(timestamper: TimestamperService): TimestampedWireTransaction {
|
||||
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. */
|
||||
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(
|
||||
/** A serialised SignedWireTransaction */
|
||||
val signedWireTX: ByteArray,
|
||||
val signedWireTX: OpaqueBytes,
|
||||
|
||||
/** Signature from a timestamping authority. For instance using RFC 3161 */
|
||||
val timestamp: ByteArray
|
||||
val timestamp: OpaqueBytes?
|
||||
) : SerializeableWithKryo {
|
||||
val transactionID: SecureHash = serialize().sha256()
|
||||
|
||||
fun verifyToLedgerTransaction(timestamper: TimestamperService, partyKeyMap: Map<PublicKey, Party>): LedgerTransaction {
|
||||
val stx: SignedWireTransaction = signedWireTX.deserialize()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -29,8 +29,12 @@ open class OpaqueBytes(val bits: ByteArray) : SerializeableWithKryo {
|
||||
|
||||
override fun hashCode() = Arrays.hashCode(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.hours: Duration get() = Duration.ofHours(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> 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 {
|
||||
val stream = ByteArrayOutputStream()
|
||||
@ -232,6 +233,7 @@ fun createKryo(): Kryo {
|
||||
registerDataClass<WireTransaction>()
|
||||
registerDataClass<WireCommand>()
|
||||
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
|
||||
// serialiser instead :(
|
||||
|
@ -44,7 +44,7 @@ class TransactionSerializationTests {
|
||||
signedTX.verify()
|
||||
|
||||
// Corrupt the data and ensure the signature catches the problem.
|
||||
signedTX.txBits[5] = 0
|
||||
signedTX.txBits.bits[5] = 0
|
||||
assertFailsWith(SignatureException::class) {
|
||||
signedTX.verify()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user