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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
//! Transaction types.
use borsh::{BorshDeserialize, BorshSerialize};
use super::{AccountId, Action, CryptoHash, PublicKey, SecretKey, Signature};
/// An unsigned transaction.
#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct Transaction {
/// The account that signs and pays for the transaction.
pub signer_id: AccountId,
/// The public key of the signer.
pub public_key: PublicKey,
/// Nonce for replay protection (must be greater than previous nonce).
pub nonce: u64,
/// The account that receives the transaction.
pub receiver_id: AccountId,
/// A recent block hash for transaction validity.
pub block_hash: CryptoHash,
/// The actions to execute.
pub actions: Vec<Action>,
}
impl Transaction {
/// Create a new transaction.
pub fn new(
signer_id: AccountId,
public_key: PublicKey,
nonce: u64,
receiver_id: AccountId,
block_hash: CryptoHash,
actions: Vec<Action>,
) -> Self {
Self {
signer_id,
public_key,
nonce,
receiver_id,
block_hash,
actions,
}
}
/// Get the hash of this transaction (for signing).
pub fn get_hash(&self) -> CryptoHash {
let bytes = borsh::to_vec(self).expect("transaction serialization should never fail");
CryptoHash::hash(&bytes)
}
/// Get the raw bytes of this transaction (for signing).
pub fn get_hash_and_size(&self) -> (CryptoHash, usize) {
let bytes = borsh::to_vec(self).expect("transaction serialization should never fail");
(CryptoHash::hash(&bytes), bytes.len())
}
/// Sign this transaction with a secret key.
pub fn sign(self, signer: &SecretKey) -> SignedTransaction {
let hash = self.get_hash();
let signature = signer.sign(hash.as_bytes());
SignedTransaction {
transaction: self,
signature,
}
}
}
/// A signed transaction ready to be sent.
#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct SignedTransaction {
/// The unsigned transaction.
pub transaction: Transaction,
/// The signature.
pub signature: Signature,
}
impl SignedTransaction {
/// Get the hash of the signed transaction (transaction hash).
pub fn get_hash(&self) -> CryptoHash {
self.transaction.get_hash()
}
/// Serialize to bytes for RPC submission.
pub fn to_bytes(&self) -> Vec<u8> {
borsh::to_vec(self).expect("signed transaction serialization should never fail")
}
/// Serialize to base64 for RPC submission.
pub fn to_base64(&self) -> String {
use base64::{Engine as _, engine::general_purpose::STANDARD};
STANDARD.encode(self.to_bytes())
}
/// Deserialize from bytes.
///
/// Use this to reconstruct a signed transaction that was serialized with [`to_bytes`](Self::to_bytes).
///
/// # Example
///
/// ```rust,ignore
/// use near_kit::SignedTransaction;
/// let bytes: Vec<u8> = /* received from offline signer */;
/// let signed_tx = SignedTransaction::from_bytes(&bytes)?;
/// ```
pub fn from_bytes(bytes: &[u8]) -> Result<Self, crate::error::Error> {
borsh::from_slice(bytes).map_err(|e| {
crate::error::Error::InvalidTransaction(format!(
"Failed to deserialize signed transaction: {}",
e
))
})
}
/// Deserialize from base64.
///
/// Use this to reconstruct a signed transaction that was serialized with [`to_base64`](Self::to_base64).
///
/// # Example
///
/// ```rust,no_run
/// # use near_kit::SignedTransaction;
/// let base64_str = "AgAAAGFsaWNlLnRlc3RuZXQ...";
/// let signed_tx = SignedTransaction::from_base64(base64_str)?;
/// # Ok::<(), near_kit::Error>(())
/// ```
pub fn from_base64(s: &str) -> Result<Self, crate::error::Error> {
use base64::{Engine as _, engine::general_purpose::STANDARD};
let bytes = STANDARD.decode(s).map_err(|e| {
crate::error::Error::InvalidTransaction(format!("Invalid base64: {}", e))
})?;
Self::from_bytes(&bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transaction_hash() {
let secret = SecretKey::generate_ed25519();
let public = secret.public_key();
let tx = Transaction::new(
"alice.testnet".parse().unwrap(),
public,
1,
"bob.testnet".parse().unwrap(),
CryptoHash::ZERO,
vec![],
);
let hash = tx.get_hash();
assert!(!hash.is_zero());
}
#[test]
fn test_sign_transaction() {
let secret = SecretKey::generate_ed25519();
let public = secret.public_key();
let tx = Transaction::new(
"alice.testnet".parse().unwrap(),
public,
1,
"bob.testnet".parse().unwrap(),
CryptoHash::ZERO,
vec![],
);
let signed = tx.sign(&secret);
assert!(!signed.to_bytes().is_empty());
}
}