Skip to content

Commit

Permalink
fix: add sig check (#460)
Browse files Browse the repository at this point in the history
* refactor(add 61284 sig check): verify msg vs pub key

ensure payload integrity

* refactor(add 61284 sig check): verify msg vs pub key

ensure payload integrity

* refactor(add 61284 sig check): verify msg vs pub key

ensure payload integrity

* refactor(add 61284 sig check): verify msg vs pub key

ensure payload integrity

* test ci

* test ci

* test ci

* ci test

* snapshot test

* Verify a signature on a message with this keypair's public key.

* Verify a signature on a message with this keypair's public key.

* new snapshot with dalek

* verify strict rm

* test error handling

* test error handling

* test error handling

* test error handling

* test error handling

* test error handling

* test error handling

* test dalek snapshot

* refactor

* fix(legacy jor sig check): chain crypto sig check

aligns with legacy snapshot

* fix(legacy jor sig check): chain crypto sig check

aligns with legacy snapshot

* fix(legacy jor sig check): chain crypto sig check

    aligns with legacy snapshot

* fix(legacy jor sig check): chain crypto sig check

    aligns with legacy snapshot

* fix(legacy jor sig check): chain crypto sig check

* fix(legacy jor sig check): chain crypto sig check

* fix(legacy jor sig check): chain crypto sig check

* fix(legacy jor sig check): chain crypto sig check

        aligns with legacy snapshot

* fix(legacy jor sig check): chain crypto sig check

        aligns with legacy snapshot

* fix(legacy jor sig check): chain crypto sig check

* fix(legacy jor sig check): chain crypto sig check

* fix(legacy jor sig check): chain crypto sig check

* fix(rebuild original signed 61284 payload) sig check works

* fix(rebuild original signed 61284 payload) sig check works

* clippy

* fix snapshot

* fmt

* fmt

* dalek snapshot

* dalek snapshot

---------

Co-authored-by: soze <soze@VFIEVOX3.Router>
  • Loading branch information
cong-or and soze committed May 8, 2024
1 parent 492c1fc commit 6e3c256
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 45 deletions.
1 change: 1 addition & 0 deletions catalyst-gateway/bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,4 @@ anyhow = { workspace = true }
handlebars = { workspace = true }
cddl = { workspace = true }
ciborium = { workspace = true }
ed25519-dalek = "2.1.1"
122 changes: 80 additions & 42 deletions catalyst-gateway/bin/src/cardano/cip36_registration/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
//! Verify registration TXs

use anyhow::Ok;
use cardano_chain_follower::Network;
use ciborium::{value::Integer, Value};
use cryptoxide::{blake2b::Blake2b, digest::Digest};
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use pallas::ledger::{
primitives::{conway::Metadatum, Fragment},
traverse::MultiEraMeta,
};
use serde::{Deserialize, Serialize};

use super::util::hash;

/// Pub key
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub(crate) struct PubKey(Vec<u8>);
Expand Down Expand Up @@ -43,42 +48,6 @@ impl PubKey {
}
}

/// Ed25519 signature.
///
/// This type represents a container for the byte serialization of an Ed25519
/// signature, and does not necessarily represent well-formed field or curve
/// elements.
///
/// Signature verification libraries are expected to reject invalid field
/// elements at the time a signature is verified.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[repr(C)]
pub struct Signature {
/// Component of an Ed25519 signature when serialized as bytes
r: [u8; Self::COMPONENT_SIZE],
/// Component of an Ed25519 signature when serialized as bytes
s: [u8; Self::COMPONENT_SIZE],
}

impl Signature {
/// Size of an encoded Ed25519 signature in bytes.
const BYTE_SIZE: usize = Self::COMPONENT_SIZE * 2;
/// Size of a single component of an Ed25519 signature.
const COMPONENT_SIZE: usize = 32;

/// Parse an Ed25519 signature from a byte slice.
fn from_bytes(bytes: &[u8; Self::BYTE_SIZE]) -> Self {
let mut r = <[u8; Self::COMPONENT_SIZE]>::default();
let mut s = <[u8; Self::COMPONENT_SIZE]>::default();

let components = bytes.split_at(Self::COMPONENT_SIZE);
r.copy_from_slice(components.0);
s.copy_from_slice(components.1);

Self { r, s }
}
}

