diff --git a/Cargo.lock b/Cargo.lock index 1f540e01..c727dbc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,6 +173,7 @@ dependencies = [ "k256", "multibase", "p256", + "rand", "thiserror", ] @@ -565,7 +566,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", - "pem-rfc7468", "zeroize", ] @@ -628,7 +628,6 @@ dependencies = [ "ff", "generic-array", "group", - "pem-rfc7468", "pkcs8", "rand_core", "sec1", @@ -1185,7 +1184,6 @@ dependencies = [ "elliptic-curve", "once_cell", "sha2", - "signature", ] [[package]] @@ -1480,15 +1478,6 @@ dependencies = [ "windows-targets 0.48.5", ] -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.1" diff --git a/Cargo.toml b/Cargo.toml index 08e5c4cf..5ad538c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,8 +50,9 @@ urlencoding = "2.1.3" # Cryptography ecdsa = "0.16.9" -k256 = "0.13.3" -p256 = "0.13.2" +k256 = { version = "0.13.3", default-features = false } +p256 = { version = "0.13.2", default-features = false } +rand = "0.8.5" # Networking futures = { version = "0.3.30", default-features = false, features = ["alloc"] } diff --git a/atrium-crypto/Cargo.toml b/atrium-crypto/Cargo.toml index 1345017e..f875349a 100644 --- a/atrium-crypto/Cargo.toml +++ b/atrium-crypto/Cargo.toml @@ -13,12 +13,12 @@ keywords.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -ecdsa = { workspace = true, features = ["verifying"] } -k256.workspace = true -p256.workspace = true +ecdsa = { workspace = true, features = ["signing", "verifying"] } +k256 = { workspace = true, features = ["std", "ecdsa"] } +p256 = { workspace = true, features = ["std", "ecdsa"] } multibase.workspace = true thiserror.workspace = true [dev-dependencies] -ecdsa = { workspace = true, features = ["signing"] } -hex = { workspace = true } +hex.workspace = true +rand.workspace = true diff --git a/atrium-crypto/src/algorithm.rs b/atrium-crypto/src/algorithm.rs index 34ef6a54..89602bf0 100644 --- a/atrium-crypto/src/algorithm.rs +++ b/atrium-crypto/src/algorithm.rs @@ -1,4 +1,6 @@ -use super::error::Result; +use crate::error::{Error, Result}; +use crate::keypair::verify_signature; +use crate::DID_KEY_PREFIX; use ecdsa::VerifyingKey; use k256::Secp256k1; use multibase::Base; @@ -14,6 +16,12 @@ impl Algorithm { const MULTICODE_PREFIX_P256: [u8; 2] = [0x80, 0x24]; const MULTICODE_PREFIX_SECP256K1: [u8; 2] = [0xe7, 0x01]; + pub 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 { match prefix { Self::MULTICODE_PREFIX_P256 => Some(Self::P256), @@ -33,13 +41,26 @@ impl Algorithm { pub fn decompress_pubkey(&self, key: &[u8]) -> Result> { self.pubkey_bytes(key, false) } + pub fn verify_signature(&self, did: &str, msg: &[u8], signature: &[u8]) -> Result<()> { + if let Some(multikey) = did.strip_prefix(DID_KEY_PREFIX) { + let (_, decoded) = multibase::decode(multikey)?; + if decoded[..2] == self.prefix() { + return match self { + Algorithm::P256 => unimplemented!(), + Algorithm::Secp256k1 => { + verify_signature::(&decoded[2..], msg, signature) + } + }; + } + } + Err(Error::IncorrectDIDKeyPrefix(did.to_string())) + } fn pubkey_bytes(&self, key: &[u8], compress: bool) -> Result> { Ok(match self { Algorithm::P256 => VerifyingKey::::from_sec1_bytes(key)? .to_encoded_point(compress) .as_bytes() .to_vec(), - Algorithm::Secp256k1 => VerifyingKey::::from_sec1_bytes(key)? .to_encoded_point(compress) .as_bytes() diff --git a/atrium-crypto/src/did.rs b/atrium-crypto/src/did.rs index fee7ac9b..fe4903a7 100644 --- a/atrium-crypto/src/did.rs +++ b/atrium-crypto/src/did.rs @@ -76,18 +76,20 @@ mod tests { fn secp256k1() { for (seed, id) in secp256k1_vectors() { let bytes = hex::decode(seed).expect("hex decoding should succeed"); - let sign = SigningKey::::from_slice(&bytes) + let sig_key = SigningKey::::from_slice(&bytes) .expect("initializing signing key should succeed"); - let did_key = - format_did_key(Algorithm::Secp256k1, &sign.verifying_key().to_sec1_bytes()) - .expect("formatting DID key should succeed"); + let did_key = format_did_key( + Algorithm::Secp256k1, + &sig_key.verifying_key().to_sec1_bytes(), + ) + .expect("formatting DID key should succeed"); assert_eq!(did_key, id); let (alg, key) = parse_did_key(&did_key).expect("parsing DID key should succeed"); assert_eq!(alg, Algorithm::Secp256k1); assert_eq!( &key, - sign.verifying_key().to_encoded_point(false).as_bytes() + sig_key.verifying_key().to_encoded_point(false).as_bytes() ); } } @@ -98,9 +100,9 @@ mod tests { let bytes = Base::Base58Btc .decode(private_key_base58) .expect("multibase decoding should succeed"); - let sign = SigningKey::::from_slice(&bytes) + let sig_key = SigningKey::::from_slice(&bytes) .expect("initializing signing key should succeed"); - let did_key = format_did_key(Algorithm::P256, &sign.verifying_key().to_sec1_bytes()) + let did_key = format_did_key(Algorithm::P256, &sig_key.verifying_key().to_sec1_bytes()) .expect("formatting DID key should succeed"); assert_eq!(did_key, id); @@ -108,7 +110,7 @@ mod tests { assert_eq!(alg, Algorithm::P256); assert_eq!( &key, - sign.verifying_key().to_encoded_point(false).as_bytes() + sig_key.verifying_key().to_encoded_point(false).as_bytes() ); } } diff --git a/atrium-crypto/src/keypair.rs b/atrium-crypto/src/keypair.rs new file mode 100644 index 00000000..c9959975 --- /dev/null +++ b/atrium-crypto/src/keypair.rs @@ -0,0 +1,151 @@ +use crate::error::Result; +use ecdsa::elliptic_curve::{ + generic_array::ArrayLength, + ops::Invert, + point::PointCompression, + sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint}, + subtle::CtOption, + AffinePoint, CurveArithmetic, FieldBytesSize, PrimeCurve, Scalar, +}; +use ecdsa::hazmat::{DigestPrimitive, SignPrimitive, VerifyPrimitive}; +use ecdsa::signature::{ + rand_core::CryptoRngCore, + {Signer, Verifier}, +}; +use ecdsa::{Signature, SignatureSize, SigningKey, VerifyingKey}; +use k256::Secp256k1; + +pub struct Keypair +where + C: PrimeCurve + CurveArithmetic, + Scalar: Invert>> + SignPrimitive, + SignatureSize: ArrayLength, +{ + signing_key: SigningKey, +} + +impl Keypair +where + C: PrimeCurve + CurveArithmetic, + Scalar: Invert>> + SignPrimitive, + SignatureSize: ArrayLength, +{ + pub fn create(rng: &mut impl CryptoRngCore) -> Self { + Self { + signing_key: SigningKey::::random(rng), + } + } + pub fn import(priv_key: &[u8]) -> Result { + Ok(Self { + signing_key: SigningKey::from_slice(priv_key)?, + }) + } +} + +impl Keypair +where + C: PrimeCurve + CurveArithmetic + DigestPrimitive, + Scalar: Invert>> + SignPrimitive, + SignatureSize: ArrayLength, +{ + pub fn sign(&self, msg: &[u8]) -> Result> { + let signature: Signature<_> = self.signing_key.try_sign(msg)?; + Ok(signature.to_bytes().to_vec()) + } +} + +impl Keypair +where + C: PrimeCurve + CurveArithmetic + PointCompression, + Scalar: Invert>> + SignPrimitive, + SignatureSize: ArrayLength, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + pub fn public_key_bytes(&self) -> Vec { + self.signing_key.verifying_key().to_sec1_bytes().to_vec() + } +} + +pub trait Export { + fn export(&self) -> Vec; +} + +pub type Secp256k1Keypair = Keypair; + +impl Export for Secp256k1Keypair { + fn export(&self) -> Vec { + self.signing_key.to_bytes().to_vec() + } +} + +pub(crate) fn verify_signature(public_key: &[u8], msg: &[u8], signature: &[u8]) -> Result<()> +where + C: PrimeCurve + CurveArithmetic + DigestPrimitive, + AffinePoint: VerifyPrimitive + FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, + SignatureSize: ArrayLength, +{ + let verifying_key = VerifyingKey::::from_sec1_bytes(public_key)?; + let signature = Signature::from_slice(signature)?; + Ok(verifying_key.verify(msg, &signature)?) +} + +#[cfg(test)] +mod tests { + use super::Secp256k1Keypair; + use crate::{did::format_did_key, Algorithm}; + use rand::rngs::ThreadRng; + + #[test] + fn secp256k1_export() { + let keypair = Secp256k1Keypair::create(&mut ThreadRng::default()); + let exported = { + use super::Export; + keypair.export() + }; + let imported = + Secp256k1Keypair::import(&exported).expect("importing keypair should succeed"); + let did = format_did_key(Algorithm::Secp256k1, &keypair.public_key_bytes()) + .expect("formatting to did key should succeed"); + let imported_did = format_did_key(Algorithm::Secp256k1, &imported.public_key_bytes()) + .expect("formatting to did key should succeed"); + assert_eq!(did, imported_did); + } + + #[test] + fn secp256k1_verify() { + let keypair = Secp256k1Keypair::create(&mut ThreadRng::default()); + let did = format_did_key(Algorithm::Secp256k1, &keypair.public_key_bytes()) + .expect("formatting to did key should succeed"); + let msg = [1, 2, 3, 4, 5, 6, 7, 8]; + let signature = keypair.sign(&msg).expect("signing should succeed"); + let mut corrupted_signature = signature.clone(); + corrupted_signature[0] = corrupted_signature[0].wrapping_add(1); + + assert!( + Algorithm::Secp256k1 + .verify_signature(&did, &msg, &signature) + .is_ok(), + "verifying signature should succeed" + ); + assert!( + Algorithm::Secp256k1 + .verify_signature(&did, &msg[..7], &signature) + .is_err(), + "verifying signature should fail with incorrect message" + ); + assert!( + Algorithm::Secp256k1 + .verify_signature(&did, &msg, &corrupted_signature) + .is_err(), + "verifying signature should fail with incorrect signature" + ); + assert!( + Algorithm::P256 + .verify_signature(&did, &msg, &signature) + .is_err(), + "verifying signature should fail with incorrect algorithm" + ); + } +} diff --git a/atrium-crypto/src/lib.rs b/atrium-crypto/src/lib.rs index 8866b59c..07085637 100644 --- a/atrium-crypto/src/lib.rs +++ b/atrium-crypto/src/lib.rs @@ -2,6 +2,7 @@ mod algorithm; pub mod did; pub mod error; +pub mod keypair; pub use algorithm::Algorithm;