#![cfg(test)]
use ed25519_dalek::{PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH, Signature, SigningKey, VerifyingKey};
use indoc::indoc;
use pretty_assertions::assert_eq;
use std::{
fmt::Display,
ops::Range,
thread,
time::{Duration, SystemTime},
};
use crate::{SignedPayload, Sulid, UnsignedPayload};
#[test]
fn the_tests() {
let message = "This message will be signed and given an sULID.";
let secret = SigningKey::from_bytes(&[
43u8, 124, 75, 178, 112, 26, 99, 95, 202, 162, 110, 219, 239, 94, 157, 1, 8, 94, 219, 92,
103, 89, 196, 8, 0, 221, 163, 183, 0, 198, 101, 61,
]);
let app_context = b"my-app".to_vec();
let now_ms: u64 = 1787794007019; let now = SystemTime::UNIX_EPOCH + Duration::from_millis(now_ms);
let signed = Sulid::sign(
&secret,
UnsignedPayload {
timestamp: now,
message_hash: blake3hash(message.as_bytes()),
message_length: message.as_bytes().len() as u64,
app_metadata: app_context,
},
);
assert_eq!("01M10D78ZBGZNVWHA60G8P95W1", signed.sulid.to_string());
assert_eq!(true, signed.is_valid());
let mut bits_flipped = 0;
let modified_payloads = flip_bits(signed.clone());
while let Some(modified) = modified_payloads.recv().expect("recv()") {
assert_eq!(false, modified.is_valid());
bits_flipped += 1;
}
assert_eq!(1008, bits_flipped, "expected bit flips");
let mut lines = vec![];
lines.push(format!("id: {}", signed.sulid));
lines.push(format!("by: {}", b58(signed.public_key.as_bytes())));
lines.push(format!("sig: {}", b58(&signed.signature.to_bytes())));
lines.push("".into());
lines.push(message.into());
let text = lines.join("\n");
let expected = indoc! {"
id: 01M10D78ZBGZNVWHA60G8P95W1
by: WchunVqWZD7TfVoYkM1BPCzDpsrKTyN8ur2aZxWwbjQ
sig: 46kq3wNwQTjZKH9PjSWJeX9a7SwMkSni2t7hxorRDmgNXqrkYPey7WodyLs1npHBsFcdFGCJcV7dHF2hJtWPcpKR
This message will be signed and given an sULID."
};
assert_eq!(expected, text);
}
fn b58(value: &[u8]) -> impl Display {
bs58::encode(value).into_string()
}
fn flip_bits(original: SignedPayload) -> crossbeam::channel::Receiver<Option<SignedPayload>> {
let (tx, rx) = crossbeam::channel::bounded(1);
thread::spawn(move || {
for bit in original.sulid.bit_range() {
let mut modified = original.clone();
modified.sulid.flip_bit(bit);
tx.send(Some(modified)).expect("tx.send()");
}
for bit in original.signature.bit_range() {
let mut modified = original.clone();
modified.signature.flip_bit(bit);
tx.send(Some(modified)).expect("tx.send()");
}
for bit in original.app_metadata.bit_range() {
let mut modified = original.clone();
modified.app_metadata.flip_bit(bit);
tx.send(Some(modified)).expect("tx.send()");
}
for bit in original.message_hash.bit_range() {
let mut modified = original.clone();
modified.message_hash.flip_bit(bit);
tx.send(Some(modified)).expect("tx.send()");
}
for bit in original.message_length.bit_range() {
let mut modified = original.clone();
modified.message_length.flip_bit(bit);
tx.send(Some(modified)).expect("tx.send()");
}
tx.send(None).expect("tx.send()");
});
return rx;
}
pub fn blake3hash(bytes: &[u8]) -> blake3::Hash {
let mut hasher = blake3::Hasher::new();
hasher.update(bytes);
hasher.finalize()
}
trait FlipBit {
fn bit_range(&self) -> Range<usize>;
fn flip_bit(&mut self, offset: usize);
}
impl FlipBit for u8 {
fn bit_range(&self) -> Range<usize> {
0..8
}
fn flip_bit(&mut self, offset: usize) {
let mask = match offset {
0 => 0b10000000,
1 => 0b01000000,
2 => 0b00100000,
3 => 0b00010000,
4 => 0b00001000,
5 => 0b00000100,
6 => 0b00000010,
7 => 0b00000001,
_ => panic!("Offset out of bound for u8: {offset}"),
};
*self ^= mask;
}
}
impl FlipBit for [u8] {
fn bit_range(&self) -> Range<usize> {
0..(self.len() * 8)
}
fn flip_bit(&mut self, offset: usize) {
let byte_offset = offset / 8;
let bit_offset = offset % 8;
self[byte_offset].flip_bit(bit_offset);
}
}
impl FlipBit for Sulid {
fn bit_range(&self) -> Range<usize> {
0..128
}
fn flip_bit(&mut self, offset: usize) {
let mut bytes = self.to_bytes();
bytes.flip_bit(offset);
*self = bytes.into();
}
}
impl FlipBit for VerifyingKey {
fn bit_range(&self) -> Range<usize> {
0..(PUBLIC_KEY_LENGTH * 8)
}
fn flip_bit(&mut self, offset: usize) {
let mut bytes = self.to_bytes();
bytes.flip_bit(offset);
*self = VerifyingKey::from_bytes(&bytes).expect("valid public key?");
eprintln!("flipping bit on key {offset}");
}
}
impl FlipBit for Signature {
fn bit_range(&self) -> Range<usize> {
0..(SIGNATURE_LENGTH * 8)
}
fn flip_bit(&mut self, offset: usize) {
let mut bytes = self.to_bytes();
bytes.flip_bit(offset);
*self = Signature::from_bytes(&bytes);
}
}
impl FlipBit for blake3::Hash {
fn bit_range(&self) -> Range<usize> {
0..(blake3::OUT_LEN * 8)
}
fn flip_bit(&mut self, offset: usize) {
let mut bytes = self.as_bytes().to_owned();
bytes.flip_bit(offset);
*self = bytes.into();
}
}
impl FlipBit for u64 {
fn bit_range(&self) -> Range<usize> {
0..64
}
fn flip_bit(&mut self, offset: usize) {
let mut bytes = self.to_be_bytes();
bytes.flip_bit(offset);
*self = u64::from_be_bytes(bytes);
}
}