Skip to content

Commit

Permalink
Update documents
Browse files Browse the repository at this point in the history
  • Loading branch information
sugyan committed May 16, 2024
1 parent a435510 commit 438f001
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 29 deletions.
7 changes: 5 additions & 2 deletions atrium-crypto/src/algorithm.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
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,
}

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<Self> {
pub(crate) fn from_prefix(prefix: [u8; 2]) -> Option<Self> {
match prefix {
Self::MULTICODE_PREFIX_P256 => Some(Self::P256),
Self::MULTICODE_PREFIX_SECP256K1 => Some(Self::Secp256k1),
Expand Down
90 changes: 78 additions & 12 deletions atrium-crypto/src/did.rs
Original file line number Diff line number Diff line change
@@ -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<u8>)> {
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::<k256::Secp256k1>::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<String> {
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<u8>) = 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<u8>)> {
if let Some(multikey) = did.strip_prefix(DID_KEY_PREFIX) {
parse_multikey(multikey)
Expand All @@ -20,10 +62,34 @@ pub fn parse_did_key(did: &str) -> Result<(Algorithm, Vec<u8>)> {
}
}

pub fn format_did_key(alg: Algorithm, key: &[u8]) -> Result<String> {
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<u8>) = parse_multikey("zQ3shokFTS3brHcDQrn82RUDfCZESWL1ZdCEJwekUDPQiYBme")?;
/// assert_eq!(alg, Algorithm::Secp256k1);
/// assert_eq!(key.len(), 65);
/// # Ok(())
/// # }
/// ```
pub fn parse_multikey(multikey: &str) -> Result<(Algorithm, Vec<u8>)> {
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 {
Expand Down
10 changes: 9 additions & 1 deletion atrium-crypto/src/error.rs
Original file line number Diff line number Diff line change
@@ -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<T> = std::result::Result<T, Error>;
24 changes: 22 additions & 2 deletions atrium-crypto/src/keypair.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -14,6 +15,7 @@ use ecdsa::{Signature, SignatureSize, SigningKey};
use k256::Secp256k1;
use p256::NistP256;

/// A keypair for signing messages.
pub struct Keypair<C>
where
C: PrimeCurve + CurveArithmetic,
Expand All @@ -29,14 +31,22 @@ where
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
{
/// Generate a cryptographically random [`SigningKey`].
///
/// ```
/// use atrium_crypto::keypair::Keypair;
///
/// let keypair = Keypair::<k256::Secp256k1>::create(&mut rand::thread_rng());
/// ```
pub fn create(rng: &mut impl CryptoRngCore) -> Self {
Self {
signing_key: SigningKey::<C>::random(rng),
}
}
pub fn import(priv_key: &[u8]) -> Result<Self> {
/// Initialize signing key from a raw scalar serialized as a byte slice.
pub fn import(bytes: &[u8]) -> Result<Self> {
Ok(Self {
signing_key: SigningKey::from_slice(priv_key)?,
signing_key: SigningKey::from_slice(bytes)?,
})
}
}
Expand All @@ -63,6 +73,12 @@ where
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
{
/// 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<Vec<u8>> {
let signature: Signature<_> = self.signing_key.try_sign(msg)?;
Ok(signature
Expand All @@ -73,10 +89,12 @@ where
}
}

/// Generate a DID key string from a keypair.
pub trait Did<C> {
fn did(&self) -> String;
}

/// Export a keypair as a byte vector.
pub trait Export<C> {
fn export(&self) -> Vec<u8>;
}
Expand All @@ -92,6 +110,7 @@ where
}
}

/// Type alias for a P-256 keypair.
pub type P256Keypair = Keypair<NistP256>;

impl Did<NistP256> for P256Keypair {
Expand All @@ -100,6 +119,7 @@ impl Did<NistP256> for P256Keypair {
}
}

/// Type alias for a secp256k1 keypair.
pub type Secp256k1Keypair = Keypair<Secp256k1>;

impl Did<Secp256k1> for Secp256k1Keypair {
Expand Down
5 changes: 3 additions & 2 deletions atrium-crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:";
38 changes: 35 additions & 3 deletions atrium-crypto/src/verify.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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,
Expand All @@ -39,7 +69,9 @@ impl Verifier {
Algorithm::Secp256k1 => self.verify_inner::<Secp256k1>(public_key, msg, signature),
}
}
fn verify_inner<C>(&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<C>(&self, public_key: &[u8], msg: &[u8], bytes: &[u8]) -> Result<()>
where
C: PrimeCurve + CurveArithmetic + DigestPrimitive,
AffinePoint<C>: VerifyPrimitive<C> + FromEncodedPoint<C> + ToEncodedPoint<C>,
Expand All @@ -49,7 +81,7 @@ impl Verifier {
<FieldBytesSize<C> as Add>::Output: Add<MaxOverhead> + ArrayLength<u8>,
{
let verifying_key = VerifyingKey::<C>::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);
Expand All @@ -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,
Expand Down
18 changes: 11 additions & 7 deletions atrium-libs/src/identity/did/atproto_data.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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<T> = std::result::Result<T, Error>;
Expand Down Expand Up @@ -50,12 +53,13 @@ fn get_did_key_from_multibase(
) -> Result<Option<String>> {
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)?)
Expand Down

0 comments on commit 438f001

Please sign in to comment.