use elements::{
bitcoin::{
self,
bip32::{ChildNumber, DerivationPath, Fingerprint, Xpub},
sign_message::MessageSignature,
XKeyIdentifier,
},
pset::PartiallySignedTransaction,
};
use elements_miniscript::slip77::MasterBlindingKey;
use crate::descriptor::Bip;
fn hardened(index: u32) -> Result<ChildNumber, bitcoin::bip32::Error> {
ChildNumber::from_hardened_idx(index)
}
pub trait Signer {
type Error: std::fmt::Debug;
fn sign(&self, pset: &mut PartiallySignedTransaction) -> Result<u32, Self::Error>;
fn derive_xpub(&self, path: &DerivationPath) -> Result<Xpub, Self::Error>;
fn slip77_master_blinding_key(&self) -> Result<MasterBlindingKey, Self::Error>;
fn xpub(&self) -> Result<Xpub, Self::Error> {
self.derive_xpub(&DerivationPath::master())
}
fn identifier(&self) -> Result<XKeyIdentifier, Self::Error> {
Ok(self.xpub()?.identifier())
}
fn fingerprint(&self) -> Result<Fingerprint, Self::Error> {
Ok(self.xpub()?.fingerprint())
}
fn keyorigin_xpub(&self, bip: Bip, is_mainnet: bool) -> Result<String, Self::Error> {
let coin_type = if is_mainnet { 1776 } else { 1 };
let purpose = match bip {
Bip::Bip84 => 84,
Bip::Bip49 => 49,
Bip::Bip87 => 87,
};
let account = 0;
let path = [purpose, coin_type, account];
let derivation_path =
DerivationPath::from_iter(path.map(|index| hardened(index).expect("static")));
let path = format!("{purpose}h/{coin_type}h/{account}h");
let fingerprint = self.fingerprint()?;
let xpub = self.derive_xpub(&derivation_path)?;
let keyorigin_xpub = format!("[{fingerprint}/{path}]{xpub}");
Ok(keyorigin_xpub)
}
fn is_mainnet(&self) -> Result<bool, Self::Error> {
let xpub = match self.xpub() {
Ok(xpub) => xpub,
Err(_) => {
let path = [44, 1, 0].map(|index| hardened(index).expect("static")); self.derive_xpub(&DerivationPath::from_iter(path))?
}
};
Ok(xpub.network == bitcoin::NetworkKind::Main)
}
fn wpkh_slip77_descriptor(&self) -> Result<String, String> {
crate::singlesig_desc(
self,
crate::Singlesig::Wpkh,
crate::DescriptorBlindingKey::Slip77,
)
}
fn sign_message(
&self,
message: &str,
path: &DerivationPath,
) -> Result<MessageSignature, Self::Error>;
}
#[cfg(feature = "amp0")]
pub mod amp0 {
use super::*;
use crate::Network;
use elements::hex::ToHex;
use elements::Address;
use serde::{Deserialize, Serialize};
use serde_json;
use std::str::FromStr;
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
pub struct Amp0SignerData {
master_xpub: Xpub,
register_xpub: Xpub,
login_xpub: Xpub,
client_secret_xpub: Xpub,
slip77_key: String,
}
impl std::fmt::Display for Amp0SignerData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match serde_json::to_string(self) {
Ok(s) => write!(f, "{s}"),
Err(e) => write!(f, "Error serializing: {e}"),
}
}
}
impl FromStr for Amp0SignerData {
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(s)
}
}
impl Amp0SignerData {
pub fn master_xpub(&self) -> &Xpub {
&self.master_xpub
}
pub fn register_xpub(&self) -> &Xpub {
&self.register_xpub
}
pub fn login_xpub(&self) -> &Xpub {
&self.login_xpub
}
pub fn client_secret_xpub(&self) -> &Xpub {
&self.client_secret_xpub
}
pub fn login_address(&self, network: &Network) -> Address {
let pk = bitcoin::PublicKey::new(self.master_xpub.public_key);
let params = network.address_params();
Address::p2pkh(&pk, None, params)
}
pub fn slip77_key(&self) -> &str {
&self.slip77_key
}
}
pub trait Amp0Signer: Signer {
fn amp0_signer_data(&self) -> Result<Amp0SignerData, Self::Error> {
let master_xpub = self.xpub()?;
let register_path = DerivationPath::from_str("m/18241h").expect("static");
let register_xpub = self.derive_xpub(®ister_path)?;
let login_path = DerivationPath::from_str("m/1195487518").expect("static");
let login_xpub = self.derive_xpub(&login_path)?;
let client_secret_path = DerivationPath::from_str("m/1885434739h").expect("static");
let client_secret_xpub = self.derive_xpub(&client_secret_path)?;
let slip77_key = self.slip77_master_blinding_key()?.to_string();
Ok(Amp0SignerData {
master_xpub,
register_xpub,
login_xpub,
client_secret_xpub,
slip77_key,
})
}
fn amp0_sign_challenge(&self, challenge: &str) -> Result<String, Self::Error> {
let message = format!("greenaddress.it login {challenge}");
let path = DerivationPath::from_str("m/1195487518").expect("static");
let sig = self.sign_message(&message, &path)?;
let der_sig = sig.signature.to_standard().serialize_der();
Ok(der_sig.to_hex())
}
fn amp0_account_xpub(&self, account: u32) -> Result<Xpub, Self::Error> {
let path = DerivationPath::from_str(&format!("m/3h/{account}h")).expect("TODO");
self.derive_xpub(&path)
}
}
}