use bitcoin::secp256k1::SecretKey;
use bitcoin::ScriptBuf;
use serde::{Deserialize, Serialize};
use sidestr_core::address::script_to_address;
use crate::account::{account_of, x_of};
use crate::error::{Error, Result};
use crate::keys;
use crate::ledger::Ledger;
use crate::DEPOSIT_TAG;
pub const DEFAULT_HRP: &str = "tb";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DepositAddress {
pub tweak: String,
pub point: String,
pub x_only: String,
pub script: String,
pub address: String,
pub account: String,
pub nonce: u64,
}
impl DepositAddress {
pub fn script_pubkey(&self) -> ScriptBuf {
ScriptBuf::from_hex(&self.script).expect("written by deposit_address")
}
}
pub(crate) fn is_x(s: &str) -> bool {
s.len() == 64 && s.bytes().all(|c| matches!(c, b'0'..=b'9' | b'a'..=b'f'))
}
pub fn deposit_address(
operator: &str,
ledger_hash: &str,
account: &str,
nonce: u64,
hrp: &str,
) -> Result<DepositAddress> {
if !is_x(ledger_hash) {
return Err(Error::DepositLedgerHash);
}
let p = keys::base_point(&keys::did(&keys::base_point(operator)?)?)?;
let lh = hex::decode(ledger_hash).expect("checked hex");
let ax = hex::decode(x_of(account)?).expect("an account's x is hex");
let t = keys::tagged_scalar(DEPOSIT_TAG, &[&lh, &ax, &nonce.to_be_bytes()])?;
let q = keys::tweak_point(&p, &t)?;
let x = keys::x_only(&q)?;
let script = format!("5120{x}");
let spk = ScriptBuf::from_hex(&script).expect("hex");
let address = script_to_address(&spk, hrp).ok_or_else(|| Error::Address(hrp.to_owned()))?;
Ok(DepositAddress {
tweak: t,
point: q,
x_only: x,
script,
address,
account: account_of(account)?,
nonce,
})
}
pub fn deposit_secret(operator_secret: &SecretKey, tweak: &str) -> Result<SecretKey> {
keys::tweaked_secret(&keys::normalized(operator_secret), &keys::scalar(tweak)?)
}
pub fn watch_list(
ledger: &Ledger,
operator: &str,
extra_accounts: &[&str],
) -> Result<Vec<DepositAddress>> {
let extra: Vec<String> = extra_accounts
.iter()
.map(|a| account_of(a))
.collect::<Result<_>>()?;
let mut accounts: Vec<&str> = Vec::new();
let all = ledger
.entries
.iter()
.map(|e| e.url.as_str())
.chain(ledger.deposits.iter().map(|d| d.account.as_str()))
.chain(extra.iter().map(String::as_str));
for a in all {
if !accounts.contains(&a) {
accounts.push(a);
}
}
accounts
.into_iter()
.map(|a| deposit_address(operator, &ledger.hash, a, 0, DEFAULT_HRP))
.collect()
}