Skip to content

Commit 8843e57

Browse files
authored
Merge pull request #290 from Zondax/fix/irc21
Fix IRC21 message format
2 parents dc51d2b + 77afc46 commit 8843e57

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+584
-206
lines changed

app/Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ INCLUDES_PATH += $(CURDIR)/src/common
8080
INCLUDES_PATH += $(CURDIR)/../deps/tinycbor/src
8181
INCLUDES_PATH += $(CURDIR)/../deps/nanopb/
8282

83+
########################################
84+
# Application communication interfaces #
85+
########################################
86+
ENABLE_BLUETOOTH = 1
87+
#ENABLE_NFC = 1
88+
8389
########################################
8490
# NBGL custom features #
8591
########################################

app/Makefile.version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ APPVERSION_M=4
33
# This is the minor version of this release
44
APPVERSION_N=0
55
# This is the patch version of this release
6-
APPVERSION_P=4
6+
APPVERSION_P=5

app/rust/src/candid_header.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,23 @@ use crate::{
55
};
66

77
#[cfg_attr(any(feature = "derive-debug", test), derive(Debug))]
8-
pub struct CandidHeader<const MAX_FIELDS: usize, const MAX_ARGS: usize> {
9-
pub type_table: TypeTable<MAX_FIELDS>,
8+
pub struct CandidHeader<const MAX_ARGS: usize> {
9+
pub type_table: TypeTable,
1010
pub arguments: ArgumentTypes<MAX_ARGS>,
1111
}
1212

13-
impl<const MAX_FIELDS: usize, const MAX_ARGS: usize> CandidHeader<MAX_FIELDS, MAX_ARGS> {
14-
pub fn new(type_table: TypeTable<MAX_FIELDS>, arguments: ArgumentTypes<MAX_ARGS>) -> Self {
13+
impl<const MAX_ARGS: usize> CandidHeader<MAX_ARGS> {
14+
pub fn new(type_table: TypeTable, arguments: ArgumentTypes<MAX_ARGS>) -> Self {
1515
Self {
1616
type_table,
1717
arguments,
1818
}
1919
}
2020
}
2121

22-
pub fn parse_candid_header<const MAX_FIELDS: usize, const MAX_ARGS: usize>(
22+
pub fn parse_candid_header<const MAX_ARGS: usize>(
2323
input: &[u8],
24-
) -> Result<(&[u8], CandidHeader<MAX_FIELDS, MAX_ARGS>), ParserError> {
24+
) -> Result<(&[u8], CandidHeader<MAX_ARGS>), ParserError> {
2525
// 1. Magic number
2626
let (rem, _) = nom::bytes::complete::tag("DIDL")(input)
2727
.map_err(|_: nom::Err<ParserError>| ParserError::UnexpectedError)?;

app/rust/src/constants.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,19 @@ pub const DEFAULT_SENDER: u8 = 0x04;
6262
// Defines the minimum number of elements
6363
// in our candid type table in order
6464
// to parse the type using it
65-
pub const MAX_TABLE_FIELDS: usize = 12;
65+
pub const MAX_TABLE_FIELDS: usize = 15;
66+
67+
// Maximum fields per type entry
68+
// Analysis of ICRC-21 test vectors shows max 4 fields per type
69+
pub const MAX_FIELDS_PER_TYPE: usize = 10;
6670
// the max number of candid arguments in memory
6771
pub const MAX_ARGS: usize = 5;
6872

6973
// The size of the hash
7074
pub const SHA256_DIGEST_LENGTH: usize = 32;
75+
76+
// Candid Header Entry Type
77+
pub const CANDID_HEADER_ENTRY_TYPE: usize = 4;
78+
79+
// Display Record Type
80+
pub const DISPLAY_RECORD_TYPE: usize = 5;

app/rust/src/ffi.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ mod ffi_verify_cert {
2929
verify_certificate::rs_verify_certificate,
3030
};
3131

32-
const CERT: &str = "d9d9f7a364747265658301830182045820e7819691af7d32b3b7430996762358b90001c194baf168cbf66fa4bc7b02eaa683018204582083adf0dfaa3182c16f61b7e7a93ea61a9500f4f764c40421cf72c61ec6c29ca683024e726571756573745f73746174757383018301830182045820a634cd52a537fe1472c8d715b7df4da47ae9cb1d712aa630b47d2df0dfd0c6d7830183018301830183018204582046d18fc1f1191c8f6836c8ece13c8fd9688a56f0537b95195953cade214d3a5f8302582038ea4db10868be10af596cfb1c1d557fd135d774720ac546d4df842bbefd5d4f83018302457265706c79820358ee4449444c0b6b02bc8a0104c5fed201016b04d1c4987c03a3f2efe602029a8597e60302e3c581900f026c01fc91f4f805716c02fc91f4f80571c498b1b50d7d6c02efcee7800409e29fdcc806056b029ee0b53b06fcdfd79a0f716c02f99cba840807dcec99f409716d086c02007101716c02aeaeb1cc050ad880c6d007716e760100000002656e0003066d6574686f640d69637263325f617070726f76650a746573745f6669656c641054657374206669656c642076616c75650d616e6f746865725f6669656c640d416e6f746865722076616c756517496e74656e7420746f2069637263325f617070726f76658302467374617475738203477265706c6965648204582011dd41ee7dd9b2a15fa3324e05ce4a2b5e03fba4a1d8919901927652854ffc1182045820cee39b9eaefe16c51951d684376e5284b7a79f1e746af4e0cae5a1a926b550a9820458206be70aaeb3bbe7a9308c5ef39d108685edef57c6b4e9705dd32ec00f4371b57482045820de720e89d1d92fe1d07d4bf44b57be7c6225c9056b99e3373a437ffc184faec5820458204a2d65fdde220461793ce98e9302a08ecddb7b07c5ae1116da379dc8840f842f82045820ae6934dda4d3b666b15530ab97127cc31c23ea834f061aa63ade1fde01768e9b830182045820dca3e48639d8b08e2a674f8337f76e2f5bd90b0d2ec7664627ea6a3ecf9cd10683024474696d6582034995aaa9cbd7c2dfa018697369676e6174757265583093c79c491e90989d416553a94fbdd73941e406efe81e65776db00b31a267d3a777fddb9f142b8ba953a48f3d5b67e4f66a64656c65676174696f6ea2697375626e65745f6964581d806b712f189b5651e7349743820c5f45a81592464c25048e79f25cd6026b636572746966696361746559027dd9d9f7a26474726565830182045820b63acd339116f6b01d11462ef4c0dba98d1e124737eea9038fa30c08dbecbe8d83018301820458201252cea180dcebc7baba86ad402e724d24ff040463cc6db912689789824feb078302467375626e6574830183018204582001f6986a6a6e353c4cc6ce59203166d6fcdfaea1f9f20a9dc1939219368e7a8d8301830182045820654f222a0d7785c9406b4cc30970eae4ac4e992191aa2097005a315948ace9db830182045820c809a2f71c483679ea083fca3da5837de2906de073159c766b43963e9eeeea6883018302581d806b712f189b5651e7349743820c5f45a81592464c25048e79f25cd602830183024f63616e69737465725f72616e6765738203581bd9d9f781824a0000000000b0000001014a0000000000bfffff010183024a7075626c69635f6b657982035885308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c05030201036100af4f78ab97b3cdc76075f552e83842d5b07c4e35f9f83614b4976e70751f94ea1bc5ebec45892086f4344ea8fb3f7e3a053da28155c0ac501ab6a26ccfc60e729d0bbe91bf0772837cf2911fb4e952d066daf14ea45c08c2104cc69bf221a4f8820458202e465da6f8bd618119e1767791e131ce9696113a8aff5a118c28fdd4992892a082045820853cc4424d45777421ca30310ee0c7cb8c07972a997f696a9de6a242bd8f4af6820458203cf359df3cf0830ea62ec97a8be98a43a4cfde07ec1252c95609d34163bc52a383024474696d65820349bb8682d2c1c1dfa018697369676e61747572655830aff4319e113e1aa58603e08a9ad2c80992fafea6945c79c2901747a797702e5dc5d05dd0de6ae3f4d6a898e80922daf6";
33-
const CALL: &str = "d9d9f7a167636f6e74656e74a763617267586d4449444c066e7d6d7b6e016e786c02b3b0dac30368ad86ca8305026c08c6fcb60200ba89e5c20402a2de94eb060282f3f3910c03d8a38ca80d7d919c9cbf0d00dea7f7da0d03cb96dcb40e0401050000000080c2d72f000100c24c00aacc4118010a00000000000000070101006b63616e69737465725f69644a0000000000b003e601016e696e67726573735f657870697279c24818417e48714bc8006b6d6574686f645f6e616d656d69637263325f617070726f7665656e6f6e636550393bbfbb9c828604a08e77d4624a01f56c726571756573745f747970656463616c6c6673656e646572582005020000ff0000060000000200000a050000050b000000000b00060600006a0e";
34-
const CONSENT: &str = "d9d9f7a167636f6e74656e74a76361726758cb4449444c076d7b6e766c02aeaeb1cc0501d880c6d007716b028beabfc2067fa9898b8f0a7f6e036c02efcee7800402c4fbf2db05046c03d6fca70200e1edeb4a7184f7fee80a0501066d4449444c066e7d6d7b6e016e786c02b3b0dac30368ad86ca8305026c08c6fcb60200ba89e5c20402a2de94eb060282f3f3910c03d8a38ca80d7d919c9cbf0d00dea7f7da0d03cb96dcb40e0401050000000080c2d72f000100c24c00aacc4118010a00000000000000070101000d69637263325f617070726f76650002656e01016b63616e69737465725f69644a0000000000b003e601016e696e67726573735f657870697279c24818417e48714bc8006b6d6574686f645f6e616d6578246963726332315f63616e69737465725f63616c6c5f636f6e73656e745f6d657373616765656e6f6e6365507685da12a7aa34a4b240e5089e8e62366c726571756573745f747970656463616c6c6673656e6465724104";
32+
const CERT: &str = "d9d9f7a3647472656583018301820458209b2d6a38f0e5c7ac52a8f7bf6d7751b79b0100857c870eae71666c75d4a1f7f5830182045820380a9672b9af551df307e91c777b4fa8f37960e0a107cf489c759b1ef0dfe33383024e726571756573745f7374617475738301830183018301830183018204582010d2494eb22918a6c994dc6fefb58f7d7fb1b2d17a52ee9edf28e241a6d7a783830182045820f5d09b5a356a77d4820aeb17fcbcb93a0fd55ddee4ee3f8dde4bf7cbfd397b9d83018204582046a6c3582218264ea6d3c34dc225b09b642605b1753eca2961c06eabfa7cb309830258200db17a4999e6d1bb9dadb9042489dfe1eee589e3f036aa1c7bfe12964c1ec75a83018302457265706c7982035901234449444c0f6b02bc8a0101c5fed2010c6c02efcee7800402e29fdcc806046c02aeaeb1cc0503d880c6d007716e766b029ee0b53b05fcdfd79a0f716c02f99cba840806dcec99f409716d076c02007101086b04cdf1cbbe030991c38ee7040ae998e3c30a0bebd2e8d60f0b6c01b99adecb01716c03c295a993017bd8a38ca80d78d8def6f60e716c01d8a38ca80d786b04d1c4987c0da3f2efe6020e9a8597e6030ee3c581900f0e6c02fc91f4f80571c498b1b50d7d6c01fc91f4f805710100000002656e00040455736572000d48656c6c6f2c20776f726c64210a637265617465645f617402f0bc7068000000000a6163746976655f666f7203580200000000000006616d6f756e74010800c2eb0b00000000034943500a477265657420757365728302467374617475738203477265706c696564820458208264bfd38120767311a6858cd1ee4ce2a697f4e17026e824dd5e2f06a1e90181820458200a8c7afcbde567117cf1f3dae404fc6bd2d7d094774f740b5fb1abf522015d268204582029633b2785b61f32e8764ac65a977d7e609f1e05e9e5949a0b2d419a08b67e9882045820669b89933ea8636d1ba28e1ab072e2b980df498d0d0ea24c58c46848b48e9e38820458200423cae37e0670c3426f6f04b3a010c1f6010524d31486b769951324e800118283018204582079f11424b2c5e29ca256d504261cacdf7626a1a28f7863dd2b6bf93ffa3836e783024474696d65820349c699bbc6aefdb7aa18697369676e61747572655830b49a681d69c6e1f7089165cd0473f5e78fa09917e825c07041cdc0664f8977d9b1804e2c651ef7cf304bd99299997f4b6a64656c65676174696f6ea2697375626e65745f6964581d2c55b347ecf2686c83781d6c59d1b43e7b4cba8deb6c1b376107f2cd026b6365727469666963617465590294d9d9f7a264747265658301820458209b9acf5ebb96de4fd39f4928d5fe9ab3873620c4d5a71947c4580ac3a9c169ec8301830182045820d8470247bcd58a7a80aa89c80b203ca1b4b03de290996402f9df8f39f98043748302467375626e65748301830183018204582071387af6dac4d6350824bf1c24c8dd0af43a910b2dad8ce86390c816e63816158301830183018302581d2c55b347ecf2686c83781d6c59d1b43e7b4cba8deb6c1b376107f2cd02830183024f63616e69737465725f72616e67657382035832d9d9f782824a000000000060000001014a00000000006000ae0101824a00000000006000b001014a00000000006fffff010183024a7075626c69635f6b657982035885308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c0503020103610090075120778eb21a530a02bcc763e7f4a192933506966af7b54c10a4d2b24de6a86b200e3440bae6267bf4c488d9a11d0472c38c1b6221198f98e4e6882ba38a5a4e3aa5afce899b7f825ed95adfa12629688073556f2747527213e8d73e40ce8204582036f3cd257d90fb38e42597f193a5e031dbd585b6292793bb04db4794803ce06e820458205b9edd6408228a3956c4b4164ed3d9ec38afabe63310f84da7b97005e1fae375820458202729572815f63e48d2248738a83546bf521d479351ff52f172de77602ff682d382045820afaa8832101bcee23eb871f6a3b372b927eb3ad5bacbbbf67aa4df296bf8c49382045820962337bb2648bcf41e6d4f3cd80d6f6b3876dd18e62a8fb0cc5dc45f6e9d283e83024474696d65820349c8b9a8faedf8b7aa18697369676e617475726558308b0fc21a67a745f0716ba21ae4ec570cadbdb873c07b3584600d7815bae0e9c45c974ef4af028af5b165017b78ec9fc4";
33+
const CALL: &str = "d9d9f7a167636f6e74656e74a663617267554449444c0001710d48656c6c6f2c20776f726c64216b63616e69737465725f69644a0000000000600b7301016e696e67726573735f6578706972791b1854e015fd2598006b6d6574686f645f6e616d656567726565746c726571756573745f747970656571756572796673656e646572581d052c5f6f270fc4a3a882a8075732cba90ad4bd25d30bd2cf7b0bfe7c02";
34+
const CONSENT: &str = "d9d9f7a167636f6e74656e74a763617267586b4449444c076d7b6e766c02aeaeb1cc0501d880c6d007716b028beabfc2067fa9898b8f0a7f6e036c02efcee7800402c4fbf2db05046c03d6fca70200e1edeb4a7184f7fee80a050106154449444c0001710d48656c6c6f2c20776f726c64210567726565740002656e01016b63616e69737465725f69644a0000000000600b7301016e696e67726573735f6578706972791b1854e015fd2598006b6d6574686f645f6e616d6578246963726332315f63616e69737465725f63616c6c5f636f6e73656e745f6d657373616765656e6f6e6365506157607f0b54aa831d246383cd7ac6976c726571756573745f747970656463616c6c6673656e6465724104";
3535
const ROOT_KEY: &str =
3636
"814c0e6ec71fab583b08bd81373c255c3c371b2e84863c98a4f1e08b74235d14fb5d9c0cd546d9685f913a0c0b2cc5341583bf4b4392e467db96d65b9bb4cb717112f8472e0d5a4d14505ffd7484b01291091c5f87b98883463f98091a0baaae";
3737

app/rust/src/parser.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ mod snapshots_common;
3030
pub use certificate::*;
3131

3232
pub trait FromCandidHeader<'a> {
33-
fn from_candid_header<const TABLE_SIZE: usize, const MAX_ARGS: usize>(
33+
fn from_candid_header<const MAX_ARGS: usize>(
3434
input: &'a [u8],
3535
out: &mut core::mem::MaybeUninit<Self>,
36-
header: &CandidHeader<TABLE_SIZE, MAX_ARGS>,
36+
header: &CandidHeader<MAX_ARGS>,
3737
) -> Result<&'a [u8], ParserError>
3838
where
3939
Self: Sized;
@@ -74,10 +74,10 @@ pub trait FromBytes<'b>: Sized {
7474
}
7575

7676
pub trait FromTable<'a>: Sized {
77-
fn from_table<const MAX_FIELDS: usize>(
77+
fn from_table(
7878
input: &'a [u8],
7979
out: &mut MaybeUninit<Self>,
80-
type_table: &crate::type_table::TypeTable<MAX_FIELDS>,
80+
type_table: &crate::type_table::TypeTable,
8181
type_index: usize,
8282
) -> Result<&'a [u8], crate::error::ParserError>;
8383
}

app/rust/src/parser/call_request/icrc21_message_request.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
candid_header::parse_candid_header,
55
candid_types::IDLTypes,
66
candid_utils::{parse_bytes, parse_text},
7-
constants::{MAX_ARGS, MAX_TABLE_FIELDS},
7+
constants::MAX_ARGS,
88
error::ParserError,
99
type_table::{TypeTable, TypeTableEntry},
1010
zlog, FromCandidHeader,
@@ -63,9 +63,9 @@ impl<'a> Icrc21ConsentMessageRequest<'a> {
6363
})
6464
}
6565

66-
fn find_request_type<const MAX_FIELDS: usize>(
67-
table: &TypeTable<MAX_FIELDS>,
68-
) -> Option<&TypeTableEntry<MAX_FIELDS>> {
66+
fn find_request_type(
67+
table: &TypeTable,
68+
) -> Option<&TypeTableEntry> {
6969
// In order to not depend on Self::INDEX
7070
// we can try to look at the table for the entry
7171
// that contains our 3 fields, method, arg and preferences
@@ -102,7 +102,7 @@ impl<'a> Icrc21ConsentMessageRequest<'a> {
102102
fn get_field(&self, field: u32) -> Result<Option<Icrc21Field<'a>>, ParserError> {
103103
zlog("Icrc21ConsentMessageRequest::get_field\x00");
104104

105-
let (raw_request, header) = parse_candid_header::<MAX_TABLE_FIELDS, MAX_ARGS>(self.0)?;
105+
let (raw_request, header) = parse_candid_header::<MAX_ARGS>(self.0)?;
106106

107107
let entry =
108108
Self::find_request_type(&header.type_table).ok_or(ParserError::FieldNotFound)?;

app/rust/src/parser/call_request/icrc21_message_spec.rs

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ pub enum DeviceSpec<'a> {
5757
}
5858

5959
impl<'a> crate::FromCandidHeader<'a> for Icrc21ConsentMessageSpec<'a> {
60-
fn from_candid_header<const TABLE_SIZE: usize, const MAX_ARGS: usize>(
60+
fn from_candid_header<const MAX_ARGS: usize>(
6161
input: &'a [u8],
6262
out: &mut MaybeUninit<Self>,
63-
header: &CandidHeader<TABLE_SIZE, MAX_ARGS>,
63+
header: &CandidHeader<MAX_ARGS>,
6464
) -> Result<&'a [u8], ParserError> {
6565
crate::zlog("Icrc21ConsentMessageSpec::from_candid_header\x00");
6666

@@ -85,9 +85,9 @@ impl<'a> crate::FromCandidHeader<'a> for Icrc21ConsentMessageSpec<'a> {
8585
}
8686
}
8787

88-
fn parse_opt_device_spec<'a, const MAX_FIELDS: usize>(
88+
fn parse_opt_device_spec<'a>(
8989
input: &'a [u8],
90-
_type_table: &TypeTable<MAX_FIELDS>,
90+
_type_table: &TypeTable,
9191
) -> Result<(&'a [u8], Option<DeviceSpec<'a>>), ParserError> {
9292
let (rem, has_value) = decompress_leb128(input)?;
9393

@@ -100,30 +100,66 @@ fn parse_opt_device_spec<'a, const MAX_FIELDS: usize>(
100100
match variant_idx {
101101
0 => {
102102
// Parse FieldsDisplayMessage variant
103+
// Note: The field order in ICRC21 spec is different from consent message
104+
// ICRC21 has intent first, then fields
103105
// First parse intent (text)
104106
let (rem, intent) = crate::candid_utils::parse_text(rem)?;
105107

106108
// Then parse fields vector
107109
let (rem, field_count) = crate::utils::decompress_leb128(rem)?;
108110

111+
// Store the start position of fields data
112+
let fields_start = rem;
113+
109114
// Calculate total size of fields data
115+
// Each field is a record with key (text) and value (variant)
110116
let mut current = rem;
111-
let mut total_size = 0;
112117
for _ in 0..field_count {
113-
// Skip key
118+
// Skip key (text)
114119
let (new_rem, _) = crate::candid_utils::parse_text(current)?;
115-
// Skip value
116-
let (new_rem, _) = crate::candid_utils::parse_text(new_rem)?;
117-
total_size += current.len() - new_rem.len();
120+
121+
// Skip value - it's a variant, not just text
122+
// Read variant index
123+
let (new_rem, value_variant_idx) = crate::utils::decompress_leb128(new_rem)?;
124+
125+
// Skip based on variant type
126+
// The value can be one of: Text, TimestampSeconds, DurationSeconds, TokenAmount
127+
let new_rem = match value_variant_idx {
128+
0 => {
129+
// Text variant - skip the text content
130+
let (rem, _) = crate::candid_utils::parse_text(new_rem)?;
131+
rem
132+
}
133+
1 | 2 => {
134+
// TimestampSeconds or DurationSeconds - skip u64
135+
if new_rem.len() < 8 {
136+
return Err(ParserError::UnexpectedBufferEnd);
137+
}
138+
let (rem, _) = crate::utils::read_u64_le(new_rem)?;
139+
rem
140+
}
141+
3 => {
142+
// TokenAmount - skip amount (u64), decimals (u8), symbol (text)
143+
let (rem, _) = crate::utils::read_u64_le(new_rem)?;
144+
let (rem, _) = crate::utils::read_u8(rem)?;
145+
let (rem, _) = crate::candid_utils::parse_text(rem)?;
146+
rem
147+
}
148+
_ => return Err(ParserError::UnexpectedType),
149+
};
150+
118151
current = new_rem;
119152
}
120153

121-
// Take the fields data
122-
let (rem, fields) = nom::bytes::complete::take(total_size)(rem)
154+
// Calculate total size from start to current position
155+
let total_size = fields_start.len() - current.len();
156+
157+
// Take the fields data from the correct position
158+
let (_fields_end, fields) = nom::bytes::complete::take(total_size)(fields_start)
123159
.map_err(|_: nom::Err<nom::error::Error<&[u8]>>| ParserError::UnexpectedError)?;
124160

125161
Ok((
126-
rem,
162+
current,
127163
Some(DeviceSpec::FieldsDisplayMessage {
128164
intent,
129165
fields,

0 commit comments

Comments
 (0)