use std::collections::BTreeMap;
use crate::poe_standard::{ErrorCode, PoeRecord, Severity};
pub use crate::verifier::fetch::{
FetchOutboundOptions, FetchOutboundResult, FetchTransport, HttpCallRecord, HttpMethod,
HttpPurpose,
};
pub const NETWORK_CARDANO_MAINNET: &str = "cardano:mainnet";
pub const CONFIRMATION_DEPTH_THRESHOLD_DEFAULT: u32 = 15;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Verdict {
Valid,
Pending,
Failed,
}
impl Verdict {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Verdict::Valid => "valid",
Verdict::Pending => "pending",
Verdict::Failed => "failed",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExitCode {
Ok,
Integrity,
Network,
InsufficientDepth,
}
impl ExitCode {
#[must_use]
pub const fn as_u8(self) -> u8 {
match self {
ExitCode::Ok => 0,
ExitCode::Integrity => 1,
ExitCode::Network => 2,
ExitCode::InsufficientDepth => 3,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Profile {
Core,
Signed,
Sealed,
RecipientSealed,
}
impl Profile {
#[must_use]
pub const fn rank(self) -> u8 {
match self {
Profile::Core => 0,
Profile::Signed => 1,
Profile::Sealed => 2,
Profile::RecipientSealed => 3,
}
}
#[must_use]
pub const fn at_least(self, required: Profile) -> bool {
self.rank() >= required.rank()
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Profile::Core => "core",
Profile::Signed => "signed",
Profile::Sealed => "sealed",
Profile::RecipientSealed => "recipient-sealed",
}
}
}
pub const DEFAULT_PROFILE: Profile = Profile::RecipientSealed;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VerifierIssue {
pub code: ErrorCode,
pub path: Vec<PathSegment>,
pub message: String,
pub severity: Severity,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PathSegment {
Key(String),
Index(usize),
}
impl VerifierIssue {
#[must_use]
pub fn new(code: ErrorCode, path: Vec<PathSegment>, message: impl Into<String>) -> Self {
Self {
code,
path,
message: message.into(),
severity: code.severity(),
}
}
}
impl From<&crate::poe_standard::ValidationIssue> for VerifierIssue {
fn from(issue: &crate::poe_standard::ValidationIssue) -> Self {
Self {
code: issue.code,
path: Vec::new(),
message: issue.message.clone(),
severity: issue.severity,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ValidationSummary {
pub valid: bool,
pub issues: Vec<VerifierIssue>,
pub warnings: Vec<VerifierIssue>,
pub info: Vec<VerifierIssue>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignerType {
InSignatureKid,
WalletInlineKey,
}
impl SignerType {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
SignerType::InSignatureKid => "in-signature-kid",
SignerType::WalletInlineKey => "wallet-inline-key",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SigFailureReason {
MalformedSigCoseSign1,
SignatureUnsupported,
SignerKeyUnresolved,
SignatureInvalid,
WalletAddressMismatch,
}
impl SigFailureReason {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
SigFailureReason::MalformedSigCoseSign1 => "MALFORMED_SIG_COSE_SIGN1",
SigFailureReason::SignatureUnsupported => "SIGNATURE_UNSUPPORTED",
SigFailureReason::SignerKeyUnresolved => "SIGNER_KEY_UNRESOLVED",
SigFailureReason::SignatureInvalid => "SIGNATURE_INVALID",
SigFailureReason::WalletAddressMismatch => "WALLET_ADDRESS_MISMATCH",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SignatureCheck {
pub index: usize,
pub valid: bool,
pub signer_pub: Option<String>,
pub signer_type: Option<SignerType>,
pub reason: Option<SigFailureReason>,
}
impl SignatureCheck {
#[must_use]
pub const fn verdict_str(&self) -> &'static str {
match self.reason {
None => "valid",
Some(SigFailureReason::SignatureUnsupported) => "unsupported",
Some(SigFailureReason::SignerKeyUnresolved) => "unresolved",
Some(_) => "invalid",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DecryptionFailureReason {
NoEncEnvelope,
UriFetchFailed,
CiphertextUnavailable,
UriTargetForbidden,
ContentUnavailable,
WrongRecipientKey,
TamperedHeader,
TamperedCiphertext,
WrongDecryptionInputShape,
KdfDerivationFailed,
UriIntegrityMismatch,
}
impl DecryptionFailureReason {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
DecryptionFailureReason::NoEncEnvelope => "no_enc_envelope",
DecryptionFailureReason::UriFetchFailed => "URI_FETCH_FAILED",
DecryptionFailureReason::CiphertextUnavailable => "CIPHERTEXT_UNAVAILABLE",
DecryptionFailureReason::UriTargetForbidden => "URI_TARGET_FORBIDDEN",
DecryptionFailureReason::ContentUnavailable => "CONTENT_UNAVAILABLE",
DecryptionFailureReason::WrongRecipientKey => "WRONG_RECIPIENT_KEY",
DecryptionFailureReason::TamperedHeader => "TAMPERED_HEADER",
DecryptionFailureReason::TamperedCiphertext => "TAMPERED_CIPHERTEXT",
DecryptionFailureReason::WrongDecryptionInputShape => "WRONG_DECRYPTION_INPUT_SHAPE",
DecryptionFailureReason::KdfDerivationFailed => "KDF_DERIVATION_FAILED",
DecryptionFailureReason::UriIntegrityMismatch => "URI_INTEGRITY_MISMATCH",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DecryptResult {
pub item_index: i64,
pub ok: bool,
pub plaintext_hash_ok: Option<bool>,
pub reason: Option<DecryptionFailureReason>,
}
impl DecryptResult {
#[must_use]
pub const fn verdict_str(&self) -> &'static str {
match self.reason {
None => "decrypted",
Some(DecryptionFailureReason::NoEncEnvelope) => "no-enc-envelope",
Some(DecryptionFailureReason::WrongDecryptionInputShape) => "wrong-input-shape",
Some(DecryptionFailureReason::CiphertextUnavailable)
| Some(DecryptionFailureReason::UriTargetForbidden) => "ciphertext-unavailable",
Some(DecryptionFailureReason::ContentUnavailable)
| Some(DecryptionFailureReason::UriFetchFailed) => "content-unavailable",
Some(DecryptionFailureReason::WrongRecipientKey) => "wrong-key",
Some(DecryptionFailureReason::TamperedHeader) => "tampered-header",
Some(DecryptionFailureReason::TamperedCiphertext)
| Some(DecryptionFailureReason::UriIntegrityMismatch) => "tampered-ciphertext",
Some(DecryptionFailureReason::KdfDerivationFailed) => "kdf-failed",
}
}
#[must_use]
pub fn reason_str(&self) -> Option<&'static str> {
match self.reason {
None | Some(DecryptionFailureReason::NoEncEnvelope) => None,
Some(r) => Some(r.as_str()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MerkleCheckReason {
MerkleLeavesUnavailable,
MerkleRootMismatch,
MerkleUnsupported,
SchemaMerkleLeafCountMismatch,
SchemaMerkleLeavesFormatUnsupported,
}
impl MerkleCheckReason {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
MerkleCheckReason::MerkleLeavesUnavailable => "MERKLE_LEAVES_UNAVAILABLE",
MerkleCheckReason::MerkleRootMismatch => "MERKLE_ROOT_MISMATCH",
MerkleCheckReason::MerkleUnsupported => "MERKLE_UNSUPPORTED",
MerkleCheckReason::SchemaMerkleLeafCountMismatch => "SCHEMA_MERKLE_LEAF_COUNT_MISMATCH",
MerkleCheckReason::SchemaMerkleLeavesFormatUnsupported => {
"SCHEMA_MERKLE_LEAVES_FORMAT_UNSUPPORTED"
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MerkleCheck {
pub merkle_index: usize,
pub alg: String,
pub root_ok: Option<bool>,
pub reason: Option<MerkleCheckReason>,
}
impl MerkleCheck {
#[must_use]
pub const fn verdict_str(&self) -> &'static str {
match self.reason {
None => {
if matches!(self.root_ok, Some(false)) {
"mismatch"
} else {
"valid"
}
}
Some(MerkleCheckReason::MerkleUnsupported) => "unsupported",
Some(MerkleCheckReason::MerkleLeavesUnavailable) => "unavailable",
Some(MerkleCheckReason::SchemaMerkleLeavesFormatUnsupported) => "format-unsupported",
Some(MerkleCheckReason::MerkleRootMismatch)
| Some(MerkleCheckReason::SchemaMerkleLeafCountMismatch) => "mismatch",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UriFailureReason {
UriFetchFailed,
UriIntegrityMismatch,
UriTargetForbidden,
ContentUnavailable,
}
impl UriFailureReason {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
UriFailureReason::UriFetchFailed => "URI_FETCH_FAILED",
UriFailureReason::UriIntegrityMismatch => "URI_INTEGRITY_MISMATCH",
UriFailureReason::UriTargetForbidden => "URI_TARGET_FORBIDDEN",
UriFailureReason::ContentUnavailable => "CONTENT_UNAVAILABLE",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VerifyTxWitness {
pub vkey: String,
pub key_hash: String,
pub signature_valid: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VerifyTxOutput {
pub address: String,
pub lovelace: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VerifyTxSummary {
pub fee_lovelace: String,
pub input_count: u64,
pub output_count: u64,
pub outputs: Vec<VerifyTxOutput>,
pub total_output_lovelace: String,
pub script_witness_count: u64,
pub invalid_before: Option<u64>,
pub invalid_hereafter: Option<u64>,
pub required_signer_key_hashes: Option<Vec<String>>,
pub network_id: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct TxDescription {
pub tx_witnesses: Option<Vec<VerifyTxWitness>>,
pub tx_summary: Option<VerifyTxSummary>,
pub metadata_labels: Option<Vec<i64>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UriCheck {
pub item_index: i64,
pub uri: String,
pub ok: bool,
pub reason: Option<UriFailureReason>,
}
#[derive(Debug, Clone)]
pub enum Decryption {
Recipient {
item_index: i64,
recipient_secret_key: Vec<u8>,
},
Passphrase {
item_index: i64,
passphrase: String,
},
}
impl Decryption {
#[must_use]
pub const fn item_index(&self) -> i64 {
match self {
Decryption::Recipient { item_index, .. }
| Decryption::Passphrase { item_index, .. } => *item_index,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CardanoNetwork {
#[default]
Mainnet,
Preprod,
}
pub struct VerifyTxInput<'a> {
pub tx_hash: String,
pub profile: Profile,
pub cardano_gateway_chain: Option<Vec<String>>,
pub blockfrost_project_id: Option<String>,
pub arweave_gateway_chain: Option<Vec<String>>,
pub ipfs_gateway_chain: Option<Vec<String>>,
pub confirmation_depth_threshold: Option<u32>,
pub deny_hosts: Option<Vec<String>>,
pub decryption: Option<Vec<Decryption>>,
pub ciphertext_bytes: Option<BTreeMap<i64, Vec<u8>>>,
pub merkle_leaves: Option<BTreeMap<usize, Vec<u8>>>,
pub cardano_network: CardanoNetwork,
pub fetch_outbound: Option<&'a dyn FetchTransport>,
}
impl<'a> VerifyTxInput<'a> {
#[must_use]
pub fn new(tx_hash: impl Into<String>) -> Self {
Self {
tx_hash: tx_hash.into(),
profile: DEFAULT_PROFILE,
cardano_gateway_chain: None,
blockfrost_project_id: None,
arweave_gateway_chain: None,
ipfs_gateway_chain: None,
confirmation_depth_threshold: None,
deny_hosts: None,
decryption: None,
ciphertext_bytes: None,
merkle_leaves: None,
cardano_network: CardanoNetwork::Mainnet,
fetch_outbound: None,
}
}
#[must_use]
pub fn threshold(&self) -> u32 {
self.confirmation_depth_threshold
.unwrap_or(CONFIRMATION_DEPTH_THRESHOLD_DEFAULT)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VerifyReport {
pub tx_hash: String,
pub verdict: Verdict,
pub exit_code: ExitCode,
pub profile: Profile,
pub network: &'static str,
pub confirmation_depth_threshold: u32,
pub validation: ValidationSummary,
pub http_calls: Vec<HttpCallRecord>,
pub metadata_present: bool,
pub num_confirmations: u32,
pub block_time: Option<u64>,
pub block_slot: Option<u64>,
pub record: Option<PoeRecord>,
pub record_signatures: Option<Vec<SignatureCheck>>,
pub item_decryptions: Option<Vec<DecryptResult>>,
pub tx_witnesses: Option<Vec<VerifyTxWitness>>,
pub tx_summary: Option<VerifyTxSummary>,
pub metadata_labels: Option<Vec<i64>>,
pub uri_checks: Option<Vec<UriCheck>>,
pub merkle_checks: Option<Vec<MerkleCheck>>,
}