Skip to content

Commit c3cda81

Browse files
committed
cardano: use custom cbor decoder to save binary space
Coded by ChatGPT o1. Reduces binary size by 2760 bytes.
1 parent 402201e commit c3cda81

File tree

1 file changed

+195
-28
lines changed
  • src/rust/bitbox02-rust/src/hww/api/cardano

1 file changed

+195
-28
lines changed

src/rust/bitbox02-rust/src/hww/api/cardano/address.rs

Lines changed: 195 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -62,50 +62,215 @@ fn decode_shelley_payment_address(params: &params::Params, address: &str) -> Res
6262
Ok(data)
6363
}
6464

65+
/// CBOR major type for unsigned integers (major type 0).
66+
/// See [RFC 7049 §2.1](https://www.rfc-editor.org/rfc/rfc7049#section-2.1).
67+
const MAJOR_TYPE_UNSIGNED: u8 = 0x00; // 0x00..0x1b
68+
69+
/// CBOR major type for byte strings (major type 2).
70+
/// See [RFC 7049 §2.1](https://www.rfc-editor.org/rfc/rfc7049#section-2.1).
71+
const MAJOR_TYPE_BYTES: u8 = 0x40; // 0x40..0x5b
72+
73+
/// CBOR major type for arrays (major type 4).
74+
/// See [RFC 7049 §2.1](https://www.rfc-editor.org/rfc/rfc7049#section-2.1).
75+
const MAJOR_TYPE_ARRAY: u8 = 0x80; // 0x80..0x9b
76+
77+
/// CBOR major type for maps (major type 5).
78+
/// See [RFC 7049 §2.1](https://www.rfc-editor.org/rfc/rfc7049#section-2.1).
79+
const MAJOR_TYPE_MAP: u8 = 0xa0; // 0xa0..0xbb
80+
81+
/// CBOR major type for tags (major type 6).
82+
/// See [RFC 7049 §2.1](https://www.rfc-editor.org/rfc/rfc7049#section-2.1).
83+
const MAJOR_TYPE_TAG: u8 = 0xc0; // 0xc0..0xdb
84+
85+
// We use a custom CborReader instead of the minicbor library as it saves almost 3kB of binary
86+
// space.
87+
struct CborReader<'a> {
88+
buf: &'a [u8],
89+
}
90+
91+
impl<'a> CborReader<'a> {
92+
fn new(buf: &'a [u8]) -> Self {
93+
Self { buf }
94+
}
95+
96+
/// Reads the next byte from the internal buffer.
97+
/// Returns an error if the buffer is empty.
98+
fn read_u8(&mut self) -> Result<u8, ()> {
99+
if self.buf.is_empty() {
100+
return Err(());
101+
}
102+
let b = self.buf[0];
103+
self.buf = &self.buf[1..];
104+
Ok(b)
105+
}
106+
107+
/// Reads a CBOR unsigned integer, interpreting the `initial` byte's lower 5 bits
108+
/// to determine how many additional bytes to consume.
109+
/// See [RFC 7049 §2.1](https://www.rfc-editor.org/rfc/rfc7049#section-2.1) for details.
110+
fn read_uint(&mut self, initial: u8) -> Result<u64, ()> {
111+
let additional = initial & 0x1f;
112+
match additional {
113+
n @ 0..=23 => Ok(n as u64),
114+
24 => {
115+
let b = self.read_u8()? as u64;
116+
Ok(b)
117+
}
118+
25 => {
119+
if self.buf.len() < 2 {
120+
return Err(());
121+
}
122+
let val = u16::from_be_bytes([self.buf[0], self.buf[1]]) as u64;
123+
self.buf = &self.buf[2..];
124+
Ok(val)
125+
}
126+
26 => {
127+
if self.buf.len() < 4 {
128+
return Err(());
129+
}
130+
let val =
131+
u32::from_be_bytes([self.buf[0], self.buf[1], self.buf[2], self.buf[3]]) as u64;
132+
self.buf = &self.buf[4..];
133+
Ok(val)
134+
}
135+
27 => {
136+
if self.buf.len() < 8 {
137+
return Err(());
138+
}
139+
let val = u64::from_be_bytes([
140+
self.buf[0],
141+
self.buf[1],
142+
self.buf[2],
143+
self.buf[3],
144+
self.buf[4],
145+
self.buf[5],
146+
self.buf[6],
147+
self.buf[7],
148+
]);
149+
self.buf = &self.buf[8..];
150+
Ok(val)
151+
}
152+
_ => Err(()),
153+
}
154+
}
155+
156+
/// Reads and returns a CBOR tag (major type 6) as a `u64`.
157+
/// See [RFC 7049 §2.4](https://www.rfc-editor.org/rfc/rfc7049#section-2.4).
158+
fn read_tag(&mut self) -> Result<u64, ()> {
159+
let b = self.read_u8()?;
160+
if (b & 0xe0) != MAJOR_TYPE_TAG {
161+
return Err(());
162+
}
163+
self.read_uint(b)
164+
}
165+
166+
/// Reads and returns a byte string (major type 2). Returns a slice of the
167+
/// requested length, advancing the reader.
168+
/// See [RFC 7049 §2.1](https://www.rfc-editor.org/rfc/rfc7049#section-2.1).
169+
fn read_bytes(&mut self) -> Result<&'a [u8], ()> {
170+
let b = self.read_u8()?;
171+
if (b & 0xe0) != MAJOR_TYPE_BYTES {
172+
return Err(());
173+
}
174+
let len = self.read_uint(b)? as usize;
175+
if self.buf.len() < len {
176+
return Err(());
177+
}
178+
let out = &self.buf[..len];
179+
self.buf = &self.buf[len..];
180+
Ok(out)
181+
}
182+
183+
/// Reads and returns the length of an array (major type 4).
184+
/// See [RFC 7049 §2.1](https://www.rfc-editor.org/rfc/rfc7049#section-2.1).
185+
fn read_array_len(&mut self) -> Result<usize, ()> {
186+
let b = self.read_u8()?;
187+
if (b & 0xe0) != MAJOR_TYPE_ARRAY {
188+
return Err(());
189+
}
190+
let len = self.read_uint(b)? as usize;
191+
Ok(len)
192+
}
193+
194+
/// Reads and returns the length of a map (major type 5).
195+
/// See [RFC 7049 §2.1](https://www.rfc-editor.org/rfc/rfc7049#section-2.1).
196+
fn read_map_len(&mut self) -> Result<usize, ()> {
197+
let b = self.read_u8()?;
198+
if (b & 0xe0) != MAJOR_TYPE_MAP {
199+
return Err(());
200+
}
201+
let len = self.read_uint(b)? as usize;
202+
Ok(len)
203+
}
204+
205+
/// Reads a 32-bit unsigned integer from the buffer,
206+
/// expecting a CBOR major type 0 (unsigned integer).
207+
fn read_u32(&mut self) -> Result<u32, ()> {
208+
let b = self.read_u8()?;
209+
if (b & 0xe0) != MAJOR_TYPE_UNSIGNED {
210+
return Err(());
211+
}
212+
let val = self.read_uint(b)?;
213+
if val > u32::MAX as u64 {
214+
return Err(());
215+
}
216+
Ok(val as u32)
217+
}
218+
}
219+
220+
fn decode_u32_from_cbor_bytes(data: &[u8]) -> Result<u32, ()> {
221+
let mut r = CborReader::new(data);
222+
r.read_u32()
223+
}
224+
65225
/// Decode a base58-encoded Byron payment address, validate it's checksum and that it was encoded for the right network.
66226
///
67227
/// A byron address is cbor encoded data: https://raw.githubusercontent.com/cardano-foundation/CIPs/0081c890995ff94618145ae5beb7f288c029a86a/CIP-0019/CIP-0019-byron-addresses.cddl
68228
///
69229
/// See also:
70230
/// - https://github.com/input-output-hk/cardano-ledger/blob/d0aa86ded0b973b09b629e5aa62aa1e71364d088/eras/alonzo/test-suite/cddl-files/alonzo.cddl#L134-L135
71231
/// - https://github.com/input-output-hk/technical-docs/blob/8d4f08bc05ec611f3943cdc09a4ae18e72a0eb3c/cardano-components/cardano-wallet/doc/About-Address-Format---Byron.md
72-
fn decode_byron_payment_address(params: &params::Params, address: &str) -> Result<Vec<u8>, ()> {
232+
pub fn decode_byron_payment_address(params: &params::Params, address: &str) -> Result<Vec<u8>, ()> {
73233
let base58_decoded = bitcoin::base58::decode(address).or(Err(()))?;
74-
let payload = {
75-
let mut decoder = minicbor::Decoder::new(&base58_decoded);
76-
if decoder.array().or(Err(()))?.ok_or(())? != 2 {
77-
return Err(());
78-
}
79-
if decoder.tag().or(Err(()))? != minicbor::data::IanaTag::Cbor.tag() {
80-
return Err(());
81-
}
82-
let payload = decoder.bytes().or(Err(()))?;
83-
let address_crc = decoder.u32().or(Err(()))?;
84-
if crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC).checksum(payload) != address_crc {
85-
return Err(());
86-
}
87-
payload
88-
};
234+
let mut top = CborReader::new(&base58_decoded);
235+
236+
// Top-level array: [ (tag=24) bytes, crc_u32 ]
237+
if top.read_array_len()? != 2 {
238+
return Err(());
239+
}
240+
241+
let tag = top.read_tag()?;
242+
if tag != 24 {
243+
return Err(());
244+
}
245+
246+
let payload_slice = top.read_bytes()?;
247+
let address_crc = top.read_u32()?;
89248

90-
let mut decoder = minicbor::Decoder::new(payload);
91-
// Array with three elements.
92-
if decoder.array().or(Err(()))?.ok_or(())? != 3 {
249+
if crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC).checksum(payload_slice) != address_crc {
93250
return Err(());
94251
}
252+
253+
let mut p = CborReader::new(payload_slice);
254+
// payload: [rootDigest: bytes(28), attributes: map, type: u32]
255+
if p.read_array_len()? != 3 {
256+
return Err(());
257+
}
258+
95259
// First element: address root digest.
96-
if decoder.bytes().or(Err(()))?.len() != 28 {
260+
if p.read_bytes()?.len() != 28 {
97261
return Err(());
98262
}
263+
99264
// Second element: address attributes map. Item with key 2 is the network magic. If absent, it
100265
// is mainnet. If present, must not be mainnet.
266+
let map_len = p.read_map_len()?;
101267
let mut magic: Option<u32> = None;
102-
for item in decoder
103-
.map_iter::<u32, minicbor::bytes::ByteVec>()
104-
.or(Err(()))?
105-
{
106-
let (key, value) = item.or(Err(()))?;
268+
for _ in 0..map_len {
269+
let key = p.read_u32()?;
270+
let val = p.read_bytes()?;
107271
if key == 2 {
108-
magic = Some(minicbor::decode(&value).or(Err(()))?);
272+
let m = decode_u32_from_cbor_bytes(val)?;
273+
magic = Some(m);
109274
break;
110275
}
111276
}
@@ -123,15 +288,17 @@ fn decode_byron_payment_address(params: &params::Params, address: &str) -> Resul
123288
}
124289
}
125290
}
291+
126292
// Third element: address type
127-
let typ = decoder.u32().or(Err(()))?;
293+
let typ = p.read_u32()?;
128294
if typ != 0 && typ != 2 {
129295
return Err(());
130296
}
131297
Ok(base58_decoded)
132298
}
133299

134-
/// Decode a Byron or Shelley payment address string and check that it was encoded for the right network.
300+
/// Decode a Byron or Shelley payment address string and check that it was encoded for the right
301+
/// network.
135302
pub fn decode_payment_address(params: &params::Params, address: &str) -> Result<Vec<u8>, Error> {
136303
if let Ok(address) = decode_shelley_payment_address(params, address) {
137304
return Ok(address);

0 commit comments

Comments
 (0)