Skip to content

Commit 10d7a38

Browse files
committed
[DRAFT] Implement hmac for repeated pages
Incomplete: using a placeholder instead of the actual HMAC (that should use a private key, possibly key deterministically crated from the app hash (this would allow stored HMACs to remain valid for future executions of the app). Exploratory commit to modify the protocol to compute an hmac before dropping a code page, so that the required data for pages sent multiple times is reduced; moreover, the client could (in principle) store it permanently for future executions of the V-App).
1 parent 37f8c63 commit 10d7a38

File tree

3 files changed

+260
-67
lines changed

3 files changed

+260
-67
lines changed

client-sdk/src/vanadium_client.rs

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ use common::client_commands::{
1818
BufferType, ClientCommandCode, CommitPageMessage, CommitPageProofContinuedMessage,
1919
CommitPageProofContinuedResponse, CommitPageProofResponse, GetPageMessage,
2020
GetPageProofContinuedMessage, GetPageProofContinuedResponse, GetPageResponse, Message,
21-
MessageDeserializationError, ReceiveBufferMessage, ReceiveBufferResponse, SectionKind,
22-
SendBufferContinuedMessage, SendBufferMessage,
21+
MessageDeserializationError, PageProofKind, ReceiveBufferMessage, ReceiveBufferResponse,
22+
SectionKind, SendBufferContinuedMessage, SendBufferMessage, StorePageProofMessage,
2323
};
2424
use common::constants::{DEFAULT_STACK_START, PAGE_SIZE};
2525
use common::manifest::Manifest;
@@ -128,6 +128,7 @@ struct VAppEngine<E: std::fmt::Debug + Send + Sync + 'static> {
128128
engine_to_client_sender: mpsc::Sender<VAppMessage>,
129129
client_to_engine_receiver: mpsc::Receiver<ClientMessage>,
130130
print_writer: Box<dyn std::io::Write + Send>,
131+
stored_code_page_hmacs: Vec<Option<[u8; 32]>>,
131132
}
132133

133134
impl<E: std::fmt::Debug + Send + Sync + 'static> VAppEngine<E> {
@@ -166,6 +167,7 @@ impl<E: std::fmt::Debug + Send + Sync + 'static> VAppEngine<E> {
166167
(status, result) = match client_command_code {
167168
ClientCommandCode::GetPage => self.process_get_page(&result).await?,
168169
ClientCommandCode::CommitPage => self.process_commit_page(&result).await?,
170+
ClientCommandCode::StorePageProof => self.process_store_page_proof(&result).await?,
169171
_ => return Ok((status, result)),
170172
}
171173
}
@@ -204,10 +206,37 @@ impl<E: std::fmt::Debug + Send + Sync + 'static> VAppEngine<E> {
204206
let is_encrypted = header[0] != 0;
205207
let nonce: [u8; 12] = header[1..13].try_into().unwrap();
206208

207-
// Convert HashOutput<32> to [u8; 32]
208-
let proof: Vec<[u8; 32]> = proof.into_iter().map(|h| h.0).collect();
209+
// If this is a code page and we have a stored HMAC for it, send that instead of the Merkle proof.
210+
if section_kind == SectionKind::Code {
211+
if let Some(Some(hmac)) = self.stored_code_page_hmacs.get(page_index as usize) {
212+
// Build a single-element proof slice containing the HMAC
213+
let response = GetPageResponse::new(
214+
(*data).try_into().unwrap(),
215+
is_encrypted,
216+
nonce,
217+
1, // n
218+
1, // t
219+
PageProofKind::Hmac,
220+
&[*hmac],
221+
)
222+
.serialize();
209223

210-
// Calculate how many proof elements we can send in one message
224+
#[cfg(feature = "debug")]
225+
debug!(
226+
"Sending HMAC proof for code page {} instead of Merkle proof",
227+
page_index
228+
);
229+
230+
return self
231+
.transport
232+
.exchange(&apdu_continue(response))
233+
.await
234+
.map_err(VAppEngineError::TransportError);
235+
}
236+
}
237+
238+
// Fall back to Merkle proof
239+
let proof: Vec<[u8; 32]> = proof.into_iter().map(|h| h.0).collect();
211240
let t = min(proof.len(), GetPageResponse::max_proof_size()) as u8;
212241

213242
// Create the page response
@@ -217,6 +246,7 @@ impl<E: std::fmt::Debug + Send + Sync + 'static> VAppEngine<E> {
217246
nonce,
218247
proof.len() as u8,
219248
t,
249+
PageProofKind::Merkle,
220250
&proof[0..t as usize],
221251
)
222252
.serialize();
@@ -377,6 +407,32 @@ impl<E: std::fmt::Debug + Send + Sync + 'static> VAppEngine<E> {
377407
Ok((status, result))
378408
}
379409

