diff --git a/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt b/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt index d7d3e8f205..75bdb2e73b 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CompositeKey.kt @@ -4,6 +4,7 @@ import net.corda.core.crypto.CompositeKey.NodeAndWeight import net.corda.core.serialization.CordaSerializable import org.bouncycastle.asn1.* import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo +import java.nio.ByteBuffer import java.security.PublicKey import java.util.* @@ -109,11 +110,11 @@ class CompositeKey private constructor (val threshold: Int, // We don't allow zero or negative weights. Minimum weight = 1. require (weight > 0) { "A non-positive weight was detected. Node info: $this" } } + override fun compareTo(other: NodeAndWeight): Int { - if (weight == other.weight) { - return node.hashCode().compareTo(other.node.hashCode()) - } - else return weight.compareTo(other.weight) + return if (weight == other.weight) { + ByteBuffer.wrap(node.toSHA256Bytes()).compareTo(ByteBuffer.wrap(other.node.toSHA256Bytes())) + } else weight.compareTo(other.weight) } override fun toASN1Primitive(): ASN1Primitive { diff --git a/core/src/main/kotlin/net/corda/core/crypto/EncodingUtils.kt b/core/src/main/kotlin/net/corda/core/crypto/EncodingUtils.kt index b1681a19f9..a79821b760 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/EncodingUtils.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/EncodingUtils.kt @@ -65,4 +65,4 @@ fun String.hexToBase64(): String = hexToByteArray().toBase64() // structure, e.g. mapping a PublicKey to a condition with the specific feature (ED25519). fun parsePublicKeyBase58(base58String: String): PublicKey = base58String.base58ToByteArray().deserialize() fun PublicKey.toBase58String(): String = this.serialize().bytes.toBase58() -fun PublicKey.toSHA256Bytes(): ByteArray = this.serialize().bytes.sha256().bytes +fun PublicKey.toSHA256Bytes(): ByteArray = this.serialize().bytes.sha256().bytes // TODO: decide on the format of hashed key (encoded Vs serialised). diff --git a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt index 591d56001a..bee35e0a9d 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/CompositeKeyTests.kt @@ -1,7 +1,7 @@ package net.corda.core.crypto -import net.corda.core.utilities.OpaqueBytes import net.corda.core.serialization.serialize +import net.corda.core.utilities.OpaqueBytes import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -259,4 +259,22 @@ class CompositeKeyTests { val signaturesWithoutRSA = listOf(K1Signature, R1Signature, EdSignature, SPSignature) assertFalse { compositeKey.isFulfilledBy(signaturesWithoutRSA.byKeys()) } } + + @Test + fun `CompositeKey deterministic children sorting`() { + val (_, pub1) = Crypto.generateKeyPair(Crypto.EDDSA_ED25519_SHA512) + val (_, pub2) = Crypto.generateKeyPair(Crypto.ECDSA_SECP256K1_SHA256) + val (_, pub3) = Crypto.generateKeyPair(Crypto.RSA_SHA256) + val (_, pub4) = Crypto.generateKeyPair(Crypto.EDDSA_ED25519_SHA512) + val (_, pub5) = Crypto.generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256) + val (_, pub6) = Crypto.generateKeyPair(Crypto.SPHINCS256_SHA256) + val (_, pub7) = Crypto.generateKeyPair(Crypto.ECDSA_SECP256K1_SHA256) + + // Using default weight = 1, thus all weights are equal. + val composite1 = CompositeKey.Builder().addKeys(pub1, pub2, pub3, pub4, pub5, pub6, pub7).build() as CompositeKey + // Store in reverse order. + val composite2 = CompositeKey.Builder().addKeys(pub7, pub6, pub5, pub4, pub3, pub2, pub1).build() as CompositeKey + // There are 7! = 5040 permutations, but as sorting is deterministic the following should never fail. + assertEquals(composite1.children, composite2.children) + } }