1#[cfg(feature = "yubi")]
3use elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
4#[cfg(feature = "yubi")]
5use p256::{NistP256, PublicKey};
6#[cfg(feature = "yubi")]
7use signature::Verifier;
8#[cfg(feature = "yubi")]
9use yubihsm::{
10 asymmetric::Algorithm::EcP256, ecdsa::Signer as YubiSigner, object, object::Label, Capability,
11 Client, Connector, Credentials, Domain,
12};
13
14use crate::{
15 neo_clients::public_key_to_address,
16 neo_crypto::{HashableForVec, Secp256r1PublicKey},
17 neo_types::Address,
18 neo_wallets::{WalletError, WalletSigner},
19};
20
21#[cfg(feature = "yubi")]
22impl WalletSigner<YubiSigner<NistP256>> {
23 pub fn connect(
25 connector: Connector,
26 credentials: Credentials,
27 id: object::Id,
28 ) -> Result<Self, WalletError> {
29 let client = Client::open(connector, credentials, true).map_err(|e| {
30 WalletError::YubiHsmError(format!("Failed to open YubiHSM client: {e}"))
31 })?;
32
33 let signer = YubiSigner::create(client, id).map_err(|e| {
34 WalletError::YubiHsmError(format!("Failed to create YubiHSM signer: {e}"))
35 })?;
36
37 Ok(signer.into())
38 }
39
40 pub fn new(
42 connector: Connector,
43 credentials: Credentials,
44 id: object::Id,
45 label: Label,
46 domain: Domain,
47 ) -> Result<Self, WalletError> {
48 let client = Client::open(connector, credentials, true).map_err(|e| {
49 WalletError::YubiHsmError(format!("Failed to open YubiHSM client: {e}"))
50 })?;
51
52 let id = client
53 .generate_asymmetric_key(id, label, domain, Capability::SIGN_ECDSA, EcP256)
54 .map_err(|e| {
55 WalletError::YubiHsmError(format!("Failed to generate asymmetric key: {e}"))
56 })?;
57
58 let signer = YubiSigner::create(client, id).map_err(|e| {
59 WalletError::YubiHsmError(format!("Failed to create YubiHSM signer: {e}"))
60 })?;
61
62 Ok(signer.into())
63 }
64
65 pub fn from_key(
67 connector: Connector,
68 credentials: Credentials,
69 id: object::Id,
70 label: Label,
71 domain: Domain,
72 key: impl Into<Vec<u8>>,
73 ) -> Result<Self, WalletError> {
74 let client = Client::open(connector, credentials, true).map_err(|e| {
75 WalletError::YubiHsmError(format!("Failed to open YubiHSM client: {e}"))
76 })?;
77
78 let id = client
79 .put_asymmetric_key(id, label, domain, Capability::SIGN_ECDSA, EcP256, key)
80 .map_err(|e| WalletError::YubiHsmError(format!("Failed to put asymmetric key: {e}")))?;
81
82 let signer = YubiSigner::create(client, id).map_err(|e| {
83 WalletError::YubiHsmError(format!("Failed to create YubiHSM signer: {e}"))
84 })?;
85
86 Ok(signer.into())
87 }
88
89 }
112
113#[cfg(feature = "yubi")]
114impl From<YubiSigner<NistP256>> for WalletSigner<YubiSigner<NistP256>> {
115 fn from(signer: YubiSigner<NistP256>) -> Self {
116 let public_key = PublicKey::from_encoded_point(signer.public_key());
118 if !bool::from(public_key.is_some()) {
119 eprintln!(
121 "Warning: YubiSigner provided invalid public key, using zero address as fallback"
122 );
123 return Self { signer, address: Address::default(), network: None };
124 }
125
126 let public_key = public_key.unwrap();
127 let public_key = public_key.to_encoded_point(true);
128 let public_key = public_key.as_bytes();
129
130 debug_assert!(public_key[0] == 0x02 || public_key[0] == 0x03);
132
133 let secp_public_key = match Secp256r1PublicKey::from_bytes(&public_key) {
134 Ok(key) => key,
135 Err(_) => {
136 eprintln!("Warning: Failed to convert YubiSigner public key to Secp256r1PublicKey, using zero address as fallback");
137 return Self { signer, address: Address::default(), network: None };
138 },
139 };
140
141 let address = public_key_to_address(&secp_public_key);
142
143 Self { signer, address, network: None }
144 }
145}
146
147#[cfg(test)]
148pub mod tests {
149 use std::str::FromStr;
150
151 use super::*;
152
153 #[cfg(feature = "mock-hsm")]
154 #[tokio::test]
155 async fn from_key() {
156 let key = hex::decode("2d8c44dc2dd2f0bea410e342885379192381e82d855b1b112f9b55544f1e0900")
157 .expect("Should be able to decode valid hex");
158
159 let connector = yubihsm::Connector::mockhsm();
160 let wallet = WalletSigner::from_key(
161 connector,
162 Credentials::default(),
163 0,
164 Label::from_bytes(&[]).expect("Empty label should be valid"),
165 Domain::at(1).expect("Domain 1 should be valid"),
166 key,
167 )
168 .expect("Should be able to create wallet from key");
169
170 let msg = "Some data";
171 let sig = wallet
172 .sign_message(msg.as_bytes())
173 .await
174 .expect("Should be able to sign message");
175
176 let verify_key = p256::ecdsa::VerifyingKey::from_encoded_point(wallet.signer.public_key())
177 .expect("Should be able to create verifying key from public key");
178
179 assert!(verify_key.verify(msg.as_bytes(), &sig).is_ok());
180
181 assert_eq!(
182 wallet.address(),
183 Address::from_str("NPZyWCdSCWghLM7hcxT5kgc7cC2V2RGeHZ")
184 .expect("Should be able to parse valid address")
185 );
186 }
187
188 #[cfg(feature = "mock-hsm")]
189 #[tokio::test]
190 async fn new_key() {
191 let connector = yubihsm::Connector::mockhsm();
192 let wallet = WalletSigner::<YubiSigner<NistP256>>::new(
193 connector,
194 Credentials::default(),
195 0,
196 Label::from_bytes(&[]).expect("Empty label should be valid"),
197 Domain::at(1).expect("Domain 1 should be valid"),
198 )
199 .expect("Should be able to create new wallet");
200
201 let msg = "Some data";
202 let sig = wallet
203 .sign_message(msg.as_bytes())
204 .await
205 .expect("Should be able to sign message");
206
207 let verify_key = p256::ecdsa::VerifyingKey::from_encoded_point(wallet.signer.public_key())
208 .expect("Should be able to create verifying key from public key");
209
210 assert!(verify_key.verify(msg.as_bytes(), &sig).is_ok());
211 }
212
213 #[test]
215 fn test_wallet_signer_creation() {
216 #[cfg(feature = "yubi")]
221 {
222 use std::marker::PhantomData;
223 let _phantom: PhantomData<WalletSigner<YubiSigner<NistP256>>> = PhantomData;
224 }
225
226 let _error = WalletError::YubiHsmError("test".to_string());
228
229 assert!(true);
231 }
232}