Skip to content

Commit

Permalink
did-simple: implement varint decode and keytype detect
Browse files Browse the repository at this point in the history
  • Loading branch information
TheButlah committed May 18, 2024
1 parent b8ab348 commit b199b2b
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 13 deletions.
54 changes: 54 additions & 0 deletions crates/did-simple/src/key_algos.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/// A key algorithm.
pub trait KeyAlgo {
fn pub_key_size(&self) -> usize;
fn multicodec_value(&self) -> u16;
}

/// A key algorithm that is known statically, at compile time.
pub trait StaticKeyAlgo: KeyAlgo {
const PUB_KEY_SIZE: usize;
const MULTICODEC_VALUE: u16;
}

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;

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

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

impl PartialEq<Ed25519> for DynKeyAlgo {
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,
}
}
}
2 changes: 2 additions & 0 deletions crates/did-simple/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@

use std::str::FromStr;

pub mod key_algos;
pub mod methods;
pub mod uri;
pub mod utf8bytes;
pub mod varint;

pub trait Did: FromStr {
fn uri(&self) -> self::uri::DidUri;
Expand Down
49 changes: 36 additions & 13 deletions crates/did-simple/src/methods/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,27 @@
use std::fmt::Display;

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

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

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

impl DidKey {
pub const PREFIX: &'static str = "did:key:";
pub const PREFIX: &str = "did:key:";

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

/// Gets the buffer representing the did:key uri as a str.
pub fn as_str(&self) -> &str {
Expand All @@ -42,13 +44,19 @@ impl DidKey {
}
}

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

fn decode_multibase(
s: &Utf8Bytes,
out_buf: &mut Vec<u8>,
) -> Result<(), MultibaseDecodeError> {
out_buf.clear();
// did:key only uses base58-btc, so its not actually any arbitrary multibase.
let multibase_part = &s.as_slice()[DidKey::PREFIX.len()..];
let multibase_part = &s.as_slice()[PREFIX.len()..];
// the first character should always be 'z'
let base = multibase_part[0];
if base != b'z' {
Expand All @@ -59,6 +67,7 @@ fn decode_multibase(
.onto(out_buf)?;
Ok(())
}

#[derive(thiserror::Error, Debug)]
pub enum MultibaseDecodeError {
#[error(
Expand All @@ -79,15 +88,24 @@ impl TryFrom<DidUri> for DidKey {
}
debug_assert_eq!(
value.as_slice().len() - value.method_specific_id().as_slice().len(),
Self::PREFIX.len(),
PREFIX.len(),
"sanity check that prefix has expected length"
);

let s = value.as_utf8_bytes().clone();
let mut decoded = Vec::new();
decode_multibase(&s, &mut decoded)?;

Ok(Self { s, decoded })
// TODO: Need to actually support other algos.
let multicodec_key_type = decode_varint(decoded[0..2].try_into().unwrap())?;
let key_algo = match multicodec_key_type {
Ed25519::MULTICODEC_VALUE => DynKeyAlgo::Ed25519,
_ => return Err(FromUriError::UnknownKeyAlgo(multicodec_key_type)),
};
Ok(Self {
s,
mb_value: decoded,
key_algo,
})
}
}

Expand All @@ -97,6 +115,10 @@ pub enum FromUriError {
WrongMethod(DidMethod),
#[error(transparent)]
MultibaseDecode(#[from] MultibaseDecodeError),
#[error("unknown multicodec value for key algorithm: decoded varint as {0}")]
UnknownKeyAlgo(u16),
#[error(transparent)]
Varint(#[from] crate::varint::DecodeError),
}

impl Display for DidKey {
Expand All @@ -112,7 +134,7 @@ mod test {
use eyre::WrapErr;
use hex_literal::hex;
use std::str::FromStr;

//0xed
// From: https://w3c-ccg.github.io/did-method-key/#example-5
fn ed25519_examples() -> &'static [&'static str] {
&[
Expand All @@ -131,6 +153,7 @@ mod test {
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);
}
Ok(())
}
Expand Down Expand Up @@ -160,7 +183,7 @@ mod test {

let mut buf = Vec::new();
for e @ Example { decoded, encoded } in examples {
let s = format!("{}z{encoded}", DidKey::PREFIX).into();
let s = format!("{}z{encoded}", PREFIX).into();
decode_multibase(&s, &mut buf)
.wrap_err_with(|| format!("Failed to decode example {e:?}"))?;
assert_eq!(buf, decoded, "failed comparison in example {e:?}");
Expand Down
77 changes: 77 additions & 0 deletions crates/did-simple/src/varint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
pub const fn decode_varint(encoded: &[u8]) -> Result<u16, DecodeError> {
// TODO: Technically, some three byte encodings could fit into a u16, we
// should support those in the future.
// Luckily none of them are used for did:key afaik.
if encoded.len() > 2 {
return Err(DecodeError::WouldOverflow);
}
if encoded.is_empty() {
return Err(DecodeError::MissingBytes);
}

/// bitmask for 7 least significant bits
const LSB_7: u8 = u8::MAX / 2;
/// bitmask for most significant bit
const MSB: u8 = !LSB_7;

#[inline]
const fn msb_is_1(val: u8) -> bool {
val & MSB == MSB
}

let a = encoded[0];
let mut result: u16 = (a & LSB_7) as u16;
if msb_is_1(a) {
// There is another 7 bits to decode.
if encoded.len() < 2 {
return Err(DecodeError::MissingBytes);
}
let b = encoded[1];

result |= ((b & LSB_7) as u16) << 7;
if msb_is_1(b) {
// We were provided a varint that ought to have had at least another byte.
return Err(DecodeError::MissingBytes);
}
}
Ok(result)
}

#[derive(thiserror::Error, Debug, Eq, PartialEq)]
pub enum DecodeError {
#[error("expected more bytes than what were provided")]
MissingBytes,
#[error(
"the decoded number is too large to fit into the type without overflowing"
)]
WouldOverflow,
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_known_examples() {
// See https://github.com/multiformats/unsigned-varint/blob/16bf9f7d3ff78c10c1ab26d397c03c91205cd4ee/README.md
let examples1 = [
(0x01, [0x01].as_slice()),
(0x02, &[0x02]),
(0x7f, &[0x7f]), // 127
(0x80, &[0x80, 0x01]), // 128
(0x81, &[0x81, 0x01]), // 129
(0xff, &[0xff, 0x01]), // 255
(0x012c, &[0xac, 0x02]), // 300
];
let examples2 = [
(0xed, [0xed, 0x01].as_slice()), // ed25519
(0xec, &[0xec, 0x01]), // x25519
(0x1200, &[0x80, 0x24]), // P256
(0xe7, &[0xe7, 0x01]), // Secp256k1
];

for (decoded, encoded) in examples1.into_iter().chain(examples2) {
assert_eq!(Ok(decoded), decode_varint(encoded))
}
}
}

0 comments on commit b199b2b

Please sign in to comment.