Skip to content

Commit

Permalink
Avoid Vec allocation for i2osp
Browse files Browse the repository at this point in the history
In the vast majority of cases, we essentially want i2osp to act like
`to_be_bytes`. But, it does help to actually express things in a manner
consistent with the specs.  So, this makes i2osp a function with a const
generic parameter to control the size of the returned array.  It remains
as flexible as it was, but in the vast majority of cases where a usize
is turned into an 8-byte array, this compiles down to just a byte swap.
  • Loading branch information
jeddenlea committed Sep 18, 2024
1 parent b428e66 commit 8ce7423
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 22 deletions.
2 changes: 1 addition & 1 deletion src/bbsplus/generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ where
let mut buffer = vec![0u8; CS::EXPAND_LEN];
let mut generators = Vec::new();
for i in 1..count + 1 {
v = [v, i2osp(i, 8)].concat();
v = [&*v, &i2osp::<8>(i)].concat();
CS::Expander::expand_message(&[&v], &[&seed_dst], CS::EXPAND_LEN)
.unwrap()
.fill_bytes(&mut buffer);
Expand Down
2 changes: 1 addition & 1 deletion src/bbsplus/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ where
let key_dst = key_dst.unwrap_or(&key_dst_default);

// derive_input = key_material || I2OSP(length(key_info), 2) || key_info
let derive_input = [key_material, &i2osp(key_info.len(), 2), key_info].concat();
let derive_input = [key_material, &i2osp::<2>(key_info.len()), key_info].concat();

// SK = hash_to_scalar(derive_input, key_dst)
let sk = hash_to_scalar::<CS>(&derive_input, key_dst)?;
Expand Down
6 changes: 3 additions & 3 deletions src/bbsplus/proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -646,9 +646,9 @@ where
let ph = ph.unwrap_or(b"");

let mut c_arr: Vec<u8> = Vec::new();
c_arr.extend_from_slice(&i2osp(R, 8));
c_arr.extend_from_slice(&i2osp::<8>(R));
for (i, m) in core::iter::zip(disclosed_indexes, disclosed_messages) {
c_arr.extend_from_slice(&i2osp(*i, 8));
c_arr.extend_from_slice(&i2osp::<8>(*i));
c_arr.extend_from_slice(&m.value.to_bytes_be());
}
c_arr.extend_from_slice(&init_res.Abar.to_affine().to_compressed());
Expand All @@ -658,7 +658,7 @@ where
c_arr.extend_from_slice(&init_res.T2.to_affine().to_compressed());
c_arr.extend_from_slice(&init_res.domain.to_bytes_be());

let ph_i2osp = i2osp(ph.len(), 8);
let ph_i2osp = i2osp::<8>(ph.len());

c_arr.extend_from_slice(&ph_i2osp);
c_arr.extend_from_slice(ph);
Expand Down
58 changes: 41 additions & 17 deletions src/utils/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,19 @@ pub mod bbsplus_utils {
secret
}

pub fn i2osp(x: usize, x_len: usize) -> Vec<u8> {
let mut result = Vec::new();

let mut x_copy = x;

for _ in 0..x_len {
result.push((x_copy % 256) as u8);
x_copy /= 256;
pub fn i2osp<const N: usize>(x: usize) -> [u8; N] {
const SYS_LEN: usize = (usize::BITS / 8) as usize;
assert!(N >= SYS_LEN || x >> (8 * N) == 0, "i2osp overflow");
let be_bytes = x.to_be_bytes();
let mut out = [0; N];

use core::cmp::Ordering;
match N.cmp(&SYS_LEN) {
Ordering::Equal => out.copy_from_slice(&be_bytes),
Ordering::Greater => out[N - SYS_LEN..].copy_from_slice(&x.to_be_bytes()),
Ordering::Less => out.copy_from_slice(&be_bytes[SYS_LEN - N..]),
}

result.reverse(); // Since the most significant byte is at the end
result
out
}

/// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-06#name-hash-to-scalar
Expand Down Expand Up @@ -164,8 +165,7 @@ pub mod bbsplus_utils {
let domain_dst = [api_id, CS::H2S].concat();

let mut dom_octs: Vec<u8> = Vec::new();
let L_i2osp = i2osp(L, 8);
dom_octs.extend_from_slice(&L_i2osp);
dom_octs.extend_from_slice(&i2osp::<8>(L));
dom_octs.extend_from_slice(&Q1.to_affine().to_compressed());

H_points
Expand All @@ -179,9 +179,7 @@ pub mod bbsplus_utils {
dom_input.extend_from_slice(&pk.to_bytes());
dom_input.extend_from_slice(&dom_octs);

let header_i2osp = i2osp(header.len(), 8);

dom_input.extend_from_slice(&header_i2osp);
dom_input.extend_from_slice(&i2osp::<8>(header.len()));
dom_input.extend_from_slice(header);

hash_to_scalar::<CS>(&dom_input, &domain_dst)
Expand Down Expand Up @@ -389,7 +387,7 @@ pub mod bbsplus_utils {
let blind_challenge_dst = [api_id, CS::H2S].concat();

let mut c_arr: Vec<u8> = Vec::new();
c_arr.extend_from_slice(&i2osp(M, 8));
c_arr.extend_from_slice(&i2osp::<8>(M));
generators
.iter()
.for_each(|&i| c_arr.extend_from_slice(&i.to_affine().to_compressed()));
Expand Down Expand Up @@ -526,3 +524,29 @@ pub(crate) fn get_remaining_indexes(length: usize, indexes: &[usize]) -> Vec<usi

remaining
}

#[cfg(test)]
mod tests {
use super::bbsplus_utils::i2osp;

#[test]
fn test_i2osp() {
assert_eq!([0, 0, 0, 0, 1, 2, 3, 4], i2osp::<8>(0x1020304));
assert_eq!([0xf0, 0x0f], i2osp::<2>(0xf00f));
assert_eq!([0, 0, 0, 0, 0, 0, 0x12, 0x34], i2osp::<8>(0x1234));
assert_eq!([0, 0, 0, 0, 0x12, 0x34, 0x56, 0x78], i2osp::<8>(0x12345678));
assert_eq!([0, 0, 0x12, 0x34, 0x56, 0x78], i2osp::<6>(0x12345678));
assert_eq!([0, 0x12, 0x34, 0x56, 0x78], i2osp::<5>(0x12345678));
assert_eq!([0x12, 0x34, 0x56, 0x78], i2osp::<4>(0x12345678));
assert_eq!(
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0x12, 0x34, 0x56, 0x78],
i2osp::<13>(0x12345678)
);
}

#[test]
#[should_panic(expected = "i2osp overflow")]
fn test_i2osp_over() {
let _x = i2osp::<3>(0x12345678);
}
}

0 comments on commit 8ce7423

Please sign in to comment.