Implement correct `did:key` format
We weren't including the *multicodec* bytes previously. This is now tested against some test vectors from the spec. Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
parent
a2fc976ed6
commit
240825387d
|
|
@ -131,6 +131,8 @@ pub enum PublicKeyError {
|
||||||
InvalidLength(usize),
|
InvalidLength(usize),
|
||||||
#[error("invalid multibase string: {0}")]
|
#[error("invalid multibase string: {0}")]
|
||||||
Multibase(#[from] multibase::Error),
|
Multibase(#[from] multibase::Error),
|
||||||
|
#[error("invalid multicodec prefix, expected {0:?}")]
|
||||||
|
Multicodec([u8; 2]),
|
||||||
#[error("invalid key: {0}")]
|
#[error("invalid key: {0}")]
|
||||||
InvalidKey(#[from] ed25519::Error),
|
InvalidKey(#[from] ed25519::Error),
|
||||||
}
|
}
|
||||||
|
|
@ -198,8 +200,21 @@ impl TryFrom<&[u8]> for PublicKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PublicKey {
|
impl PublicKey {
|
||||||
|
/// Multicodec key type for Ed25519 keys.
|
||||||
|
pub const MULTICODEC_TYPE: [u8; 2] = [0xED, 0x1];
|
||||||
|
|
||||||
|
/// Encode public key in human-readable format.
|
||||||
|
///
|
||||||
|
/// We use the format specified by the DID `key` method, which is described as:
|
||||||
|
///
|
||||||
|
/// `did:key:MULTIBASE(base58-btc, MULTICODEC(public-key-type, raw-public-key-bytes))`
|
||||||
|
///
|
||||||
pub fn to_human(&self) -> String {
|
pub fn to_human(&self) -> String {
|
||||||
multibase::encode(multibase::Base::Base58Btc, self.0.deref())
|
let mut buf = [0; 2 + ed25519::PublicKey::BYTES];
|
||||||
|
buf[..2].copy_from_slice(&Self::MULTICODEC_TYPE);
|
||||||
|
buf[2..].copy_from_slice(self.0.deref());
|
||||||
|
|
||||||
|
multibase::encode(multibase::Base::Base58Btc, &buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -208,12 +223,14 @@ impl FromStr for PublicKey {
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let (_, bytes) = multibase::decode(s)?;
|
let (_, bytes) = multibase::decode(s)?;
|
||||||
let array: [u8; 32] = bytes
|
|
||||||
.try_into()
|
if let Some(bytes) = bytes.strip_prefix(&Self::MULTICODEC_TYPE) {
|
||||||
.map_err(|v: Vec<u8>| PublicKeyError::InvalidLength(v.len()))?;
|
let key = ed25519::PublicKey::from_slice(bytes)?;
|
||||||
let key = ed25519::PublicKey::new(array);
|
|
||||||
|
|
||||||
Ok(Self(key))
|
Ok(Self(key))
|
||||||
|
} else {
|
||||||
|
Err(PublicKeyError::Multicodec(Self::MULTICODEC_TYPE))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -246,4 +263,12 @@ mod test {
|
||||||
|
|
||||||
assert_eq!(input, decoded);
|
assert_eq!(input, decoded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encode_decode() {
|
||||||
|
let input = "z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK";
|
||||||
|
let key = PublicKey::from_str(input).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(key.to_string(), input);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,4 +117,19 @@ mod test {
|
||||||
let json = serde_json::to_string(&did).unwrap();
|
let json = serde_json::to_string(&did).unwrap();
|
||||||
assert_eq!(format!("\"{}\"", did), json);
|
assert_eq!(format!("\"{}\"", did), json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_did_encode_decode() {
|
||||||
|
let input = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK";
|
||||||
|
let Did(key) = Did::decode(input).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(Did::from(key).encode(), input);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_did_vectors() {
|
||||||
|
Did::decode("did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp").unwrap();
|
||||||
|
Did::decode("did:key:z6MkjchhfUsD6mmvni8mCdXHw216Xrm9bQe2mBH1P5RDjVJG").unwrap();
|
||||||
|
Did::decode("did:key:z6MknGc3ocHs3zdPiJbnaaqDi58NGb4pk1Sp9WxWufuXSdxf").unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,8 @@ pub mod fmt {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_key() {
|
fn test_key() {
|
||||||
let pk = PublicKey::from_str("zF4VJZgNEeL1niWmKu1NtT1B4ZyGpjACyhs2VEZvtsws5").unwrap();
|
let pk =
|
||||||
|
PublicKey::from_str("z6MktWkM9vcfysWFq1c2aaLjJ6j4PYYg93TLPswR4qtuoAeT").unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
key(&pk),
|
key(&pk),
|
||||||
|
|
@ -63,8 +64,8 @@ pub mod fmt {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fingerprint() {
|
fn test_fingerprint() {
|
||||||
let pk = PublicKey::from_str("zF4VJZgNEeL1niWmKu1NtT1B4ZyGpjACyhs2VEZvtsws5").unwrap();
|
let pk =
|
||||||
|
PublicKey::from_str("z6MktWkM9vcfysWFq1c2aaLjJ6j4PYYg93TLPswR4qtuoAeT").unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fingerprint(&pk),
|
fingerprint(&pk),
|
||||||
"SHA256:gE/Ty4fuXzww49lcnNe9/GI0L7xSEQdFp/v9tOjFwB4"
|
"SHA256:gE/Ty4fuXzww49lcnNe9/GI0L7xSEQdFp/v9tOjFwB4"
|
||||||
|
|
@ -360,7 +361,7 @@ mod test {
|
||||||
fn test_agent_encoding_remove() {
|
fn test_agent_encoding_remove() {
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
let pk = PublicKey::from_str("zF4VJZgNEeL1niWmKu1NtT1B4ZyGpjACyhs2VEZvtsws5").unwrap();
|
let pk = PublicKey::from_str("z6MktWkM9vcfysWFq1c2aaLjJ6j4PYYg93TLPswR4qtuoAeT").unwrap();
|
||||||
let expected = [
|
let expected = [
|
||||||
0, 0, 0, 56, // Message length
|
0, 0, 0, 56, // Message length
|
||||||
18, // Message type (remove identity)
|
18, // Message type (remove identity)
|
||||||
|
|
@ -387,7 +388,7 @@ mod test {
|
||||||
fn test_agent_encoding_sign() {
|
fn test_agent_encoding_sign() {
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
let pk = PublicKey::from_str("zF4VJZgNEeL1niWmKu1NtT1B4ZyGpjACyhs2VEZvtsws5").unwrap();
|
let pk = PublicKey::from_str("z6MktWkM9vcfysWFq1c2aaLjJ6j4PYYg93TLPswR4qtuoAeT").unwrap();
|
||||||
let expected = [
|
let expected = [
|
||||||
0, 0, 0, 73, // Message length
|
0, 0, 0, 73, // Message length
|
||||||
13, // Message type (sign request)
|
13, // Message type (sign request)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue