use crate::ed25519;
use codec::{ConstEncodedLen, Decode, Encode, MaxEncodedLen};
use std::io::Write;
const ANONYMOUS_PEER_ID_SUFFIX: [u8; 24] = [
0x96, 0x29, 0x01, 0x7b, 0x62, 0xef, 0x4c, 0x58, 0x58, 0x0a, 0x92, 0x32, 0x15, 0x46, 0x82, 0x0d,
0xee, 0x7e, 0xa0, 0xde, 0x28, 0x44, 0x16, 0xcd,
];
#[derive(Clone, Copy, Encode, Decode, MaxEncodedLen, Hash, PartialEq, Eq)]
pub struct PeerId(pub ed25519::Public);
impl ConstEncodedLen for PeerId {}
impl PeerId {
pub fn anonymous(index: u64) -> Self {
let mut public = [0; ed25519::PUBLIC_LEN];
public[..8].copy_from_slice(&index.to_le_bytes());
public[8..].copy_from_slice(&ANONYMOUS_PEER_ID_SUFFIX);
Self(public.into())
}
pub fn is_anonymous(&self) -> bool {
self.0.as_bytes()[8..] == ANONYMOUS_PEER_ID_SUFFIX
}
pub fn anonymous_index(&self) -> Option<u64> {
if self.is_anonymous() {
Some(u64::from_le_bytes(
*self.0.as_bytes()[..8].as_array().expect("Slice has correct length"),
))
} else {
None
}
}
pub fn to_text(&self) -> Text {
self.into()
}
}
impl From<ed25519::Public> for PeerId {
fn from(pk: ed25519::Public) -> Self {
Self(pk)
}
}
impl core::fmt::Debug for PeerId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
f.pad(self.to_text().as_str())
}
}
impl core::fmt::Display for PeerId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
f.pad(self.to_text().as_str())
}
}
#[derive(Clone, Copy, Encode, Decode, MaxEncodedLen, PartialEq, Eq)]
pub struct P256PeerId {
pub y_odd: bool,
pub x: [u8; 32],
}
impl ConstEncodedLen for P256PeerId {}
impl P256PeerId {
pub const ZERO: Self = Self { y_odd: false, x: [0; 32] };
pub fn is_zero(&self) -> bool {
self == &Self::ZERO
}
pub fn to_text(&self) -> Text {
self.into()
}
}
impl From<&p256::ecdsa::VerifyingKey> for P256PeerId {
fn from(pk: &p256::ecdsa::VerifyingKey) -> Self {
let point = pk.to_encoded_point(true);
use p256::elliptic_curve::sec1::Tag;
let y_odd = match point.tag() {
Tag::CompressedEvenY => false,
Tag::CompressedOddY => true,
_ => unreachable!(),
};
let x = point.x().expect("Verifying key should never be the identity point!");
Self { y_odd, x: (*x).into() }
}
}
impl core::fmt::Debug for P256PeerId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
f.pad(self.to_text().as_str())
}
}
impl core::fmt::Display for P256PeerId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
f.pad(self.to_text().as_str())
}
}
const BITS_TO_CHAR: &[u8; 32] = b"abcdefghijklmnopqrstuvwxyz234567";
const fn gen_char_to_bits() -> [u8; 256] {
let mut char_to_bits = [255; 256];
let mut i = 0;
while i < BITS_TO_CHAR.len() {
char_to_bits[BITS_TO_CHAR[i] as usize] = i as u8;
i += 1;
}
char_to_bits
}
const CHAR_TO_BITS: [u8; 256] = gen_char_to_bits();
const TEXT_SIZE: usize = 1 + 256_usize.div_ceil(5);
pub struct Text([u8; TEXT_SIZE]);
impl Text {
fn new(prefix: u8, bytes: &[u8; 32]) -> Self {
let mut text = [0; TEXT_SIZE];
text[0] = prefix;
for (char, i) in core::iter::zip(&mut text[1..], (0..).step_by(5)) {
let low = bytes[i / 8];
let high = bytes.get((i / 8) + 1).copied().unwrap_or(0);
let window = (low as u16) | ((high as u16) << 8);
let bits = (window >> (i % 8)) & 0x1f;
*char = BITS_TO_CHAR[bits as usize];
}
Self(text)
}
pub fn as_str(&self) -> &str {
core::str::from_utf8(&self.0).expect("Only contains ASCII characters")
}
}
impl From<&PeerId> for Text {
fn from(peer_id: &PeerId) -> Self {
if let Some(index) = peer_id.anonymous_index() {
let mut text = [0; TEXT_SIZE];
let mut w = text.as_mut_slice();
write!(w, "anonymous{index:044}").expect("Buffer is exactly big enough");
assert!(w.is_empty());
Self(text)
} else {
Self::new(b'e', peer_id.0.as_bytes())
}
}
}
impl From<&P256PeerId> for Text {
fn from(peer_id: &P256PeerId) -> Self {
let prefix = if peer_id.y_odd { b'o' } else { b'v' };
Self::new(prefix, &peer_id.x)
}
}
#[derive(Debug, thiserror::Error)]
pub enum ParseErr {
#[error("Bad length {0}; peer IDs are always {TEXT_SIZE} characters long")]
BadLength(usize),
#[error("ECDSA P-256 peer IDs (beginning with 'o' or 'v') are not supported")]
P256Prefix,
#[error("Bad prefix; peer IDs always begin with 'e'")]
BadPrefix,
#[error("Bad character; peer IDs only contain characters from the set {}",
core::str::from_utf8(BITS_TO_CHAR).expect("BITS_TO_CHAR only contains ASCII characters"))]
BadChar,
#[error("Non-zero trailing bits")]
NonZeroTrailingBits,
}
impl TryFrom<&str> for PeerId {
type Error = ParseErr;
fn try_from(text: &str) -> Result<Self, Self::Error> {
if !text.is_ascii() {
return Err(ParseErr::BadChar)
}
let text = text.as_bytes();
if text.len() != TEXT_SIZE {
return Err(ParseErr::BadLength(text.len()));
}
if (text[0] == b'o') || (text[0] == b'v') {
return Err(ParseErr::P256Prefix);
}
if text[0] != b'e' {
return Err(ParseErr::BadPrefix);
}
let mut public = [0; ed25519::PUBLIC_LEN];
let mut i = 0;
let mut acc_bits: u16 = 0;
let mut num_acc_bits = 0;
for char in &text[1..] {
let bits = CHAR_TO_BITS[*char as usize];
if bits > 0x1f {
return Err(ParseErr::BadChar);
}
acc_bits |= (bits as u16) << num_acc_bits;
num_acc_bits += 5;
if num_acc_bits >= 8 {
public[i] = acc_bits as u8;
i += 1;
acc_bits >>= 8;
num_acc_bits -= 8;
}
}
assert_eq!(i, ed25519::PUBLIC_LEN);
if acc_bits != 0 {
return Err(ParseErr::NonZeroTrailingBits);
}
Ok(Self(ed25519::Public::from(public)))
}
}
impl core::str::FromStr for PeerId {
type Err = ParseErr;
fn from_str(text: &str) -> Result<Self, Self::Err> {
text.try_into()
}
}
#[cfg(any(test, feature = "rand"))]
impl rand::distr::Distribution<PeerId> for rand::distr::StandardUniform {
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> PeerId {
PeerId(rng.random())
}
}
#[cfg(any(test, feature = "rand"))]
impl rand::distr::Distribution<P256PeerId> for rand::distr::StandardUniform {
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> P256PeerId {
P256PeerId { y_odd: rng.random(), x: rng.random() }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ed25519::Secret;
use rand::Rng;
#[test]
fn text_round_trip() {
let mut rng = rand::rng();
for _ in 0..1000 {
let peer_id = PeerId(Secret::new(&mut rng).public());
assert_eq!(peer_id, peer_id.to_text().as_str().try_into().unwrap());
}
}
#[test]
fn anonymous_text() {
assert_eq!(
PeerId::anonymous(1234).to_text().as_str(),
"anonymous00000000000000000000000000000000000000001234"
);
}
#[test]
fn anonymous_round_trip() {
let mut rng = rand::rng();
for _ in 0..100 {
let index: u64 = rng.random();
assert_eq!(PeerId::anonymous(index).anonymous_index(), Some(index));
}
}
#[test]
fn non_anonymous() {
let mut rng = rand::rng();
for _ in 0..100 {
let peer_id = PeerId(Secret::new(&mut rng).public());
assert_eq!(peer_id.anonymous_index(), None);
}
}
}