1#![warn(rust_2018_idioms)]
2#![warn(future_incompatible)]
3#![forbid(missing_docs)]
4#![forbid(unsafe_code)]
5
6use curve25519_dalek::{EdwardsPoint, Scalar, edwards::CompressedEdwardsY};
9use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey, EncodePublicKey};
10use ed25519_dalek::{Digest, Sha512};
11use ed25519_dalek::{Signer, SigningKey, VerifyingKey};
12use hex_literal::hex;
13use thiserror::Error;
14use zeroize::ZeroizeOnDrop;
15
16pub use ic_principal::Principal as CanisterId;
17
18#[derive(Clone, Debug, Error)]
20pub enum PrivateKeyDecodingError {
21 #[error("The outer PEM encoding is invalid: {0}")]
23 InvalidPemEncoding(String),
24 #[error("The PEM label was not the expected value: {0}")]
26 UnexpectedPemLabel(String),
27 #[error("The private key seems invalid in some way: {0}")]
29 InvalidKeyEncoding(String),
30}
31
32#[derive(Clone, Eq, PartialEq, ZeroizeOnDrop)]
34pub struct PrivateKey {
35 sk: SigningKey,
36}
37
38impl std::fmt::Debug for PrivateKey {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.debug_struct("PrivateKey")
41 .field("public_key", &self.public_key().serialize_raw())
42 .finish_non_exhaustive() }
44}
45
46const BUGGY_RING_V2_DER_PREFIX: [u8; 16] = [
54 48, 83, 2, 1, 1, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32, ];
62
63const BUGGY_RING_V2_DER_PK_PREFIX: [u8; 5] = [
64 161,
65 35, 3, 33, 0, ];
69
70const BUGGY_RING_V2_LEN: usize = BUGGY_RING_V2_DER_PREFIX.len()
71 + PrivateKey::BYTES
72 + BUGGY_RING_V2_DER_PK_PREFIX.len()
73 + PublicKey::BYTES;
74
75#[derive(Copy, Clone, Eq, PartialEq, Debug)]
77pub enum PrivateKeyFormat {
78 Pkcs8v1,
80 Pkcs8v2,
86 Pkcs8v2WithRingBug,
95}
96
97impl PrivateKey {
98 pub const BYTES: usize = 32;
100
101 #[cfg(feature = "rand")]
103 pub fn generate() -> Self {
104 let mut rng = rand::thread_rng();
105 Self::generate_using_rng(&mut rng)
106 }
107
108 #[cfg(feature = "rand")]
110 pub fn generate_using_rng<R: rand::CryptoRng + rand::Rng>(rng: &mut R) -> Self {
111 let sk = SigningKey::generate(rng);
112 Self { sk }
113 }
114
115 pub fn generate_from_seed(seed: &[u8]) -> Self {
122 let digest: [u8; 32] = {
123 let mut sha2 = Sha512::new();
124 sha2.update(seed);
125 let digest: [u8; 64] = sha2.finalize().into();
126 let mut truncated = [0u8; 32];
127 truncated.copy_from_slice(&digest[..32]);
128 truncated
129 };
130
131 Self {
132 sk: SigningKey::from_bytes(&digest),
133 }
134 }
135
136 pub fn sign_message(&self, msg: &[u8]) -> [u8; 64] {
140 self.sk.sign(msg).into()
141 }
142
143 pub fn public_key(&self) -> PublicKey {
145 PublicKey::new(self.sk.verifying_key())
146 }
147
148 pub fn serialize_raw(&self) -> [u8; Self::BYTES] {
153 self.sk.to_bytes()
154 }
155
156 pub fn deserialize_raw(bytes: &[u8]) -> Result<Self, PrivateKeyDecodingError> {
163 let bytes = <[u8; Self::BYTES]>::try_from(bytes).map_err(|_| {
164 PrivateKeyDecodingError::InvalidKeyEncoding(format!(
165 "Expected key of exactly {} bytes, got {}",
166 Self::BYTES,
167 bytes.len()
168 ))
169 })?;
170
171 Ok(Self::deserialize_raw_32(&bytes))
172 }
173
174 pub fn deserialize_raw_32(bytes: &[u8; 32]) -> Self {
181 let sk = SigningKey::from_bytes(bytes);
182 Self { sk }
183 }
184
185 pub fn serialize_pkcs8(&self, format: PrivateKeyFormat) -> Vec<u8> {
189 let sk_bytes = self.serialize_raw();
190 let pk_bytes = self.public_key().serialize_raw();
191
192 fn to_pkcs8<T: EncodePrivateKey>(v: &T) -> Vec<u8> {
193 let pkcs8 = v.to_pkcs8_der();
194
195 pkcs8.expect("PKCS8 encoding failed").to_bytes().to_vec()
204 }
205
206 match format {
207 PrivateKeyFormat::Pkcs8v1 => {
208 let kp = ed25519_dalek::pkcs8::KeypairBytes {
209 secret_key: sk_bytes,
210 public_key: None,
211 };
212
213 to_pkcs8(&kp)
214 }
215 PrivateKeyFormat::Pkcs8v2 => {
216 let kp = ed25519_dalek::pkcs8::KeypairBytes {
217 secret_key: sk_bytes,
218 public_key: Some(ed25519_dalek::pkcs8::PublicKeyBytes(pk_bytes)),
219 };
220
221 to_pkcs8(&kp)
222 }
223 PrivateKeyFormat::Pkcs8v2WithRingBug => {
224 let mut ringv2 = Vec::with_capacity(BUGGY_RING_V2_LEN);
225
226 ringv2.extend_from_slice(&BUGGY_RING_V2_DER_PREFIX);
227 ringv2.extend_from_slice(&sk_bytes);
228 ringv2.extend_from_slice(&BUGGY_RING_V2_DER_PK_PREFIX);
229 ringv2.extend_from_slice(&pk_bytes);
230
231 ringv2
232 }
233 }
234 }
235
236 pub fn deserialize_pkcs8(bytes: &[u8]) -> Result<Self, PrivateKeyDecodingError> {
244 if bytes.len() == BUGGY_RING_V2_LEN && bytes.starts_with(&BUGGY_RING_V2_DER_PREFIX) {
245 let sk_offset = BUGGY_RING_V2_DER_PREFIX.len();
246 Self::deserialize_raw(&bytes[sk_offset..sk_offset + Self::BYTES])
247 } else {
248 let sk = SigningKey::from_pkcs8_der(bytes)
249 .map_err(|e| PrivateKeyDecodingError::InvalidKeyEncoding(format!("{e:?}")))?;
250 Ok(Self { sk })
251 }
252 }
253
254 pub fn serialize_pkcs8_pem(&self, format: PrivateKeyFormat) -> String {
258 let pkcs8 = self.serialize_pkcs8(format);
259
260 pem::encode(&pem::Pem {
261 tag: "PRIVATE KEY".to_string(),
262 contents: pkcs8,
263 })
264 }
265
266 pub fn deserialize_pkcs8_pem(pem: &str) -> Result<Self, PrivateKeyDecodingError> {
272 let der = pem::parse(pem)
273 .map_err(|e| PrivateKeyDecodingError::InvalidPemEncoding(format!("{e:?}")))?;
274 if der.tag != "PRIVATE KEY" {
275 return Err(PrivateKeyDecodingError::UnexpectedPemLabel(der.tag));
276 }
277
278 Self::deserialize_pkcs8(&der.contents)
279 }
280
281 pub fn derive_subkey(&self, derivation_path: &DerivationPath) -> (DerivedPrivateKey, [u8; 32]) {
292 let chain_code = [0u8; 32];
293 self.derive_subkey_with_chain_code(derivation_path, &chain_code)
294 }
295
296 pub fn derive_subkey_with_chain_code(
308 &self,
309 derivation_path: &DerivationPath,
310 chain_code: &[u8; 32],
311 ) -> (DerivedPrivateKey, [u8; 32]) {
312 let sk_scalar = self.sk.to_scalar();
313 let pt = EdwardsPoint::mul_base(&sk_scalar);
314
315 let (pt, sum, chain_code) = derivation_path.derive_offset(pt, chain_code);
316
317 let derived_scalar = sk_scalar + sum;
318
319 let derived_hash_prefix = {
320 let mut sha2 = Sha512::new();
323 sha2.update(derived_scalar.to_bytes());
324 sha2.update(chain_code);
325 let hash: [u8; 64] = sha2.finalize().into();
326 let mut truncated = [0u8; 32];
327 truncated.copy_from_slice(&hash[..32]);
328 truncated
329 };
330
331 let dpk = DerivedPrivateKey::new(derived_scalar, derived_hash_prefix, pt);
332 (dpk, chain_code)
333 }
334}
335
336pub struct DerivedPrivateKey {
342 esk: ed25519_dalek::hazmat::ExpandedSecretKey,
344 vk: ed25519_dalek::VerifyingKey,
345}
346
347impl DerivedPrivateKey {
348 fn new(scalar: Scalar, hash_prefix: [u8; 32], pk: EdwardsPoint) -> Self {
349 let esk = ed25519_dalek::hazmat::ExpandedSecretKey {
350 scalar,
351 hash_prefix,
352 };
353
354 let vk = ed25519_dalek::VerifyingKey::from(pk);
355
356 Self { esk, vk }
357 }
358
359 pub fn sign_message(&self, msg: &[u8]) -> [u8; 64] {
363 ed25519_dalek::hazmat::raw_sign::<Sha512>(&self.esk, msg, &self.vk).to_bytes()
364 }
365
366 pub fn public_key(&self) -> PublicKey {
368 PublicKey::new(self.vk)
369 }
370
371 pub fn derive_subkey(&self, derivation_path: &DerivationPath) -> (DerivedPrivateKey, [u8; 32]) {
382 let chain_code = [0u8; 32];
383 self.derive_subkey_with_chain_code(derivation_path, &chain_code)
384 }
385
386 pub fn derive_subkey_with_chain_code(
398 &self,
399 derivation_path: &DerivationPath,
400 chain_code: &[u8; 32],
401 ) -> (DerivedPrivateKey, [u8; 32]) {
402 let sk_scalar = self.esk.scalar;
403 let pt = EdwardsPoint::mul_base(&sk_scalar);
404
405 let (pt, sum, chain_code) = derivation_path.derive_offset(pt, chain_code);
406
407 let derived_scalar = sk_scalar + sum;
408
409 let derived_hash_prefix = {
410 let mut sha2 = Sha512::new();
413 sha2.update(derived_scalar.to_bytes());
414 sha2.update(chain_code);
415 let hash: [u8; 64] = sha2.finalize().into();
416 let mut truncated = [0u8; 32];
417 truncated.copy_from_slice(&hash[..32]);
418 truncated
419 };
420
421 let dpk = DerivedPrivateKey::new(derived_scalar, derived_hash_prefix, pt);
422 (dpk, chain_code)
423 }
424}
425
426#[derive(Clone, Debug, Error)]
428pub enum PublicKeyDecodingError {
429 #[error("The outer PEM encoding is invalid: {0}")]
431 InvalidPemEncoding(String),
432 #[error("The PEM label was not the expected value: {0}")]
434 UnexpectedPemLabel(String),
435 #[error("The encoding of the public key is invalid: {0}")]
437 InvalidKeyEncoding(String),
438}
439
440struct Signature {
442 r: EdwardsPoint,
443 r_bytes: [u8; 32], s: Scalar,
445}
446
447impl Signature {
448 fn from_slice(signature: &[u8]) -> Result<Self, SignatureError> {
449 if signature.len() != 64 {
450 return Err(SignatureError::InvalidLength);
451 }
452
453 let (r, r_bytes) = {
454 let mut r_bytes = [0u8; 32];
455 r_bytes.copy_from_slice(&signature[..32]);
456 let r = CompressedEdwardsY(r_bytes)
457 .decompress()
458 .ok_or(SignatureError::InvalidSignature)?;
459
460 (r, r_bytes)
461 };
462
463 let s = {
464 let mut s_bytes = [0u8; 32];
465 s_bytes.copy_from_slice(&signature[32..]);
466 Option::<Scalar>::from(Scalar::from_canonical_bytes(s_bytes))
467 .ok_or(SignatureError::InvalidSignature)?
468 };
469
470 Ok(Self { r, r_bytes, s })
471 }
472
473 pub fn r_bytes(&self) -> &[u8; 32] {
474 &self.r_bytes
475 }
476
477 pub fn r(&self) -> &EdwardsPoint {
478 &self.r
479 }
480
481 pub fn s(&self) -> &Scalar {
482 &self.s
483 }
484}
485
486#[derive(Copy, Clone, Eq, PartialEq, Debug)]
488pub enum MasterPublicKeyId {
489 Key1,
491 TestKey1,
493}
494
495#[derive(Copy, Clone, Eq, PartialEq, Debug)]
497pub enum PocketIcMasterPublicKeyId {
498 Key1,
500 TestKey1,
502 DfxTestKey,
504}
505
506#[derive(Copy, Clone, Eq, PartialEq, Debug)]
508pub struct PublicKey {
509 pk: VerifyingKey,
510 edwards: EdwardsPoint,
515}
516
517#[derive(Copy, Clone, Debug, Error)]
519pub enum SignatureError {
520 #[error("The signature had an invalid length, and cannot possibly be valid")]
522 InvalidLength,
523 #[error(
526 "The batch was invalid (e.g., due to length mismatch between number of
527 messages and number of signatures)"
528 )]
529 InvalidBatch,
530 #[error("A signature was invalid")]
532 InvalidSignature,
533}
534
535impl PublicKey {
536 pub const BYTES: usize = 32;
538
539 fn new(pk: VerifyingKey) -> Self {
547 let edwards = CompressedEdwardsY(pk.to_bytes()).decompress().unwrap();
548
549 Self { pk, edwards }
550 }
551
552 pub fn is_torsion_free(&self) -> bool {
555 self.edwards.is_torsion_free()
559 }
560
561 pub fn is_canonical(&self) -> bool {
563 self.pk.to_bytes() == self.edwards.compress().0
564 }
565
566 pub fn convert_raw_to_der(raw: &[u8]) -> Result<Vec<u8>, PublicKeyDecodingError> {
575 if raw.len() != Self::BYTES {
578 return Err(PublicKeyDecodingError::InvalidKeyEncoding(format!(
579 "Expected key of exactly {} bytes, got {}",
580 Self::BYTES,
581 raw.len()
582 )));
583 };
584
585 const DER_PREFIX: [u8; 12] = [
586 48, 42, 48, 5, 6, 3, 43, 101, 112, 3, 33, 0, ];
592
593 let mut der_enc = Vec::with_capacity(DER_PREFIX.len() + Self::BYTES);
594 der_enc.extend_from_slice(&DER_PREFIX);
595 der_enc.extend_from_slice(raw);
596 Ok(der_enc)
597 }
598
599 pub fn serialize_raw(&self) -> [u8; Self::BYTES] {
603 *self.pk.as_bytes()
604 }
605
606 pub fn deserialize_raw(bytes: &[u8]) -> Result<Self, PublicKeyDecodingError> {
617 let bytes = <[u8; Self::BYTES]>::try_from(bytes).map_err(|_| {
618 PublicKeyDecodingError::InvalidKeyEncoding(format!(
619 "Expected key of exactly {} bytes, got {}",
620 Self::BYTES,
621 bytes.len()
622 ))
623 })?;
624 let pk = VerifyingKey::from_bytes(&bytes)
625 .map_err(|e| PublicKeyDecodingError::InvalidKeyEncoding(format!("{e:?}")))?;
626
627 Ok(Self::new(pk))
628 }
629
630 pub fn serialize_rfc8410_der(&self) -> Vec<u8> {
634 let der = self.pk.to_public_key_der();
635
636 der.expect("Encoding public key as DER failed")
638 .as_bytes()
639 .to_vec()
640 }
641
642 pub fn serialize_rfc8410_pem(&self) -> Vec<u8> {
646 pem::encode(&pem::Pem {
647 tag: "PUBLIC KEY".to_string(),
648 contents: self.serialize_rfc8410_der(),
649 })
650 .into()
651 }
652
653 pub fn deserialize_rfc8410_der(bytes: &[u8]) -> Result<Self, PublicKeyDecodingError> {
664 let pk = VerifyingKey::from_public_key_der(bytes)
665 .map_err(|e| PublicKeyDecodingError::InvalidKeyEncoding(format!("{e:?}")))?;
666 Ok(Self::new(pk))
667 }
668
669 pub fn deserialize_rfc8410_pem(pem: &str) -> Result<Self, PublicKeyDecodingError> {
680 let der = pem::parse(pem)
681 .map_err(|e| PublicKeyDecodingError::InvalidPemEncoding(format!("{e:?}")))?;
682 if der.tag != "PUBLIC KEY" {
683 return Err(PublicKeyDecodingError::UnexpectedPemLabel(der.tag));
684 }
685
686 Self::deserialize_rfc8410_der(&der.contents)
687 }
688
689 fn compute_challenge(sig: &Signature, pk: &Self, msg: &[u8]) -> Scalar {
691 let mut sha512 = Sha512::new();
692 sha512.update(sig.r_bytes());
693 sha512.update(pk.pk.as_bytes());
696 sha512.update(msg);
697 Scalar::from_hash(sha512)
698 }
699
700 pub fn verify_signature(&self, msg: &[u8], signature: &[u8]) -> Result<(), SignatureError> {
706 let signature = Signature::from_slice(signature)?;
707
708 let k = Self::compute_challenge(&signature, self, msg);
709 let minus_a = -self.edwards;
710 let recomputed_r =
711 EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &minus_a, signature.s());
712
713 use curve25519_dalek::traits::IsIdentity;
714
715 if (recomputed_r - signature.r())
716 .mul_by_cofactor()
717 .is_identity()
718 {
719 Ok(())
720 } else {
721 Err(SignatureError::InvalidSignature)
722 }
723 }
724
725 #[cfg(feature = "rand")]
735 pub fn batch_verify<R: rand::CryptoRng + rand::Rng>(
736 messages: &[&[u8]],
737 signatures: &[&[u8]],
738 keys: &[Self],
739 rng: &mut R,
740 ) -> Result<(), SignatureError> {
741 if messages.len() != signatures.len() || signatures.len() != keys.len() {
742 return Err(SignatureError::InvalidBatch);
743 }
744
745 use curve25519_dalek::{
746 constants::ED25519_BASEPOINT_POINT, traits::IsIdentity, traits::VartimeMultiscalarMul,
747 };
748 use std::iter::once;
749
750 let signatures = signatures
751 .iter()
752 .map(|s| Signature::from_slice(s))
753 .collect::<Result<Vec<_>, _>>()?;
754
755 let n = signatures.len();
756
757 let hrams = (0..n)
758 .map(|i| Self::compute_challenge(&signatures[i], &keys[i], messages[i]))
759 .collect::<Vec<_>>();
760
761 let zs: Vec<Scalar> = (0..n).map(|_| Scalar::from(rng.r#gen::<u128>())).collect();
763
764 let b_coefficient: Scalar = signatures
765 .iter()
766 .zip(zs.iter())
767 .map(|(sig, z)| sig.s() * z)
768 .sum();
769
770 let zhrams = hrams.iter().zip(zs.iter()).map(|(hram, z)| hram * z);
771
772 let r = signatures.iter().map(|sig| *sig.r());
773 let pk = keys.iter().map(|pk| pk.edwards);
774
775 let id = EdwardsPoint::vartime_multiscalar_mul(
776 once(-b_coefficient).chain(zs.iter().cloned()).chain(zhrams),
777 once(ED25519_BASEPOINT_POINT).chain(r).chain(pk),
778 )
779 .mul_by_cofactor();
780
781 if id.is_identity() {
782 Ok(())
783 } else {
784 Err(SignatureError::InvalidSignature)
785 }
786 }
787
788 pub fn mainnet_key(key_id: MasterPublicKeyId) -> Self {
790 match key_id {
791 MasterPublicKeyId::Key1 => Self::deserialize_raw(&hex!(
792 "476374d9df3a8af28d3164dc2422cff894482eadd1295290b6d9ad92b2eeaa5c"
793 ))
794 .expect("Hardcoded master key was rejected"),
795 MasterPublicKeyId::TestKey1 => Self::deserialize_raw(&hex!(
796 "6c0824beb37621bcca6eecc237ed1bc4e64c9c59dcb85344aa7f9cc8278ee31f"
797 ))
798 .expect("Hardcoded master key was rejected"),
799 }
800 }
801
802 pub fn pocketic_key(key_id: PocketIcMasterPublicKeyId) -> Self {
807 match key_id {
808 PocketIcMasterPublicKeyId::Key1 => Self::deserialize_raw(&hex!(
809 "db415b8eb85bd5127b0984723e0448054042cf40e7a9c262ed0cc87ecea98349"
810 ))
811 .expect("Hardcoded master key was rejected"),
812 PocketIcMasterPublicKeyId::TestKey1 => Self::deserialize_raw(&hex!(
813 "6ed9121ecf701b9e301fce17d8a65214888984e8211225691b089d6b219ec144"
814 ))
815 .expect("Hardcoded master key was rejected"),
816 PocketIcMasterPublicKeyId::DfxTestKey => Self::deserialize_raw(&hex!(
817 "7124afcb1be5927cac0397a7447b9c3cda2a4099af62d9bc0a2c2fe42d33efe1"
818 ))
819 .expect("Hardcoded master key was rejected"),
820 }
821 }
822
823 pub fn derive_mainnet_key(
827 key_id: MasterPublicKeyId,
828 canister_id: &CanisterId,
829 derivation_path: &[Vec<u8>],
830 ) -> (Self, [u8; 32]) {
831 let mk = PublicKey::mainnet_key(key_id);
832 mk.derive_subkey(&DerivationPath::from_canister_id_and_path(
833 canister_id.as_slice(),
834 derivation_path,
835 ))
836 }
837
838 pub fn derive_pocketic_key(
843 key_id: PocketIcMasterPublicKeyId,
844 canister_id: &CanisterId,
845 derivation_path: &[Vec<u8>],
846 ) -> (Self, [u8; 32]) {
847 let mk = PublicKey::pocketic_key(key_id);
848 mk.derive_subkey(&DerivationPath::from_canister_id_and_path(
849 canister_id.as_slice(),
850 derivation_path,
851 ))
852 }
853
854 pub fn derive_subkey(&self, derivation_path: &DerivationPath) -> (Self, [u8; 32]) {
859 let chain_code = [0u8; 32];
860 self.derive_subkey_with_chain_code(derivation_path, &chain_code)
861 }
862
863 pub fn derive_subkey_with_chain_code(
869 &self,
870 derivation_path: &DerivationPath,
871 chain_code: &[u8; 32],
872 ) -> (Self, [u8; 32]) {
873 let pt = CompressedEdwardsY(self.pk.to_bytes()).decompress().unwrap();
876
877 let (pt, _sum, chain_code) = derivation_path.derive_offset(pt, chain_code);
878
879 let key = Self::new(VerifyingKey::from(pt));
880
881 (key, chain_code)
882 }
883}
884
885#[derive(Clone, Debug)]
887pub struct DerivationIndex(pub Vec<u8>);
888
889#[derive(Clone, Debug)]
893pub struct DerivationPath {
894 path: Vec<DerivationIndex>,
895}
896
897impl DerivationPath {
898 pub fn new_bip32(bip32: &[u32]) -> Self {
900 let mut path = Vec::with_capacity(bip32.len());
901 for n in bip32 {
902 path.push(DerivationIndex(n.to_be_bytes().to_vec()));
903 }
904 Self::new(path)
905 }
906
907 pub fn new(path: Vec<DerivationIndex>) -> Self {
909 Self { path }
910 }
911
912 pub fn from_canister_id_and_path(canister_id: &[u8], path: &[Vec<u8>]) -> Self {
914 let mut vpath = Vec::with_capacity(1 + path.len());
915 vpath.push(DerivationIndex(canister_id.to_vec()));
916
917 for n in path {
918 vpath.push(DerivationIndex(n.to_vec()));
919 }
920 Self::new(vpath)
921 }
922
923 pub fn len(&self) -> usize {
925 self.path.len()
926 }
927
928 pub fn is_empty(&self) -> bool {
930 self.len() == 0
931 }
932
933 pub fn path(&self) -> &[DerivationIndex] {
935 &self.path
936 }
937
938 fn derive_offset(
939 &self,
940 mut pt: EdwardsPoint,
941 chain_code: &[u8; 32],
942 ) -> (EdwardsPoint, Scalar, [u8; 32]) {
943 let mut chain_code = *chain_code;
944 let mut sum = Scalar::ZERO;
945
946 for idx in self.path() {
947 let mut ikm = Vec::with_capacity(PublicKey::BYTES + idx.0.len());
948 ikm.extend_from_slice(&pt.compress().0);
949 ikm.extend_from_slice(&idx.0);
950
951 let hkdf = hkdf::Hkdf::<Sha512>::new(Some(&chain_code), &ikm);
952
953 let mut okm = [0u8; 96];
954 hkdf.expand(b"Ed25519", &mut okm)
955 .expect("96 is a valid length for HKDF-SHA-512");
956
957 let mut offset = [0u8; 64];
958 offset.copy_from_slice(&okm[0..64]);
959 offset.reverse(); let offset = Scalar::from_bytes_mod_order_wide(&offset);
961
962 pt += EdwardsPoint::mul_base(&offset);
963 sum += offset;
964 chain_code.copy_from_slice(&okm[64..]);
965 }
966
967 (pt, sum, chain_code)
968 }
969}