Skip to main content

heddle_api/
mint_root_association.rs

1//! Checked wire decoding for messages whose mint-root association is security
2//! sensitive. Protobuf oneofs otherwise decode duplicate alternatives last-wins.
3
4use prost::Message;
5
6use crate::heddle::api::v1alpha2::{SpoolCreationProof, ThreadControlAuthority};
7
8const OWNER_MINT_ROOT_ATTACHMENT_FIELD: u64 = 4;
9const PASSKEY_MINT_ROOT_ATTACHMENT_FIELD: u64 = 6;
10const MAX_PROTOBUF_FIELD_NUMBER: u64 = (1 << 29) - 1;
11
12/// Failure while checking or decoding a mint-root association envelope.
13#[derive(Debug, thiserror::Error)]
14pub enum MintRootAssociationWireError {
15    /// Both proof alternatives occurred in the original bytes.
16    #[error("mint-root association contains both owner-v1 and passkey-v2 fields")]
17    BothArms,
18    /// The input is not a well-formed protobuf message.
19    #[error("mint-root association contains malformed protobuf wire bytes")]
20    Malformed,
21    /// The checked bytes could not be decoded as the requested message.
22    #[error("mint-root association protobuf decode failed: {0}")]
23    Decode(#[from] prost::DecodeError),
24}
25
26fn read_varint(bytes: &[u8], offset: &mut usize) -> Result<u64, MintRootAssociationWireError> {
27    let mut value = 0_u64;
28    for index in 0..10 {
29        let byte = *bytes
30            .get(*offset)
31            .ok_or(MintRootAssociationWireError::Malformed)?;
32        *offset += 1;
33        if index == 9 && byte > 1 {
34            return Err(MintRootAssociationWireError::Malformed);
35        }
36        value |= u64::from(byte & 0x7f) << (index * 7);
37        if byte & 0x80 == 0 {
38            return Ok(value);
39        }
40    }
41    Err(MintRootAssociationWireError::Malformed)
42}
43
44fn skip(
45    bytes: &[u8],
46    offset: &mut usize,
47    length: usize,
48) -> Result<(), MintRootAssociationWireError> {
49    *offset = offset
50        .checked_add(length)
51        .filter(|end| *end <= bytes.len())
52        .ok_or(MintRootAssociationWireError::Malformed)?;
53    Ok(())
54}
55
56/// Scan top-level protobuf tags and reject an encoding that contains both
57/// mint-root alternatives. Call this before any ordinary protobuf decoder.
58pub fn verify_mint_root_association_wire(bytes: &[u8]) -> Result<(), MintRootAssociationWireError> {
59    let mut offset = 0;
60    let mut owner = false;
61    let mut passkey = false;
62    while offset < bytes.len() {
63        let key = read_varint(bytes, &mut offset)?;
64        let field = key >> 3;
65        let wire_type = key & 0x07;
66        if field == 0 || field > MAX_PROTOBUF_FIELD_NUMBER {
67            return Err(MintRootAssociationWireError::Malformed);
68        }
69        match field {
70            OWNER_MINT_ROOT_ATTACHMENT_FIELD => owner = true,
71            PASSKEY_MINT_ROOT_ATTACHMENT_FIELD => passkey = true,
72            _ => {}
73        }
74        if owner && passkey {
75            return Err(MintRootAssociationWireError::BothArms);
76        }
77        match wire_type {
78            0 => {
79                read_varint(bytes, &mut offset)?;
80            }
81            1 => skip(bytes, &mut offset, 8)?,
82            2 => {
83                let length = usize::try_from(read_varint(bytes, &mut offset)?)
84                    .map_err(|_| MintRootAssociationWireError::Malformed)?;
85                skip(bytes, &mut offset, length)?;
86            }
87            5 => skip(bytes, &mut offset, 4)?,
88            _ => return Err(MintRootAssociationWireError::Malformed),
89        }
90    }
91    Ok(())
92}
93
94/// Verify the raw oneof tags, then decode a `ThreadControlAuthority`.
95pub fn decode_thread_control_authority_for_verification(
96    bytes: &[u8],
97) -> Result<ThreadControlAuthority, MintRootAssociationWireError> {
98    verify_mint_root_association_wire(bytes)?;
99    Ok(ThreadControlAuthority::decode(bytes)?)
100}
101
102/// Verify the raw oneof tags, then decode a `SpoolCreationProof`.
103pub fn decode_spool_creation_proof_for_verification(
104    bytes: &[u8],
105) -> Result<SpoolCreationProof, MintRootAssociationWireError> {
106    verify_mint_root_association_wire(bytes)?;
107    Ok(SpoolCreationProof::decode(bytes)?)
108}