shuttle-core 0.3.1

Library to read, write, hash, and sign the xdr structures used in the Stellar Network.
Documentation
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde::de::Error as SerdeError;
use std::result;
use serde_xdr::opaque_data;
use xdr::PublicKey;
use xdr::TimeBounds;
use xdr::Memo;
use xdr::Operation;
use xdr::DecoratedSignature;
use error::{Error, Result};
use xdr::{FromXdr, ToXdr};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Transaction {
    pub source: PublicKey,
    pub fee: u32,
    pub sequence: u64,
    pub time_bounds: Option<TimeBounds>,
    pub memo: Memo,
    pub operations: Vec<Operation>,
    pub ext: i32,
}

impl ToXdr<Transaction> for ::Transaction {
    fn to_xdr(&self) -> Result<Transaction> {
        let source = self.source.to_xdr()?;
        let fee = self.fee.0 as u32;
        let sequence = self.sequence;
        let time_bounds = match self.time_bounds {
            None => None,
            Some(ref t) => Some(t.to_xdr()?),
        };
        let memo = self.memo.to_xdr()?;
        let ops_res: Result<Vec<_>> = self.operations.iter().map(|op| op.to_xdr()).collect();
        let operations = ops_res?;
        Ok(Transaction {
            source,
            fee,
            sequence,
            time_bounds,
            memo,
            operations,
            ext: 0,
        })
    }
}

impl<'de> FromXdr<'de, Transaction> for ::Transaction {
    fn from_xdr(tx: Transaction) -> Result<::Transaction> {
        let source = ::PublicKey::from_xdr(tx.source)?;
        let sequence = tx.sequence;
        let time_bounds = match tx.time_bounds {
            None => None,
            Some(t) => Some(::TimeBounds::from_xdr(t)?),
        };
        let memo = ::Memo::from_xdr(tx.memo)?;
        let operations_res: Result<Vec<_>> = tx.operations
            .into_iter()
            .map(|op| ::Operation::from_xdr(op))
            .collect();
        let operations = operations_res?;
        Ok(::Transaction::new(
            source,
            sequence,
            time_bounds,
            memo,
            operations,
        ))
    }
}

#[derive(Debug, Clone)]
pub enum EnvelopeType {
    Tx = 2,
}

impl Serialize for EnvelopeType {
    fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_i32(self.clone() as i32)
    }
}

