mirror of
https://github.com/stakwork/sphinx-key.git
synced 2026-02-01 05:44:19 +01:00
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@
|
||||
target
|
||||
Cargo.lock
|
||||
!sphinx-key/Cargo.lock
|
||||
!factory/Cargo.lock
|
||||
.DS_Store
|
||||
notes.md
|
||||
test-flash
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
[workspace]
|
||||
|
||||
members = [
|
||||
"signer",
|
||||
"parser",
|
||||
"signer",
|
||||
"tester",
|
||||
]
|
||||
|
||||
exclude = [
|
||||
"sphinx-key",
|
||||
"persister",
|
||||
"broker",
|
||||
"factory",
|
||||
"persister",
|
||||
"sphinx-key",
|
||||
]
|
||||
|
||||
34
factory/.cargo/config.toml
Normal file
34
factory/.cargo/config.toml
Normal file
@@ -0,0 +1,34 @@
|
||||
[build]
|
||||
# Uncomment the relevant target for your chip here (ESP32, ESP32-S2, ESP32-S3 or ESP32-C3)
|
||||
#target = "xtensa-esp32-espidf"
|
||||
#target = "xtensa-esp32s2-espidf"
|
||||
#target = "xtensa-esp32s3-espidf"
|
||||
target = "riscv32imc-esp-espidf"
|
||||
|
||||
[target.xtensa-esp32-espidf]
|
||||
linker = "ldproxy"
|
||||
|
||||
[target.xtensa-esp32s2-espidf]
|
||||
linker = "ldproxy"
|
||||
|
||||
[target.xtensa-esp32s3-espidf]
|
||||
linker = "ldproxy"
|
||||
|
||||
[target.riscv32imc-esp-espidf]
|
||||
linker = "ldproxy"
|
||||
|
||||
# Future - necessary for the experimental "native build" of esp-idf-sys with ESP32C3
|
||||
# See also https://github.com/ivmarkov/embuild/issues/16
|
||||
rustflags = ["-C", "default-linker-libraries"]
|
||||
|
||||
[unstable]
|
||||
|
||||
build-std = ["std", "panic_abort"]
|
||||
build-std-features = ["panic_immediate_abort"] # Required for older ESP-IDF versions without a realpath implementation
|
||||
|
||||
[env]
|
||||
# Note: these variables are not used when using pio builder
|
||||
# Enables the esp-idf-sys "native" build feature (`cargo build --features native`) to build against ESP-IDF stable (v4.4)
|
||||
ESP_IDF_VERSION = { value = "release/v4.4" }
|
||||
# Enables the esp-idf-sys "native" build feature (`cargo build --features native`) to build against ESP-IDF master (mainline)
|
||||
#ESP_IDF_VERSION = { value = "master" }
|
||||
1897
factory/Cargo.lock
generated
Normal file
1897
factory/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
factory/Cargo.toml
Normal file
33
factory/Cargo.toml
Normal file
@@ -0,0 +1,33 @@
|
||||
[package]
|
||||
name = "sphinx-key-factory"
|
||||
version = "0.1.0"
|
||||
authors = ["decentclock <decentclock.5uh2k@slmail.me>"]
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
pio = ["esp-idf-sys/pio"]
|
||||
|
||||
[dependencies]
|
||||
esp-idf-sys = { version = "0.31.8", features = ["binstart"] }
|
||||
esp-idf-svc = { version = "0.42.3", features = ["experimental", "alloc"] }
|
||||
esp-idf-hal = "0.38.1"
|
||||
embedded-hal = "0.2.7"
|
||||
embedded-svc = "0.22.1"
|
||||
anyhow = { version = "1.0.65", features = ["backtrace"] }
|
||||
rand = "0.8.5"
|
||||
log = "0.4.17"
|
||||
bitflags = "1.3.2"
|
||||
|
||||
[build-dependencies]
|
||||
embuild = "0.29"
|
||||
anyhow = "1"
|
||||
|
||||
[package.metadata.espflash]
|
||||
partition_table = "table.csv"
|
||||
|
||||
[profile.release]
|
||||
strip = true # Automatically strip symbols from the binary.
|
||||
opt-level = "z" # Optimize for size.
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
14
factory/README.md
Normal file
14
factory/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Sphinx Key Factory App
|
||||
|
||||
The main function of this app is to write any `update.bin` files from the sd card to the flash of the ESP, and configure the ESP so that on the next boot, it boots the freshly written app.
|
||||
|
||||
## Background Reading
|
||||
|
||||
- Partition Tables: https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-guides/partition-tables.html
|
||||
- Over-the-Air Updates: https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/system/ota.html
|
||||
|
||||
## Flashing factory and sphinx-key
|
||||
|
||||
- First flash the factory app here using the usual `espflash` command, but add the `--partition-table` flag and point it to `table.csv` here. See `espflash -h` for more info.
|
||||
- Then use `esptool.py` to flash the sphinx-key binary at offset `0xc0000`.
|
||||
- Finally use this command to tell the ESP to boot the sphinx-key binary first ( there is no update to write to the ESP yet, so we don't boot the factory app ): `otatool.py switch_ota_partition --slot 0`
|
||||
5
factory/build.rs
Normal file
5
factory/build.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
// Necessary because of this issue: https://github.com/rust-lang/cargo/issues/9641
|
||||
fn main() -> anyhow::Result<()> {
|
||||
embuild::build::CfgArgs::output_propagated("ESP_IDF")?;
|
||||
embuild::build::LinkArgs::output_propagated("ESP_IDF")
|
||||
}
|
||||
2
factory/rust-toolchain.toml
Normal file
2
factory/rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
11
factory/sdkconfig.defaults
Normal file
11
factory/sdkconfig.defaults
Normal file
@@ -0,0 +1,11 @@
|
||||
# Rust often needs a bit of an extra main task stack size compared to C (the default is 3K)
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=64000
|
||||
# CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
|
||||
|
||||
# Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default).
|
||||
# This allows to use 1 ms granuality for thread sleeps (10 ms by default).
|
||||
#CONFIG_FREERTOS_HZ=1000
|
||||
|
||||
# Workaround for https://github.com/espressif/esp-idf/issues/7631
|
||||
#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n
|
||||
#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n
|
||||
41
factory/src/led.rs
Normal file
41
factory/src/led.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use embedded_hal::blocking::delay::DelayMs;
|
||||
use esp_idf_hal::delay::Ets;
|
||||
use esp_idf_hal::peripherals::Peripherals;
|
||||
use esp_idf_hal::rmt::config::TransmitConfig;
|
||||
use esp_idf_hal::rmt::{FixedLengthSignal, PinState, Pulse, Transmit};
|
||||
use std::time::Duration;
|
||||
|
||||
pub fn set_ota_led() {
|
||||
let peripherals = Peripherals::take().unwrap();
|
||||
let led = peripherals.pins.gpio8.into_output().unwrap();
|
||||
let channel = peripherals.rmt.channel0;
|
||||
let config = TransmitConfig::new().clock_divider(1);
|
||||
let mut tx = Transmit::new(led, channel, &config).unwrap();
|
||||
|
||||
let rgb = 0xffa500; // Orange
|
||||
|
||||
let ticks_hz = tx.counter_clock().unwrap();
|
||||
let t0h = Pulse::new_with_duration(ticks_hz, PinState::High, &ns(350)).unwrap();
|
||||
let t0l = Pulse::new_with_duration(ticks_hz, PinState::Low, &ns(800)).unwrap();
|
||||
let t1h = Pulse::new_with_duration(ticks_hz, PinState::High, &ns(700)).unwrap();
|
||||
let t1l = Pulse::new_with_duration(ticks_hz, PinState::Low, &ns(600)).unwrap();
|
||||
|
||||
let mut signal = FixedLengthSignal::<24>::new();
|
||||
for i in 0..24 {
|
||||
let bit = 2_u32.pow(i) & rotate_rgb(rgb) != 0;
|
||||
let (high_pulse, low_pulse) = if bit { (t1h, t1l) } else { (t0h, t0l) };
|
||||
signal.set(i as usize, &(high_pulse, low_pulse)).unwrap();
|
||||
}
|
||||
tx.start_blocking(&signal).unwrap();
|
||||
Ets.delay_ms(10u8);
|
||||
}
|
||||
|
||||
fn ns(nanos: u64) -> Duration {
|
||||
Duration::from_nanos(nanos)
|
||||
}
|
||||
|
||||
fn rotate_rgb(rgb: u32) -> u32 {
|
||||
let b_mask: u32 = 0xff;
|
||||
let blue = (rgb & b_mask) << 16;
|
||||
blue | (rgb >> 8)
|
||||
}
|
||||
37
factory/src/main.rs
Normal file
37
factory/src/main.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
mod led;
|
||||
mod ota;
|
||||
mod sdcard;
|
||||
use crate::led::set_ota_led;
|
||||
use esp_idf_sys as _; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported
|
||||
use log::{error, info, warn};
|
||||
use ota::{run_sdcard_ota_update, set_boot_main_app, UPDATE_BIN_PATH};
|
||||
use std::path::Path;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
// Temporary. Will disappear once ESP-IDF 4.4 is released, but for now it is necessary to call this function once,
|
||||
// or else some patches to the runtime implemented by esp-idf-sys might not link properly.
|
||||
esp_idf_svc::log::EspLogger::initialize_default();
|
||||
esp_idf_sys::link_patches();
|
||||
|
||||
thread::sleep(Duration::from_secs(10));
|
||||
set_ota_led();
|
||||
info!("Hello, world! Mounting sd card...");
|
||||
sdcard::mount_sd_card();
|
||||
info!("SD card mounted! Checking for update...");
|
||||
if let Ok(true) = Path::new(UPDATE_BIN_PATH).try_exists() {
|
||||
info!("Found update.bin file! Launching the update process...");
|
||||
while let Err(e) = run_sdcard_ota_update() {
|
||||
error!("OTA update failed: {}", e.to_string());
|
||||
error!("Trying again...");
|
||||
thread::sleep(Duration::from_secs(5));
|
||||
}
|
||||
info!("OTA update complete!");
|
||||
} else {
|
||||
warn!("Update file not found! Setting up main app boot...");
|
||||
set_boot_main_app();
|
||||
}
|
||||
info!("Restarting ESP, booting the main app...");
|
||||
unsafe { esp_idf_sys::esp_restart() };
|
||||
}
|
||||
51
factory/src/ota.rs
Normal file
51
factory/src/ota.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use anyhow::Result;
|
||||
use embedded_svc::io::Write;
|
||||
use embedded_svc::ota::Ota;
|
||||
use embedded_svc::ota::OtaUpdate;
|
||||
use esp_idf_svc::ota::EspOta;
|
||||
use esp_idf_sys::{esp, esp_ota_get_next_update_partition, esp_ota_set_boot_partition};
|
||||
use log::info;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::io::Read;
|
||||
use std::ptr;
|
||||
|
||||
pub const UPDATE_BIN_PATH: &str = "/sdcard/update.bin";
|
||||
const BUFFER_LEN: usize = 1024;
|
||||
|
||||
pub fn run_sdcard_ota_update() -> Result<()> {
|
||||
let f = File::open(UPDATE_BIN_PATH)?;
|
||||
let mut reader = BufReader::with_capacity(BUFFER_LEN, f);
|
||||
|
||||
let mut ota = EspOta::new()?;
|
||||
let mut ota = ota.initiate_update()?;
|
||||
|
||||
let mut buf = [0_u8; BUFFER_LEN];
|
||||
let mut read_tot: usize = 0;
|
||||
let mut write_tot: usize = 0;
|
||||
let mut i = 0;
|
||||
loop {
|
||||
let r = reader.read(&mut buf)?;
|
||||
if r == 0 {
|
||||
break;
|
||||
}
|
||||
let w = ota.write(&buf[..r])?;
|
||||
read_tot += r;
|
||||
write_tot += w;
|
||||
i += 1;
|
||||
if i % 20 == 0 {
|
||||
info!("Cumulative bytes read: {}", read_tot);
|
||||
info!("Cumulative bytes written: {}", write_tot);
|
||||
}
|
||||
}
|
||||
info!("TOTAL read: {}", read_tot);
|
||||
info!("TOTAL write: {}", write_tot);
|
||||
ota.complete()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_boot_main_app() {
|
||||
let partition = unsafe { esp_ota_get_next_update_partition(ptr::null()) };
|
||||
esp!(unsafe { esp_ota_set_boot_partition(partition) })
|
||||
.expect("Couldn't set next boot partition...");
|
||||
}
|
||||
138
factory/src/sdcard.rs
Normal file
138
factory/src/sdcard.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
use bitflags::bitflags;
|
||||
use esp_idf_sys::c_types::c_char;
|
||||
use esp_idf_sys::{
|
||||
esp, esp_vfs_fat_sdmmc_mount_config_t, esp_vfs_fat_sdspi_mount, gpio_num_t, sdmmc_card_t,
|
||||
sdmmc_host_t, sdspi_device_config_t, spi_bus_config_t, spi_bus_initialize, spi_host_device_t,
|
||||
spi_host_device_t_SPI2_HOST,
|
||||
};
|
||||
use std::ptr;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
const C_MOUNT_POINT: &'static [u8] = b"/sdcard\0";
|
||||
|
||||
const SPI_HOST_SLOT: spi_host_device_t = spi_host_device_t_SPI2_HOST;
|
||||
const SPI_GPIO_MOSI: gpio_num_t = 7;
|
||||
const SPI_GPIO_CLK: gpio_num_t = 6;
|
||||
const SPI_GPIO_MISO: gpio_num_t = 2;
|
||||
const SPI_GPIO_CS: gpio_num_t = 10;
|
||||
|
||||
bitflags! {
|
||||
struct SDMMCHostFlag: u32 {
|
||||
/// host supports 1-line SD and MMC protocol
|
||||
const BIT1 = 1 << 0;
|
||||
/// host supports 4-line SD and MMC protocol
|
||||
const BIT4 = 1 << 1;
|
||||
/// host supports 8-line MMC protocol
|
||||
const BIT8 = 1 << 2;
|
||||
/// host supports SPI protocol
|
||||
const SPI = 1 << 3;
|
||||
/// host supports DDR mode for SD/MMC
|
||||
const DDR = 1 << 4;
|
||||
/// host `deinit` function called with the slot argument
|
||||
const DEINIT_ARG = 1 << 5;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
enum SDMMCFreq {
|
||||
/// SD/MMC Default speed (limited by clock divider)
|
||||
Default = 20000,
|
||||
/// SD High speed (limited by clock divider)
|
||||
HighSPeed = 40000,
|
||||
/// SD/MMC probing speed
|
||||
Probing = 400,
|
||||
/// MMC 52MHz speed
|
||||
_52M = 52000,
|
||||
/// MMC 26MHz speed
|
||||
_26M = 26000,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn mount_sd_card() {
|
||||
while let Err(e) = setup() {
|
||||
println!("Failed to mount sd card. Make sure it is connected, trying again...");
|
||||
thread::sleep(Duration::from_secs(5));
|
||||
}
|
||||
}
|
||||
|
||||
fn setup() -> anyhow::Result<()> {
|
||||
let mount_config = esp_vfs_fat_sdmmc_mount_config_t {
|
||||
format_if_mount_failed: false,
|
||||
max_files: 5,
|
||||
allocation_unit_size: 16 * 1024,
|
||||
};
|
||||
|
||||
let mut card: *mut sdmmc_card_t = ptr::null_mut();
|
||||
|
||||
let bus_cfg = spi_bus_config_t {
|
||||
__bindgen_anon_1: esp_idf_sys::spi_bus_config_t__bindgen_ty_1 {
|
||||
mosi_io_num: SPI_GPIO_MOSI,
|
||||
},
|
||||
__bindgen_anon_2: esp_idf_sys::spi_bus_config_t__bindgen_ty_2 {
|
||||
miso_io_num: SPI_GPIO_MISO,
|
||||
},
|
||||
sclk_io_num: SPI_GPIO_CLK,
|
||||
__bindgen_anon_3: esp_idf_sys::spi_bus_config_t__bindgen_ty_3 { quadwp_io_num: -1 },
|
||||
__bindgen_anon_4: esp_idf_sys::spi_bus_config_t__bindgen_ty_4 { quadhd_io_num: -1 },
|
||||
data4_io_num: -1,
|
||||
data5_io_num: -1,
|
||||
data6_io_num: -1,
|
||||
data7_io_num: -1,
|
||||
max_transfer_sz: 4000,
|
||||
flags: 0,
|
||||
intr_flags: 0,
|
||||
};
|
||||
|
||||
if let Err(error) = esp!(unsafe {
|
||||
spi_bus_initialize(
|
||||
SPI_HOST_SLOT as u32,
|
||||
&bus_cfg,
|
||||
esp_idf_sys::spi_common_dma_t_SPI_DMA_CH_AUTO,
|
||||
)
|
||||
}) {
|
||||
if error.code() != 259 {
|
||||
return Err(anyhow::Error::new(error));
|
||||
}
|
||||
}
|
||||
|
||||
println!("Initialized SPI BUS!");
|
||||
|
||||
let slot_config = sdspi_device_config_t {
|
||||
host_id: SPI_HOST_SLOT,
|
||||
gpio_cs: SPI_GPIO_CS,
|
||||
gpio_cd: -1,
|
||||
gpio_wp: -1,
|
||||
gpio_int: -1,
|
||||
};
|
||||
|
||||
let host = sdmmc_host_t {
|
||||
flags: (SDMMCHostFlag::SPI | SDMMCHostFlag::DEINIT_ARG).bits, //SDMMC_HOST_FLAG_SPI | SDMMC_HOST_FLAG_DEINIT_ARG,
|
||||
slot: SPI_HOST_SLOT as i32,
|
||||
max_freq_khz: SDMMCFreq::Default as i32, //SDMMC_FREQ_DEFAULT,
|
||||
io_voltage: 3.3f32,
|
||||
init: Some(esp_idf_sys::sdspi_host_init),
|
||||
set_bus_width: None,
|
||||
get_bus_width: None,
|
||||
set_bus_ddr_mode: None,
|
||||
set_card_clk: Some(esp_idf_sys::sdspi_host_set_card_clk),
|
||||
do_transaction: Some(esp_idf_sys::sdspi_host_do_transaction),
|
||||
__bindgen_anon_1: esp_idf_sys::sdmmc_host_t__bindgen_ty_1 {
|
||||
deinit_p: Some(esp_idf_sys::sdspi_host_remove_device),
|
||||
},
|
||||
io_int_enable: Some(esp_idf_sys::sdspi_host_io_int_enable),
|
||||
io_int_wait: Some(esp_idf_sys::sdspi_host_io_int_wait),
|
||||
command_timeout_ms: 0,
|
||||
};
|
||||
|
||||
esp!(unsafe {
|
||||
esp_vfs_fat_sdspi_mount(
|
||||
C_MOUNT_POINT.as_ptr() as *const c_char,
|
||||
&host,
|
||||
&slot_config,
|
||||
&mount_config,
|
||||
&mut card as *mut *mut sdmmc_card_t,
|
||||
)
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
7
factory/table.csv
Normal file
7
factory/table.csv
Normal file
@@ -0,0 +1,7 @@
|
||||
# ESP-IDF Partition Table
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x4000,
|
||||
otadata, data, ota, 0xd000, 0x2000,
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
factory, app, factory, 0x10000, 448K,
|
||||
ota_0, app, ota_0, 0x80000, 3584K,
|
||||
|
@@ -2,7 +2,9 @@ use anyhow::Result;
|
||||
use sphinx_auther::nonce;
|
||||
use sphinx_auther::secp256k1::{PublicKey, SecretKey};
|
||||
use sphinx_auther::token::Token;
|
||||
pub use sphinx_glyph::types::{Config, ControlMessage, ControlResponse, Interval, Policy};
|
||||
pub use sphinx_glyph::types::{
|
||||
Config, ControlMessage, ControlResponse, Interval, OtaParams, Policy,
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
// u64 is the nonce. Each signature must have a higher nonce
|
||||
@@ -56,7 +58,7 @@ impl Controller {
|
||||
Ok(rmp_serde::from_slice(input)?)
|
||||
}
|
||||
// return the OG message for further processing
|
||||
pub fn handle(&mut self, input: &[u8]) -> anyhow::Result<(Vec<u8>, ControlMessage)> {
|
||||
pub fn handle(&mut self, input: &[u8]) -> anyhow::Result<(ControlMessage, ControlResponse)> {
|
||||
let msg_nonce = self.parse_msg_no_nonce(input)?;
|
||||
let msg = msg_nonce.0;
|
||||
// nonce must be higher each time
|
||||
@@ -106,12 +108,11 @@ impl Controller {
|
||||
ControlResponse::AllowlistUpdated(na)
|
||||
}
|
||||
ControlMessage::Ota(params) => {
|
||||
// ...
|
||||
// same as above comments, actually done in core/events.rs
|
||||
ControlResponse::OtaConfirm(params)
|
||||
}
|
||||
};
|
||||
let response = self.build_response(res)?;
|
||||
Ok((response, msg))
|
||||
Ok((msg, res))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
38
sphinx-key/Cargo.lock
generated
38
sphinx-key/Cargo.lock
generated
@@ -280,12 +280,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cargo_toml"
|
||||
version = "0.11.8"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e72c3ff59e3b7d24630206bb63a73af65da4ed5df1f76ee84dfafb9fee2ba60e"
|
||||
checksum = "ee685beed1fe2ab3cb9eb95d65727413b5e27f2b34014a3ea9c92053f8c230fc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"toml",
|
||||
]
|
||||
|
||||
@@ -663,6 +662,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"embedded-io",
|
||||
"enumset",
|
||||
"futures",
|
||||
"heapless",
|
||||
"log",
|
||||
"no-std-net",
|
||||
@@ -695,9 +695,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "embuild"
|
||||
version = "0.30.3"
|
||||
version = "0.30.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01f030081cf69373a6c4b811934a81c7c9d3a11066ef22b564327a33f9991125"
|
||||
checksum = "c6ca66d283ba92dc33ce461ba27bb5085a769dd740bad3f66e60e70926b34d05"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bindgen",
|
||||
@@ -794,6 +794,7 @@ dependencies = [
|
||||
"esp-idf-sys",
|
||||
"heapless",
|
||||
"log",
|
||||
"uncased",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -805,7 +806,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"bindgen",
|
||||
"cargo_metadata",
|
||||
"embuild 0.30.3",
|
||||
"embuild 0.30.4",
|
||||
"envy",
|
||||
"libc",
|
||||
"regex",
|
||||
@@ -1488,9 +1489,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.43"
|
||||
version = "1.0.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
|
||||
checksum = "7bd7356a8122b6c4a24a82b278680c73357984ca2fc79a0f9fa6dea7dced7c58"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -2234,18 +2235,18 @@ checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.35"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85"
|
||||
checksum = "0a99cb8c4b9a8ef0e7907cd3b617cc8dc04d571c4e73c8ae403d80ac160bb122"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.35"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783"
|
||||
checksum = "3a891860d3c8d66fec8e73ddb3765f90082374dbaaa833407b904a94f1a7eb43"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2351,6 +2352,15 @@ version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||
|
||||
[[package]]
|
||||
name = "uncased"
|
||||
version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.8"
|
||||
@@ -2579,9 +2589,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.22.4"
|
||||
version = "0.22.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf"
|
||||
checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be"
|
||||
dependencies = [
|
||||
"webpki",
|
||||
]
|
||||
|
||||
@@ -25,7 +25,7 @@ esp-idf-sys = { version = "0.31.6", features = ["binstart"] }
|
||||
sphinx-key-signer = { path = "../signer", optional = true }
|
||||
sphinx-crypter = { git = "https://github.com/stakwork/sphinx-rs.git" }
|
||||
embedded-svc = "0.22.1"
|
||||
esp-idf-svc = "0.42.1"
|
||||
esp-idf-svc = { version = "0.42.1", features = ["experimental", "alloc"] }
|
||||
esp-idf-hal = "0.38.0"
|
||||
embedded-hal = "=1.0.0-alpha.8"
|
||||
anyhow = {version = "1", features = ["backtrace"]}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Rust often needs a bit of an extra main task stack size compared to C (the default is 3K)
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=64000
|
||||
#CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
|
||||
CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=10000
|
||||
# CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
|
||||
|
||||
# Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default).
|
||||
# This allows to use 1 ms granuality for thread sleeps (10 ms by default).
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use crate::conn::mqtt::QOS;
|
||||
use crate::ota::{update_sphinx_key, validate_ota_message};
|
||||
|
||||
use sphinx_key_signer::control::{Config, ControlMessage, ControlResponse, Controller, Policy};
|
||||
use sphinx_key_signer::lightning_signer::bitcoin::Network;
|
||||
use sphinx_key_signer::vls_protocol::model::PubKey;
|
||||
use sphinx_key_signer::{self, make_init_msg, topics, InitResponse, ParserError, RootHandler};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
|
||||
use embedded_svc::httpd::Result;
|
||||
use embedded_svc::mqtt::client::utils::ConnState;
|
||||
@@ -32,6 +34,7 @@ pub enum Status {
|
||||
ConnectingToMqtt,
|
||||
Connected,
|
||||
Signing,
|
||||
Ota,
|
||||
}
|
||||
|
||||
pub const ROOT_STORE: &str = "/sdcard/store";
|
||||
@@ -114,7 +117,11 @@ pub fn make_event_loop(
|
||||
Event::Control(ref msg_bytes) => {
|
||||
log::info!("GOT A CONTROL MSG");
|
||||
let cres = ctrlr.handle(msg_bytes);
|
||||
if let Some(res_data) = handle_control_response(&root_handler, cres, network) {
|
||||
if let Some(res) =
|
||||
handle_control_response(&root_handler, cres, network, led_tx.clone())
|
||||
{
|
||||
let res_data =
|
||||
rmp_serde::to_vec(&res).expect("could not publish control response");
|
||||
mqtt.publish(topics::CONTROL_RETURN, QOS, false, &res_data)
|
||||
.expect("could not publish control response");
|
||||
}
|
||||
@@ -127,43 +134,66 @@ pub fn make_event_loop(
|
||||
|
||||
fn handle_control_response(
|
||||
root_handler: &RootHandler,
|
||||
cres: anyhow::Result<(Vec<u8>, ControlMessage)>,
|
||||
cres: anyhow::Result<(ControlMessage, ControlResponse)>,
|
||||
network: Network,
|
||||
) -> Option<Vec<u8>> {
|
||||
led_tx: mpsc::Sender<Status>,
|
||||
) -> Option<ControlResponse> {
|
||||
match cres {
|
||||
Ok((mut response, parsed_msg)) => {
|
||||
Ok((control_msg, mut control_res)) => {
|
||||
// the following msg types require other actions besides Flash persistence
|
||||
match parsed_msg {
|
||||
match control_msg {
|
||||
ControlMessage::UpdatePolicy(new_policy) => {
|
||||
if let Err(e) =
|
||||
sphinx_key_signer::set_policy(&root_handler, network, new_policy)
|
||||
{
|
||||
log::error!("set policy failed {:?}", e);
|
||||
control_res = ControlResponse::Error(format!("set policy failed {:?}", e))
|
||||
}
|
||||
}
|
||||
ControlMessage::UpdateAllowlist(al) => {
|
||||
if let Err(e) = sphinx_key_signer::set_allowlist(&root_handler, &al) {
|
||||
log::error!("set allowlist failed {:?}", e);
|
||||
control_res =
|
||||
ControlResponse::Error(format!("set allowlist failed {:?}", e))
|
||||
}
|
||||
}
|
||||
// overwrite the real Allowlist response, loaded from Node
|
||||
ControlMessage::QueryAllowlist => {
|
||||
if let Ok(al) = sphinx_key_signer::get_allowlist(&root_handler) {
|
||||
response = rmp_serde::to_vec(&ControlResponse::AllowlistCurrent(al))
|
||||
.expect("couldnt build ControlResponse::AllowlistCurrent");
|
||||
match sphinx_key_signer::get_allowlist(&root_handler) {
|
||||
Ok(al) => control_res = ControlResponse::AllowlistCurrent(al),
|
||||
Err(e) => {
|
||||
log::error!("read allowlist failed {:?}", e);
|
||||
control_res =
|
||||
ControlResponse::Error(format!("read allowlist failed {:?}", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
ControlMessage::Ota(params) => {
|
||||
if let Err(e) = validate_ota_message(params.clone()) {
|
||||
log::error!("OTA update cannot launch {:?}", e.to_string());
|
||||
control_res =
|
||||
ControlResponse::Error(format!("OTA update cannot launch {:?}", e))
|
||||
} else {
|
||||
log::error!("read allowlist failed");
|
||||
thread::spawn(move || {
|
||||
led_tx.send(Status::Ota).unwrap();
|
||||
if let Err(e) = update_sphinx_key(params, led_tx) {
|
||||
log::error!("OTA update failed {:?}", e.to_string());
|
||||
} else {
|
||||
log::info!("OTA flow complete, restarting esp...");
|
||||
unsafe { esp_idf_sys::esp_restart() };
|
||||
}
|
||||
});
|
||||
log::info!("OTA update launched...");
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
Some(response)
|
||||
Some(control_res)
|
||||
}
|
||||
Err(e) => {
|
||||
let response = rmp_serde::to_vec(&ControlResponse::Error(e.to_string()))
|
||||
.expect("couldnt build ControlResponse::Error");
|
||||
let control_res = ControlResponse::Error(e.to_string());
|
||||
log::warn!("error parsing ctrl msg {:?}", e);
|
||||
Some(response)
|
||||
Some(control_res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#![feature(once_cell)]
|
||||
mod conn;
|
||||
mod core;
|
||||
mod ota;
|
||||
mod periph;
|
||||
|
||||
use crate::core::control::{controller_from_seed, FlashPersister};
|
||||
|
||||
123
sphinx-key/src/ota.rs
Normal file
123
sphinx-key/src/ota.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
use crate::core::events::Status;
|
||||
use anyhow::{anyhow, Result};
|
||||
use embedded_svc::http::client::Client;
|
||||
use embedded_svc::http::client::Request;
|
||||
use embedded_svc::http::client::Response;
|
||||
use embedded_svc::http::Status as HttpStatus;
|
||||
use embedded_svc::io::Read;
|
||||
use embedded_svc::ota::Ota;
|
||||
use esp_idf_svc::http::client::EspHttpClient;
|
||||
use esp_idf_svc::http::client::EspHttpClientConfiguration;
|
||||
use esp_idf_svc::http::client::FollowRedirectsPolicy::FollowNone;
|
||||
use esp_idf_svc::ota::EspOta;
|
||||
use log::{error, info};
|
||||
use sphinx_key_signer::control::OtaParams;
|
||||
use std::fs::{remove_file, File};
|
||||
use std::io::BufWriter;
|
||||
use std::io::Write;
|
||||
use std::sync::mpsc;
|
||||
|
||||
const BUFFER_LEN: usize = 3072;
|
||||
const UPDATE_BIN_PATH: &str = "/sdcard/update.bin";
|
||||
|
||||
fn factory_reset() -> Result<()> {
|
||||
let mut ota = EspOta::new()?;
|
||||
if ota.is_factory_reset_supported()? {
|
||||
info!("Factory reset supported, attempting reset...");
|
||||
ota.factory_reset()?;
|
||||
Ok(())
|
||||
} else {
|
||||
error!("FACTORY RESET CURRENTLY NOT SUPPORTED!");
|
||||
error!("Only wrote the update binary to the sdcard");
|
||||
Err(anyhow!("Factory reset not supported"))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_update(params: OtaParams, led_tx: mpsc::Sender<Status>) -> Result<()> {
|
||||
let configuration = EspHttpClientConfiguration {
|
||||
buffer_size: Some(BUFFER_LEN),
|
||||
buffer_size_tx: Some(BUFFER_LEN / 3),
|
||||
follow_redirects_policy: FollowNone,
|
||||
use_global_ca_store: true,
|
||||
crt_bundle_attach: None,
|
||||
};
|
||||
let mut client = EspHttpClient::new(&configuration)?;
|
||||
let full_url = params_to_url(params);
|
||||
let mut response = client.get(&full_url)?.submit()?;
|
||||
let mut reader = response.reader();
|
||||
|
||||
let _ = remove_file(UPDATE_BIN_PATH);
|
||||
let file = File::create(UPDATE_BIN_PATH)?;
|
||||
let mut writer = BufWriter::new(file);
|
||||
|
||||
let mut buf = [0_u8; BUFFER_LEN];
|
||||
let mut read_tot: usize = 0;
|
||||
let mut write_tot: usize = 0;
|
||||
let mut i = 0;
|
||||
loop {
|
||||
let r = reader.read(&mut buf)?;
|
||||
if r == 0 {
|
||||
break;
|
||||
}
|
||||
let w = writer.write(&buf[..r])?;
|
||||
read_tot += r;
|
||||
write_tot += w;
|
||||
i += 1;
|
||||
if i % 20 == 0 {
|
||||
led_tx.send(Status::Ota).unwrap();
|
||||
info!("Cumulative bytes read: {}", read_tot);
|
||||
info!("Cumulative bytes written: {}", write_tot);
|
||||
}
|
||||
}
|
||||
info!("TOTAL read: {}", read_tot);
|
||||
info!("TOTAL written: {}", write_tot);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_sphinx_key(params: OtaParams, led_tx: mpsc::Sender<Status>) -> Result<()> {
|
||||
info!("Getting the update...");
|
||||
get_update(params, led_tx)?;
|
||||
info!("Update written to sd card, performing factory reset");
|
||||
factory_reset()?;
|
||||
info!("Factory reset completed!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_ota_message(params: OtaParams) -> Result<()> {
|
||||
let configuration = EspHttpClientConfiguration {
|
||||
buffer_size: Some(BUFFER_LEN / 3),
|
||||
buffer_size_tx: Some(BUFFER_LEN / 3),
|
||||
follow_redirects_policy: FollowNone,
|
||||
use_global_ca_store: true,
|
||||
crt_bundle_attach: None,
|
||||
};
|
||||
let mut client = EspHttpClient::new(&configuration)?;
|
||||
let full_url = params_to_url(params);
|
||||
info!("Pinging this url for an update: {}", full_url);
|
||||
let response = client.get(&full_url)?.submit()?;
|
||||
let status = response.status();
|
||||
if status == 200 {
|
||||
info!("Got valid OTA url! Proceeding with OTA update...");
|
||||
Ok(())
|
||||
} else if status == 404 {
|
||||
error!("got 404, update not found on server, make sure the url and version are correct");
|
||||
Err(anyhow!(
|
||||
"got 404, update not found on server, make sure the url and version are correct"
|
||||
))
|
||||
} else {
|
||||
error!(
|
||||
"got {} code when fetching update, something is wrong",
|
||||
&status.to_string()
|
||||
);
|
||||
Err(anyhow!(
|
||||
"got {} code when fetching update, something is wrong",
|
||||
&status.to_string()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn params_to_url(params: OtaParams) -> String {
|
||||
let mut url = params.url.clone();
|
||||
url.push_str(¶ms.version.to_string());
|
||||
url
|
||||
}
|
||||
@@ -19,15 +19,16 @@ pub struct Led {
|
||||
|
||||
fn states() -> BTreeMap<Status, (Color, Time)> {
|
||||
let mut s = BTreeMap::new();
|
||||
s.insert(Status::Starting, (0x000001, 100));
|
||||
s.insert(Status::MountingSDCard, (0x000102, 100));
|
||||
s.insert(Status::SyncingTime, (0x000122, 100));
|
||||
s.insert(Status::WifiAccessPoint, (0x000100, 100));
|
||||
s.insert(Status::Configuring, (0x010000, 20));
|
||||
s.insert(Status::ConnectingToWifi, (0x010100, 350));
|
||||
s.insert(Status::ConnectingToMqtt, (0x010001, 100));
|
||||
s.insert(Status::Connected, (0x000101, 400));
|
||||
s.insert(Status::Signing, (0x111111, 100));
|
||||
s.insert(Status::Starting, (0x000001, 100)); // Blue
|
||||
s.insert(Status::MountingSDCard, (0x000102, 100)); // Cyan
|
||||
s.insert(Status::SyncingTime, (0x000122, 100)); // Cyan
|
||||
s.insert(Status::WifiAccessPoint, (0x000100, 100)); // Green
|
||||
s.insert(Status::Configuring, (0x010000, 20)); // Red
|
||||
s.insert(Status::ConnectingToWifi, (0x010100, 350)); // Yellow
|
||||
s.insert(Status::ConnectingToMqtt, (0x010001, 100)); // Purple
|
||||
s.insert(Status::Connected, (0x000101, 400)); // Cyan
|
||||
s.insert(Status::Signing, (0x111111, 100)); // White
|
||||
s.insert(Status::Ota, (0xffa500, 100)); // Orange
|
||||
s
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ serde_json = "1.0"
|
||||
urlencoding = "2.1.0"
|
||||
dotenv = "0.15.0"
|
||||
rocket = "0.5.0-rc.2"
|
||||
rmp-serde = "1.1.0"
|
||||
|
||||
[[bin]]
|
||||
name = "config"
|
||||
@@ -36,4 +37,4 @@ path = "src/server.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "ctrl"
|
||||
path = "src/ctrl.rs"
|
||||
path = "src/ctrl.rs"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use dotenv::dotenv;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sphinx_key_parser::control::{ControlMessage, Controller};
|
||||
use sphinx_key_parser::control::{ControlMessage, Controller, OtaParams};
|
||||
use sphinx_key_signer::lightning_signer::bitcoin::Network;
|
||||
use std::env;
|
||||
use std::time::Duration;
|
||||
@@ -25,6 +25,15 @@ async fn main() -> anyhow::Result<()> {
|
||||
let seed = hex::decode(seed_string).expect("yo");
|
||||
let mut ctrl = controller_from_seed(&Network::Regtest, &seed, nonce);
|
||||
|
||||
let version_string: String = env::var("VERSION").unwrap_or("0".to_string());
|
||||
let version: u64 = version_string.parse::<u64>().expect("failed to parse version");
|
||||
let ota_url: String = env::var("OTA_URL").unwrap_or("http://192.168.1.10/sphinx-update-".to_string());
|
||||
let control_message = ControlMessage::Ota(OtaParams {
|
||||
version: version,
|
||||
url: ota_url
|
||||
});
|
||||
|
||||
//let msg = ctrl.build_msg(control_message)?;
|
||||
let msg = ctrl.build_msg(ControlMessage::Nonce)?;
|
||||
let msg_hex = hex::encode(&msg);
|
||||
|
||||
|
||||
@@ -118,13 +118,14 @@ async fn run_main(mut eventloop: EventLoop, client: &AsyncClient, mut ctrlr: Con
|
||||
}
|
||||
topics::CONTROL => {
|
||||
match ctrlr.handle(&msg_bytes) {
|
||||
Ok((response, _new_policy)) => {
|
||||
Ok((_msg, res)) => {
|
||||
let res_data = rmp_serde::to_vec(&res).expect("could not build control response");
|
||||
client
|
||||
.publish(
|
||||
topics::CONTROL_RETURN,
|
||||
QoS::AtMostOnce,
|
||||
false,
|
||||
response,
|
||||
res_data,
|
||||
)
|
||||
.await
|
||||
.expect("could not mqtt publish");
|
||||
@@ -175,13 +176,14 @@ async fn run_test(mut eventloop: EventLoop, client: &AsyncClient, mut ctrlr: Con
|
||||
}
|
||||
topics::CONTROL => {
|
||||
match ctrlr.handle(&msg_bytes) {
|
||||
Ok((response, _new_policy)) => {
|
||||
Ok((_msg, res)) => {
|
||||
let res_data = rmp_serde::to_vec(&res).expect("could not build control response");
|
||||
client
|
||||
.publish(
|
||||
topics::CONTROL_RETURN,
|
||||
QoS::AtMostOnce,
|
||||
false,
|
||||
response,
|
||||
res_data,
|
||||
)
|
||||
.await
|
||||
.expect("could not mqtt publish");
|
||||
|
||||
Reference in New Issue
Block a user