1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
use filament_crypto::{SignBytes, Signature, VerificationKey};
use serde::{Deserialize, Serialize};
use sha2::{Digest as _, Sha256};

use crate::{input::Input, Address, ChainId};

/// A filament transaction.
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct Transaction {
    /// Authentication data for the transaction body.
    pub auth: Auth,
    /// Transaction body with inputs and auxiliary data.
    pub body: Body,
}

impl Transaction {
    pub fn inputs(&self) -> impl Iterator<Item = &Input> {
        self.body.inputs.iter()
    }
}

/// Authentication information.
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub enum Auth {
    /// Single signature.
    Ed25519 {
        verification_key: VerificationKey,
        signature: Signature,
    },
}

impl From<&Auth> for Address {
    fn from(auth: &Auth) -> Self {
        match auth {
            Auth::Ed25519 {
                verification_key, ..
            } => Address::from(*verification_key),
        }
    }
}

/// Body of a filament transaction.
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct Body {
    /// List of inputs carried by the transaction to advance the state machine.
    // TODO(xla): Use a container that can't be constructed without at least one element. e.g: https://github.com/cloudhead/nonempty
    pub inputs: Vec<Input>,

    /// Intended chain for the transaction to land on, to be included to prevent replays on other
    /// chains.
    pub chain_id: ChainId,
    /// Maximum height until the transaction is valid.
    pub max_height: Option<u64>,

    /// Account id to match tx signers account.
    pub account_id: u64,
    /// Account sequence to match tx signers account state.
    pub sequence: u64,
}

impl SignBytes for Body {
    fn sign_bytes(&self) -> eyre::Result<Vec<u8>> {
        let mut hasher = Sha256::new();
        hasher.update(filament_encoding::to_bytes(&self)?);
        Ok(hasher.finalize().to_vec())
    }
}