Spaces:
Building
Building
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");
}
}
|