From 2ad918d3f1fd856d4eacb4d4c03a4aa593104008 Mon Sep 17 00:00:00 2001 From: Alexander Wagner Date: Wed, 11 Sep 2024 18:08:19 +0200 Subject: [PATCH] Add SST impl --- README.md | 35 +++ benches/mod.rs | 8 +- examples/lms-demo.rs | 6 +- examples/sst-demo.rs | 417 ++++++++++++++++++++++++++ src/constants.rs | 31 +- src/hss/aux.rs | 20 +- src/hss/definitions.rs | 139 +++++++-- src/hss/mod.rs | 33 +- src/hss/reference_impl_private_key.rs | 118 ++++++-- src/hss/signing.rs | 9 +- src/hss/verify.rs | 8 +- src/lib.rs | 16 +- src/lms/definitions.rs | 14 +- src/lms/helper.rs | 6 +- src/lms/mod.rs | 5 +- src/lms/signing.rs | 1 + src/lms/verify.rs | 1 + src/sst/gen_key.rs | 232 ++++++++++++++ src/sst/helper.rs | 213 +++++++++++++ src/sst/mod.rs | 5 + src/sst/parameters.rs | 37 +++ tests/reference_implementation.rs | 5 +- 22 files changed, 1265 insertions(+), 94 deletions(-) create mode 100644 examples/sst-demo.rs create mode 100644 src/sst/gen_key.rs create mode 100644 src/sst/helper.rs create mode 100644 src/sst/mod.rs create mode 100644 src/sst/parameters.rs diff --git a/README.md b/README.md index af58f5c8..42a77210 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,41 @@ HBS_LMS_MAX_HASH_OPTIMIZATIONS=1000 HBS_LMS_THREADS=2 cargo run --release --exam cargo run --release --example lms-demo -- verify mykey message.txt ``` +The SST extension can be used as follows: + +``` +# Key generation: prepare +# Generates intermediate node, generates or reads the tree identifier (init_tree_ident 1/0), and uses "mykey" as filename base. +# One dedicated signing entity has to create the common L-0 tree identifier (--init_tree_ident=1) before other signing entities +# can generate their subtrees. +# +# The following example uses two HSS levels, first with tree height = 10 / Winternitz = 8, second with 5 / 2. +# First, a signing entity (here: 1 of 8) creates the tree identifier +cargo run --release --example sst-demo -- prepare_keygen mykey 10/8,5/2 --ssts=1/8 --auxsize=2048 \ + --seed=c912a74bc8c5fc1b2a73b96e6ce1eb2317dc9aa49806b30e --init_tree_ident +# The signing instance index is 3 of total 8, and this signing entity will use the tree identifier and use another secret seed. +# This will use "mykey.5.prv" and "mykey.5.aux" for private key and aux data, and "mykey_treeident.bin" to write the tree identifier +seq 2 8 | xargs -i{} cargo run --release --example sst-demo -- prepare_keygen mykey 10/8,5/2 --ssts={}/8 --auxsize=2048 \ + --seed=1eb2317dc9aa49806b30e578436d0f659b1f5c912a74bc8c + +# Key generation: finalize +# After all signing entities have created their intermediate node values, the public key can be generated. +# This will use mykey.5.pub to write the public key for signing entity index 5. +cargo run --release --example sst-demo -- finalize_keygen mykey 5 + +# Signing +# Generates `message.txt.sig` using mykey.5.prv +cargo run --release --example sst-demo -- sign mykey 5 message.txt + +# Verification +# Verifies `message.txt` with `message.txt.sig` against `mykey.5.pub` +cargo run --release --example sst-demo -- verify mykey.5 message.txt + +# Verification can as well performed with lms-demo +# Verifies `message.txt` with `message.txt.sig` against `mykey.5.pub` +cargo run --release --example lms-demo -- verify mykey.5 message.txt +``` + ## Naming conventions wrt to the IETF RFC The naming in the RFC is done by using a single character. To allow for a better understanding of the implementation, we have decided to use more descriptive designations. diff --git a/benches/mod.rs b/benches/mod.rs index 7ede62d2..47554c39 100644 --- a/benches/mod.rs +++ b/benches/mod.rs @@ -135,7 +135,7 @@ mod tests { b.iter(|| { let mut signing_key = signing_key.clone(); signing_key - .try_sign_with_aux(&MESSAGE, Some(aux_slice)) + .try_sign_with_aux(&MESSAGE, Some(aux_slice), None) .unwrap() }); } @@ -153,7 +153,7 @@ mod tests { b.iter(|| { let mut signing_key = signing_key.clone(); signing_key - .try_sign_with_aux(&MESSAGE, Some(aux_slice)) + .try_sign_with_aux(&MESSAGE, Some(aux_slice), None) .unwrap() }); } @@ -171,7 +171,7 @@ mod tests { b.iter(|| { let mut signing_key = signing_key.clone(); signing_key - .try_sign_with_aux(&MESSAGE, Some(aux_slice)) + .try_sign_with_aux(&MESSAGE, Some(aux_slice), None) .unwrap() }); } @@ -203,7 +203,7 @@ mod tests { b.iter(|| { let mut signing_key = signing_key.clone(); signing_key - .try_sign_with_aux(&MESSAGE, Some(aux_slice)) + .try_sign_with_aux(&MESSAGE, Some(aux_slice), None) .unwrap() }); } diff --git a/examples/lms-demo.rs b/examples/lms-demo.rs index 1a65905e..48eaa5c7 100644 --- a/examples/lms-demo.rs +++ b/examples/lms-demo.rs @@ -39,7 +39,7 @@ impl DemoError { } } -type Hasher = Sha256_256; +type Hasher = Sha256_192; struct GenKeyParameter { parameter: Vec>, @@ -95,7 +95,7 @@ fn main() -> Result<(), Box> { if let Some(args) = matches.subcommand_matches(VERIFY_COMMAND) { let result = verify(args); if result { - println!("Successful!"); + println!("Verification successful!"); exit(0); } else { println!("Wrong signature"); @@ -146,6 +146,7 @@ fn sign(args: &ArgMatches) -> Result<(), std::io::Error> { &private_key_data, &mut private_key_update_function, Some(aux_slice), + None, ) } else { hbs_lms::sign::( @@ -153,6 +154,7 @@ fn sign(args: &ArgMatches) -> Result<(), std::io::Error> { &private_key_data, &mut private_key_update_function, None, + None, ) }; diff --git a/examples/sst-demo.rs b/examples/sst-demo.rs new file mode 100644 index 00000000..de1c63c9 --- /dev/null +++ b/examples/sst-demo.rs @@ -0,0 +1,417 @@ +use clap::{Arg, ArgAction, ArgMatches, Command}; +use hbs_lms::*; +use std::{ + convert::TryFrom, + convert::TryInto, + error::Error, + fmt, + fs::{read, File, OpenOptions}, + io::{Read, Write}, + process::exit, +}; +use tinyvec::ArrayVec; + +const GENKEY1_COMMAND: &str = "prepare_keygen"; +const GENKEY2_COMMAND: &str = "finalize_keygen"; +const VERIFY_COMMAND: &str = "verify"; +const SIGN_COMMAND: &str = "sign"; + +const ARG_KEYNAME: &str = "keyname"; +const ARG_MESSAGE: &str = "file"; +const ARG_HSS_PARAMETER: &str = "hss"; +const ARG_SIGN_ENTITY_IDX: &str = "se_param"; +const ARG_SEED: &str = "seed"; +const ARG_AUXSIZE: &str = "auxsize"; +const ARG_INIT_TREE_IDENT: &str = "init_tree_ident"; +const ARG_SSTS_PARAM: &str = "ssts"; + +const AUX_DATA_DEFAULT_SIZE: usize = 100_000_000; + +#[derive(Debug)] +struct DemoError(String); + +impl fmt::Display for DemoError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "There is an error: {}", self.0) + } +} + +impl Error for DemoError {} + +type Hasher = Sha256_192; + +struct GenKeyParameter { + hss_parameters: ArrayVec<[HssParameter; hbs_lms::REF_IMPL_MAX_ALLOWED_HSS_LEVELS]>, + sst_extension: SstExtension, + aux_data: usize, +} + +impl GenKeyParameter { + pub fn new( + hss_parameters: ArrayVec<[HssParameter; hbs_lms::REF_IMPL_MAX_ALLOWED_HSS_LEVELS]>, + sst_extension: SstExtension, + aux_data: Option, + ) -> Self { + let aux_data = aux_data.unwrap_or(AUX_DATA_DEFAULT_SIZE); + Self { + hss_parameters, + sst_extension, + aux_data, + } + } +} + +fn main() -> Result<(), Box> { + let command = Command::new("SSTS Demo") + .about("Generates SSTS keys and uses them for signing and verifying.") + .subcommand( + Command::new(GENKEY1_COMMAND) + .arg(Arg::new(ARG_KEYNAME).required(true)) + .arg(Arg::new(ARG_HSS_PARAMETER).required(true).help( + "Specify LMS parameters (e.g. 10/2 => tree height = 10, Winternitz parameter = 2)")) + .arg(Arg::new(ARG_SSTS_PARAM).long(ARG_SSTS_PARAM).required(true).takes_value(true).value_name(ARG_SSTS_PARAM) + .help( + "Specify SSTS parameters (e.g. --ssts=3/8 => signing entity 3 of total 8")) + .arg(Arg::new(ARG_INIT_TREE_IDENT).long(ARG_INIT_TREE_IDENT).action(ArgAction::SetTrue).help("Announce initialization of tree identifier")) + .arg(Arg::new(ARG_AUXSIZE).long(ARG_AUXSIZE).required(false).takes_value(true).value_name(ARG_AUXSIZE).help( + "Specify AUX data size in bytes")) + .arg(Arg::new(ARG_SEED).long(ARG_SEED).required(true).takes_value(true).value_name(ARG_SEED)), + ) + .subcommand( + Command::new(GENKEY2_COMMAND) + .arg(Arg::new(ARG_KEYNAME).required(true)) + .arg(Arg::new(ARG_SIGN_ENTITY_IDX).required(true).help( + "Specify signing entity index (1..n))")) + ) + .subcommand( + Command::new(VERIFY_COMMAND) + .arg(Arg::new(ARG_KEYNAME).required(true)) + .arg(Arg::new(ARG_MESSAGE).required(true).help("File to verify"))) + .subcommand( + Command::new(SIGN_COMMAND) + .arg(Arg::new(ARG_KEYNAME).required(true)) + .arg(Arg::new(ARG_MESSAGE).required(true)) + ); + + let matches = command.get_matches(); + + if let Some(args) = matches.subcommand_matches(GENKEY1_COMMAND) { + prepare_keygen(args)?; + println!( + "Single-subtree-structure: intermediate node and private key successfully generated!" + ); + return Ok(()); + } + + if let Some(args) = matches.subcommand_matches(GENKEY2_COMMAND) { + finalize_keygen(args)?; + println!("Single-subtree-structure: public key successfully generated!"); + return Ok(()); + } + + if let Some(args) = matches.subcommand_matches(SIGN_COMMAND) { + sign(args)?; + println!("Signature successfully generated!"); + return Ok(()); + } + + if let Some(args) = matches.subcommand_matches(VERIFY_COMMAND) { + let result = verify(args); + if result { + println!("Verification successful!"); + return Ok(()); + } else { + println!("Verification failed!"); + exit(-1); + } + } + + Ok(()) +} + +fn sign(args: &ArgMatches) -> Result<(), std::io::Error> { + let keyname = get_parameter(ARG_KEYNAME, args); + let message_name = get_parameter(ARG_MESSAGE, args); + + let private_key_filename = get_private_key_filename(&keyname, None); + let signature_filename = get_signature_filename(&message_name); + + let private_key_data = read_file(&private_key_filename); + let message_data = read_file(&message_name); + + let aux_data_filename = get_aux_filename(&keyname, None); + let mut aux_data = read(&aux_data_filename) + .unwrap_or_else(|_| panic!("{} file cannot be read", &aux_data_filename)); + + let tree_ident_filedata = read_file(&format!("{}_treeident.bin", keyname)); + let tree_identifier: LmsTreeIdentifier = tree_ident_filedata + .try_into() + .unwrap_or_else(|_| panic!("Tree identifier has wrong length")); + + let mut private_key_update_function = + |new_key: &[u8]| write(&private_key_filename, new_key).map_err(|_| ()); + + let signature = hbs_lms::sign::( + &message_data, + &private_key_data, + &mut private_key_update_function, + Some(&mut &mut aux_data[..]), + Some(&tree_identifier), + ) + .unwrap_or_else(|_| panic!("Signing failed")); + + write(&signature_filename, signature.as_ref()) +} + +fn verify(args: &ArgMatches) -> bool { + let keyname: String = get_parameter(ARG_KEYNAME, args); + let message_name: String = get_parameter(ARG_MESSAGE, args); + + let public_key_name = get_public_key_filename(&keyname, None); + let signature_name = get_signature_filename(&message_name); + + let signature_data = read_file(&signature_name); + let message_data = read_file(&message_name); + let public_key_data = read_file(&public_key_name); + + hbs_lms::verify::(&message_data, &signature_data, &public_key_data).is_ok() +} + +fn get_filename(filename: &str, idx: Option, suffix: &str) -> String { + idx.map_or(format!("{}.{}", filename, suffix), |idx| { + format!("{}.{}.{}", filename, &idx, suffix) + }) +} + +fn get_public_key_filename(keyname: &str, idx: Option) -> String { + get_filename(keyname, idx, "pub") +} + +fn get_signature_filename(message_name: &str) -> String { + get_filename(message_name, None, "sig") +} + +fn get_private_key_filename(private_key: &str, idx: Option) -> String { + get_filename(private_key, idx, "prv") +} + +fn get_aux_filename(keyname: &str, idx: Option) -> String { + get_filename(keyname, idx, "aux") +} + +fn get_treeident_filename(keyname: &str) -> String { + format!("{}_treeident.bin", keyname) +} + +fn get_parameter(name: &str, args: &ArgMatches) -> String { + args.value_of(name) + .expect("Parameter must be present.") + .into() +} + +fn read_file(file_name: &str) -> Vec { + let mut data: Vec = Vec::new(); + std::fs::File::open(file_name) + .unwrap_or_else(|_| panic!("{} file could not be opened", file_name)) + .read_to_end(&mut data) + .unwrap_or_else(|_| panic!("{} file could not be read", file_name)); + data +} + +fn prepare_keygen(args: &ArgMatches) -> Result<(), Box> { + let keyname: String = get_parameter(ARG_KEYNAME, args); + + let arg_init_tree_ident = args.get_flag(ARG_INIT_TREE_IDENT); + let treeident_filename = get_treeident_filename(&keyname); + let mut tree_identifier = if !arg_init_tree_ident { + read_file(&treeident_filename) + .try_into() + .unwrap_or_else(|_| panic!("Tree identifier has wrong length")) + } else { + LmsTreeIdentifier::default() + }; + + let genkey_parameter = parse_genkey1_parameter( + &get_parameter(ARG_HSS_PARAMETER, args), + &get_parameter(ARG_SSTS_PARAM, args), + &get_parameter(ARG_AUXSIZE, args), + ); + let sst_extension = genkey_parameter.sst_extension; + + let encoded_seed = args + .value_of(ARG_SEED) + .ok_or(DemoError("No seed given".to_string()))?; + let decoded_seed = hex::decode(encoded_seed)?; + (decoded_seed.len() == Hasher::OUTPUT_SIZE.into()) + .then_some(()) + .ok_or(DemoError(format!( + "Seed length is {} bytes, but length of {} bytes is expected", + decoded_seed.len(), + Hasher::OUTPUT_SIZE + )))?; + let mut seed = Seed::::default(); + seed.as_mut_slice().copy_from_slice(&decoded_seed[..]); + + let mut aux_data = vec![0u8; genkey_parameter.aux_data]; + let aux_slice: &mut &mut [u8] = &mut &mut aux_data[..]; + + // create our private key + let (signing_key, intermed_node_hashval) = prepare_sst_keygen( + &genkey_parameter.hss_parameters, + &sst_extension, + &seed, + Some(aux_slice), + &mut tree_identifier, + ) + .unwrap_or_else(|_| panic!("Could not generate keys")); + + let private_key_filename = + get_private_key_filename(&keyname, Some(sst_extension.signing_entity_idx())); + write(private_key_filename.as_str(), signing_key.as_slice())?; + + // write own node value and signing entity to file + let interm_node_filename = format!("node_si.{}.bin", sst_extension.signing_entity_idx()); + + // if file exists, overwrite + write( + interm_node_filename.as_str(), + &sst_extension.signing_entity_idx().to_be_bytes(), + )?; + // and append + let mut intermed_node_file = OpenOptions::new() + .create(true) + .append(true) + .open(interm_node_filename.as_str()) + .unwrap(); + intermed_node_file.write_all(intermed_node_hashval.as_slice())?; + + let aux_filename: String = get_aux_filename(&keyname, Some(sst_extension.signing_entity_idx())); + write(&aux_filename, aux_slice)?; + + Ok(write(&treeident_filename, &tree_identifier)?) +} + +fn finalize_keygen(args: &ArgMatches) -> Result<(), Box> { + // get signing entity number and name of private keyfile from args + let keyname: String = get_parameter(ARG_KEYNAME, args); + let signing_entity: u8 = get_parameter(ARG_SIGN_ENTITY_IDX, args) + .parse::() + .unwrap(); + + // AUX data: created in genkey1, here we read the file + let aux_filename: String = get_aux_filename(&keyname, Some(signing_entity)); + let mut aux_data_v: Vec = read_file(&aux_filename); + let aux_slice: &mut &mut [u8] = &mut &mut aux_data_v[..]; + + // read private key + let private_key_name = get_private_key_filename(&keyname, Some(signing_entity)); + let private_key_data = read_file(&private_key_name); + + // here we need one additional API call to know which files we have to read dep. on HSS config. + let num_signing_entities = get_num_signing_entities::(&private_key_data) + .unwrap_or_else(|_| panic!("genkey step 2: invalid config")); + + // read intermediate node values from files (ours and others) and pass for calc. + + let mut node_array = + ArrayVec::<[ArrayVec<[u8; MAX_HASH_SIZE]>; MAX_SSTS_SIGNING_ENTITIES]>::new(); + + for idx in 1..=num_signing_entities { + let file_data: Vec = read_file(&format!("node_si.{idx}.bin")); + (file_data.len() == (1 + Hasher::OUTPUT_SIZE as usize)) + .then_some(()) + .unwrap_or_else(|| { + panic!( + "genkey2(): intermediate node file size is {}, should be {}", + file_data.len(), + (1 + MAX_HASH_SIZE) + ) + }); + let node = ArrayVec::<[u8; MAX_HASH_SIZE]>::try_from(&file_data[1..]).unwrap(); + node_array.push(node); + } + + let treeident_filename = get_treeident_filename(&keyname); + let tree_ident_filedata = read_file(&treeident_filename); + let tree_identifier = tree_ident_filedata + .try_into() + .unwrap_or_else(|_| panic!("Tree identifier has wrong length")); + + let verifying_key = finalize_sst_keygen::( + &private_key_data, + &node_array, + Some(aux_slice), + &tree_identifier, + ) + .unwrap_or_else(|_| panic!("Could not generate verifying key")); + + write(&aux_filename, aux_slice)?; + + let public_key_filename = get_public_key_filename(&keyname, Some(signing_entity)); + Ok(write(&public_key_filename, verifying_key.as_slice())?) +} + +fn parse_genkey1_parameter(hss_params: &str, ssts_params: &str, auxsize: &str) -> GenKeyParameter { + let mut vec_hss_params: ArrayVec<[_; hbs_lms::REF_IMPL_MAX_ALLOWED_HSS_LEVELS]> = + Default::default(); + + let auxsize: usize = auxsize.parse().expect("Could not parse aux data size"); + let aux_data_size = (auxsize != 0).then_some(auxsize); + + for hss_param in hss_params.split(',') { + let mut splitted = hss_param.split('/'); + let height: u8 = splitted + .next() + .expect("Splitted does not contain height") + .parse::() + .expect("Parsing of height failed"); + let winternitz_parameter: u8 = splitted + .next() + .expect("Splitted does not contain winternitz_parameter") + .parse::() + .expect("Parsing of winternitz_parameter failed"); + + let lms = match height { + 5 => LmsAlgorithm::LmsH5, + 10 => LmsAlgorithm::LmsH10, + 15 => LmsAlgorithm::LmsH15, + 20 => LmsAlgorithm::LmsH20, + 25 => LmsAlgorithm::LmsH25, + _ => panic!("Height not supported"), + }; + let lm_ots = match winternitz_parameter { + 1 => LmotsAlgorithm::LmotsW1, + 2 => LmotsAlgorithm::LmotsW2, + 4 => LmotsAlgorithm::LmotsW4, + 8 => LmotsAlgorithm::LmotsW8, + _ => panic!("Wrong winternitz parameter"), + }; + + vec_hss_params.push(HssParameter::new(lm_ots, lms)); + } + + let mut splitted = ssts_params.split('/'); + let si_idx: u8 = splitted + .next() + .expect("Splitted does not contain si_idx") + .parse::() + .expect("Parsing of si_idx failed"); + let total_num_si: u8 = splitted + .next() + .expect("Splitted does not contain total_num_si") + .parse::() + .expect("Parsing of total_num_si failed"); + + let l0_top_div = ((total_num_si as f32).log2().fract() == 0.0) + .then_some((total_num_si as f32).log2() as u8) + .unwrap(); + + let sst_extension = SstExtension::new(si_idx, l0_top_div).unwrap(); + + GenKeyParameter::new(vec_hss_params, sst_extension, aux_data_size) +} + +fn write(filename: &str, content: &[u8]) -> Result<(), std::io::Error> { + let mut file = File::create(filename)?; + file.write_all(content) +} diff --git a/src/constants.rs b/src/constants.rs index 1f60c5fe..3ad8a024 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -43,10 +43,19 @@ pub const fn prng_len(seed_len: usize) -> usize { 23 + seed_len } -pub const HSS_COMPRESSED_USED_LEAFS_SIZE: usize = 8; +pub const HSS_COMPRESSED_USED_LEAFS_SIZE: usize = size_of::(); pub const REF_IMPL_MAX_ALLOWED_HSS_LEVELS: usize = 8; + +pub const SST_SIGNING_ENTITY_IDX_SIZE: usize = size_of::(); +pub const SST_L0_TOP_DIV_SIZE: usize = size_of::(); +pub const SST_SIZE: usize = SST_SIGNING_ENTITY_IDX_SIZE + SST_L0_TOP_DIV_SIZE; + +pub const SST_IMPL_MAX_PRIVATE_KEY_SIZE: usize = + SST_SIZE + HSS_COMPRESSED_USED_LEAFS_SIZE + REF_IMPL_MAX_ALLOWED_HSS_LEVELS + MAX_SEED_LEN; pub const REF_IMPL_MAX_PRIVATE_KEY_SIZE: usize = HSS_COMPRESSED_USED_LEAFS_SIZE + REF_IMPL_MAX_ALLOWED_HSS_LEVELS + MAX_SEED_LEN; +// TODO Rework if `const_trait_impl` is in stable +pub const IMPL_MAX_PRIVATE_KEY_SIZE: usize = SST_IMPL_MAX_PRIVATE_KEY_SIZE; pub const MAX_HASH_SIZE: usize = 32; pub const MAX_HASH_BLOCK_SIZE: usize = 64; @@ -69,10 +78,14 @@ pub const MAX_HSS_SIGNED_PUBLIC_KEY_LENGTH: usize = hss_signed_public_key_length(MAX_HASH_SIZE, MAX_NUM_WINTERNITZ_CHAINS, MAX_TREE_HEIGHT); pub const MAX_HSS_SIGNATURE_LENGTH: usize = get_hss_signature_length(); +pub const MAX_SSTS_L0_TOP_DIV: u32 = 8; // top division height for Single-Subtree-scheme +pub const MAX_SSTS_SIGNING_ENTITIES: usize = 2usize.pow(MAX_SSTS_L0_TOP_DIV); + /// Calculated using the formula from RFC 8554 Appendix B /// https://datatracker.ietf.org/doc/html/rfc8554#appendix-B -const HASH_CHAIN_COUNTS: [usize; 12] = [136, 200, 265, 68, 101, 133, 35, 51, 67, 18, 26, 34]; +const NUM_WINTERNITZ_CHAINS: [usize; 12] = [136, 200, 265, 68, 101, 133, 35, 51, 67, 18, 26, 34]; +// RFC 8554: "p"; see terminology: "single Winternitz chain", "number of independent Winternitz chains" pub const fn get_num_winternitz_chains(winternitz_parameter: usize, output_size: usize) -> usize { let w_i = match winternitz_parameter { 1 => 0usize, @@ -89,13 +102,13 @@ pub const fn get_num_winternitz_chains(winternitz_parameter: usize, output_size: _ => panic!("Invalid Output Size. Allowed is: 16, 24 or 32"), }; - HASH_CHAIN_COUNTS[w_i * 3 + o_i] + NUM_WINTERNITZ_CHAINS[w_i * 3 + o_i] } -pub const fn lmots_signature_length(hash_size: usize, hash_chain_count: usize) -> usize { +pub const fn lmots_signature_length(hash_size: usize, num_winternitz_chains: usize) -> usize { size_of::() // LMOTS Parameter TypeId + hash_size // Signature Randomizer - + (hash_size * hash_chain_count) // Signature Data + + (hash_size * num_winternitz_chains) // Signature Data } pub const fn lms_public_key_length(hash_size: usize) -> usize { @@ -107,21 +120,21 @@ pub const fn lms_public_key_length(hash_size: usize) -> usize { pub const fn lms_signature_length( hash_size: usize, - hash_chain_count: usize, + num_winternitz_chains: usize, tree_height: usize, ) -> usize { size_of::() // LMS Leaf Identifier - + lmots_signature_length(hash_size, hash_chain_count) // LMOTS Signature + + lmots_signature_length(hash_size, num_winternitz_chains) // LMOTS Signature + size_of::() // LMS Parameter TypeId + (hash_size * tree_height) // Authentication Path } pub const fn hss_signed_public_key_length( hash_size: usize, - hash_chain_count: usize, + num_winternitz_chains: usize, tree_height: usize, ) -> usize { - lms_signature_length(hash_size, hash_chain_count, tree_height) // LMS Signature + lms_signature_length(hash_size, num_winternitz_chains, tree_height) // LMS Signature + MAX_LMS_PUBLIC_KEY_LENGTH // LMS PublicKey } diff --git a/src/hss/aux.rs b/src/hss/aux.rs index 424c4633..d3c7798f 100644 --- a/src/hss/aux.rs +++ b/src/hss/aux.rs @@ -33,16 +33,28 @@ pub struct MutableExpandedAuxData<'a> { pub hmac: &'a mut [u8], } +// Rework: +// In case of SST, use aux_data for nodes lower than the SST root nodes pub fn hss_optimal_aux_level( mut max_length: usize, lms_parameter: LmsParameter, actual_len: Option<&mut usize>, + opt_l0_top_div: Option, ) -> AuxLevel { let mut aux_level = AuxLevel::default(); let size_hash = lms_parameter.get_hash_function_output_size(); let orig_max_length = max_length; + // If SST is used, reserve space for SST root nodes + if let Some(l0_top_div) = opt_l0_top_div { + aux_level |= 0x80000000 | (1 << l0_top_div); + + let sst_nodes_size = 2usize.pow(l0_top_div as u32) * size_hash; + // Saturated sub to avoid underflow, safe to use because leftovers for markers needed! + max_length = max_length.saturating_sub(sst_nodes_size); + } + if max_length < AUX_DATA_HASHES + size_hash { if let Some(actual_len) = actual_len { *actual_len = 1; @@ -51,8 +63,9 @@ pub fn hss_optimal_aux_level( } max_length -= AUX_DATA_HASHES + size_hash; - let h0 = lms_parameter.get_tree_height().into(); - for level in (1..=h0).rev().step_by(MIN_SUBTREE) { + // If SST is used, exclude SST root node layer, else include leaf node layer, i.e. height + 1 + let h0 = opt_l0_top_div.unwrap_or(lms_parameter.get_tree_height() + 1); + for level in (1..h0).rev().step_by(MIN_SUBTREE) { let len_this_level = size_hash << level; if max_length >= len_this_level { @@ -131,10 +144,11 @@ pub fn hss_expand_aux_data<'a, H: HashChain>( pub fn hss_get_aux_data_len( max_length: usize, lms_parameter: LmsParameter, + opt_l0_top_div: Option, ) -> usize { let mut len = 0; - if hss_optimal_aux_level(max_length, lms_parameter, Some(&mut len)) == 0 { + if hss_optimal_aux_level(max_length, lms_parameter, Some(&mut len), opt_l0_top_div) == 0 { return 1; } diff --git a/src/hss/definitions.rs b/src/hss/definitions.rs index cdcae158..8185bd56 100644 --- a/src/hss/definitions.rs +++ b/src/hss/definitions.rs @@ -3,27 +3,30 @@ use core::convert::TryInto; use tinyvec::ArrayVec; use crate::{ - constants::{MAX_ALLOWED_HSS_LEVELS, MAX_HSS_PUBLIC_KEY_LENGTH}, + constants::{ + LmsTreeIdentifier, Node, MAX_ALLOWED_HSS_LEVELS, MAX_HSS_PUBLIC_KEY_LENGTH, + MAX_SSTS_SIGNING_ENTITIES, + }, hasher::HashChain, hss::aux::{ - hss_expand_aux_data, hss_finalize_aux_data, hss_optimal_aux_level, hss_store_aux_marker, + hss_expand_aux_data, hss_finalize_aux_data, hss_get_aux_data_len, hss_is_aux_data_used, + hss_optimal_aux_level, hss_save_aux_data, hss_store_aux_marker, MutableExpandedAuxData, }, lms::{ self, definitions::{InMemoryLmsPublicKey, LmsPrivateKey, LmsPublicKey}, generate_key_pair, parameters::LmsParameter, + signing::LmsSignature, }, + sst::helper::get_sst_root_node_idx, + sst::parameters::SstExtension, util::helper::read_and_advance, }; -use crate::{hss::aux::hss_get_aux_data_len, lms::signing::LmsSignature}; -use super::{ - aux::{hss_is_aux_data_used, MutableExpandedAuxData}, - reference_impl_private_key::{ - generate_child_seed_and_lms_tree_identifier, generate_signature_randomizer, - ReferenceImplPrivateKey, - }, +use super::reference_impl_private_key::{ + generate_child_seed_and_lms_tree_identifier, generate_signature_randomizer, + ReferenceImplPrivateKey, }; #[derive(Debug, Default, PartialEq)] @@ -41,10 +44,18 @@ impl HssPrivateKey { pub fn from( private_key: &ReferenceImplPrivateKey, aux_data: &mut Option, + tree_identifier: Option<&LmsTreeIdentifier>, ) -> Result { let mut hss_private_key: HssPrivateKey = Default::default(); let mut current_seed = private_key.generate_root_seed_and_lms_tree_identifier(); + + if let Some(tree_identifier) = tree_identifier { + current_seed + .lms_tree_identifier + .clone_from_slice(tree_identifier); + } + let parameters = private_key.compressed_parameter.to::()?; let used_leafs_indexes = private_key.compressed_used_leafs_indexes.to(¶meters); @@ -54,6 +65,7 @@ impl HssPrivateKey { lmots_parameter: *parameters[0].get_lmots_parameter(), lms_parameter: *parameters[0].get_lms_parameter(), used_leafs_index: used_leafs_indexes[0], + sst_option: private_key.sst_option.clone(), }; hss_private_key.private_key.push(lms_private_key); @@ -67,8 +79,13 @@ impl HssPrivateKey { let signature_randomizer = generate_signature_randomizer::(¤t_seed, &parent_used_leafs_index); - let lms_keypair = - generate_key_pair(¤t_seed, parameter, &used_leafs_indexes[i], &mut None); + let lms_keypair = generate_key_pair( + ¤t_seed, + parameter, + &used_leafs_indexes[i], + &mut None, + None, + ); let signature = lms::signing::LmsSignature::sign( &mut hss_private_key.private_key[i - 1], @@ -95,15 +112,21 @@ impl HssPrivateKey { let aux_data = aux_data?; if is_aux_data_used { + // has been created, shrinked, populated and provided with HMAC before return hss_expand_aux_data::(Some(aux_data), Some(private_key.seed.as_slice())); } + let opt_l0_top_div = private_key + .sst_option + .as_ref() + .map(|sst_extension| sst_extension.l0_top_div()); + // Shrink input slice - let aux_len = hss_get_aux_data_len(aux_data.len(), *top_lms_parameter); + let aux_len = hss_get_aux_data_len(aux_data.len(), *top_lms_parameter, opt_l0_top_div); let moved = core::mem::take(aux_data); *aux_data = &mut moved[..aux_len]; - let aux_level = hss_optimal_aux_level(aux_len, *top_lms_parameter, None); + let aux_level = hss_optimal_aux_level(aux_len, *top_lms_parameter, None, opt_l0_top_div); hss_store_aux_marker(aux_data, aux_level); hss_expand_aux_data::(Some(aux_data), None) @@ -161,11 +184,7 @@ impl HssPublicKey { let top_lms_parameter = parameters[0].get_lms_parameter(); - let is_aux_data_used = if let Some(ref aux_data) = aux_data { - hss_is_aux_data_used(aux_data) - } else { - false - }; + let is_aux_data_used = aux_data.as_ref().map_or(false, |d| hss_is_aux_data_used(d)); let mut expanded_aux_data = HssPrivateKey::get_expanded_aux_data( aux_data, @@ -181,6 +200,7 @@ impl HssPublicKey { ¶meters[0], &used_leafs_indexes[0], &mut expanded_aux_data, + None, ); if let Some(expanded_aux_data) = expanded_aux_data.as_mut() { @@ -194,6 +214,65 @@ impl HssPublicKey { level: levels, }) } + + pub fn from_with_sst( + private_key: &ReferenceImplPrivateKey, + aux_data: Option<&mut &mut [u8]>, + intermed_nodes: &ArrayVec<[Node; MAX_SSTS_SIGNING_ENTITIES]>, + tree_identifier: &LmsTreeIdentifier, + ) -> Result { + let sst_extension = private_key.sst_option.as_ref().ok_or(())?; + + let parameters = private_key.compressed_parameter.to::()?; + let used_leafs_indexes = private_key.compressed_used_leafs_indexes.to(¶meters); + + let top_lms_parameter = parameters[0].get_lms_parameter(); + + let is_aux_data_used = aux_data.as_ref().map_or(false, |d| hss_is_aux_data_used(d)); + let mut opt_expanded_aux_data = HssPrivateKey::get_expanded_aux_data( + aux_data, + private_key, + top_lms_parameter, + is_aux_data_used, + ); + + let mut current_seed = private_key.generate_root_seed_and_lms_tree_identifier(); + current_seed + .lms_tree_identifier + .copy_from_slice(tree_identifier); + + // Move intermed_nodes, i.e. other subtree root nodes, into aux_data + (intermed_nodes.len() == 2usize.pow(sst_extension.l0_top_div().into())) + .then_some(()) + .ok_or(())?; + for (se_node, se_idx) in intermed_nodes.iter().zip(1..=intermed_nodes.len() as u8) { + let si_sst_ext = + SstExtension::new(se_idx, sst_extension.l0_top_div()).map_err(|_| ())?; + let node_idx = get_sst_root_node_idx(top_lms_parameter, &si_sst_ext) as usize; + hss_save_aux_data::(opt_expanded_aux_data.as_mut().ok_or(())?, node_idx, se_node); + } + + // Calculate public key with the help of the aux_data containing other subtree root nodes + let lms_keypair = generate_key_pair( + ¤t_seed, + ¶meters[0], + &used_leafs_indexes[0], + &mut opt_expanded_aux_data, + None, + ); + + // Finalize aux_data by updating the HMAC + hss_finalize_aux_data::( + opt_expanded_aux_data.as_mut().ok_or(())?, + private_key.seed.as_slice(), + ); + + Ok(Self { + public_key: lms_keypair.public_key, + level: parameters.len(), + }) + } + pub fn to_binary_representation(&self) -> ArrayVec<[u8; MAX_HSS_PUBLIC_KEY_LENGTH]> { let mut result = ArrayVec::new(); @@ -232,9 +311,9 @@ mod tests { hss::{ definitions::InMemoryHssPublicKey, reference_impl_private_key::{ReferenceImplPrivateKey, SeedAndLmsTreeIdentifier}, - HashChain, HssParameter, + HashChain, }, - lms, LmotsAlgorithm, LmsAlgorithm, + lms, HssParameter, LmotsAlgorithm, LmsAlgorithm, }; use super::{HssPrivateKey, HssPublicKey}; @@ -313,15 +392,14 @@ mod tests { ]; let seed = gen_random_seed::(); - let mut rfc_key = ReferenceImplPrivateKey::generate(¶meters, &seed).unwrap(); - - let hss_key_before = HssPrivateKey::from(&rfc_key, &mut None).unwrap(); + let mut rfc_key = ReferenceImplPrivateKey::generate(¶meters, &seed, None).unwrap(); + let hss_key_before = HssPrivateKey::from(&rfc_key, &mut None, None).unwrap(); for _ in 0..increment_by { rfc_key.increment(&hss_key_before); } - let hss_key_after = HssPrivateKey::from(&rfc_key, &mut None).unwrap(); + let hss_key_after = HssPrivateKey::from(&rfc_key, &mut None, None).unwrap(); (hss_key_before, hss_key_after) } @@ -339,8 +417,8 @@ mod tests { ]; let seed = gen_random_seed::(); - let mut private_key = ReferenceImplPrivateKey::generate(¶meters, &seed).unwrap(); - let hss_key = HssPrivateKey::from(&private_key, &mut None).unwrap(); + let mut private_key = ReferenceImplPrivateKey::generate(¶meters, &seed, None).unwrap(); + let hss_key = HssPrivateKey::from(&private_key, &mut None, None).unwrap(); let tree_heights = hss_key .private_key @@ -352,7 +430,7 @@ mod tests { const STEP_BY: usize = 27; for index in (0..total_ots_count).step_by(STEP_BY) { - let hss_key = HssPrivateKey::from(&private_key, &mut None).unwrap(); + let hss_key = HssPrivateKey::from(&private_key, &mut None, None).unwrap(); assert_eq!(hss_key.get_lifetime(), total_ots_count - index,); @@ -374,10 +452,10 @@ mod tests { ]; let seed = gen_random_seed::(); - let private_key = ReferenceImplPrivateKey::generate(¶meters, &seed).unwrap(); + let private_key = ReferenceImplPrivateKey::generate(¶meters, &seed, None).unwrap(); - let hss_key = HssPrivateKey::from(&private_key, &mut None).unwrap(); - let hss_key_second = HssPrivateKey::from(&private_key, &mut None).unwrap(); + let hss_key = HssPrivateKey::from(&private_key, &mut None, None).unwrap(); + let hss_key_second = HssPrivateKey::from(&private_key, &mut None, None).unwrap(); assert_eq!(hss_key, hss_key_second); } @@ -390,6 +468,7 @@ mod tests { &HssParameter::construct_default_parameters(), &0, &mut None, + None, ); let public_key: HssPublicKey = HssPublicKey { level: 18, diff --git a/src/hss/mod.rs b/src/hss/mod.rs index 8f3664da..2ba4253a 100644 --- a/src/hss/mod.rs +++ b/src/hss/mod.rs @@ -10,15 +10,14 @@ use core::{convert::TryFrom, marker::PhantomData}; use tinyvec::ArrayVec; use crate::{ - constants::{MAX_HSS_PUBLIC_KEY_LENGTH, REF_IMPL_MAX_PRIVATE_KEY_SIZE}, - hss::{aux::hss_is_aux_data_used, reference_impl_private_key::Seed}, + constants::{LmsTreeIdentifier, IMPL_MAX_PRIVATE_KEY_SIZE, MAX_HSS_PUBLIC_KEY_LENGTH}, + hss::{aux::hss_is_aux_data_used, parameter::HssParameter, reference_impl_private_key::Seed}, signature::{Error, SignerMut, Verifier}, HashChain, Signature, VerifierSignature, }; use self::{ definitions::{HssPrivateKey, HssPublicKey, InMemoryHssPublicKey}, - parameter::HssParameter, reference_impl_private_key::ReferenceImplPrivateKey, signing::{HssSignature, InMemoryHssSignature}, }; @@ -28,7 +27,7 @@ use self::{ */ #[derive(Clone, Debug, PartialEq, Eq)] pub struct SigningKey { - pub bytes: ArrayVec<[u8; REF_IMPL_MAX_PRIVATE_KEY_SIZE]>, + pub bytes: ArrayVec<[u8; IMPL_MAX_PRIVATE_KEY_SIZE]>, phantom_data: PhantomData, } @@ -54,7 +53,8 @@ impl SigningKey { let rfc_sk = ReferenceImplPrivateKey::from_binary_representation(self.bytes.as_slice()) .map_err(|_| Error::new())?; - let parsed_sk = HssPrivateKey::::from(&rfc_sk, &mut None).map_err(|_| Error::new())?; + let parsed_sk = + HssPrivateKey::::from(&rfc_sk, &mut None, None).map_err(|_| Error::new())?; Ok(parsed_sk.get_lifetime()) } @@ -63,6 +63,7 @@ impl SigningKey { &mut self, msg: &[u8], aux_data: Option<&mut &mut [u8]>, + tree_identifier: Option<&LmsTreeIdentifier>, ) -> Result { let private_key = self.bytes; let mut private_key_update_function = |new_key: &[u8]| { @@ -75,13 +76,14 @@ impl SigningKey { private_key.as_slice(), &mut private_key_update_function, aux_data, + tree_identifier, ) } } impl SignerMut for SigningKey { fn try_sign(&mut self, msg: &[u8]) -> Result { - self.try_sign_with_aux(msg, None) + self.try_sign_with_aux(msg, None, None) } } @@ -157,6 +159,7 @@ pub fn hss_sign( private_key: &[u8], private_key_update_function: &mut dyn FnMut(&[u8]) -> Result<(), ()>, aux_data: Option<&mut &mut [u8]>, + tree_identifier: Option<&LmsTreeIdentifier>, ) -> Result { hss_sign_core::( Some(message), @@ -164,6 +167,7 @@ pub fn hss_sign( private_key, private_key_update_function, aux_data, + tree_identifier, ) } @@ -189,6 +193,7 @@ pub fn hss_sign_mut( private_key, private_key_update_function, aux_data, + None, ) } @@ -198,6 +203,7 @@ fn hss_sign_core( private_key: &[u8], private_key_update_function: &mut dyn FnMut(&[u8]) -> Result<(), ()>, aux_data: Option<&mut &mut [u8]>, + tree_identifier: Option<&LmsTreeIdentifier>, ) -> Result { let mut rfc_private_key = ReferenceImplPrivateKey::from_binary_representation(private_key) .map_err(|_| Error::new())?; @@ -219,8 +225,9 @@ fn hss_sign_core( is_aux_data_used, ); - let mut private_key = HssPrivateKey::::from(&rfc_private_key, &mut expanded_aux_data) - .map_err(|_| Error::new())?; + let mut private_key = + HssPrivateKey::::from(&rfc_private_key, &mut expanded_aux_data, tree_identifier) + .map_err(|_| Error::new())?; let hss_signature = HssSignature::sign( &mut private_key, @@ -280,7 +287,7 @@ pub fn hss_keygen( aux_data: Option<&mut &mut [u8]>, ) -> Result<(SigningKey, VerifyingKey), Error> { let private_key = - ReferenceImplPrivateKey::generate(parameters, seed).map_err(|_| Error::new())?; + ReferenceImplPrivateKey::generate(parameters, seed, None).map_err(|_| Error::new())?; let hss_public_key = HssPublicKey::from(&private_key, aux_data).map_err(|_| Error::new())?; @@ -291,9 +298,10 @@ pub fn hss_keygen( #[cfg(test)] mod tests { + use super::parameter::HssParameter; use crate::util::helper::test_helper::gen_random_seed; use crate::{ - constants::{HSS_COMPRESSED_USED_LEAFS_SIZE, MAX_HASH_SIZE}, + constants::{HSS_COMPRESSED_USED_LEAFS_SIZE, MAX_HASH_SIZE, REF_IMPL_MAX_PRIVATE_KEY_SIZE}, hasher::{ sha256::{Sha256_128, Sha256_192, Sha256_256}, shake256::{Shake256_128, Shake256_192, Shake256_256}, @@ -331,6 +339,7 @@ mod tests { signing_key_const.as_slice(), &mut update_private_key, None, + None, ) .expect("Signing should complete without error."); @@ -386,6 +395,7 @@ mod tests { signing_key_const.as_slice(), &mut update_private_key, None, + None, ) .expect("Signing should complete without error."); @@ -429,6 +439,7 @@ mod tests { signing_key_const.as_slice(), &mut update_private_key, None, + None, ) .unwrap_or_else(|_| { if index < keypair_lifetime { @@ -507,6 +518,7 @@ mod tests { fn test_signing_core(aux_data: &mut Option<&mut [u8]>) { let seed = gen_random_seed::(); + let (mut signing_key, verifying_key) = hss_keygen::( &[ HssParameter::construct_default_parameters(), @@ -536,6 +548,7 @@ mod tests { signing_key_const.as_slice(), &mut update_private_key, aux_data.as_mut(), + None, ) .expect("Signing should complete without error."); diff --git a/src/hss/reference_impl_private_key.rs b/src/hss/reference_impl_private_key.rs index 4cdee7c1..68e2f3d0 100644 --- a/src/hss/reference_impl_private_key.rs +++ b/src/hss/reference_impl_private_key.rs @@ -1,11 +1,14 @@ use crate::{ constants::{ - LmsTreeIdentifier, D_TOPSEED, HSS_COMPRESSED_USED_LEAFS_SIZE, ILEN, MAX_ALLOWED_HSS_LEVELS, - MAX_HASH_SIZE, MAX_SEED_LEN, REF_IMPL_MAX_PRIVATE_KEY_SIZE, SEED_CHILD_SEED, - SEED_SIGNATURE_RANDOMIZER_SEED, TOPSEED_D, TOPSEED_LEN, TOPSEED_SEED, TOPSEED_WHICH, + LmsTreeIdentifier, D_TOPSEED, HSS_COMPRESSED_USED_LEAFS_SIZE, ILEN, + IMPL_MAX_PRIVATE_KEY_SIZE, MAX_ALLOWED_HSS_LEVELS, MAX_HASH_SIZE, MAX_SEED_LEN, + REF_IMPL_MAX_PRIVATE_KEY_SIZE, SEED_CHILD_SEED, SEED_SIGNATURE_RANDOMIZER_SEED, + SST_IMPL_MAX_PRIVATE_KEY_SIZE, SST_SIZE, TOPSEED_D, TOPSEED_LEN, TOPSEED_SEED, + TOPSEED_WHICH, }, hasher::HashChain, hss::{definitions::HssPrivateKey, seed_derive::SeedDerive}, + sst::{helper, parameters::SstExtension}, util::{helper::read_and_advance, ArrayVecZeroize}, HssParameter, LmotsAlgorithm, LmsAlgorithm, }; @@ -82,11 +85,12 @@ impl SeedAndLmsTreeIdentifier { } } -#[derive(Clone, Default, PartialEq, Eq, Zeroize, ZeroizeOnDrop)] +#[derive(Default, PartialEq, Eq, Zeroize, ZeroizeOnDrop)] pub struct ReferenceImplPrivateKey { pub compressed_used_leafs_indexes: CompressedUsedLeafsIndexes, pub compressed_parameter: CompressedParameterSet, pub seed: Seed, + pub sst_option: Option, } impl ReferenceImplPrivateKey { @@ -94,21 +98,41 @@ impl ReferenceImplPrivateKey { self.seed = Seed::default(); self.compressed_parameter = CompressedParameterSet::default(); self.compressed_used_leafs_indexes = CompressedUsedLeafsIndexes::new(0); + self.sst_option = None; } - pub fn generate(parameters: &[HssParameter], seed: &Seed) -> Result { + pub fn generate( + parameters: &[HssParameter], + seed: &Seed, + sst_option: Option, + ) -> Result { + // In case of SST, the private key is generated with an offset. The offset is only + // needed for the root tree due to the single sub trees. + let mut used_leafs_indexes = [0u32; MAX_ALLOWED_HSS_LEVELS]; + used_leafs_indexes[0] = sst_option.as_ref().map_or(0, |sst_extension| { + helper::get_sst_first_leaf_idx(parameters[0].get_lms_parameter(), sst_extension) + }); + let private_key: ReferenceImplPrivateKey = ReferenceImplPrivateKey { - compressed_used_leafs_indexes: CompressedUsedLeafsIndexes::new(0), + compressed_used_leafs_indexes: CompressedUsedLeafsIndexes::from( + &used_leafs_indexes, + parameters, + ), compressed_parameter: CompressedParameterSet::from(parameters)?, seed: seed.clone(), + sst_option, }; Ok(private_key) } - pub fn to_binary_representation(&self) -> ArrayVec<[u8; REF_IMPL_MAX_PRIVATE_KEY_SIZE]> { + pub fn to_binary_representation(&self) -> ArrayVec<[u8; IMPL_MAX_PRIVATE_KEY_SIZE]> { let mut result = ArrayVec::new(); + if let Some(sst_extension) = &self.sst_option { + result.extend_from_slice(&sst_extension.signing_entity_idx().to_be_bytes()); + result.extend_from_slice(&sst_extension.l0_top_div().to_be_bytes()); + } result.extend_from_slice(&self.compressed_used_leafs_indexes.count.to_be_bytes()); result.extend_from_slice(&self.compressed_parameter.0); result.extend_from_slice(self.seed.as_slice()); @@ -117,13 +141,26 @@ impl ReferenceImplPrivateKey { } pub fn from_binary_representation(data: &[u8]) -> Result { - if data.len() != REF_IMPL_MAX_PRIVATE_KEY_SIZE - MAX_SEED_LEN + H::OUTPUT_SIZE as usize { + fn get_impl_private_key_size(max_size: usize) -> usize { + // Key size depends on the selected hasher type, i.e. security level + max_size - MAX_SEED_LEN + H::OUTPUT_SIZE as usize + } + + if [REF_IMPL_MAX_PRIVATE_KEY_SIZE, SST_IMPL_MAX_PRIVATE_KEY_SIZE] + .iter() + .all(|&v| data.len() != get_impl_private_key_size::(v)) + { return Err(()); } let mut result = Self::default(); let mut index = 0; + if data.len() == get_impl_private_key_size::(SST_IMPL_MAX_PRIVATE_KEY_SIZE) { + let sst_ext = read_and_advance(data, SST_SIZE, &mut index); + result.sst_option = SstExtension::from_slice(sst_ext).ok(); + } + let compressed_used_leafs_indexes = read_and_advance(data, HSS_COMPRESSED_USED_LEAFS_SIZE, &mut index); result.compressed_used_leafs_indexes = @@ -168,8 +205,8 @@ impl ReferenceImplPrivateKey { hash_preimage[TOPSEED_WHICH] = 0x02; hasher.update(&hash_preimage); - let mut lms_tree_identifier = LmsTreeIdentifier::default(); - lms_tree_identifier.copy_from_slice(&hasher.finalize_reset()[..ILEN]); + // Root LmsTreeIdentifier needs to be equal for all signing entities for sign & verify + let lms_tree_identifier = hasher.finalize_reset()[..ILEN].try_into().unwrap(); SeedAndLmsTreeIdentifier::new(&seed, &lms_tree_identifier) } @@ -196,8 +233,7 @@ pub fn generate_child_seed_and_lms_tree_identifier( derive.set_child_seed(SEED_CHILD_SEED); let seed = Seed::try_from(derive.seed_derive(true)).unwrap(); - let mut lms_tree_identifier = LmsTreeIdentifier::default(); - lms_tree_identifier.copy_from_slice(&derive.seed_derive(false)[..ILEN]); + let lms_tree_identifier = derive.seed_derive(false)[..ILEN].try_into().unwrap(); SeedAndLmsTreeIdentifier::new(&seed, &lms_tree_identifier) } @@ -298,9 +334,27 @@ impl CompressedUsedLeafsIndexes { } } + pub fn from( + used_leafs_indexes: &[u32; MAX_ALLOWED_HSS_LEVELS], + parameters: &[HssParameter], + ) -> Self { + let mut compressed_used_leafs_indexes: u64 = 0; + + for (i, parameter) in parameters.iter().enumerate() { + let shift: u32 = parameter.get_lms_parameter().get_tree_height().into(); + compressed_used_leafs_indexes <<= shift; + compressed_used_leafs_indexes |= used_leafs_indexes[i] as u64; + } + + CompressedUsedLeafsIndexes { + count: compressed_used_leafs_indexes, + } + } + + // this works because we limit the total num of signatures / leafs to 2^64 (see RFC) pub fn to( &self, - parameters: &ArrayVec<[HssParameter; MAX_ALLOWED_HSS_LEVELS]>, + parameters: &[HssParameter], ) -> [u32; MAX_ALLOWED_HSS_LEVELS] { let mut lms_leaf_identifier_set = [0u32; MAX_ALLOWED_HSS_LEVELS]; let mut compressed_used_leafs_indexes = self.count; @@ -311,6 +365,7 @@ impl CompressedUsedLeafsIndexes { (compressed_used_leafs_indexes & (2u32.pow(tree_height) - 1) as u64) as u32; compressed_used_leafs_indexes >>= tree_height; } + lms_leaf_identifier_set } @@ -332,16 +387,37 @@ impl CompressedUsedLeafsIndexes { #[cfg(test)] mod tests { use super::{CompressedParameterSet, ReferenceImplPrivateKey}; + use crate::sst::parameters::SstExtension; + use crate::util::helper::test_helper::gen_random_seed; use crate::{ constants::MAX_ALLOWED_HSS_LEVELS, hss::definitions::HssPrivateKey, HssParameter, LmotsAlgorithm, LmsAlgorithm, Sha256_256, }; - - use crate::util::helper::test_helper::gen_random_seed; use tinyvec::ArrayVec; type Hasher = Sha256_256; + #[test] + fn generate_sst_impl_private_key() { + const SIGNING_ENTITY_IDX: u32 = 3; + const L0_TOP_DIV: u32 = 4; + + let seed = gen_random_seed::(); + let hss_parameters = [ + HssParameter::construct_default_parameters(), + HssParameter::construct_default_parameters(), + ]; + let sst_extension = SstExtension::new(SIGNING_ENTITY_IDX as u8, L0_TOP_DIV as u8).unwrap(); + let impl_private_key = + ReferenceImplPrivateKey::generate(&hss_parameters, &seed, Some(sst_extension)).unwrap(); + assert_eq!( + impl_private_key + .compressed_used_leafs_indexes + .to(&hss_parameters)[0], + 2u32.pow(SIGNING_ENTITY_IDX - 1) + ); + } + #[test] fn exhaust_state() { let lmots = LmotsAlgorithm::LmotsW4; @@ -349,9 +425,10 @@ mod tests { let parameters = [HssParameter::::new(lmots, lms)]; let seed = gen_random_seed::(); - let mut rfc_private_key = ReferenceImplPrivateKey::generate(¶meters, &seed).unwrap(); + let mut rfc_private_key = + ReferenceImplPrivateKey::generate(¶meters, &seed, None).unwrap(); - let hss_private_key = HssPrivateKey::from(&rfc_private_key, &mut None).unwrap(); + let hss_private_key = HssPrivateKey::from(&rfc_private_key, &mut None, None).unwrap(); let seed = rfc_private_key.seed.clone(); @@ -376,9 +453,10 @@ mod tests { let parameters = [HssParameter::::new(lmots, lms)]; let seed = gen_random_seed::(); - let mut rfc_private_key = ReferenceImplPrivateKey::generate(¶meters, &seed).unwrap(); + let mut rfc_private_key = + ReferenceImplPrivateKey::generate(¶meters, &seed, None).unwrap(); - let hss_private_key = HssPrivateKey::from(&rfc_private_key, &mut None).unwrap(); + let hss_private_key = HssPrivateKey::from(&rfc_private_key, &mut None, None).unwrap(); let keypair_lifetime = hss_private_key.get_lifetime(); for _ in 0..keypair_lifetime { @@ -425,7 +503,7 @@ mod tests { ]; let seed = gen_random_seed::(); - let key = ReferenceImplPrivateKey::generate(¶meters, &seed).unwrap(); + let key = ReferenceImplPrivateKey::generate(¶meters, &seed, None).unwrap(); let binary_representation = key.to_binary_representation(); let deserialized = ReferenceImplPrivateKey::::from_binary_representation( diff --git a/src/hss/signing.rs b/src/hss/signing.rs index cba89ea8..78e495cd 100644 --- a/src/hss/signing.rs +++ b/src/hss/signing.rs @@ -276,16 +276,18 @@ mod tests { #[should_panic(expected = "Signing should panic!")] fn reuse_loaded_keypair() { let seed = gen_random_seed::(); + let private_key = ReferenceImplPrivateKey::::generate( &[ HssParameter::construct_default_parameters(), HssParameter::construct_default_parameters(), ], &seed, + None, ) .unwrap(); - let mut private_key = HssPrivateKey::from(&private_key, &mut None).unwrap(); + let mut private_key = HssPrivateKey::from(&private_key, &mut None, None).unwrap(); let message = [2, 56, 123, 22, 42, 49, 22]; @@ -307,6 +309,7 @@ mod tests { &HssParameter::construct_default_parameters(), &0, &mut None, + None, ); let message = [3, 54, 32, 45, 67, 32, 12, 58, 29, 49]; @@ -336,16 +339,18 @@ mod tests { #[test] fn test_hss_signature_binary_representation() { let seed = gen_random_seed::(); + let private_key = ReferenceImplPrivateKey::::generate( &[ HssParameter::construct_default_parameters(), HssParameter::construct_default_parameters(), ], &seed, + None, ) .unwrap(); - let mut private_key = HssPrivateKey::from(&private_key, &mut None).unwrap(); + let mut private_key = HssPrivateKey::from(&private_key, &mut None, None).unwrap(); let message_values = [2, 56, 123, 22, 42, 49, 22]; let mut message = [0u8; 64]; diff --git a/src/hss/verify.rs b/src/hss/verify.rs index 25652ee2..c1a3f1d7 100644 --- a/src/hss/verify.rs +++ b/src/hss/verify.rs @@ -30,6 +30,8 @@ pub fn verify<'a, H: HashChain>( #[cfg(test)] mod tests { + + use crate::util::helper::test_helper::gen_random_seed; use crate::{ hasher::{sha256::Sha256_256, HashChain}, hss::{ @@ -41,22 +43,22 @@ mod tests { HssParameter, }; - use crate::util::helper::test_helper::gen_random_seed; - #[test] fn test_hss_verify() { type H = Sha256_256; let seed = gen_random_seed::(); + let rfc_key = ReferenceImplPrivateKey::::generate( &[ HssParameter::construct_default_parameters(), HssParameter::construct_default_parameters(), ], &seed, + None, ) .unwrap(); - let mut private_key = HssPrivateKey::from(&rfc_key, &mut None).unwrap(); + let mut private_key = HssPrivateKey::from(&rfc_key, &mut None, None).unwrap(); let public_key = HssPublicKey::from(&rfc_key, None).unwrap(); let message_values = [42, 57, 20, 59, 33, 1, 49, 3, 99, 130, 50, 20]; diff --git a/src/lib.rs b/src/lib.rs index e2adea9c..74a84803 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,7 @@ //! Signature, signature::{SignerMut, Verifier}, //! Sha256_256, HashChain, Seed, //! }; +//! use hbs_lms::REF_IMPL_MAX_ALLOWED_HSS_LEVELS; //! //! let message: [u8; 7] = [42, 84, 34, 12, 64, 34, 32]; //! @@ -89,6 +90,7 @@ mod hasher; mod hss; mod lm_ots; mod lms; +mod sst; mod util; // Re-export the `signature` crate @@ -116,10 +118,21 @@ pub use crate::hss::hss_sign_mut as sign_mut; pub use crate::hss::hss_verify as verify; pub use crate::hss::{SigningKey, VerifyingKey}; +// TODO/Review: API for distributed state management (SST, SingleSubTree) +// should not contain anything else, while crate's modules get to use more of "sst" +pub use crate::constants::LmsTreeIdentifier; +pub use crate::constants::MAX_SSTS_SIGNING_ENTITIES; + +pub use crate::sst::gen_key::finalize_sst_keygen; +pub use crate::sst::gen_key::prepare_sst_keygen; +pub use crate::sst::get_num_signing_entities; +pub use crate::sst::parameters::SstExtension; + use core::convert::TryFrom; use signature::Error; use tinyvec::ArrayVec; +pub use crate::constants::REF_IMPL_MAX_ALLOWED_HSS_LEVELS; use constants::MAX_HSS_SIGNATURE_LENGTH; /** @@ -186,14 +199,13 @@ impl<'a> signature::Signature for VerifierSignature<'a> { #[cfg(test)] mod tests { + use crate::util::helper::test_helper::gen_random_seed; use crate::{keygen, HssParameter, LmotsAlgorithm, LmsAlgorithm, Sha256_256}; use crate::{ signature::{SignerMut, Verifier}, SigningKey, VerifierSignature, VerifyingKey, }; - use crate::util::helper::test_helper::gen_random_seed; - #[test] fn get_signing_and_verifying_key() { type H = Sha256_256; diff --git a/src/lms/definitions.rs b/src/lms/definitions.rs index 1db5f144..6cae2ec3 100644 --- a/src/lms/definitions.rs +++ b/src/lms/definitions.rs @@ -5,6 +5,8 @@ use crate::lm_ots::parameters::{LmotsAlgorithm, LmotsParameter}; use crate::lms::helper::get_tree_element; use crate::lms::parameters::LmsAlgorithm; use crate::lms::MutableExpandedAuxData; +use crate::sst::helper::get_sst_last_leaf_idx; +use crate::sst::parameters::SstExtension; use crate::util::helper::read_and_advance; use crate::{lm_ots, Seed}; @@ -23,6 +25,7 @@ pub struct LmsPrivateKey { pub lmots_parameter: LmotsParameter, #[zeroize(skip)] pub lms_parameter: LmsParameter, + pub sst_option: Option, } impl LmsPrivateKey { @@ -32,6 +35,7 @@ impl LmsPrivateKey { used_leafs_index: u32, lmots_parameter: LmotsParameter, lms_parameter: LmsParameter, + sst_option: Option, ) -> Self { LmsPrivateKey { seed, @@ -39,11 +43,16 @@ impl LmsPrivateKey { used_leafs_index, lmots_parameter, lms_parameter, + sst_option, } } pub fn use_lmots_private_key(&mut self) -> Result, ()> { - let number_of_lm_ots_keys = self.lms_parameter.number_of_lm_ots_keys(); + let number_of_lm_ots_keys = self.sst_option.as_ref().map_or_else( + || self.lms_parameter.number_of_lm_ots_keys(), + // last leaf idx is total_num-1, hence add 1 + |v| get_sst_last_leaf_idx(&self.lms_parameter, v) as usize + 1, + ); if self.used_leafs_index as usize >= number_of_lm_ots_keys { return Err(()); @@ -119,7 +128,7 @@ impl<'a, H: HashChain> PartialEq> for InMemoryLmsPublicKey<'a, H impl<'a, H: HashChain> InMemoryLmsPublicKey<'a, H> { pub fn new(data: &'a [u8]) -> Option { - // Parsing like desribed in 5.4.2 + // Parsing like described in 5.4.2 let mut data_index = 0; let lms_parameter = LmsAlgorithm::get_from_type(u32::from_be_bytes( @@ -172,6 +181,7 @@ mod tests { 0, LmotsAlgorithm::construct_default_parameter(), LmsAlgorithm::construct_default_parameter(), + None, ); let public_key = LmsPublicKey::new(&private_key, &mut None); diff --git a/src/lms/helper.rs b/src/lms/helper.rs index 2a99cce0..85ad6846 100644 --- a/src/lms/helper.rs +++ b/src/lms/helper.rs @@ -1,6 +1,4 @@ -use tinyvec::ArrayVec; - -use crate::constants::{D_INTR, D_LEAF, MAX_HASH_SIZE}; +use crate::constants::{Node, D_INTR, D_LEAF}; use crate::hasher::HashChain; use crate::hss::aux::{hss_extract_aux_data, hss_save_aux_data, MutableExpandedAuxData}; use crate::lm_ots; @@ -11,7 +9,7 @@ pub fn get_tree_element( index: usize, private_key: &LmsPrivateKey, aux_data: &mut Option, -) -> ArrayVec<[u8; MAX_HASH_SIZE]> { +) -> Node { // Check if we already have the value cached if let Some(aux_data) = aux_data { if let Some(result) = hss_extract_aux_data::(aux_data, index) { diff --git a/src/lms/mod.rs b/src/lms/mod.rs index 5434d186..568d649e 100644 --- a/src/lms/mod.rs +++ b/src/lms/mod.rs @@ -4,9 +4,10 @@ use crate::hss::parameter::HssParameter; use crate::hss::reference_impl_private_key::SeedAndLmsTreeIdentifier; use crate::lms::definitions::LmsPrivateKey; use crate::lms::definitions::LmsPublicKey; +use crate::sst::parameters::SstExtension; pub mod definitions; -mod helper; +pub(crate) mod helper; pub mod parameters; pub mod signing; pub mod verify; @@ -21,6 +22,7 @@ pub fn generate_key_pair( parameter: &HssParameter, used_leafs_index: &u32, aux_data: &mut Option, + sst_option: Option, ) -> LmsKeyPair { let lmots_parameter = parameter.get_lmots_parameter(); let lms_parameter = parameter.get_lms_parameter(); @@ -31,6 +33,7 @@ pub fn generate_key_pair( *used_leafs_index, *lmots_parameter, *lms_parameter, + sst_option, ); let public_key = LmsPublicKey::new(&private_key, aux_data); diff --git a/src/lms/signing.rs b/src/lms/signing.rs index 3a55e6a9..17a2359f 100644 --- a/src/lms/signing.rs +++ b/src/lms/signing.rs @@ -233,6 +233,7 @@ mod tests { 0, LmotsAlgorithm::construct_default_parameter(), LmsAlgorithm::construct_default_parameter(), + None, ); let message = "Hi, what up?".as_bytes(); diff --git a/src/lms/verify.rs b/src/lms/verify.rs index 9dfbcf9e..e7b3e5c3 100644 --- a/src/lms/verify.rs +++ b/src/lms/verify.rs @@ -108,6 +108,7 @@ mod tests { 0, LmotsAlgorithm::construct_default_parameter(), LmsAlgorithm::construct_default_parameter(), + None, ); let public_key = LmsPublicKey::new(&private_key, &mut None).to_binary_representation(); diff --git a/src/sst/gen_key.rs b/src/sst/gen_key.rs new file mode 100644 index 00000000..04e44020 --- /dev/null +++ b/src/sst/gen_key.rs @@ -0,0 +1,232 @@ +use crate::signature::Error; +use crate::{ + constants::{LmsTreeIdentifier, Node, MAX_HASH_SIZE, MAX_SSTS_SIGNING_ENTITIES}, + hasher::HashChain, + hss::{ + aux::{hss_finalize_aux_data, hss_is_aux_data_used}, + definitions::HssPrivateKey, + definitions::HssPublicKey, + reference_impl_private_key::{ReferenceImplPrivateKey, Seed}, + SigningKey, VerifyingKey, + }, + lms::definitions::LmsPrivateKey, + lms::helper::get_tree_element, + sst::{helper::get_sst_root_node_idx, parameters::SstExtension}, + HssParameter, +}; + +use tinyvec::ArrayVec; + +pub fn prepare_sst_keygen( + hss_parameters: &[HssParameter], + sst_extension: &SstExtension, + seed: &Seed, + aux_data: Option<&mut &mut [u8]>, + tree_identifier: &mut LmsTreeIdentifier, +) -> Result<(SigningKey, Node), Error> { + let rfc_private_key = + ReferenceImplPrivateKey::generate(hss_parameters, seed, Some(sst_extension.clone())) + .map_err(|_| Error::new())?; + + let is_aux_data_used = aux_data.as_ref().map_or(false, |d| hss_is_aux_data_used(d)); + + let mut expanded_aux_data = HssPrivateKey::get_expanded_aux_data( + aux_data, + &rfc_private_key, + hss_parameters[0].get_lms_parameter(), + is_aux_data_used, + ); + + // Harmonising LMS tree identifier of root tree + let mut seed_and_lms_tree_ident = rfc_private_key.generate_root_seed_and_lms_tree_identifier(); + if tree_identifier.iter().all(|&byte| byte == 0) { + tree_identifier.clone_from_slice(&seed_and_lms_tree_ident.lms_tree_identifier); + } else { + seed_and_lms_tree_ident + .lms_tree_identifier + .clone_from_slice(tree_identifier); + } + + let our_node_index = + get_sst_root_node_idx(hss_parameters[0].get_lms_parameter(), sst_extension); + + let lms_private_key = LmsPrivateKey::::new( + seed_and_lms_tree_ident.seed.clone(), + seed_and_lms_tree_ident.lms_tree_identifier, + rfc_private_key + .compressed_used_leafs_indexes + .to(hss_parameters)[0], + *hss_parameters[0].get_lmots_parameter(), + *hss_parameters[0].get_lms_parameter(), + Some(sst_extension.clone()), + ); + + let our_intermed_node_value = get_tree_element( + our_node_index as usize, + &lms_private_key, + &mut expanded_aux_data, + ); + + hss_finalize_aux_data::( + expanded_aux_data.as_mut().ok_or(Error::new())?, + rfc_private_key.seed.as_slice(), + ); + + let signing_key = SigningKey::from_bytes(&rfc_private_key.to_binary_representation())?; + Ok((signing_key, our_intermed_node_value)) +} + +pub fn finalize_sst_keygen( + private_key: &[u8], + interm_nodes: &ArrayVec<[ArrayVec<[u8; MAX_HASH_SIZE]>; MAX_SSTS_SIGNING_ENTITIES]>, + aux_data: Option<&mut &mut [u8]>, + tree_identifier: &LmsTreeIdentifier, +) -> Result, Error> { + let rfc_private_key = ReferenceImplPrivateKey::::from_binary_representation(private_key) + .map_err(|_| Error::new())?; + + let hss_public_key = + HssPublicKey::from_with_sst(&rfc_private_key, aux_data, interm_nodes, tree_identifier) + .map_err(|_| Error::new())?; + + VerifyingKey::::from_bytes(&hss_public_key.to_binary_representation()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::util::helper::test_helper::gen_random_seed; + use crate::HssParameter; + use crate::Sha256_128; + use crate::Sha256_192; + use crate::Sha256_256; + use signature::Verifier; + + #[test] + fn test_prepare_sst_keygen_se_without_lms_tree_id() { + const SIGNING_ENTITY_IDX: u32 = 3; + const L0_TOP_DIV: u32 = 4; + + let mut lms_tree_identifier = LmsTreeIdentifier::default(); + let seed = gen_random_seed::(); + let hss_parameters = [ + HssParameter::construct_default_parameters(), + HssParameter::construct_default_parameters(), + ]; + let sst_extension = SstExtension::new(SIGNING_ENTITY_IDX as u8, L0_TOP_DIV as u8).unwrap(); + let mut aux_data = [0u8; 4 + MAX_HASH_SIZE + 2usize.pow(L0_TOP_DIV) * MAX_HASH_SIZE]; + let aux_ref: &mut &mut [u8] = &mut &mut aux_data[..]; + let aux_option = Some(aux_ref); + + let (_signing_key, interm_node) = prepare_sst_keygen::( + &hss_parameters, + &sst_extension, + &seed, + aux_option, + &mut lms_tree_identifier, + ) + .unwrap(); + assert_ne!(lms_tree_identifier, LmsTreeIdentifier::default()); + assert_eq!( + interm_node.as_slice().len(), + LmsTreeIdentifier::default().len() + ); + assert_ne!(interm_node.as_slice(), &LmsTreeIdentifier::default()); + } + + #[test] + fn test_prepare_sst_keygen_se_with_lms_tree_id() { + const SIGNING_ENTITY_IDX: u32 = 3; + const L0_TOP_DIV: u32 = 4; + + let mut lms_tree_identifier = LmsTreeIdentifier::default(); + lms_tree_identifier[0] = 0xaa; + let copy_lms_tree_identifier = lms_tree_identifier; + let seed = gen_random_seed::(); + let hss_parameters = [ + HssParameter::construct_default_parameters(), + HssParameter::construct_default_parameters(), + ]; + let sst_extension = SstExtension::new(SIGNING_ENTITY_IDX as u8, L0_TOP_DIV as u8).unwrap(); + let mut aux_data = [0u8; 4 + MAX_HASH_SIZE + 2usize.pow(L0_TOP_DIV) * MAX_HASH_SIZE]; + let aux_ref: &mut &mut [u8] = &mut &mut aux_data[..]; + let aux_option = Some(aux_ref); + + let _ = prepare_sst_keygen::( + &hss_parameters, + &sst_extension, + &seed, + aux_option, + &mut lms_tree_identifier, + ) + .unwrap(); + assert_eq!(lms_tree_identifier, copy_lms_tree_identifier); + } + + #[test] + fn signing_sst_sha256_128() { + signing_sst_core::(); + } + + #[test] + fn signing_sst_sha256_192() { + signing_sst_core::(); + } + + #[test] + fn signing_sst_sha256_256() { + signing_sst_core::(); + } + + fn signing_sst_core() { + const SIGNING_ENTITY_IDX: u32 = 3; + const L0_TOP_DIV: u32 = 4; + + let mut message = [ + 32u8, 48, 2, 1, 48, 58, 20, 57, 9, 83, 99, 255, 0, 34, 2, 1, 0, + ]; + + let mut lms_tree_identifier = LmsTreeIdentifier::default(); + let seed = gen_random_seed::(); + let hss_parameters = [ + HssParameter::construct_default_parameters(), + HssParameter::construct_default_parameters(), + ]; + let sst_extension = SstExtension::new(SIGNING_ENTITY_IDX as u8, L0_TOP_DIV as u8).unwrap(); + let mut aux_data = [0u8; 4 + MAX_HASH_SIZE + 2usize.pow(L0_TOP_DIV) * MAX_HASH_SIZE]; + let aux_ref: &mut &mut [u8] = &mut &mut aux_data[..]; + let mut aux_option = Some(aux_ref); + + let (mut signing_key, interm_node) = prepare_sst_keygen::( + &hss_parameters, + &sst_extension, + &seed, + Some(aux_option.as_mut().unwrap()), + &mut lms_tree_identifier, + ) + .unwrap(); + + let mut interm_nodes = ArrayVec::<[Node; MAX_SSTS_SIGNING_ENTITIES]>::new(); + let mut tmp_node = Node::new(); + tmp_node.extend_from_slice(&interm_node); + for _ in 0..2usize.pow(L0_TOP_DIV) { + interm_nodes.push(tmp_node); + } + + let verifying_key = finalize_sst_keygen::( + signing_key.as_slice(), + &interm_nodes, + Some(aux_option.as_mut().unwrap()), + &lms_tree_identifier, + ) + .unwrap(); + + let signature = signing_key + .try_sign_with_aux(&message, aux_option, Some(&lms_tree_identifier)) + .unwrap(); + + assert!(verifying_key.verify(&message, &signature).is_ok()); + message[0] = 33; + assert!(verifying_key.verify(&message, &signature).is_err()); + } +} diff --git a/src/sst/helper.rs b/src/sst/helper.rs new file mode 100644 index 00000000..27f7e12d --- /dev/null +++ b/src/sst/helper.rs @@ -0,0 +1,213 @@ +use crate::hss::reference_impl_private_key::ReferenceImplPrivateKey; +use crate::lms::parameters::LmsParameter; +use crate::signature::Error; +use crate::sst::parameters::SstExtension; +use crate::HashChain; + +// Returns the sub-tree root node index within a LMS with SST extension +// signing_entity_idx counts from left-most = 1 to right-most = total number of sub-trees +// sub-tree root node index starts with offset of 0 +pub fn get_sst_root_node_idx( + lms_parameter: &LmsParameter, + sst_extension: &SstExtension, +) -> u32 { + assert!(sst_extension.l0_top_div() <= lms_parameter.get_tree_height()); + 2u32.pow(sst_extension.l0_top_div() as u32) + (sst_extension.signing_entity_idx() as u32) - 1 +} + +fn get_sst_number_of_lm_ots_keys( + lms_parameter: &LmsParameter, + sst_extension: &SstExtension, +) -> u32 { + assert!(sst_extension.l0_top_div() <= lms_parameter.get_tree_height()); + 2u32.pow((lms_parameter.get_tree_height() - sst_extension.l0_top_div()) as u32) +} + +// For a subtree, depending on whole SSTS and division, get first leaf idx where leafs start with 0 +pub fn get_sst_first_leaf_idx( + lms_parameter: &LmsParameter, + sst_extension: &SstExtension, +) -> u32 { + ((sst_extension.signing_entity_idx() as u32) - 1) + * get_sst_number_of_lm_ots_keys(lms_parameter, sst_extension) +} + +// For a subtree, depending on whole SSTS and division, get last leaf idx where leafs start with 0 +pub fn get_sst_last_leaf_idx( + lms_parameter: &LmsParameter, + sst_extension: &SstExtension, +) -> u32 { + get_sst_first_leaf_idx(lms_parameter, sst_extension) + + get_sst_number_of_lm_ots_keys(lms_parameter, sst_extension) + - 1 +} + +pub fn get_num_signing_entities(private_key: &[u8]) -> Result { + let rfc_private_key = ReferenceImplPrivateKey::::from_binary_representation(private_key) + .map_err(|_| Error::new())?; + + if let Some(sst_extension) = &rfc_private_key.sst_option { + Ok(2u32.pow(sst_extension.l0_top_div() as u32)) + } else { + Err(Error::new()) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::lms::parameters::LmsAlgorithm; + use crate::util::helper::test_helper::gen_random_seed; + use crate::HssParameter; + use crate::Sha256_128; + + #[test] + fn test_get_sst_root_node_idx_1() { + let lms_parameter = LmsAlgorithm::get_from_type::(5).unwrap(); + let sst_extension = SstExtension::new(1, 2).unwrap(); + let subtree_node_idx = get_sst_root_node_idx(&lms_parameter, &sst_extension); + assert_eq!(4, subtree_node_idx); + } + + #[test] + fn test_get_sst_root_node_idx_2() { + let lms_parameter = LmsAlgorithm::get_from_type::(5).unwrap(); + let sst_extension = SstExtension::new(2, 2).unwrap(); + let subtree_node_idx = get_sst_root_node_idx(&lms_parameter, &sst_extension); + assert_eq!(5, subtree_node_idx); + } + + #[test] + fn test_get_sst_root_node_idx_3() { + let lms_parameter = LmsAlgorithm::get_from_type::(5).unwrap(); + let sst_extension = SstExtension::new(1, 3).unwrap(); + let subtree_node_idx = get_sst_root_node_idx(&lms_parameter, &sst_extension); + assert_eq!(8, subtree_node_idx); + } + + #[test] + fn test_get_sst_root_node_idx_4() { + let lms_parameter = LmsAlgorithm::get_from_type::(5).unwrap(); + let sst_extension = SstExtension::new(2, 3).unwrap(); + let subtree_node_idx = get_sst_root_node_idx(&lms_parameter, &sst_extension); + assert_eq!(9, subtree_node_idx); + } + + #[test] + fn test_get_sst_root_node_idx_5() { + let lms_parameter = LmsAlgorithm::get_from_type::(5).unwrap(); + let sst_extension = SstExtension::new(1, 4).unwrap(); + let subtree_node_idx = get_sst_root_node_idx(&lms_parameter, &sst_extension); + assert_eq!(16, subtree_node_idx); + } + + #[test] + fn test_get_sst_root_node_idx_6() { + let lms_parameter = LmsAlgorithm::get_from_type::(5).unwrap(); + let sst_extension = SstExtension::new(9, 4).unwrap(); + let subtree_node_idx = get_sst_root_node_idx(&lms_parameter, &sst_extension); + assert_eq!(24, subtree_node_idx); + } + + // one outermost "leaf" + #[test] + fn test_get_sst_root_node_idx_7() { + let lms_parameter = LmsAlgorithm::get_from_type::(5).unwrap(); + let sst_extension = SstExtension::new(16, 4).unwrap(); + let subtree_node_idx = get_sst_root_node_idx(&lms_parameter, &sst_extension); + assert_eq!(31, subtree_node_idx); + } + + // wrong config, subtree_node_idx too high + #[test] + #[should_panic] + fn test_get_sst_root_node_idx_8() { + let lms_parameter = LmsAlgorithm::get_from_type::(5).unwrap(); + let sst_extension = SstExtension::new(17, 4).unwrap(); + let _subtree_node_idx = get_sst_root_node_idx(&lms_parameter, &sst_extension); + } + + #[test] + fn test_get_sst_number_of_lm_ots_keys_1() { + let lms_parameter = LmsAlgorithm::get_from_type::(5).unwrap(); + let sst_extension = SstExtension::new(16, 4).unwrap(); + let num_lm_ots_keys = get_sst_number_of_lm_ots_keys(&lms_parameter, &sst_extension); + assert_eq!(2, num_lm_ots_keys); + } + + #[test] + #[should_panic] + fn test_get_sst_number_of_lm_ots_keys_2() { + let lms_parameter = LmsAlgorithm::get_from_type::(5).unwrap(); + let sst_extension = SstExtension::new(16, 6).unwrap(); + let _num_lm_ots_keys = get_sst_number_of_lm_ots_keys(&lms_parameter, &sst_extension); + } + + #[test] + fn test_get_sst_first_leaf_idx_1() { + let lms_parameter = LmsAlgorithm::get_from_type::(5).unwrap(); + let sst_extension = SstExtension::new(4, 3).unwrap(); + let first_leaf_idx = get_sst_first_leaf_idx(&lms_parameter, &sst_extension); + assert_eq!(12, first_leaf_idx); + } + + #[test] + fn test_get_sst_first_leaf_idx_2() { + let lms_parameter = LmsAlgorithm::get_from_type::(5).unwrap(); + let sst_extension = SstExtension::new(4, 4).unwrap(); + let first_leaf_idx = get_sst_first_leaf_idx(&lms_parameter, &sst_extension); + assert_eq!(6, first_leaf_idx); + } + + #[test] + fn test_get_sst_last_leaf_idx_1() { + let lms_parameter = LmsAlgorithm::get_from_type::(5).unwrap(); + let sst_extension = SstExtension::new(4, 3).unwrap(); + let first_leaf_idx = get_sst_last_leaf_idx(&lms_parameter, &sst_extension); + assert_eq!(15, first_leaf_idx); + } + + #[test] + fn test_get_sst_last_leaf_idx_2() { + let lms_parameter = LmsAlgorithm::get_from_type::(5).unwrap(); + let sst_extension = SstExtension::new(4, 4).unwrap(); + let first_leaf_idx = get_sst_last_leaf_idx(&lms_parameter, &sst_extension); + assert_eq!(7, first_leaf_idx); + } + + #[test] + fn test_get_num_signing_entities_1() { + let seed = gen_random_seed::(); + let sst_extension = SstExtension::new(4, 3).unwrap(); + let rfc_private_key = ReferenceImplPrivateKey::::generate( + &[ + HssParameter::construct_default_parameters(), + HssParameter::construct_default_parameters(), + ], + &seed, + Some(sst_extension), + ) + .unwrap(); + let private_key = rfc_private_key.to_binary_representation(); + let num_se = get_num_signing_entities::(&private_key).unwrap(); + assert_eq!(8, num_se); + } + + #[test] + #[should_panic] + fn test_get_num_signing_entities_2() { + let seed = gen_random_seed::(); + let rfc_private_key = ReferenceImplPrivateKey::::generate( + &[ + HssParameter::construct_default_parameters(), + HssParameter::construct_default_parameters(), + ], + &seed, + None, + ) + .unwrap(); + let private_key = rfc_private_key.to_binary_representation(); + let _num_se = get_num_signing_entities::(&private_key).unwrap(); + } +} diff --git a/src/sst/mod.rs b/src/sst/mod.rs new file mode 100644 index 00000000..22631e86 --- /dev/null +++ b/src/sst/mod.rs @@ -0,0 +1,5 @@ +pub mod gen_key; +pub use helper::get_num_signing_entities; + +pub(crate) mod helper; +pub(crate) mod parameters; diff --git a/src/sst/parameters.rs b/src/sst/parameters.rs new file mode 100644 index 00000000..88d6ee88 --- /dev/null +++ b/src/sst/parameters.rs @@ -0,0 +1,37 @@ +use crate::{constants::SST_SIZE, Error}; + +use zeroize::{Zeroize, ZeroizeOnDrop}; + +#[derive(Debug, Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)] +pub struct SstExtension { + signing_entity_idx: u8, // from 1 to (2^l0_top_div) + l0_top_div: u8, // e.g. L-0 LMS height of 5 and l0_top_div = 3: division top/bottom is 3/2 -> 2^3 = 8 signing entities +} + +impl SstExtension { + pub fn new(signing_entity_idx: u8, l0_top_div: u8) -> Result { + (signing_entity_idx != 0 && l0_top_div != 0) + .then_some(()) + .ok_or(Error::new())?; + (signing_entity_idx as u32 <= 2u32.pow(l0_top_div as u32)) + .then_some(()) + .ok_or(Error::new())?; + Ok(Self { + signing_entity_idx, + l0_top_div, + }) + } + + pub(crate) fn from_slice(data: &[u8]) -> Result { + (data.len() == SST_SIZE).then_some(()).ok_or(())?; + Self::new(data[0], data[1]).map_err(|_| ()) + } + + pub fn signing_entity_idx(&self) -> u8 { + self.signing_entity_idx + } + + pub(crate) fn l0_top_div(&self) -> u8 { + self.l0_top_div + } +} diff --git a/tests/reference_implementation.rs b/tests/reference_implementation.rs index 96f601d1..54ff1af5 100644 --- a/tests/reference_implementation.rs +++ b/tests/reference_implementation.rs @@ -125,8 +125,8 @@ fn should_produce_same_private_key() { let ref_signing_key = read_private_key(path); let ref_verifying_key = read_public_key(path); - assert!(ref_signing_key == sk.as_slice()); - assert!(ref_verifying_key == vk.as_slice()); + assert_eq!(ref_signing_key, sk.as_slice()); + assert_eq!(ref_verifying_key, vk.as_slice()); } fn read_private_key(path: &Path) -> Vec { @@ -182,6 +182,7 @@ fn own_signing( &private_key_before, &mut update_private_key, Some(aux_slice), + None, ) .expect("Signing should succed."); save_file(