silent_payments_core/error.rs
1//! Per-domain error enums for Silent Payments operations.
2//!
3//! Three error types cover different failure domains:
4//! - [`AddressError`] -- SP address parsing and encoding failures
5//! - [`InputError`] -- transaction input classification failures
6//! - [`CryptoError`] -- cryptographic operation failures
7//!
8//! All enums implement [`std::error::Error`] via `thiserror` and provide
9//! wallet-developer-friendly error messages (not cryptographer jargon).
10
11use thiserror::Error;
12
13/// Errors from parsing or encoding BIP 352 Silent Payment addresses.
14#[derive(Debug, Error)]
15pub enum AddressError {
16 /// Bech32m decoding failure (checksum, character set, or length).
17 #[error("invalid bech32m encoding: {0}")]
18 InvalidBech32(String),
19
20 /// Address version is unsupported.
21 /// Version 31 is always rejected (backward-incompatible).
22 /// Versions 1-30 are forward-compatible (first 66 bytes are read).
23 #[error(
24 "unsupported SP address version {version}: \
25 versions 1-30 are forward-compatible, version 31 is rejected"
26 )]
27 UnsupportedVersion { version: u8 },
28
29 /// Payload length does not match the expected length for the given version.
30 #[error(
31 "wrong payload length for version {version}: \
32 expected {expected} bytes, got {actual}"
33 )]
34 WrongPayloadLength {
35 version: u8,
36 expected: usize,
37 actual: usize,
38 },
39
40 /// Human-readable prefix is not recognized (not sp/tsp/sprt).
41 #[error("unknown human-readable prefix: {hrp}")]
42 UnknownHrp { hrp: String },
43
44 /// A public key in the address payload is invalid.
45 #[error("invalid public key in address payload: {reason}")]
46 InvalidPublicKey { reason: String },
47}
48
49/// Errors from classifying transaction inputs and extracting public keys.
50#[derive(Debug, Clone, Error)]
51pub enum InputError {
52 /// Input type is not eligible for Silent Payments (SEC-03: explicit error, never filter).
53 #[error("ineligible input type: {script_type} ({reason})")]
54 IneligibleInput { script_type: String, reason: String },
55
56 /// Failed to extract a public key from an otherwise eligible input.
57 #[error("failed to extract public key from {input_type} input: {reason}")]
58 PubKeyExtraction { input_type: String, reason: String },
59
60 /// Transaction has zero eligible inputs for Silent Payments.
61 #[error("no eligible inputs found in transaction")]
62 NoEligibleInputs,
63}
64
65/// Errors from cryptographic operations (EC math, key validation, ECDH).
66#[derive(Debug, Error)]
67pub enum CryptoError {
68 /// A secp256k1 library operation failed.
69 /// Wraps the error as a string to avoid exposing the transitive type.
70 #[error("secp256k1 operation failed: {0}")]
71 Secp256k1(String),
72
73 /// ECDH or tweak computation produced a zero result
74 /// (hash output = 0 or >= curve order).
75 #[error(
76 "shared secret or tweak computation produced zero result \
77 (hash output = 0 or >= curve order)"
78 )]
79 ZeroTweakResult,
80
81 /// A point operation resulted in the point at infinity (identity element).
82 #[error("point operation resulted in the point at infinity")]
83 PointAtInfinity,
84
85 /// A public key is invalid.
86 #[error("invalid public key: {reason}")]
87 InvalidPublicKey { reason: String },
88
89 /// A secret key is invalid (zero or >= curve order).
90 #[error("invalid secret key: key is zero or >= curve order")]
91 InvalidSecretKey,
92}
93
94impl From<bitcoin::secp256k1::Error> for CryptoError {
95 fn from(err: bitcoin::secp256k1::Error) -> Self {
96 CryptoError::Secp256k1(err.to_string())
97 }
98}