diff --git a/Cargo.lock b/Cargo.lock index 625a0276a68..cebaf261bb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6780,7 +6780,6 @@ dependencies = [ "hex", "ic-crypto-internal-bls12-381-type", "ic-crypto-internal-seed", - "ic-crypto-internal-threshold-sig-bls12381-der", "ic-crypto-internal-types", "ic-crypto-secrets-containers", "ic-crypto-sha2", @@ -6800,13 +6799,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ic-crypto-internal-threshold-sig-bls12381-der" -version = "0.9.0" -dependencies = [ - "simple_asn1", -] - [[package]] name = "ic-crypto-internal-threshold-sig-ecdsa" version = "0.9.0" @@ -7375,10 +7367,9 @@ version = "0.9.0" dependencies = [ "base64 0.13.1", "hex", - "ic-crypto-internal-threshold-sig-bls12381", - "ic-crypto-internal-threshold-sig-bls12381-der", "ic-crypto-internal-types", "ic-types", + "simple_asn1", "tempfile", ] diff --git a/Cargo.toml b/Cargo.toml index c4b69c8ea5a..e2e745cff78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,6 @@ members = [ "rs/crypto/internal/crypto_lib/sha2", "rs/crypto/secrets_containers", "rs/crypto/internal/crypto_lib/threshold_sig/bls12_381", - "rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/der_utils", "rs/crypto/internal/crypto_lib/threshold_sig/tecdsa", "rs/crypto/internal/crypto_lib/threshold_sig/tecdsa/test_utils", "rs/crypto/internal/crypto_lib/tls", diff --git a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/BUILD.bazel b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/BUILD.bazel index 4d1298e18cc..58642e49e91 100644 --- a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/BUILD.bazel +++ b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/BUILD.bazel @@ -10,7 +10,6 @@ package(default_visibility = [ DEPENDENCIES = [ "//rs/crypto/internal/crypto_lib/bls12_381/type", "//rs/crypto/internal/crypto_lib/seed", - "//rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/der_utils", "//rs/crypto/internal/crypto_lib/types", "//rs/crypto/secrets_containers", "//rs/crypto/sha2", diff --git a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/Cargo.toml b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/Cargo.toml index 617898bf2ee..eea7f1643e0 100644 --- a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/Cargo.toml +++ b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/Cargo.toml @@ -13,7 +13,6 @@ cached = { version = "0.41", default-features = false } parking_lot = "0.12.1" ic-crypto-internal-bls12-381-type = { path = "../../bls12_381/type" } ic-crypto-internal-seed = { path = "../../seed" } -ic-crypto-internal-threshold-sig-bls12381-der = { path = "der_utils" } ic-crypto-secrets-containers = { path = "../../../../secrets_containers" } ic-crypto-internal-types = { path = "../../types" } ic-crypto-sha2 = { path = "../../../../sha2" } diff --git a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/der_utils/BUILD.bazel b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/der_utils/BUILD.bazel deleted file mode 100644 index a5566217de7..00000000000 --- a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/der_utils/BUILD.bazel +++ /dev/null @@ -1,17 +0,0 @@ -load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") - -rust_library( - name = "der_utils", - srcs = glob(["src/**"]), - crate_name = "ic_crypto_internal_threshold_sig_bls12381_der", - version = "0.9.0", - visibility = ["//rs/crypto:__subpackages__"], - deps = [ - "@crate_index//:simple_asn1", - ], -) - -rust_test( - name = "der_utils_test", - crate = ":der_utils", -) diff --git a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/der_utils/Cargo.toml b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/der_utils/Cargo.toml deleted file mode 100644 index 7dd0c955b29..00000000000 --- a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/der_utils/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "ic-crypto-internal-threshold-sig-bls12381-der" -description = "Internal utils for serializing threshold signatures to DER with minimal dependencies" -version.workspace = true -authors.workspace = true -edition.workspace = true -documentation.workspace = true - -[dependencies] -simple_asn1 = { workspace = true } diff --git a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/der_utils/src/lib.rs b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/der_utils/src/lib.rs deleted file mode 100644 index 7a59b4895b6..00000000000 --- a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/der_utils/src/lib.rs +++ /dev/null @@ -1,82 +0,0 @@ -use simple_asn1::{oid, ASN1Block}; - -/// Byte size of the public key, which is a G2 element. -pub const PUBLIC_KEY_SIZE: usize = 96; - -/// Converts public key bytes into its DER-encoded form. -/// -/// See [the Interface Spec](https://sdk.dfinity.org/docs/interface-spec/index.html#_certificate) -/// and [RFC 5480](https://tools.ietf.org/html/rfc5480). -pub fn public_key_to_der(key: &[u8]) -> Result, String> { - simple_asn1::to_der(&ASN1Block::Sequence( - 2, - vec![ - ASN1Block::Sequence(0, vec![bls_algorithm_id(), bls_curve_id()]), - ASN1Block::BitString(0, key.len() * 8, key.to_vec()), - ], - )) - .map_err(|e| e.to_string()) -} - -/// Parses a `PublicKeyBytes` from its DER-encoded form. -/// -/// See [the Interface Spec](https://sdk.dfinity.org/docs/interface-spec/index.html#_certificate) -/// and [RFC 5480](https://tools.ietf.org/html/rfc5480). -/// -/// # Errors -/// * Returns a string describing the error if the given `bytes` are not valid -/// ASN.1, or include unexpected ASN.1 structures. -pub fn public_key_from_der(bytes: &[u8]) -> Result<[u8; PUBLIC_KEY_SIZE], String> { - use simple_asn1::{ - from_der, - ASN1Block::{BitString, Sequence}, - }; - - let unexpected_struct_err = |s: &ASN1Block| { - format!( - "unexpected ASN1 structure: {:?}, wanted: seq(seq(OID, OID), bitstring)", - s - ) - }; - - let asn1_values = - from_der(bytes).map_err(|e| format!("failed to deserialize DER blocks: {}", e))?; - - match asn1_values[..] { - [Sequence(_, ref seq)] => match &seq[..] { - [Sequence(_, ids), BitString(_, len, key)] => { - if ids.len() != 2 { - return Err(unexpected_struct_err(&asn1_values[0])); - } - - if *len != PUBLIC_KEY_SIZE * 8 { - return Err(format!("unexpected key length: {} bits", len)); - } - - if ids[0] == bls_algorithm_id() && ids[1] == bls_curve_id() { - let mut key_bytes = [0u8; PUBLIC_KEY_SIZE]; - key_bytes.copy_from_slice(key.as_slice()); - Ok(key_bytes) - } else { - Err(format!( - "unsupported algorithm ({:?}) and/or curve ({:?}) OIDs", - ids[0], ids[1], - )) - } - } - _ => Err(unexpected_struct_err(&asn1_values[0])), - }, - _ => Err(format!( - "expected exactly one ASN1 block, got sequence: {:?}", - asn1_values - )), - } -} - -fn bls_algorithm_id() -> ASN1Block { - ASN1Block::ObjectIdentifier(0, oid!(1, 3, 6, 1, 4, 1, 44668, 5, 3, 1, 2, 1)) -} - -fn bls_curve_id() -> ASN1Block { - ASN1Block::ObjectIdentifier(0, oid!(1, 3, 6, 1, 4, 1, 44668, 5, 3, 2, 1)) -} diff --git a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/api.rs b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/api.rs index 1ab02de4603..6fb813e3df0 100644 --- a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/api.rs +++ b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/api.rs @@ -42,11 +42,10 @@ use crate::api::threshold_sign_error::ClibThresholdSignError; use crate::types::public_coefficients::conversions::pub_key_bytes_from_pub_coeff_bytes; use crate::types::PublicKey; use ic_crypto_internal_seed::Seed; -use ic_crypto_internal_threshold_sig_bls12381_der as der; use ic_crypto_internal_types::sign::threshold_sig::ni_dkg::ni_dkg_groth20_bls12_381::PublicCoefficientsBytes; use ic_crypto_internal_types::sign::threshold_sig::public_key::bls12_381::PublicKeyBytes; use ic_types::{ - crypto::{AlgorithmId, CryptoError, CryptoResult}, + crypto::{CryptoError, CryptoResult}, NodeIndex, NumberOfNodes, }; use std::convert::{TryFrom, TryInto}; @@ -284,33 +283,3 @@ pub fn verify_combined_signature_with_cache( pub fn bls_signature_cache_statistics() -> crate::cache::SignatureCacheStatistics { crate::cache::SignatureCache::global().cache_statistics() } - -/// Converts public key bytes into its DER-encoded form. -/// -/// See [the Interface Spec](https://sdk.dfinity.org/docs/interface-spec/index.html#_certificate) and [RFC 5480](https://tools.ietf.org/html/rfc5480). -pub fn public_key_to_der(key: PublicKeyBytes) -> CryptoResult> { - der::public_key_to_der(&key.0).map_err(|e| CryptoError::MalformedPublicKey { - algorithm: AlgorithmId::ThresBls12_381, - key_bytes: Some(key.0.to_vec()), - internal_error: format!("Conversion to DER failed with error {}", e), - }) -} - -/// Parses a `PublicKeyBytes` from its DER-encoded form. -/// -/// See [the Interface Spec](https://sdk.dfinity.org/docs/interface-spec/index.html#_certificate) -/// and [RFC 5480](https://tools.ietf.org/html/rfc5480). -/// -/// # Errors -/// * `CryptoError::MalformedPublicKey` if the given `bytes` are not valid -/// ASN.1, or include unexpected ASN.1 structures.. -pub fn public_key_from_der(bytes: &[u8]) -> CryptoResult { - match der::public_key_from_der(bytes) { - Ok(key_bytes) => Ok(PublicKeyBytes(key_bytes)), - Err(internal_error) => Err(CryptoError::MalformedPublicKey { - algorithm: AlgorithmId::ThresBls12_381, - key_bytes: Some(bytes.to_vec()), - internal_error, - }), - } -} diff --git a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/api/tests.rs b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/api/tests.rs index 596b37b69e5..b72f3fdd788 100644 --- a/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/api/tests.rs +++ b/rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/src/api/tests.rs @@ -254,60 +254,6 @@ fn should_invalid_threshold_signatures_not_be_cached() { } } -#[test] -fn test_public_key_to_der() { - // Test vectors generated from Haskell as follows: - // ic-ref/impl $ cabal repl ic-ref - // … - // Ok, 35 modules loaded. - // *Main> import IC.Types (prettyBlob) - // *Main IC.Types> import qualified IC.Crypto.DER as DER - // *Main IC.Types DER> import qualified IC.Crypto.BLS as BLS - // *Main IC.Types DER BLS> :set -XOverloadedStrings - // *Main IC.Types DER BLS> let pk1 = BLS.toPublicKey (BLS.createKey "testseed1") - // *Main IC.Types DER BLS> putStrLn (prettyBlob pk1) - // 0xa7623a93cdb56c4d23d99c14216afaab3dfd6d4f9eb3db23d038280b6d5cb2caaee2a19dd92c9df7001dede23bf036bc0f33982dfb41e8fa9b8e96b5dc3e83d55ca4dd146c7eb2e8b6859cb5a5db815db86810b8d12cee1588b5dbf34a4dc9a5 - // *Main IC.Types DER BLS> putStrLn (prettyBlob (DER.encode DER.BLS pk1)) - // 0x308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c05030201036100a7623a93cdb56c4d23d99c14216afaab3dfd6d4f9eb3db23d038280b6d5cb2caaee2a19dd92c9df7001dede23bf036bc0f33982dfb41e8fa9b8e96b5dc3e83d55ca4dd146c7eb2e8b6859cb5a5db815db86810b8d12cee1588b5dbf34a4dc9a5 - // *Main IC.Types DER BLS> let pk2 = BLS.toPublicKey (BLS.createKey "testseed2") - // *Main IC.Types DER BLS> putStrLn (prettyBlob pk2) - // 0xb613303bda180e6b474bc15183870828c54999ee3a4797c9dd00cabe59ce78e307b212884878ec437ae9fd73f5c1f13d01f34edf1e746c192f7f6e9614bc950b705b5d2825d87499c9778db2b032955badb5b4eb103b46b0f4fa476b45b784ed - // *Main IC.Types DER BLS> putStrLn (prettyBlob (DER.encode DER.BLS pk2)) - // 0x308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c05030201036100b613303bda180e6b474bc15183870828c54999ee3a4797c9dd00cabe59ce78e307b212884878ec437ae9fd73f5c1f13d01f34edf1e746c192f7f6e9614bc950b705b5d2825d87499c9778db2b032955badb5b4eb103b46b0f4fa476b45b784edu - struct BlsPublicKey<'a> { - raw_hex: &'a str, - der_hex: &'a str, - } - - let test_vectors = [ - BlsPublicKey { - raw_hex: "a7623a93cdb56c4d23d99c14216afaab3dfd6d4f9eb3db23d038280b6d5cb2caaee2a19dd92c9df7001dede23bf036bc0f33982dfb41e8fa9b8e96b5dc3e83d55ca4dd146c7eb2e8b6859cb5a5db815db86810b8d12cee1588b5dbf34a4dc9a5", - der_hex: "308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c05030201036100a7623a93cdb56c4d23d99c14216afaab3dfd6d4f9eb3db23d038280b6d5cb2caaee2a19dd92c9df7001dede23bf036bc0f33982dfb41e8fa9b8e96b5dc3e83d55ca4dd146c7eb2e8b6859cb5a5db815db86810b8d12cee1588b5dbf34a4dc9a5" - }, - BlsPublicKey { - raw_hex: "b613303bda180e6b474bc15183870828c54999ee3a4797c9dd00cabe59ce78e307b212884878ec437ae9fd73f5c1f13d01f34edf1e746c192f7f6e9614bc950b705b5d2825d87499c9778db2b032955badb5b4eb103b46b0f4fa476b45b784ed", - der_hex: "308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c05030201036100b613303bda180e6b474bc15183870828c54999ee3a4797c9dd00cabe59ce78e307b212884878ec437ae9fd73f5c1f13d01f34edf1e746c192f7f6e9614bc950b705b5d2825d87499c9778db2b032955badb5b4eb103b46b0f4fa476b45b784ed" - } - ]; - - for public_key in test_vectors.iter() { - let mut bytes = [0u8; PublicKeyBytes::SIZE]; - bytes.copy_from_slice(&hex::decode(public_key.raw_hex).unwrap()); - let public_key_raw = PublicKeyBytes(bytes); - let der = hex::decode(public_key.der_hex).unwrap(); - - assert_eq!(tsig::public_key_to_der(public_key_raw).unwrap(), der); - assert_eq!(public_key_raw, tsig::public_key_from_der(&der[..]).unwrap()); - - let mut buf = der.clone(); - for i in 0..der.len() { - buf[i] = !buf[i]; - assert_ne!(tsig::public_key_from_der(&buf), Ok(public_key_raw)); - buf[i] = !buf[i]; - } - } -} - proptest! { #![proptest_config(ProptestConfig { cases: 4, @@ -327,11 +273,3 @@ proptest! { test_threshold_sig_api_and_core_match(Seed::from_bytes(&seed), NumberOfNodes::from(threshold + redundancy), NumberOfNodes::from(threshold), &message); } } - -#[test] -fn should_use_correct_key_size_in_der_utils() { - assert_eq!( - ic_crypto_internal_threshold_sig_bls12381_der::PUBLIC_KEY_SIZE, - PublicKeyBytes::SIZE - ); -} diff --git a/rs/crypto/utils/threshold_sig_der/BUILD.bazel b/rs/crypto/utils/threshold_sig_der/BUILD.bazel index 28e82ba5146..f2c5ca2ed03 100644 --- a/rs/crypto/utils/threshold_sig_der/BUILD.bazel +++ b/rs/crypto/utils/threshold_sig_der/BUILD.bazel @@ -1,4 +1,4 @@ -load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test_suite") package(default_visibility = ["//visibility:public"]) @@ -8,18 +8,19 @@ rust_library( crate_name = "ic_crypto_utils_threshold_sig_der", version = "0.9.0", deps = [ - "//rs/crypto/internal/crypto_lib/threshold_sig/bls12_381", - "//rs/crypto/internal/crypto_lib/threshold_sig/bls12_381/der_utils", "//rs/crypto/internal/crypto_lib/types", "//rs/types/types", "@crate_index//:base64", + "@crate_index//:simple_asn1", ], ) -rust_test( - name = "threshold_sig_der_test", - crate = ":threshold_sig_der", +rust_test_suite( + name = "threshold_sig_der_integration", + srcs = glob(["tests/**/*.rs"]), deps = [ + ":threshold_sig_der", + "//rs/crypto/internal/crypto_lib/types", "@crate_index//:hex", "@crate_index//:tempfile", ], diff --git a/rs/crypto/utils/threshold_sig_der/Cargo.toml b/rs/crypto/utils/threshold_sig_der/Cargo.toml index c5b8318ca78..2a871789ae2 100644 --- a/rs/crypto/utils/threshold_sig_der/Cargo.toml +++ b/rs/crypto/utils/threshold_sig_der/Cargo.toml @@ -8,14 +8,13 @@ documentation.workspace = true [dependencies] base64 = { workspace = true } -ic-crypto-internal-threshold-sig-bls12381 = { path = "../../internal/crypto_lib/threshold_sig/bls12_381" } -ic-crypto-internal-threshold-sig-bls12381-der = { path = "../../internal/crypto_lib/threshold_sig/bls12_381/der_utils" } +simple_asn1 = { workspace = true } ic-crypto-internal-types = { path = "../../internal/crypto_lib/types/" } ic-types = { path = "../../../types/types" } # Note: keep this crate as light-weight as possible. In particular, do not add # dependencies that make this crate (e.g., transitively) dependent on -# heavy-weight crates such as miracl_core. +# heavy-weight crates such as bls12_381. [dev-dependencies] hex = "0.4.2" diff --git a/rs/crypto/utils/threshold_sig_der/src/conversions.rs b/rs/crypto/utils/threshold_sig_der/src/conversions.rs index dacab2042a5..e6fa63e0b17 100644 --- a/rs/crypto/utils/threshold_sig_der/src/conversions.rs +++ b/rs/crypto/utils/threshold_sig_der/src/conversions.rs @@ -1,5 +1,4 @@ -use ic_crypto_internal_threshold_sig_bls12381 as bls12_381; -use ic_crypto_internal_threshold_sig_bls12381_der::public_key_from_der; +use crate::{public_key_from_der, public_key_to_der}; use ic_crypto_internal_types::sign::threshold_sig::public_key::bls12_381::PublicKeyBytes; use ic_types::crypto::threshold_sig::ThresholdSigPublicKey; use ic_types::crypto::{AlgorithmId, CryptoError, CryptoResult}; @@ -71,8 +70,14 @@ pub fn parse_threshold_sig_key_from_der(der_bytes: &[u8]) -> Result CryptoResult> { // TODO(CRP-641): add a check that the key is indeed a BLS key. - let pk = PublicKeyBytes(pk.into_bytes()); - bls12_381::api::public_key_to_der(pk) + + let key = PublicKeyBytes(pk.into_bytes()); + + public_key_to_der(&key.0).map_err(|e| CryptoError::MalformedPublicKey { + algorithm: AlgorithmId::ThresBls12_381, + key_bytes: Some(key.0.to_vec()), + internal_error: format!("Conversion to DER failed with error {}", e), + }) } /// Decodes a threshold signature public key from DER. @@ -80,53 +85,16 @@ pub fn threshold_sig_public_key_to_der(pk: ThresholdSigPublicKey) -> CryptoResul /// # Errors /// * `CryptoError::MalformedPublicKey`: if the public cannot be DER decoded. pub fn threshold_sig_public_key_from_der(bytes: &[u8]) -> CryptoResult { - let pk = bls12_381::api::public_key_from_der(bytes)?; - Ok(pk.into()) + match public_key_from_der(bytes) { + Ok(key_bytes) => Ok(PublicKeyBytes(key_bytes).into()), + Err(internal_error) => Err(CryptoError::MalformedPublicKey { + algorithm: AlgorithmId::ThresBls12_381, + key_bytes: Some(bytes.to_vec()), + internal_error, + }), + } } fn invalid_data_err(msg: impl std::string::ToString) -> Error { Error::new(ErrorKind::InvalidData, msg.to_string()) } - -#[cfg(test)] -mod tests { - #![allow(clippy::unwrap_used)] - use super::*; - - #[test] - fn can_parse_pem_file() { - use std::io::Write; - - let contents = r#"-----BEGIN PUBLIC KEY----- -MIGCMB0GDSsGAQQBgtx8BQMBAgEGDCsGAQQBgtx8BQMCAQNhAKOY3Qk9qTesCRaL -GY4Bb/WQ5wfxhiUca4hbVIRfOkPlNtXSg/AHff5QIckWPifeyRB/S9A1jjg1XdKP -5lSemYM6VVTrGhjShUwHqVmdOBJ8ofpb2+qV/2ppvxc+3OFBvA== ------END PUBLIC KEY----- -"#; - - let mut tmpfile = tempfile::NamedTempFile::new().unwrap(); - tmpfile.write_all(contents.as_bytes()).unwrap(); - let pk = parse_threshold_sig_key(tmpfile.path()).unwrap(); - assert_eq!( - hex::encode(&pk.into_bytes()[..]), - "a398dd093da937ac09168b198e016ff590e707f186251c6b885b54845f3a43e536d5d283f0077dfe5021c9163e27dec9107f4bd0358e38355dd28fe6549e99833a5554eb1a18d2854c07a9599d38127ca1fa5bdbea95ff6a69bf173edce141bc" - ); - } - - #[test] - fn base64_decode_fails() { - use std::io::Write; - - let contents = r#"-----BEGIN PUBLIC KEY----- -MIGCMB0GDSsGAQQBgtx8BQMBAgEGDCsGAQQBgtx8BQMCAQNhAKOY3Qk9qTesCRaL -GY4Bb/WQ5wfxhiUca4hbVIRfOkPlNtXSg/AHff5QIckWPifeyRB/S9A1jjg1XdKP -5lSemYM6VVTGhjShUwHqVmdOBJ8ofpb2+qV/2ppvxc+3OFBvA== ------END PUBLIC KEY----- -"#; - - let mut tmpfile = tempfile::NamedTempFile::new().unwrap(); - tmpfile.write_all(contents.as_bytes()).unwrap(); - let pk = parse_threshold_sig_key(tmpfile.path()); - assert!(pk.is_err()); - } -} diff --git a/rs/crypto/utils/threshold_sig_der/src/lib.rs b/rs/crypto/utils/threshold_sig_der/src/lib.rs index 886d6d4e7c2..678f0070acc 100644 --- a/rs/crypto/utils/threshold_sig_der/src/lib.rs +++ b/rs/crypto/utils/threshold_sig_der/src/lib.rs @@ -1,5 +1,85 @@ -pub use ic_crypto_internal_threshold_sig_bls12381_der::public_key_from_der; -pub use ic_crypto_internal_threshold_sig_bls12381_der::public_key_to_der; +use simple_asn1::{oid, ASN1Block}; + +/// Byte size of the public key, which is a G2 element. +pub const PUBLIC_KEY_SIZE: usize = 96; + +/// Converts public key bytes into its DER-encoded form. +/// +/// See [the Interface Spec](https://sdk.dfinity.org/docs/interface-spec/index.html#_certificate) +/// and [RFC 5480](https://tools.ietf.org/html/rfc5480). +pub fn public_key_to_der(key: &[u8]) -> Result, String> { + simple_asn1::to_der(&ASN1Block::Sequence( + 2, + vec![ + ASN1Block::Sequence(0, vec![bls_algorithm_id(), bls_curve_id()]), + ASN1Block::BitString(0, key.len() * 8, key.to_vec()), + ], + )) + .map_err(|e| e.to_string()) +} + +/// Parses a `PublicKeyBytes` from its DER-encoded form. +/// +/// See [the Interface Spec](https://sdk.dfinity.org/docs/interface-spec/index.html#_certificate) +/// and [RFC 5480](https://tools.ietf.org/html/rfc5480). +/// +/// # Errors +/// * Returns a string describing the error if the given `bytes` are not valid +/// ASN.1, or include unexpected ASN.1 structures. +pub fn public_key_from_der(bytes: &[u8]) -> Result<[u8; PUBLIC_KEY_SIZE], String> { + use simple_asn1::{ + from_der, + ASN1Block::{BitString, Sequence}, + }; + + let unexpected_struct_err = |s: &ASN1Block| { + format!( + "unexpected ASN1 structure: {:?}, wanted: seq(seq(OID, OID), bitstring)", + s + ) + }; + + let asn1_values = + from_der(bytes).map_err(|e| format!("failed to deserialize DER blocks: {}", e))?; + + match asn1_values[..] { + [Sequence(_, ref seq)] => match &seq[..] { + [Sequence(_, ids), BitString(_, len, key)] => { + if ids.len() != 2 { + return Err(unexpected_struct_err(&asn1_values[0])); + } + + if *len != PUBLIC_KEY_SIZE * 8 { + return Err(format!("unexpected key length: {} bits", len)); + } + + if ids[0] == bls_algorithm_id() && ids[1] == bls_curve_id() { + let mut key_bytes = [0u8; PUBLIC_KEY_SIZE]; + key_bytes.copy_from_slice(key.as_slice()); + Ok(key_bytes) + } else { + Err(format!( + "unsupported algorithm ({:?}) and/or curve ({:?}) OIDs", + ids[0], ids[1], + )) + } + } + _ => Err(unexpected_struct_err(&asn1_values[0])), + }, + _ => Err(format!( + "expected exactly one ASN1 block, got sequence: {:?}", + asn1_values + )), + } +} + +fn bls_algorithm_id() -> ASN1Block { + ASN1Block::ObjectIdentifier(0, oid!(1, 3, 6, 1, 4, 1, 44668, 5, 3, 1, 2, 1)) +} + +fn bls_curve_id() -> ASN1Block { + ASN1Block::ObjectIdentifier(0, oid!(1, 3, 6, 1, 4, 1, 44668, 5, 3, 2, 1)) +} mod conversions; pub use conversions::*; diff --git a/rs/crypto/utils/threshold_sig_der/tests/tests.rs b/rs/crypto/utils/threshold_sig_der/tests/tests.rs new file mode 100644 index 00000000000..ee589e485a3 --- /dev/null +++ b/rs/crypto/utils/threshold_sig_der/tests/tests.rs @@ -0,0 +1,101 @@ +use ic_crypto_internal_types::sign::threshold_sig::public_key::bls12_381::PublicKeyBytes; +use ic_crypto_utils_threshold_sig_der::*; + +#[test] +fn can_parse_pem_file() { + use std::io::Write; + + let contents = r#"-----BEGIN PUBLIC KEY----- +MIGCMB0GDSsGAQQBgtx8BQMBAgEGDCsGAQQBgtx8BQMCAQNhAKOY3Qk9qTesCRaL +GY4Bb/WQ5wfxhiUca4hbVIRfOkPlNtXSg/AHff5QIckWPifeyRB/S9A1jjg1XdKP +5lSemYM6VVTrGhjShUwHqVmdOBJ8ofpb2+qV/2ppvxc+3OFBvA== +-----END PUBLIC KEY----- +"#; + + let mut tmpfile = tempfile::NamedTempFile::new().unwrap(); + tmpfile.write_all(contents.as_bytes()).unwrap(); + let pk = parse_threshold_sig_key(tmpfile.path()).unwrap(); + assert_eq!( + hex::encode(&pk.into_bytes()[..]), + "a398dd093da937ac09168b198e016ff590e707f186251c6b885b54845f3a43e536d5d283f0077dfe5021c9163e27dec9107f4bd0358e38355dd28fe6549e99833a5554eb1a18d2854c07a9599d38127ca1fa5bdbea95ff6a69bf173edce141bc" + ); +} + +#[test] +fn base64_decode_fails() { + use std::io::Write; + + let contents = r#"-----BEGIN PUBLIC KEY----- +MIGCMB0GDSsGAQQBgtx8BQMBAgEGDCsGAQQBgtx8BQMCAQNhAKOY3Qk9qTesCRaL +GY4Bb/WQ5wfxhiUca4hbVIRfOkPlNtXSg/AHff5QIckWPifeyRB/S9A1jjg1XdKP +5lSemYM6VVTGhjShUwHqVmdOBJ8ofpb2+qV/2ppvxc+3OFBvA== +-----END PUBLIC KEY----- +"#; + + let mut tmpfile = tempfile::NamedTempFile::new().unwrap(); + tmpfile.write_all(contents.as_bytes()).unwrap(); + let pk = parse_threshold_sig_key(tmpfile.path()); + assert!(pk.is_err()); +} + +#[test] +fn should_use_correct_key_size_in_der_utils() { + assert_eq!(PUBLIC_KEY_SIZE, PublicKeyBytes::SIZE); +} + +#[test] +fn test_public_key_to_der() { + // Test vectors generated from Haskell as follows: + // ic-ref/impl $ cabal repl ic-ref + // … + // Ok, 35 modules loaded. + // *Main> import IC.Types (prettyBlob) + // *Main IC.Types> import qualified IC.Crypto.DER as DER + // *Main IC.Types DER> import qualified IC.Crypto.BLS as BLS + // *Main IC.Types DER BLS> :set -XOverloadedStrings + // *Main IC.Types DER BLS> let pk1 = BLS.toPublicKey (BLS.createKey "testseed1") + // *Main IC.Types DER BLS> putStrLn (prettyBlob pk1) + // 0xa7623a93cdb56c4d23d99c14216afaab3dfd6d4f9eb3db23d038280b6d5cb2caaee2a19dd92c9df7001dede23bf036bc0f33982dfb41e8fa9b8e96b5dc3e83d55ca4dd146c7eb2e8b6859cb5a5db815db86810b8d12cee1588b5dbf34a4dc9a5 + // *Main IC.Types DER BLS> putStrLn (prettyBlob (DER.encode DER.BLS pk1)) + // 0x308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c05030201036100a7623a93cdb56c4d23d99c14216afaab3dfd6d4f9eb3db23d038280b6d5cb2caaee2a19dd92c9df7001dede23bf036bc0f33982dfb41e8fa9b8e96b5dc3e83d55ca4dd146c7eb2e8b6859cb5a5db815db86810b8d12cee1588b5dbf34a4dc9a5 + // *Main IC.Types DER BLS> let pk2 = BLS.toPublicKey (BLS.createKey "testseed2") + // *Main IC.Types DER BLS> putStrLn (prettyBlob pk2) + // 0xb613303bda180e6b474bc15183870828c54999ee3a4797c9dd00cabe59ce78e307b212884878ec437ae9fd73f5c1f13d01f34edf1e746c192f7f6e9614bc950b705b5d2825d87499c9778db2b032955badb5b4eb103b46b0f4fa476b45b784ed + // *Main IC.Types DER BLS> putStrLn (prettyBlob (DER.encode DER.BLS pk2)) + // 0x308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c05030201036100b613303bda180e6b474bc15183870828c54999ee3a4797c9dd00cabe59ce78e307b212884878ec437ae9fd73f5c1f13d01f34edf1e746c192f7f6e9614bc950b705b5d2825d87499c9778db2b032955badb5b4eb103b46b0f4fa476b45b784edu + struct BlsPublicKey<'a> { + raw_hex: &'a str, + der_hex: &'a str, + } + + let test_vectors = [ + BlsPublicKey { + raw_hex: "a7623a93cdb56c4d23d99c14216afaab3dfd6d4f9eb3db23d038280b6d5cb2caaee2a19dd92c9df7001dede23bf036bc0f33982dfb41e8fa9b8e96b5dc3e83d55ca4dd146c7eb2e8b6859cb5a5db815db86810b8d12cee1588b5dbf34a4dc9a5", + der_hex: "308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c05030201036100a7623a93cdb56c4d23d99c14216afaab3dfd6d4f9eb3db23d038280b6d5cb2caaee2a19dd92c9df7001dede23bf036bc0f33982dfb41e8fa9b8e96b5dc3e83d55ca4dd146c7eb2e8b6859cb5a5db815db86810b8d12cee1588b5dbf34a4dc9a5" + }, + BlsPublicKey { + raw_hex: "b613303bda180e6b474bc15183870828c54999ee3a4797c9dd00cabe59ce78e307b212884878ec437ae9fd73f5c1f13d01f34edf1e746c192f7f6e9614bc950b705b5d2825d87499c9778db2b032955badb5b4eb103b46b0f4fa476b45b784ed", + der_hex: "308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c05030201036100b613303bda180e6b474bc15183870828c54999ee3a4797c9dd00cabe59ce78e307b212884878ec437ae9fd73f5c1f13d01f34edf1e746c192f7f6e9614bc950b705b5d2825d87499c9778db2b032955badb5b4eb103b46b0f4fa476b45b784ed" + } + ]; + + for public_key in test_vectors.iter() { + let mut bytes = [0u8; PublicKeyBytes::SIZE]; + bytes.copy_from_slice(&hex::decode(public_key.raw_hex).unwrap()); + let public_key_raw = PublicKeyBytes(bytes); + let der = hex::decode(public_key.der_hex).unwrap(); + + assert_eq!(public_key_to_der(&public_key_raw.0).unwrap(), der); + assert_eq!( + public_key_raw, + PublicKeyBytes(public_key_from_der(&der[..]).unwrap()) + ); + + let mut buf = der.clone(); + for i in 0..der.len() { + buf[i] = !buf[i]; + assert_ne!(public_key_from_der(&buf), Ok(public_key_raw.0)); + buf[i] = !buf[i]; + } + } +}