Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crawB Binary Token Serialization #507

Merged
merged 8 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion crates/cdk/src/nuts/nut00/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
126 changes: 126 additions & 0 deletions crates/cdk/src/nuts/nut00/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,15 @@ impl Token {

v3_token.to_string()
}

/// Serialize the token to raw binary
pub fn to_raw_bytes(&self) -> Result<Vec<u8>, Error> {
let token = match self {
Self::TokenV3(token) => token.to_raw_bytes(),
Self::TokenV4(token) => token.to_raw_bytes(),
};
token
}
}

impl FromStr for Token {
Expand Down Expand Up @@ -152,6 +161,32 @@ 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))
}
"crawA" => {
let token: TokenV3 = ciborium::from_reader(&bytes[5..])?;
Ok(Token::TokenV3(token))
}
_ => {
return Err(Error::UnsupportedToken);
}
}
}
}

/// Token V3 Token
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TokenV3Token {
Expand Down Expand Up @@ -244,6 +279,15 @@ impl TokenV3 {

mint_urls
}

/// Serialize the token to raw binary
pub fn to_raw_bytes(&self) -> Result<Vec<u8>, Error> {
let mut prefix = b"crawA".to_vec();
let mut data = Vec::new();
ciborium::into_writer(self, &mut data).map_err(|e| Error::CiboriumSerError(e))?;
prefix.extend(data.into_iter());
Ok(prefix)
}
}

impl FromStr for TokenV3 {
Expand All @@ -261,6 +305,25 @@ impl FromStr for TokenV3 {
}
}

impl TryFrom<&Vec<u8>> for TokenV3 {
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() == "crawA" {
let token: TokenV3 = ciborium::from_reader(&bytes[5..])?;
Ok(token)
} else {
Err(Error::UnsupportedToken)
}
}
}

impl fmt::Display for TokenV3 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let json_string = serde_json::to_string(self).map_err(|_| fmt::Error)?;
Expand Down Expand Up @@ -329,6 +392,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(|e| Error::CiboriumSerError(e))?;
prefix.extend(data.into_iter());
Ok(prefix)
}
}

impl fmt::Display for TokenV4 {
Expand All @@ -355,6 +427,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> {
Expand Down Expand Up @@ -434,6 +525,7 @@ mod tests {

use super::*;
use crate::mint_url::MintUrl;
use crate::util::hex;

#[test]
fn test_token_padding() {
Expand Down Expand Up @@ -554,4 +646,38 @@ mod tests {

assert!(correct_token.is_ok());
}

#[test]
fn test_token_v3_raw_roundtrip() {
let token_raw = hex::decode("6372617741a365746f6b656e81a2646d696e747768747470733a2f2f383333332e73706163653a333333386670726f6f667382a466616d6f756e740262696470303039613166323933323533653431656673656372657478403430373931356263323132626536316137376533653664326165623463373237393830626461353163643036613661666332396532383631373638613738333761437842303262633930393739393764383161666232636337333436623565343334356139333436626432613530366562373935383539386137326630636638353136336561a466616d6f756e74086269647030303961316632393332353365343165667365637265747840666531353130393331346536316437373536623066386565306632336136323461636161336634653034326636313433336337323863373035376239333162656143784230323965386535303530623839306137643663303936386462313662633164356435666130343065613164653238346636656336396436313239396636373130353964756e697463736174646d656d6f6a5468616e6b20796f752e").expect("");
let token = TokenV3::try_from(&token_raw).expect("Token deserialization error");
let token_raw_ = token.to_raw_bytes().expect("Token serialization error");
let token_ = TokenV3::try_from(&token_raw_).expect("Token deserialization error");
assert!(token_ == token)
}

#[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);
let tokenv3_raw = hex::decode("6372617741a365746f6b656e81a2646d696e747768747470733a2f2f383333332e73706163653a333333386670726f6f667382a466616d6f756e740262696470303039613166323933323533653431656673656372657478403430373931356263323132626536316137376533653664326165623463373237393830626461353163643036613661666332396532383631373638613738333761437842303262633930393739393764383161666232636337333436623565343334356139333436626432613530366562373935383539386137326630636638353136336561a466616d6f756e74086269647030303961316632393332353365343165667365637265747840666531353130393331346536316437373536623066386565306632336136323461636161336634653034326636313433336337323863373035376239333162656143784230323965386535303530623839306137643663303936386462313662633164356435666130343065613164653238346636656336396436313239396636373130353964756e697463736174646d656d6f6a5468616e6b20796f752e").expect("");
let tokenv3 = Token::try_from(&tokenv3_raw).expect("Token deserialization error");
let tokenv3_ = TokenV3::try_from(&tokenv3_raw).expect("Token deserialization error");
let tokenv3_bytes = tokenv3.to_raw_bytes().expect("Serialization error");
let tokenv3_bytes_ = tokenv3_.to_raw_bytes().expect("Serialization error");
assert!(tokenv3_bytes == tokenv3_bytes_)
}
}
42 changes: 42 additions & 0 deletions crates/cdk/src/wallet/receive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,46 @@ 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, 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
}
}
Loading