Skip to main content

privacy_core/
intent.rs

1//! Bridge / federation helpers — V1 single-signature BTC deposit address.
2//!
3//! Operators import BTC to one watch-only (or hot) address, then submit `shield(...)` on
4//! Ethereum after observing the expected `ShieldIntent` on disk or queue.
5//! (Extracted from `privacybtc-bridge`; the unused commitment-tree import was dropped.)
6
7use crate::types::OrchardStoredBundle;
8use serde::{Deserialize, Serialize};
9use sha2::{Digest, Sha256};
10use thiserror::Error;
11
12/// Operator-configured Bitcoin deposit destination (single-sig).
13#[derive(Debug, Clone)]
14pub struct BtcDepositConfigV1 {
15    /// Bech32 (`bc1...`) or Base58 (legacy / P2SH) deposit address.
16    pub btc_deposit_address: String,
17}
18
19/// JSON artifact the user exchanges with the operator (or stores for polling).
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct ShieldIntentV1 {
22    pub protocol_version: u32,
23    pub btc_deposit_address: String,
24    pub amount_sats: u64,
25    /// SHA-256 of canonical `OrchardStoredBundle` JSON bytes (operator verifies file matches).
26    pub bundle_sha256_hex: String,
27    /// First output note commitment (hex, no `0x`).
28    pub orchard_cmx_hex: String,
29    /// Optional human-readable note (e.g. email ticket id).
30    pub operator_reference: Option<String>,
31    /// BTC txid of the user's deposit transaction (for exact UTXO matching).
32    #[serde(default)]
33    pub btc_txid: Option<String>,
34}
35
36#[derive(Debug, Error)]
37pub enum BridgeError {
38    #[error("stored bundle must contain at least one action")]
39    NoActions,
40}
41
42/// Stable content hash for an `OrchardStoredBundle` (serialized JSON, pretty=false).
43pub fn bundle_content_sha256(bundle: &OrchardStoredBundle) -> [u8; 32] {
44    let bytes = serde_json::to_vec(bundle).expect("OrchardStoredBundle serializes");
45    Sha256::digest(bytes).into()
46}
47
48/// Builds the V1 shield intent JSON struct.
49pub fn build_shield_intent_v1(
50    bundle: &OrchardStoredBundle,
51    cfg: &BtcDepositConfigV1,
52    amount_sats: u64,
53    operator_reference: Option<String>,
54    btc_txid: Option<String>,
55) -> Result<ShieldIntentV1, BridgeError> {
56    let action = bundle.actions.first().ok_or(BridgeError::NoActions)?;
57    Ok(ShieldIntentV1 {
58        protocol_version: 1,
59        btc_deposit_address: cfg.btc_deposit_address.clone(),
60        amount_sats,
61        bundle_sha256_hex: hex::encode(bundle_content_sha256(bundle)),
62        orchard_cmx_hex: hex::encode(action.cmx),
63        operator_reference,
64        btc_txid,
65    })
66}