ethers_signers/wallet/
yubi.rs

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