Skip to main content

auths_cli/commands/
anchor.rs

1//! Offline verification of witness-network anchor artifacts.
2//!
3//! The witness returns a portable, self-contained duplicity proof when a party
4//! equivocates. This command re-checks such a proof with no witness contacted
5//! and no registry consulted — the artifact a counterparty or a regulator can
6//! verify for themselves.
7
8use std::path::PathBuf;
9
10use anyhow::{Result, anyhow};
11use auths_anchor::DuplicityProof;
12use clap::{Parser, Subcommand};
13
14use crate::config::CliConfig;
15
16/// Verify witness-network anchor evidence, offline.
17#[derive(Parser, Debug, Clone)]
18pub struct AnchorCommand {
19    #[command(subcommand)]
20    pub subcommand: AnchorSubcommand,
21}
22
23/// Anchor subcommands.
24#[derive(Subcommand, Debug, Clone)]
25pub enum AnchorSubcommand {
26    /// Verify a duplicity proof offline — no witness contacted, no registry.
27    ///
28    /// Re-checks that one party key signed two different heads at one
29    /// `(seed, index)`. Exits non-zero with a named verdict if the proof is
30    /// forged or tampered — the artifact you hand a counterparty or a court.
31    Verify {
32        /// Path to the duplicity-proof JSON (`-` reads stdin).
33        #[clap(long)]
34        proof: PathBuf,
35    },
36}
37
38/// Dispatch an `auths anchor` subcommand.
39///
40/// Args:
41/// * `cmd`: the parsed anchor command.
42///
43/// Usage:
44/// ```ignore
45/// handle_anchor(cmd)?;
46/// ```
47pub fn handle_anchor(cmd: AnchorCommand) -> Result<()> {
48    match cmd.subcommand {
49        AnchorSubcommand::Verify { proof } => verify_duplicity(proof),
50    }
51}
52
53/// Read a duplicity proof (file path or `-` for stdin) and re-verify it offline.
54fn verify_duplicity(path: PathBuf) -> Result<()> {
55    let bytes = if path.as_os_str() == "-" {
56        let mut buf = Vec::new();
57        std::io::Read::read_to_end(&mut std::io::stdin(), &mut buf)
58            .map_err(|e| anyhow!("could not read the proof from stdin: {e}"))?;
59        buf
60    } else {
61        std::fs::read(&path).map_err(|e| anyhow!("could not read {}: {e}", path.display()))?
62    };
63    let proof: DuplicityProof = serde_json::from_slice(&bytes)
64        .map_err(|e| anyhow!("not a readable duplicity proof: {e}"))?;
65    match proof.verify() {
66        Ok(()) => {
67            println!(
68                "DUPLICITY PROVEN (offline, no witness contacted)\n  \
69                 seed  {}\n  index {}\n  heads {} / {}\n  \
70                 => one party key signed two heads at one index. CHEAT PROVEN.",
71                proof.seed_id.to_hex(),
72                proof.index,
73                proof.anchor_a.head.to_hex(),
74                proof.anchor_b.head.to_hex(),
75            );
76            Ok(())
77        }
78        Err(e) => Err(anyhow!("INVALID duplicity proof: {e}")),
79    }
80}
81
82impl crate::commands::executable::ExecutableCommand for AnchorCommand {
83    fn execute(&self, _ctx: &CliConfig) -> Result<()> {
84        handle_anchor(self.clone())
85    }
86}