ic_agent/identity/
secp256k1.rs1use crate::{agent::EnvelopeContent, export::Principal, Identity, Signature};
2
3#[cfg(feature = "pem")]
4use crate::identity::error::PemError;
5
6use k256::{
7 ecdsa::{self, signature::Signer, SigningKey, VerifyingKey},
8 pkcs8::{Document, EncodePublicKey},
9 SecretKey,
10};
11#[cfg(feature = "pem")]
12use std::path::Path;
13
14use super::Delegation;
15
16#[derive(Clone, Debug)]
20pub struct Secp256k1Identity {
21 private_key: SigningKey,
22 _public_key: VerifyingKey,
23 der_encoded_public_key: Document,
24}
25
26impl Secp256k1Identity {
27 #[cfg(feature = "pem")]
29 pub fn from_pem_file<P: AsRef<Path>>(file_path: P) -> Result<Self, PemError> {
30 Self::from_pem(std::fs::read(file_path)?)
31 }
32
33 #[cfg(feature = "pem")]
37 pub fn from_pem<B: AsRef<[u8]>>(pem_contents: B) -> Result<Self, PemError> {
38 use pkcs8::{AssociatedOid, PrivateKeyInfo};
39 use sec1::{pem::PemLabel, EcPrivateKey};
40
41 const EC_PARAMETERS: &str = "EC PARAMETERS";
42 const SECP256K1: &[u8] = b"\x06\x05\x2b\x81\x04\x00\x0a";
43
44 let contents = pem_contents.as_ref();
45
46 for pem in pem::parse_many(contents)? {
47 if pem.tag() == EC_PARAMETERS && pem.contents() != SECP256K1 {
48 return Err(PemError::UnsupportedKeyCurve(
49 "secp256k1".to_string(),
50 pem.contents().to_vec(),
51 ));
52 }
53
54 if pem.tag() == EcPrivateKey::PEM_LABEL {
55 let private_key = SecretKey::from_sec1_der(pem.contents())
57 .map_err(|_| pkcs8::Error::KeyMalformed)?;
58 return Ok(Self::from_private_key(private_key));
59 }
60
61 if pem.tag() == PrivateKeyInfo::PEM_LABEL {
62 let key_bytes = super::parse_ec_pkcs8_key_bytes(
64 pem.contents(),
65 k256::Secp256k1::OID,
66 "secp256k1",
67 )?;
68 let private_key =
69 SecretKey::from_sec1_der(&key_bytes).map_err(|_| pkcs8::Error::KeyMalformed)?;
70 return Ok(Self::from_private_key(private_key));
71 }
72 }
73 Err(pem::PemError::MissingData.into())
74 }
75
76 pub fn from_private_key(private_key: SecretKey) -> Self {
78 let public_key = private_key.public_key();
79 let der_encoded_public_key = public_key
80 .to_public_key_der()
81 .expect("Cannot DER encode secp256k1 public key.");
82 Self {
83 private_key: private_key.into(),
84 _public_key: public_key.into(),
85 der_encoded_public_key,
86 }
87 }
88}
89
90impl Identity for Secp256k1Identity {
91 fn sender(&self) -> Result<Principal, String> {
92 Ok(Principal::self_authenticating(
93 self.der_encoded_public_key.as_ref(),
94 ))
95 }
96
97 fn public_key(&self) -> Option<Vec<u8>> {
98 Some(self.der_encoded_public_key.as_ref().to_vec())
99 }
100
101 fn sign(&self, content: &EnvelopeContent) -> Result<Signature, String> {
102 self.sign_arbitrary(&content.to_request_id().signable())
103 }
104
105 fn sign_delegation(&self, content: &Delegation) -> Result<Signature, String> {
106 self.sign_arbitrary(&content.signable())
107 }
108
109 fn sign_arbitrary(&self, content: &[u8]) -> Result<Signature, String> {
110 let ecdsa_sig: ecdsa::Signature = self
111 .private_key
112 .try_sign(content)
113 .map_err(|err| format!("Cannot create secp256k1 signature: {err}"))?;
114 let r = ecdsa_sig.r().as_ref().to_bytes();
115 let s = ecdsa_sig.s().as_ref().to_bytes();
116 let mut bytes = [0u8; 64];
117 if r.len() > 32 || s.len() > 32 {
118 return Err("Cannot create secp256k1 signature: malformed signature.".to_string());
119 }
120 bytes[(32 - r.len())..32].clone_from_slice(&r);
121 bytes[32 + (32 - s.len())..].clone_from_slice(&s);
122 let signature = Some(bytes.to_vec());
123 let public_key = self.public_key();
124 Ok(Signature {
125 public_key,
126 signature,
127 delegations: None,
128 })
129 }
130}
131
132#[cfg(feature = "pem")]
133#[cfg(test)]
134mod test {
135 use super::*;
136 use candid::Encode;
137 use k256::{
138 ecdsa::{signature::Verifier, Signature},
139 elliptic_curve::PrimeField,
140 Scalar,
141 };
142
143 const WRONG_CURVE_IDENTITY_FILE: &str = "-----BEGIN EC PARAMETERS-----
148BgUrgQQAHg==
149-----END EC PARAMETERS-----
150-----BEGIN EC PRIVATE KEY-----
151MFACAQEEFI9cF6zXxMKhtjn1gBD7AHPbzehfoAcGBSuBBAAeoSwDKgAEh5NXszgR
152oGSXVWaGxcQhQWlFG4pbnOG+93xXzfRD7eKWOdmun2bKxQ==
153-----END EC PRIVATE KEY-----
154";
155
156 const WRONG_CURVE_IDENTITY_FILE_NO_PARAMS: &str = "-----BEGIN EC PRIVATE KEY-----
161MFACAQEEFI9cF6zXxMKhtjn1gBD7AHPbzehfoAcGBSuBBAAeoSwDKgAEh5NXszgR
162oGSXVWaGxcQhQWlFG4pbnOG+93xXzfRD7eKWOdmun2bKxQ==
163-----END EC PRIVATE KEY-----
164";
165
166 const IDENTITY_FILE: &str = "-----BEGIN EC PARAMETERS-----
170BgUrgQQACg==
171-----END EC PARAMETERS-----
172-----BEGIN EC PRIVATE KEY-----
173MHQCAQEEIAgy7nZEcVHkQ4Z1Kdqby8SwyAiyKDQmtbEHTIM+WNeBoAcGBSuBBAAK
174oUQDQgAEgO87rJ1ozzdMvJyZQ+GABDqUxGLvgnAnTlcInV3NuhuPv4O3VGzMGzeB
175N3d26cRxD99TPtm8uo2OuzKhSiq6EQ==
176-----END EC PRIVATE KEY-----
177";
178
179 const DER_ENCODED_PUBLIC_KEY: &str = "3056301006072a8648ce3d020106052b8104000a0342000480ef3bac9d68cf374cbc9c9943e180043a94c462ef8270274e57089d5dcdba1b8fbf83b7546ccc1b3781377776e9c4710fdf533ed9bcba8d8ebb32a14a2aba11";
183
184 #[test]
185 #[should_panic(expected = "UnsupportedKeyCurve")]
186 fn test_secp256k1_reject_wrong_curve() {
187 Secp256k1Identity::from_pem(WRONG_CURVE_IDENTITY_FILE.as_bytes()).unwrap();
188 }
189
190 #[test]
191 #[should_panic(expected = "KeyMalformed")]
192 fn test_secp256k1_reject_wrong_curve_no_id() {
193 Secp256k1Identity::from_pem(WRONG_CURVE_IDENTITY_FILE_NO_PARAMS.as_bytes()).unwrap();
194 }
195
196 const IDENTITY_FILE_PKCS8: &str = "-----BEGIN PRIVATE KEY-----
199MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgCDLudkRxUeRDhnUp2pvL
200xLDICLIoNCa1sQdMgz5Y14GhRANCAASA7zusnWjPN0y8nJlD4YAEOpTEYu+CcCdO
201VwidXc26G4+/g7dUbMwbN4E3d3bpxHEP31M+2by6jY67MqFKKroR
202-----END PRIVATE KEY-----
203";
204
205 #[test]
206 fn test_secp256k1_public_key() {
207 let identity = Secp256k1Identity::from_pem(IDENTITY_FILE.as_bytes())
209 .expect("Cannot create secp256k1 identity from PEM file.");
210
211 assert!(DER_ENCODED_PUBLIC_KEY == hex::encode(identity.der_encoded_public_key));
213 }
214
215 #[test]
216 fn test_secp256k1_pkcs8_public_key() {
217 let identity = Secp256k1Identity::from_pem(IDENTITY_FILE_PKCS8.as_bytes())
218 .expect("Cannot create secp256k1 identity from PKCS#8 PEM file.");
219 assert_eq!(
220 DER_ENCODED_PUBLIC_KEY,
221 hex::encode(identity.der_encoded_public_key)
222 );
223 }
224
225 #[test]
226 #[should_panic(expected = "UnsupportedKeyCurve")]
227 fn test_secp256k1_pkcs8_reject_wrong_curve() {
228 const PRIME256V1_PKCS8: &str = "-----BEGIN PRIVATE KEY-----
230MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgvXJuZvDH64piyxw5
231ly/vUyYqGs2p88/SR7W6cRPkBjihRANCAARRttlXg16tlM9Z9tDvj38Y0u7xNofw
232FRL8jv/6Kmy74w/LB+cEUhlKSzjEZsD9ltrOysyXi/jjpTlQhXEIYZor
233-----END PRIVATE KEY-----
234";
235 Secp256k1Identity::from_pem(PRIME256V1_PKCS8.as_bytes()).unwrap();
236 }
237
238 #[test]
239 fn test_secp256k1_signature() {
240 let identity = Secp256k1Identity::from_pem(IDENTITY_FILE.as_bytes())
242 .expect("Cannot create secp256k1 identity from PEM file.");
243
244 let message = EnvelopeContent::Call {
246 nonce: None,
247 ingress_expiry: 0,
248 sender: identity.sender().unwrap(),
249 canister_id: "bkyz2-fmaaa-aaaaa-qaaaq-cai".parse().unwrap(),
250 method_name: "greet".to_string(),
251 arg: Encode!(&"world").unwrap(),
252 sender_info: None,
253 };
254 let signature = identity
255 .sign(&message)
256 .expect("Cannot create secp256k1 signature.")
257 .signature
258 .expect("Cannot find secp256k1 signature bytes.");
259
260 let r: Scalar = Option::from(Scalar::from_repr(
262 <[u8; 32]>::try_from(&signature[0..32])
263 .expect("Cannot extract r component from secp256k1 signature bytes.")
264 .into(),
265 ))
266 .expect("Cannot extract r component from secp256k1 signature bytes.");
267 let s: Scalar = Option::from(Scalar::from_repr(
268 <[u8; 32]>::try_from(&signature[32..])
269 .expect("Cannot extract s component from secp256k1 signature bytes.")
270 .into(),
271 ))
272 .expect("Cannot extract s component from secp256k1 signature bytes.");
273 let ecdsa_sig = Signature::from_scalars(r, s)
274 .expect("Cannot create secp256k1 signature from r and s components.");
275
276 identity
278 ._public_key
279 .verify(&message.to_request_id().signable(), &ecdsa_sig)
280 .expect("Cannot verify secp256k1 signature.");
281 }
282}