Skip to content

Commit

Permalink
Implement crypto/keypair
Browse files Browse the repository at this point in the history
  • Loading branch information
sugyan committed May 13, 2024
1 parent 1cb62e7 commit 30faf8e
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 29 deletions.
13 changes: 1 addition & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
10 changes: 5 additions & 5 deletions atrium-crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
25 changes: 23 additions & 2 deletions atrium-crypto/src/algorithm.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<Self> {
match prefix {
Self::MULTICODE_PREFIX_P256 => Some(Self::P256),
Expand All @@ -33,13 +41,26 @@ impl Algorithm {
pub fn decompress_pubkey(&self, key: &[u8]) -> Result<Vec<u8>> {
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::<Secp256k1>(&decoded[2..], msg, signature)
}
};
}
}
Err(Error::IncorrectDIDKeyPrefix(did.to_string()))
}
fn pubkey_bytes(&self, key: &[u8], compress: bool) -> Result<Vec<u8>> {
Ok(match self {
Algorithm::P256 => VerifyingKey::<NistP256>::from_sec1_bytes(key)?
.to_encoded_point(compress)
.as_bytes()
.to_vec(),

Algorithm::Secp256k1 => VerifyingKey::<Secp256k1>::from_sec1_bytes(key)?
.to_encoded_point(compress)
.as_bytes()
Expand Down
18 changes: 10 additions & 8 deletions atrium-crypto/src/did.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Secp256k1>::from_slice(&bytes)
let sig_key = SigningKey::<Secp256k1>::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()
);
}
}
Expand All @@ -98,17 +100,17 @@ mod tests {
let bytes = Base::Base58Btc
.decode(private_key_base58)
.expect("multibase decoding should succeed");
let sign = SigningKey::<NistP256>::from_slice(&bytes)
let sig_key = SigningKey::<NistP256>::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);

let (alg, key) = parse_did_key(&did_key).expect("parsing DID key should succeed");
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()
);
}
}
Expand Down
151 changes: 151 additions & 0 deletions atrium-crypto/src/keypair.rs
Original file line number Diff line number Diff line change
@@ -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<C>
where
C: PrimeCurve + CurveArithmetic,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
{
signing_key: SigningKey<C>,
}

impl<C> Keypair<C>
where
C: PrimeCurve + CurveArithmetic,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
{
pub fn create(rng: &mut impl CryptoRngCore) -> Self {
Self {
signing_key: SigningKey::<C>::random(rng),
}
}
pub fn import(priv_key: &[u8]) -> Result<Self> {
Ok(Self {
signing_key: SigningKey::from_slice(priv_key)?,
})
}
}

impl<C> Keypair<C>
where
C: PrimeCurve + CurveArithmetic + DigestPrimitive,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
{
pub fn sign(&self, msg: &[u8]) -> Result<Vec<u8>> {
let signature: Signature<_> = self.signing_key.try_sign(msg)?;
Ok(signature.to_bytes().to_vec())
}
}

impl<C> Keypair<C>
where
C: PrimeCurve + CurveArithmetic + PointCompression,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
FieldBytesSize<C>: ModulusSize,
{
pub fn public_key_bytes(&self) -> Vec<u8> {
self.signing_key.verifying_key().to_sec1_bytes().to_vec()
}
}

pub trait Export<C> {
fn export(&self) -> Vec<u8>;
}

pub type Secp256k1Keypair = Keypair<Secp256k1>;

impl Export<Secp256k1> for Secp256k1Keypair {
fn export(&self) -> Vec<u8> {
self.signing_key.to_bytes().to_vec()
}
}

pub(crate) fn verify_signature<C>(public_key: &[u8], msg: &[u8], signature: &[u8]) -> Result<()>
where
C: PrimeCurve + CurveArithmetic + DigestPrimitive,
AffinePoint<C>: VerifyPrimitive<C> + FromEncodedPoint<C> + ToEncodedPoint<C>,
FieldBytesSize<C>: ModulusSize,
SignatureSize<C>: ArrayLength<u8>,
{
let verifying_key = VerifyingKey::<C>::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"
);
}
}
1 change: 1 addition & 0 deletions atrium-crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
mod algorithm;
pub mod did;
pub mod error;
pub mod keypair;

pub use algorithm::Algorithm;

Expand Down

0 comments on commit 30faf8e

Please sign in to comment.