Skip to content

Commit ba2ca69

Browse files
committed
refactor(crypto): CRP-2443 CRP-2235 Use ic-crypto-ed25519 for all Ed25519 key serialization and deserialization
1 parent 71bf6eb commit ba2ca69

File tree

26 files changed

+168
-873
lines changed

26 files changed

+168
-873
lines changed

Cargo.lock

Lines changed: 6 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rs/canister_client/sender/BUILD.bazel

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ package(default_visibility = ["//visibility:public"])
44

55
DEPENDENCIES = [
66
"//rs/crypto/ecdsa_secp256k1",
7-
"//rs/crypto/internal/crypto_lib/types",
8-
"//rs/crypto/utils/basic_sig",
7+
"//rs/crypto/ed25519",
98
"//rs/types/base_types",
109
"//rs/types/types",
11-
"@crate_index//:ed25519-consensus",
1210
"@crate_index//:rand",
1311
"@crate_index//:rand_chacha",
1412
]

rs/canister_client/sender/Cargo.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ documentation.workspace = true
99
[dependencies]
1010
ic-base-types = { path = "../../types/base_types" }
1111
ic-crypto-ecdsa-secp256k1 = { path = "../../crypto/ecdsa_secp256k1" }
12-
ic-crypto-internal-types = { path = "../../crypto/internal/crypto_lib/types" }
13-
ic-crypto-utils-basic-sig = { path = "../../crypto/utils/basic_sig" }
12+
ic-crypto-ed25519 = { path = "../../crypto/ed25519" }
1413
ic-types = { path = "../../types/types" }
15-
ed25519-consensus = "2.0.1"
1614
rand = "0.8"
1715
rand_chacha = "0.3"

rs/canister_client/sender/src/lib.rs

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,6 @@ mod secp256k1_conversions;
33
mod tests;
44