/// The source of voting power for a given registration
///
/// The voting power can either come from:
Expand Down Expand Up @@ -142,6 +111,7 @@ impl Cip36Metadata {
let mut registration = None;
let mut raw_metadata = None;
let mut signature = None;
let mut raw_61284 = None;
let mut errors_report = Vec::new();

if let pallas::ledger::traverse::MultiEraMeta::AlonzoCompatible(tx_metadata) = tx_metadata {
Expand All @@ -157,6 +127,7 @@ impl Cip36Metadata {
},
Some,
);

for (key, metadata) in tx_metadata.iter() {
match *key {
CIP36_REGISTRATION_CBOR_KEY => {
Expand All @@ -168,6 +139,14 @@ impl Cip36Metadata {
},
Some,
);

raw_61284 = original_61284_payload(metadata).map_or_else(
|err| {
errors_report.push(format!("{err}"));
None
},
Some,
);
},
CIP36_WITNESS_CBOR_KEY => {
signature = inspect_witness_from_metadata(metadata).map_or_else(
Expand All @@ -183,15 +162,48 @@ impl Cip36Metadata {
}
};

if let Some(raw_61284) = raw_61284 {
let _ =
validate_signature(&raw_61284, &registration.clone(), &signature).map_err(|err| {
errors_report.push(format!("{err}"));
});
}

Some(Self {
registration,
registration: registration.clone(),
raw_metadata,
signature,
errors_report,
})
}
}

/// The signature is generated by:
/// - CBOR encoding the registration
/// - blake2b-256 hashing those bytes
/// - signing the hash with the private key used to generate the stake key
pub fn validate_signature(
raw_61284: &[u8], registration: &Option<Registration>, signature: &Option<Signature>,
) -> anyhow::Result<()> {
let hash_bytes = hash(raw_61284);

let verifying_key = VerifyingKey::from_bytes(
registration
.clone()
.ok_or("no registration data".to_string())
.map_err(|err| anyhow::anyhow!("{err}"))?
.stake_key
.bytes()
.try_into()?,
)?;

let sig = signature
.ok_or("cannot verify payload without signature".to_string())
.map_err(|err| anyhow::anyhow!("{err}"))?;

Ok(verifying_key.verify(&hash_bytes, &sig)?)
}

/// Validate binary data against CIP-36 registration CDDL spec
fn validate_cip36_registration(data: &[u8]) -> anyhow::Result<()> {
/// Cip36 registration CDDL definition
Expand Down Expand Up @@ -270,16 +282,42 @@ fn inspect_signature(cbor_value: ciborium::value::Value) -> anyhow::Result<Signa
.map_err(|_| anyhow::anyhow!("Invalid cip36 witness cbor, signature should be bytes"))?
.try_into()
.map_err(|vec: Vec<_>| {
anyhow::anyhow!(
"Invalid signature length, expected: {}, got {}",
Signature::BYTE_SIZE,
vec.len()
)
anyhow::anyhow!("Invalid signature length, expected: got {}", vec.len())
})?;

Ok(Signature::from_bytes(&signature))
}

/// Rebuild 61284 from pallas metadata to match original signed 61284 payload, pallas does
/// not preserve exact 61284 bytes which is required for signature verification. It must
/// be rebuilt and re-serialized to perform signature verification.
fn original_61284_payload(metadata: &Metadatum) -> anyhow::Result<Vec<u8>> {
/// 61284 CIP-36
const CIP_36_61284: usize = 61284;

let metadata_bytes = metadata
.encode_fragment()
.map_err(|err| anyhow::anyhow!("cannot encode metadata into bytes {err}"))?;

let cbor_value = ciborium::de::from_reader::<ciborium::Value, _>(metadata_bytes.as_slice())
.map_err(|err| anyhow::anyhow!("Cannot decode cbor object from bytes, err: {err}"))?;

let cbor_map = cbor_value.as_map().ok_or(anyhow::anyhow!(
"Not a valid cbor cip36 registration, should be a map"
))?;

let registration_payload = Value::Map(vec![(
Value::Integer(Integer::from(CIP_36_61284)),
ciborium::Value::Map(cbor_map.clone()),
)]);

let mut raw_61284 = Vec::new();
ciborium::ser::into_writer(&registration_payload, &mut raw_61284)
.map_err(|err| anyhow::anyhow!("Cannot decode cbor object from bytes, err: {err}"))?;

Ok(raw_61284)
}

/// Extract registration from tx metadata
fn inspect_registration_from_metadata(
metadata: &Metadatum, network: Network,
Expand Down
14 changes: 14 additions & 0 deletions catalyst-gateway/bin/src/cardano/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ pub type StakeCredentialHash = String;
/// Correct stake credential key in hex
pub type StakeCredentialKey = String;

/// Hash size
pub(crate) const BLAKE_2B_256_HASH_SIZE: usize = 256 / 8;

/// Helper function to generate the `blake2b_256` hash of a byte slice
#[allow(dead_code)]
pub(crate) fn hash(bytes: &[u8]) -> [u8; BLAKE_2B_256_HASH_SIZE] {
let mut digest = [0u8; BLAKE_2B_256_HASH_SIZE];
let mut context = Blake2b::new(BLAKE_2B_256_HASH_SIZE);
context.input(bytes);
context.result(&mut digest);

digest
}

#[derive(Default, Debug, Serialize)]
/// Assets
pub struct Asset {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ impl EventDB {
let Some(cip36) = Cip36Metadata::generate_from_tx_metadata(&tx.metadata(), network) else {
return Ok(());
};

let tx_hash = tx.hash().to_vec();
let (stake_credential, voting_info, rewards_address, nonce) =
if let Some(reg) = cip36.registration {
Expand Down
6 changes: 3 additions & 3 deletions catalyst-gateway/tests/api_tests/snapshot_tool-56364174.json
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@
"voting_power": 403470717,
"voting_purpose": 0,
"tx_id": 1205915,
"nonce": 36168765
"nonce": 36151489
},
{
"delegations": [
Expand All @@ -224,7 +224,7 @@
{
"delegations": [
[
"0x030606c166a947a33bf60043e70f240eac1df7936aa056429f2521553f9d4220",
"0x926cddb15c7ec46a71db0323d1d8ab18a83f8f9d73e159000b2dcfc3f7050de0",
1
]
],
Expand All @@ -233,7 +233,7 @@
"voting_power": 894284145,
"voting_purpose": 0,
"tx_id": 1151432,
"nonce": 181865062
"nonce": 36190561
},
{
"delegations": "0x1493fb2c46ef5e99928f08776b83166b2b18833389d06f8cb66e816e3829a627",
Expand Down

0 comments on commit 6e3c256

Please sign in to comment.