const IV: [u32; 8] = [
0x7380_166f,
0x4914_b2b9,
0x1724_42d7,
0xda8a_0600,
0xa96f_30bc,
0x1631_38aa,
0xe38d_ee4d,
0xb0fb_0e4e,
];
#[inline]
fn p0(x: u32) -> u32 {
x ^ x.rotate_left(9) ^ x.rotate_left(17)
}
#[inline]
fn p1(x: u32) -> u32 {
x ^ x.rotate_left(15) ^ x.rotate_left(23)
}
pub fn sm3(data: &[u8]) -> [u8; 32] {
let mut v = IV;
let bit_len = (data.len() as u64).wrapping_mul(8);
let mut msg = data.to_vec();
msg.push(0x80);
while msg.len() % 64 != 56 {
msg.push(0x00);
}
msg.extend_from_slice(&bit_len.to_be_bytes());
for block in msg.chunks_exact(64) {
compress(&mut v, block);
}
let mut out = [0u8; 32];
for (i, word) in v.iter().enumerate() {
out[i * 4..i * 4 + 4].copy_from_slice(&word.to_be_bytes());
}
out
}
fn compress(v: &mut [u32; 8], block: &[u8]) {
let mut w = [0u32; 68];
for i in 0..16 {
w[i] = u32::from_be_bytes([
block[i * 4],
block[i * 4 + 1],
block[i * 4 + 2],
block[i * 4 + 3],
]);
}
for i in 16..68 {
w[i] = p1(w[i - 16] ^ w[i - 9] ^ w[i - 3].rotate_left(15))
^ w[i - 13].rotate_left(7)
^ w[i - 6];
}
let mut w1 = [0u32; 64];
for i in 0..64 {
w1[i] = w[i] ^ w[i + 4];
}
let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = *v;
for j in 0..64 {
let t = if j < 16 {
0x79cc_4519u32
} else {
0x7a87_9d8au32
};
let ss1 = a
.rotate_left(12)
.wrapping_add(e)
.wrapping_add(t.rotate_left(j as u32))
.rotate_left(7);
let ss2 = ss1 ^ a.rotate_left(12);
let (ff, gg) = if j < 16 {
(a ^ b ^ c, e ^ f ^ g)
} else {
((a & b) | (a & c) | (b & c), (e & f) | (!e & g))
};
let tt1 = ff.wrapping_add(d).wrapping_add(ss2).wrapping_add(w1[j]);
let tt2 = gg.wrapping_add(h).wrapping_add(ss1).wrapping_add(w[j]);
d = c;
c = b.rotate_left(9);
b = a;
a = tt1;
h = g;
g = f.rotate_left(19);
f = e;
e = p0(tt2);
}
v[0] ^= a;
v[1] ^= b;
v[2] ^= c;
v[3] ^= d;
v[4] ^= e;
v[5] ^= f;
v[6] ^= g;
v[7] ^= h;
}
const B64: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
pub fn base64_encode(data: &[u8]) -> String {
let mut out = String::with_capacity(data.len().div_ceil(3) * 4);
for chunk in data.chunks(3) {
let b0 = chunk[0] as usize;
let b1 = chunk.get(1).copied().unwrap_or(0) as usize;
let b2 = chunk.get(2).copied().unwrap_or(0) as usize;
out.push(B64[b0 >> 2] as char);
out.push(B64[((b0 & 0x03) << 4) | (b1 >> 4)] as char);
out.push(if chunk.len() > 1 {
B64[((b1 & 0x0f) << 2) | (b2 >> 6)] as char
} else {
'='
});
out.push(if chunk.len() > 2 {
B64[b2 & 0x3f] as char
} else {
'='
});
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sm3_abc() {
let digest = sm3(b"abc");
let hex: String = digest.iter().map(|b| format!("{b:02x}")).collect();
assert_eq!(
hex,
"66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0"
);
}
#[test]
fn sm3_long() {
let data = b"abcd".repeat(16);
let digest = sm3(&data);
let hex: String = digest.iter().map(|b| format!("{b:02x}")).collect();
assert_eq!(
hex,
"debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732"
);
}
#[test]
fn base64_basic() {
assert_eq!(base64_encode(b""), "");
assert_eq!(base64_encode(b"f"), "Zg==");
assert_eq!(base64_encode(b"fo"), "Zm8=");
assert_eq!(base64_encode(b"foo"), "Zm9v");
assert_eq!(base64_encode(b"foobar"), "Zm9vYmFy");
}
#[test]
fn checkvalue_form() {
let cv = base64_encode(&sm3(b"hello"));
assert_eq!(cv.len(), 44);
assert!(cv.ends_with('='));
}
}