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:
Mike Hearn 2015-12-07 17:53:01 +01:00
parent f4ddbc9e82
commit 73cbd41a09
6 changed files with 32 additions and 14 deletions

View File

@ -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) {

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -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())

View File

@ -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 :(

View File

@ -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()
}