Conflicting Implementations Workaround? abstracting two implementations into one generic

I'm attempting to put together a common interface for cryptographic secret keys using both the Dalek and RustCrypto implementations. Generate, Sign, Verify etc. by using generics to manage typing. The Ed25519 implementation by Dalek has a different signature from the RustCrypto k256/p256/p384 implementations.

my goal is to be able to :

fn main() {
    let key: SecretKey<Curve25519> = SecretKey::new(); //or

    let key: SecretKey<Secp256k1> = SecretKey::new(); //or

    let key: SecretKey<NistP256> = SecretKey::new(); //or

    let key: SecretKey<NistP384> = SecretKey::new();
}

and abstract away the differences behind a simple interface.

this is the implementation:

use std::marker::PhantomData;

use ecdsa::PrimeCurve;
use ed25519_dalek::SecretKey as Ed25519SecretKey;
use elliptic_curve::{ProjectiveArithmetic, SecretKey as EcdsaSecretKey};

use k256::Secp256k1;
use p256::NistP256;
use p384::NistP384;

// this section intended to emulate the behavior of the non-dalek curves
struct Curve25519;

trait Ed25519Curve {}
impl Ed25519Curve for Curve25519 {}

pub trait SecretKeyAdapter {}
impl<T> SecretKeyAdapter for EcdsaSecretKey<T> where T: PrimeCurve + ProjectiveArithmetic {}
impl SecretKeyAdapter for Ed25519SecretKey {}

struct SecretKey<T> {
    pub algorithm: PhantomData<T>,
    pub secret: Box<dyn SecretKeyAdapter>,
}

trait SecretKeyTrait {
    fn new() -> Self;
}

impl<T> SecretKeyTrait for SecretKey<T>
where
    T: Ed25519Curve,
{
    fn new() -> Self {
        let secret = Ed25519SecretKey::generate(&mut rand::rngs::OsRng);

        Self {
            algorithm: PhantomData,
            secret: Box::new(secret),
        }
    }
}

impl<T> SecretKeyTrait for SecretKey<T>
where
    T: PrimeCurve + ProjectiveArithmetic,
{
    fn new() -> Self {
        let secret: EcdsaSecretKey<T> = EcdsaSecretKey::random(rand_core::OsRng);

        Self {
            algorithm: PhantomData,
            secret: Box::new(secret),
        }
    }
}

which gives me a conflicting implementation error.

I totally get /why/ it's giving me the error: it's theoretically possible for a struct to impl /both/ Ed25519Curve /and/ PrimeCurve + ProjectiveArithmetic... sooooo kaboomboom.

How do I work around it? How do I write my trait impl for both sides?

The way traits are supposed to be used is by implementing a trait for a type in order to capture some common behavior that type has with other types. "Inferring arbitrary types and algorithms" doesn't really fit this well, which shows that you are trying to implement the trait at the wrong level.

You should ask yourself, what is the actual common behavior here? The answer is: generating a new, concrete, low-level key. Thus, you should try to formalize that instead of the generation of arbitrary keys. This is easily done by implementing a trait for each of the concrete key types:

pub trait SecretKeyAdapter {
    fn generate() -> Self
    where
        Self: Sized;
}

impl<T> SecretKeyAdapter for EcdsaSecretKey<T> where T: PrimeCurve + ProjectiveArithmetic {
    fn generate() -> Self {
        EcdsaSecretKey::random(rand_core::OsRng)
    }
}

impl SecretKeyAdapter for Ed25519SecretKey {
    fn generate() -> Self {
        Ed25519SecretKey::generate(&mut rand::rngs::OsRng)
    }
}

If you have this set up, you can now create just a single impl for SecretKey:

impl<T> SecretKeyTrait for SecretKey<T>
where
    T: SecretKeyAdapter + 'static,
{
    fn new() -> Self {
        let secret = T::generate();

        Self {
            algorithm: PhantomData,
            secret: Box::new(secret),
        }
    }
}

Playground

1 Like

thanks!.... implement the common behavior before attempting to merge makes sense.

However, this would require I invoke with this syntax:

let key: SecretKey<Ed25519SecretKey> = SecretKey::new(); // instead of 

let key: SecretKey<Curve25519> = SecretKey::new(); //and 

let key: SecretKey<EcdsaSecretKey<Secp256k1>> = SecretKey::new(); // instead of 

let key: SecretKey<Secp256k1> = SecretKey::new();

which I might be able to live with..... tho this is the point I could just declare aliases, i guess.

Well, the thing is, at the type system level, currently there is no way the algorithm can determine the key (nor should it – in general, no such conditions hold between arbitrary types).

If you are sure the algorithm unambiguously determines the key type, you should encode this constraint as an associated type (which requires an additional trait): Playground

trait KeyAlgorithm {
    type Key: SecretKeyAdapter + 'static;
}

impl KeyAlgorithm for Secp256k1 {
    type Key = EcdsaSecretKey<Self>;
}

impl KeyAlgorithm for Curve25519 {
    type Key = Ed25519SecretKey;
}

impl<T> SecretKeyTrait for SecretKey<T>
where
    T: KeyAlgorithm,
{
    fn new() -> Self {
        let secret = T::Key::generate();

        Self {
            algorithm: PhantomData,
            secret: Box::new(secret),
        }
    }
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.