iuna

iuna - experimental devnet protocol
git clone https://iuna.jhx.app/git/iuna.git
Log | Files | Refs | README | LICENSE

reveal.rs (8299B)


      1 use anyhow::{Context, Result, bail};
      2 use serde::{Deserialize, Serialize};
      3 
      4 use super::{Amount, BlindedReveal, REVEAL_COMMITTEE_SIZE, hex_hash};
      5 
      6 #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
      7 #[serde(rename_all = "camelCase")]
      8 pub struct RevealBundle {
      9     pub height: u64,
     10     pub prev_hash: String,
     11     pub slot: u8,
     12     pub member: String,
     13     pub reveals: Vec<BlindedReveal>,
     14     pub signature: String,
     15 }
     16 
     17 impl RevealBundle {
     18     pub fn canonical_payload(&self) -> String {
     19         RevealBundlePayload {
     20             height: self.height,
     21             prev_hash: self.prev_hash.clone(),
     22             slot: self.slot,
     23             member: self.member.clone(),
     24             reveals: self.reveals.clone(),
     25         }
     26         .canonical()
     27     }
     28 
     29     pub fn canonical(&self) -> String {
     30         format!("{}:{}", self.canonical_payload(), self.signature)
     31     }
     32 
     33     pub fn bundle_hash(&self) -> String {
     34         hex_hash(self.canonical())
     35     }
     36 
     37     pub fn serialized_size_bytes(&self) -> Result<usize> {
     38         serde_json::to_vec(self)
     39             .map(|bytes| bytes.len())
     40             .context("failed to serialize reveal bundle for size check")
     41     }
     42 }
     43 
     44 #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
     45 #[serde(rename_all = "camelCase")]
     46 pub struct RevealBundleSignature {
     47     pub slot: u8,
     48     pub member: String,
     49     pub signature: String,
     50 }
     51 
     52 #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
     53 #[serde(rename_all = "camelCase")]
     54 pub struct MaskedBlindedReveal {
     55     pub reveal: BlindedReveal,
     56     pub bundle_mask: u8,
     57 }
     58 
     59 #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
     60 #[serde(rename_all = "camelCase")]
     61 pub struct RevealBundleSection {
     62     #[serde(default, skip_serializing_if = "Vec::is_empty")]
     63     pub signatures: Vec<RevealBundleSignature>,
     64     #[serde(default, skip_serializing_if = "Vec::is_empty")]
     65     pub reveals: Vec<MaskedBlindedReveal>,
     66 }
     67 
     68 impl RevealBundleSection {
     69     pub fn is_empty(&self) -> bool {
     70         self.signatures.is_empty() && self.reveals.is_empty()
     71     }
     72 
     73     pub fn all_reveals(&self) -> Vec<&BlindedReveal> {
     74         self.reveals.iter().map(|masked| &masked.reveal).collect()
     75     }
     76 
     77     pub fn included_bundle_count(&self) -> usize {
     78         self.signatures.len()
     79     }
     80 
     81     pub fn expand(&self, height: u64, prev_hash: &str) -> Vec<RevealBundle> {
     82         self.signatures
     83             .iter()
     84             .map(|signature| {
     85                 let slot_mask = reveal_bundle_slot_mask(signature.slot).unwrap_or(0);
     86                 let reveals = self
     87                     .reveals
     88                     .iter()
     89                     .filter(|masked| masked.bundle_mask & slot_mask != 0)
     90                     .map(|masked| masked.reveal.clone())
     91                     .collect();
     92                 RevealBundle {
     93                     height,
     94                     prev_hash: prev_hash.to_string(),
     95                     slot: signature.slot,
     96                     member: signature.member.clone(),
     97                     reveals,
     98                     signature: signature.signature.clone(),
     99                 }
    100             })
    101             .collect()
    102     }
    103 
    104     pub fn reveal_bundle_hashes(
    105         &self,
    106         height: u64,
    107         prev_hash: &str,
    108     ) -> [String; REVEAL_COMMITTEE_SIZE] {
    109         let bundles = self.expand(height, prev_hash);
    110         reveal_bundle_hashes(&bundles)
    111     }
    112 
    113     pub(super) fn canonical(&self) -> String {
    114         let signatures = self
    115             .signatures
    116             .iter()
    117             .map(|signature| {
    118                 format!(
    119                     "{}:{}:{}",
    120                     signature.slot, signature.member, signature.signature
    121                 )
    122             })
    123             .collect::<Vec<_>>()
    124             .join("|");
    125         let reveals = self
    126             .reveals
    127             .iter()
    128             .map(|masked| format!("{}:{}", masked.bundle_mask, masked.reveal.canonical()))
    129             .collect::<Vec<_>>()
    130             .join("|");
    131         format!("reveal-bundle-section-v1:{signatures}:reveals:{reveals}")
    132     }
    133 }
    134 
    135 #[derive(Clone, Debug, Eq, PartialEq)]
    136 pub(super) struct RevealBundlePayload {
    137     pub(super) height: u64,
    138     pub(super) prev_hash: String,
    139     pub(super) slot: u8,
    140     pub(super) member: String,
    141     pub(super) reveals: Vec<BlindedReveal>,
    142 }
    143 
    144 impl RevealBundlePayload {
    145     pub(super) fn canonical(&self) -> String {
    146         let reveals = self
    147             .reveals
    148             .iter()
    149             .map(BlindedReveal::canonical)
    150             .collect::<Vec<_>>()
    151             .join("|");
    152         format!(
    153             "iuna-reveal-bundle-v1:{}:{}:{}:{}:{}",
    154             self.height, self.prev_hash, self.slot, self.member, reveals
    155         )
    156     }
    157 }
    158 
    159 #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
    160 #[serde(rename_all = "camelCase")]
    161 pub struct RevealCommitteeMember {
    162     pub slot: u8,
    163     pub rank: u32,
    164     pub ticket_id: String,
    165     pub owner: String,
    166     pub amount: Amount,
    167 }
    168 
    169 pub fn default_reveal_bundle_hash(slot: usize) -> String {
    170     hex_hash(format!("iuna-default-reveal-bundle-v1:{slot}"))
    171 }
    172 
    173 pub(super) fn reveal_bundle_slot_mask(slot: u8) -> Result<u8> {
    174     if usize::from(slot) >= REVEAL_COMMITTEE_SIZE || slot >= 8 {
    175         bail!("reveal bundle slot is invalid");
    176     }
    177     Ok(1_u8 << slot)
    178 }
    179 
    180 pub(super) fn reveal_committee_mask() -> u8 {
    181     (0..REVEAL_COMMITTEE_SIZE).fold(0_u8, |mask, slot| mask | (1_u8 << slot))
    182 }
    183 
    184 pub(super) fn reveal_bundle_hashes(bundles: &[RevealBundle]) -> [String; REVEAL_COMMITTEE_SIZE] {
    185     std::array::from_fn(|slot| {
    186         bundles
    187             .iter()
    188             .find(|bundle| usize::from(bundle.slot) == slot)
    189             .map(RevealBundle::bundle_hash)
    190             .unwrap_or_else(|| default_reveal_bundle_hash(slot))
    191     })
    192 }
    193 
    194 pub(super) fn canonical_reveal_bundle_hashes(
    195     bundle_hashes: &[String; REVEAL_COMMITTEE_SIZE],
    196 ) -> String {
    197     bundle_hashes.join("|")
    198 }
    199 
    200 #[cfg(test)]
    201 mod tests {
    202     use super::{
    203         MaskedBlindedReveal, RevealBundle, RevealBundlePayload, RevealBundleSection,
    204         RevealBundleSignature, default_reveal_bundle_hash, reveal_bundle_hashes,
    205         reveal_bundle_slot_mask,
    206     };
    207     use crate::domain::BlindedReveal;
    208 
    209     #[test]
    210     fn reveal_bundle_payload_is_canonical() {
    211         let payload = RevealBundlePayload {
    212             height: 7,
    213             prev_hash: "prev".to_string(),
    214             slot: 1,
    215             member: "member".to_string(),
    216             reveals: vec![BlindedReveal {
    217                 commitment: "commitment".to_string(),
    218                 key: "key".to_string(),
    219             }],
    220         };
    221 
    222         assert_eq!(
    223             payload.canonical(),
    224             "iuna-reveal-bundle-v1:7:prev:1:member:blinded-reveal:commitment:key"
    225         );
    226     }
    227 
    228     #[test]
    229     fn reveal_bundle_hashes_fill_missing_slots_with_defaults() {
    230         let bundle = RevealBundle {
    231             height: 1,
    232             prev_hash: "prev".to_string(),
    233             slot: 1,
    234             member: "member".to_string(),
    235             reveals: Vec::new(),
    236             signature: "sig".to_string(),
    237         };
    238 
    239         let hashes = reveal_bundle_hashes(&[bundle.clone()]);
    240 
    241         assert_eq!(hashes[0], default_reveal_bundle_hash(0));
    242         assert_eq!(hashes[1], bundle.bundle_hash());
    243         assert_eq!(hashes[2], default_reveal_bundle_hash(2));
    244     }
    245 
    246     #[test]
    247     fn reveal_bundle_section_expands_masked_reveals_by_slot() {
    248         let reveal = BlindedReveal {
    249             commitment: "commitment".to_string(),
    250             key: "key".to_string(),
    251         };
    252         let section = RevealBundleSection {
    253             signatures: vec![
    254                 RevealBundleSignature {
    255                     slot: 0,
    256                     member: "a".to_string(),
    257                     signature: "sig-a".to_string(),
    258                 },
    259                 RevealBundleSignature {
    260                     slot: 1,
    261                     member: "b".to_string(),
    262                     signature: "sig-b".to_string(),
    263                 },
    264             ],
    265             reveals: vec![MaskedBlindedReveal {
    266                 reveal: reveal.clone(),
    267                 bundle_mask: reveal_bundle_slot_mask(1).unwrap(),
    268             }],
    269         };
    270 
    271         let expanded = section.expand(3, "prev");
    272 
    273         assert!(expanded[0].reveals.is_empty());
    274         assert_eq!(expanded[1].reveals, vec![reveal]);
    275     }
    276 }