Skip to content
202 changes: 149 additions & 53 deletions rust/rbac-registration/src/registration/cardano/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ use std::{collections::HashMap, sync::Arc};
use anyhow::bail;
use c509_certificate::c509::C509;
use pallas::{
codec::utils::Bytes, crypto::hash::Hash, ledger::traverse::MultiEraTx,
crypto::hash::Hash,
ledger::{
addresses::{Address, ShelleyAddress, ShelleyPaymentPart},
traverse::MultiEraTx,
},
network::miniprotocols::Point,
};
use payment_history::PaymentHistory;
Expand Down Expand Up @@ -51,8 +55,8 @@ impl RegistrationChain {
///
/// Returns an error if data is invalid
pub fn new(
&self, point: Point, tracking_payment_keys: Vec<Ed25519PublicKey>, tx_idx: usize,
txn: &MultiEraTx, cip509: Cip509,
point: Point, tracking_payment_keys: &[ShelleyAddress], tx_idx: usize, txn: &MultiEraTx,
cip509: Cip509,
) -> anyhow::Result<Self> {
let inner = RegistrationChainInner::new(cip509, tracking_payment_keys, point, tx_idx, txn)?;

Expand Down Expand Up @@ -124,16 +128,10 @@ impl RegistrationChain {
&self.inner.role_data
}

/// Get the list of payment keys to track.
/// Get the map of tracked payment keys to its history.
#[must_use]
pub fn tracking_payment_keys(&self) -> &Vec<Ed25519PublicKey> {
&self.inner.tracking_payment_keys
}

/// Get the map of payment key to its history.
#[must_use]
pub fn payment_history(&self) -> &HashMap<Ed25519PublicKey, Vec<PaymentHistory>> {
&self.inner.payment_history
pub fn tracking_payment_history(&self) -> &HashMap<ShelleyAddress, Vec<PaymentHistory>> {
&self.inner.tracking_payment_history
}
}

Expand All @@ -158,10 +156,8 @@ struct RegistrationChainInner {
// Role
/// Map of role number to point, transaction index, and role data.
role_data: HashMap<u8, (PointTxIdx, RoleData)>,
/// List of payment keys to track.
tracking_payment_keys: Arc<Vec<Ed25519PublicKey>>,
/// Map of payment key to its history.
payment_history: HashMap<Ed25519PublicKey, Vec<PaymentHistory>>,
/// Map of tracked payment key to its history.
tracking_payment_history: HashMap<ShelleyAddress, Vec<PaymentHistory>>,
}

impl RegistrationChainInner {
Expand All @@ -179,7 +175,7 @@ impl RegistrationChainInner {
///
/// Returns an error if data is invalid
fn new(
cip509: Cip509, tracking_payment_keys: Vec<Ed25519PublicKey>, point: Point, tx_idx: usize,
cip509: Cip509, tracking_payment_keys: &[ShelleyAddress], point: Point, tx_idx: usize,
txn: &MultiEraTx,
) -> anyhow::Result<Self> {
// Should be chain root, return immediately if not
Expand Down Expand Up @@ -207,12 +203,13 @@ impl RegistrationChainInner {
let revocations = revocations_list(registration.revocation_list, &point_tx_idx);
let role_data_map = chain_root_role_data(registration.role_set, txn, &point_tx_idx)?;

let mut payment_history = HashMap::new();
for tracking_key in &tracking_payment_keys {
// Keep record of payment history, the payment key that we want to track
let histories = update_payment_history(tracking_key, txn, &point_tx_idx)?;
payment_history.insert(tracking_key.clone(), histories);
let mut tracking_payment_history = HashMap::new();
// Create a payment history for each tracking payment key
for tracking_key in tracking_payment_keys {
tracking_payment_history.insert(tracking_key.clone(), Vec::new());
}
// Keep record of payment history, the payment key that we want to track
update_tracking_payment_history(&mut tracking_payment_history, txn, &point_tx_idx)?;

Ok(Self {
purpose,
Expand All @@ -222,8 +219,7 @@ impl RegistrationChainInner {
simple_keys: public_key_map,
revocations,
role_data: role_data_map,
tracking_payment_keys: Arc::new(tracking_payment_keys),
payment_history,
tracking_payment_history,
})
}

Expand Down Expand Up @@ -280,16 +276,11 @@ impl RegistrationChainInner {

update_role_data(&mut new_inner, registration.role_set, txn, &point_tx_idx)?;

for tracking_key in self.tracking_payment_keys.iter() {
let histories = update_payment_history(tracking_key, txn, &point_tx_idx)?;
// If tracking payment key doesn't exist, insert an empty vector,
// then add the histories to the history vector
new_inner
.payment_history
.entry(tracking_key.clone())
.or_default()
.extend(histories);
}
update_tracking_payment_history(
&mut new_inner.tracking_payment_history,
txn,
&point_tx_idx,
)?;

Ok(new_inner)
}
Expand Down Expand Up @@ -448,7 +439,7 @@ fn chain_root_role_data(
let encryption_key = role_data.role_encryption_key.clone();

// Get the payment key
let payment_key = get_payment_key_from_tx(txn, role_data.payment_key)?;
let payment_key = get_payment_addr_from_tx(txn, role_data.payment_key)?;

// Map of role number to point and role data
role_data_map.insert(
Expand Down Expand Up @@ -496,7 +487,7 @@ fn update_role_data(
}
},
};
let payment_key = get_payment_key_from_tx(txn, role_data.payment_key)?;
let payment_key = get_payment_addr_from_tx(txn, role_data.payment_key)?;

// Map of role number to point and role data
// Note that new role data will overwrite the old one
Expand All @@ -517,10 +508,10 @@ fn update_role_data(
Ok(())
}

/// Helper function for retrieving the payment key from the transaction.
fn get_payment_key_from_tx(
/// Helper function for retrieving the Shelley address from the transaction.
fn get_payment_addr_from_tx(
txn: &MultiEraTx, payment_key_ref: Option<i16>,
) -> anyhow::Result<Ed25519PublicKey> {
) -> anyhow::Result<Option<ShelleyPaymentPart>> {
// The index should exist since it pass the basic validation
if let Some(key_ref) = payment_key_ref {
if let MultiEraTx::Conway(tx) = txn {
Expand All @@ -533,11 +524,13 @@ fn get_payment_key_from_tx(
pallas::ledger::primitives::conway::PseudoTransactionOutput::PostAlonzo(
o,
) => {
let payment_key: Ed25519PublicKey =
o.address.clone().try_into().map_err(|_| {
anyhow::anyhow!("Failed to convert Vec<u8> to Ed25519PublicKey in payment key reference")
})?;
return Ok(payment_key);
let address =
Address::from_bytes(&o.address).map_err(|e| anyhow::anyhow!(e))?;

if let Address::Shelley(addr) = address {
return Ok(Some(addr.payment().clone()));
}
bail!("Unsupported address type in payment key reference");
},
// Not support legacy form of transaction output
pallas::ledger::primitives::conway::PseudoTransactionOutput::Legacy(_) => {
Expand All @@ -552,26 +545,34 @@ fn get_payment_key_from_tx(
bail!("Unsupported payment key reference to transaction input");
}
}
Ok(Ed25519PublicKey::default())
Ok(None)
}

/// Update the payment history given the tracking payment keys.
fn update_payment_history(
tracking_key: &Ed25519PublicKey, txn: &MultiEraTx, point_tx_idx: &PointTxIdx,
) -> anyhow::Result<Vec<PaymentHistory>> {
let mut payment_history = Vec::new();
fn update_tracking_payment_history(
tracking_payment_history: &mut HashMap<ShelleyAddress, Vec<PaymentHistory>>, txn: &MultiEraTx,
point_tx_idx: &PointTxIdx,
) -> anyhow::Result<()> {
if let MultiEraTx::Conway(tx) = txn {
// Conway era -> Post alonzo tx output
for (index, output) in tx.transaction_body.outputs.iter().enumerate() {
match output {
pallas::ledger::primitives::conway::PseudoTransactionOutput::PostAlonzo(o) => {
let address_bytes: Bytes = tracking_key.clone().into();
if address_bytes == o.address {
let address =
Address::from_bytes(&o.address).map_err(|e| anyhow::anyhow!(e))?;
let shelley_payment = if let Address::Shelley(addr) = address {
addr.clone()
} else {
bail!("Unsupported address type in update payment history");
};
// If the payment key from the output exist in the payment history, add the
// history
if let Some(vec) = tracking_payment_history.get_mut(&shelley_payment) {
let output_index: u16 = index.try_into().map_err(|_| {
anyhow::anyhow!("Cannot convert usize to u16 in update payment history")
})?;

payment_history.push(PaymentHistory::new(
vec.push(PaymentHistory::new(
point_tx_idx.clone(),
txn.hash(),
output_index,
Expand All @@ -585,5 +586,100 @@ fn update_payment_history(
}
}
}
Ok(payment_history)
Ok(())
}

#[cfg(test)]
mod test {
use minicbor::{Decode, Decoder};
use pallas::{ledger::traverse::MultiEraTx, network::miniprotocols::Point};

use super::RegistrationChain;
use crate::cardano::{cip509::Cip509, transaction::raw_aux_data::RawAuxData};

fn cip_509_aux_data(tx: &MultiEraTx<'_>) -> Vec<u8> {
let raw_auxiliary_data = tx
.as_conway()
.unwrap()
.clone()
.auxiliary_data
.map(|aux| aux.raw_cbor());

let raw_cbor_data = match raw_auxiliary_data {
pallas::codec::utils::Nullable::Some(data) => Ok(data),
_ => Err("Auxiliary data not found"),
};

let auxiliary_data = RawAuxData::new(raw_cbor_data.expect("Failed to get raw cbor data"));
auxiliary_data
.get_metadata(509)
.expect("Failed to get metadata")
.to_vec()
}

fn conway_1() -> Vec<u8> {
hex::decode(include_str!("../../test_data/cardano/conway_1.block"))
.expect("Failed to decode hex block.")
}

fn conway_4() -> Vec<u8> {
hex::decode(include_str!("../../test_data/cardano/conway_4.block"))
.expect("Failed to decode hex block.")
}

#[test]
fn test_new_and_update_registration() {
let conway_block_data_1 = conway_1();
let point_1 = Point::new(
77_429_134,
hex::decode("62483f96613b4c48acd28de482eb735522ac180df61766bdb476a7bf83e7bb98")
.unwrap(),
);
let multi_era_block_1 =
pallas::ledger::traverse::MultiEraBlock::decode(&conway_block_data_1)
.expect("Failed to decode MultiEraBlock");

let transactions_1 = multi_era_block_1.txs();
// Forth transaction of this test data contains the CIP509 auxiliary data
let tx_1 = transactions_1
.get(3)
.expect("Failed to get transaction index");

let aux_data_1 = cip_509_aux_data(tx_1);
let mut decoder = Decoder::new(aux_data_1.as_slice());
let cip509_1 = Cip509::decode(&mut decoder, &mut ()).expect("Failed to decode Cip509");
let tracking_payment_keys = vec![];

let registration_chain =
RegistrationChain::new(point_1.clone(), &tracking_payment_keys, 3, tx_1, cip509_1);
// Able to add chain root to the registration chain
assert!(registration_chain.is_ok());

let conway_block_data_4 = conway_4();
let point_4 = Point::new(
77_436_369,
hex::decode("b174fc697126f05046b847d47e60d66cbedaf25240027f9c07f27150889aac24")
.unwrap(),
);

let multi_era_block_4 =
pallas::ledger::traverse::MultiEraBlock::decode(&conway_block_data_4)
.expect("Failed to decode MultiEraBlock");

let transactions_4 = multi_era_block_4.txs();
// Second transaction of this test data contains the CIP509 auxiliary data
let tx = transactions_4
.get(1)
.expect("Failed to get transaction index");

let aux_data_4 = cip_509_aux_data(tx);
let mut decoder = Decoder::new(aux_data_4.as_slice());
let cip509 = Cip509::decode(&mut decoder, &mut ()).expect("Failed to decode Cip509");

// Update the registration chain
assert!(registration_chain
.unwrap()
.update(point_4.clone(), 1, tx, cip509)
.is_ok());
}
}
10 changes: 6 additions & 4 deletions rust/rbac-registration/src/registration/cardano/role_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

use std::collections::HashMap;

use crate::cardano::cip509::rbac::{pub_key::Ed25519PublicKey, role_data::KeyLocalRef};
use pallas::ledger::addresses::ShelleyPaymentPart;

use crate::cardano::cip509::rbac::role_data::KeyLocalRef;

/// Role data
#[derive(Clone)]
Expand All @@ -12,7 +14,7 @@ pub struct RoleData {
/// An encryption keys to the data within registration.
encryption_ref: Option<KeyLocalRef>,
/// A payment key where reward will be distributed to.
payment_key: Ed25519PublicKey,
payment_key: Option<ShelleyPaymentPart>,
/// Map of role extended data (10-99) to its data
role_extended_data: HashMap<u8, Vec<u8>>,
}
Expand All @@ -21,7 +23,7 @@ impl RoleData {
/// Create an instance of role data.
pub(crate) fn new(
signing_key_ref: Option<KeyLocalRef>, encryption_ref: Option<KeyLocalRef>,
payment_key: Ed25519PublicKey, role_extended_data: HashMap<u8, Vec<u8>>,
payment_key: Option<ShelleyPaymentPart>, role_extended_data: HashMap<u8, Vec<u8>>,
) -> Self {
RoleData {
signing_key_ref,
Expand All @@ -45,7 +47,7 @@ impl RoleData {

/// Get the payment key.
#[must_use]
pub fn payment_key(&self) -> &Ed25519PublicKey {
pub fn payment_key(&self) -> &Option<ShelleyPaymentPart> {
&self.payment_key
}

Expand Down
Loading