#![allow(clippy::all)]
use std::{error::Error, ffi::OsString, io::Cursor};
use crate::verify::{is_valid_rewards_address, stake_key_hash, StakeKeyHash};
use bytekind::{Bytes, HexString};
use ciborium::value::Value;
use clap::builder::OsStr;
use hex::FromHexError;
use microtype::microtype;
use serde::{Deserialize, Serialize, Serializer};
pub use crypto2::{PubKey, Sig};
mod crypto2;
pub mod hex_bytes;
mod network_id;
pub use network_id::NetworkId;
use test_strategy::Arbitrary;
use hash::hash;
mod hash;
const NETWORK_ID: usize = 0;
const KEY_61284: usize = 0;
const DELEGATIONS_OR_DIRECT: usize = 0;
const STAKE_ADDRESS: usize = 1;
const PAYMENT_ADDRESS: usize = 2;
const NONCE: usize = 3;
const VOTE_PURPOSE: usize = 4;
const VOTE_KEY: usize = 0;
const WEIGHT: usize = 1;
const KEY_61285: usize = 0;
const WITNESS: usize = 0;
use crate::{
verify::{validate_reg_cddl, validate_sig_cddl, CddlConfig},
RegistrationError,
};
use cardano_serialization_lib::chain_crypto::Ed25519;
use cardano_serialization_lib::chain_crypto::{
AsymmetricPublicKey, Verification, VerificationAlgorithm,
};
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
#[derive(Debug, Clone, PartialEq)]
pub enum VotingKey {
Direct(VotingKeyHex),
Delegated(Vec<(VotingKeyHex, u64)>),
}
impl VotingKey {
#[inline]
pub fn direct_from_hex(s: &str) -> Result<Self, FromHexError> {
let bytes = hex::decode(s)?;
Ok(Self::Direct(PubKey(bytes).into()))
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Registration {
#[serde(rename = "1")]
pub voting_key: VotingKey,
#[serde(rename = "2")]
pub stake_key: StakeKeyHex,
#[serde(rename = "3", serialize_with = "ox_hex_")]
pub rewards_address: RewardsAddress,
#[serde(rename = "4")]
pub nonce: Nonce,
#[serde(rename = "5")]
pub voting_purpose: Option<VotingPurpose>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Signature {
#[serde(rename = "1", serialize_with = "ox_hex_sig")]
pub inner: Sig,
}
fn ox_hex_sig<T, S>(v: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: AsRef<[u8]>,
S: Serializer,
{
serializer.serialize_str(&format!("0x{}", hex::encode(v.as_ref())))
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct SignedRegistration {
#[serde(rename = "61284")]
pub registration: Registration,
#[serde(rename = "61285")]
pub signature: Signature,
#[serde(serialize_with = "ox_hex")]
pub stake_key_hash: StakeKeyHash,
pub tx_id: TxId,
pub slot: u64,
}
fn ox_hex<T, S>(v: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: AsRef<[u8]>,
S: Serializer,
{
serializer.serialize_str(&format!("0x{}", hex::encode(v.as_ref())))
}
impl SignedRegistration {
pub fn validate_signature_bin(&self, bin_reg: Vec<u8>) -> Result<(), RegistrationError> {
let bytes = bin_reg;
let hash_bytes = hash(&bytes);
let pub_key = Ed25519::public_from_binary(self.registration.stake_key.as_ref())
.map_err(|e| RegistrationError::StakePublicKeyError { err: e.to_string() })?;
let sig = Ed25519::signature_from_bytes(self.signature.inner.as_ref())
.map_err(|e| RegistrationError::SignatureError { err: e.to_string() })?;
match Ed25519::verify_bytes(&pub_key, &sig, &hash_bytes) {
Verification::Success => Ok(()),
Verification::Failed => Err(RegistrationError::MismatchedSignature { hash_bytes }),
}
}
pub fn validate_multi_delegation(
&self,
multi_delegation_enabled: bool,
) -> Result<(), RegistrationError> {
match &self.registration.voting_key {
VotingKey::Direct(_) => (),
VotingKey::Delegated(delegation) => {
if !multi_delegation_enabled && delegation.len() != 1 {
return Err(RegistrationError::DelegationError {
err: "CIP-36 Multiple Delegations are unsupported.".to_string(),
});
}
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct RawRegistration {
pub json_reg: serde_json::Value,
pub json_sig: serde_json::Value,
pub bin_reg: Vec<u8>,
pub bin_sig: Vec<u8>,
pub tx_id: TxId,
pub slot: u64,
}
impl RawRegistration {
pub fn to_signed(
&self,
cddl_config: &CddlConfig,
network_id: NetworkId,
) -> Result<SignedRegistration, Box<dyn Error>> {
validate_reg_cddl(&self.bin_reg, cddl_config)?;
validate_sig_cddl(&self.bin_sig, cddl_config)?;
let registration = self.raw_reg_conversion(network_id)?;
let signature = self.raw_sig_conversion()?;
Ok(SignedRegistration {
registration: registration.clone(),
signature,
stake_key_hash: stake_key_hash(®istration.stake_key, network_id),
tx_id: self.tx_id,
slot: self.slot,
})
}
fn raw_reg_conversion(&self, network_id: NetworkId) -> Result<Registration, Box<dyn Error>> {
let decoded: ciborium::value::Value =
ciborium::de::from_reader(Cursor::new(&self.bin_reg))?;
let spec_61284 = match inspect_cip36_reg(&decoded) {
Ok(value) => value,
Err(value) => return value,
};
let metamap = match inspect_metamap_reg(&spec_61284) {
Ok(value) => value,
Err(value) => return value,
};
let voting_key = match inspect_voting_key(metamap) {
Ok(value) => value,
Err(value) => return value,
};
let stake_key = match inspect_stake_key(metamap) {
Ok(value) => value,
Err(value) => return value,
};
let rewards_address = match inspect_rewards_addr(metamap, network_id) {
Ok(value) => value,
Err(value) => return value,
};
let nonce = match inspect_nonce(metamap) {
Ok(value) => value,
Err(value) => return value,
};
let voting_purpose = inspect_voting_purpose(metamap);
Ok(Registration {
voting_key,
stake_key,
rewards_address,
nonce,
voting_purpose,
})
}
fn raw_sig_conversion(&self) -> Result<Signature, Box<dyn Error>> {
let decoded: ciborium::value::Value =
ciborium::de::from_reader(Cursor::new(&self.bin_sig))?;
let spec_61285 = match inspect_cip36_sig(decoded) {
Ok(value) => value,
Err(value) => return value,
};
let metamap = match inspect_metamap_sig(&spec_61285) {
Ok(value) => value,
Err(value) => return value,
};
let sig = match inspect_witness(metamap) {
Ok(value) => value,
Err(value) => return value,
};
Ok(Signature { inner: Sig(sig) })
}
}
fn inspect_witness(
metamap: &[(Value, Value)],
) -> Result<[u8; 64], Result<Signature, Box<dyn Error>>> {
let witness = match &metamap[WITNESS] {
(Value::Integer(_one), Value::Bytes(witness)) => witness.as_slice(),
_ => {
return Err(Err(Box::new(
RegistrationError::RawBinCborSignatureFailure {
err: "Unable to extract witness".to_string(),
},
)))
}
};
let sig: [u8; 64] = match witness.try_into() {
Ok(bytes) => bytes,
Err(err) => {
return Err(Err(Box::new(
RegistrationError::RawBinCborSignatureFailure {
err: format!("ED25119 signature should be 64 bytes {err}"),
},
)))
}
};
Ok(sig)
}
#[allow(clippy::manual_let_else)]
fn inspect_metamap_sig(
spec_61285: &[Value],
) -> Result<&Vec<(Value, Value)>, Result<Signature, Box<dyn Error>>> {
let metamap = match &spec_61285[KEY_61285] {
Value::Map(metamap) => metamap,
_ => {
return Err(Err(Box::new(
RegistrationError::RawBinCborSignatureFailure {
err: "Not congruent with CIP-36".to_string(),
},
)))
}
};
Ok(metamap)
}
fn inspect_cip36_sig(decoded: Value) -> Result<Vec<Value>, Result<Signature, Box<dyn Error>>> {
let spec_61285 = match decoded {
Value::Map(m) => m.iter().map(|entry| entry.1.clone()).collect::<Vec<_>>(),
_ => {
return Err(Err(Box::new(
RegistrationError::RawBinCborSignatureFailure {
err: format!("Not congruent with CIP-36 {decoded:?}"),
},
)))
}
};
Ok(spec_61285)
}
fn inspect_voting_purpose(metamap: &Vec<(Value, Value)>) -> Option<VotingPurpose> {
if metamap.len() == 5 {
match metamap[VOTE_PURPOSE] {
(Value::Integer(_five), Value::Integer(purpose)) => {
Some(VotingPurpose(purpose.try_into().unwrap()))
}
_ => None,
}
} else {
None
}
}
fn inspect_nonce(
metamap: &[(Value, Value)],
) -> Result<Nonce, Result<Registration, Box<dyn Error>>> {
let nonce = match metamap[NONCE] {
(Value::Integer(_four), Value::Integer(nonce)) => Nonce(nonce.try_into().unwrap()),
_ => {
return Err(Err(Box::new(
RegistrationError::RawBinCborRegistrationFailure {
err: "Unable to extract Nonce".to_string(),
},
)))
}
};
Ok(nonce)
}
fn inspect_rewards_addr(
metamap: &[(Value, Value)],
network_id: NetworkId,
) -> Result<RewardsAddress, Result<Registration, Box<dyn Error>>> {
let rewards_address = match &metamap[PAYMENT_ADDRESS] {
(Value::Integer(_three), Value::Bytes(rewards_addr)) => {
RewardsAddress::from_hex(&hex::encode(rewards_addr)).unwrap()
}
_ => {
return Err(Err(Box::new(
RegistrationError::RawBinCborRegistrationFailure {
err: "Unable to extract rewards addr".to_string(),
},
)))
}
};
if !is_valid_rewards_address(&rewards_address.0[NETWORK_ID], network_id) {
return Err(Err(Box::new(RegistrationError::InvalidAddressPrefix {
err: format!(
"Invalid rewards address: address prefix is: {:?}\n the first hex char should be in the range\n
0x0? - 0x7? or 0xE? , 0xF?\n
followed by 0 or 1 which defines the network id",
format!("{:x}", &rewards_address.0[NETWORK_ID]),
),
})));
}
Ok(rewards_address)
}
fn inspect_stake_key(
metamap: &[(Value, Value)],
) -> Result<StakeKeyHex, Result<Registration, Box<dyn Error>>> {
let stake_key = match &metamap[STAKE_ADDRESS] {
(Value::Integer(_two), Value::Bytes(stake_addr)) => {
StakeKeyHex(PubKey(stake_addr.clone()))
}
_ => {
return Err(Err(Box::new(
RegistrationError::RawBinCborRegistrationFailure {
err: "Unable to extract stake key".to_string(),
},
)))
}
};
Ok(stake_key)
}
#[allow(clippy::manual_let_else)]
fn inspect_voting_key(
metamap: &[(Value, Value)],
) -> Result<VotingKey, Result<Registration, Box<dyn Error>>> {
let voting_key = match &metamap[DELEGATIONS_OR_DIRECT] {
(Value::Integer(_one), Value::Bytes(direct)) => {
VotingKey::Direct(PubKey(direct.clone()).into())
}
(Value::Integer(_one), Value::Array(delegations)) => {
let mut delegations_map: Vec<(VotingKeyHex, u64)> = Vec::new();
for d in delegations {
match d {
Value::Array(delegations) => {
let voting_key = match delegations[VOTE_KEY].as_bytes() {
Some(key) => key,
None => {
return Err(Err(Box::new(
RegistrationError::RawBinCborRegistrationFailure {
err: "Unable to extract voting key from delegation"
.to_string(),
},
)))
}
};
let weight = match delegations[WEIGHT].as_integer() {
Some(weight) => match weight.try_into() {
Ok(weight) => weight,
Err(err) => {
return Err(Err(Box::new(
RegistrationError::RawBinCborRegistrationFailure {
err: format!(
"Unable to extract weight for delegation {err}"
),
},
)))
}
},
None => {
return Err(Err(Box::new(
RegistrationError::RawBinCborRegistrationFailure {
err: "Unable to extract voting key from delegation"
.to_string(),
},
)))
}
};
delegations_map.push((VotingKeyHex(PubKey(voting_key.clone())), weight));
}
_ => {
return Err(Err(Box::new(
RegistrationError::RawBinCborRegistrationFailure {
err: "Not congruent with CIP-36".to_string(),
},
)))
}
}
}
VotingKey::Delegated(delegations_map)
}
_ => {
return Err(Err(Box::new(
RegistrationError::RawBinCborRegistrationFailure {
err: "Unable to ascertain direct or delegated voting keys".to_string(),
},
)))
}
};
Ok(voting_key)
}
#[allow(clippy::manual_let_else)]
fn inspect_metamap_reg(
spec_61284: &[Value],
) -> Result<&Vec<(Value, Value)>, Result<Registration, Box<dyn Error>>> {
let metamap = match &spec_61284[KEY_61284] {
Value::Map(metamap) => metamap,
_ => {
return Err(Err(Box::new(
RegistrationError::RawBinCborRegistrationFailure {
err: "Unable to obtain metadata map".to_string(),
},
)))
}
};
Ok(metamap)
}
fn inspect_cip36_reg(decoded: &Value) -> Result<Vec<Value>, Result<Registration, Box<dyn Error>>> {
let spec_61284 = match decoded {
Value::Map(m) => m.iter().map(|entry| entry.1.clone()).collect::<Vec<_>>(),
_ => {
return Err(Err(Box::new(
RegistrationError::RawBinCborRegistrationFailure {
err: format!("Not congruent with CIP-36 {decoded:?}"),
},
)))
}
};
Ok(spec_61284)
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct SnapshotEntry {
#[serde(rename = "delegations")]
pub voting_key: VotingKey,
#[serde(serialize_with = "ox_hex_")]
pub rewards_address: RewardsAddress,
#[serde(rename = "stake_public_key")]
pub stake_key: StakeKeyHex,
pub voting_power: u128,
pub voting_purpose: Option<VotingPurpose>,
pub tx_id: TxId,
pub nonce: u64,
}
fn ox_hex_<T, S>(v: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: AsRef<[u8]>,
S: Serializer,
{
serializer.serialize_str(&format!("0x{}", hex::encode(v.as_ref())))
}
microtype! {
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Arbitrary)]
pub PubKey {
VotingKeyHex,
StakeKeyHex,
}
#[derive(Debug, PartialEq, Clone)]
#[string]
pub String {
DbName,
DbUser,
DbHost,
StakeAddr,
StakePubKey,
}
#[secret]
#[string]
pub String {
DbPass,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Arbitrary)]
#[int]
pub u64 {
Nonce,
SlotNo,
VotingPurpose,
TxId,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub Bytes<HexString> {
RewardsAddress,
}
}
impl VotingPurpose {
pub const CATALYST: VotingPurpose = VotingPurpose(0);
}
impl From<VotingPurpose> for OsStr {
fn from(purpose: VotingPurpose) -> Self {
OsStr::from(OsString::from(purpose.to_string()))
}
}
impl From<NetworkId> for OsStr {
fn from(id: NetworkId) -> Self {
OsStr::from(match id {
NetworkId::Mainnet => "mainnet",
NetworkId::Testnet => "testnet",
})
}
}
impl SlotNo {
#[inline]
#[must_use]
pub fn into_i64(self) -> Option<i64> {
self.0.try_into().ok()
}
}
impl RewardsAddress {
#[inline]
pub fn from_hex(s: &str) -> Result<Self, FromHexError> {
let bytes = hex::decode(s)?;
Ok(Self(bytes.into()))
}
}
impl AsRef<[u8]> for RewardsAddress {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
macro_rules! hex_impls {
($t:ty) => {
impl core::fmt::Display for $t {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "0x{}", hex::encode(&self.0))
}
}
impl AsRef<[u8]> for $t {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
};
}
hex_impls!(VotingKeyHex);
hex_impls!(StakeKeyHex);