1use std::convert::TryInto;
3
4use anyhow::anyhow;
5use byteorder::{BigEndian, WriteBytesExt};
6use oasis_core_runtime::consensus::beacon;
7use rand_core::{OsRng, RngCore};
8
9use crate::{
10 context::Context,
11 core::common::crypto::{mrae::deoxysii, x25519},
12 crypto::signature::context::get_chain_context_for,
13 keymanager, module,
14 modules::core::Error,
15 state::CurrentState,
16 types::{
17 self,
18 transaction::{Call, CallFormat, CallResult},
19 },
20};
21
22const MAX_EPHEMERAL_KEY_AGE: beacon::EpochTime = 5;
27
28pub enum Metadata {
30 Empty,
31 EncryptedX25519DeoxysII {
32 pk: x25519::PublicKey,
34 sk: x25519::PrivateKey,
36 index: usize,
38 },
39}
40
41impl std::fmt::Debug for Metadata {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 match self {
44 Self::Empty => f.debug_struct("Metadata::Empty").finish(),
45 Self::EncryptedX25519DeoxysII { pk, index, .. } => f
46 .debug_struct("Metadata::EncryptedX25519DeoxysII")
47 .field("pk", pk)
48 .field("index", index)
49 .finish_non_exhaustive(),
50 }
51 }
52}
53
54pub fn get_key_pair_id(epoch: beacon::EpochTime) -> keymanager::KeyPairId {
56 keymanager::get_key_pair_id([
57 get_chain_context_for(types::callformat::CALL_DATA_KEY_PAIR_ID_CONTEXT_BASE).as_slice(),
58 &epoch.to_be_bytes(),
59 ])
60}
61
62fn verify_epoch<C: Context>(ctx: &C, epoch: beacon::EpochTime) -> Result<(), Error> {
63 if epoch > ctx.epoch() {
64 return Err(Error::InvalidCallFormat(anyhow!("epoch in the future")));
65 }
66 if epoch < ctx.epoch().saturating_sub(MAX_EPHEMERAL_KEY_AGE) {
67 return Err(Error::InvalidCallFormat(anyhow!(
68 "epoch too far in the past"
69 )));
70 }
71 Ok(())
72}
73
74pub fn decode_call<C: Context>(
79 ctx: &C,
80 call: Call,
81 index: usize,
82) -> Result<Option<(Call, Metadata)>, Error> {
83 decode_call_ex(ctx, call, index, false )
84}
85
86pub fn decode_call_ex<C: Context>(
92 ctx: &C,
93 call: Call,
94 index: usize,
95 assume_km_reachable: bool,
96) -> Result<Option<(Call, Metadata)>, Error> {
97 match call.format {
98 CallFormat::Plain => Ok(Some((call, Metadata::Empty))),
100
101 CallFormat::EncryptedX25519DeoxysII => {
103 if !call.method.is_empty() {
105 return Err(Error::InvalidCallFormat(anyhow!("non-empty method")));
106 }
107 let envelope: types::callformat::CallEnvelopeX25519DeoxysII =
109 cbor::from_value(call.body)
110 .map_err(|_| Error::InvalidCallFormat(anyhow!("bad call envelope")))?;
111 let pk = envelope.pk;
112
113 let key_manager = ctx
115 .key_manager()
116 .ok_or_else(|| Error::InvalidCallFormat(anyhow!("confidential txs unavailable")))?;
117
118 if !assume_km_reachable && CurrentState::with_env(|env| !env.is_execute()) {
121 return Ok(None);
122 }
123
124 let decrypt = |epoch: beacon::EpochTime| {
125 let keypair = key_manager
126 .get_or_create_ephemeral_keys(get_key_pair_id(epoch), epoch)
127 .map_err(|err| match err {
128 keymanager::KeyManagerError::InvalidEpoch(..) => {
129 Error::InvalidCallFormat(anyhow!("invalid epoch"))
130 }
131 _ => Error::Abort(err.into()),
132 })?;
133 let sk = keypair.input_keypair.sk;
134 deoxysii::box_open(
136 &envelope.nonce,
137 envelope.data.clone(),
138 vec![],
139 &envelope.pk.0,
140 &sk.0,
141 )
142 .map(|data| (data, sk))
143 };
144
145 let (data, sk) = if envelope.epoch > 0 {
148 verify_epoch(ctx, envelope.epoch)?;
149 decrypt(envelope.epoch)
150 } else {
151 decrypt(ctx.epoch()).or_else(|_| decrypt(ctx.epoch() - 1))
154 }
155 .map_err(Error::InvalidCallFormat)?;
156
157 let read_only = call.read_only;
158 let call: Call = cbor::from_slice(&data)
159 .map_err(|_| Error::InvalidCallFormat(anyhow!("malformed call")))?;
160
161 if call.read_only != read_only {
164 return Err(Error::InvalidCallFormat(anyhow!("read-only flag mismatch")));
165 }
166
167 Ok(Some((
168 call,
169 Metadata::EncryptedX25519DeoxysII { pk, sk, index },
170 )))
171 }
172 }
173}
174
175#[cfg(any(test, feature = "test"))]
176pub fn encode_call<C: Context>(
178 ctx: &C,
179 mut call: Call,
180 client_keypair: &(x25519_dalek::PublicKey, x25519_dalek::StaticSecret),
181) -> Result<Call, Error> {
182 match call.format {
183 CallFormat::Plain => Ok(call),
185
186 CallFormat::EncryptedX25519DeoxysII => {
188 let key_manager = ctx.key_manager().ok_or_else(|| {
189 Error::InvalidCallFormat(anyhow!("confidential transactions not available"))
190 })?;
191 let epoch = ctx.epoch();
192 let runtime_keypair = key_manager
193 .get_or_create_ephemeral_keys(get_key_pair_id(epoch), epoch)
194 .map_err(|err| Error::Abort(err.into()))?;
195 let runtime_pk = runtime_keypair.input_keypair.pk;
196 let nonce = [0u8; deoxysii::NONCE_SIZE];
197
198 Ok(Call {
199 format: call.format,
200 method: std::mem::take(&mut call.method),
201 body: cbor::to_value(types::callformat::CallEnvelopeX25519DeoxysII {
202 pk: client_keypair.0.into(),
203 nonce,
204 epoch,
205 data: deoxysii::box_seal(
206 &nonce,
207 cbor::to_vec(call),
208 vec![],
209 &runtime_pk.0,
210 &client_keypair.1,
211 )
212 .unwrap(),
213 }),
214 ..Default::default()
215 })
216 }
217 }
218}
219
220pub fn encode_result<C: Context>(
222 ctx: &C,
223 result: module::CallResult,
224 metadata: Metadata,
225) -> CallResult {
226 encode_result_ex(ctx, result, metadata, false )
227}
228
229pub fn encode_result_ex<C: Context>(
233 ctx: &C,
234 result: module::CallResult,
235 metadata: Metadata,
236 expose_failure: bool,
237) -> CallResult {
238 match metadata {
239 Metadata::Empty => result.into(),
241
242 Metadata::EncryptedX25519DeoxysII { pk, sk, index } => {
244 let result: CallResult = result.into();
246
247 if expose_failure {
248 if result.is_success() {
249 return CallResult::Ok(encrypt_result_x25519_deoxysii(
250 ctx, result, pk, sk, index,
251 ));
252 }
253
254 return result;
255 }
256
257 CallResult::Unknown(encrypt_result_x25519_deoxysii(ctx, result, pk, sk, index))
258 }
259 }
260}
261
262pub fn encrypt_result_x25519_deoxysii<C: Context>(
264 ctx: &C,
265 result: types::transaction::CallResult,
266 pk: x25519::PublicKey,
267 sk: x25519::PrivateKey,
268 index: usize,
269) -> cbor::Value {
270 let mut nonce = Vec::with_capacity(deoxysii::NONCE_SIZE);
271 if CurrentState::with_env(|env| env.is_execute()) {
272 nonce
274 .write_u64::<BigEndian>(ctx.runtime_header().round)
275 .unwrap();
276 nonce
277 .write_u32::<BigEndian>(index.try_into().unwrap())
278 .unwrap();
279 nonce.extend(&[0, 0, 0]);
280 } else {
281 nonce.resize(deoxysii::NONCE_SIZE, 0);
283 OsRng.fill_bytes(&mut nonce);
284 }
285 let nonce = nonce.try_into().unwrap();
286 let result = cbor::to_vec(result);
287 let data = deoxysii::box_seal(&nonce, result, vec![], &pk.0, &sk.0).unwrap();
288
289 cbor::to_value(types::callformat::ResultEnvelopeX25519DeoxysII { nonce, data })
291}
292
293#[cfg(any(test, feature = "test"))]
294pub fn decode_result<C: Context>(
295 ctx: &C,
296 format: CallFormat,
297 result: CallResult,
298 client_keypair: &(x25519_dalek::PublicKey, x25519_dalek::StaticSecret),
299) -> Result<module::CallResult, Error> {
300 if matches!(format, CallFormat::Plain) {
301 return Ok(result.into_call_result().expect("CallResult was Unknown"));
302 }
303 let envelope_value = match result {
304 CallResult::Ok(v) | CallResult::Unknown(v) => v,
305 CallResult::Failed {
306 module,
307 code,
308 message,
309 } => {
310 return Ok(module::CallResult::Failed {
311 module,
312 code,
313 message,
314 })
315 }
316 };
317 match format {
318 CallFormat::Plain => unreachable!("checked above"),
319 CallFormat::EncryptedX25519DeoxysII => {
320 let envelope: types::callformat::ResultEnvelopeX25519DeoxysII =
321 cbor::from_value(envelope_value)
322 .map_err(|_| Error::InvalidCallFormat(anyhow!("bad result envelope")))?;
323
324 let key_manager = ctx
327 .key_manager()
328 .ok_or_else(|| Error::InvalidCallFormat(anyhow!("confidential txs unavailable")))?;
329 let keypair = key_manager
330 .get_or_create_ephemeral_keys(get_key_pair_id(ctx.epoch()), ctx.epoch())
331 .map_err(|err| Error::Abort(err.into()))?;
332 let runtime_pk = keypair.input_keypair.pk;
333
334 let data = deoxysii::box_open(
335 &envelope.nonce,
336 envelope.data,
337 vec![],
338 &runtime_pk.0,
339 &client_keypair.1,
340 )
341 .map_err(Error::InvalidCallFormat)?;
342 let call_result: CallResult = cbor::from_slice(&data)
343 .map_err(|_| Error::InvalidCallFormat(anyhow!("malformed call")))?;
344 Ok(call_result
345 .into_call_result()
346 .expect("CallResult was Unknown"))
347 }
348 }
349}