impl<'de> Deserialize<'de> for EnvelopeType {
    fn deserialize<D>(deserializer: D) -> result::Result<EnvelopeType, D::Error>
    where
        D: Deserializer<'de>,
    {
        let case = i32::deserialize(deserializer)?;
        match case {
            2 => Ok(EnvelopeType::Tx),
            t => Err(D::Error::custom(format!("Unknown EnvelopeType {}", t))),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransactionSignaturePayload {
    #[serde(with = "opaque_data::fixed_length")] network_id: [u8; 32],
    envelope_type: EnvelopeType,
    transaction: Transaction,
}

impl<'a> ToXdr<TransactionSignaturePayload>
    for super::super::transaction::TransactionSignaturePayload<'a> {
    fn to_xdr(&self) -> Result<TransactionSignaturePayload> {
        let mut network_id = [0; 32];
        if self.network_id.len() > 32 {
            return Err(Error::InvalidNetworkId);
        }
        network_id.copy_from_slice(&self.network_id);
        let transaction = self.transaction.to_xdr()?;
        Ok(TransactionSignaturePayload {
            network_id,
            envelope_type: EnvelopeType::Tx,
            transaction,
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransactionEnvelope {
    pub transaction: Transaction,
    pub signatures: Vec<DecoratedSignature>,
}

impl ToXdr<TransactionEnvelope> for ::SignedTransaction {
    fn to_xdr(&self) -> Result<TransactionEnvelope> {
        let transaction = self.transaction().to_xdr()?;
        let signatures_res: Result<Vec<_>> = self.signatures().iter().map(|s| s.to_xdr()).collect();
        let signatures = signatures_res?;
        Ok(TransactionEnvelope {
            transaction,
            signatures,
        })
    }
}

impl<'de> FromXdr<'de, TransactionEnvelope> for ::SignedTransaction {
    fn from_xdr(envelope: TransactionEnvelope) -> Result<::SignedTransaction> {
        let transaction = ::Transaction::from_xdr(envelope.transaction)?;
        let signatures_res: Result<Vec<_>> = envelope
            .signatures
            .into_iter()
            .map(|s| ::DecoratedSignature::from_xdr(s))
            .collect();
        let signatures = signatures_res?;
        Ok(::SignedTransaction::new_without_network(
            transaction,
            signatures,
        ))
    }
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;
    use {Account, Amount, Asset, KeyPair, Memo, Network};
    use {OperationBuilder, TransactionBuilder};
    use {FromXdr, ToXdr};
    use serde::{Deserialize, Serialize};

    fn do_it<'de, U, T>(tx: T, expected: &str)
    where
        U: Deserialize<'de> + Serialize,
        T: ToXdr<U> + FromXdr<'de, U>,
    {
        let encoded = tx.to_base64().unwrap();
        assert_eq!(encoded, expected);
        let _decoded = T::from_base64(&encoded).unwrap();
    }

    #[test]
    fn test_transaction() {
        let kp = KeyPair::from_secret_seed(
            "SDFRU2NGDPXYIY67BVS6L6W4OY33HCFCEJQ73TZZPR3IDYVVI7BVPV5Q",
        ).unwrap();

        let mut account = Account::new(kp.public_key().clone(), 999);
        let tx = TransactionBuilder::new(&mut account)
            .operation(OperationBuilder::inflation().build())
            .operation(
                OperationBuilder::payment(
                    kp.public_key().clone(),
                    Asset::native(),
                    Amount::from_str("123.4").unwrap(),
                ).build(),
            )
            .with_memo(Memo::Id(123))
            .build();
        do_it(tx, "AAAAAGPgfnO/mUw71s3yUF9r1CS8IOgym90QLaqUHgG8D4URAAAAyAAAAAAAAAPoAAAAAAAAAAIAAAAAAAAAewAAAAIAAAAAAAAACQAAAAAAAAABAAAAAGPgfnO/mUw71s3yUF9r1CS8IOgym90QLaqUHgG8D4URAAAAAAAAAABJjViAAAAAAA==");
    }

    #[test]
    fn test_signed_transaction() {
        let kp = KeyPair::from_secret_seed(
            "SDFRU2NGDPXYIY67BVS6L6W4OY33HCFCEJQ73TZZPR3IDYVVI7BVPV5Q",
        ).unwrap();

        let mut account = Account::new(kp.public_key().clone(), 999);
        let tx = TransactionBuilder::new(&mut account)
            .operation(OperationBuilder::inflation().build())
            .build();
        let network = Network::public_network();
        let signed_tx = tx.sign(&kp, &network).unwrap();
        let expected_signature_base = vec![
            0x7A, 0xC3, 0x39, 0x97, 0x54, 0x4E, 0x31, 0x75, 0xD2, 0x66, 0xBD, 0x2, 0x24, 0x39,
            0xB2, 0x2C, 0xDB, 0x16, 0x50, 0x8C, 0x1, 0x16, 0x3F, 0x26, 0xE5, 0xCB, 0x2A, 0x3E,
            0x10, 0x45, 0xA9, 0x79, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x63, 0xE0, 0x7E, 0x73,
            0xBF, 0x99, 0x4C, 0x3B, 0xD6, 0xCD, 0xF2, 0x50, 0x5F, 0x6B, 0xD4, 0x24, 0xBC, 0x20,
            0xE8, 0x32, 0x9B, 0xDD, 0x10, 0x2D, 0xAA, 0x94, 0x1E, 0x1, 0xBC, 0xF, 0x85, 0x11, 0x0,
            0x0, 0x0, 0x64, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xE8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x0,
            0x0,
        ];
        assert_eq!(signed_tx.signature_base().unwrap(), expected_signature_base);
        do_it(signed_tx, "AAAAAGPgfnO/mUw71s3yUF9r1CS8IOgym90QLaqUHgG8D4URAAAAZAAAAAAAAAPoAAAAAAAAAAAAAAABAAAAAAAAAAkAAAAAAAAAAbwPhREAAABAkqlNirgebGCMoc0kdl7FLMl/k2q36LZN1EI7+kfY5xiGg9Mb0txYsIZY3zx1RREQywp/wgpLTpfHqIcnDs2HAg==");
    }
}