mirror of
https://github.com/corda/corda.git
synced 2025-01-31 00:24:59 +00:00
Deterministic Key Generation for ECDSA and EdDSA (#729)
Deterministic Key Derivation for ECDSA R1/K1 and EdDSA * DKG description and comments * Removing a (confusing) not-required comma in comments. * rename deterministic and generate to derive
This commit is contained in:
parent
56bad3a9b4
commit
ec0e0dd442
@ -31,6 +31,12 @@ import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey
|
|||||||
import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter
|
import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter
|
||||||
import org.bouncycastle.jce.ECNamedCurveTable
|
import org.bouncycastle.jce.ECNamedCurveTable
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
|
import org.bouncycastle.jce.spec.ECParameterSpec
|
||||||
|
import org.bouncycastle.jce.spec.ECPrivateKeySpec
|
||||||
|
import org.bouncycastle.jce.spec.ECPublicKeySpec
|
||||||
|
import org.bouncycastle.math.ec.ECConstants
|
||||||
|
import org.bouncycastle.math.ec.FixedPointCombMultiplier
|
||||||
|
import org.bouncycastle.math.ec.WNafUtil
|
||||||
import org.bouncycastle.operator.ContentSigner
|
import org.bouncycastle.operator.ContentSigner
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder
|
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder
|
||||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||||
@ -50,6 +56,8 @@ import java.security.spec.InvalidKeySpecException
|
|||||||
import java.security.spec.PKCS8EncodedKeySpec
|
import java.security.spec.PKCS8EncodedKeySpec
|
||||||
import java.security.spec.X509EncodedKeySpec
|
import java.security.spec.X509EncodedKeySpec
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import javax.crypto.Mac
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This object controls and provides the available and supported signature schemes for Corda.
|
* This object controls and provides the available and supported signature schemes for Corda.
|
||||||
@ -245,8 +253,7 @@ object Crypto {
|
|||||||
*/
|
*/
|
||||||
@Throws(IllegalArgumentException::class, InvalidKeySpecException::class)
|
@Throws(IllegalArgumentException::class, InvalidKeySpecException::class)
|
||||||
fun decodePrivateKey(signatureScheme: SignatureScheme, encodedKey: ByteArray): PrivateKey {
|
fun decodePrivateKey(signatureScheme: SignatureScheme, encodedKey: ByteArray): PrivateKey {
|
||||||
if (!isSupportedSignatureScheme(signatureScheme))
|
require(isSupportedSignatureScheme(signatureScheme)) { "Unsupported key/algorithm for schemeCodeName: ${signatureScheme.schemeCodeName}" }
|
||||||
throw IllegalArgumentException("Unsupported key/algorithm for schemeCodeName: $signatureScheme.schemeCodeName")
|
|
||||||
try {
|
try {
|
||||||
return KeyFactory.getInstance(signatureScheme.algorithmName, providerMap[signatureScheme.providerName]).generatePrivate(PKCS8EncodedKeySpec(encodedKey))
|
return KeyFactory.getInstance(signatureScheme.algorithmName, providerMap[signatureScheme.providerName]).generatePrivate(PKCS8EncodedKeySpec(encodedKey))
|
||||||
} catch (ikse: InvalidKeySpecException) {
|
} catch (ikse: InvalidKeySpecException) {
|
||||||
@ -301,8 +308,7 @@ object Crypto {
|
|||||||
*/
|
*/
|
||||||
@Throws(IllegalArgumentException::class, InvalidKeySpecException::class)
|
@Throws(IllegalArgumentException::class, InvalidKeySpecException::class)
|
||||||
fun decodePublicKey(signatureScheme: SignatureScheme, encodedKey: ByteArray): PublicKey {
|
fun decodePublicKey(signatureScheme: SignatureScheme, encodedKey: ByteArray): PublicKey {
|
||||||
if (!isSupportedSignatureScheme(signatureScheme))
|
require(isSupportedSignatureScheme(signatureScheme)) { "Unsupported key/algorithm for schemeCodeName: ${signatureScheme.schemeCodeName}" }
|
||||||
throw IllegalArgumentException("Unsupported key/algorithm for schemeCodeName: $signatureScheme.schemeCodeName")
|
|
||||||
try {
|
try {
|
||||||
return KeyFactory.getInstance(signatureScheme.algorithmName, providerMap[signatureScheme.providerName]).generatePublic(X509EncodedKeySpec(encodedKey))
|
return KeyFactory.getInstance(signatureScheme.algorithmName, providerMap[signatureScheme.providerName]).generatePublic(X509EncodedKeySpec(encodedKey))
|
||||||
} catch (ikse: InvalidKeySpecException) {
|
} catch (ikse: InvalidKeySpecException) {
|
||||||
@ -348,8 +354,7 @@ object Crypto {
|
|||||||
*/
|
*/
|
||||||
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
|
@Throws(IllegalArgumentException::class, InvalidKeyException::class, SignatureException::class)
|
||||||
fun doSign(signatureScheme: SignatureScheme, privateKey: PrivateKey, clearData: ByteArray): ByteArray {
|
fun doSign(signatureScheme: SignatureScheme, privateKey: PrivateKey, clearData: ByteArray): ByteArray {
|
||||||
if (!isSupportedSignatureScheme(signatureScheme))
|
require(isSupportedSignatureScheme(signatureScheme)) { "Unsupported key/algorithm for schemeCodeName: ${signatureScheme.schemeCodeName}" }
|
||||||
throw IllegalArgumentException("Unsupported key/algorithm for schemeCodeName: $signatureScheme.schemeCodeName")
|
|
||||||
val signature = Signature.getInstance(signatureScheme.signatureName, providerMap[signatureScheme.providerName])
|
val signature = Signature.getInstance(signatureScheme.signatureName, providerMap[signatureScheme.providerName])
|
||||||
if (clearData.isEmpty()) throw Exception("Signing of an empty array is not permitted!")
|
if (clearData.isEmpty()) throw Exception("Signing of an empty array is not permitted!")
|
||||||
signature.initSign(privateKey)
|
signature.initSign(privateKey)
|
||||||
@ -428,8 +433,7 @@ object Crypto {
|
|||||||
*/
|
*/
|
||||||
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
|
@Throws(InvalidKeyException::class, SignatureException::class, IllegalArgumentException::class)
|
||||||
fun doVerify(signatureScheme: SignatureScheme, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray): Boolean {
|
fun doVerify(signatureScheme: SignatureScheme, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray): Boolean {
|
||||||
if (!isSupportedSignatureScheme(signatureScheme))
|
require(isSupportedSignatureScheme(signatureScheme)) { "Unsupported key/algorithm for schemeCodeName: ${signatureScheme.schemeCodeName}" }
|
||||||
throw IllegalArgumentException("Unsupported key/algorithm for schemeCodeName: $signatureScheme.schemeCodeName")
|
|
||||||
if (signatureData.isEmpty()) throw IllegalArgumentException("Signature data is empty!")
|
if (signatureData.isEmpty()) throw IllegalArgumentException("Signature data is empty!")
|
||||||
if (clearData.isEmpty()) throw IllegalArgumentException("Clear data is empty, nothing to verify!")
|
if (clearData.isEmpty()) throw IllegalArgumentException("Clear data is empty, nothing to verify!")
|
||||||
val verificationResult = isValid(signatureScheme, publicKey, signatureData, clearData)
|
val verificationResult = isValid(signatureScheme, publicKey, signatureData, clearData)
|
||||||
@ -491,8 +495,7 @@ object Crypto {
|
|||||||
*/
|
*/
|
||||||
@Throws(SignatureException::class, IllegalArgumentException::class)
|
@Throws(SignatureException::class, IllegalArgumentException::class)
|
||||||
fun isValid(signatureScheme: SignatureScheme, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray): Boolean {
|
fun isValid(signatureScheme: SignatureScheme, publicKey: PublicKey, signatureData: ByteArray, clearData: ByteArray): Boolean {
|
||||||
if (!isSupportedSignatureScheme(signatureScheme))
|
require(isSupportedSignatureScheme(signatureScheme)) { "Unsupported key/algorithm for schemeCodeName: ${signatureScheme.schemeCodeName}" }
|
||||||
throw IllegalArgumentException("Unsupported key/algorithm for schemeCodeName: $signatureScheme.schemeCodeName")
|
|
||||||
val signature = Signature.getInstance(signatureScheme.signatureName, providerMap[signatureScheme.providerName])
|
val signature = Signature.getInstance(signatureScheme.signatureName, providerMap[signatureScheme.providerName])
|
||||||
signature.initVerify(publicKey)
|
signature.initVerify(publicKey)
|
||||||
signature.update(clearData)
|
signature.update(clearData)
|
||||||
@ -519,8 +522,7 @@ object Crypto {
|
|||||||
@Throws(IllegalArgumentException::class)
|
@Throws(IllegalArgumentException::class)
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun generateKeyPair(signatureScheme: SignatureScheme = DEFAULT_SIGNATURE_SCHEME): KeyPair {
|
fun generateKeyPair(signatureScheme: SignatureScheme = DEFAULT_SIGNATURE_SCHEME): KeyPair {
|
||||||
if (!isSupportedSignatureScheme(signatureScheme))
|
require(isSupportedSignatureScheme(signatureScheme)) { "Unsupported key/algorithm for schemeCodeName: ${signatureScheme.schemeCodeName}" }
|
||||||
throw IllegalArgumentException("Unsupported key/algorithm for schemeCodeName: $signatureScheme.schemeCodeName")
|
|
||||||
val keyPairGenerator = KeyPairGenerator.getInstance(signatureScheme.algorithmName, providerMap[signatureScheme.providerName])
|
val keyPairGenerator = KeyPairGenerator.getInstance(signatureScheme.algorithmName, providerMap[signatureScheme.providerName])
|
||||||
if (signatureScheme.algSpec != null)
|
if (signatureScheme.algSpec != null)
|
||||||
keyPairGenerator.initialize(signatureScheme.algSpec, newSecureRandom())
|
keyPairGenerator.initialize(signatureScheme.algSpec, newSecureRandom())
|
||||||
@ -529,6 +531,107 @@ object Crypto {
|
|||||||
return keyPairGenerator.generateKeyPair()
|
return keyPairGenerator.generateKeyPair()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deterministically generate/derive a [KeyPair] using an existing private key and a seed as inputs.
|
||||||
|
* This operation is currently supported for ECDSA secp256r1 (NIST P-256), ECDSA secp256k1 and EdDSA ed25519.
|
||||||
|
*
|
||||||
|
* Similarly to BIP32, the implemented algorithm uses an HMAC function based on SHA512 and it is actually
|
||||||
|
* a variation of the private-parent-key -> private-child-key hardened key generation of BIP32.
|
||||||
|
*
|
||||||
|
* Unlike BIP32, where both private and public keys are extended to prevent deterministically
|
||||||
|
* generated child keys from depending solely on the key itself, current method uses normal elliptic curve keys
|
||||||
|
* without a chain-code and the generated key relies solely on the security of the private key.
|
||||||
|
*
|
||||||
|
* Although without a chain-code we lose the aforementioned property of not depending solely on the key,
|
||||||
|
* it should be mentioned that the cryptographic strength of the HMAC depends upon the size of the secret key.
|
||||||
|
* @see <a href="https://en.wikipedia.org/wiki/Hash-based_message_authentication_code#Security">HMAC Security</a>
|
||||||
|
* Thus, as long as the master key is kept secret and has enough entropy (~256 bits for EC-schemes), the system
|
||||||
|
* is considered secure.
|
||||||
|
*
|
||||||
|
* It is also a fact that if HMAC is used as PRF and/or MAC but not as checksum function, the function is still
|
||||||
|
* secure even if the underlying hash function is not collision resistant (e.g. if we used MD5).
|
||||||
|
* In practice, for our DKG purposes (thus PRF), a collision would not necessarily reveal the master HMAC key,
|
||||||
|
* because multiple inputs can produce the same hash output.
|
||||||
|
*
|
||||||
|
* All in all, this algorithm can be used with a counter as seed, however it is suggested that the output does
|
||||||
|
* not solely depend on the key, i.e. a secret salt per user or a random nonce per transaction could serve this role.
|
||||||
|
*
|
||||||
|
* @param signatureScheme the [SignatureScheme] of the private key input.
|
||||||
|
* @param privateKey the [PrivateKey] that will be used as key to the HMAC-ed DKG function.
|
||||||
|
* @param seed an extra seed that will be used as value to the underlying HMAC.
|
||||||
|
* @return a new deterministically generated [KeyPair].
|
||||||
|
* @throws IllegalArgumentException if the requested signature scheme is not supported.
|
||||||
|
* @throws UnsupportedOperationException if deterministic key generation is not supported for this particular scheme.
|
||||||
|
*/
|
||||||
|
fun deterministicKeyPair(signatureScheme: SignatureScheme, privateKey: PrivateKey, seed: ByteArray): KeyPair {
|
||||||
|
require(isSupportedSignatureScheme(signatureScheme)) { "Unsupported key/algorithm for schemeCodeName: ${signatureScheme.schemeCodeName}" }
|
||||||
|
when (signatureScheme) {
|
||||||
|
ECDSA_SECP256R1_SHA256, ECDSA_SECP256K1_SHA256 -> return deriveKeyPairECDSA(signatureScheme.algSpec as ECParameterSpec, privateKey, seed)
|
||||||
|
EDDSA_ED25519_SHA512 -> return deriveKeyPairEdDSA(privateKey, seed)
|
||||||
|
}
|
||||||
|
throw UnsupportedOperationException("Although supported for signing, deterministic key generation is not currently implemented for ${signatureScheme.schemeCodeName}")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deterministically generate/derive a [KeyPair] using an existing private key and a seed as inputs.
|
||||||
|
* Use this method if the [SignatureScheme] of the private key input is not known.
|
||||||
|
* @param privateKey the [PrivateKey] that will be used as key to the HMAC-ed DKG function.
|
||||||
|
* @param seed an extra seed that will be used as value to the underlying HMAC.
|
||||||
|
* @return a new deterministically generated [KeyPair].
|
||||||
|
* @throws IllegalArgumentException if the requested signature scheme is not supported.
|
||||||
|
* @throws UnsupportedOperationException if deterministic key generation is not supported for this particular scheme.
|
||||||
|
*/
|
||||||
|
fun deterministicKeyPair(privateKey: PrivateKey, seed: ByteArray): KeyPair {
|
||||||
|
return deterministicKeyPair(findSignatureScheme(privateKey), privateKey, seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given the domain parameters, this routine deterministically generates an ECDSA key pair
|
||||||
|
// in accordance with X9.62 section 5.2.1 pages 26, 27.
|
||||||
|
private fun deriveKeyPairECDSA(parameterSpec: ECParameterSpec, privateKey: PrivateKey, seed: ByteArray): KeyPair {
|
||||||
|
// Compute hmac(privateKey, seed).
|
||||||
|
val mac = Mac.getInstance("HmacSHA512", providerMap[BouncyCastleProvider.PROVIDER_NAME])
|
||||||
|
val key = SecretKeySpec((privateKey as BCECPrivateKey).d.toByteArray(), "HmacSHA512")
|
||||||
|
mac.init(key)
|
||||||
|
val macBytes = mac.doFinal(seed)
|
||||||
|
|
||||||
|
// Calculate value d for private key.
|
||||||
|
val deterministicD = BigInteger(1, macBytes).mod(parameterSpec.n)
|
||||||
|
// Key generation checks follow the BC logic found in
|
||||||
|
// https://github.com/bcgit/bc-java/blob/master/core/src/main/java/org/bouncycastle/crypto/generators/ECKeyPairGenerator.java
|
||||||
|
if (deterministicD < ECConstants.TWO
|
||||||
|
|| WNafUtil.getNafWeight(deterministicD) < parameterSpec.n.bitLength().ushr(2)) {
|
||||||
|
throw InvalidKeyException("Cannot generate key for this seed, please use another seed value")
|
||||||
|
}
|
||||||
|
val privateKeySpec = ECPrivateKeySpec(deterministicD, parameterSpec)
|
||||||
|
val privateKeyD = BCECPrivateKey(privateKey.algorithm, privateKeySpec, BouncyCastleProvider.CONFIGURATION)
|
||||||
|
|
||||||
|
// Compute the public key by scalar multiplication.
|
||||||
|
val pointQ = FixedPointCombMultiplier().multiply(parameterSpec.g, deterministicD)
|
||||||
|
// This is unlikely to happen, but we should check for point at infinity.
|
||||||
|
if (pointQ.isInfinity)
|
||||||
|
throw InvalidKeyException("Cannot generate key for this seed, please use another seed value")
|
||||||
|
val publicKeySpec = ECPublicKeySpec(pointQ, parameterSpec)
|
||||||
|
val publicKeyD = BCECPublicKey(privateKey.algorithm, publicKeySpec, BouncyCastleProvider.CONFIGURATION)
|
||||||
|
|
||||||
|
return KeyPair(publicKeyD, privateKeyD)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deterministically generate an EdDSA key.
|
||||||
|
private fun deriveKeyPairEdDSA(privateKey: PrivateKey, seed: ByteArray): KeyPair {
|
||||||
|
// Compute HMAC(privateKey, seed).
|
||||||
|
val mac = Mac.getInstance("HmacSHA512", providerMap[BouncyCastleProvider.PROVIDER_NAME])
|
||||||
|
val key = SecretKeySpec((privateKey as EdDSAPrivateKey).geta(), "HmacSHA512")
|
||||||
|
mac.init(key)
|
||||||
|
val macBytes = mac.doFinal(seed)
|
||||||
|
|
||||||
|
// Calculate key pair.
|
||||||
|
val params = EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec
|
||||||
|
val bytes = macBytes.copyOf(params.curve.field.getb() / 8) // Need to pad the entropy to the valid seed length.
|
||||||
|
val privateKeyD = EdDSAPrivateKeySpec(bytes, params)
|
||||||
|
val publicKeyD = EdDSAPublicKeySpec(privateKeyD.a, params)
|
||||||
|
return KeyPair(EdDSAPublicKey(publicKeyD), EdDSAPrivateKey(privateKeyD))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a key pair derived from the given [BigInteger] entropy. This is useful for unit tests
|
* Returns a key pair derived from the given [BigInteger] entropy. This is useful for unit tests
|
||||||
* and other cases where you want hard-coded private keys.
|
* and other cases where you want hard-coded private keys.
|
||||||
@ -538,11 +641,11 @@ object Crypto {
|
|||||||
* @return a new [KeyPair] from an entropy input.
|
* @return a new [KeyPair] from an entropy input.
|
||||||
* @throws IllegalArgumentException if the requested signature scheme is not supported for KeyPair generation using an entropy input.
|
* @throws IllegalArgumentException if the requested signature scheme is not supported for KeyPair generation using an entropy input.
|
||||||
*/
|
*/
|
||||||
fun generateKeyPairFromEntropy(signatureScheme: SignatureScheme, entropy: BigInteger): KeyPair {
|
fun deriveKeyPairFromEntropy(signatureScheme: SignatureScheme, entropy: BigInteger): KeyPair {
|
||||||
when (signatureScheme) {
|
when (signatureScheme) {
|
||||||
EDDSA_ED25519_SHA512 -> return generateEdDSAKeyPairFromEntropy(entropy)
|
EDDSA_ED25519_SHA512 -> return deriveEdDSAKeyPairFromEntropy(entropy)
|
||||||
}
|
}
|
||||||
throw IllegalArgumentException("Unsupported signature scheme for fixed entropy-based key pair generation: $signatureScheme.schemeCodeName")
|
throw IllegalArgumentException("Unsupported signature scheme for fixed entropy-based key pair generation: ${signatureScheme.schemeCodeName}")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -550,12 +653,12 @@ object Crypto {
|
|||||||
* @param entropy a [BigInteger] value.
|
* @param entropy a [BigInteger] value.
|
||||||
* @return a new [KeyPair] from an entropy input.
|
* @return a new [KeyPair] from an entropy input.
|
||||||
*/
|
*/
|
||||||
fun generateKeyPairFromEntropy(entropy: BigInteger): KeyPair = generateKeyPairFromEntropy(DEFAULT_SIGNATURE_SCHEME, entropy)
|
fun deriveKeyPairFromEntropy(entropy: BigInteger): KeyPair = deriveKeyPairFromEntropy(DEFAULT_SIGNATURE_SCHEME, entropy)
|
||||||
|
|
||||||
// custom key pair generator from entropy.
|
// custom key pair generator from entropy.
|
||||||
private fun generateEdDSAKeyPairFromEntropy(entropy: BigInteger): KeyPair {
|
private fun deriveEdDSAKeyPairFromEntropy(entropy: BigInteger): KeyPair {
|
||||||
val params = EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec
|
val params = EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec
|
||||||
val bytes = entropy.toByteArray().copyOf(params.curve.field.getb() / 8) // need to pad the entropy to the valid seed length.
|
val bytes = entropy.toByteArray().copyOf(params.curve.field.getb() / 8) // Need to pad the entropy to the valid seed length.
|
||||||
val priv = EdDSAPrivateKeySpec(bytes, params)
|
val priv = EdDSAPrivateKeySpec(bytes, params)
|
||||||
val pub = EdDSAPublicKeySpec(priv.a, params)
|
val pub = EdDSAPublicKeySpec(priv.a, params)
|
||||||
return KeyPair(EdDSAPublicKey(pub), EdDSAPrivateKey(priv))
|
return KeyPair(EdDSAPublicKey(pub), EdDSAPrivateKey(priv))
|
||||||
@ -666,8 +769,7 @@ object Crypto {
|
|||||||
*/
|
*/
|
||||||
@Throws(IllegalArgumentException::class)
|
@Throws(IllegalArgumentException::class)
|
||||||
fun publicKeyOnCurve(signatureScheme: SignatureScheme, publicKey: PublicKey): Boolean {
|
fun publicKeyOnCurve(signatureScheme: SignatureScheme, publicKey: PublicKey): Boolean {
|
||||||
if (!isSupportedSignatureScheme(signatureScheme))
|
require(isSupportedSignatureScheme(signatureScheme)) { "Unsupported key/algorithm for schemeCodeName: ${signatureScheme.schemeCodeName}" }
|
||||||
throw IllegalArgumentException("Unsupported signature scheme: $signatureScheme.schemeCodeName")
|
|
||||||
when (publicKey) {
|
when (publicKey) {
|
||||||
is BCECPublicKey -> return (publicKey.parameters == signatureScheme.algSpec && !publicKey.q.isInfinity && publicKey.q.isValid)
|
is BCECPublicKey -> return (publicKey.parameters == signatureScheme.algSpec && !publicKey.q.isInfinity && publicKey.q.isValid)
|
||||||
is EdDSAPublicKey -> return (publicKey.params == signatureScheme.algSpec && !isEdDSAPointAtInfinity(publicKey) && publicKey.a.isOnCurve)
|
is EdDSAPublicKey -> return (publicKey.params == signatureScheme.algSpec && !isEdDSAPointAtInfinity(publicKey) && publicKey.a.isOnCurve)
|
||||||
|
@ -144,7 +144,7 @@ fun generateKeyPair(): KeyPair = Crypto.generateKeyPair()
|
|||||||
* you want hard-coded private keys.
|
* you want hard-coded private keys.
|
||||||
* This currently works for the default signature scheme EdDSA ed25519 only.
|
* This currently works for the default signature scheme EdDSA ed25519 only.
|
||||||
*/
|
*/
|
||||||
fun entropyToKeyPair(entropy: BigInteger): KeyPair = Crypto.generateKeyPairFromEntropy(entropy)
|
fun entropyToKeyPair(entropy: BigInteger): KeyPair = Crypto.deriveKeyPairFromEntropy(entropy)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function for signing.
|
* Helper function for signing.
|
||||||
|
@ -2,6 +2,7 @@ package net.corda.core.crypto
|
|||||||
|
|
||||||
import com.google.common.collect.Sets
|
import com.google.common.collect.Sets
|
||||||
import net.i2p.crypto.eddsa.EdDSAKey
|
import net.i2p.crypto.eddsa.EdDSAKey
|
||||||
|
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
||||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||||
import net.i2p.crypto.eddsa.math.GroupElement
|
import net.i2p.crypto.eddsa.math.GroupElement
|
||||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec
|
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec
|
||||||
@ -9,6 +10,7 @@ import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
|
|||||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
|
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
|
||||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
|
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
|
||||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
||||||
|
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey
|
||||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey
|
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey
|
||||||
import org.bouncycastle.jce.ECNamedCurveTable
|
import org.bouncycastle.jce.ECNamedCurveTable
|
||||||
import org.bouncycastle.jce.interfaces.ECKey
|
import org.bouncycastle.jce.interfaces.ECKey
|
||||||
@ -640,9 +642,9 @@ class CryptoUtilsTest {
|
|||||||
val keyPairEdDSA = Crypto.generateKeyPair(Crypto.EDDSA_ED25519_SHA512)
|
val keyPairEdDSA = Crypto.generateKeyPair(Crypto.EDDSA_ED25519_SHA512)
|
||||||
val pubEdDSA = keyPairEdDSA.public
|
val pubEdDSA = keyPairEdDSA.public
|
||||||
assertTrue(Crypto.publicKeyOnCurve(Crypto.EDDSA_ED25519_SHA512, pubEdDSA))
|
assertTrue(Crypto.publicKeyOnCurve(Crypto.EDDSA_ED25519_SHA512, pubEdDSA))
|
||||||
// use R1 curve for check.
|
// Use R1 curve for check.
|
||||||
assertFalse(Crypto.publicKeyOnCurve(Crypto.ECDSA_SECP256R1_SHA256, pubEdDSA))
|
assertFalse(Crypto.publicKeyOnCurve(Crypto.ECDSA_SECP256R1_SHA256, pubEdDSA))
|
||||||
// check for point at infinity.
|
// Check for point at infinity.
|
||||||
val pubKeySpec = EdDSAPublicKeySpec((Crypto.EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec).curve.getZero(GroupElement.Representation.P3), Crypto.EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec)
|
val pubKeySpec = EdDSAPublicKeySpec((Crypto.EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec).curve.getZero(GroupElement.Representation.P3), Crypto.EDDSA_ED25519_SHA512.algSpec as EdDSANamedCurveSpec)
|
||||||
assertFalse(Crypto.publicKeyOnCurve(Crypto.EDDSA_ED25519_SHA512, EdDSAPublicKey(pubKeySpec)))
|
assertFalse(Crypto.publicKeyOnCurve(Crypto.EDDSA_ED25519_SHA512, EdDSAPublicKey(pubKeySpec)))
|
||||||
}
|
}
|
||||||
@ -652,8 +654,131 @@ class CryptoUtilsTest {
|
|||||||
val keyGen = KeyPairGenerator.getInstance("EC") // sun.security.ec.ECPublicKeyImpl
|
val keyGen = KeyPairGenerator.getInstance("EC") // sun.security.ec.ECPublicKeyImpl
|
||||||
keyGen.initialize(256, newSecureRandom())
|
keyGen.initialize(256, newSecureRandom())
|
||||||
val pairSun = keyGen.generateKeyPair()
|
val pairSun = keyGen.generateKeyPair()
|
||||||
val pubSun = pairSun.getPublic()
|
val pubSun = pairSun.public
|
||||||
// should fail as pubSun is not a BCECPublicKey.
|
// Should fail as pubSun is not a BCECPublicKey.
|
||||||
Crypto.publicKeyOnCurve(Crypto.ECDSA_SECP256R1_SHA256, pubSun)
|
Crypto.publicKeyOnCurve(Crypto.ECDSA_SECP256R1_SHA256, pubSun)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ECDSA secp256R1 deterministic key generation`() {
|
||||||
|
val (priv, pub) = Crypto.generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
|
||||||
|
val (dpriv, dpub) = Crypto.deterministicKeyPair(priv, "seed-1".toByteArray())
|
||||||
|
|
||||||
|
// Check scheme.
|
||||||
|
assertEquals(priv.algorithm, dpriv.algorithm)
|
||||||
|
assertEquals(pub.algorithm, dpub.algorithm)
|
||||||
|
assertTrue(dpriv is BCECPrivateKey)
|
||||||
|
assertTrue(dpub is BCECPublicKey)
|
||||||
|
assertEquals((dpriv as ECKey).parameters, ECNamedCurveTable.getParameterSpec("secp256r1"))
|
||||||
|
assertEquals((dpub as ECKey).parameters, ECNamedCurveTable.getParameterSpec("secp256r1"))
|
||||||
|
assertEquals(Crypto.findSignatureScheme(dpriv), Crypto.ECDSA_SECP256R1_SHA256)
|
||||||
|
assertEquals(Crypto.findSignatureScheme(dpub), Crypto.ECDSA_SECP256R1_SHA256)
|
||||||
|
|
||||||
|
// Validate public key.
|
||||||
|
assertTrue(Crypto.publicKeyOnCurve(Crypto.ECDSA_SECP256R1_SHA256, dpub))
|
||||||
|
|
||||||
|
// Try to sign/verify.
|
||||||
|
val signedData = Crypto.doSign(dpriv, testBytes)
|
||||||
|
val verification = Crypto.doVerify(dpub, signedData, testBytes)
|
||||||
|
assertTrue(verification)
|
||||||
|
|
||||||
|
// Check it is a new keyPair.
|
||||||
|
assertNotEquals(priv, dpriv)
|
||||||
|
assertNotEquals(pub, dpub)
|
||||||
|
|
||||||
|
// A new keyPair is always generated per different seed.
|
||||||
|
val (dpriv2, dpub2) = Crypto.deterministicKeyPair(priv, "seed-2".toByteArray())
|
||||||
|
assertNotEquals(dpriv, dpriv2)
|
||||||
|
assertNotEquals(dpub, dpub2)
|
||||||
|
|
||||||
|
// Check if the same input always produces the same output (i.e. deterministically generated).
|
||||||
|
val (dpriv_1, dpub_1) = Crypto.deterministicKeyPair(priv, "seed-1".toByteArray())
|
||||||
|
assertEquals(dpriv, dpriv_1)
|
||||||
|
assertEquals(dpub, dpub_1)
|
||||||
|
val (dpriv_2, dpub_2) = Crypto.deterministicKeyPair(priv, "seed-2".toByteArray())
|
||||||
|
assertEquals(dpriv2, dpriv_2)
|
||||||
|
assertEquals(dpub2, dpub_2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ECDSA secp256K1 deterministic key generation`() {
|
||||||
|
val (priv, pub) = Crypto.generateKeyPair(Crypto.ECDSA_SECP256K1_SHA256)
|
||||||
|
val (dpriv, dpub) = Crypto.deterministicKeyPair(priv, "seed-1".toByteArray())
|
||||||
|
|
||||||
|
// Check scheme.
|
||||||
|
assertEquals(priv.algorithm, dpriv.algorithm)
|
||||||
|
assertEquals(pub.algorithm, dpub.algorithm)
|
||||||
|
assertTrue(dpriv is BCECPrivateKey)
|
||||||
|
assertTrue(dpub is BCECPublicKey)
|
||||||
|
assertEquals((dpriv as ECKey).parameters, ECNamedCurveTable.getParameterSpec("secp256k1"))
|
||||||
|
assertEquals((dpub as ECKey).parameters, ECNamedCurveTable.getParameterSpec("secp256k1"))
|
||||||
|
assertEquals(Crypto.findSignatureScheme(dpriv), Crypto.ECDSA_SECP256K1_SHA256)
|
||||||
|
assertEquals(Crypto.findSignatureScheme(dpub), Crypto.ECDSA_SECP256K1_SHA256)
|
||||||
|
|
||||||
|
// Validate public key.
|
||||||
|
assertTrue(Crypto.publicKeyOnCurve(Crypto.ECDSA_SECP256K1_SHA256, dpub))
|
||||||
|
|
||||||
|
// Try to sign/verify.
|
||||||
|
val signedData = Crypto.doSign(dpriv, testBytes)
|
||||||
|
val verification = Crypto.doVerify(dpub, signedData, testBytes)
|
||||||
|
assertTrue(verification)
|
||||||
|
|
||||||
|
// check it is a new keyPair.
|
||||||
|
assertNotEquals(priv, dpriv)
|
||||||
|
assertNotEquals(pub, dpub)
|
||||||
|
|
||||||
|
// A new keyPair is always generated per different seed.
|
||||||
|
val (dpriv2, dpub2) = Crypto.deterministicKeyPair(priv, "seed-2".toByteArray())
|
||||||
|
assertNotEquals(dpriv, dpriv2)
|
||||||
|
assertNotEquals(dpub, dpub2)
|
||||||
|
|
||||||
|
// Check if the same input always produces the same output (i.e. deterministically generated).
|
||||||
|
val (dpriv_1, dpub_1) = Crypto.deterministicKeyPair(priv, "seed-1".toByteArray())
|
||||||
|
assertEquals(dpriv, dpriv_1)
|
||||||
|
assertEquals(dpub, dpub_1)
|
||||||
|
val (dpriv_2, dpub_2) = Crypto.deterministicKeyPair(priv, "seed-2".toByteArray())
|
||||||
|
assertEquals(dpriv2, dpriv_2)
|
||||||
|
assertEquals(dpub2, dpub_2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `EdDSA ed25519 deterministic key generation`() {
|
||||||
|
val (priv, pub) = Crypto.generateKeyPair(Crypto.EDDSA_ED25519_SHA512)
|
||||||
|
val (dpriv, dpub) = Crypto.deterministicKeyPair(priv, "seed-1".toByteArray())
|
||||||
|
|
||||||
|
// Check scheme.
|
||||||
|
assertEquals(priv.algorithm, dpriv.algorithm)
|
||||||
|
assertEquals(pub.algorithm, dpub.algorithm)
|
||||||
|
assertTrue(dpriv is EdDSAPrivateKey)
|
||||||
|
assertTrue(dpub is EdDSAPublicKey)
|
||||||
|
assertEquals((dpriv as EdDSAKey).params, EdDSANamedCurveTable.getByName("ED25519"))
|
||||||
|
assertEquals((dpub as EdDSAKey).params, EdDSANamedCurveTable.getByName("ED25519"))
|
||||||
|
assertEquals(Crypto.findSignatureScheme(dpriv), Crypto.EDDSA_ED25519_SHA512)
|
||||||
|
assertEquals(Crypto.findSignatureScheme(dpub), Crypto.EDDSA_ED25519_SHA512)
|
||||||
|
|
||||||
|
// Validate public key.
|
||||||
|
assertTrue(Crypto.publicKeyOnCurve(Crypto.EDDSA_ED25519_SHA512, dpub))
|
||||||
|
|
||||||
|
// Try to sign/verify.
|
||||||
|
val signedData = Crypto.doSign(dpriv, testBytes)
|
||||||
|
val verification = Crypto.doVerify(dpub, signedData, testBytes)
|
||||||
|
assertTrue(verification)
|
||||||
|
|
||||||
|
// Check it is a new keyPair.
|
||||||
|
assertNotEquals(priv, dpriv)
|
||||||
|
assertNotEquals(pub, dpub)
|
||||||
|
|
||||||
|
// A new keyPair is always generated per different seed.
|
||||||
|
val (dpriv2, dpub2) = Crypto.deterministicKeyPair(priv, "seed-2".toByteArray())
|
||||||
|
assertNotEquals(dpriv, dpriv2)
|
||||||
|
assertNotEquals(dpub, dpub2)
|
||||||
|
|
||||||
|
// Check if the same input always produces the same output (i.e. deterministically generated).
|
||||||
|
val (dpriv_1, dpub_1) = Crypto.deterministicKeyPair(priv, "seed-1".toByteArray())
|
||||||
|
assertEquals(dpriv, dpriv_1)
|
||||||
|
assertEquals(dpub, dpub_1)
|
||||||
|
val (dpriv_2, dpub_2) = Crypto.deterministicKeyPair(priv, "seed-2".toByteArray())
|
||||||
|
assertEquals(dpriv2, dpriv_2)
|
||||||
|
assertEquals(dpub2, dpub_2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user