File size: 4,568 Bytes
1295969
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//! Ledger hardware wallet signer via ethers-rs.
//!
//! Production: connects to physical Ledger device via HID, signs transactions
//! on the secure element. Private key never leaves the device.
//!
//! Dev (LEDGER_DEV_MODE=1): returns a deterministic stub signature so the
//! rest of the pipeline can be exercised without hardware.
//!
//! NOTE: actual transaction signing is now handled in bttc.rs via
//! `SignerMiddleware<Provider<Http>, Ledger>`. This module exposes the
//! lower-level `sign_bytes` helper for use by other callers (e.g. DDEX
//! manifest signing, ISO 9001 audit log sealing).

#[cfg(feature = "ledger")]
use tracing::info;
use tracing::{instrument, warn};

/// Signs arbitrary bytes with the Ledger's Ethereum personal_sign path.
/// For EIP-712 structured data, use the middleware in bttc.rs directly.
#[allow(dead_code)]
#[instrument(skip(payload))]
pub async fn sign_bytes(payload: &[u8]) -> anyhow::Result<Vec<u8>> {
    if std::env::var("LEDGER_DEV_MODE").unwrap_or_default() == "1" {
        warn!("LEDGER_DEV_MODE=1 — returning deterministic stub signature");
        // Deterministic stub: sha256(payload) ++ 65 zero bytes (r,s,v)
        use sha2::{Digest, Sha256};
        let mut sig = Sha256::digest(payload).to_vec();
        sig.resize(32 + 65, 0);
        return Ok(sig);
    }

    #[cfg(feature = "ledger")]
    {
        use ethers_signers::{HDPath, Ledger, Signer};

        let chain_id = std::env::var("BTTC_CHAIN_ID")
            .unwrap_or_else(|_| "199".into()) // BTTC mainnet
            .parse::<u64>()
            .map_err(|_| anyhow::anyhow!("BTTC_CHAIN_ID must be a u64"))?;

        let ledger = Ledger::new(HDPath::LedgerLive(0), chain_id)
            .await
            .map_err(|e| {
                anyhow::anyhow!(
                    "Cannot open Ledger: {}. Device must be connected, unlocked, \
                 Ethereum app open.",
                    e
                )
            })?;

        let sig = ledger
            .sign_message(payload)
            .await
            .map_err(|e| anyhow::anyhow!("Ledger sign_message failed: {}", e))?;

        let mut out = Vec::with_capacity(65);
        let mut r_bytes = [0u8; 32];
        let mut s_bytes = [0u8; 32];
        sig.r.to_big_endian(&mut r_bytes);
        sig.s.to_big_endian(&mut s_bytes);
        out.extend_from_slice(&r_bytes);
        out.extend_from_slice(&s_bytes);
        out.push(sig.v as u8);

        info!(addr=%ledger.address(), "Ledger signature produced");
        Ok(out)
    }

    #[cfg(not(feature = "ledger"))]
    {
        anyhow::bail!(
            "Ledger feature not enabled. Set LEDGER_DEV_MODE=1 for development \
             or compile with --features ledger for production."
        )
    }
}

/// Returns the Ledger's Ethereum address at `m/44'/60'/0'/0/0`.
/// Used to pre-verify the correct device is connected before submitting.
#[allow(dead_code)]
pub async fn get_address() -> anyhow::Result<String> {
    if std::env::var("LEDGER_DEV_MODE").unwrap_or_default() == "1" {
        return Ok("0xDEV0000000000000000000000000000000000001".into());
    }

    #[cfg(feature = "ledger")]
    {
        use ethers_signers::{HDPath, Ledger, Signer};
        let chain_id = std::env::var("BTTC_CHAIN_ID")
            .unwrap_or_else(|_| "199".into())
            .parse::<u64>()?;
        let ledger = Ledger::new(HDPath::LedgerLive(0), chain_id)
            .await
            .map_err(|e| anyhow::anyhow!("Ledger not found: {}", e))?;
        Ok(format!("{:#x}", ledger.address()))
    }

    #[cfg(not(feature = "ledger"))]
    {
        anyhow::bail!("Ledger feature not compiled in")
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn dev_mode_stub_is_deterministic() {
        std::env::set_var("LEDGER_DEV_MODE", "1");
        let sig1 = sign_bytes(b"hello retrosync").await.unwrap();
        let sig2 = sign_bytes(b"hello retrosync").await.unwrap();
        assert_eq!(
            sig1, sig2,
            "dev stub must be deterministic for test reproducibility"
        );
        // Different payload → different stub
        let sig3 = sign_bytes(b"different payload").await.unwrap();
        assert_ne!(sig1, sig3);
        std::env::remove_var("LEDGER_DEV_MODE");
    }

    #[tokio::test]
    async fn dev_mode_address_returns_stub() {
        std::env::set_var("LEDGER_DEV_MODE", "1");
        let addr = get_address().await.unwrap();
        assert!(addr.starts_with("0x"), "address must be hex");
        std::env::remove_var("LEDGER_DEV_MODE");
    }
}