410+
async fn process_store_page_proof(
411+
&mut self,
412+
command: &[u8],
413+
) -> Result<(StatusWord, Vec<u8>), VAppEngineError<E>> {
414+
let msg = StorePageProofMessage::deserialize(command)?;
415+
416+
#[cfg(feature = "debug")]
417+
debug!("<- StorePageProofMessage(page = {})", msg.page_index);
418+
419+
if msg.section_kind != SectionKind::Code {
420+
// The app will never ask to store an hmac as proof for non-code pages
421+
return Err(VAppEngineError::AccessViolation);
422+
}
423+
if msg.page_index as usize >= self.stored_code_page_hmacs.len() {
424+
self.stored_code_page_hmacs
425+
.resize(msg.page_index as usize + 1, None);
426+
}
427+
self.stored_code_page_hmacs[msg.page_index as usize] = Some(msg.hmac);
428+
429+
Ok(self
430+
.transport
431+
.exchange(&apdu_continue(vec![]))
432+
.await
433+
.map_err(VAppEngineError::TransportError)?)
434+
}
435+
380436
async fn process_send_buffer_generic(
381437
&mut self,
382438
command: &[u8],
@@ -580,6 +636,7 @@ impl<E: std::fmt::Debug + Send + Sync + 'static> VAppEngine<E> {
580636
ClientCommandCode::CommitPage => self.process_commit_page(&result).await?,
581637
ClientCommandCode::SendBuffer => self.process_send_buffer(&result).await?,
582638
ClientCommandCode::ReceiveBuffer => self.process_receive_buffer(&result).await?,
639+
ClientCommandCode::StorePageProof => self.process_store_page_proof(&result).await?,
583640
ClientCommandCode::SendBufferContinued
584641
| ClientCommandCode::GetPageProofContinued
585642
| ClientCommandCode::CommitPageProofContinued => {
@@ -693,6 +750,7 @@ impl<E: std::fmt::Debug + Send + Sync + 'static> GenericVanadiumClient<E> {
693750
engine_to_client_sender,
694751
client_to_engine_receiver,
695752
print_writer,
753+
stored_code_page_hmacs: Vec::new(),
696754
};
697755

698756
// Start the VAppEngine in a task

common/src/client_commands.rs

Lines changed: 91 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ pub enum ClientCommandCode {
6262
SendBuffer = 4,
6363
SendBufferContinued = 5,
6464
ReceiveBuffer = 6,
65+
StorePageProof = 7,
6566
}
6667

6768
impl TryFrom<u8> for ClientCommandCode {
@@ -76,12 +77,13 @@ impl TryFrom<u8> for ClientCommandCode {
7677
4 => Ok(ClientCommandCode::SendBuffer),
7778
5 => Ok(ClientCommandCode::SendBufferContinued),
7879
6 => Ok(ClientCommandCode::ReceiveBuffer),
80+
7 => Ok(ClientCommandCode::StorePageProof),
7981
_ => Err("Invalid value for ClientCommandCode"),
8082
}
8183
}
8284
}
8385

