1#[cfg(feature = "random")]
5pub use rand_core;
6
7#[cfg(feature = "ed25519")]
8pub mod ed25519;
9
10#[derive(Debug, Eq, PartialEq, Copy, Clone)]
49pub struct Context<'a>(&'a [u8]);
50
51macro_rules! ctx_len {
53 ("max") => {
54 255
55 };
56 ("min") => {
57 4
58 };
59}
60
61impl<'a> Context<'a> {
62 pub const MAX_LEN: usize = Self::max_len();
63 pub const MIN_LEN: usize = Self::min_len();
64
65 pub const fn from_bytes(value: &'a [u8]) -> Self {
67 match Self::try_from_bytes(value) {
68 Ok(ctx) => ctx,
69 Err(err) => panic!("{}", err.const_display()),
70 }
71 }
72
73 pub const fn try_from_bytes(value: &'a [u8]) -> Result<Self, ContextError> {
74 let len = value.len();
75 if len < Self::MIN_LEN {
76 Err(ContextError::SliceTooShort)
77 } else if len > Self::MAX_LEN {
78 Err(ContextError::SliceTooLong(len))
79 } else {
80 Ok(Context(value))
81 }
82 }
83
84 const fn max_len() -> usize {
85 let result = ed25519_dalek::Context::<ed25519_dalek::VerifyingKey>::MAX_LENGTH;
86 assert!(result == ctx_len!("max"));
87 result
88 }
89
90 const fn min_len() -> usize {
91 let result = ctx_len!("min");
92 assert!(result <= Self::MAX_LEN);
93 result
94 }
95}
96
97impl<'a> TryFrom<&'a [u8]> for Context<'a> {
98 type Error = ContextError;
99
100 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
101 Self::try_from_bytes(value)
102 }
103}
104
105#[derive(thiserror::Error, Debug, Eq, PartialEq)]
106pub enum ContextError {
107 #[error(
108 "requires a slice of at least length {} but got a slice of length {0}",
109 Context::MIN_LEN
110 )]
111 SliceTooShort,
112 #[error(
113 "requires a slice of at most length {} but got a slice of length {0}",
114 Context::MAX_LEN
115 )]
116 SliceTooLong(usize),
117}
118
119impl ContextError {
120 const fn const_display(&self) -> &str {
121 match self {
122 Self::SliceTooShort => {
123 concat!("requires a slice of at least length ", ctx_len!("min"))
124 }
125 Self::SliceTooLong(_len) => {
126 concat!("requires a slice of at most length ", ctx_len!("max"))
127 }
128 }
129 }
130}
131
132#[cfg(test)]
133mod test {
134 use super::*;
135
136 #[test]
137 fn test_ctx_try_from() {
138 let valid = [
139 [0; Context::MIN_LEN].as_slice(),
140 [0; Context::MAX_LEN].as_slice(),
141 ];
142 for s in valid {
143 assert_eq!(Context::from_bytes(s), Context::try_from_bytes(s).unwrap());
144 }
145
146 let too_short = [&[], [0; Context::MIN_LEN - 1].as_slice()];
147 for s in too_short {
148 assert_eq!(Context::try_from_bytes(s), Err(ContextError::SliceTooShort));
149 assert!(std::panic::catch_unwind(|| Context::from_bytes(s)).is_err());
150 }
151
152 let too_long = [
153 [0; Context::MAX_LEN + 1].as_slice(),
154 [0; Context::MAX_LEN + 2].as_slice(),
155 ];
156 for s in too_long {
157 assert_eq!(
158 Context::try_from_bytes(s),
159 Err(ContextError::SliceTooLong(s.len()))
160 );
161 assert!(std::panic::catch_unwind(|| Context::from_bytes(s)).is_err());
162 }
163 }
164}