diff --git a/atrium-crypto/src/algorithm.rs b/atrium-crypto/src/algorithm.rs index 884421b1..d65f4898 100644 --- a/atrium-crypto/src/algorithm.rs +++ b/atrium-crypto/src/algorithm.rs @@ -1,8 +1,11 @@ use multibase::Base; +/// Supported algorithms (elliptic curves) for atproto cryptography. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Algorithm { + /// [`p256`] elliptic curve: aka "NIST P-256", aka `secp256r1` (note the `r`), aka `prime256v1`. P256, + /// [`k256`] elliptic curve: aka "NIST K-256", aka `secp256k1` (note the `k`). Secp256k1, } @@ -10,13 +13,13 @@ impl Algorithm { const MULTICODE_PREFIX_P256: [u8; 2] = [0x80, 0x24]; const MULTICODE_PREFIX_SECP256K1: [u8; 2] = [0xe7, 0x01]; - pub fn prefix(&self) -> [u8; 2] { + pub(crate) fn prefix(&self) -> [u8; 2] { match self { Self::P256 => Self::MULTICODE_PREFIX_P256, Self::Secp256k1 => Self::MULTICODE_PREFIX_SECP256K1, } } - pub fn from_prefix(prefix: [u8; 2]) -> Option { + pub(crate) fn from_prefix(prefix: [u8; 2]) -> Option { match prefix { Self::MULTICODE_PREFIX_P256 => Some(Self::P256), Self::MULTICODE_PREFIX_SECP256K1 => Some(Self::Secp256k1), diff --git a/atrium-crypto/src/did.rs b/atrium-crypto/src/did.rs index 9635c600..071a8602 100644 --- a/atrium-crypto/src/did.rs +++ b/atrium-crypto/src/did.rs @@ -1,17 +1,59 @@ +//! Functions for parsing and formatting DID keys. use crate::encoding::{compress_pubkey, decompress_pubkey}; use crate::error::{Error, Result}; use crate::{Algorithm, DID_KEY_PREFIX}; -pub fn parse_multikey(multikey: &str) -> Result<(Algorithm, Vec)> { - let (_, decoded) = multibase::decode(multikey)?; - if let Ok(prefix) = decoded[..2].try_into() { - if let Some(alg) = Algorithm::from_prefix(prefix) { - return Ok((alg, decompress_pubkey(alg, &decoded[2..])?)); - } - } - Err(Error::UnsupportedMultikeyType) +/// Format a public key as a DID key string. +/// +/// The public key will be compressed and encoded with multibase and multicode. +/// The resulting string will start with `did:key:`. +/// +/// Details: +/// [https://atproto.com/specs/cryptography#public-key-encoding](https://atproto.com/specs/cryptography#public-key-encoding) +/// +/// # Examples +/// +/// ``` +/// use atrium_crypto::Algorithm; +/// use atrium_crypto::did::format_did_key; +/// +/// # fn main() -> atrium_crypto::Result<()> { +/// let signing_key = ecdsa::SigningKey::::from_slice( +/// &hex::decode("9085d2bef69286a6cbb51623c8fa258629945cd55ca705cc4e66700396894e0c").unwrap() +/// )?; +/// let public_key = signing_key.verifying_key(); +/// let did_key = format_did_key(Algorithm::Secp256k1, &public_key.to_sec1_bytes())?; +/// assert_eq!(did_key, "did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme"); +/// # Ok(()) +/// # } +/// ``` +pub fn format_did_key(alg: Algorithm, key: &[u8]) -> Result { + Ok(prefix_did_key( + &alg.format_mulikey_compressed(&compress_pubkey(alg, key)?), + )) } +/// Parse a DID key string. +/// +/// Input should be a string starting with `did:key:`. +/// The rest of the string is the multibase and multicode encoded public key, +/// which will be parsed with [`parse_multikey`]. +/// +/// Returns the parsed [`Algorithm`] and bytes of the public key. +/// +/// # Examples +/// +/// ``` +/// use atrium_crypto::Algorithm; +/// use atrium_crypto::did::parse_did_key; +/// +/// # fn main() -> atrium_crypto::Result<()> { +/// let (alg, key): (Algorithm, Vec) = parse_did_key("did:key:zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme")?; +/// assert_eq!(alg, Algorithm::Secp256k1); +/// assert_eq!(key.len(), 65); +/// # Ok(()) +/// # } +/// ``` pub fn parse_did_key(did: &str) -> Result<(Algorithm, Vec)> { if let Some(multikey) = did.strip_prefix(DID_KEY_PREFIX) { parse_multikey(multikey) @@ -20,10 +62,34 @@ pub fn parse_did_key(did: &str) -> Result<(Algorithm, Vec)> { } } -pub fn format_did_key(alg: Algorithm, key: &[u8]) -> Result { - Ok(prefix_did_key( - &alg.format_mulikey_compressed(&compress_pubkey(alg, key)?), - )) +/// Parse a multibase and multicode encoded public key string. +/// +/// Details: +/// [https://atproto.com/specs/cryptography#public-key-encoding](https://atproto.com/specs/cryptography#public-key-encoding) +/// +/// Returns the parsed [`Algorithm`] and bytes of the public key. +/// +/// # Examples +/// +/// ``` +/// use atrium_crypto::Algorithm; +/// use atrium_crypto::did::parse_multikey; +/// +/// # fn main() -> atrium_crypto::Result<()> { +/// let (alg, key): (Algorithm, Vec) = parse_multikey("zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme")?; +/// assert_eq!(alg, Algorithm::Secp256k1); +/// assert_eq!(key.len(), 65); +/// # Ok(()) +/// # } +/// ``` +pub fn parse_multikey(multikey: &str) -> Result<(Algorithm, Vec)> { + let (_, decoded) = multibase::decode(multikey)?; + if let Ok(prefix) = decoded[..2].try_into() { + if let Some(alg) = Algorithm::from_prefix(prefix) { + return Ok((alg, decompress_pubkey(alg, &decoded[2..])?)); + } + } + Err(Error::UnsupportedMultikeyType) } pub(crate) fn prefix_did_key(multikey: &str) -> String { diff --git a/atrium-crypto/src/error.rs b/atrium-crypto/src/error.rs index 0036e25e..f2cb74c0 100644 --- a/atrium-crypto/src/error.rs +++ b/atrium-crypto/src/error.rs @@ -1,19 +1,27 @@ use thiserror::Error; +/// Error types. #[derive(Error, Debug)] pub enum Error { + /// Unsupported multikey type. #[error("Unsupported key type")] UnsupportedMultikeyType, + /// Incorrect prefix for DID key. #[error("Incorrect prefix for did:key: {0}")] IncorrectDIDKeyPrefix(String), - #[error("Low-S Signature is not allowed")] + /// Low-S signature is not allowed. + #[error("Low-S signature is not allowed")] LowSSignatureNotAllowed, + /// Signature is invalid. #[error("Signature is invalid")] InvalidSignature, + /// Error in [`multibase`] encoding or decoding. #[error(transparent)] Multibase(#[from] multibase::Error), + /// Error in [`ecdsa::signature`]. #[error(transparent)] Signature(#[from] ecdsa::signature::Error), } +/// Type alias to use this library's [`Error`](crate::Error) type in a [`Result`](core::result::Result). pub type Result = std::result::Result; diff --git a/atrium-crypto/src/keypair.rs b/atrium-crypto/src/keypair.rs index e5ca79fc..a3ce229a 100644 --- a/atrium-crypto/src/keypair.rs +++ b/atrium-crypto/src/keypair.rs @@ -1,3 +1,4 @@ +//! Keypair structs for signing, and utility trait implementations. use crate::did::prefix_did_key; use crate::error::Result; use crate::Algorithm; @@ -14,6 +15,7 @@ use ecdsa::{Signature, SignatureSize, SigningKey}; use k256::Secp256k1; use p256::NistP256; +/// A keypair for signing messages. pub struct Keypair where C: PrimeCurve + CurveArithmetic, @@ -29,14 +31,22 @@ where Scalar: Invert>> + SignPrimitive, SignatureSize: ArrayLength, { + /// Generate a cryptographically random [`SigningKey`]. + /// + /// ``` + /// use atrium_crypto::keypair::Keypair; + /// + /// let keypair = Keypair::::create(&mut rand::thread_rng()); + /// ``` pub fn create(rng: &mut impl CryptoRngCore) -> Self { Self { signing_key: SigningKey::::random(rng), } } - pub fn import(priv_key: &[u8]) -> Result { + /// Initialize signing key from a raw scalar serialized as a byte slice. + pub fn import(bytes: &[u8]) -> Result { Ok(Self { - signing_key: SigningKey::from_slice(priv_key)?, + signing_key: SigningKey::from_slice(bytes)?, }) } } @@ -63,6 +73,12 @@ where Scalar: Invert>> + SignPrimitive, SignatureSize: ArrayLength, { + /// Sign a message with the keypair. + /// + /// Returns the signature as a byte vector of the "low-S" form. + /// + /// Details: + /// [https://atproto.com/specs/cryptography#ecdsa-signature-malleability](https://atproto.com/specs/cryptography#ecdsa-signature-malleability) pub fn sign(&self, msg: &[u8]) -> Result> { let signature: Signature<_> = self.signing_key.try_sign(msg)?; Ok(signature @@ -73,10 +89,12 @@ where } } +/// Generate a DID key string from a keypair. pub trait Did { fn did(&self) -> String; } +/// Export a keypair as a byte vector. pub trait Export { fn export(&self) -> Vec; } @@ -92,6 +110,7 @@ where } } +/// Type alias for a P-256 keypair. pub type P256Keypair = Keypair; impl Did for P256Keypair { @@ -100,6 +119,7 @@ impl Did for P256Keypair { } } +/// Type alias for a secp256k1 keypair. pub type Secp256k1Keypair = Keypair; impl Did for Secp256k1Keypair { diff --git a/atrium-crypto/src/lib.rs b/atrium-crypto/src/lib.rs index ae973a3d..dd1bcfa5 100644 --- a/atrium-crypto/src/lib.rs +++ b/atrium-crypto/src/lib.rs @@ -2,11 +2,12 @@ mod algorithm; pub mod did; mod encoding; -pub mod error; +mod error; pub mod keypair; pub mod verify; -pub use algorithm::Algorithm; +pub use crate::algorithm::Algorithm; +pub use crate::error::{Error, Result}; pub use multibase; const DID_KEY_PREFIX: &str = "did:key:"; diff --git a/atrium-crypto/src/verify.rs b/atrium-crypto/src/verify.rs index 8f800438..34dcf318 100644 --- a/atrium-crypto/src/verify.rs +++ b/atrium-crypto/src/verify.rs @@ -1,3 +1,4 @@ +//! Verifies a signature for a message using a public key. use crate::did::parse_did_key; use crate::error::{Error, Result}; use crate::Algorithm; @@ -13,20 +14,49 @@ use k256::Secp256k1; use p256::NistP256; use std::ops::Add; +/// Verify a signature for a message using the given DID key formatted public key. +/// +/// This function verifies a signature using [`Verifier::default()`]. +/// +/// # Examples +/// +/// ``` +/// use atrium_crypto::verify::verify_signature; +/// +/// # fn main() -> atrium_crypto::Result<()> { +/// let did_key = "did:key:zQ3shtNTBUUCARYFEkRPZQ9NCaM5i5hVHPeEsEKXpmVkR2Upq"; +/// let signature = hex::decode( +/// "fdaa28ab03d6767c11d71fa39627c770ff62f91ca9661401ca0e2c475ae96a8c27064fbde3c355fa8121d2e8bbcf87a2de308e1d72b9bf4270f1e7cd8a1575ab" +/// ).unwrap(); +/// assert!(verify_signature(did_key, b"Hello, world!", &signature).is_ok()); +/// assert!(verify_signature(did_key, b"Hello, world?", &signature).is_err()); +/// # Ok(()) +/// # } +/// ``` pub fn verify_signature(did_key: &str, msg: &[u8], signature: &[u8]) -> Result<()> { let (alg, public_key) = parse_did_key(did_key)?; Verifier::default().verify(alg, &public_key, msg, signature) } +/// Verifier for verifying signatures for a message using a public key. +/// +/// This verifier can be configured to `allow_malleable` mode, which allows +/// verifying signatures with "high-S" or DER-encoded ones. +/// By default, this verifier allows only "low-S" signatures. +/// +/// See also: [https://github.com/bluesky-social/atproto/pull/1839](https://github.com/bluesky-social/atproto/pull/1839) #[derive(Debug, Default)] pub struct Verifier { allow_malleable: bool, } impl Verifier { + /// Create a new verifier with the given malleable mode. pub fn new(allow_malleable: bool) -> Self { Self { allow_malleable } } + /// Verify a signature for a message using the given public key. + /// The `algorithm` is used to determine the curve for the public key. pub fn verify( &self, algorithm: Algorithm, @@ -39,7 +69,9 @@ impl Verifier { Algorithm::Secp256k1 => self.verify_inner::(public_key, msg, signature), } } - fn verify_inner(&self, public_key: &[u8], msg: &[u8], signature: &[u8]) -> Result<()> + /// Verify a signature for a message using the given public key. + /// Any elliptic curve of the generics implementation of [`ECDSA`](ecdsa) can be used for parameter `C`. + pub fn verify_inner(&self, public_key: &[u8], msg: &[u8], bytes: &[u8]) -> Result<()> where C: PrimeCurve + CurveArithmetic + DigestPrimitive, AffinePoint: VerifyPrimitive + FromEncodedPoint + ToEncodedPoint, @@ -49,7 +81,7 @@ impl Verifier { as Add>::Output: Add + ArrayLength, { let verifying_key = VerifyingKey::::from_sec1_bytes(public_key)?; - if let Ok(mut signature) = ecdsa::Signature::from_slice(signature) { + if let Ok(mut signature) = ecdsa::Signature::from_slice(bytes) { if let Some(normalized) = signature.normalize_s() { if !self.allow_malleable { return Err(Error::LowSSignatureNotAllowed); @@ -62,7 +94,7 @@ impl Verifier { &signature, )?) } else if self.allow_malleable { - let signature = ecdsa::der::Signature::from_bytes(signature)?; + let signature = ecdsa::der::Signature::from_bytes(bytes)?; Ok(ecdsa::signature::Verifier::verify( &verifying_key, msg, diff --git a/atrium-libs/src/identity/did/atproto_data.rs b/atrium-libs/src/identity/did/atproto_data.rs index 2cf8d41d..ad95e2cd 100644 --- a/atrium-libs/src/identity/did/atproto_data.rs +++ b/atrium-libs/src/identity/did/atproto_data.rs @@ -1,5 +1,6 @@ use crate::common_web::did_doc::DidDocument; -use atrium_crypto::did::{format_did_key, format_did_key_str, parse_multikey}; +use atrium_crypto::did::{format_did_key, parse_multikey}; +use atrium_crypto::multibase; use atrium_crypto::Algorithm; use thiserror::Error; @@ -12,7 +13,9 @@ pub enum Error { #[error("Could not parse pds from doc: {0:?}")] Pds(DidDocument), #[error(transparent)] - Crypto(#[from] atrium_crypto::error::Error), + Crypto(#[from] atrium_crypto::Error), + #[error(transparent)] + Multibase(#[from] multibase::Error), } pub type Result = std::result::Result; @@ -50,12 +53,13 @@ fn get_did_key_from_multibase( ) -> Result> { Ok(match r#type.as_str() { "EcdsaSecp256r1VerificationKey2019" => { - Some(format_did_key_str(Algorithm::P256, &public_key_multibase)?) + let (_, key) = multibase::decode(public_key_multibase)?; + Some(format_did_key(Algorithm::P256, &key)?) + } + "EcdsaSecp256k1VerificationKey2019" => { + let (_, key) = multibase::decode(public_key_multibase)?; + Some(format_did_key(Algorithm::Secp256k1, &key)?) } - "EcdsaSecp256k1VerificationKey2019" => Some(format_did_key_str( - Algorithm::Secp256k1, - &public_key_multibase, - )?), "Multikey" => { let (alg, key) = parse_multikey(&public_key_multibase)?; Some(format_did_key(alg, &key)?)