crypto_vote/error.rs
1//! Error type returned by every fallible public function.
2//!
3//! Errors are intentionally coarse-grained: the host never needs to do
4//! anything with them other than reject the request. We give them
5//! descriptive `Display` strings so logs are useful, but there is no
6//! `#[non_exhaustive]` machinery, no error codes, no nested causes — the
7//! crate's job is to say "valid" or "not valid", and these variants only
8//! exist to explain *why* an input could not even be parsed.
9
10use core::fmt;
11
12/// Anything that can go wrong when *parsing* or *processing* inputs.
13///
14/// Note that a signature being mathematically invalid is not an error —
15/// it is a `false` return from [`crate::verify_vote`]. An `Error` is only
16/// produced when the caller hands us malformed bytes (wrong length, not
17/// on the curve, etc.) or asks for an operation that does not make sense
18/// (e.g. signing with a key that is not in the authorised ring).
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum Error {
21 /// A byte slice did not have the size required for the type it was
22 /// supposed to decode into.
23 InvalidLength {
24 /// Human-readable name of the value being decoded.
25 what: &'static str,
26 /// Number of bytes that were expected.
27 expected: usize,
28 /// Number of bytes that were actually provided.
29 got: usize,
30 },
31 /// 32 bytes that do not represent a valid Ristretto255 point.
32 InvalidPoint,
33 /// A Ristretto255 point decoded correctly but is the identity point,
34 /// which is not a valid public protocol value.
35 InvalidIdentityPoint,
36 /// 32 bytes that do not represent a canonical scalar (mod ℓ).
37 InvalidScalar,
38 /// A scalar decoded correctly but is not usable as a secret key.
39 InvalidSecretKey,
40 /// The hex string handed to a `*_from_hex` constructor could not be
41 /// decoded.
42 InvalidHex,
43 /// A prefixed string (e.g. `pk_…`) handed to a `*_from_prefixed`
44 /// constructor was not in the expected three-part shape, or carried
45 /// the wrong tag for the type being decoded (e.g. a `ki_` value where
46 /// a `pk_` one was required).
47 InvalidPrefix {
48 /// The tag the constructor required, without the trailing `_`
49 /// (`"pk"`, `"sk"`, `"ki"` or `"blsag"`).
50 expected: &'static str,
51 /// The tag actually found, or empty when the string was not even
52 /// in `tag_body_checksum` shape. Never echoes the value's body,
53 /// so it cannot leak secret-key material.
54 got: String,
55 },
56 /// A prefixed string decoded structurally but its trailing checksum
57 /// did not match the body — the value was almost certainly mistyped,
58 /// truncated or corrupted in transit.
59 InvalidChecksum,
60 /// `sign_vote` was called with a secret key whose public key is not
61 /// in the supplied authorised ring. Signing would still mathematically
62 /// produce something, but it would not verify, so we refuse it early.
63 SignerNotInRing,
64 /// `sign_vote` or `verify_vote` was called with an authorised ring
65 /// containing fewer than two members. A ring of one trivially
66 /// de-anonymises the signer, so we reject it.
67 RingTooSmall,
68 /// `sign_vote` was given a ring containing the same public key twice.
69 /// The protocol's anonymity guarantees assume distinct members, and
70 /// we refuse to silently de-duplicate.
71 DuplicateRingMember,
72 /// `sign_vote` was called with a zero-byte ballot. The library has
73 /// no opinion on the payload format, but an empty payload is almost
74 /// always a caller bug (forgot to serialise the form, fed in the
75 /// wrong variable, …) so we surface it instead of silently signing
76 /// nothing.
77 EmptyVote,
78 /// `sign_vote` was called with a zero-byte election identifier.
79 /// Allowing it would defeat the whole point of binding signatures
80 /// to an election context, so we refuse early.
81 EmptyElectionId,
82}
83
84impl fmt::Display for Error {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 match self {
87 Error::InvalidLength {
88 what,
89 expected,
90 got,
91 } => write!(
92 f,
93 "invalid length for {what}: expected {expected} bytes, got {got}"
94 ),
95 Error::InvalidPoint => f.write_str("bytes do not encode a valid Ristretto255 point"),
96 Error::InvalidIdentityPoint => f.write_str("point must not be the Ristretto identity"),
97 Error::InvalidScalar => f.write_str("bytes do not encode a canonical scalar"),
98 Error::InvalidSecretKey => f.write_str("secret key must be a non-zero scalar"),
99 Error::InvalidHex => f.write_str("input is not valid hexadecimal"),
100 Error::InvalidPrefix { expected, got } => {
101 if got.is_empty() {
102 write!(f, "expected a \"{expected}_\"-prefixed value")
103 } else {
104 write!(f, "expected a \"{expected}_\" prefix, got \"{got}_\"")
105 }
106 }
107 Error::InvalidChecksum => {
108 f.write_str("checksum mismatch: the value was mistyped or corrupted")
109 }
110 Error::SignerNotInRing => {
111 f.write_str("signer's public key is not part of the authorised ring")
112 }
113 Error::RingTooSmall => f.write_str("authorised ring must contain at least two members"),
114 Error::DuplicateRingMember => {
115 f.write_str("authorised ring contains a duplicate public key")
116 }
117 Error::EmptyVote => f.write_str("ballot is empty"),
118 Error::EmptyElectionId => f.write_str("election identifier is empty"),
119 }
120 }
121}
122
123impl std::error::Error for Error {}
124
125/// Local `Result` alias to keep signatures readable.
126pub type Result<T> = core::result::Result<T, Error>;