use amplify::Slice32;
use bitcoin_scripts::taproot::DfsPath;
use strict_encoding::{StrictDecode, StrictEncode};
use crate::raw::ProprietaryKey;
use crate::Output;
pub const PSBT_TAPRET_PREFIX: &[u8] = b"TAPRET";
pub const PSBT_IN_TAPRET_TWEAK: u8 = 0x00;
pub const PSBT_OUT_TAPRET_HOST: u8 = 0x00;
pub const PSBT_OUT_TAPRET_COMMITMENT: u8 = 0x01;
pub const PSBT_OUT_TAPRET_PROOF: u8 = 0x02;
pub trait ProprietaryKeyTapret {
fn tapret_tweak() -> ProprietaryKey {
ProprietaryKey {
prefix: PSBT_TAPRET_PREFIX.to_vec(),
subtype: PSBT_IN_TAPRET_TWEAK,
key: vec![],
}
}
fn tapret_host() -> ProprietaryKey {
ProprietaryKey {
prefix: PSBT_TAPRET_PREFIX.to_vec(),
subtype: PSBT_OUT_TAPRET_HOST,
key: vec![],
}
}
fn tapret_commitment() -> ProprietaryKey {
ProprietaryKey {
prefix: PSBT_TAPRET_PREFIX.to_vec(),
subtype: PSBT_OUT_TAPRET_COMMITMENT,
key: vec![],
}
}
fn tapret_proof() -> ProprietaryKey {
ProprietaryKey {
prefix: PSBT_TAPRET_PREFIX.to_vec(),
subtype: PSBT_OUT_TAPRET_PROOF,
key: vec![],
}
}
}
impl ProprietaryKeyTapret for ProprietaryKey {}
#[derive(
Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error, From
)]
#[display(doc_comments)]
pub enum TapretKeyError {
OutputAlreadyHasCommitment,
TapretProhibited,
#[from(strict_encoding::Error)]
InvalidKeyValue,
}
#[derive(
Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error
)]
#[display("incorrect DFS path data inside PSBT proprietary key value")]
pub struct DfsPathEncodeError;
impl Output {
#[inline]
pub fn is_tapret_host(&self) -> bool {
self.proprietary
.contains_key(&ProprietaryKey::tapret_host())
}
pub fn tapret_dfs_path(&self) -> Option<Result<DfsPath, DfsPathEncodeError>> {
self.proprietary
.get(&ProprietaryKey::tapret_host())
.map(|data| DfsPath::strict_deserialize(data).map_err(|_| DfsPathEncodeError))
}
pub fn set_tapret_dfs_path(&mut self, path: &DfsPath) -> Result<(), TapretKeyError> {
if self.tapret_dfs_path().is_some() {
return Err(TapretKeyError::OutputAlreadyHasCommitment);
}
self.proprietary.insert(
ProprietaryKey::tapret_host(),
path.strict_serialize()
.expect("DFS paths are always compact and serializable"),
);
Ok(())
}
pub fn has_tapret_commitment(&self) -> bool {
self.proprietary
.contains_key(&ProprietaryKey::tapret_commitment())
}
pub fn tapret_commitment(&self) -> Option<Slice32> {
self.proprietary
.get(&ProprietaryKey::tapret_commitment())
.and_then(Slice32::from_slice)
}
pub fn set_tapret_commitment(
&mut self,
commitment: impl Into<[u8; 32]>,
proof: &impl StrictEncode,
) -> Result<(), TapretKeyError> {
if !self.is_tapret_host() {
return Err(TapretKeyError::TapretProhibited);
}
if self.has_tapret_commitment() {
return Err(TapretKeyError::OutputAlreadyHasCommitment);
}
self.proprietary.insert(
ProprietaryKey::tapret_commitment(),
commitment.into().to_vec(),
);
self.proprietary
.insert(ProprietaryKey::tapret_proof(), proof.strict_serialize()?);
Ok(())
}
pub fn has_tapret_proof(&self) -> bool {
self.proprietary
.contains_key(&ProprietaryKey::tapret_proof())
}
pub fn tapret_proof<T>(&self) -> Result<Option<T>, TapretKeyError>
where
T: StrictDecode,
{
self.proprietary
.get(&ProprietaryKey::tapret_proof())
.map(T::strict_deserialize)
.transpose()
.map_err(TapretKeyError::from)
}
}