use std::fmt::Debug;
use elements::hex::ToHex;
use lwk_common::Network;
use rand::RngCore;
use serde::{Deserialize, Serialize};
use serde_cbor::Value;
use crate::{
error::ErrorDetails,
get_receive_address::GetReceiveAddressParams,
register_multisig::{GetRegisteredMultisigParams, RegisterMultisigParams},
sign_liquid_tx::{SignLiquidTxParams, TxInputParams},
};
#[derive(Debug, Serialize)]
pub struct FullRequest {
pub id: String,
pub method: String,
pub params: Request,
}
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum Request {
Ping,
Logout,
GetVersionInfo,
SetEpoch(EpochParams),
AddEntropy(EntropyParams),
AuthUser(AuthUserParams),
UpdatePinserver(UpdatePinserverParams),
GetXpub(GetXpubParams),
GetReceiveAddress(GetReceiveAddressParams),
GetMasterBlindingKey(GetMasterBlindingKeyParams),
SignMessage(SignMessageParams),
GetSignature(GetSignatureParams),
SignLiquidTx(SignLiquidTxParams),
TxInput(TxInputParams),
DebugSetMnemonic(DebugSetMnemonicParams),
RegisterMultisig(RegisterMultisigParams),
GetRegisteredMultisigs,
GetRegisteredMultisig(GetRegisteredMultisigParams),
Generic(GenericMethod),
}
#[derive(Debug, Serialize)]
pub struct GenericMethod {
#[serde(skip)]
pub(crate) method: String,
#[serde(flatten)]
pub(crate) params: serde_cbor::Value,
}
impl std::fmt::Display for Request {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Request::Ping => write!(f, "ping"),
Request::Logout => write!(f, "logout"),
Request::GetVersionInfo => write!(f, "get_version_info"),
Request::SetEpoch(_) => write!(f, "set_epoch"),
Request::AddEntropy(_) => write!(f, "add_entropy"),
Request::AuthUser(_) => write!(f, "auth_user"),
Request::UpdatePinserver(_) => write!(f, "update_pinserver"),
Request::GetXpub(_) => write!(f, "get_xpub"),
Request::GetReceiveAddress(_) => write!(f, "get_receive_address"),
Request::GetMasterBlindingKey(_) => write!(f, "get_master_blinding_key"),
Request::SignMessage(_) => write!(f, "sign_message"),
Request::GetSignature(_) => write!(f, "get_signature"),
Request::SignLiquidTx(_) => write!(f, "sign_liquid_tx"),
Request::TxInput(_) => write!(f, "tx_input"),
Request::DebugSetMnemonic(_) => write!(f, "debug_set_mnemonic"),
Request::RegisterMultisig(_) => write!(f, "register_multisig"),
Request::GetRegisteredMultisigs => write!(f, "get_registered_multisigs"),
Request::GetRegisteredMultisig(_) => write!(f, "get_registered_multisig"),
Request::Generic(g) => write!(f, "{0}", g.method),
}
}
}
impl Request {
pub fn network(&self) -> Option<Network> {
match self {
Request::GetXpub(e) => Some(e.network),
Request::GetReceiveAddress(e) => Some(e.network),
Request::SignLiquidTx(e) => Some(e.network),
Request::RegisterMultisig(e) => Some(e.network),
_ => None,
}
}
}
impl Request {
pub fn serialize(self) -> Result<Vec<u8>, crate::Error> {
let mut rng = rand::thread_rng();
let id = rng.next_u32().to_string();
let method = self.to_string();
let req = FullRequest {
id,
method,
params: self,
};
let mut buf = Vec::new();
serde_cbor::to_writer(&mut buf, &req)?;
log::debug!(
"\n--->\t{:#?}\n\t({} bytes) {}",
&req,
buf.len(),
&hex::encode(&buf),
);
Ok(buf)
}
}
#[derive(Debug, Serialize)]
pub struct AuthUserParams {
pub network: Network,
pub epoch: u64,
}
impl AuthUserParams {
pub fn new(network: Network) -> Self {
let epoch = web_time::SystemTime::now()
.duration_since(web_time::SystemTime::UNIX_EPOCH)
.map(|e| e.as_secs())
.unwrap_or(0);
AuthUserParams { network, epoch }
}
}
#[derive(Debug, Serialize)]
pub struct EpochParams {
pub epoch: u64,
}
#[derive(Debug, Serialize)]
pub struct EntropyParams {
#[serde(with = "serde_bytes")]
pub entropy: Vec<u8>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct GetXpubParams {
pub network: Network,
pub path: Vec<u32>,
}
#[derive(Deserialize, Serialize)]
pub struct SignMessageParams {
pub message: String,
pub path: Vec<u32>,
#[serde(with = "serde_bytes")]
pub ae_host_commitment: Vec<u8>,
}
impl Debug for SignMessageParams {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SignMessageParams")
.field("message", &self.message)
.field("path", &self.path)
.field("ae_host_commitment", &self.ae_host_commitment.to_hex())
.finish()
}
}
#[derive(Deserialize, Serialize)]
pub struct GetSignatureParams {
#[serde(with = "serde_bytes")]
pub ae_host_entropy: Vec<u8>,
}
impl Debug for GetSignatureParams {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GetSignatureParams")
.field("ae_host_entropy", &self.ae_host_entropy.to_hex())
.finish()
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct HandshakeComplete {
pub encrypted_data: String,
pub hmac_encrypted_data: String,
pub ske: String,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct DebugSetMnemonicParams {
pub mnemonic: String,
pub passphrase: Option<String>,
pub temporary_wallet: bool,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Response<T> {
pub id: String,
pub result: Option<T>,
pub error: Option<ErrorDetails>,
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub struct VersionInfoResult {
pub jade_version: String,
pub jade_ota_max_chunk: u32,
pub jade_config: String,
pub board_type: String,
pub jade_features: String,
pub idf_version: String,
pub chip_features: String,
pub efusemac: String,
pub battery_status: u8,
pub jade_state: JadeState,
pub jade_networks: String,
pub jade_has_pin: bool,
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "UPPERCASE")]
pub enum JadeState {
Uninit,
Unsaved,
Locked,
Ready,
Temp,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum IsAuthResult {
AlreadyAuth(bool),
AuthResult(AuthResult),
}
#[derive(Debug, Deserialize, Serialize)]
pub struct AuthResult {
http_request: HttpRequest,
}
impl AuthResult {
pub fn url(&self, use_tor: bool) -> Option<&str> {
select_url(&self.http_request.params.urls, use_tor)
}
pub fn data(&self) -> &Value {
&self.http_request.params.data
}
pub fn on_reply(&self) -> &str {
&self.http_request.on_reply
}
}
fn select_url(urls: &[String], use_tor: bool) -> Option<&str> {
let (onion, clear): (Vec<_>, Vec<_>) = urls.iter().partition(|e| e.ends_with(".onion"));
if use_tor {
onion.first()
} else {
let https = clear.iter().find(|e| e.starts_with("https://"));
match https {
Some(url) => Some(url),
None => clear.first(),
}
}
.map(|e| e.as_str())
}
#[derive(Debug, Deserialize, Serialize)]
pub struct HttpRequest {
params: HttpParams,
#[serde(rename = "on-reply")]
on_reply: String,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct HttpParams {
urls: Vec<String>,
method: String,
accept: String,
data: Value,
}
#[derive(Deserialize, Serialize)]
pub struct UpdatePinserverParams {
pub reset_details: bool,
pub reset_certificate: bool,
#[serde(rename = "urlA")]
pub url_a: String,
#[serde(rename = "urlB")]
pub url_b: String,
#[serde(with = "serde_bytes")]
pub pubkey: Vec<u8>,
pub certificate: String,
}
impl Debug for UpdatePinserverParams {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UpdatePinserverParams")
.field("reset_details", &self.reset_details)
.field("reset_certificate", &self.reset_certificate)
.field("url_a", &self.url_a)
.field("url_b", &self.url_b)
.field("pubkey", &self.pubkey.to_hex())
.field("certificate", &self.certificate)
.finish()
}
}
#[derive(Debug, Serialize, Clone)]
pub struct GetMasterBlindingKeyParams {
pub only_if_silent: bool,
}
#[cfg(test)]
mod test {
use crate::protocol::select_url;
#[test]
fn serialize_empty() {
let a = super::Request::Ping;
let s = serde_json::to_string(&a).unwrap();
assert_eq!(s, "null");
}
#[test]
fn test_select_url() {
let http = "http://ciao.it";
let https = "https://ciao.it";
let onion = "http://mrrxtq6tjpbnbm7vh5jt6mpjctn7ggyfy5wegvbeff3x7jrznqawlmid.onion";
let urls = [http.to_owned(), https.to_owned()];
assert_eq!(select_url(&urls, false), Some(https));
let urls = [onion.to_owned()];
assert_eq!(select_url(&urls, false), None);
let urls = [onion.to_owned(), https.to_owned()];
assert_eq!(select_url(&urls, false), Some(https));
let urls = [onion.to_owned(), http.to_owned()];
assert_eq!(select_url(&urls, false), Some(http));
let urls = [https.to_owned(), onion.to_owned()];
assert_eq!(select_url(&urls, true), Some(onion));
}
}