alloy_signer_local/
yubi.rs

1//! [YubiHSM2](yubihsm) signer implementation.
2
3use super::LocalSigner;
4use alloy_signer::utils::raw_public_key_to_address;
5use elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
6use k256::{PublicKey, Secp256k1};
7use yubihsm::{
8    asymmetric::Algorithm::EcK256, ecdsa::Signer as YubiSigner, object, object::Label, Capability,
9    Client, Connector, Credentials, Domain,
10};
11
12impl LocalSigner<YubiSigner<Secp256k1>> {
13    /// Connects to a yubi key's ECDSA account at the provided id
14    pub fn connect(connector: Connector, credentials: Credentials, id: object::Id) -> Self {
15        let client = Client::open(connector, credentials, true).unwrap();
16        let signer = YubiSigner::create(client, id).unwrap();
17        signer.into()
18    }
19
20    /// Creates a new random ECDSA keypair on the yubi at the provided id
21    pub fn new(
22        connector: Connector,
23        credentials: Credentials,
24        id: object::Id,
25        label: Label,
26        domain: Domain,
27    ) -> Self {
28        let client = Client::open(connector, credentials, true).unwrap();
29        let id = client
30            .generate_asymmetric_key(id, label, domain, Capability::SIGN_ECDSA, EcK256)
31            .unwrap();
32        let signer = YubiSigner::create(client, id).unwrap();
33        signer.into()
34    }
35
36    /// Uploads the provided keypair on the yubi at the provided id
37    pub fn from_key(
38        connector: Connector,
39        credentials: Credentials,
40        id: object::Id,
41        label: Label,
42        domain: Domain,
43        key: impl Into<Vec<u8>>,
44    ) -> Self {
45        let client = Client::open(connector, credentials, true).unwrap();
46        let id = client
47            .put_asymmetric_key(id, label, domain, Capability::SIGN_ECDSA, EcK256, key)
48            .unwrap();
49        let signer = YubiSigner::create(client, id).unwrap();
50        signer.into()
51    }
52}
53
54impl From<YubiSigner<Secp256k1>> for LocalSigner<YubiSigner<Secp256k1>> {
55    fn from(credential: YubiSigner<Secp256k1>) -> Self {
56        // Uncompress the public key.
57        let pubkey = PublicKey::from_encoded_point(credential.public_key()).unwrap();
58        let pubkey = pubkey.to_encoded_point(false);
59        let bytes = pubkey.as_bytes();
60        debug_assert_eq!(bytes[0], 0x04);
61        let address = raw_public_key_to_address(&bytes[1..]);
62        Self::new_with_credential(credential, address, None)
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use crate::SignerSync;
70    use alloy_primitives::{address, hex};
71
72    #[test]
73    fn from_key() {
74        let key = hex::decode("2d8c44dc2dd2f0bea410e342885379192381e82d855b1b112f9b55544f1e0900")
75            .unwrap();
76
77        let connector = yubihsm::Connector::mockhsm();
78        let signer = LocalSigner::from_key(
79            connector,
80            Credentials::default(),
81            0,
82            Label::from_bytes(&[]).unwrap(),
83            Domain::at(1).unwrap(),
84            key,
85        );
86
87        let msg = "Some data";
88        let sig = signer.sign_message_sync(msg.as_bytes()).unwrap();
89        assert_eq!(sig.recover_address_from_msg(msg).unwrap(), signer.address());
90        assert_eq!(signer.address(), address!("2DE2C386082Cff9b28D62E60983856CE1139eC49"));
91    }
92
93    #[test]
94    fn new_key() {
95        let connector = yubihsm::Connector::mockhsm();
96        let signer = LocalSigner::<YubiSigner<Secp256k1>>::new(
97            connector,
98            Credentials::default(),
99            0,
100            Label::from_bytes(&[]).unwrap(),
101            Domain::at(1).unwrap(),
102        );
103
104        let msg = "Some data";
105        let sig = signer.sign_message_sync(msg.as_bytes()).unwrap();
106        assert_eq!(sig.recover_address_from_msg(msg).unwrap(), signer.address());
107    }
108}