From 2fd87b60a0f591dc5882d320671ae8cfc962ecc7 Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Thu, 13 Feb 2025 14:45:05 -0500 Subject: [PATCH] chore(crypto): CRP-2682 Change key derivation used in VetKD to BLS12-381 hash_to_scalar (#3736) The approach originally used for the vetKD prototype for key derivation used SHAKE, largely as an artifact that we implemented an IBE demo as part of the same prototype, and SHAKE happened to be convenient for implementing IBE. However for production use of vetKD we will switch to using `hash_to_scalar`, a variant of RFC 9380's `hash_to_field`, since we already depend on the security of `hash_to_field` for implementing the hash to curve operation, upon which the security of vetKD depends. Thus we slightly reduce the number of cryptographic primitives vetKD depends on for its security. --- Cargo.lock | 1 - .../crypto_lib/bls12_381/type/src/lib.rs | 14 +++++- .../crypto_lib/bls12_381/type/tests/tests.rs | 33 ++++++++++++++ .../crypto_lib/bls12_381/vetkd/BUILD.bazel | 1 - .../crypto_lib/bls12_381/vetkd/Cargo.toml | 1 - .../crypto_lib/bls12_381/vetkd/src/lib.rs | 20 ++++++--- .../crypto_lib/bls12_381/vetkd/src/ro.rs | 43 ------------------- .../crypto_lib/bls12_381/vetkd/tests/tests.rs | 41 +++++++----------- .../src/vault/local_csp_vault/vetkd/tests.rs | 6 +-- 9 files changed, 78 insertions(+), 82 deletions(-) delete mode 100644 rs/crypto/internal/crypto_lib/bls12_381/vetkd/src/ro.rs diff --git a/Cargo.lock b/Cargo.lock index 667c4c298aa..f3b1d09628f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7621,7 +7621,6 @@ dependencies = [ "hex", "ic-crypto-internal-bls12-381-type", "ic-crypto-test-utils-reproducible-rng", - "ic-sha3 1.0.0", "rand 0.8.5", "rand_chacha 0.3.1", ] diff --git a/rs/crypto/internal/crypto_lib/bls12_381/type/src/lib.rs b/rs/crypto/internal/crypto_lib/bls12_381/type/src/lib.rs index 57fe1df6d3c..b4a9fb47aba 100644 --- a/rs/crypto/internal/crypto_lib/bls12_381/type/src/lib.rs +++ b/rs/crypto/internal/crypto_lib/bls12_381/type/src/lib.rs @@ -31,7 +31,7 @@ pub type NodeIndex = u32; #[cfg(test)] mod tests; -use ic_bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve}; +use ic_bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField}; use itertools::multiunzip; use pairing::group::{ff::Field, Group}; use paste::paste; @@ -234,6 +234,18 @@ impl Scalar { Self::new(ic_bls12_381::Scalar::one()) } + /// Hash to scalar + /// + /// Uses the same mechanism as RFC 9380's hash_to_field except + /// targeting the scalar group. + pub fn hash(domain_sep: &[u8], input: &[u8]) -> Self { + let mut s = [ic_bls12_381::Scalar::zero()]; + ::hash_to_field::>( + input, domain_sep, &mut s, + ); + Self::new(s[0]) + } + /// Return true iff this value is zero pub fn is_zero(&self) -> bool { bool::from(self.value.is_zero()) diff --git a/rs/crypto/internal/crypto_lib/bls12_381/type/tests/tests.rs b/rs/crypto/internal/crypto_lib/bls12_381/type/tests/tests.rs index 4cc6aceafb8..7648996cfaa 100644 --- a/rs/crypto/internal/crypto_lib/bls12_381/type/tests/tests.rs +++ b/rs/crypto/internal/crypto_lib/bls12_381/type/tests/tests.rs @@ -1332,6 +1332,39 @@ fn test_verify_bls_signature_batch_with_same_pk() { } } +#[test] +fn test_hash_to_scalar_matches_known_values() { + // I was not able to locate any official test vectors for BLS12-381 hash_to_scalar + // so these were just generated using ic_bls12_381 itself. + + let dst = b"QUUX-V01-CS02-with-BLS12381SCALAR_XMD:SHA-256_SSWU_RO_"; + + scalar_test_encoding( + Scalar::hash(&dst[..], b""), + "3b3fdf74b194c0a0f683d67a312a4e72d663d74b8478dc7b56be41e0ce11caa1", + ); + + scalar_test_encoding( + Scalar::hash(&dst[..], b"abc"), + "47e7a8839695a3df27f202cf71e295a8554b47cef75c1e316b1865317720e188", + ); + + scalar_test_encoding( + Scalar::hash(&dst[..], b"abcdef0123456789"), + "3dff572f262e702f2ee8fb79b70e3225f5ee543a389eea2e58eec7b2bfd6afeb", + ); + + scalar_test_encoding( + Scalar::hash(&dst[..], format!("q128_{}", "q".repeat(128)).as_bytes()), + "2874c0e7814fcf42a5f63258417d4be8ea0465ff7352691493d0eca2dd5a9729", + ); + + scalar_test_encoding( + Scalar::hash(&dst[..], format!("a512_{}", "a".repeat(512)).as_bytes()), + "3cf6864b1a81fba0798c370f6daf9c23a838f9dbb96ea3a3a1145899ddf259b4", + ); +} + #[test] fn test_hash_to_g1_matches_draft() { /* diff --git a/rs/crypto/internal/crypto_lib/bls12_381/vetkd/BUILD.bazel b/rs/crypto/internal/crypto_lib/bls12_381/vetkd/BUILD.bazel index 039aea2f17d..7ec5c5b6967 100644 --- a/rs/crypto/internal/crypto_lib/bls12_381/vetkd/BUILD.bazel +++ b/rs/crypto/internal/crypto_lib/bls12_381/vetkd/BUILD.bazel @@ -5,7 +5,6 @@ package(default_visibility = ["//rs/crypto:__subpackages__"]) DEPENDENCIES = [ # Keep sorted. - "//packages/ic-sha3", "//rs/crypto/internal/crypto_lib/bls12_381/type", "@crate_index//:rand", ] diff --git a/rs/crypto/internal/crypto_lib/bls12_381/vetkd/Cargo.toml b/rs/crypto/internal/crypto_lib/bls12_381/vetkd/Cargo.toml index 091fe601ccb..539f80ff069 100644 --- a/rs/crypto/internal/crypto_lib/bls12_381/vetkd/Cargo.toml +++ b/rs/crypto/internal/crypto_lib/bls12_381/vetkd/Cargo.toml @@ -8,7 +8,6 @@ documentation.workspace = true [dependencies] ic-crypto-internal-bls12-381-type = { path = "../type" } -ic-sha3 = { path = "../../../../../../packages/ic-sha3" } rand = { workspace = true } [dev-dependencies] diff --git a/rs/crypto/internal/crypto_lib/bls12_381/vetkd/src/lib.rs b/rs/crypto/internal/crypto_lib/bls12_381/vetkd/src/lib.rs index e7c6bafd5b8..c57013b6f7d 100644 --- a/rs/crypto/internal/crypto_lib/bls12_381/vetkd/src/lib.rs +++ b/rs/crypto/internal/crypto_lib/bls12_381/vetkd/src/lib.rs @@ -10,8 +10,6 @@ use ic_crypto_internal_bls12_381_type::{G2Prepared, Gt, LagrangeCoefficients}; use rand::{CryptoRng, RngCore}; -mod ro; - /// The index of a node pub type NodeIndex = u32; @@ -21,18 +19,26 @@ pub struct DerivationPath { delta: Scalar, } +const DERIVATION_PATH_DOMAIN_SEP: &[u8; 44] = b"ic-crypto-vetkd-bls12-381-g2-derivation-path"; + impl DerivationPath { /// Create a new derivation path pub fn new>(canister_id: &[u8], extra_paths: &[U]) -> Self { - let mut ro = ro::RandomOracle::new("ic-crypto-vetkd-bls12-381-derivation-path"); + let mut combined_inputs = vec![]; - ro.update_bin(canister_id); + // Each input is prefixed with an 8 byte length field + let len = canister_id.len() as u64; + combined_inputs.extend_from_slice(&len.to_be_bytes()); + combined_inputs.extend_from_slice(canister_id); - for path in extra_paths { - ro.update_bin(path.as_ref()); + for input in extra_paths { + let len = input.as_ref().len() as u64; + combined_inputs.extend_from_slice(&len.to_be_bytes()); // 8 bytes length + combined_inputs.extend_from_slice(input.as_ref()); } - let delta = ro.finalize_to_scalar(); + let delta = Scalar::hash(DERIVATION_PATH_DOMAIN_SEP, &combined_inputs); + Self { delta } } diff --git a/rs/crypto/internal/crypto_lib/bls12_381/vetkd/src/ro.rs b/rs/crypto/internal/crypto_lib/bls12_381/vetkd/src/ro.rs deleted file mode 100644 index 96fb4677c4e..00000000000 --- a/rs/crypto/internal/crypto_lib/bls12_381/vetkd/src/ro.rs +++ /dev/null @@ -1,43 +0,0 @@ -use ic_crypto_internal_bls12_381_type::Scalar; -use ic_sha3::Shake256; - -#[derive(Clone)] -pub(crate) struct RandomOracle { - shake: Shake256, -} - -impl RandomOracle { - pub(crate) fn new(domain_separator: &str) -> Self { - let mut ro = Self { - shake: Shake256::default(), - }; - - ro.update_str(domain_separator); - - ro - } - - pub(crate) fn update_str(&mut self, s: &str) { - self.update_bin(s.as_bytes()); - } - - pub(crate) fn update_bin(&mut self, v: &[u8]) { - let v_len = v.len() as u64; - self.shake.update(v_len.to_be_bytes()); - self.shake.update(v); - } - - fn finalize(mut self, output: &mut [u8]) { - let o_len = output.len() as u64; - self.shake.update(o_len.to_be_bytes()); - - let mut xof = self.shake.finalize_xof(); - xof.read(output); - } - - pub(crate) fn finalize_to_scalar(self) -> Scalar { - let mut output = [0u8; 2 * Scalar::BYTES]; - self.finalize(&mut output); - Scalar::from_bytes_wide(&output) - } -} diff --git a/rs/crypto/internal/crypto_lib/bls12_381/vetkd/tests/tests.rs b/rs/crypto/internal/crypto_lib/bls12_381/vetkd/tests/tests.rs index 016f96bb3cd..ddd197d8980 100644 --- a/rs/crypto/internal/crypto_lib/bls12_381/vetkd/tests/tests.rs +++ b/rs/crypto/internal/crypto_lib/bls12_381/vetkd/tests/tests.rs @@ -110,15 +110,6 @@ fn random_derivation_path(rng: &mut R) -> DerivationPath DerivationPath::new(&canister_id, &extra_paths) } -fn shake256(bytes: &[u8]) -> [u8; 32] { - let mut output = [0u8; 32]; - let mut shake = ic_sha3::Shake256::new(); - shake.update(bytes); - let mut xof_reader = shake.finalize_xof(); - xof_reader.read(&mut output); - output -} - #[test] fn encrypted_key_share_creation_is_stable() { let mut rng = rand_chacha::ChaCha20Rng::seed_from_u64(1); @@ -136,27 +127,27 @@ fn encrypted_key_share_creation_is_stable() { let derivation_path = random_derivation_path(&mut rng); let did = rng.gen::<[u8; 28]>(); - const EXPECTED_EKS_HASH: [&str; NODES] = [ - "cb3075ecd2c5cd946dcfaf8486031cf7bbca656092aada97644307c598af5073", - "fbfaa432bd0fc9c60b456742368034d35d5fefceae1cdd027c27147ecc7f012c", - "7d6bd8ef201443fe5b570b62d244fc8192819f53783a1ae81e3fe747a793decd", - "12f894d3ddce41e5fa0cb1cd05e77d50ed214248cba809a9fe6fccf5515f5c13", - "d187e7bf4defab92ce9c82c692a9b3c3de65994cd4bf422438cbc515a8a48501", - "9aa47ad2dcf06a6a308152d935698684799714c772dfaf2b6654e6642a11937b", - "d96c4f897ba7e7ac657d363171b324adbad2faf13ef395bd0efa28a60273583d", - "fc3b2d0eae4c7d96212058d815d48701267a673c20197a08f28762301043405d", - "85ba21972c3a7717922d6bc734217d5dae12be7df42ffb93604e0ae7228ac9ed", - "3f7b6c16d35118519c92d31e074633f00fe30b6e4b522cda47d81088dc4011bb", - "658383824de7771db64a87b884ffbefe92567baeb851229785d9f3717c91bdda", - "e7a69b641278a5a8084938d1c03de2850a6639a882713a190fe0417f678e118e", - "d59283b0a48181d93c6e1a22e5ff9130df68a01bcadf96a25d37a2a092cb1a1a", + const EXPECTED_EKS_VALUE: [&str; NODES] = [ + "a9940d0137c71275b4d11bf9a80bcf484a93e777a20734b95dd57e835ac484661f4056bbd4fa3ed218f50d967617c2b781516e7bd2baace117b77803686953aaebba83589a10a15b7a139da6dc3f1093561ccce5ded1ad7bfed4e35a198a0a0e14936d6a37ebba930bfae338185ac104c4ef67f899c0cc25467f80f09a886574dbc0642a6ed1a2516d4f7c90c9128610ae6d24d5bea60cdc939f5ebe2a98ce274cbb57003db162bac370a255eeab6ba1ff639d19fe97d61045a671162b15fe60", +"b9d2a21b758fc961c8a98dc7aa5a5597bcbc365134d3da90527a6b60a30831803afd52fc5e0163c5fd43154c0d2177f49317ddf48515edd84ccf2ecd8724f808b24ca44f10a684cdc4eb56f23c2f2a5152c44cbb59a0c70155aecda3f95710770582abdf6febb54b078a90b96af6fb1b27073dd10cd5366e1e6ce378d882b7251f361d4979388d0a8efe288811adb3b5a24f833bdb6fa7dda962970410ab4364c2a837781176187fa62ff51841f81a8f6bbea3e7b171ebb93d4ce11bd9be7b4d", +"811d9cd064cd7c6ab4a448c68b8b5fc8a5f08cd17c78d46469e810e8df3d1f8d3f1431ccf627e2198b72d850eb99b2e2b2dfe2c43ddf86e5403ba474a3a0ba596ddabecfe303eddfd70444057baaae733f92eca70509425f87633ce9d78a28040b559446e22f73d637b04c6eca6ecff71fb3d1a662d86ef22fa6c3ef8157c582e4afdc7a0047d4535c6e01e9c65eae7890de3f7362e9d027f847484e5e3a2734c57f02030c44425012f17277c825d8093bef8862bd69753c7ac0d80b45c4257c", +"aca469bb1dd96297c86044e6032c15fbf2a2c380764085be21d50cff4b9f52f5e32e8c7fd718d64b435334b4aa9f8016a4dbcedc7da27a8b23d4b254850a26e75cb7e744585f0bcf5fb4b7ffb7befa0a983cbff9de8d22be23f6674a32fd058a03f31140af7af5934a23ef4494fe1bd2ff3947dae35f4bc653388878fdea0a451d7bdd1ecaabec217faa1efcf24dd5a68aaae8ce07882503721cbbb0a6afed9bdfe611b6e6f70cf1f36300d296d97c284bdc1c06f2fad6c3b9b3f2bed39f6f6d", +"abc9b461b441ce17b4d581f5a82f0f21dfb0091c9c097e2f4ce7962adf7c14d14d86c9765dc2e296c66a02187699fbe396767f7fdbe594a787368bb59ef5d43b70bd0c7a39e07b27107cdc7fb06de7da15a2a33cd10593b65764c98a7591726a0f603451396769fdaf5bd3b69ff0b7f515b4fd1dd0629b3762bc3916b8c2e01d2bfd5ddebc9f638191cc919da5a5d619864bc64eaa58a9af0a48487c12040619f1d45e5c42959b7cc89e036caeb207e9e33def185af6b4f11c06ba9d7505b800", +"92ef0d8f00e18f23879783c51c72b9706c7ad0df2a2ba44d651c5c3d903431ca49732637a579eb8c423e2b79faf9fb0b95fb96958149013e2932286ccf93bcbb94386df13e0519927b231a39f9a40b82bcd7fea94490e574493fa38a7a809a2211f4f2296639157bfe932518a5b4d8ccee3ac4997dce11aab4ff31633f715c1b1c3a6cc3a9ee14b879d0c890721d0e3f833c9383b01a183c11076f08c6e70fa1d4e14189878614ba5c49631f013f2b680ec56d582bf1ca5ac17a96bee0c80925", +"8efef76050abe03984f8b22b1cd6075a89e361c8f70bd2b493185d710ff284cca2d32c41d05735f5d9f39a4ea9b5e2cb8b4b207ec0d535c835768f95a0a1e13018b185b77f6e506fe85c2014989113b2c59935f2e323db5913bdddff8b56aa5a000a6f4e4c409a9ead3945979e3914e903b75bf754587ffbcead720960dfbcb6407d193acc91734cf61ef47d8b8684c59575f5590e3a5cc065f065f9adf4e30cbf6fe3f2469eeacb0c8baae349f86f1c0d0ac6dafb600c7a8feb05cbd8490fb5", +"80ac9ea725ec6101b18e316e7f8ff9cdbf7619549b5b9cea767203deb4b0e0496c1c84e828e74268d2368937e7f9a34195b3697321df62a084bcc2cc2bf079ce2638625bbf243d906130a700908ce481dbbe1d32b014e89baef97cc10aa6f914092a904efd5d904a1ed3687361a186126858f14bc84cdd00cc0c4671313874f1ebedf1bddc3bb3d0484e23dfc754a6768b55ea732ff9c02a374c8e1fef793c01b37c464099a2863cb4c15469c4d867083db6924fb4cceda762d10a01777cdc97", +"92b3a643f608fd4ccbb7644cbb1b38ac39a6cb4461846c6cf79c3610a411fd4e8709b960260966586f67e2e0d6cf6c5b85b45c442c3f78bda0666389631a8a8c8b1c642eaa4c0214781c6ada600fd1a7663b43b844a6f7a96741315674090b7f10534d4789de9fc1829b7a97f30dedd86b8f801ac01c1f8a8e1fb247cec9f726dabe43a6f246e7248b8a6675370c47318a3c4788b4eb5092b283f7f81f6a5903c35f024159b8fc5bd2a01a4dc118b1855bcbd409abe6d54bc3daaf795694a580", +"807e2fa6fe3e4bec12953acc2b57bf4cd49614cfbccb393bbaa4a04d614f1b85a7bbae48ed5c147e05786a7a29494c2092cfd5bd41d088213fa676abd137ad6e4511ff65811c7622068d995c1d38e36f5a90284c7a5769510c1e71fb46bace121742873aa75b1956f331d13e7541fdd862d617965ed541376598a541bf2bc47e370f728e86c909b943b1db0cf6e38dba8b4ef50a2cb42410b5ccd9c2788a49df08585284c4dadeb7ff93befe0beb46ca998041ece1dd36832e5e44783f0e6005", +"b90e01a902b1af0a9039d32b1325c030424791d2a7c72dbb68cf17c962b80e2897b29bccf1bda9587f62d019098938b1b68aaf0628576dd28569de2bbc705ac51ae9e69552e912894b47d87d3af588466ad30316de574dd7663129ee342c949f101501911075cb27af2255635e9ea68401ccda1edf127ec3472d1018b0e1a012d352849ef40b835ab5357785798bbd15a90db5211cc61dd44b1660a6a2d68deb708d772b8b32fa29e9c5890ffd812709913c0488c159fe44bb8582677d171950", +"b653a21c480fccfb9b69419240cdb530e18f23b8a2bc5296073e8bea44f49104172158f2e2deec0cc1f6ed44d667bab5ab0cabfb9bd23b775fc903c2d2ddb3874c522c54e3465ed7ebbb8cec4f3e0bbca5537c8e4792a4bd0ce0e81196c49e14046ea5482f08f760d3be6b524a86f81771934cc91e1f6ed5aafc38f236ae74eba2d3d21458c0d19907b762a8fa2b860dac60873dda48bca15a4363b1271354e6623438ca5075e19a6f967c4912d8ce193e3f1f9de3f20c26a4d3bc2f7041b319", +"8489576ac848031c6e90ad9e0477fc72e766ccf00894db8ae9536415d817a4478726e4e1247ffa8656209a23ca261fa088312490c4d7b4f029f9b944196e4e3f1eb52c6a89e1ba9fc87f42c09ed3ae1490d10cdbda013863c775f00f3d070de413af98b3b688a397ab4124bc316876a4c015059607040c9faed27191db1e875e072c923fb4575f771cdb70f4ef0fbd7390b77f611ca8840b31d5d353a0039b011fc798ba380c99ed00ab4a3fedba0ded56dd630655431784d1a02767f44bfa85", ]; - for (node_idx, expected_eks_hash) in EXPECTED_EKS_HASH.iter().enumerate() { + for (node_idx, expected_eks) in EXPECTED_EKS_VALUE.iter().enumerate() { let node_sk = poly.evaluate_at(&Scalar::from_node_index(node_idx as u32)); let eks = EncryptedKeyShare::create(&mut rng, &master_pk, &node_sk, &tpk, &derivation_path, &did); - assert_eq!(hex::encode(shake256(&eks.serialize())), *expected_eks_hash,); + assert_eq!(*expected_eks, hex::encode(eks.serialize())); } } diff --git a/rs/crypto/internal/crypto_service_provider/src/vault/local_csp_vault/vetkd/tests.rs b/rs/crypto/internal/crypto_service_provider/src/vault/local_csp_vault/vetkd/tests.rs index 68108017335..af79065b791 100644 --- a/rs/crypto/internal/crypto_service_provider/src/vault/local_csp_vault/vetkd/tests.rs +++ b/rs/crypto/internal/crypto_service_provider/src/vault/local_csp_vault/vetkd/tests.rs @@ -36,9 +36,9 @@ fn should_correctly_create_encrypted_vetkd_key_share_for_smoke_test_vector() { f3e68e13f62604d027660883213c90ea72810bcecee58b883fb62118e538243\ 03718e6876ea400d083beb0439d3934122a4c4b2e58e3f145305b9a0c0a00e3\ 2dd808574dec2605dbc7f122fe593ca0c07ca92720d0f17b7d53c9c68dbb93d\ - 489078859e5e5fe2b6612ac9536fe7f8b463cac948e6db97908b7f5a67b33b3\ - e60a1c160889ef49448519a84be1aba0611829a7cca180cbbdf94f7b2cda2db\ - 6b14c65", + 489078859e5e5fe2b6612ac9536fe7f8b463cafcc003efe88f1cddd26499322\ + 9229edb5ec96ea3fd2d083b815776b6cc7e03586d864374ba54d44c09201db8\ + d33dd44", ) .expect("invalid test vector") ))