use std::{fmt, io};
use std::borrow::Cow;
use std::str::FromStr;
use bitcoin::bech32::{self, ByteIterExt, Fe32IterExt};
use bitcoin::hashes::{sha256, Hash};
use bitcoin::secp256k1::{Keypair, PublicKey};
use crate::{ProtocolDecodingError, ProtocolEncoding, VtxoPolicy};
use crate::encode::{ReadExt, WriteExt};
use crate::mailbox::{BlindedMailboxIdentifier, MailboxIdentifier};
const HRP_MAINNET: bech32::Hrp = bech32::Hrp::parse_unchecked("ark");
const HRP_TESTNET: bech32::Hrp = bech32::Hrp::parse_unchecked("tark");
const VERSION_ARKADE: bech32::Fe32 = bech32::Fe32::Q;
const VERSION_POLICY: bech32::Fe32 = bech32::Fe32::P;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ArkId([u8; 4]);
impl_byte_newtype!(ArkId, 4);
impl ArkId {
pub fn from_server_pubkey(server_pubkey: PublicKey) -> ArkId {
let mut buf = [0u8; 4];
let hash = sha256::Hash::hash(&server_pubkey.serialize());
buf[0..4].copy_from_slice(&hash[0..4]);
ArkId(buf)
}
pub fn is_for_server(&self, server_pubkey: PublicKey) -> bool {
*self == ArkId::from_server_pubkey(server_pubkey)
}
}
impl From<PublicKey> for ArkId {
fn from(pk: PublicKey) -> Self {
ArkId::from_server_pubkey(pk)
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum VtxoDelivery {
ServerMailbox {
blinded_id: BlindedMailboxIdentifier,
},
Unknown {
delivery_type: u8,
data: Vec<u8>,
},
}
#[allow(unused)]
const DELIVERY_BUILTIN: u8 = 0x00;
const DELIVERY_MAILBOX: u8 = 0x01;
impl VtxoDelivery {
pub fn is_unknown(&self) -> bool {
match self {
Self::Unknown { .. } => true,
_ => false,
}
}
fn encoded_length(&self) -> usize {
match self {
Self::ServerMailbox { .. } => 1 + 33,
Self::Unknown { data, .. } => 1 + data.len(),
}
}
fn encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<(), io::Error> {
match self {
Self::ServerMailbox { blinded_id } => {
w.emit_u8(DELIVERY_MAILBOX)?;
w.emit_slice(blinded_id.as_ref())?;
},
Self::Unknown { delivery_type, data } => {
w.emit_u8(*delivery_type)?;
w.emit_slice(data)?;
},
}
Ok(())
}
fn decode(payload: &[u8]) -> Result<Self, ParseAddressError> {
if payload.is_empty() {
return Err(ParseAddressError::Eof);
}
match payload[0] {
DELIVERY_MAILBOX => Ok(Self::ServerMailbox {
blinded_id: BlindedMailboxIdentifier::from_slice(&payload[1..]).map_err(
|_| ParseAddressError::Invalid("invalid blinded mailbox identifier"),
)?,
}),
delivery_type => Ok(Self::Unknown {
delivery_type: delivery_type,
data: payload[1..].to_vec(),
}),
}
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Address {
testnet: bool,
ark_id: ArkId,
policy: VtxoPolicy,
delivery: Vec<VtxoDelivery>,
}
impl Address {
pub fn builder() -> Builder {
Builder::new()
}
pub fn new(
testnet: bool,
ark_id: impl Into<ArkId>,
policy: VtxoPolicy,
delivery: Vec<VtxoDelivery>,
) -> Address {
Address {
testnet: testnet,
ark_id: ark_id.into(),
policy: policy,
delivery: delivery,
}
}
pub fn is_testnet(&self) -> bool {
self.testnet
}
pub fn ark_id(&self) -> ArkId {
self.ark_id
}
pub fn is_for_server(&self, server_pubkey: PublicKey) -> bool {
self.ark_id().is_for_server(server_pubkey)
}
pub fn policy(&self) -> &VtxoPolicy {
&self.policy
}
pub fn delivery(&self) -> &[VtxoDelivery] {
&self.delivery
}
pub fn encode_payload<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<(), io::Error> {
writer.emit_slice(&self.ark_id.to_byte_array())?;
let mut buf = Vec::with_capacity(128); self.policy.encode(&mut buf)?;
writer.emit_compact_size(buf.len() as u64)?;
writer.emit_slice(&buf[..])?;
for delivery in &self.delivery {
writer.emit_compact_size(delivery.encoded_length() as u64)?;
delivery.encode(writer)?;
}
Ok(())
}
pub fn decode_payload(
testnet: bool,
bytes: impl Iterator<Item = u8>,
) -> Result<Address, ParseAddressError> {
let mut peekable = bytes.peekable();
let mut reader = ByteIter(&mut peekable);
let ark_id = {
let mut buf = [0u8; 4];
reader.read_slice(&mut buf).map_err(|_| ParseAddressError::Eof)?;
ArkId(buf)
};
let mut buf = Vec::new();
let policy = {
let len = reader.read_compact_size()? as usize;
buf.resize(len, 0);
reader.read_slice(&mut buf[..])?;
VtxoPolicy::deserialize(&buf[..]).map_err(ParseAddressError::VtxoPolicy)?
};
let mut delivery = Vec::new();
while reader.0.peek().is_some() {
let len = reader.read_compact_size()? as usize;
buf.resize(len, 0);
reader.read_slice(&mut buf[..])?;
delivery.push(VtxoDelivery::decode(&buf[..])?);
}
Ok(Address::new(testnet, ark_id, policy, delivery))
}
}
impl fmt::Display for Address {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let hrp = if self.testnet {
HRP_TESTNET
} else {
HRP_MAINNET
};
let ver = VERSION_POLICY;
let payload = {
let mut buf = Vec::with_capacity(128);
self.encode_payload(&mut buf).expect("buffers don't error");
buf
};
let chars = [ver].into_iter().chain(payload.into_iter().bytes_to_fes())
.with_checksum::<bech32::Bech32m>(&hrp)
.chars();
const BUF_LENGTH: usize = 128;
let mut buf = [0u8; BUF_LENGTH];
let mut pos = 0;
for c in chars {
buf[pos] = c as u8;
pos += 1;
if pos == BUF_LENGTH {
let s = core::str::from_utf8(&buf).expect("we only write ASCII");
f.write_str(s)?;
pos = 0;
}
}
let s = core::str::from_utf8(&buf[..pos]).expect("we only write ASCII");
f.write_str(s)?;
Ok(())
}
}
impl fmt::Debug for Address {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
#[derive(Debug, thiserror::Error)]
pub enum ParseAddressError {
#[error("bech32m decoding error: {0}")]
Bech32(bech32::DecodeError),
#[error("invalid HRP: '{0}'")]
Hrp(bech32::Hrp),
#[error("address is an Arkade address and cannot be used here")]
Arkade,
#[error("unknown version: '{version}'")]
UnknownVersion {
version: bech32::Fe32,
},
#[error("invalid encoding: unexpected end of bytes")]
Eof,
#[error("invalid or unknown VTXO policy")]
VtxoPolicy(ProtocolDecodingError),
#[error("invalid address")]
Invalid(&'static str),
}
impl From<bech32::primitives::decode::UncheckedHrpstringError> for ParseAddressError {
fn from(e: bech32::primitives::decode::UncheckedHrpstringError) -> Self {
Self::Bech32(e.into())
}
}
impl From<bech32::primitives::decode::ChecksumError> for ParseAddressError {
fn from(e: bech32::primitives::decode::ChecksumError) -> Self {
Self::Bech32(bech32::DecodeError::Checksum(e))
}
}
impl From<io::Error> for ParseAddressError {
fn from(e: io::Error) -> Self {
match e.kind() {
io::ErrorKind::UnexpectedEof => ParseAddressError::Eof,
io::ErrorKind::InvalidData => ParseAddressError::Invalid("invalid encoding"),
_ => {
if cfg!(debug_assertions) {
panic!("unexpected I/O error while parsing address: {}", e);
}
ParseAddressError::Invalid("unexpected I/O error")
},
}
}
}
impl FromStr for Address {
type Err = ParseAddressError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let raw = bech32::primitives::decode::UncheckedHrpstring::new(s)?;
let testnet = if raw.hrp() == HRP_MAINNET {
false
} else if raw.hrp() == HRP_TESTNET {
true
} else {
return Err(ParseAddressError::Hrp(raw.hrp()));
};
let checked = raw.validate_and_remove_checksum::<bech32::Bech32m>()?;
let mut iter = checked.fe32_iter::<std::iter::Empty<u8>>();
let ver = iter.next().ok_or(ParseAddressError::Invalid("empty address"))?;
match ver {
VERSION_POLICY => {},
VERSION_ARKADE => return Err(ParseAddressError::Arkade),
_ => return Err(ParseAddressError::UnknownVersion { version: ver }),
}
Address::decode_payload(testnet, iter.fes_to_bytes())
}
}
impl serde::Serialize for Address {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.collect_str(&self)
}
}
impl<'de> serde::Deserialize<'de> for Address {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s: Cow<'de, str> = serde::Deserialize::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Debug, thiserror::Error)]
#[error("error building address: {msg}")]
pub struct AddressBuilderError {
msg: &'static str,
}
impl From<&'static str> for AddressBuilderError {
fn from(msg: &'static str) -> Self {
AddressBuilderError { msg }
}
}
#[derive(Debug)]
pub struct Builder {
testnet: bool,
server_pubkey: Option<PublicKey>,
policy: Option<VtxoPolicy>,
delivery: Vec<VtxoDelivery>,
mailbox_id: Option<BlindedMailboxIdentifier>,
}
impl Builder {
pub fn new() -> Self {
Self {
testnet: false,
server_pubkey: None,
policy: None,
delivery: Vec::new(),
mailbox_id: None,
}
}
pub fn testnet(mut self, testnet: bool) -> Self {
self.testnet = testnet;
self
}
pub fn server_pubkey(mut self, server_pubkey: PublicKey) -> Self {
self.server_pubkey = Some(server_pubkey);
self
}
pub fn policy(mut self, policy: VtxoPolicy) -> Self {
self.policy = Some(policy);
self
}
pub fn pubkey_policy(self, user_pubkey: PublicKey) -> Self {
self.policy(VtxoPolicy::new_pubkey(user_pubkey))
}
pub fn delivery(mut self, delivery: VtxoDelivery) -> Self {
self.delivery.push(delivery);
self
}
pub fn mailbox(
mut self,
server_mailbox_pubkey: PublicKey,
mailbox: MailboxIdentifier,
vtxo_key: &Keypair,
) -> Result<Self, AddressBuilderError> {
let pol = self.policy.as_ref().ok_or("set policy first")?;
if vtxo_key.public_key() != pol.user_pubkey() {
return Err("VTXO key does not match policy".into());
}
self.mailbox_id = Some(mailbox.to_blinded(server_mailbox_pubkey, vtxo_key));
Ok(self)
}
pub fn into_address(self) -> Result<Address, AddressBuilderError> {
Ok(Address {
testnet: self.testnet,
ark_id: self.server_pubkey.ok_or("missing server pubkey")?.into(),
policy: self.policy.ok_or("missing policy")?,
delivery: {
let mut ret = Vec::new();
if let Some(blinded_id) = self.mailbox_id {
ret.push(VtxoDelivery::ServerMailbox { blinded_id });
}
ret.extend(self.delivery);
if ret.is_empty() {
return Err("missing delivery mechanism".into());
}
ret
}
})
}
}
struct ByteIter<T>(T);
impl<T: Iterator<Item = u8>> io::Read for ByteIter<T> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut written = 0;
for e in buf.iter_mut() {
if let Some(n) = self.0.next() {
*e = n;
written += 1;
} else {
break;
}
}
Ok(written)
}
}
#[cfg(test)]
mod test {
use bitcoin::secp256k1::rand;
use crate::SECP;
use super::*;
#[test]
fn test_versions() {
assert_eq!(VERSION_POLICY, bech32::Fe32::try_from(1u8).unwrap());
}
fn test_roundtrip(addr: &Address) -> Address {
let parsed = Address::from_str(&addr.to_string()).unwrap();
assert_eq!(parsed, *addr);
parsed
}
#[test]
fn address_roundtrip() {
let ark = PublicKey::from_str("02037188bdd7579a0cd0b22a51110986df1ea08e30192658fe0e219590e4a723d3").unwrap();
let ark_id = ArkId::from_server_pubkey(ark);
let ark_mailbox_pk = PublicKey::from_str("02165c883d8c2e3fe0887800191503beb27c9896d7ff5dfdfc5e9b9dcb25da04c1").unwrap();
let usr_sk = Keypair::from_str("6b0f024af54172a9aed9a0f044689175787676c469ff2aa75024cae5445c7a02").unwrap();
let usr = usr_sk.public_key();
let usr_mailbox_id = MailboxIdentifier::from_str("025d1404cf97bcbc81d0d387cd3416238aeb5362b3877fc54c0ae9b6c1f925ced1").unwrap();
println!("ark pk: {} (id {})", ark, ark_id);
println!("usr pk: {}", usr);
let policy = VtxoPolicy::new_pubkey(usr);
let addr = Address::builder()
.server_pubkey(ark)
.pubkey_policy(usr)
.mailbox(ark_mailbox_pk, usr_mailbox_id, &usr_sk).unwrap()
.into_address().unwrap();
assert_eq!(addr.to_string(), "ark1pwh9vsmezqqpharv69q4z8m6x364d5m5prnmcalcalq9pdmzw0y7mpveck4pcfhezqypczkrrj3lkx5ue4qrf4jc7ztpt9htdttmh2judhqnu7aue8p0y9mqkr4cf5");
let parsed = test_roundtrip(&addr);
assert_eq!(parsed.ark_id, ark_id);
assert_eq!(parsed.policy, policy);
assert!(matches!(parsed.delivery[0], VtxoDelivery::ServerMailbox { .. }));
let addr = Address::builder()
.testnet(true)
.server_pubkey(ark)
.pubkey_policy(usr)
.mailbox(ark_mailbox_pk, usr_mailbox_id, &usr_sk).unwrap()
.into_address().unwrap();
assert_eq!(addr.to_string(), "tark1pwh9vsmezqqpharv69q4z8m6x364d5m5prnmcalcalq9pdmzw0y7mpveck4pcfhezqypczkrrj3lkx5ue4qrf4jc7ztpt9htdttmh2judhqnu7aue8p0y9mq47jn9z");
let parsed = test_roundtrip(&addr);
assert_eq!(parsed.ark_id, ArkId::from_server_pubkey(ark));
assert_eq!(parsed.policy, policy);
assert!(matches!(parsed.delivery[0], VtxoDelivery::ServerMailbox { .. }));
}
#[test]
fn test_mailbox() {
let server_key = Keypair::new(&SECP, &mut rand::thread_rng());
let server_mailbox_key = Keypair::new(&SECP, &mut rand::thread_rng());
let bark_mailbox_key = Keypair::new(&SECP, &mut rand::thread_rng());
let vtxo_key = Keypair::new(&SECP, &mut rand::thread_rng());
let mailbox = MailboxIdentifier::from_pubkey(bark_mailbox_key.public_key());
let addr = Address::builder()
.server_pubkey(server_key.public_key())
.pubkey_policy(vtxo_key.public_key())
.mailbox(server_mailbox_key.public_key(), mailbox, &vtxo_key).expect("error mailbox call")
.into_address().unwrap();
let blinded = match addr.delivery[0] {
VtxoDelivery::ServerMailbox { blinded_id } => blinded_id,
_ => panic!("unexpected delivery"),
};
let unblinded = MailboxIdentifier::from_blinded(
blinded, addr.policy().user_pubkey(), &server_mailbox_key);
assert_eq!(mailbox, unblinded);
}
}