boltlabs-inc / tss-ecdsa

An implementation of a threshold ECDSA signature scheme
Other
12 stars 5 forks source link

Use types instead of enums for our `LocalStorage` implementation. #181

Closed gatoWololo closed 1 year ago

gatoWololo commented 1 year ago

Our LocalStorage has a few shortcomings we would like to fix:

There is a couple of problems with this approach:

Instead, here is a sketch implementation for a new storage type:

/// A type implementing `TypeTag` can be used to store and retrieve values of type `<T as TypeTag>::Value`. (Associated types are cool!)
trait TypeTag: 'static {
    type Value;
}

/// One storage to rule them all. We store dynamic types, indexing them based on a [`TypeId`](https://doc.rust-lang.org/stable/std/any/struct.TypeId.html). A compiler built-in type to uniquely identify types.
struct GenericStorage {
    storage: HashMap<(ParticipantIdentifier, Identifier, TypeId), Box<dyn Any>>,
}

impl GenericStorage {
    /// Store arbitrary types specifying them via a `TypeTag`. Notice the `TypeId` is an implementation detail hidden from user.
    fn store<T: TypeTag>(
        &mut self,
        p: ParticipantIdentifier,
        sub_protocol: Identifier,
        v: T::Value,
    ) {
        self.storage
            .insert((p, sub_protocol, TypeId::of::<T>()), Box::new(v));
    }

    /// Retrieve a value by specifying a `TypeTag`. The compiler knows the expected value, and returns it with no conversion necessary by the user.
    fn retrieve<T: TypeTag>(
        &mut self,
        p: ParticipantIdentifier,
        sub_protocol: Identifier,
    ) -> Option<T::Value> {
        self.storage
            .remove(&(p, sub_protocol, TypeId::of::<T>()))
            .map(|any| *any.downcast::<T::Value>().unwrap())
    }
}

/// Example of a storage for key generation. Just wraps our generic storage.
struct KeyGenStorage(GenericStorage);

/// We can turn the enum variants of `StorableType` into proper "type tags".
struct KeygenReady;
struct KeygenSchnorrPrecom;

/// Some stored values, like "key gen ready" do no have actual data associated with them. Use unit type!
impl TypeTag for KeygenReady {
    type Value = ();
}

/// The KeygenSchnorrPrecom tag always stores/retrieves a `PiSchPrecommit`
impl TypeTag for KeygenSchnorrPrecom {
    type Value = PiSchPrecommit;
}

fn example_usage(storage: KeyGenStorage, p: ParticipantIdentifier, sub_protocol: Identifier) {
    // Same function returns different types based on specified type tag.
    let v: PiSchPrecommit = storage.retrieve::<KeygenSchnorrPrecom>::(p, sub_protocol).unwrap();
    let v2: () = storage.retrieve::<KeygenReady>::(p, sub_protocol).unwrap();
}

Anyways. I got carried away having fun with this.

Closing Criteria

amaloz commented 1 year ago

One thought: if we are planning to split out Storage into a trait in the future, we might want to hide T::Value from the caller (since the implementer of the Storage trait won't know the internal details, right?). So should we still use Vec<u8> as the underlying storage in the HashMap instead of Box<dyn Any>?

gatoWololo commented 1 year ago

I was thinking that the design and implementation here would only be for local storage. As the main storage will likely have to look different and have different requirements:

amaloz commented 1 year ago

I was thinking that the design and implementation here would only be for local storage.

Ah, that makes sense!