use super::xizage::{Xizage, XizageSize};
#[cfg(feature = "alloc")]
#[allow(
unused_imports,
reason = "alloc prelude items; subset used per cfg/feature combination"
)]
use alloc::{borrow::ToOwned, string::String};
use thiserror::Error as ThisError;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum IndexMode {
Both,
CurrentOnly,
}
#[derive(Debug, ThisError, PartialEq, Eq)]
pub enum CodeError {
#[error("unknown indexed sig code: '{0}'")]
UnknownCode(String),
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum IndexedSigCode {
Ed25519,
Ed25519Crt,
ECDSA256k1,
ECDSA256k1Crt,
ECDSA256r1,
ECDSA256r1Crt,
Ed448,
Ed448Crt,
Ed25519Big,
Ed25519BigCrt,
ECDSA256k1Big,
ECDSA256k1BigCrt,
ECDSA256r1Big,
ECDSA256r1BigCrt,
Ed448Big,
Ed448BigCrt,
}
impl IndexedSigCode {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Ed25519 => "A",
Self::Ed25519Crt => "B",
Self::ECDSA256k1 => "C",
Self::ECDSA256k1Crt => "D",
Self::ECDSA256r1 => "E",
Self::ECDSA256r1Crt => "F",
Self::Ed448 => "0A",
Self::Ed448Crt => "0B",
Self::Ed25519Big => "2A",
Self::Ed25519BigCrt => "2B",
Self::ECDSA256k1Big => "2C",
Self::ECDSA256k1BigCrt => "2D",
Self::ECDSA256r1Big => "2E",
Self::ECDSA256r1BigCrt => "2F",
Self::Ed448Big => "3A",
Self::Ed448BigCrt => "3B",
}
}
pub fn from_hard(hard: &str) -> Result<Self, CodeError> {
match hard {
"A" => Ok(Self::Ed25519),
"B" => Ok(Self::Ed25519Crt),
"C" => Ok(Self::ECDSA256k1),
"D" => Ok(Self::ECDSA256k1Crt),
"E" => Ok(Self::ECDSA256r1),
"F" => Ok(Self::ECDSA256r1Crt),
"0A" => Ok(Self::Ed448),
"0B" => Ok(Self::Ed448Crt),
"2A" => Ok(Self::Ed25519Big),
"2B" => Ok(Self::Ed25519BigCrt),
"2C" => Ok(Self::ECDSA256k1Big),
"2D" => Ok(Self::ECDSA256k1BigCrt),
"2E" => Ok(Self::ECDSA256r1Big),
"2F" => Ok(Self::ECDSA256r1BigCrt),
"3A" => Ok(Self::Ed448Big),
"3B" => Ok(Self::Ed448BigCrt),
_ => Err(CodeError::UnknownCode(hard.to_owned())),
}
}
#[must_use]
pub const fn get_xizage(&self) -> Xizage {
match self {
Self::Ed25519
| Self::Ed25519Crt
| Self::ECDSA256k1
| Self::ECDSA256k1Crt
| Self::ECDSA256r1
| Self::ECDSA256r1Crt => Xizage::new(1, 1, 0, XizageSize::Fixed(88), 0),
Self::Ed448 | Self::Ed448Crt => Xizage::new(2, 2, 1, XizageSize::Fixed(156), 0),
Self::Ed25519Big
| Self::Ed25519BigCrt
| Self::ECDSA256k1Big
| Self::ECDSA256k1BigCrt
| Self::ECDSA256r1Big
| Self::ECDSA256r1BigCrt => Xizage::new(2, 4, 2, XizageSize::Fixed(92), 0),
Self::Ed448Big | Self::Ed448BigCrt => Xizage::new(2, 6, 3, XizageSize::Fixed(160), 0),
}
}
#[must_use]
pub const fn mode(&self) -> IndexMode {
match self {
Self::Ed25519
| Self::ECDSA256k1
| Self::ECDSA256r1
| Self::Ed448
| Self::Ed25519Big
| Self::ECDSA256k1Big
| Self::ECDSA256r1Big
| Self::Ed448Big => IndexMode::Both,
Self::Ed25519Crt
| Self::ECDSA256k1Crt
| Self::ECDSA256r1Crt
| Self::Ed448Crt
| Self::Ed25519BigCrt
| Self::ECDSA256k1BigCrt
| Self::ECDSA256r1BigCrt
| Self::Ed448BigCrt => IndexMode::CurrentOnly,
}
}
#[must_use]
pub const fn is_big(&self) -> bool {
match self {
Self::Ed25519
| Self::Ed25519Crt
| Self::ECDSA256k1
| Self::ECDSA256k1Crt
| Self::ECDSA256r1
| Self::ECDSA256r1Crt
| Self::Ed448
| Self::Ed448Crt => false,
Self::Ed25519Big
| Self::Ed25519BigCrt
| Self::ECDSA256k1Big
| Self::ECDSA256k1BigCrt
| Self::ECDSA256r1Big
| Self::ECDSA256r1BigCrt
| Self::Ed448Big
| Self::Ed448BigCrt => true,
}
}
#[must_use]
pub const fn raw_size(&self) -> usize {
match self {
Self::Ed25519
| Self::Ed25519Crt
| Self::Ed25519Big
| Self::Ed25519BigCrt
| Self::ECDSA256k1
| Self::ECDSA256k1Crt
| Self::ECDSA256k1Big
| Self::ECDSA256k1BigCrt
| Self::ECDSA256r1
| Self::ECDSA256r1Crt
| Self::ECDSA256r1Big
| Self::ECDSA256r1BigCrt => 64,
Self::Ed448 | Self::Ed448Crt | Self::Ed448Big | Self::Ed448BigCrt => 114,
}
}
#[must_use]
#[allow(clippy::as_conversions, reason = "From::from() is not const-stable")]
pub const fn max_index(&self) -> u32 {
let xizage = self.get_xizage();
let exp = (xizage.ss - xizage.os) as u32;
64u32.pow(exp) - 1
}
#[must_use]
#[allow(clippy::as_conversions, reason = "From::from() is not const-stable")]
pub const fn max_ondex(&self) -> Option<u32> {
match self.mode() {
IndexMode::CurrentOnly => None,
IndexMode::Both => {
let os = self.get_xizage().os as u32;
if os == 0 {
Some(self.max_index())
} else {
Some(64u32.pow(os) - 1)
}
}
}
}
#[must_use]
pub const fn for_index(self, index: u32) -> Self {
if self.is_big() || index <= self.max_index() {
return self;
}
match self {
Self::Ed25519 => Self::Ed25519Big,
Self::Ed25519Crt => Self::Ed25519BigCrt,
Self::ECDSA256k1 => Self::ECDSA256k1Big,
Self::ECDSA256k1Crt => Self::ECDSA256k1BigCrt,
Self::ECDSA256r1 => Self::ECDSA256r1Big,
Self::ECDSA256r1Crt => Self::ECDSA256r1BigCrt,
Self::Ed448 => Self::Ed448Big,
Self::Ed448Crt => Self::Ed448BigCrt,
_ => self,
}
}
}
#[must_use]
pub const fn hardage(c: char) -> Option<usize> {
match c {
'A'..='Z' | 'a'..='z' => Some(1),
'0'..='4' => Some(2),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
#[case(IndexedSigCode::Ed25519, "A")]
#[case(IndexedSigCode::Ed25519Crt, "B")]
#[case(IndexedSigCode::ECDSA256k1, "C")]
#[case(IndexedSigCode::ECDSA256k1Crt, "D")]
#[case(IndexedSigCode::ECDSA256r1, "E")]
#[case(IndexedSigCode::ECDSA256r1Crt, "F")]
#[case(IndexedSigCode::Ed448, "0A")]
#[case(IndexedSigCode::Ed448Crt, "0B")]
#[case(IndexedSigCode::Ed25519Big, "2A")]
#[case(IndexedSigCode::Ed25519BigCrt, "2B")]
#[case(IndexedSigCode::ECDSA256k1Big, "2C")]
#[case(IndexedSigCode::ECDSA256k1BigCrt, "2D")]
#[case(IndexedSigCode::ECDSA256r1Big, "2E")]
#[case(IndexedSigCode::ECDSA256r1BigCrt, "2F")]
#[case(IndexedSigCode::Ed448Big, "3A")]
#[case(IndexedSigCode::Ed448BigCrt, "3B")]
fn as_str_values(#[case] code: IndexedSigCode, #[case] expected: &str) {
assert_eq!(code.as_str(), expected);
}
#[rstest]
#[case(IndexedSigCode::Ed25519, "A")]
#[case(IndexedSigCode::Ed25519Crt, "B")]
#[case(IndexedSigCode::ECDSA256k1, "C")]
#[case(IndexedSigCode::ECDSA256k1Crt, "D")]
#[case(IndexedSigCode::ECDSA256r1, "E")]
#[case(IndexedSigCode::ECDSA256r1Crt, "F")]
#[case(IndexedSigCode::Ed448, "0A")]
#[case(IndexedSigCode::Ed448Crt, "0B")]
#[case(IndexedSigCode::Ed25519Big, "2A")]
#[case(IndexedSigCode::Ed25519BigCrt, "2B")]
#[case(IndexedSigCode::ECDSA256k1Big, "2C")]
#[case(IndexedSigCode::ECDSA256k1BigCrt, "2D")]
#[case(IndexedSigCode::ECDSA256r1Big, "2E")]
#[case(IndexedSigCode::ECDSA256r1BigCrt, "2F")]
#[case(IndexedSigCode::Ed448Big, "3A")]
#[case(IndexedSigCode::Ed448BigCrt, "3B")]
fn from_hard_roundtrip(#[case] code: IndexedSigCode, #[case] wire: &str) {
assert_eq!(IndexedSigCode::from_hard(wire).unwrap(), code);
assert_eq!(IndexedSigCode::from_hard(code.as_str()).unwrap(), code);
}
#[rstest]
#[case("Z")]
#[case("0Z")]
#[case("XX")]
#[case("")]
fn from_hard_unknown(#[case] bad: &str) {
assert!(IndexedSigCode::from_hard(bad).is_err());
let err = IndexedSigCode::from_hard(bad).unwrap_err();
assert_eq!(err, CodeError::UnknownCode(bad.to_owned()));
}
#[rstest]
#[case(
IndexedSigCode::Ed25519,
Xizage::new(1, 1, 0, XizageSize::Fixed(88), 0)
)]
#[case(
IndexedSigCode::Ed25519Crt,
Xizage::new(1, 1, 0, XizageSize::Fixed(88), 0)
)]
#[case(
IndexedSigCode::ECDSA256k1,
Xizage::new(1, 1, 0, XizageSize::Fixed(88), 0)
)]
#[case(
IndexedSigCode::ECDSA256k1Crt,
Xizage::new(1, 1, 0, XizageSize::Fixed(88), 0)
)]
#[case(
IndexedSigCode::ECDSA256r1,
Xizage::new(1, 1, 0, XizageSize::Fixed(88), 0)
)]
#[case(
IndexedSigCode::ECDSA256r1Crt,
Xizage::new(1, 1, 0, XizageSize::Fixed(88), 0)
)]
#[case(IndexedSigCode::Ed448, Xizage::new(2, 2, 1, XizageSize::Fixed(156), 0))]
#[case(
IndexedSigCode::Ed448Crt,
Xizage::new(2, 2, 1, XizageSize::Fixed(156), 0)
)]
#[case(
IndexedSigCode::Ed25519Big,
Xizage::new(2, 4, 2, XizageSize::Fixed(92), 0)
)]
#[case(
IndexedSigCode::Ed25519BigCrt,
Xizage::new(2, 4, 2, XizageSize::Fixed(92), 0)
)]
#[case(
IndexedSigCode::ECDSA256k1Big,
Xizage::new(2, 4, 2, XizageSize::Fixed(92), 0)
)]
#[case(
IndexedSigCode::ECDSA256k1BigCrt,
Xizage::new(2, 4, 2, XizageSize::Fixed(92), 0)
)]
#[case(
IndexedSigCode::ECDSA256r1Big,
Xizage::new(2, 4, 2, XizageSize::Fixed(92), 0)
)]
#[case(
IndexedSigCode::ECDSA256r1BigCrt,
Xizage::new(2, 4, 2, XizageSize::Fixed(92), 0)
)]
#[case(
IndexedSigCode::Ed448Big,
Xizage::new(2, 6, 3, XizageSize::Fixed(160), 0)
)]
#[case(
IndexedSigCode::Ed448BigCrt,
Xizage::new(2, 6, 3, XizageSize::Fixed(160), 0)
)]
fn xizage_values(#[case] code: IndexedSigCode, #[case] expected: Xizage) {
assert_eq!(code.get_xizage(), expected);
}
#[rstest]
#[case(IndexedSigCode::Ed25519, IndexMode::Both)]
#[case(IndexedSigCode::Ed25519Crt, IndexMode::CurrentOnly)]
#[case(IndexedSigCode::ECDSA256k1, IndexMode::Both)]
#[case(IndexedSigCode::ECDSA256k1Crt, IndexMode::CurrentOnly)]
#[case(IndexedSigCode::ECDSA256r1, IndexMode::Both)]
#[case(IndexedSigCode::ECDSA256r1Crt, IndexMode::CurrentOnly)]
#[case(IndexedSigCode::Ed448, IndexMode::Both)]
#[case(IndexedSigCode::Ed448Crt, IndexMode::CurrentOnly)]
#[case(IndexedSigCode::Ed25519Big, IndexMode::Both)]
#[case(IndexedSigCode::Ed25519BigCrt, IndexMode::CurrentOnly)]
#[case(IndexedSigCode::ECDSA256k1Big, IndexMode::Both)]
#[case(IndexedSigCode::ECDSA256k1BigCrt, IndexMode::CurrentOnly)]
#[case(IndexedSigCode::ECDSA256r1Big, IndexMode::Both)]
#[case(IndexedSigCode::ECDSA256r1BigCrt, IndexMode::CurrentOnly)]
#[case(IndexedSigCode::Ed448Big, IndexMode::Both)]
#[case(IndexedSigCode::Ed448BigCrt, IndexMode::CurrentOnly)]
fn mode_values(#[case] code: IndexedSigCode, #[case] expected: IndexMode) {
assert_eq!(code.mode(), expected);
}
#[rstest]
#[case(IndexedSigCode::Ed25519, false)]
#[case(IndexedSigCode::Ed25519Crt, false)]
#[case(IndexedSigCode::ECDSA256k1, false)]
#[case(IndexedSigCode::ECDSA256k1Crt, false)]
#[case(IndexedSigCode::ECDSA256r1, false)]
#[case(IndexedSigCode::ECDSA256r1Crt, false)]
#[case(IndexedSigCode::Ed448, false)]
#[case(IndexedSigCode::Ed448Crt, false)]
#[case(IndexedSigCode::Ed25519Big, true)]
#[case(IndexedSigCode::Ed25519BigCrt, true)]
#[case(IndexedSigCode::ECDSA256k1Big, true)]
#[case(IndexedSigCode::ECDSA256k1BigCrt, true)]
#[case(IndexedSigCode::ECDSA256r1Big, true)]
#[case(IndexedSigCode::ECDSA256r1BigCrt, true)]
#[case(IndexedSigCode::Ed448Big, true)]
#[case(IndexedSigCode::Ed448BigCrt, true)]
fn is_big_values(#[case] code: IndexedSigCode, #[case] expected: bool) {
assert_eq!(code.is_big(), expected);
}
#[rstest]
#[case(IndexedSigCode::Ed25519, 64)]
#[case(IndexedSigCode::Ed25519Crt, 64)]
#[case(IndexedSigCode::ECDSA256k1, 64)]
#[case(IndexedSigCode::ECDSA256k1Crt, 64)]
#[case(IndexedSigCode::ECDSA256r1, 64)]
#[case(IndexedSigCode::ECDSA256r1Crt, 64)]
#[case(IndexedSigCode::Ed448, 114)]
#[case(IndexedSigCode::Ed448Crt, 114)]
#[case(IndexedSigCode::Ed25519Big, 64)]
#[case(IndexedSigCode::Ed25519BigCrt, 64)]
#[case(IndexedSigCode::ECDSA256k1Big, 64)]
#[case(IndexedSigCode::ECDSA256k1BigCrt, 64)]
#[case(IndexedSigCode::ECDSA256r1Big, 64)]
#[case(IndexedSigCode::ECDSA256r1BigCrt, 64)]
#[case(IndexedSigCode::Ed448Big, 114)]
#[case(IndexedSigCode::Ed448BigCrt, 114)]
fn raw_size_values(#[case] code: IndexedSigCode, #[case] expected: usize) {
assert_eq!(code.raw_size(), expected);
}
#[rstest]
#[case(IndexedSigCode::Ed25519, 63)]
#[case(IndexedSigCode::Ed25519Crt, 63)]
#[case(IndexedSigCode::ECDSA256k1, 63)]
#[case(IndexedSigCode::ECDSA256k1Crt, 63)]
#[case(IndexedSigCode::ECDSA256r1, 63)]
#[case(IndexedSigCode::ECDSA256r1Crt, 63)]
#[case(IndexedSigCode::Ed448, 63)]
#[case(IndexedSigCode::Ed448Crt, 63)]
#[case(IndexedSigCode::Ed25519Big, 4095)]
#[case(IndexedSigCode::Ed25519BigCrt, 4095)]
#[case(IndexedSigCode::ECDSA256k1Big, 4095)]
#[case(IndexedSigCode::ECDSA256k1BigCrt, 4095)]
#[case(IndexedSigCode::ECDSA256r1Big, 4095)]
#[case(IndexedSigCode::ECDSA256r1BigCrt, 4095)]
#[case(IndexedSigCode::Ed448Big, 262_143)]
#[case(IndexedSigCode::Ed448BigCrt, 262_143)]
fn max_index_values(#[case] code: IndexedSigCode, #[case] expected: u32) {
assert_eq!(code.max_index(), expected);
}
#[rstest]
#[case(IndexedSigCode::Ed25519, Some(63))]
#[case(IndexedSigCode::ECDSA256k1, Some(63))]
#[case(IndexedSigCode::ECDSA256r1, Some(63))]
#[case(IndexedSigCode::Ed25519Crt, None)]
#[case(IndexedSigCode::ECDSA256k1Crt, None)]
#[case(IndexedSigCode::ECDSA256r1Crt, None)]
#[case(IndexedSigCode::Ed448, Some(63))]
#[case(IndexedSigCode::Ed448Crt, None)]
#[case(IndexedSigCode::Ed25519Big, Some(4095))]
#[case(IndexedSigCode::Ed25519BigCrt, None)]
#[case(IndexedSigCode::ECDSA256k1Big, Some(4095))]
#[case(IndexedSigCode::ECDSA256k1BigCrt, None)]
#[case(IndexedSigCode::ECDSA256r1Big, Some(4095))]
#[case(IndexedSigCode::ECDSA256r1BigCrt, None)]
#[case(IndexedSigCode::Ed448Big, Some(262_143))]
#[case(IndexedSigCode::Ed448BigCrt, None)]
fn max_ondex_values(#[case] code: IndexedSigCode, #[case] expected: Option<u32>) {
assert_eq!(code.max_ondex(), expected);
}
#[rstest]
#[case(IndexedSigCode::Ed25519, 0, IndexedSigCode::Ed25519)]
#[case(IndexedSigCode::Ed25519, 63, IndexedSigCode::Ed25519)]
#[case(IndexedSigCode::Ed25519Crt, 63, IndexedSigCode::Ed25519Crt)]
#[case(IndexedSigCode::ECDSA256k1, 0, IndexedSigCode::ECDSA256k1)]
#[case(IndexedSigCode::ECDSA256r1, 63, IndexedSigCode::ECDSA256r1)]
#[case(IndexedSigCode::Ed448, 63, IndexedSigCode::Ed448)]
#[case(IndexedSigCode::Ed25519, 64, IndexedSigCode::Ed25519Big)]
#[case(IndexedSigCode::Ed25519Crt, 64, IndexedSigCode::Ed25519BigCrt)]
#[case(IndexedSigCode::ECDSA256k1, 100, IndexedSigCode::ECDSA256k1Big)]
#[case(IndexedSigCode::ECDSA256k1Crt, 100, IndexedSigCode::ECDSA256k1BigCrt)]
#[case(IndexedSigCode::ECDSA256r1, 200, IndexedSigCode::ECDSA256r1Big)]
#[case(IndexedSigCode::ECDSA256r1Crt, 200, IndexedSigCode::ECDSA256r1BigCrt)]
#[case(IndexedSigCode::Ed448, 64, IndexedSigCode::Ed448Big)]
#[case(IndexedSigCode::Ed448Crt, 64, IndexedSigCode::Ed448BigCrt)]
#[case(IndexedSigCode::Ed25519Big, 0, IndexedSigCode::Ed25519Big)]
#[case(IndexedSigCode::Ed25519Big, 4095, IndexedSigCode::Ed25519Big)]
#[case(IndexedSigCode::Ed448Big, 262_143, IndexedSigCode::Ed448Big)]
fn for_index_values(
#[case] code: IndexedSigCode,
#[case] index: u32,
#[case] expected: IndexedSigCode,
) {
assert_eq!(code.for_index(index), expected);
}
#[rstest]
#[case('A', Some(1))]
#[case('Z', Some(1))]
#[case('a', Some(1))]
#[case('z', Some(1))]
#[case('M', Some(1))]
#[case('0', Some(2))]
#[case('1', Some(2))]
#[case('2', Some(2))]
#[case('3', Some(2))]
#[case('4', Some(2))]
#[case('5', None)]
#[case('9', None)]
#[case('-', None)]
#[case('_', None)]
#[case(' ', None)]
fn hardage_values(#[case] c: char, #[case] expected: Option<usize>) {
assert_eq!(hardage(c), expected);
}
#[test]
fn for_index_preserves_mode() {
assert_eq!(
IndexedSigCode::Ed25519.for_index(64).mode(),
IndexMode::Both
);
assert_eq!(
IndexedSigCode::Ed25519Crt.for_index(64).mode(),
IndexMode::CurrentOnly,
);
}
#[test]
fn for_index_preserves_raw_size() {
assert_eq!(
IndexedSigCode::Ed25519.for_index(64).raw_size(),
IndexedSigCode::Ed25519.raw_size(),
);
assert_eq!(
IndexedSigCode::Ed448.for_index(64).raw_size(),
IndexedSigCode::Ed448.raw_size(),
);
}
}