Skip to content

Commit 4f3ae1c

Browse files
committed
[WIP] hs1: use u128 instead of BigInt
With a better algorithm (based on Horner's Method, as recommended by Ted Krovetz), a bigint isn't necessary, just 64-64 multiplication. This addresses the XXX_QUESTION around NH and Hash. Further efficiency is gained by avoiding a physical modulo operation, instead using the identity (a % (2^61 - 1)) = (a >> 61) + (a & (2^61 -1)), and borrow's from Ted's reference implementation. This pulls in the extprim crate as an interim measure until rust-lang/rust#35954 is released, which provides native 128-bit words.
1 parent 6df53ff commit 4f3ae1c

File tree

3 files changed

+114
-65
lines changed

3 files changed

+114
-65
lines changed

Cargo.toml

+5-1
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,9 @@ libc = "^0.2"
2424
time = "^0.1"
2525
rand = "^0.3"
2626
rustc-serialize = "^0.3"
27-
num = "*"
2827
bit-vec = "^0.4"
28+
extprim = "^1.1"
29+
30+
[dev-dependencies]
31+
num = "*"
32+
quickcheck = "*"

src/hs1.rs

+104-63
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,16 @@ use std::iter::repeat;
2828
use std::result::Result;
2929
use std::slice::Chunks;
3030
use std::vec::Vec;
31-
32-
pub use num::bigint::{BigInt, ToBigInt};
33-
pub use num::traits::{FromPrimitive, ToPrimitive};
31+
use std::num::Wrapping;
3432

3533
use chacha20::ChaCha20;
3634
use cryptoutil::xor_keystream;
3735
use symmetriccipher::SynchronousStreamCipher; // Used in order to call ChaCha20::process().
3836

3937
extern crate bit_vec;
38+
extern crate extprim;
4039
use self::bit_vec::BitVec;
41-
42-
macro_rules! u64toBI {
43-
($x:expr) => (BigInt::from_u64($x).expect(&format!("Couldn't convert {:?} into BigInt", $x)[..]))
44-
}
40+
use self::extprim::u128::u128;
4541

4642
#[derive(Debug, Clone, Copy)]
4743
pub enum Error {
@@ -327,11 +323,11 @@ impl Subkeygen for HS1 {
327323
assert_eq!(kPrime.len(), 32);
328324

329325
N = toStr(12,
330-
&((self.parameters.b as u64 * 2u64.pow(48) +
331-
self.parameters.t as u64 * 2u64.pow(40) +
332-
self.parameters.r as u64 * 2u64.pow(32) +
333-
self.parameters.l as u64 * 2u64.pow(16) +
334-
K.len() as u64) as usize));
326+
((self.parameters.b as u64 * 2u64.pow(48) +
327+
self.parameters.t as u64 * 2u64.pow(40) +
328+
self.parameters.r as u64 * 2u64.pow(32) +
329+
self.parameters.l as u64 * 2u64.pow(16) +
330+
K.len() as u64) as usize));
335331
N.truncate(12);
336332

337333
chacha = ChaCha20::new(&kPrime, &N[..], Some(self.parameters.r as i8));
@@ -480,45 +476,65 @@ impl PRF for HS1 {
480476
/// 7. else Y = toStr(4, (kA[0] + kA[1] × (h mod 2^32) + kA[2] × (h div 2 ^32)) div 2^32)
481477
impl Hash for HS1 {
482478
fn hash(&self, kN: &[u32], kP: &u64, kA: &[u64], M: &[u8]) -> Vec<u8> {
479+
const T61_1: u64 = (1 << 61) - 1;
480+
const T60_1: u64 = (1 << 60) - 1;
483481
let n: u32;
484482
let Mi: Chunks<u8>;
485483
let mut Y: Vec<u8>;
486-
let mut a: Vec<BigInt> = Vec::new();
487-
let mut h: BigInt;
488-
let mut m: BigInt; // m is set to one of two moduli, each reused rather than recomputed.
484+
let mut h: u64 = 1;
485+
486+
fn mod60(a: u64) -> u64 {
487+
(a >> 60) + (a & T60_1)
488+
}
489+
490+
fn mod61(a: u64) -> u64 {
491+
(a >> 61) + (a & T61_1)
492+
}
493+
494+
fn poly_step(a: u64, m: u64, k: u64) -> u64 {
495+
let tmp = mod61((u128::new(a).wrapping_mul(u128::new(k))).low64());
496+
tmp + m
497+
}
498+
499+
fn poly_finalize(mut a: u64) -> u64 {
500+
a = (a & T61_1) + (a >> 61);
501+
if a == T61_1 {
502+
0
503+
} else {
504+
a
505+
}
506+
}
489507

490508
// 1. n = max(⌈|M|/b⌉, 1)
491509
n = std::cmp::max(M.len() as u32 / self.parameters.b as u32, 1);
492510

493511
// 2. M_1 || M_2 || … || M_n = M and |M_i| = b for each 1 ≤ i ≤ n.
494512
Mi = M.chunks(self.parameters.b as usize);
495513

514+
debug_assert!(Mi.clone().count() == n as usize);
515+
496516
// 3. m_i = toInts(4, pad(16, M_i)) for each 1 ≤ i ≤ n.
497-
for (_, chunk) in Mi.enumerate() {
517+
for (i, chunk) in Mi.enumerate() {
498518
let mi: Vec<u32> = toInts4(&pad(16, &chunk)).unwrap();
499519
// 4. a_i = NH(kN, m_i) mod 2^60 + (|M_i| mod 16) for each 1 ≤ i ≤ n.
500-
a.push(NH(kN, &mi) + BigInt::from_u8(self.parameters.b % 16u8).unwrap());
501-
}
502-
// 5. h = kP^n + (a_1 × kP^(n-1)) + (a_2 × kP^(n-2)) + ... + (a_n × kP^0) mod (2^61 - 1)
503-
h = u64toBI!((*kP as u64).pow(n) % 2u64.pow(61) - 1);
504-
m = u64toBI!(2u64.pow(61) - 1);
505-
for (ai, j) in a.iter().zip(n as i32..0) {
506-
h = h + (ai % m.clone()) * (u64toBI!(kP.pow(j as u32)) % m.clone()) % m.clone();
520+
let ai: u64 = NH(kN, &mi);// + ((self.parameters.b % 16u8) as u64);
521+
// 5. h = kP^n + (a_1 × kP^(n-1)) + (a_2 × kP^(n-2)) + ... + (a_n × kP^0) mod (2^61 - 1)
522+
// (computed via horner's method)
523+
h = poly_step(h, mod60(ai), *kP);
507524
}
525+
h = poly_finalize(h);
508526
// 6. if (t ≤ 4) Y = toStr(8, h)
509527
if self.parameters.t <= 4 {
510-
Y = toStr(8, &(h.to_u64().unwrap() as usize));
528+
Y = toStr(8, h as usize); // XXX cmr: I don't like the look of that 'as usize'
511529
Y.truncate(8);
512530
} else {
513531
// 7. else Y = toStr(4, (kA[0] + kA[1] × (h mod 2^32) + kA[2] × (h div 2 ^32)) div 2^32)
514-
m = u64toBI!(2u64.pow(32));
515-
Y = toStr(4,
516-
&(((u64toBI!(kA[0].clone()) +
517-
u64toBI!(kA[1].clone()) * (h.clone() % m.clone()) +
518-
u64toBI!(kA[2].clone()) * (h.clone() / m.clone())) /
519-
m.clone())
520-
.to_u64()
521-
.unwrap() as usize));
532+
let m: u64 = 1 << 32;
533+
let div: u64 = h / m;
534+
let mod_: u64 = h % m;
535+
536+
let tmp = (kA[0].wrapping_add(kA[1].wrapping_mul(mod_)).wrapping_add(kA[2].wrapping_mul(div))) / m;
537+
Y = toStr(4, tmp as usize); // XXX cmr: I don't like the look of that 'as usize'
522538
Y.truncate(4);
523539
}
524540
Y
@@ -561,7 +577,7 @@ impl Encrypt for HS1 {
561577
let C: Vec<u8>;
562578

563579
k = self.subkeygen(&take32(K));
564-
m = [pad(16, &A), pad(16, &M), toStr(8, &A.len()), toStr(8, &M.len())].concat();
580+
m = [pad(16, &A), pad(16, &M), toStr(8, A.len()), toStr(8, M.len())].concat();
565581

566582
// XXX_QUESTION: Here we are supposed to use `l` as the final parameter to prf(). However,
567583
// because y must equal 32 — as noted in a XXX_QUESTION above in prf() — we can only do this
@@ -638,7 +654,7 @@ impl Decrypt for HS1 {
638654
&C,
639655
&self.prf(&k, &T, N, (64 + C.len()) as i64)[64..C.len()]);
640656
M = out.to_vec();
641-
m = [pad(16, &A), pad(16, &M), toStr(8, &A.len()), toStr(8, &M.len())].concat();
657+
m = [pad(16, &A), pad(16, &M), toStr(8, A.len()), toStr(8, M.len())].concat();
642658
t = self.prf(&k, &m, N, self.parameters.l as i64);
643659

644660
if *T == t {
@@ -649,18 +665,12 @@ impl Decrypt for HS1 {
649665
}
650666
}
651667

652-
// XXX_QUESTION: We moved the `mod 2^60` from hash() to NH() for simplicity… should this be changed
653-
// in the spec?
654-
//
655-
// XXX_QUESTION: This is the only place in HS1-SIV which requires a bignum… everything else can get
656-
// away with utilising either u32 or u64. Should/can this be restructured to avoid needing a bignum?
657-
//
658668
/// Given vectors of integers, `v1` and `v2`, returns the result of the following algorithm:
659669
///
660670
/// ```text
661-
/// n/4 ⎛ ⎞
662-
/// NH(v1, v2) = Σ ⎜(v1[4i-3]+v2[4i-3]) × (v1[4i-1]+v2[4i-1]) +⎟
663-
/// i=1 ⎝(v1[4i-2]+v2[4i-2]) × (v1[4i]+v2[4i])
671+
/// n/4 ⎛
672+
/// NH(v1, v2) = Σ ⎜(v1[4i-3]+v2[4i-3] mod 2^32) × (v1[4i-1]+v2[4i-1] mod 2^32) +⎟ mod 2^64
673+
/// i=1 ⎝(v1[4i-2]+v2[4i-2] mod 2^32) × (v1[4i ]+v2[4i ] mod 2^32)
664674
/// ```
665675
/// where `n = min(v1.len(), v2.len())` and is alway a multiple of 4.
666676
///
@@ -675,21 +685,24 @@ impl Decrypt for HS1 {
675685
/// let v2: Vec<u32> = vec![543516756, 2003792483, 1768711712, 1629516645,
676686
/// 1768759412, 1734962788, 3044456, 0];
677687
///
678-
/// assert_eq!(NH(&v1, &v2).to_u64().unwrap(), 162501409595406698u64);
688+
/// assert_eq!(NH(&v1, &v2), 162501409595406698u64);
679689
/// ```
680-
pub fn NH(v1: &[u32], v2: &[u32]) -> BigInt {
681-
let mut sum: BigInt = BigInt::from_usize(0).unwrap();
682-
let m: BigInt = BigInt::from_u64(2u64.pow(60)).unwrap();
683-
let bn1: Vec<BigInt> = v1.iter().map(|x| x.to_bigint().unwrap()).collect();
684-
let bn2: Vec<BigInt> = v2.iter().map(|x| x.to_bigint().unwrap()).collect();
685-
686-
for i in 1..std::cmp::min(bn1.len(), bn2.len()) / 4 {
687-
sum = sum +
688-
(((&bn1[4 * i - 3] + &bn2[4 * i - 3]) * (&bn1[4 * i - 1] + &bn2[4 * i - 1]) +
689-
(&bn1[4 * i - 2] + &bn2[4 * i - 2]) * (&bn1[4 * i] + &bn2[4 * i])) %
690-
&m);
690+
pub fn NH(v1: &[u32], v2: &[u32]) -> u64 {
691+
let mut sum: Wrapping<u64> = Wrapping(0);
692+
693+
for i in 1..std::cmp::min(v1.len(), v2.len()) / 4 {
694+
// XXX cmr: RIP debug perf.
695+
let va = Wrapping(v1[4 * i - 3]);
696+
let vb = Wrapping(v2[4 * i - 3]);
697+
let vc = Wrapping(v1[4 * i - 2]);
698+
let vd = Wrapping(v2[4 * i - 2]);
699+
let ve = Wrapping(v1[4 * i - 1]);
700+
let vf = Wrapping(v2[4 * i - 1]);
701+
let vg = Wrapping(v1[4 * i]);
702+
let vh = Wrapping(v2[4 * i]);
703+
sum += (Wrapping((va + vb).0 as u64) * Wrapping((ve + vf).0 as u64)) + (Wrapping((vc + vd).0 as u64) * Wrapping((vg + vh).0 as u64));
691704
}
692-
sum
705+
sum.0
693706
}
694707

695708
// -------------------------------------------------------------------------------------------------
@@ -745,7 +758,7 @@ fn pad(multiple: usize, input: &[u8]) -> Vec<u8> {
745758
/// let s3: Vec<u8> = toStr(4, &4294967295);
746759
/// assert!(vec![255, 255, 255, 255] == s3);
747760
/// ```
748-
pub fn toStr<'a>(n: isize, x: &'a usize) -> Vec<u8> {
761+
pub fn toStr(n: isize, x: usize) -> Vec<u8> {
749762
let binary: String = format!("{:b}", x.to_le());
750763
let len: isize = n * 8isize - binary.len() as isize;
751764
let bits = (0..binary.len()).map(|i| binary.chars().nth(i).unwrap() == '1');
@@ -852,7 +865,11 @@ fn take32<'a>(x: &'a [u8]) -> [u8; 32] {
852865
#[cfg(test)]
853866
mod tests {
854867
use hs1::*;
868+
use std;
869+
use quickcheck::TestResult;
855870
use std::iter::repeat;
871+
use num::bigint::{BigInt, ToBigInt};
872+
use num::traits::{FromPrimitive, ToPrimitive};
856873

857874
static KEY_32_BYTES: [u8; 32] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
858875
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20,
@@ -872,32 +889,32 @@ mod tests {
872889
#[test]
873890
fn test_hs1_toStr_toInts4_3() {
874891
let orig: usize = 3;
875-
assert_eq!(toInts4(&toStr(4, &orig)).unwrap()[0], orig as u32);
892+
assert_eq!(toInts4(&toStr(4, orig)).unwrap()[0], orig as u32);
876893
}
877894
#[test]
878895
fn test_hs1_toStr_toInts4_256() {
879896
let orig: usize = 256;
880-
assert_eq!(toInts4(&toStr(4, &orig)).unwrap()[0], orig as u32);
897+
assert_eq!(toInts4(&toStr(4, orig)).unwrap()[0], orig as u32);
881898
}
882899
#[test]
883900
fn test_hs1_toStr_toInts4_4294967295() {
884901
let orig: usize = 4294967295;
885-
assert_eq!(toInts4(&toStr(4, &orig)).unwrap()[0], orig as u32);
902+
assert_eq!(toInts4(&toStr(4, orig)).unwrap()[0], orig as u32);
886903
}
887904
#[test]
888905
fn test_hs1_toStr_toInts8_3() {
889906
let orig: usize = 3;
890-
assert_eq!(toInts8(&toStr(8, &orig)).unwrap()[0], orig as u64);
907+
assert_eq!(toInts8(&toStr(8, orig)).unwrap()[0], orig as u64);
891908
}
892909
#[test]
893910
fn test_hs1_toStr_toInts8_256() {
894911
let orig: usize = 256;
895-
assert_eq!(toInts8(&toStr(8, &orig)).unwrap()[0], orig as u64);
912+
assert_eq!(toInts8(&toStr(8, orig)).unwrap()[0], orig as u64);
896913
}
897914
#[test]
898915
fn test_hs1_toStr_toInts8_18446744073709551615() {
899916
let orig: usize = 18446744073709551615;
900-
assert_eq!(toInts8(&toStr(8, &orig)).unwrap()[0], orig as u64);
917+
assert_eq!(toInts8(&toStr(8, orig)).unwrap()[0], orig as u64);
901918
}
902919

903920
#[test]
@@ -1063,4 +1080,28 @@ mod tests {
10631080
hs1.decrypt(&KEY_32_BYTES[..], &a, &e, &associated_data(), &nonce());
10641081
assert_eq!(&d.unwrap()[..], &msg()[..]);
10651082
}
1083+
1084+
fn old_nh(v1: &[u32], v2: &[u32]) -> u64 {
1085+
let mut sum: BigInt = BigInt::from_usize(0).unwrap();
1086+
let m: BigInt = BigInt::from_u64(2u64.pow(60)).unwrap();
1087+
let bn1: Vec<BigInt> = v1.iter().map(|x| x.to_bigint().unwrap()).collect();
1088+
let bn2: Vec<BigInt> = v2.iter().map(|x| x.to_bigint().unwrap()).collect();
1089+
1090+
for i in 1..std::cmp::min(bn1.len(), bn2.len()) / 4 {
1091+
sum = sum +
1092+
(((&bn1[4 * i - 3] + &bn2[4 * i - 3]) * (&bn1[4 * i - 1] + &bn2[4 * i - 1]) +
1093+
(&bn1[4 * i - 2] + &bn2[4 * i - 2]) * (&bn1[4 * i] + &bn2[4 * i])) %
1094+
&m);
1095+
}
1096+
sum.to_u64().unwrap()
1097+
}
1098+
1099+
quickcheck! {
1100+
fn nh_optzn_is_correct(v1: Vec<u32>, v2: Vec<u32>) -> TestResult {
1101+
if v1.len() != v2.len() || std::cmp::min(v1.len(), v2.len()) % 4 != 0 {
1102+
return TestResult::discard();
1103+
}
1104+
return TestResult::from_bool(super::NH(&v1, &v2) == old_nh(&v1, &v2));
1105+
}
1106+
}
10661107
}

src/lib.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@ extern crate rand;
1414
extern crate rustc_serialize as serialize;
1515
extern crate time;
1616
extern crate libc;
17-
extern crate num;
1817

1918
#[cfg(all(test, feature = "with-bench"))]
2019
extern crate test;
20+
#[cfg(test)]
21+
#[macro_use]
22+
extern crate quickcheck;
23+
#[cfg(test)]
24+
extern crate num;
2125

2226
pub mod aead;
2327
pub mod aes;

0 commit comments

Comments
 (0)