84-
#[derive(Debug, Clone, Copy)]
86+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8587
#[repr(u8)]
8688
pub enum SectionKind {
8789
Code = 0,
@@ -154,14 +156,33 @@ impl<'a> Message<'a> for GetPageMessage {
154156

155157
/// Message sent by client in response to the VM's GetPageProofMessage
156158
/// It contains the page's metadata, and the merkle proof of the page (or part of it)
159+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
160+
#[repr(u8)]
161+
pub enum PageProofKind {
162+
Merkle = 0,
163+
Hmac = 1,
164+
}
165+
166+
impl TryFrom<u8> for PageProofKind {
167+
type Error = &'static str;
168+
fn try_from(value: u8) -> Result<Self, Self::Error> {
169+
match value {
170+
0 => Ok(PageProofKind::Merkle),
171+
1 => Ok(PageProofKind::Hmac),
172+
_ => Err("Invalid page proof kind"),
173+
}
174+
}
175+
}
176+
157177
#[derive(Debug, Clone)]
158178
pub struct GetPageResponse<'a> {
159179
pub page_data: &'a [u8; PAGE_SIZE],
160-
pub is_encrypted: bool, // whether the page is encrypted
161-
pub nonce: [u8; 12], // nonce of the page encryption (all zeros if not encrypted)
162-
pub n: u8, // number of element in the proof
163-
pub t: u8, // number of proof elements in this message
164-
pub proof: &'a [[u8; 32]], // hashes of the proof
180+
pub is_encrypted: bool,
181+
pub nonce: [u8; 12],
182+
pub n: u8,
183+
pub t: u8,
184+
pub proof_kind: PageProofKind,
185+
pub proof: &'a [[u8; 32]], // Merkle proof elements OR single HMAC (len()==1)
165186
}
166187

167188
impl<'a> GetPageResponse<'a> {
@@ -172,6 +193,7 @@ impl<'a> GetPageResponse<'a> {
172193
nonce: [u8; 12],
173194
n: u8,
174195
t: u8,
196+
proof_kind: PageProofKind,
175197
proof: &'a [[u8; 32]],
176198
) -> Self {
177199
GetPageResponse {
@@ -180,12 +202,14 @@ impl<'a> GetPageResponse<'a> {
180202
nonce,
181203
n,
182204
t,
205+
proof_kind,
183206
proof,
184207
}
185208
}
186209

187210
pub const fn max_proof_size() -> usize {
188-
(MAX_APDU_DATA_SIZE - PAGE_SIZE - 1 - 12 - 1 - 1) / 32
211+
// page_data | n | t | is_encrypted | nonce(12) | proof_kind | proof elements
212+
(MAX_APDU_DATA_SIZE - PAGE_SIZE - 1 - 1 - 1 - 12 - 1) / 32
189213
}
190214
}
191215

@@ -197,13 +221,14 @@ impl<'a> Message<'a> for GetPageResponse<'a> {
197221
f(&[self.t]);
198222
f(&[self.is_encrypted as u8]);
199223
f(&self.nonce);
224+
f(&[self.proof_kind as u8]);
200225
for p in self.proof {
201226
f(p);
202227
}
203228
}
204229

205230
fn deserialize(data: &'a [u8]) -> Result<Self, MessageDeserializationError> {
206-
if data.len() < PAGE_SIZE + 1 + 1 + 1 + 12 {
231+
if data.len() < PAGE_SIZE + 1 + 1 + 1 + 12 + 1 {
207232
return Err(MessageDeserializationError::InvalidDataLength);
208233
}
209234
let page_data = data[0..PAGE_SIZE].try_into().unwrap();
@@ -217,13 +242,16 @@ impl<'a> Message<'a> for GetPageResponse<'a> {
217242
} else {
218243
[0; 12]
219244
};
220-
let proof_len = data.len() - (PAGE_SIZE + 1 + 1 + 1 + 12);
245+
let pk_byte = data[PAGE_SIZE + 15];
246+
let proof_kind = PageProofKind::try_from(pk_byte)
247+
.map_err(|_| MessageDeserializationError::InvalidDataLength)?;
248+
let proof_len = data.len() - (PAGE_SIZE + 1 + 1 + 1 + 12 + 1);
221249
if proof_len % 32 != 0 {
222250
return Err(MessageDeserializationError::InvalidDataLength);
223251
}
224252
let slice_len = proof_len / 32;
225253
let proof = unsafe {
226-
let ptr = data.as_ptr().add(PAGE_SIZE + 1 + 1 + 1 + 12) as *const [u8; 32];
254+
let ptr = data.as_ptr().add(PAGE_SIZE + 1 + 1 + 1 + 12 + 1) as *const [u8; 32];
227255
core::slice::from_raw_parts(ptr, slice_len)
228256
};
229257

@@ -233,10 +261,63 @@ impl<'a> Message<'a> for GetPageResponse<'a> {
233261
nonce,
234262
n,
235263
t,
264+
proof_kind,
236265
proof,
237266
})
238267
}
239268
}
269+
/// Message sent by the VM instructing the host to store a compact HMAC proof for a code page.
270+
#[derive(Debug, Clone)]
271+
pub struct StorePageProofMessage {
272+
pub command_code: ClientCommandCode,
273+
pub section_kind: SectionKind,
274+
pub page_index: u32,
275+
pub hmac: [u8; 32],
276+
}
277+
278+
impl StorePageProofMessage {
279+
#[inline]
280+
pub fn new(section_kind: SectionKind, page_index: u32, hmac: [u8; 32]) -> Self {
281+
StorePageProofMessage {
282+
command_code: ClientCommandCode::StorePageProof,
283+
section_kind,
284+
page_index,
285+
hmac,
286+
}
287+
}
288+
}
289+
290+
impl<'a> Message<'a> for StorePageProofMessage {
291+
#[inline]
292+
fn serialize_with<F: FnMut(&[u8])>(&self, mut f: F) {
293+
f(&[self.command_code as u8]);
294+
f(&[self.section_kind as u8]);
295+
f(&self.page_index.to_be_bytes());
296+
f(&self.hmac);
297+
}
298+
299+
fn deserialize(data: &'a [u8]) -> Result<Self, MessageDeserializationError> {
300+
if data.len() != 1 + 1 + 4 + 32 {
301+
return Err(MessageDeserializationError::InvalidDataLength);
302+
}
303+
let command_code = ClientCommandCode::try_from(data[0])
304+
.map_err(|_| MessageDeserializationError::InvalidClientCommandCode)?;
305+
if !matches!(command_code, ClientCommandCode::StorePageProof) {
306+
return Err(MessageDeserializationError::MismatchingClientCommandCode);
307+
}
308+
let section_kind = SectionKind::try_from(data[1])
309+
.map_err(|_| MessageDeserializationError::InvalidSectionKind)?;
310+
let page_index = u32::from_be_bytes([data[2], data[3], data[4], data[5]]);
311+
let mut hmac = [0u8; 32];
312+
hmac.copy_from_slice(&data[6..38]);
313+
Ok(StorePageProofMessage {
314+
command_code,
315+
section_kind,
316+
page_index,
317+
hmac,
318+
})
319+
}
320+
}
240321

241322
/// Message sent by the VM to request the rest of the proof, if it didn't fit
242323
/// in GetPageResponse

0 commit comments

Comments
 (0)