crawB Binary Token Serialization (#507)

This commit is contained in:
lollerfirst
2024-12-19 19:27:05 +01:00
committed by GitHub
parent 0e12314643
commit 8055c0ced1
3 changed files with 123 additions and 1 deletions

View File

@@ -75,9 +75,12 @@ pub enum Error {
/// Base64 error
#[error(transparent)]
Base64Error(#[from] bitcoin::base64::DecodeError),
/// Ciborium error
/// Ciborium deserialization error
#[error(transparent)]
CiboriumError(#[from] ciborium::de::Error<std::io::Error>),
/// Ciborium serialization error
#[error(transparent)]
CiboriumSerError(#[from] ciborium::ser::Error<std::io::Error>),
/// Amount Error
#[error(transparent)]
Amount(#[from] crate::amount::Error),

View File

@@ -122,6 +122,14 @@ impl Token {
v3_token.to_string()
}
/// Serialize the token to raw binary
pub fn to_raw_bytes(&self) -> Result<Vec<u8>, Error> {
match self {
Self::TokenV3(_) => Err(Error::UnsupportedToken),
Self::TokenV4(token) => token.to_raw_bytes(),
}
}
}
impl FromStr for Token {
@@ -152,6 +160,26 @@ impl FromStr for Token {
}
}
impl TryFrom<&Vec<u8>> for Token {
type Error = Error;
fn try_from(bytes: &Vec<u8>) -> Result<Self, Self::Error> {
if bytes.len() < 5 {
return Err(Error::UnsupportedToken);
}
let prefix = String::from_utf8(bytes[..5].to_vec())?;
match prefix.as_str() {
"crawB" => {
let token: TokenV4 = ciborium::from_reader(&bytes[5..])?;
Ok(Token::TokenV4(token))
}
_ => Err(Error::UnsupportedToken),
}
}
}
/// Token V3 Token
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TokenV3Token {
@@ -329,6 +357,15 @@ impl TokenV4 {
pub fn unit(&self) -> &CurrencyUnit {
&self.unit
}
/// Serialize the token to raw binary
pub fn to_raw_bytes(&self) -> Result<Vec<u8>, Error> {
let mut prefix = b"crawB".to_vec();
let mut data = Vec::new();
ciborium::into_writer(self, &mut data).map_err(Error::CiboriumSerError)?;
prefix.extend(data);
Ok(prefix)
}
}
impl fmt::Display for TokenV4 {
@@ -355,6 +392,25 @@ impl FromStr for TokenV4 {
}
}
impl TryFrom<&Vec<u8>> for TokenV4 {
type Error = Error;
fn try_from(bytes: &Vec<u8>) -> Result<Self, Self::Error> {
if bytes.len() < 5 {
return Err(Error::UnsupportedToken);
}
let prefix = String::from_utf8(bytes[..5].to_vec())?;
if prefix.as_str() == "crawB" {
let token: TokenV4 = ciborium::from_reader(&bytes[5..])?;
Ok(token)
} else {
Err(Error::UnsupportedToken)
}
}
}
impl TryFrom<TokenV3> for TokenV4 {
type Error = Error;
fn try_from(token: TokenV3) -> Result<Self, Self::Error> {
@@ -434,6 +490,7 @@ mod tests {
use super::*;
use crate::mint_url::MintUrl;
use crate::util::hex;
#[test]
fn test_token_padding() {
@@ -554,4 +611,23 @@ mod tests {
assert!(correct_token.is_ok());
}
#[test]
fn test_token_v4_raw_roundtrip() {
let token_raw = hex::decode("6372617742a4617481a261694800ad268c4d1f5826617081a3616101617378403961366462623834376264323332626137366462306466313937323136623239643362386363313435353363643237383237666331636339343266656462346561635821038618543ffb6b8695df4ad4babcde92a34a96bdcd97dcee0d7ccf98d4721267926164695468616e6b20796f75616d75687474703a2f2f6c6f63616c686f73743a33333338617563736174").unwrap();
let token = TokenV4::try_from(&token_raw).expect("Token deserialization error");
let token_raw_ = token.to_raw_bytes().expect("Token serialization error");
let token_ = TokenV4::try_from(&token_raw_).expect("Token deserialization error");
assert!(token_ == token)
}
#[test]
fn test_token_generic_raw_roundtrip() {
let tokenv4_raw = hex::decode("6372617742a4617481a261694800ad268c4d1f5826617081a3616101617378403961366462623834376264323332626137366462306466313937323136623239643362386363313435353363643237383237666331636339343266656462346561635821038618543ffb6b8695df4ad4babcde92a34a96bdcd97dcee0d7ccf98d4721267926164695468616e6b20796f75616d75687474703a2f2f6c6f63616c686f73743a33333338617563736174").unwrap();
let tokenv4 = Token::try_from(&tokenv4_raw).expect("Token deserialization error");
let tokenv4_ = TokenV4::try_from(&tokenv4_raw).expect("Token deserialization error");
let tokenv4_bytes = tokenv4.to_raw_bytes().expect("Serialization error");
let tokenv4_bytes_ = tokenv4_.to_raw_bytes().expect("Serialization error");
assert!(tokenv4_bytes_ == tokenv4_bytes);
}
}

View File

@@ -213,4 +213,47 @@ impl Wallet {
Ok(amount)
}
/// Receive
/// # Synopsis
/// ```rust, no_run
/// use std::sync::Arc;
///
/// use cdk::amount::SplitTarget;
/// use cdk::cdk_database::WalletMemoryDatabase;
/// use cdk::nuts::CurrencyUnit;
/// use cdk::wallet::Wallet;
/// use cdk::util::hex;
/// use rand::Rng;
///
/// #[tokio::main]
/// async fn main() -> anyhow::Result<()> {
/// let seed = rand::thread_rng().gen::<[u8; 32]>();
/// let mint_url = "https://testnut.cashu.space";
/// let unit = CurrencyUnit::Sat;
///
/// let localstore = WalletMemoryDatabase::default();
/// let wallet = Wallet::new(mint_url, unit, Arc::new(localstore), &seed, None).unwrap();
/// let token_raw = hex::decode("6372617742a4617481a261694800ad268c4d1f5826617081a3616101617378403961366462623834376264323332626137366462306466313937323136623239643362386363313435353363643237383237666331636339343266656462346561635821038618543ffb6b8695df4ad4babcde92a34a96bdcd97dcee0d7ccf98d4721267926164695468616e6b20796f75616d75687474703a2f2f6c6f63616c686f73743a33333338617563736174").unwrap();
/// let amount_receive = wallet.receive_raw(&token_raw, SplitTarget::default(), &[], &[]).await?;
/// Ok(())
/// }
/// ```
#[instrument(skip_all)]
pub async fn receive_raw(
&self,
binary_token: &Vec<u8>,
amount_split_target: SplitTarget,
p2pk_signing_keys: &[SecretKey],
preimages: &[String],
) -> Result<Amount, Error> {
let token_str = Token::try_from(binary_token)?.to_string();
self.receive(
token_str.as_str(),
amount_split_target,
p2pk_signing_keys,
preimages,
)
.await
}
}