Skip to main content

ic_pub_key/
lib.rs

1#![forbid(unsafe_code)]
2#![forbid(missing_docs)]
3#![warn(clippy::all)]
4#![warn(clippy::nursery)]
5#![warn(future_incompatible)]
6#![warn(rust_2018_idioms)]
7#![warn(rustdoc::broken_intra_doc_links)]
8#![warn(rustdoc::missing_crate_level_docs)]
9#![deny(unused_must_use)]
10#![deny(unused_results)]
11
12//! A crate for performing derivation of threshold public keys
13//!
14//! The Internet Computer platform offers a threshold signing interface
15//! supporting ECDSA, Ed25519, BIP340-Schnorr, and BLS. The signing interface
16//! allows signing under many different keys which are derived from a master key
17//! using a derivation algorithm.
18//!
19//! The management canister interface allows for *online* derivation of public
20//! keys: a canister running on the IC can invoke the call to determine what
21//! public key will be used for any particular context argument.
22//!
23//! This crate mirrors that functionality in an offline way: it can be called either
24//! by canisters that do not wish to call the management canister interface, or
25//! by applications not running on that IC at all.
26//!
27//! As an extension of the management canister interface, this crate also allows
28//! deriving keys relative to the hardcoded test master keys used by PocketIC.
29
30#[cfg(not(any(feature = "secp256k1", feature = "ed25519", feature = "vetkeys")))]
31compile_error!("At least one of the features (secp256k1, ed25519, vetkeys) must be enabled");
32
33pub use ic_management_canister_types::{
34    CanisterId, EcdsaCurve, EcdsaKeyId, EcdsaPublicKeyArgs, EcdsaPublicKeyResult, SchnorrAlgorithm,
35    SchnorrKeyId, SchnorrPublicKeyArgs, SchnorrPublicKeyResult, VetKDCurve, VetKDKeyId,
36    VetKDPublicKeyArgs, VetKDPublicKeyResult,
37};
38
39/// Error that can occur during public key derivation
40#[derive(Copy, Clone, Debug, Eq, PartialEq)]
41pub enum Error {
42    /// The specified master public key is not known
43    UnknownKeyIdentifier,
44    /// The algorithm is not supported (possibly due to an unset feature)
45    AlgorithmNotSupported,
46    /// The canister must be specified in the argument structs since the library
47    /// has no other way of determining the correct value, and the canister id
48    /// is a necessary component of the key derivation.
49    CanisterIdMissing,
50    /// The derivation path is not valid for this algorithm
51    ///
52    /// This mostly affects VetKD derivation, which only supports a single
53    /// context string rather than a sequence of them.
54    InvalidPath,
55}
56
57enum MasterPublicKeyInner {
58    #[cfg(feature = "secp256k1")]
59    EcdsaSecp256k1(ic_secp256k1::PublicKey),
60    #[cfg(feature = "secp256k1")]
61    Bip340Secp256k1(ic_secp256k1::PublicKey),
62    #[cfg(feature = "ed25519")]
63    Ed25519(ic_ed25519::PublicKey),
64    #[cfg(feature = "vetkeys")]
65    VetKD(ic_vetkeys::MasterPublicKey),
66}
67
68/// The master public key of a threshold signature system
69pub struct MasterPublicKey {
70    inner: MasterPublicKeyInner,
71}
72
73impl MasterPublicKey {
74    /// Derive the master key for a canister
75    pub fn derive_canister_key(&self, canister_id: &CanisterId) -> CanisterMasterKey {
76        let inner = match &self.inner {
77            #[cfg(feature = "secp256k1")]
78            MasterPublicKeyInner::EcdsaSecp256k1(mk) => {
79                let path = ic_secp256k1::DerivationPath::new(vec![ic_secp256k1::DerivationIndex(
80                    canister_id.as_slice().to_vec(),
81                )]);
82                DerivedPublicKeyInner::EcdsaSecp256k1(mk.derive_subkey(&path))
83            }
84            #[cfg(feature = "secp256k1")]
85            MasterPublicKeyInner::Bip340Secp256k1(mk) => {
86                let path = ic_secp256k1::DerivationPath::new(vec![ic_secp256k1::DerivationIndex(
87                    canister_id.as_slice().to_vec(),
88                )]);
89                DerivedPublicKeyInner::Bip340Secp256k1(mk.derive_subkey(&path))
90            }
91            #[cfg(feature = "ed25519")]
92            MasterPublicKeyInner::Ed25519(mk) => {
93                let path = ic_ed25519::DerivationPath::new(vec![ic_ed25519::DerivationIndex(
94                    canister_id.as_slice().to_vec(),
95                )]);
96                DerivedPublicKeyInner::Ed25519(mk.derive_subkey(&path))
97            }
98            #[cfg(feature = "vetkeys")]
99            MasterPublicKeyInner::VetKD(mk) => {
100                DerivedPublicKeyInner::VetKD(mk.derive_canister_key(canister_id.as_slice()))
101            }
102        };
103        CanisterMasterKey { inner }
104    }
105}
106
107impl TryFrom<&EcdsaKeyId> for MasterPublicKey {
108    type Error = Error;
109
110    fn try_from(key_id: &EcdsaKeyId) -> Result<Self, Self::Error> {
111        if key_id.curve != EcdsaCurve::Secp256k1 {
112            return Err(Error::AlgorithmNotSupported);
113        }
114
115        #[cfg(feature = "secp256k1")]
116        {
117            let mk = match (key_id.curve, key_id.name.as_ref()) {
118                (EcdsaCurve::Secp256k1, "key_1") => {
119                    ic_secp256k1::PublicKey::mainnet_key(ic_secp256k1::MasterPublicKeyId::EcdsaKey1)
120                }
121                (EcdsaCurve::Secp256k1, "test_key_1") => ic_secp256k1::PublicKey::mainnet_key(
122                    ic_secp256k1::MasterPublicKeyId::EcdsaTestKey1,
123                ),
124                (EcdsaCurve::Secp256k1, "pocketic_key_1") => ic_secp256k1::PublicKey::pocketic_key(
125                    ic_secp256k1::PocketIcMasterPublicKeyId::EcdsaKey1,
126                ),
127                (EcdsaCurve::Secp256k1, "pocketic_test_key_1") => {
128                    ic_secp256k1::PublicKey::pocketic_key(
129                        ic_secp256k1::PocketIcMasterPublicKeyId::EcdsaTestKey1,
130                    )
131                }
132                (EcdsaCurve::Secp256k1, "dfx_test_key") => ic_secp256k1::PublicKey::pocketic_key(
133                    ic_secp256k1::PocketIcMasterPublicKeyId::EcdsaDfxTestKey,
134                ),
135                (_, _) => return Err(Error::UnknownKeyIdentifier),
136            };
137
138            let inner = MasterPublicKeyInner::EcdsaSecp256k1(mk);
139            Ok(Self { inner })
140        }
141
142        #[cfg(not(feature = "secp256k1"))]
143        {
144            Err(Error::AlgorithmNotSupported)
145        }
146    }
147}
148
149impl TryFrom<&SchnorrKeyId> for MasterPublicKey {
150    type Error = Error;
151
152    fn try_from(key_id: &SchnorrKeyId) -> Result<Self, Self::Error> {
153        #[cfg(feature = "secp256k1")]
154        {
155            if key_id.algorithm == SchnorrAlgorithm::Bip340secp256k1 {
156                let mk = match key_id.name.as_ref() {
157                    "key_1" => ic_secp256k1::PublicKey::mainnet_key(
158                        ic_secp256k1::MasterPublicKeyId::SchnorrKey1,
159                    ),
160                    "test_key_1" => ic_secp256k1::PublicKey::mainnet_key(
161                        ic_secp256k1::MasterPublicKeyId::SchnorrTestKey1,
162                    ),
163                    "pocketic_key_1" => ic_secp256k1::PublicKey::pocketic_key(
164                        ic_secp256k1::PocketIcMasterPublicKeyId::SchnorrKey1,
165                    ),
166                    "pocketic_test_key_1" => ic_secp256k1::PublicKey::pocketic_key(
167                        ic_secp256k1::PocketIcMasterPublicKeyId::SchnorrTestKey1,
168                    ),
169                    "dfx_test_key" => ic_secp256k1::PublicKey::pocketic_key(
170                        ic_secp256k1::PocketIcMasterPublicKeyId::SchnorrDfxTestKey,
171                    ),
172                    _ => return Err(Error::UnknownKeyIdentifier),
173                };
174
175                let inner = MasterPublicKeyInner::Bip340Secp256k1(mk);
176                return Ok(Self { inner });
177            }
178        }
179
180        #[cfg(feature = "ed25519")]
181        {
182            if key_id.algorithm == SchnorrAlgorithm::Ed25519 {
183                let mk = match key_id.name.as_ref() {
184                    "key_1" => {
185                        ic_ed25519::PublicKey::mainnet_key(ic_ed25519::MasterPublicKeyId::Key1)
186                    }
187                    "test_key_1" => {
188                        ic_ed25519::PublicKey::mainnet_key(ic_ed25519::MasterPublicKeyId::TestKey1)
189                    }
190                    "pocketic_key_1" => ic_ed25519::PublicKey::pocketic_key(
191                        ic_ed25519::PocketIcMasterPublicKeyId::Key1,
192                    ),
193                    "pocketic_test_key_1" => ic_ed25519::PublicKey::pocketic_key(
194                        ic_ed25519::PocketIcMasterPublicKeyId::TestKey1,
195                    ),
196                    "dfx_test_key" => ic_ed25519::PublicKey::pocketic_key(
197                        ic_ed25519::PocketIcMasterPublicKeyId::DfxTestKey,
198                    ),
199                    _ => return Err(Error::UnknownKeyIdentifier),
200                };
201
202                let inner = MasterPublicKeyInner::Ed25519(mk);
203                return Ok(Self { inner });
204            }
205        }
206
207        Err(Error::AlgorithmNotSupported)
208    }
209}
210
211impl TryFrom<&VetKDKeyId> for MasterPublicKey {
212    type Error = Error;
213
214    fn try_from(key_id: &VetKDKeyId) -> Result<Self, Self::Error> {
215        #[cfg(feature = "vetkeys")]
216        {
217            if let Some(mk) = ic_vetkeys::MasterPublicKey::for_mainnet_key(key_id) {
218                let inner = MasterPublicKeyInner::VetKD(mk);
219                return Ok(Self { inner });
220            }
221        }
222
223        Err(Error::AlgorithmNotSupported)
224    }
225}
226
227enum DerivedPublicKeyInner {
228    #[cfg(feature = "secp256k1")]
229    EcdsaSecp256k1((ic_secp256k1::PublicKey, [u8; 32])),
230    #[cfg(feature = "secp256k1")]
231    Bip340Secp256k1((ic_secp256k1::PublicKey, [u8; 32])),
232    #[cfg(feature = "ed25519")]
233    Ed25519((ic_ed25519::PublicKey, [u8; 32])),
234    #[cfg(feature = "vetkeys")]
235    VetKD(ic_vetkeys::DerivedPublicKey),
236}
237
238/// The canister's master public key of a threshold signature system
239///
240/// Each canister gets its own canister master key, which is derived from
241/// the system master key.
242pub struct CanisterMasterKey {
243    inner: DerivedPublicKeyInner,
244}
245
246impl CanisterMasterKey {
247    /// Derive the public key from a canister key and a single contextual input
248    ///
249    /// VetKeys requires exactly one contextual input be supplied. In addition,
250    /// if that contextual input is the empty bytestring then the "derived key"
251    /// is identical to the canister master public key. This matches the
252    /// behavior of the management canister interface.
253    ///
254    /// For other keys, which support a path of inputs, this is equivalent to deriving
255    /// using a path of length 1
256    pub fn derive_key_with_context(&self, context: &[u8]) -> DerivedPublicKey {
257        let inner = match &self.inner {
258            #[cfg(feature = "secp256k1")]
259            DerivedPublicKeyInner::EcdsaSecp256k1(ck) => {
260                let path = ic_secp256k1::DerivationPath::new(vec![ic_secp256k1::DerivationIndex(
261                    context.to_vec(),
262                )]);
263                DerivedPublicKeyInner::EcdsaSecp256k1(
264                    ck.0.derive_subkey_with_chain_code(&path, &ck.1),
265                )
266            }
267            #[cfg(feature = "secp256k1")]
268            DerivedPublicKeyInner::Bip340Secp256k1(ck) => {
269                let path = ic_secp256k1::DerivationPath::new(vec![ic_secp256k1::DerivationIndex(
270                    context.to_vec(),
271                )]);
272                DerivedPublicKeyInner::Bip340Secp256k1(
273                    ck.0.derive_subkey_with_chain_code(&path, &ck.1),
274                )
275            }
276            #[cfg(feature = "ed25519")]
277            DerivedPublicKeyInner::Ed25519(ck) => {
278                let path = ic_ed25519::DerivationPath::new(vec![ic_ed25519::DerivationIndex(
279                    context.to_vec(),
280                )]);
281                DerivedPublicKeyInner::Ed25519(ck.0.derive_subkey_with_chain_code(&path, &ck.1))
282            }
283            #[cfg(feature = "vetkeys")]
284            DerivedPublicKeyInner::VetKD(ck) => {
285                DerivedPublicKeyInner::VetKD(ck.derive_sub_key(context))
286            }
287        };
288        DerivedPublicKey { inner }
289    }
290
291    /// Derive a public key using a path of contextual inputs
292    ///
293    /// Note that VetKD does not support derivation paths, but only a single context string,
294    /// so VetKD is not supported by this function.
295    pub fn derive_key(&self, path: &[Vec<u8>]) -> Result<DerivedPublicKey, Error> {
296        let inner = match &self.inner {
297            #[cfg(feature = "secp256k1")]
298            DerivedPublicKeyInner::EcdsaSecp256k1(ck) => {
299                let path = ic_secp256k1::DerivationPath::new(
300                    path.iter()
301                        .cloned()
302                        .map(ic_secp256k1::DerivationIndex)
303                        .collect(),
304                );
305                DerivedPublicKeyInner::EcdsaSecp256k1(
306                    ck.0.derive_subkey_with_chain_code(&path, &ck.1),
307                )
308            }
309            #[cfg(feature = "secp256k1")]
310            DerivedPublicKeyInner::Bip340Secp256k1(ck) => {
311                let path = ic_secp256k1::DerivationPath::new(
312                    path.iter()
313                        .cloned()
314                        .map(ic_secp256k1::DerivationIndex)
315                        .collect(),
316                );
317                DerivedPublicKeyInner::Bip340Secp256k1(
318                    ck.0.derive_subkey_with_chain_code(&path, &ck.1),
319                )
320            }
321            #[cfg(feature = "ed25519")]
322            DerivedPublicKeyInner::Ed25519(ck) => {
323                let path = ic_ed25519::DerivationPath::new(
324                    path.iter()
325                        .cloned()
326                        .map(ic_ed25519::DerivationIndex)
327                        .collect(),
328                );
329                DerivedPublicKeyInner::Ed25519(ck.0.derive_subkey_with_chain_code(&path, &ck.1))
330            }
331            #[cfg(feature = "vetkeys")]
332            DerivedPublicKeyInner::VetKD(_ck) => {
333                // VetKD has a somewhat different design for derivation than used by
334                // the other threshold schemes - it supports only a single input rather
335                // than a path. To avoid risk of confusing behavior, just reject
336                return Err(Error::AlgorithmNotSupported);
337            }
338        };
339        Ok(DerivedPublicKey { inner })
340    }
341
342    /// Return the serialized encoding of the canister master public key
343    pub fn serialize(&self) -> Vec<u8> {
344        match &self.inner {
345            #[cfg(feature = "secp256k1")]
346            DerivedPublicKeyInner::EcdsaSecp256k1(ck) => ck.0.serialize_sec1(true),
347            #[cfg(feature = "secp256k1")]
348            DerivedPublicKeyInner::Bip340Secp256k1(ck) => ck.0.serialize_sec1(true),
349            #[cfg(feature = "ed25519")]
350            DerivedPublicKeyInner::Ed25519(ck) => ck.0.serialize_raw().to_vec(),
351            #[cfg(feature = "vetkeys")]
352            DerivedPublicKeyInner::VetKD(ck) => ck.serialize(),
353        }
354    }
355
356    /// Return the chain code used for further derivation, if relevant
357    ///
358    /// Returns None if not applicable for this algorithm
359    pub fn chain_code(&self) -> Option<Vec<u8>> {
360        match &self.inner {
361            #[cfg(feature = "secp256k1")]
362            DerivedPublicKeyInner::EcdsaSecp256k1(ck) => Some(ck.1.to_vec()),
363            #[cfg(feature = "secp256k1")]
364            DerivedPublicKeyInner::Bip340Secp256k1(ck) => Some(ck.1.to_vec()),
365            #[cfg(feature = "ed25519")]
366            DerivedPublicKeyInner::Ed25519(ck) => Some(ck.1.to_vec()),
367            #[cfg(feature = "vetkeys")]
368            DerivedPublicKeyInner::VetKD(_ck) => None,
369        }
370    }
371}
372
373/// A public key ultimately derived from a master key
374pub struct DerivedPublicKey {
375    inner: DerivedPublicKeyInner,
376}
377
378impl DerivedPublicKey {
379    /// Return the serialized encoding of the derived public key
380    pub fn serialize(&self) -> Vec<u8> {
381        match &self.inner {
382            #[cfg(feature = "secp256k1")]
383            DerivedPublicKeyInner::EcdsaSecp256k1(ck) => ck.0.serialize_sec1(true),
384            #[cfg(feature = "secp256k1")]
385            DerivedPublicKeyInner::Bip340Secp256k1(ck) => ck.0.serialize_sec1(true),
386            #[cfg(feature = "ed25519")]
387            DerivedPublicKeyInner::Ed25519(ck) => ck.0.serialize_raw().to_vec(),
388            #[cfg(feature = "vetkeys")]
389            DerivedPublicKeyInner::VetKD(ck) => ck.serialize(),
390        }
391    }
392
393    /// Return the chain code used for further derivation, if relevant
394    ///
395    /// Returns None if not applicable for this algorithm
396    pub fn chain_code(&self) -> Option<Vec<u8>> {
397        match &self.inner {
398            #[cfg(feature = "secp256k1")]
399            DerivedPublicKeyInner::EcdsaSecp256k1(ck) => Some(ck.1.to_vec()),
400            #[cfg(feature = "secp256k1")]
401            DerivedPublicKeyInner::Bip340Secp256k1(ck) => Some(ck.1.to_vec()),
402            #[cfg(feature = "ed25519")]
403            DerivedPublicKeyInner::Ed25519(ck) => Some(ck.1.to_vec()),
404            #[cfg(feature = "vetkeys")]
405            DerivedPublicKeyInner::VetKD(_ck) => None,
406        }
407    }
408}
409
410/// Derive an ECDSA public key
411///
412/// This is an offline equivalent to the `ecdsa_public_key` management canister call
413///
414/// As an extension of the management canister call this function also
415/// supports the keys that are used (for testing) by PocketIC.
416///
417/// See [IC method `ecdsa_public_key`](https://internetcomputer.org/docs/references/ic-interface-spec/#ic-ecdsa_public_key).
418///
419/// # Supported Keys
420///
421/// The following key IDs are currently supported
422///
423/// * `key_1`: The production key used on mainnet
424/// * `test_key_1`: The test key used on mainnet
425/// * `pocketic_key_1`: The key used by PocketIC when a request to the management canister
426///   uses `key_1`.
427/// * `pocketic_test_key_1`: The key used by PocketIC when a request to the management canister
428///   uses `test_key_1`.
429/// * `dfx_test_key`: PocketIC specific test key
430pub fn derive_ecdsa_key(args: &EcdsaPublicKeyArgs) -> Result<EcdsaPublicKeyResult, Error> {
431    let canister_id = args.canister_id.ok_or(Error::CanisterIdMissing)?;
432
433    let dk = MasterPublicKey::try_from(&args.key_id)?
434        .derive_canister_key(&canister_id)
435        .derive_key(&args.derivation_path)?;
436
437    Ok(EcdsaPublicKeyResult {
438        public_key: dk.serialize(),
439        chain_code: dk.chain_code().expect("Missing chain code"),
440    })
441}
442
443/// Derive a Schnorr public key
444///
445/// This is an offline equivalent to the `schnorr_public_key` management canister call
446///
447/// See [IC method `schnorr_public_key`](https://internetcomputer.org/docs/references/ic-interface-spec/#ic-schnorr_public_key).
448///
449/// # Supported Keys
450///
451/// Currently this function supports both Ed25519 and BIP340 Schnorr key derivation,
452/// using any of the following key names:
453///
454/// * `key_1`: The production key used on mainnet
455/// * `test_key_1`: The test key used on mainnet
456/// * `pocketic_key_1`: The key used by PocketIC when a request to the management canister
457///   uses `key_1`.
458/// * `pocketic_test_key_1`: The key used by PocketIC when a request to the management canister
459///   uses `test_key_1`.
460/// * `dfx_test_key`: PocketIC specific test key
461pub fn derive_schnorr_key(args: &SchnorrPublicKeyArgs) -> Result<SchnorrPublicKeyResult, Error> {
462    let canister_id = args.canister_id.ok_or(Error::CanisterIdMissing)?;
463
464    let dk = MasterPublicKey::try_from(&args.key_id)?
465        .derive_canister_key(&canister_id)
466        .derive_key(&args.derivation_path)?;
467
468    Ok(SchnorrPublicKeyResult {
469        public_key: dk.serialize(),
470        chain_code: dk.chain_code().expect("Missing chain code"),
471    })
472}
473
474/// Derive a VetKD public key
475///
476/// This is an offline equivalent to the `vetkd_public_key` management canister call
477///
478/// See [IC method `vetkd_public_key`](https://internetcomputer.org/docs/references/ic-interface-spec/#ic-vetkd_public_key)
479pub fn derive_vetkd_key(args: &VetKDPublicKeyArgs) -> Result<VetKDPublicKeyResult, Error> {
480    let canister_id = args.canister_id.ok_or(Error::CanisterIdMissing)?;
481
482    let ck = MasterPublicKey::try_from(&args.key_id)?.derive_canister_key(&canister_id);
483
484    let dk = ck.derive_key_with_context(&args.context);
485    Ok(VetKDPublicKeyResult {
486        public_key: dk.serialize(),
487    })
488}