use canton_proto::com::daml::ledger::api::v2 as pb;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum SigningAlgorithm {
Ed25519,
EcDsaSha256,
EcDsaSha384,
MlDsa65,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum SignatureFormat {
Concat,
Der,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum KeyFormat {
Raw,
DerX509SubjectPublicKeyInfo,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum KeySpec {
EcCurve25519,
EcP256,
EcP384,
EcSecp256k1,
MlDsa65,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PublicKey {
format: KeyFormat,
data: Vec<u8>,
spec: KeySpec,
}
impl PublicKey {
#[must_use]
pub fn new(format: KeyFormat, data: Vec<u8>, spec: KeySpec) -> Self {
Self { format, data, spec }
}
#[must_use]
pub fn format(&self) -> KeyFormat {
self.format
}
#[must_use]
pub fn data(&self) -> &[u8] {
&self.data
}
#[must_use]
pub fn spec(&self) -> KeySpec {
self.spec
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Signature {
format: SignatureFormat,
bytes: Vec<u8>,
signed_by: String,
algorithm: SigningAlgorithm,
}
pub(crate) const ED25519_SIGNATURE_LEN: usize = 64;
impl Signature {
pub fn new(
format: SignatureFormat,
bytes: Vec<u8>,
signed_by: impl Into<String>,
algorithm: SigningAlgorithm,
) -> canton_core::Result<Self> {
if bytes.is_empty() {
return Err(canton_core::Error::InvalidRequest(
"a signature cannot be empty".to_string(),
));
}
if algorithm == SigningAlgorithm::Ed25519
&& format == SignatureFormat::Concat
&& bytes.len() != ED25519_SIGNATURE_LEN
{
return Err(canton_core::Error::InvalidRequest(format!(
"an Ed25519 `r || s` signature is {ED25519_SIGNATURE_LEN} bytes, got {}",
bytes.len()
)));
}
Ok(Self {
format,
bytes,
signed_by: signed_by.into(),
algorithm,
})
}
#[must_use]
pub fn format(&self) -> SignatureFormat {
self.format
}
#[must_use]
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
#[must_use]
pub fn signed_by(&self) -> &str {
&self.signed_by
}
#[must_use]
pub fn algorithm(&self) -> SigningAlgorithm {
self.algorithm
}
}
impl From<SigningAlgorithm> for pb::SigningAlgorithmSpec {
fn from(value: SigningAlgorithm) -> Self {
match value {
SigningAlgorithm::Ed25519 => Self::Ed25519,
SigningAlgorithm::EcDsaSha256 => Self::EcDsaSha256,
SigningAlgorithm::EcDsaSha384 => Self::EcDsaSha384,
SigningAlgorithm::MlDsa65 => Self::MlDsa65,
}
}
}
impl From<SignatureFormat> for pb::SignatureFormat {
fn from(value: SignatureFormat) -> Self {
match value {
SignatureFormat::Concat => Self::Concat,
SignatureFormat::Der => Self::Der,
}
}
}
impl From<KeyFormat> for pb::CryptoKeyFormat {
fn from(value: KeyFormat) -> Self {
match value {
KeyFormat::Raw => Self::Raw,
KeyFormat::DerX509SubjectPublicKeyInfo => Self::DerX509SubjectPublicKeyInfo,
}
}
}
impl From<KeySpec> for pb::SigningKeySpec {
fn from(value: KeySpec) -> Self {
match value {
KeySpec::EcCurve25519 => Self::EcCurve25519,
KeySpec::EcP256 => Self::EcP256,
KeySpec::EcP384 => Self::EcP384,
KeySpec::EcSecp256k1 => Self::EcSecp256k1,
KeySpec::MlDsa65 => Self::MlDsa65,
}
}
}
impl From<&PublicKey> for pb::SigningPublicKey {
fn from(value: &PublicKey) -> Self {
Self {
format: pb::CryptoKeyFormat::from(value.format) as i32,
key_data: value.data.clone(),
key_spec: pb::SigningKeySpec::from(value.spec) as i32,
}
}
}
impl From<&Signature> for pb::Signature {
fn from(value: &Signature) -> Self {
Self {
format: pb::SignatureFormat::from(value.format) as i32,
signature: value.bytes.clone(),
signed_by: value.signed_by.clone(),
signing_algorithm_spec: pb::SigningAlgorithmSpec::from(value.algorithm) as i32,
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn each_variant_maps_to_the_proto_variant_it_names() {
assert_eq!(
pb::SigningAlgorithmSpec::from(SigningAlgorithm::Ed25519),
pb::SigningAlgorithmSpec::Ed25519
);
assert_eq!(
pb::SigningAlgorithmSpec::from(SigningAlgorithm::EcDsaSha256),
pb::SigningAlgorithmSpec::EcDsaSha256
);
assert_eq!(
pb::SigningAlgorithmSpec::from(SigningAlgorithm::EcDsaSha384),
pb::SigningAlgorithmSpec::EcDsaSha384
);
assert_eq!(
pb::SigningAlgorithmSpec::from(SigningAlgorithm::MlDsa65),
pb::SigningAlgorithmSpec::MlDsa65
);
assert_eq!(
pb::SignatureFormat::from(SignatureFormat::Concat),
pb::SignatureFormat::Concat
);
assert_eq!(
pb::SignatureFormat::from(SignatureFormat::Der),
pb::SignatureFormat::Der
);
assert_eq!(
pb::CryptoKeyFormat::from(KeyFormat::Raw),
pb::CryptoKeyFormat::Raw
);
assert_eq!(
pb::CryptoKeyFormat::from(KeyFormat::DerX509SubjectPublicKeyInfo),
pb::CryptoKeyFormat::DerX509SubjectPublicKeyInfo
);
assert_eq!(
pb::SigningKeySpec::from(KeySpec::EcCurve25519),
pb::SigningKeySpec::EcCurve25519
);
assert_eq!(
pb::SigningKeySpec::from(KeySpec::EcP256),
pb::SigningKeySpec::EcP256
);
assert_eq!(
pb::SigningKeySpec::from(KeySpec::EcP384),
pb::SigningKeySpec::EcP384
);
assert_eq!(
pb::SigningKeySpec::from(KeySpec::EcSecp256k1),
pb::SigningKeySpec::EcSecp256k1
);
assert_eq!(
pb::SigningKeySpec::from(KeySpec::MlDsa65),
pb::SigningKeySpec::MlDsa65
);
}
#[test]
fn no_variant_maps_to_unspecified() {
for algorithm in [
SigningAlgorithm::Ed25519,
SigningAlgorithm::EcDsaSha256,
SigningAlgorithm::EcDsaSha384,
SigningAlgorithm::MlDsa65,
] {
assert_ne!(
pb::SigningAlgorithmSpec::from(algorithm),
pb::SigningAlgorithmSpec::Unspecified,
"{algorithm:?}"
);
}
for format in [SignatureFormat::Concat, SignatureFormat::Der] {
assert_ne!(
pb::SignatureFormat::from(format),
pb::SignatureFormat::Unspecified,
"{format:?}"
);
}
for format in [KeyFormat::Raw, KeyFormat::DerX509SubjectPublicKeyInfo] {
assert_ne!(
pb::CryptoKeyFormat::from(format),
pb::CryptoKeyFormat::Unspecified,
"{format:?}"
);
}
for spec in [
KeySpec::EcCurve25519,
KeySpec::EcP256,
KeySpec::EcP384,
KeySpec::EcSecp256k1,
KeySpec::MlDsa65,
] {
assert_ne!(
pb::SigningKeySpec::from(spec),
pb::SigningKeySpec::Unspecified,
"{spec:?}"
);
}
}
#[test]
fn the_wire_form_carries_every_field() {
let signature = Signature::new(
SignatureFormat::Concat,
vec![7; 64],
"1220abcd",
SigningAlgorithm::Ed25519,
)
.expect("a well-formed signature");
let wire = pb::Signature::from(&signature);
assert_eq!(wire.signature, vec![7; 64]);
assert_eq!(wire.signed_by, "1220abcd");
assert_ne!(wire.format, 0, "format left unset");
assert_ne!(wire.signing_algorithm_spec, 0, "algorithm left unset");
let key = PublicKey::new(KeyFormat::Raw, vec![3; 32], KeySpec::EcCurve25519);
let wire = pb::SigningPublicKey::from(&key);
assert_eq!(wire.key_data, vec![3; 32]);
assert_ne!(wire.format, 0, "key format left unset");
assert_ne!(wire.key_spec, 0, "key spec left unset");
}
#[test]
fn a_signature_that_cannot_be_what_it_claims_is_refused() {
let err = Signature::new(
SignatureFormat::Concat,
Vec::new(),
"1220abcd",
SigningAlgorithm::Ed25519,
)
.expect_err("empty");
assert!(err.to_string().contains("cannot be empty"), "{err}");
let err = Signature::new(
SignatureFormat::Concat,
vec![7; 63],
"1220abcd",
SigningAlgorithm::Ed25519,
)
.expect_err("truncated");
assert!(err.to_string().contains("64 bytes, got 63"), "{err}");
Signature::new(
SignatureFormat::Der,
vec![7; 63],
"1220abcd",
SigningAlgorithm::EcDsaSha256,
)
.expect("a DER signature has no fixed length");
}
}