Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

did-simple: implement pubkey access with fixed size arrays #98

Merged
merged 2 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ version = "0.0.0"
license = "MIT OR BSD-2-Clause-Patent OR Apache-2.0"
repository = "https://github.com/NexusSocial/nexus-vr"
edition = "2021"
rust-version = "1.76"
rust-version = "1.78.0"

[workspace.dependencies]
base64 = "0.21.7"
Expand Down
2 changes: 1 addition & 1 deletion apps/legacy_web/backend/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ async fn delete_avatar(

async fn ensure_user_in_db(db: &mut Database, username: &str) {
db.transaction(|data| {
if data.users.get(username).is_none() {
if data.users.contains_key(username) {
println!("creating user: {}", username);
data.users
.insert(username.to_owned(), User::new(username.to_owned()));
Expand Down
2 changes: 1 addition & 1 deletion crates/did-chain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use did_simple::{methods::key::DidKey, methods::DidDyn};
/// This is like an account UUID, it provides a unique identifier for the
/// account. Changing it is impossible.
#[derive(Debug)]
pub struct DidRoot(DidKey);
pub struct DidRoot(pub DidKey);

#[derive(Debug)]
pub struct DidChain {
Expand Down
58 changes: 19 additions & 39 deletions crates/did-simple/src/key_algos.rs
Original file line number Diff line number Diff line change
@@ -1,58 +1,38 @@
use crate::varint::encode_varint;

/// A key algorithm.
pub trait KeyAlgo {
fn pub_key_size(&self) -> usize;
fn multicodec_value(&self) -> u16;
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
pub enum KeyAlgo {
Ed25519,
}

impl KeyAlgo {
pub fn pub_key_len(&self) -> usize {
match self {
Self::Ed25519 => Ed25519::PUB_KEY_LEN,
}
}
}

// ---- internal code ----

/// A key algorithm that is known statically, at compile time.
pub trait StaticKeyAlgo: KeyAlgo {
const PUB_KEY_SIZE: usize;
pub(crate) trait StaticKeyAlgo {
const PUB_KEY_LEN: usize;
const MULTICODEC_VALUE: u16;
const MULTICODEC_VALUE_ENCODED: &'static [u8] =
encode_varint(Self::MULTICODEC_VALUE).as_slice();
}

impl<T: StaticKeyAlgo> KeyAlgo for T {
fn pub_key_size(&self) -> usize {
Self::PUB_KEY_SIZE
}

fn multicodec_value(&self) -> u16 {
Self::MULTICODEC_VALUE
}
}

#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
pub struct Ed25519;
pub(crate) struct Ed25519;

impl StaticKeyAlgo for Ed25519 {
const PUB_KEY_SIZE: usize = 32;
const PUB_KEY_LEN: usize = 32;
const MULTICODEC_VALUE: u16 = 0xED;
}

#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
pub enum DynKeyAlgo {
Ed25519,
}

impl PartialEq<Ed25519> for DynKeyAlgo {
impl PartialEq<Ed25519> for KeyAlgo {
fn eq(&self, _other: &Ed25519) -> bool {
*self == DynKeyAlgo::Ed25519
}
}

impl KeyAlgo for DynKeyAlgo {
fn pub_key_size(&self) -> usize {
match self {
Self::Ed25519 => Ed25519::PUB_KEY_SIZE,
}
}

fn multicodec_value(&self) -> u16 {
match self {
Self::Ed25519 => Ed25519::MULTICODEC_VALUE,
}
*self == KeyAlgo::Ed25519
}
}
36 changes: 20 additions & 16 deletions crates/did-simple/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,39 @@
//! A Decentralized Identifier (aka [DID][spec]), is a globally unique
//! identifier that provides a general purpose way of looking up public keys
//! associated with the globally unique identifier.
//! associated with the identifier.
//!
//! This means that unlike a UUID, someone can prove that they own a DID, and
//! you can encrypt messages using DIDs! This makes DIDs strictly more useful
//! than traditional UUIDs as account identifiers and are very useful for
//! building federated or decentralized services.
//! This means that ownership of a UUID can be proven and that support for
//! common cryptography operations such as signing, verifying, and encrypting
//! messages is possible. This makes DIDs strictly more useful than traditional
//! UUIDs as account identifiers and are very useful for building federated or
//! decentralized services.
//!
//! Unlike traditional centralized accounts, services that use DIDs give users
//! custody over their account identity. Authentication of users can happen
//! without the need for a centralized service or database. Instead, whoever
//! holds the private keys associated with a DID will be able to authenticate as
//! the account owner.
//! Services that use DIDs give users self-custody over their account identity.
//! Authentication of users can happen without the need for a centralized
//! authentication service or user database. Instead, whoever holds the private
//! keys associated with a DID will be able to authenticate as the account owner.
//!
//! This gives users the ability to maintain the same account handles/identities
//! across multiple separate services (or migrate homeservers in a federated
//! system) without having to create a new, different, account or identity each
//! time.
//! system) without having to create a different account or identity for each
//! service.
//!
//! [spec]: https://www.w3.org/TR/did-core/

#![forbid(unsafe_code)]

use std::str::FromStr;

pub mod key_algos;
pub(crate) mod key_algos;
pub mod methods;
pub mod uri;
pub mod url;
pub mod utf8bytes;
pub mod varint;
mod varint;

pub use crate::key_algos::KeyAlgo;
pub use crate::methods::DidDyn;
pub use crate::url::DidUrl;

pub trait Did: FromStr {
fn uri(&self) -> self::uri::DidUri;
fn url(&self) -> self::url::DidUrl;
}
80 changes: 42 additions & 38 deletions crates/did-simple/src/methods/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,50 +5,57 @@
use std::fmt::Display;

use crate::{
key_algos::{DynKeyAlgo, Ed25519, KeyAlgo, StaticKeyAlgo},
uri::{DidMethod, DidUri},
key_algos::{Ed25519, KeyAlgo, StaticKeyAlgo},
url::{DidMethod, DidUrl},
utf8bytes::Utf8Bytes,
varint::decode_varint,
};

/// An implementation of the `did:key` method. See the [module](self) docs for more
/// info.
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub struct DidKey<A = DynKeyAlgo> {
pub struct DidKey {
/// The string representation of the DID.
s: Utf8Bytes,
/// The decoded multibase portion of the DID.
mb_value: Vec<u8>,
key_algo: A,
key_algo: KeyAlgo,
/// The index into [`Self::mb_value`] that is the public key.
pubkey_bytes: std::ops::RangeFrom<usize>,
}

pub const PREFIX: &str = "did:key:";

impl<A> DidKey<A> {
impl DidKey {
pub const PREFIX: &'static str = PREFIX;

/// Gets the buffer representing the did:key uri as a str.
/// Gets the buffer representing the did:key url as a str.
pub fn as_str(&self) -> &str {
self.s.as_str()
}

/// Gets the buffer representing the did:key uri as a byte slice.
/// Gets the buffer representing the did:key url as a byte slice.
pub fn as_slice(&self) -> &[u8] {
self.s.as_slice()
}

/// Gets the buffer representing the did:key uri as a reference counted slice
/// Gets the buffer representing the did:key url as a reference counted slice
/// that is guaranteed to be utf8.
pub fn as_utf8_bytes(&self) -> &Utf8Bytes {
&self.s
}
}

impl<A: Clone> DidKey<A> {
pub fn key_algo(&self) -> A {
self.key_algo.clone()
pub fn key_algo(&self) -> KeyAlgo {
self.key_algo
}

/// Gets the decoded bytes of the public key.
pub fn pub_key(&self) -> &[u8] {
let result = match self.key_algo {
KeyAlgo::Ed25519 => &self.mb_value[self.pubkey_bytes.clone()],
};
debug_assert_eq!(result.len(), self.key_algo.pub_key_len());
result
}
}

Expand Down Expand Up @@ -80,13 +87,13 @@ pub enum MultibaseDecodeError {
Bs58(#[from] bs58::decode::Error),
}

impl TryFrom<DidUri> for DidKey {
type Error = FromUriError;
impl TryFrom<DidUrl> for DidKey {
type Error = FromUrlError;

fn try_from(value: DidUri) -> Result<Self, Self::Error> {
fn try_from(value: DidUrl) -> Result<Self, Self::Error> {
let m = value.method();
if m != DidMethod::Key {
return Err(FromUriError::WrongMethod(m));
return Err(FromUrlError::WrongMethod(m));
}
debug_assert_eq!(
value.as_slice().len() - value.method_specific_id().as_slice().len(),
Expand All @@ -98,21 +105,18 @@ impl TryFrom<DidUri> for DidKey {
let mut decoded_multibase = Vec::new();
decode_multibase(&s, &mut decoded_multibase)?;

// TODO: Instead of comparing decoded versions which requires running the decode
// function at runtime, compare the encoded versions. We can do the encode at
// compile time.
let (multicodec_key_algo, pubkey_bytes) = decode_varint(&decoded_multibase)?;
let key_algo = match multicodec_key_algo {
Ed25519::MULTICODEC_VALUE => DynKeyAlgo::Ed25519,
_ => return Err(FromUriError::UnknownKeyAlgo(multicodec_key_algo)),
// tail bytes will end up being the pubkey bytes if everything passes validation
let (multicodec_key_algo, tail_bytes) = decode_varint(&decoded_multibase)?;
let (key_algo, pub_key_len) = match multicodec_key_algo {
Ed25519::MULTICODEC_VALUE => (KeyAlgo::Ed25519, Ed25519::PUB_KEY_LEN),
_ => return Err(FromUrlError::UnknownKeyAlgo(multicodec_key_algo)),
};

let pubkey_len = pubkey_bytes.len();
if pubkey_len != key_algo.pub_key_size() {
return Err(FromUriError::MismatchedPubkeyLen(key_algo, pubkey_len));
if tail_bytes.len() != pub_key_len {
return Err(FromUrlError::MismatchedPubkeyLen(key_algo, pub_key_len));
}

let pubkey_bytes = decoded_multibase.len() - pubkey_len..;
let pubkey_bytes = (decoded_multibase.len() - pub_key_len)..;

Ok(Self {
s,
Expand All @@ -124,7 +128,7 @@ impl TryFrom<DidUri> for DidKey {
}

#[derive(thiserror::Error, Debug)]
pub enum FromUriError {
pub enum FromUrlError {
#[error("Expected \"key\" method but got {0:?}")]
WrongMethod(DidMethod),
#[error(transparent)]
Expand All @@ -133,8 +137,8 @@ pub enum FromUriError {
UnknownKeyAlgo(u16),
#[error(transparent)]
Varint(#[from] crate::varint::DecodeError),
#[error("{0:?} requires pubkeys of length {} but got {1} bytes", .0.pub_key_size())]
MismatchedPubkeyLen(DynKeyAlgo, usize),
#[error("{0:?} requires pubkeys of length {} but got {1} bytes", .0.pub_key_len())]
MismatchedPubkeyLen(KeyAlgo, usize),
}

impl Display for DidKey {
Expand All @@ -161,15 +165,15 @@ mod test {
}

#[test]
fn test_try_from_uri() -> eyre::Result<()> {
fn test_try_from_url() -> eyre::Result<()> {
for &example in ed25519_examples() {
let uri = DidUri::from_str(example)
.wrap_err_with(|| format!("failed to parse DidUri from {example}"))?;
assert_eq!(example, uri.as_str());
let key_from_uri = DidKey::try_from(uri.clone())
.wrap_err_with(|| format!("failed to parse DidKey from {uri}"))?;
assert_eq!(example, key_from_uri.as_str());
assert_eq!(key_from_uri.key_algo(), Ed25519);
let url = DidUrl::from_str(example)
.wrap_err_with(|| format!("failed to parse DidUrl from {example}"))?;
assert_eq!(example, url.as_str());
let key_from_url = DidKey::try_from(url.clone())
.wrap_err_with(|| format!("failed to parse DidKey from {url}"))?;
assert_eq!(example, key_from_url.as_str());
assert_eq!(key_from_url.key_algo(), Ed25519);
}
Ok(())
}
Expand Down
Loading
Loading