use core::fmt;
use super::checkpoint::SignedCheckpoint;
use super::note::KeyName;
use super::signing::KeyType;
use super::tiles::TileId;
use crate::log::{ConsistencyProof, ProofError, TreeHead};
use crate::model::{DecodeError, RecordKind, SealOrderError};
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("malformed {what} at line {line}")]
Malformed {
what: Syntax,
line: u32,
},
#[error("the input exceeds {limit} bytes")]
TooLarge {
limit: usize,
},
#[error("a note carries more than 100 signatures")]
TooManySignatures,
#[error("two signatures by {name} with key ID {}", hex(*.key_id))]
DuplicateSignature {
name: KeyName,
key_id: [u8; 4],
},
#[error("not a valid key name or origin")]
BadName,
#[error("more than 63 proof hashes")]
TooManyProofLines,
#[error("signature type 0x{0:02x} is not supported")]
UnsupportedKeyType(u8),
#[error("a {found} key where a {expected} key is needed")]
WrongKeyType {
expected: KeyType,
found: KeyType,
},
#[error("the key ID does not match the key")]
KeyIdMismatch,
#[error("the Ed25519 key does not decode or has small order")]
WeakKey,
#[error("two keys named {name} share the key ID {}", hex(*.key_id))]
AmbiguousKey {
name: KeyName,
key_id: [u8; 4],
},
#[error("the key {key} cannot sign a checkpoint of {origin}")]
KeyNameMismatch {
key: KeyName,
origin: KeyName,
},
#[error("no signature from a trusted key")]
NoTrustedSignature,
#[error("the signature by {name} ({}) does not verify", hex(*.key_id))]
BadSignature {
name: KeyName,
key_id: [u8; 4],
},
#[error("no log key is known for the origin {0}")]
UnknownOrigin(KeyName),
#[error("the checkpoint's origin is not the name of a log that signed it")]
OriginNotSigned,
#[error("the quorum {quorum} is not met")]
QuorumNotMet {
quorum: String,
},
#[error("policy line {line}: {rule}")]
PolicyRule {
line: u32,
rule: PolicyRule,
},
#[error("a checkpoint of size 0 must carry the empty-tree root")]
EmptyTreeRoot,
#[error("the proof does not fit its sizes")]
ProofShape,
#[error("expected a {expected:?} record, found {found:?}")]
WrongRecordKind {
expected: RecordKind,
found: RecordKind,
},
#[error("proof: {0}")]
Proof(#[source] ProofError),
#[error("record: {0}")]
Decode(#[source] DecodeError),
#[error("seal: {0}")]
SealOrder(#[source] SealOrderError),
#[error("the bundle is at size {}, the trusted head at {}", .offered.size, trusted_size(.trusted.as_ref()))]
NotAtTrustedHead {
trusted: Option<TreeHead>,
offered: TreeHead,
},
#[error("a checkpoint of size {} is older than the trusted size {}", .offered.size, .trusted.size)]
Rollback {
trusted: TreeHead,
offered: TreeHead,
},
#[error("fork: two roots at size {}", .0.trusted_head.size)]
Fork(Box<ForkEvidence>),
#[error("inconsistent: the tree of size {} does not extend the trusted one", .0.offered.checkpoint().head().size)]
Inconsistent(Box<ForkEvidence>),
#[error("witness: {0}")]
Witness(#[source] WitnessError),
#[error("no tile has this level or width")]
InvalidTile,
#[error("{tile} is not the kind of tile asked for")]
WrongTileKind {
tile: TileId,
},
#[error("{tile} lies beyond a tree of {size} leaves")]
TileOutOfRange {
tile: TileId,
size: u64,
},
#[error("the leaf at {position} has {len} bytes, more than 65535")]
LeafTooLarge {
position: u64,
len: usize,
},
#[error("the checkpoint is of {origin}, not of this ledger")]
ForeignCheckpoint {
origin: KeyName,
},
#[error("the checkpoint is not a head of this ledger")]
CheckpointMismatch,
}
fn trusted_size(head: Option<&TreeHead>) -> String {
head.map_or_else(|| String::from("none"), |head| head.size.to_string())
}
pub(crate) fn hex(key_id: [u8; 4]) -> String {
format!("{:08x}", u32::from_be_bytes(key_id))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Syntax {
Utf8,
ControlChar,
MissingSeparator,
MissingNewline,
NoSignature,
SignatureLine,
Base64,
Decimal,
RootLength,
EmptyLine,
Vkey,
Header,
TrailingData,
PolicyLine,
TilePath,
ExtraLayout,
}
impl fmt::Display for Syntax {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Utf8 => "utf-8",
Self::ControlChar => "control character",
Self::MissingSeparator => "note (no blank line before the signatures)",
Self::MissingNewline => "final newline",
Self::NoSignature => "note (no signature line)",
Self::SignatureLine => "signature line",
Self::Base64 => "base64",
Self::Decimal => "decimal",
Self::RootLength => "hash length",
Self::EmptyLine => "empty line",
Self::Vkey => "verifier key",
Self::Header => "header line",
Self::TrailingData => "trailing data",
Self::PolicyLine => "policy line",
Self::TilePath => "tile path",
Self::ExtraLayout => "extra data",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum PolicyRule {
Charset,
Line,
ReservedName,
Name,
DuplicateName,
DuplicateKey,
WitnessIsLog,
AmbiguousKey,
KeyType,
Threshold,
Undefined,
MemberTwice,
QuorumCount,
TooMany,
}
impl fmt::Display for PolicyRule {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Charset => "an octet outside the policy character set",
Self::Line => "not a log, witness, group or quorum definition",
Self::ReservedName => "the name none is reserved",
Self::Name => "a name is empty or holds whitespace or a control character",
Self::DuplicateName => "a name is defined twice",
Self::DuplicateKey => "a public key is listed twice",
Self::WitnessIsLog => "a witness key is also a log key",
Self::AmbiguousKey => "two keys share a key name and key ID",
Self::KeyType => "a key of the wrong signature type",
Self::Threshold => "the threshold is not all, any or 1 to n",
Self::Undefined => "a name is used before its definition",
Self::MemberTwice => "a name is a group member twice",
Self::QuorumCount => "a policy needs exactly one quorum line",
Self::TooMany => "more than 256 logs, witnesses or groups",
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum WitnessError {
#[error("the witness holds size {size}")]
Conflict {
size: u64,
},
#[error("400 bad request")]
BadRequest,
#[error("403 the witness does not accept the log signature")]
Forbidden,
#[error("404 the witness does not know the log")]
UnknownLog,
#[error("422 the witness refused the proof or root")]
Unprocessable,
#[error("429 push-back, retry later")]
Pushback,
#[error("unexpected status {status}")]
Unexpected {
status: u16,
},
#[error("malformed reply")]
MalformedReply,
#[error("no cosignature from {name}")]
NoCosignature {
name: KeyName,
},
#[error("the cosignature by {name} ({}) does not verify", hex(*.key_id))]
BadCosignature {
name: KeyName,
key_id: [u8; 4],
},
#[error("the witness holds size {witness}, beyond the checkpoint's {checkpoint}")]
WitnessAhead {
witness: u64,
checkpoint: u64,
},
#[error("the witness went back from size {previous} to {size}")]
WitnessRegressed {
previous: u64,
size: u64,
},
#[error("three conflicts, at sizes {sizes:?}")]
TooManyConflicts {
sizes: Vec<u64>,
},
#[error("the session already ended")]
SessionFinished,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ForkEvidence {
pub trusted_head: TreeHead,
pub trusted: Option<SignedCheckpoint>,
pub offered: SignedCheckpoint,
pub proof: Option<ConsistencyProof>,
}
impl From<WitnessError> for Error {
fn from(error: WitnessError) -> Self {
Self::Witness(error)
}
}
impl From<ProofError> for Error {
fn from(error: ProofError) -> Self {
Self::Proof(error)
}
}
impl From<DecodeError> for Error {
fn from(error: DecodeError) -> Self {
Self::Decode(error)
}
}
impl From<SealOrderError> for Error {
fn from(error: SealOrderError) -> Self {
Self::SealOrder(error)
}
}