Explorar el Código

`crawB` Binary Token Serialization (#507)

lollerfirst hace 3 meses
padre
commit
8055c0ced1

+ 4 - 1
crates/cdk/src/nuts/nut00/mod.rs

@@ -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),

+ 76 - 0
crates/cdk/src/nuts/nut00/token.rs

@@ -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);
+    }
 }

+ 43 - 0
crates/cdk/src/wallet/receive.rs

@@ -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
+    }
 }