Skip to main content

silent_payments_psbt/
error.rs

1//! Per-domain error enums for PSBT and DLEQ operations.
2//!
3//! Two error types cover different failure domains:
4//! - [`PsbtError`] -- BIP 375 PSBT field and role operation failures
5//! - [`DleqError`] -- BIP 374 DLEQ proof generation and verification failures
6//!
7//! All enums implement [`std::error::Error`] via `thiserror` and provide
8//! wallet-developer-friendly error messages.
9
10use silent_payments_core::CryptoError;
11use thiserror::Error;
12
13/// Errors from BIP 375 PSBT operations (field access, role transitions, validation).
14#[derive(Debug, Error)]
15pub enum PsbtError {
16    /// PSBT output is missing the SP_V0_INFO field (scan + spend keys).
17    #[error("PSBT output missing scan public key in SP_V0_INFO field")]
18    MissingScanKey,
19
20    /// PSBT is missing an ECDH share for a specific scan key.
21    #[error("missing ECDH share for scan key {scan_key}")]
22    MissingEcdhShare { scan_key: String },
23
24    /// PSBT is missing a DLEQ proof for a specific scan key.
25    #[error("missing DLEQ proof for scan key {scan_key}")]
26    MissingDleqProof { scan_key: String },
27
28    /// A PSBT field has an unexpected byte length.
29    #[error("invalid {field} length: expected {expected}, got {actual}")]
30    InvalidFieldLength {
31        field: String,
32        expected: usize,
33        actual: usize,
34    },
35
36    /// A public key in a PSBT field is invalid.
37    #[error("invalid public key in PSBT field: {reason}")]
38    InvalidPublicKey { reason: String },
39
40    /// A DLEQ proof in the PSBT is invalid.
41    #[error(transparent)]
42    InvalidProof(#[from] DleqError),
43
44    /// A scan key has both global and per-input ECDH shares (BIP 375 violation).
45    #[error("scan key {scan_key} has both global and per-input ECDH shares")]
46    DuplicateShareType { scan_key: String },
47
48    /// An output index exceeds the number of PSBT outputs.
49    #[error("output index {index} out of bounds (PSBT has {count} outputs)")]
50    OutputIndexOutOfBounds { index: usize, count: usize },
51
52    /// PSBT has no outputs with Silent Payment fields.
53    #[error("PSBT has no Silent Payment output fields")]
54    NoSpOutputs,
55
56    /// Not all required ECDH shares and DLEQ proofs are present for extraction.
57    #[error("not all required ECDH shares and DLEQ proofs are present")]
58    IncompleteSigning,
59
60    /// A cryptographic operation failed.
61    #[error("cryptographic error: {0}")]
62    Crypto(CryptoError),
63}
64
65impl From<CryptoError> for PsbtError {
66    fn from(err: CryptoError) -> Self {
67        PsbtError::Crypto(err)
68    }
69}
70
71/// Errors from BIP 374 DLEQ proof operations.
72#[derive(Debug, Error)]
73pub enum DleqError {
74    /// The DLEQ proof bytes are structurally invalid.
75    #[error("DLEQ proof is invalid")]
76    InvalidProof,
77
78    /// The DLEQ proof verified but returned false (proof does not match keys).
79    #[error("DLEQ proof verification returned false")]
80    VerificationFailed,
81
82    /// The keys provided for DLEQ proof don't match the expected relationship.
83    #[error("key mismatch in DLEQ proof: {reason}")]
84    MismatchedKeys { reason: String },
85
86    /// The proof byte array has the wrong length.
87    #[error("proof must be {expected} bytes, got {actual}")]
88    MalformedProofBytes { expected: usize, actual: usize },
89
90    /// DLEQ proof generation failed.
91    #[error("DLEQ proof generation failed: {reason}")]
92    GenerationFailed { reason: String },
93
94    /// A secp256k1 operation failed (wraps as string, consistent with CryptoError pattern).
95    #[error("secp256k1 error: {0}")]
96    Crypto(String),
97}
98
99impl From<dleq::DleqError> for DleqError {
100    fn from(err: dleq::DleqError) -> Self {
101        match err {
102            dleq::DleqError::RangeError(e) => DleqError::Crypto(e.to_string()),
103            dleq::DleqError::Secp256k1Error(e) => DleqError::Crypto(e.to_string()),
104            dleq::DleqError::VerificationFailed => DleqError::VerificationFailed,
105        }
106    }
107}
108
109impl From<bitcoin::secp256k1::Error> for DleqError {
110    fn from(err: bitcoin::secp256k1::Error) -> Self {
111        DleqError::Crypto(err.to_string())
112    }
113}