use std::fmt::{self, Debug, Display, Formatter};
use std::hash::Hash;
use std::str::FromStr;
use bitcoin::hex::HexToArrayError;
use bitcoin::{OutPoint as Outpoint, Txid};
use rand::{rng, RngCore};
use strict_encoding::{StrictDecode, StrictDumb, StrictEncode};
use super::WitnessVoutError;
use crate::commit_verify::{CommitEncode, CommitEngine, CommitId, Conceal};
use crate::seals::txout::{SealTxid, TxPtr, TxoSeal};
use crate::{dbc, SecretSeal, Vout};
pub type ChainBlindSeal = BlindSeal<TxPtr>;
pub type SingleBlindSeal = BlindSeal<Txid>;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = dbc::LIB_NAME_BPCORE)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))]
pub struct BlindSeal<Id: SealTxid> {
pub txid: Id,
pub vout: Vout,
pub blinding: u64,
}
impl<Id: SealTxid> CommitEncode for BlindSeal<Id> {
type CommitmentId = SecretSeal;
fn commit_encode(&self, e: &mut CommitEngine) { e.commit_to_serialized(&self); }
}
impl<Id: SealTxid> BlindSeal<Id> {
#[inline]
pub fn to_secret_seal(&self) -> SecretSeal { self.conceal() }
}
impl<Id: SealTxid> Conceal for BlindSeal<Id> {
type Concealed = SecretSeal;
#[inline]
fn conceal(&self) -> Self::Concealed { self.commit_id() }
}
impl TryFrom<&BlindSeal<TxPtr>> for Outpoint {
type Error = WitnessVoutError;
#[inline]
fn try_from(reveal: &BlindSeal<TxPtr>) -> Result<Self, Self::Error> {
reveal
.txid
.map_to_outpoint(reveal.vout)
.ok_or(WitnessVoutError)
}
}
impl TryFrom<BlindSeal<TxPtr>> for Outpoint {
type Error = WitnessVoutError;
#[inline]
fn try_from(reveal: BlindSeal<TxPtr>) -> Result<Self, Self::Error> {
Outpoint::try_from(&reveal)
}
}
impl From<&BlindSeal<Txid>> for Outpoint {
#[inline]
fn from(reveal: &BlindSeal<Txid>) -> Self { Outpoint::new(reveal.txid, reveal.vout.into_u32()) }
}
impl From<BlindSeal<Txid>> for Outpoint {
#[inline]
fn from(reveal: BlindSeal<Txid>) -> Self { Outpoint::from(&reveal) }
}
impl<Id: SealTxid> TxoSeal for BlindSeal<Id> {
#[inline]
fn txid(&self) -> Option<Txid> { self.txid.txid() }
#[inline]
fn vout(&self) -> Vout { self.vout }
#[inline]
fn outpoint(&self) -> Option<Outpoint> { self.txid.map_to_outpoint(self.vout) }
#[inline]
fn txid_or(&self, default_txid: Txid) -> Txid { self.txid.txid_or(default_txid) }
#[inline]
fn outpoint_or(&self, default_txid: Txid) -> Outpoint {
Outpoint::new(self.txid.txid_or(default_txid), self.vout.into_u32())
}
}
impl<Id: SealTxid> BlindSeal<Id> {
pub fn rand_from(outpoint: Outpoint) -> Self {
BlindSeal::new_random(outpoint.txid, outpoint.vout)
}
pub fn new_random(txid: impl Into<Id>, vout: impl Into<Vout>) -> Self {
BlindSeal::with_rng(txid, vout, &mut rng())
}
pub fn with_rng(txid: impl Into<Id>, vout: impl Into<Vout>, rng: &mut impl RngCore) -> Self {
BlindSeal {
txid: txid.into(),
vout: vout.into(),
blinding: rng.next_u64(),
}
}
pub fn with_blinding(txid: impl Into<Id>, vout: impl Into<Vout>, blinding: u64) -> Self {
BlindSeal {
txid: txid.into(),
vout: vout.into(),
blinding,
}
}
}
impl BlindSeal<TxPtr> {
#[inline]
pub fn new_random_vout(vout: impl Into<Vout>) -> Self {
Self {
blinding: rng().next_u64(),
txid: TxPtr::WitnessTx,
vout: vout.into(),
}
}
pub fn with_blinded_vout(vout: impl Into<Vout>, blinding: u64) -> Self {
BlindSeal {
txid: TxPtr::WitnessTx,
vout: vout.into(),
blinding,
}
}
}
impl BlindSeal<Txid> {
pub fn transmutate(self) -> BlindSeal<TxPtr> {
BlindSeal::with_blinding(self.txid, self.vout, self.blinding)
}
#[inline]
pub fn to_outpoint(&self) -> Outpoint { Outpoint::new(self.txid, self.vout.into_u32()) }
}
impl BlindSeal<TxPtr> {
pub fn resolve(self, txid: Txid) -> BlindSeal<Txid> {
BlindSeal::with_blinding(self.txid().unwrap_or(txid), self.vout, self.blinding)
}
}
#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)]
#[display(doc_comments)]
pub enum ParseError {
TxidRequired,
BlindingRequired,
WrongBlinding,
WrongTxid(HexToArrayError),
WrongVout,
WrongStructure,
NonHexBlinding,
}
impl<Id: SealTxid> FromStr for BlindSeal<Id> {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut split = s.split(&[':', '#'][..]);
if split.next() != Some("txoseal") {
return Err(ParseError::WrongStructure);
}
match (split.next(), split.next(), split.next(), split.next()) {
(Some(""), ..) => Err(ParseError::TxidRequired),
(Some(_), None, ..) if !s.contains('#') => Err(ParseError::BlindingRequired),
(Some(_), Some(_), Some(blinding), None) if !blinding.starts_with("0x") => {
Err(ParseError::NonHexBlinding)
}
(Some(txid), Some(vout), Some(blinding), None) => Ok(BlindSeal {
blinding: u64::from_str_radix(blinding.trim_start_matches("0x"), 16)
.map_err(|_| ParseError::WrongBlinding)?,
txid: Id::from_str(txid).map_err(ParseError::WrongTxid)?,
vout: vout.parse().map_err(|_| ParseError::WrongVout)?,
}),
_ => Err(ParseError::WrongStructure),
}
}
}
impl<Id: SealTxid> Display for BlindSeal<Id>
where Self: TxoSeal
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "txoseal:{}:{}#{:#010x}", self.txid, self.vout, self.blinding)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn blind_seal_str() {
let mut outpoint_reveal = BlindSeal {
blinding: 0x31bbed7e7b2d,
txid: TxPtr::Txid(
Txid::from_str("646ca5c1062619e2a2d60771c9dfd820551fb773e4dc8c4ed67965a8d1fae839")
.unwrap(),
),
vout: Vout::from(21),
};
let s = outpoint_reveal.to_string();
assert_eq!(
&s,
"txoseal:646ca5c1062619e2a2d60771c9dfd820551fb773e4dc8c4ed67965a8d1fae839:21#\
0x31bbed7e7b2d"
);
assert_eq!(ChainBlindSeal::from_str(&s).unwrap(), outpoint_reveal);
outpoint_reveal.txid = TxPtr::WitnessTx;
let s = outpoint_reveal.to_string();
assert_eq!(&s, "txoseal:~:21#0x31bbed7e7b2d");
assert_eq!(ChainBlindSeal::from_str(&s).unwrap(), outpoint_reveal);
assert_eq!(
ChainBlindSeal::from_str(
"txoseal:646ca5c1062619e2a2d60771c9dfd820551fb773e4dc8c4ed67965a8d1fae839:0x765#\
0x78ca95"
),
Err(ParseError::WrongVout)
);
assert_eq!(
ChainBlindSeal::from_str(
"txoseal:646ca5c1062619e2a2d60771c9dfd820551fb773e4dc8c4ed67965a8d1fae839:i9#\
0x78ca95"
),
Err(ParseError::WrongVout)
);
assert_eq!(
ChainBlindSeal::from_str(
"txoseal:646ca5c1062619e2a2d60771c9dfd820551fb773e4dc8c4ed67965a8d1fae839:-5#\
0x78ca95"
),
Err(ParseError::WrongVout)
);
assert_eq!(
ChainBlindSeal::from_str(
"txoseal:646ca5c1062619e2a2d60771c9dfd820551fb773e4dc8c4ed67965a8d1fae839:5#0x78cs"
),
Err(ParseError::WrongBlinding)
);
assert_eq!(
ChainBlindSeal::from_str(
"txoseal:646ca5c1062619e2a2d60771c9dfd820551fb773e4dc8c4ed67965a8d1fae839:5#78ca95"
),
Err(ParseError::NonHexBlinding)
);
assert_eq!(
ChainBlindSeal::from_str(
"txoseal:646ca5c1062619e2a2d60771c9dfd820551fb773e4dc8c4ed67965a8d1fae839:5#857"
),
Err(ParseError::NonHexBlinding)
);
assert_eq!(
ChainBlindSeal::from_str(
"txoseal:646ca5c1062619e2a2d60771c9dfd820551fb773e4dc8c4ed67965a8d1fae839:5#-5"
),
Err(ParseError::NonHexBlinding)
);
let err = ChainBlindSeal::from_str(
"txoseal:646ca5c1062619e2a2d607719dfd820551fb773e4dc8c4ed67965a8d1fae839:5#0x78ca69",
)
.unwrap_err();
if let ParseError::WrongTxid(HexToArrayError::InvalidLength(e)) = err {
assert_eq!(e.invalid, 63);
assert_eq!(e.expected, 64);
} else {
panic!("unexpected error: {err:?}");
}
let err = ChainBlindSeal::from_str(
"txoseal:r46ca5c1062619e2a2d60771c9dfd820551fb773e4dc8c4ed67965a8d1fae839:21#\
0x31bbed7e7b2d",
)
.unwrap_err();
if let ParseError::WrongTxid(HexToArrayError::InvalidChar(e)) = err {
assert_eq!(e.invalid_char(), b'r');
} else {
panic!("unexpected error: {err:?}");
}
let err = ChainBlindSeal::from_str(
"txoseal:10@646ca5c1062619e2a2d60771c9dfd820551fb773e4dc8c4ed67965a8d1fae839:5#\
0x78ca69",
)
.unwrap_err();
assert!(matches!(err, ParseError::WrongTxid(HexToArrayError::InvalidLength(_))));
if let ParseError::WrongTxid(HexToArrayError::InvalidLength(e)) = err {
assert_eq!(e.invalid, 67);
assert_eq!(e.expected, 64);
} else {
panic!("unexpected error: {err:?}");
}
assert_eq!(
ChainBlindSeal::from_str(
"646ca5c1062619e2a2d60771c9dfd820551fb773e4dc8c4ed67965a8d1fae839:1"
),
Err(ParseError::WrongStructure)
);
assert_eq!(
ChainBlindSeal::from_str(
"txoseal:646ca5c1062619e2a2d60771c9dfd820551fb773e4dc8c4ed67965a8d1fae839:1"
),
Err(ParseError::WrongStructure)
);
assert_eq!(
ChainBlindSeal::from_str(
"txoseal:646ca5c1062619e2a2d60771c9dfd820551fb773e4dc8c4ed67965a8d1fae839#0x78ca"
),
Err(ParseError::WrongStructure)
);
assert_eq!(
ChainBlindSeal::from_str(
"txoseal:646ca5c1062619e2a2d60771c9dfd820551fb773e4dc8c4ed67965a8d1fae839"
),
Err(ParseError::BlindingRequired)
);
assert_eq!(
ChainBlindSeal::from_str(
"txoseal:646ca5c1062619e2a2d60771c9dfd820551fb773e4dc8c4ed67965a8d1fae839##0x78ca"
),
Err(ParseError::WrongVout)
);
assert_eq!(
ChainBlindSeal::from_str(
"txoseal:646ca5c1062619e2a2d60771c9dfd820551fb773e4dc8c4ed67965a8d1fae839:#\
0x78ca95"
),
Err(ParseError::WrongVout)
);
let err = ChainBlindSeal::from_str("txoseal:_:5#0x78ca").unwrap_err();
if let ParseError::WrongTxid(HexToArrayError::InvalidLength(e)) = err {
assert_eq!(e.invalid, 1);
assert_eq!(e.expected, 64);
} else {
panic!("unexpected error: {err:?}");
}
}
}