55
use ic_base_types::PrincipalId;
6-
use ic_crypto_internal_types::sign::eddsa::ed25519::{
7-
PublicKey as Ed25519PublicKey, SecretKey as Ed25519SecretKey,
8-
};
9-
use ic_crypto_utils_basic_sig::conversions::Ed25519PemParseError;
10-
use ic_crypto_utils_basic_sig::conversions::Ed25519SecretKeyConversions;
116
use ic_types::crypto::DOMAIN_IC_REQUEST;
127
use ic_types::messages::MessageId;
138
use rand::{CryptoRng, Rng, SeedableRng};
@@ -37,30 +32,31 @@ pub struct Ed25519KeyPair {
3732

3833
impl Ed25519KeyPair {
3934
pub fn generate<R: Rng + CryptoRng>(rng: &mut R) -> Self {
40-
let rng = ChaCha20Rng::from_seed(rng.gen());
41-
let signing_key = ed25519_consensus::SigningKey::new(rng);
35+
let mut rng = ChaCha20Rng::from_seed(rng.gen());
36+
let key = ic_crypto_ed25519::PrivateKey::generate_using_rng(&mut rng);
4237
Self {
43-
secret_key: signing_key.to_bytes(),
44-
public_key: signing_key.verification_key().to_bytes(),
38+
secret_key: key.serialize_raw(),
39+
public_key: key.public_key().serialize_raw(),
4540
}
4641
}
4742

4843
/// Parses an Ed25519KeyPair from a PEM string.
49-
pub fn from_pem(pem: &str) -> Result<Self, Ed25519PemParseError> {
50-
let (secret_key, public_key) = Ed25519SecretKey::from_pem(pem)?;
44+
pub fn from_pem(pem: &str) -> Result<Self, ic_crypto_ed25519::PrivateKeyDecodingError> {
45+
let key = ic_crypto_ed25519::PrivateKey::deserialize_pkcs8_pem(pem)?;
5146
Ok(Ed25519KeyPair {
52-
secret_key: secret_key.0,
53-
public_key: public_key.0,
47+
secret_key: key.serialize_raw(),
48+
public_key: key.public_key().serialize_raw(),
5449
})
5550
}
5651

5752
pub fn to_pem(&self) -> String {
58-
Ed25519SecretKey(self.secret_key).to_pem(&Ed25519PublicKey(self.public_key))
53+
let key = ic_crypto_ed25519::PrivateKey::deserialize_raw_32(&self.secret_key);
54+
key.serialize_pkcs8_pem(ic_crypto_ed25519::PrivateKeyFormat::Pkcs8v2WithRingBug)
5955
}
6056

6157
pub fn sign(&self, msg: &[u8]) -> [u8; 64] {
62-
let signing_key = ed25519_consensus::SigningKey::from(self.secret_key);
63-
signing_key.sign(msg).to_bytes()
58+
let key = ic_crypto_ed25519::PrivateKey::deserialize_raw_32(&self.secret_key);
59+
key.sign_message(msg)
6460
}
6561
}
6662

rs/crypto/ed25519/src/lib.rs

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ impl PrivateKey {
104104
/// Sign a message and return a signature
105105
///
106106
/// This is the non-prehashed variant of Ed25519
107-
pub fn sign_message(&self, msg: &[u8]) -> Vec<u8> {
108-
self.sk.sign(msg).to_vec()
107+
pub fn sign_message(&self, msg: &[u8]) -> [u8; 64] {
108+
self.sk.sign(msg).into()
109109
}
110110

111111
/// Return the public key associated with this secret key
@@ -127,7 +127,7 @@ impl PrivateKey {
127127
/// This is just the plain 32 byte random seed from which the
128128
/// internal key material is derived
129129
///
130-
/// This corresponds with the format used by PrivateKey::serialize
130+
/// This corresponds with the format used by PrivateKey::serialize_raw
131131
pub fn deserialize_raw(bytes: &[u8]) -> Result<Self, PrivateKeyDecodingError> {
132132
let bytes = <[u8; Self::BYTES]>::try_from(bytes).map_err(|_| {
133133
PrivateKeyDecodingError::InvalidKeyEncoding(format!(
@@ -137,8 +137,18 @@ impl PrivateKey {
137137
))
138138
})?;
139139

140-
let sk = SigningKey::from_bytes(&bytes);
141-
Ok(Self { sk })
140+
Ok(Self::deserialize_raw_32(&bytes))
141+
}
142+
143+
/// Deserialize an Ed25519 private key from raw format
144+
///
145+
/// This is just the plain 32 byte random seed from which the
146+
/// internal key material is derived
147+
///
148+
/// This corresponds with the format used by PrivateKey::serialize_raw
149+
pub fn deserialize_raw_32(bytes: &[u8; 32]) -> Self {
150+
let sk = SigningKey::from_bytes(bytes);
151+
Self { sk }
142152
}
143153

144154
/// Serialize the Ed25519 secret key in PKCS8 format
@@ -241,6 +251,10 @@ impl PrivateKey {
241251
/// An invalid key was encountered
242252
#[derive(Clone, Debug)]
243253
pub enum PublicKeyDecodingError {
254+
/// The outer PEM encoding is invalid
255+
InvalidPemEncoding(String),
256+
/// The PEM label was not the expected value
257+
UnexpectedPemLabel(String),
244258
/// The encoding of the public key is invalid, the string contains details
245259
InvalidKeyEncoding(String),
246260
/// The public key had a valid encoding, but contains elements of the torsion
@@ -330,6 +344,17 @@ impl PublicKey {
330344
.to_vec()
331345
}
332346

347+
/// Serialize this public key as a PEM encoded structure
348+
///
349+
/// See RFC 8410 for details on the format
350+
pub fn serialize_rfc8410_pem(&self) -> Vec<u8> {
351+
pem::encode(&pem::Pem {
352+
tag: "PUBLIC KEY".to_string(),
353+
contents: self.serialize_rfc8410_der(),
354+
})
355+
.into()
356+
}
357+
333358
/// Deserialize the DER encoded public key
334359
///
335360
/// See RFC 8410 for details on the format. This cooresponds to
@@ -340,6 +365,20 @@ impl PublicKey {
340365
Self::new(pk)
341366
}
342367

368+
/// Deserialize the PEM encoded public key
369+
///
370+
/// See RFC 8410 for details on the format. This cooresponds to
371+
/// Self::serialize_rfc8410_pem
372+
pub fn deserialize_rfc8410_pem(pem: &str) -> Result<Self, PublicKeyDecodingError> {
373+
let der = pem::parse(pem)
374+
.map_err(|e| PublicKeyDecodingError::InvalidPemEncoding(format!("{:?}", e)))?;
375+
if der.tag != "PUBLIC KEY" {
376+
return Err(PublicKeyDecodingError::UnexpectedPemLabel(der.tag));
377+
}
378+
379+
Self::deserialize_rfc8410_der(&der.contents)
380+
}
381+
343382
/// Verify a Ed25519 signature
344383
///
345384
/// Returns Ok if the signature is valid, or Err otherwise

rs/crypto/ed25519/tests/tests.rs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ fn public_key_deserialization_rejects_keys_of_incorrect_length() {
8080

8181
#[test]
8282
fn batch_verification_works() {
83-
fn batch_verifies(msg: &[[u8; 32]], sigs: &[Vec<u8>], keys: &[PublicKey]) -> bool {
83+
fn batch_verifies(msg: &[[u8; 32]], sigs: &[[u8; 64]], keys: &[PublicKey]) -> bool {
8484
let msg_ref = msg.iter().map(|m| m.as_ref()).collect::<Vec<&[u8]>>();
8585
let sig_ref = sigs.iter().map(|s| s.as_ref()).collect::<Vec<&[u8]>>();
8686
PublicKey::batch_verify(&msg_ref, &sig_ref, keys).is_ok()
@@ -215,6 +215,65 @@ fn can_parse_pkcs8_v2_der_secret_key() {
215215
);
216216
}
217217

218+
#[test]
219+
fn can_parse_dfx_created_private_key() {
220+
let dfx_key: &str = "-----BEGIN PRIVATE KEY-----\nMFMCAQEwBQYDK2VwBCIEIPXo8WUQM26wS/cT6mmHO1ClYHixF46uhRoQlLmPfsQl\noSMDIQAY6M8L0Ocji3w8k2EBMTwhVJT0G6HI1ZZmWrPOzv8L1Q==\n-----END PRIVATE KEY-----\n";
221+
222+
match PrivateKey::deserialize_pkcs8_pem(dfx_key) {
223+
Ok(_sk) => { /* success */ }
224+
Err(e) => panic!("Unexpected error serializing DFX generated key {:?}", e),
225+
}
226+
}
227+
228+
#[test]
229+
fn can_pass_old_basic_sig_utils_parsing_tests() {
230+
struct PublicKeyParsingTest {
231+
raw: [u8; 32],
232+
der: [u8; 44],
233+
pem: String,
234+
}
235+
236+
impl PublicKeyParsingTest {
237+
fn new(raw: [u8; 32], der: [u8; 44], pem: &'static str) -> Self {
238+
Self {
239+
raw,
240+
der,
241+
pem: pem.to_string(),
242+
}
243+
}
244+
}
245+
246+
let tests = [
247+
PublicKeyParsingTest::new(hex!("B3997656BA51FF6DA37B61D8D549EC80717266ECF48FB5DA52B654412634844C"),
248+
hex!("302A300506032B6570032100B3997656BA51FF6DA37B61D8D549EC80717266ECF48FB5DA52B654412634844C"),
249+
"-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAs5l2VrpR/22je2HY1UnsgHFyZuz0j7XaUrZUQSY0hEw=\n-----END PUBLIC KEY-----\n"
250+
),
251+
PublicKeyParsingTest::new(hex!("A5AFB5FEB6DFB6DDF5DD6563856FFF5484F5FE304391D9ED06697861F220C610"),
252+
hex!("302A300506032B6570032100A5AFB5FEB6DFB6DDF5DD6563856FFF5484F5FE304391D9ED06697861F220C610"),
253+
"-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEApa+1/rbftt313WVjhW//VIT1/jBDkdntBml4YfIgxhA=\n-----END PUBLIC KEY-----\n"
254+
),
255+
PublicKeyParsingTest::new(hex!("C8413108F121CB794A10804D15F613E40ECC7C78A4EC567040DDF78467C71DFF"),
256+
hex!("302A300506032B6570032100C8413108F121CB794A10804D15F613E40ECC7C78A4EC567040DDF78467C71DFF"),
257+
"-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAyEExCPEhy3lKEIBNFfYT5A7MfHik7FZwQN33hGfHHf8=\n-----END PUBLIC KEY-----\n"
258+
),
259+
];
260+
261+
for test in &tests {
262+
let from_raw = PublicKey::deserialize_raw(&test.raw).expect("Invalid public key (raw)");
263+
let from_der =
264+
PublicKey::deserialize_rfc8410_der(&test.der).expect("Invalid public key (DER)");
265+
let from_pem =
266+
PublicKey::deserialize_rfc8410_pem(&test.pem).expect("Invalid public key (PEM)");
267+
268+
assert_eq!(from_raw, from_der);
269+
assert_eq!(from_raw, from_pem);
270+
271+
assert_eq!(from_raw.serialize_raw(), test.raw);
272+
assert_eq!(from_der.serialize_raw(), test.raw);
273+
assert_eq!(from_pem.serialize_raw(), test.raw);
274+
}
275+
}
276+
218277
#[test]
219278
fn should_pass_wycheproof_test_vectors() {
220279
let test_set = wycheproof::eddsa::TestSet::load(wycheproof::eddsa::TestName::Ed25519)

rs/crypto/internal/crypto_lib/basic_sig/der_utils/BUILD.bazel

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,11 @@ rust_library(
1414
"//rs/types/types",
1515
"@crate_index//:hex",
1616
"@crate_index//:simple_asn1",
17-
"@crate_index//:zeroize",
1817
],
1918
)
2019

2120
rust_test(
2221
name = "der_utils_test",
2322
crate = ":der_utils",
24-
deps = [
25-
"//rs/crypto/internal/test_vectors",
26-
],
23+
deps = [],
2724
)

rs/crypto/internal/crypto_lib/basic_sig/der_utils/Cargo.toml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,4 @@ documentation.workspace = true
99
[dependencies]
1010
hex = "0.4.2"
1111
simple_asn1 = { workspace = true }
12-
zeroize = { version = "1.4.3", features = ["zeroize_derive"] }
1312
ic-types = { path = "../../../../../types/types" }
14-
15-
[dev-dependencies]
16-
ic-crypto-internal-test-vectors = { path = "../../../test_vectors" }
17-

0 commit comments

Comments
 (0)