post ID mnemonics

new-idea
Avril 4 years ago
parent e43dee3322
commit 31ecef8954
Signed by: flanchan
GPG Key ID: 284488987C31F630

1
Cargo.lock generated

@ -1971,6 +1971,7 @@ dependencies = [
"futures",
"generational-arena",
"getrandom 0.2.1",
"hex-literal 0.3.1",
"khash",
"lazy_static",
"log",

@ -6,8 +6,9 @@ authors = ["Avril <flanchan@cumallover.me>"]
edition = "2018"
[features]
default = ["nightly"]
default = ["nightly", "short-mnemonics"]
short-mnemonics = []
nightly = ["smallvec/const_generics"]
[dependencies]
@ -21,6 +22,7 @@ difference = "2.0.0"
futures = "0.3.8"
generational-arena = "0.2.8"
getrandom = "0.2.1"
hex-literal = "0.3.1"
khash = "2.0.0"
lazy_static = "1.4.0"
log = "0.4.11"

@ -1,3 +1,5 @@
use crate::mnemonic::MnemonicSaltKind;
/// Default anonymous name
pub const ANON_NAME: &'static str = "名無し";
/// Max length of `name` and `email` feilds in posts.
@ -32,3 +34,9 @@ pub const RECOGNISABLE_PEM_ENCODES: &'static [&'static str] = &[
"PUBLIC KEY",
"RSA PUBLIC KEY",
];
/// Size of the menmonic salt
pub const MNEMONIC_SALT_SIZE: usize = 16;
/// Mnemonic salt to use
pub const MNEMONIC_SALT: MnemonicSaltKind = MnemonicSaltKind::Random;

@ -367,6 +367,18 @@ mod tests
{
Self(from)
}
/// As bytes
#[inline] fn id_as_bytes(&self) -> &[u8]
{
self.0.as_bytes()
}
/// The inner UUID
#[inline] fn id_inner(&self) -> &::uuid::Uuid
{
&self.0
}
}
};

@ -2,6 +2,7 @@
#![cfg_attr(feature="nightly", feature(never_type))]
#![allow(dead_code)]
#![allow(unused_macros)]
#[cfg(all(feature="nightly", test))] extern crate test;
#[macro_use] extern crate serde;
@ -11,6 +12,7 @@
#[macro_use] extern crate cfg_if;
#[macro_use] extern crate ad_hoc_iter;
#[macro_use] extern crate maud;
#[macro_use] extern crate hex_literal;
#[allow(unused_imports)]
use std::convert::{TryFrom, TryInto};
@ -29,7 +31,7 @@ use color_eyre::{
mod bytes;
mod delta; //unused now, but tests still use it, so...
mod hard_format;
mod tripcode;
mod mnemonic;
mod defaults;
mod template;

@ -0,0 +1,261 @@
//! Mnemonic hashes, incl. tripcode.
use super::*;
use std::borrow::Borrow;
use khash::ctx::Context;
use cryptohelpers::sha256::{self, Sha256Hash};
use std::hash::{Hash, Hasher};
use std::cmp::Ordering;
use std::str;
use std::fmt;
/// A newtype tripcode string
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Tripcode(String);
lazy_static!{
static ref CONTEXT: Context = Context::new(defaults::TRIPCODE_ALGO, defaults::TRIPCODE_SALT);
}
impl Tripcode
{
/// Generate a tripcode from this string.
pub fn generate(from: impl AsRef<[u8]>) -> Result<Self, khash::error::Error>
{
khash::generate(&CONTEXT, from).map(Self)
}
/// Create a tripcode that *is* this string
#[inline] pub fn special(string: String) -> Self
{
Self(string)
}
/// As a string
#[inline] pub fn as_str(&self) -> &str
{
&self.0
}
/// Consume into regular string
#[inline] pub fn into_inner(self) -> String
{
self.0
}
}
impl From<Tripcode> for String
{
#[inline] fn from(from: Tripcode) -> Self
{
from.0
}
}
impl fmt::Display for Tripcode
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "{}", self.0)
}
}
impl AsRef<str> for Tripcode
{
fn as_ref(&self) -> &str
{
&self.0[..]
}
}
impl Borrow<String> for Tripcode
{
fn borrow(&self) -> &String
{
&self.0
}
}
use once_cell::sync::OnceCell;
/// A mnemonic base64 hash
#[derive(Debug, Clone)]
pub struct MnemonicHash(Sha256Hash, OnceCell<String>);
impl Ord for MnemonicHash
{
fn cmp(&self, other: &Self) -> Ordering
{
self.0.cmp(&other.0)
}
}
impl PartialOrd for MnemonicHash
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.0.partial_cmp(&other.0)
}
}
impl Eq for MnemonicHash{}
impl PartialEq for MnemonicHash
{
fn eq(&self, other: &Self) -> bool
{
self.0 == other.0
}
}
impl Hash for MnemonicHash {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state)
}
}
impl str::FromStr for MnemonicHash
{
type Err = std::convert::Infallible;
#[inline] fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::from_str(s))
}
}
impl MnemonicHash
{
/// Create mnemonic hash from this string slice
#[inline] pub fn from_str(string: impl AsRef<str>) -> Self
{
Self::from_slice(string.as_ref().as_bytes())
}
/// Create mnemonic hash from this slice
pub fn from_slice(data: impl AsRef<[u8]>) -> Self
{
Self(sha256::compute_slice_iter(iter![data.as_ref(), defaults::MNEMONIC_SALT.as_ref()]), OnceCell::new())
}
/// The inner hash
#[inline] pub fn as_hash(&self) -> &Sha256Hash
{
&self.0
}
/// Create a mnemonic from this hash
///
/// # Notes
/// This does not salt the hash, the hasher is responsible for salting the hash with `defaults::MNEMONIC_SALT`.
#[inline] pub fn from_hash(hash: Sha256Hash) -> Self
{
Self(hash, OnceCell::new())
}
fn render(&self) -> String
{
#[allow(unused_mut)] let mut end;
cfg_if! {
if #[cfg(feature="short-mnemonics")] {
let last = &self.0.as_ref()[..(sha256::SIZE/2)];
let first = &self.0.as_ref()[(sha256::SIZE/2)..];
end = [0u8; sha256::SIZE/2];
for (e, (f, l)) in end.iter_mut().zip(first.iter().copied().zip(last.iter().copied()))
{
*e = f ^ l;
}
} else {
end = &self.0.as_ref()[..];
}
}
base64::encode(end).chars().filter_map(|x| {
Some(match x {
'/' => 'ł',
'+' => 'þ',
'=' => return None,
x => x
})
}).collect()
}
#[inline] fn render_cache(&self) -> &String
{
self.1.get_or_init(|| {
self.render()
})
}
}
impl From<MnemonicHash> for Sha256Hash
{
fn from(from: MnemonicHash) -> Self
{
from.0
}
}
impl AsRef<Sha256Hash> for MnemonicHash
{
fn as_ref(&self) -> &Sha256Hash
{
&self.0
}
}
impl Borrow<Sha256Hash> for MnemonicHash
{
fn borrow(&self) -> &Sha256Hash {
&self.0
}
}
impl fmt::Display for MnemonicHash
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "{}", self.render_cache())
}
}
/// What kind of salt to use for hashing `MnemonicHash`.
///
/// This is intended to be global state.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum MnemonicSaltKind
{
None,
Specific([u8; defaults::MNEMONIC_SALT_SIZE]),
Random,
}
impl MnemonicSaltKind
{
/// Get as a slice.
pub fn as_slice(&self) -> Option<&[u8]>
{
lazy_static! {
static ref RANDOM_SALT: [u8; defaults::MNEMONIC_SALT_SIZE] = {
let mut output = [0u8; defaults::MNEMONIC_SALT_SIZE];
getrandom::getrandom(&mut output[..]).expect("rng fatal");
output
};
}
Some(match self {
Self::None => return None,
Self::Random => &RANDOM_SALT[..],
Self::Specific(u) => &u[..],
})
}
}
impl AsRef<[u8]> for MnemonicSaltKind
{
#[inline] fn as_ref(&self) -> &[u8]
{
self.as_slice().unwrap_or(&[])
}
}
#[cfg(test)]
mod tests
{
#[test]
fn mnemonics()
{
let _mnemonic = super::MnemonicHash::from_slice(b"hello world");
}
}

