ic_canister_kit/number/
mix.rs1use candid::CandidType;
4use serde::{Deserialize, Serialize};
5
6#[inline]
8pub fn encode_index_code(salt: &[u8], index: u64, random: Option<&[u8]>) -> Vec<u8> {
9 let trimmed = trim_index(index); let mix = mix_numbers(&trimmed, random); use sha2::Digest;
13 let mut hasher = sha2::Sha256::new();
14 hasher.update(&mix);
15 hasher.update(salt); let digest: [u8; 32] = hasher.finalize().into(); let mut show = Vec::with_capacity(mix.len() + 4);
19
20 show.extend_from_slice(&digest[0..4]); show.extend_from_slice(&mix); show
25}
26
27#[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#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
36pub enum MixNumberDecodeError {
37 WrongLength,
39 WrongChecksum,
41 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
59pub 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); }
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); let digest: [u8; 32] = hasher.finalize().into(); if show[0..4] != digest[0..4] {
73 return Err(MixNumberDecodeError::WrongChecksum); }
75
76 let trimmed = restore_numbers(mix);
77
78 let index = restore_index(&trimmed);
79
80 Ok(index)
81}
82pub 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
89fn 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#[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
111fn 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) | ((m & 0b0100_0000) >> 1) | ((m & 0b0010_0000) >> 2) | ((m & 0b0001_0000) >> 3) | ((n & 0b1000_0000) >> 1) | ((n & 0b0100_0000) >> 2) | ((n & 0b0010_0000) >> 3) | ((n & 0b0001_0000) >> 4), 0b0000_0000
126 | ((m & 0b0000_1000) << 4) | ((m & 0b0000_0100) << 3) | ((m & 0b0000_0010) << 2) | ((m & 0b0000_0001) << 1) | ((n & 0b0000_1000) << 3) | ((n & 0b0000_0100) << 2) | ((n & 0b0000_0010) << 1) | ((n & 0b0000_0001) << 0), ]
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
146fn 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)); 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}