Skip to main content

ic_canister_kit/number/
mix.rs

1//! 混合数字, 利用随机数进行数字混淆, 不容易猜中原数字
2
3use candid::CandidType;
4use serde::{Deserialize, Serialize};
5
6/// 根据指定序号生成一个加密字符串
7#[inline]
8pub fn encode_index_code(salt: &[u8], index: u64, random: Option<&[u8]>) -> Vec<u8> {
9    let trimmed = trim_index(index); // 去除前置 0
10    let mix = mix_numbers(&trimmed, random); // 用随机数拓展位数
11
12    use sha2::Digest;
13    let mut hasher = sha2::Sha256::new();
14    hasher.update(&mix);
15    hasher.update(salt); // 加盐
16    let digest: [u8; 32] = hasher.finalize().into(); // 取得 hash 结果
17
18    let mut show = Vec::with_capacity(mix.len() + 4);
19
20    show.extend_from_slice(&digest[0..4]); // 取前 4 位作为校验
21
22    show.extend_from_slice(&mix); // 补上拓展后的数据
23
24    show
25}
26
27/// 编码数字成字符串
28#[inline]
29pub fn encode_index_code_with_base32(salt: &[u8], index: u64, random: Option<&[u8]>) -> String {
30    let show = encode_index_code(salt, index, random);
31    base32::encode(base32::Alphabet::Rfc4648 { padding: false }, &show)
32}
33
34/// 混淆错误
35#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
36pub enum MixNumberDecodeError {
37    /// 长度错误
38    WrongLength,
39    /// 校验码错误
40    WrongChecksum,
41    /// 编码错误
42    Base32DecodeError,
43}
44impl std::fmt::Display for MixNumberDecodeError {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        write!(
47            f,
48            "{}",
49            match self {
50                MixNumberDecodeError::WrongLength => "Wrong mix number length",
51                MixNumberDecodeError::WrongChecksum => "Wrong mix number checksum",
52                MixNumberDecodeError::Base32DecodeError => "base32 decode failed",
53            }
54        )
55    }
56}
57impl std::error::Error for MixNumberDecodeError {}
58
59/// 根据加密结果解析回序号
60pub fn decode_index_code(salt: &[u8], show: &[u8]) -> Result<u64, MixNumberDecodeError> {
61    if show.len() <= 4 || !show.len().is_multiple_of(2) {
62        return Err(MixNumberDecodeError::WrongLength); // 长度不对
63    }
64    let mix = &show[4..];
65
66    use sha2::Digest;
67    let mut hasher = sha2::Sha256::new();
68    hasher.update(mix);
69    hasher.update(salt); // 加盐
70    let digest: [u8; 32] = hasher.finalize().into(); // 取得 hash 结果
71
72    if show[0..4] != digest[0..4] {
73        return Err(MixNumberDecodeError::WrongChecksum); // 校验失败
74    }
75
76    let trimmed = restore_numbers(mix);
77
78    let index = restore_index(&trimmed);
79
80    Ok(index)
81}
82/// 根据加密字符串解析回序号
83pub fn decode_index_code_by_base32(salt: &[u8], code: &str) -> Result<u64, MixNumberDecodeError> {
84    let show = base32::decode(base32::Alphabet::Rfc4648 { padding: false }, code)
85        .ok_or(MixNumberDecodeError::Base32DecodeError)?;
86    decode_index_code(salt, &show)
87}
88
89// ================ 工具方法 ================
90
91// 裁剪数字
92// 保留有效位的数字, 最少一个 u8 // ? 也就是说前面太多 0 的情况下会只留下后面有效的
93fn trim_index(index: u64) -> Vec<u8> {
94    let bytes = index.to_be_bytes();
95    bytes.into_iter().skip_while(|n| *n == 0).collect()
96}
97
98// 恢复数字 大端法 高位在前
99#[allow(clippy::identity_op)]
100fn restore_index(numbers: &[u8]) -> u64 {
101    let mut bytes = [0_u8; 8];
102
103    let len = numbers.len();
104    for i in 0..len {
105        bytes[8 - len + i] = numbers[i];
106    }
107
108    u64::from_be_bytes(bytes)
109}
110
111// 混合数字 // 位数交叉
112fn mix_numbers(numbers: &[u8], random: Option<&[u8]>) -> Vec<u8> {
113    #[allow(clippy::identity_op)]
114    fn mix_single(m: u8, n: u8) -> [u8; 2] {
115        [
116            0b0000_0000
117                | ((m & 0b1000_0000) >> 0)  // x000_0000 奇数位
118                | ((m & 0b0100_0000) >> 1)  // 00x0_0000 奇数位
119                | ((m & 0b0010_0000) >> 2)  // 0000_x000 奇数位
120                | ((m & 0b0001_0000) >> 3)  // 0000_00x0 奇数位
121                | ((n & 0b1000_0000) >> 1)  // 0x00_0000 偶数位
122                | ((n & 0b0100_0000) >> 2)  // 000x_0000 偶数位
123                | ((n & 0b0010_0000) >> 3)  // 0000_0x00 偶数位
124                | ((n & 0b0001_0000) >> 4), // 0000_000x 偶数位
125            0b0000_0000
126                | ((m & 0b0000_1000) << 4)  // x000_0000 奇数位
127                | ((m & 0b0000_0100) << 3)  // 00x0_0000 奇数位
128                | ((m & 0b0000_0010) << 2)  // 0000_x000 奇数位
129                | ((m & 0b0000_0001) << 1)  // 0000_00x0 奇数位
130                | ((n & 0b0000_1000) << 3)  // 0x00_0000 偶数位
131                | ((n & 0b0000_0100) << 2)  // 000x_0000 偶数位
132                | ((n & 0b0000_0010) << 1)  // 0000_0x00 偶数位
133                | ((n & 0b0000_0001) << 0), // 0000_000x 偶数位
134        ]
135    }
136
137    let random = random.unwrap_or_default();
138
139    let mut ns = Vec::with_capacity(numbers.len() * 2);
140    for (i, n) in numbers.iter().enumerate() {
141        ns.extend_from_slice(&mix_single(random.get(i).copied().unwrap_or(0), *n));
142    }
143    ns
144}
145
146// 恢复数字 // 位数交叉
147fn restore_numbers(ns: &[u8]) -> Vec<u8> {
148    #[allow(clippy::identity_op)]
149    fn restore_single(n1: u8, n2: u8) -> u8 {
150        0b0000_0000
151            | ((n1 & 0b0100_0000) << 1)
152            | ((n1 & 0b0001_0000) << 2)
153            | ((n1 & 0b0000_0100) << 3)
154            | ((n1 & 0b0000_0001) << 4)
155            | ((n2 & 0b0100_0000) >> 3)
156            | ((n2 & 0b0001_0000) >> 2)
157            | ((n2 & 0b0000_0100) >> 1)
158            | ((n2 & 0b0000_0001) >> 0)
159    }
160
161    assert!(ns.len().is_multiple_of(2)); // ! 必须是偶数
162
163    let mut numbers = Vec::new();
164    for i in 0..(ns.len() / 2) {
165        numbers.push(restore_single(ns[i * 2], ns[i * 2 + 1]));
166    }
167    numbers
168}