@ -10,7 +10,7 @@ use hard_format::formats::{
Base64FormattedStr,
self,
};
use tripcode::Tripcode;
use mnemonic::Tripcode;
id_type!(PostID; "A unique post ID");
@ -158,6 +158,11 @@ impl Post
// Ident based functions
impl Post
{
/// Get a mnemonic for this post's ID.
#[inline] pub fn post_id_mnemonic(&self) -> mnemonic::MnemonicHash
{
mnemonic::MnemonicHash::from_slice(self.id.id_as_bytes())
}
/// This post's unique identifier
#[inline] pub fn post_id(&self) -> &PostID
{

@ -27,7 +27,7 @@ impl Render for Post
{
fn render(&self) -> Markup
{
let id = self.id.0;
let id = self.post_id_mnemonic();
html! {
article#(id) {
header {
@ -38,7 +38,7 @@ impl Render for Post
}
nav {
a href=(format!("#{}", id)) { "No." }
a href=(format!("#q{}", id)) { (id) }
a href=(format!("#q_{}", id)) { (id) }
}
}
blockquote {
@ -56,7 +56,7 @@ mod tests
#[test]
fn ident_htmlrender()
{
let ident = super::Ident::new(Some("Name"), Some(crate::tripcode::Tripcode::generate("mwee").unwrap()), Some("user@example.com"));
let ident = super::Ident::new(Some("Name"), Some(crate::mnemonic::Tripcode::generate("mwee").unwrap()), Some("user@example.com"));
use maud::Render;
assert_eq!(r#"<b class="ident"><a href="mailto:user@example.com">Name</a> <code>!えナセッゲよラで</code></b>"#, &ident.render().into_string());

@ -1,70 +0,0 @@
use super::*;
use khash::ctx::Context;
//use khash::salt::Salt;
use std::fmt;
/// A newtype tripcode string
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Tripcode(String);
lazy_static!{
static ref CONTEXT: Context = Context::new(defaults::TRIPCODE_ALGO, defaults::TRIPCODE_SALT);
}
impl Tripcode
{
/// Generate a tripcode from this string.
pub fn generate(from: impl AsRef<[u8]>) -> Result<Self, khash::error::Error>
{
khash::generate(&CONTEXT, from).map(Self)
}
/// Create a tripcode that *is* this string
#[inline] pub fn special(string: String) -> Self
{
Self(string)
}
/// As a string
#[inline] pub fn as_str(&self) -> &str
{
&self.0
}
/// Consume into regular string
#[inline] pub fn into_inner(self) -> String
{
self.0
}
}
impl From<Tripcode> for String
{
#[inline] fn from(from: Tripcode) -> Self
{
from.0
}
}
impl fmt::Display for Tripcode
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "{}", self.0)
}
}
impl AsRef<str> for Tripcode
{
fn as_ref(&self) -> &str
{
&self.0[..]
}
}
impl std::borrow::Borrow<String> for Tripcode
{
fn borrow(&self) -> &String
{
&self.0
}
}
Loading…
Cancel
Save