Skip to content

Commit

Permalink
Add publicKeyMultibase (#307)
Browse files Browse the repository at this point in the history
* Add publicKeyMultibase support

Add and expand base-encoding tests.

* Remove publicKeyHex

* Add verification method publicKeyMultibase test

* Ignore wasm lint warning
  • Loading branch information
cycraig authored Jul 13, 2021
1 parent 26cd558 commit cb4bd72
Show file tree
Hide file tree
Showing 14 changed files with 355 additions and 101 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk

# Ignore IDE/editor files
.idea/
.vscode/

# remove book
book

Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
[workspace]
resolver = "2"

members = [
"identity",
"identity-comm",
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,14 @@ The individual libraries are developed to be agnostic about the utilized [Distri
- [Examples in /examples folder](https://github.com/iotaledger/identity.rs/tree/main/examples): Practical code snippets to get you started with the library.
- [IOTA Identity Experience Team Website](https://iota-community.github.io/X-Team_IOTA_Identity/): Website for a collaborative effort to provide help, guidance and spotlight to the IOTA Identity Community through offering feedback and introducing consistent workflows around IOTA Identity.

## Prerequisites

- [Rust](https://www.rust-lang.org/) (>= 1.51)
- [Cargo](https://doc.rust-lang.org/cargo/) (>= 1.51)

## Getting Started

If you want to include IOTA Identity in your project, simply add it as a dependency in your `cargo.toml`:
If you want to include IOTA Identity in your project, simply add it as a dependency in your `Cargo.toml`:
```rust
[dependencies]
identity = { git = "https://github.com/iotaledger/identity.rs", branch = "main"}
Expand Down
2 changes: 1 addition & 1 deletion bindings/wasm/src/tangle/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ impl WasmNetwork {
self.0.explorer_url().to_string()
}

#[allow(clippy::inherent_to_string)]
#[allow(clippy::inherent_to_string, clippy::wrong_self_convention)]
#[wasm_bindgen(js_name = toString)]
pub fn to_string(&self) -> String {
self.0.as_str().into()
Expand Down
3 changes: 3 additions & 0 deletions identity-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ homepage = "https://www.iota.org"
[dependencies]
base64 = { version = "0.13", default-features = false, features = ["std"] }
bs58 = { version = "0.4", default-features = false, features = ["std"] }
multibase = { version ="0.9", default-features = false, features = ["std"] }
chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
hex = { version = "0.4", default-features = false }
identity-diff = { version = "=0.3.0", path = "../identity-diff", default-features = false }
Expand All @@ -33,6 +34,8 @@ features = ["blake2b", "ed25519", "random", "sha"]

[dev-dependencies]
rand = { version = "0.8" }
quickcheck = { version = "1.0" }
quickcheck_macros = { version = "1.0" }

[package.metadata.docs.rs]
# To build locally:
Expand Down
3 changes: 3 additions & 0 deletions identity-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ pub enum Error {
/// Caused by a failure to decode base64-encoded data.
#[error("Failed to decode base64 data: {0}")]
DecodeBase64(#[from] base64::DecodeError),
/// Caused by a failure to decode multibase-encoded data.
#[error("Failed to decode multibase data: {0}")]
DecodeMultibase(#[from] multibase::Error),
/// Cause by a failure to encode a Roaring Bitmap.
#[error("Failed to encode roaring bitmap: {0}")]
EncodeBitmap(std::io::Error),
Expand Down
217 changes: 201 additions & 16 deletions identity-core/src/utils/base_encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,115 @@
use crate::error::Error;
use crate::error::Result;

/// Decodes the given `data` as base58-btc.
pub fn decode_b58<T>(data: &T) -> Result<Vec<u8>>
/// A [Multibase]-supported base. See [multibase::Base] for more information.
///
/// Excludes the identity (0x00) base as arbitrary bytes cannot be encoded to a valid UTF-8 string
/// in general.
///
/// [Multibase]: https://datatracker.ietf.org/doc/html/draft-multiformats-multibase-03
#[allow(missing_docs)]
#[derive(Debug)]
pub enum Base {
Base2,
Base8,
Base10,
Base16Lower,
Base16Upper,
Base32Lower,
Base32Upper,
Base32PadLower,
Base32PadUpper,
Base32HexLower,
Base32HexUpper,
Base32HexPadLower,
Base32HexPadUpper,
Base32Z,
Base36Lower,
Base36Upper,
Base58Flickr,
Base58Btc,
Base64,
Base64Pad,
Base64Url,
Base64UrlPad,
}

/// Wrap [multibase::Base] to exclude the identity (0x00) and avoid exporting from a pre-1.0 crate.
impl From<Base> for multibase::Base {
fn from(base: Base) -> Self {
match base {
Base::Base2 => multibase::Base::Base2,
Base::Base8 => multibase::Base::Base8,
Base::Base10 => multibase::Base::Base10,
Base::Base16Lower => multibase::Base::Base16Lower,
Base::Base16Upper => multibase::Base::Base16Upper,
Base::Base32Lower => multibase::Base::Base32Lower,
Base::Base32Upper => multibase::Base::Base32Upper,
Base::Base32PadLower => multibase::Base::Base32PadLower,
Base::Base32PadUpper => multibase::Base::Base32PadUpper,
Base::Base32HexLower => multibase::Base::Base32HexLower,
Base::Base32HexUpper => multibase::Base::Base32HexUpper,
Base::Base32HexPadLower => multibase::Base::Base32HexPadLower,
Base::Base32HexPadUpper => multibase::Base::Base32HexPadUpper,
Base::Base32Z => multibase::Base::Base32Z,
Base::Base36Lower => multibase::Base::Base36Lower,
Base::Base36Upper => multibase::Base::Base36Upper,
Base::Base58Flickr => multibase::Base::Base58Flickr,
Base::Base58Btc => multibase::Base::Base58Btc,
Base::Base64 => multibase::Base::Base64,
Base::Base64Pad => multibase::Base::Base64Pad,
Base::Base64Url => multibase::Base::Base64Url,
Base::Base64UrlPad => multibase::Base::Base64UrlPad,
}
}
}

/// Decodes the given `data` as [Multibase] with an inferred [`base`](Base).
///
/// [Multibase]: https://datatracker.ietf.org/doc/html/draft-multiformats-multibase-03
pub fn decode_multibase<T>(data: &T) -> Result<Vec<u8>>
where
T: AsRef<[u8]> + ?Sized,
T: AsRef<str> + ?Sized,
{
bs58::decode(data)
.with_alphabet(bs58::Alphabet::BITCOIN)
.into_vec()
.map_err(Error::DecodeBase58)
if data.as_ref().is_empty() {
return Ok(Vec::new());
}
multibase::decode(&data)
.map(|(_base, output)| output)
.map_err(Error::DecodeMultibase)
}

/// Encodes the given `data` as base58-btc.
pub fn encode_b58<T>(data: &T) -> String
/// Encodes the given `data` as [Multibase] with the given [`base`](Base), defaults to
/// [`Base::Base58Btc`] if omitted.
///
/// NOTE: [`encode_multibase`] with [`Base::Base58Btc`] is different from [`encode_b58`] as
/// the [Multibase] format prepends a base-encoding-character to the output.
///
/// [Multibase]: https://datatracker.ietf.org/doc/html/draft-multiformats-multibase-03
pub fn encode_multibase<T>(data: &T, base: Option<Base>) -> String
where
T: AsRef<[u8]> + ?Sized,
{
bs58::encode(data).with_alphabet(bs58::Alphabet::BITCOIN).into_string()
multibase::encode(multibase::Base::from(base.unwrap_or(Base::Base58Btc)), data)
}

/// Decodes the given `data` as base16 (hex).
pub fn decode_b16<T>(data: &T) -> Result<Vec<u8>>
/// Decodes the given `data` as base58-btc.
pub fn decode_b58<T>(data: &T) -> Result<Vec<u8>>
where
T: AsRef<[u8]> + ?Sized,
{
hex::decode(data).map_err(Error::DecodeBase16)
bs58::decode(data)
.with_alphabet(bs58::Alphabet::BITCOIN)
.into_vec()
.map_err(Error::DecodeBase58)
}

/// Encodes the given `data` as base16 (hex).
pub fn encode_b16<T>(data: &T) -> String
/// Encodes the given `data` as base58-btc.
pub fn encode_b58<T>(data: &T) -> String
where
T: AsRef<[u8]> + ?Sized,
{
hex::encode(data)
bs58::encode(data).with_alphabet(bs58::Alphabet::BITCOIN).into_string()
}

/// Decodes the given `data` as base64.
Expand All @@ -54,3 +130,112 @@ where
{
base64::encode_config(data.as_ref(), base64::URL_SAFE)
}

#[cfg(test)]
mod tests {
use quickcheck_macros::quickcheck;

use super::*;

#[test]
fn test_decode_b58_empty() {
assert_eq!(decode_b58("").unwrap(), Vec::<u8>::new());
}

#[test]
fn test_decode_b64_empty() {
assert_eq!(decode_b64("").unwrap(), Vec::<u8>::new());
}

#[test]
fn test_decode_multibase_empty() {
assert_eq!(decode_multibase("").unwrap(), Vec::<u8>::new());
}

#[quickcheck]
fn test_b58_random(data: Vec<u8>) {
assert_eq!(decode_b58(&encode_b58(&data)).unwrap(), data);
}

#[quickcheck]
fn test_b64_random(data: Vec<u8>) {
assert_eq!(decode_b64(&encode_b64(&data)).unwrap(), data);
}

#[quickcheck]
fn test_multibase_random(data: Vec<u8>) {
assert_eq!(decode_multibase(&encode_multibase(&data, None)).unwrap(), data);
}

#[quickcheck]
fn test_multibase_bases_random(data: Vec<u8>) {
let bases = [
Base::Base2,
Base::Base8,
Base::Base10,
Base::Base16Lower,
Base::Base16Upper,
Base::Base32Lower,
Base::Base32Upper,
Base::Base32PadLower,
Base::Base32PadUpper,
Base::Base32HexLower,
Base::Base32HexUpper,
Base::Base32HexPadLower,
Base::Base32HexPadUpper,
Base::Base32Z,
Base::Base36Lower,
Base::Base36Upper,
Base::Base58Flickr,
Base::Base58Btc,
Base::Base64,
Base::Base64Pad,
Base::Base64Url,
Base::Base64UrlPad,
];
for base in bases {
assert_eq!(decode_multibase(&encode_multibase(&data, Some(base))).unwrap(), data);
}
}

/// Multibase test values from Internet Engineering Task Force (IETF) draft.
/// https://datatracker.ietf.org/doc/html/draft-multiformats-multibase-03#appendix-B
#[test]
fn test_multibase() {
let data = r#"Multibase is awesome! \o/"#;
assert_eq!(
encode_multibase(data, Some(Base::Base16Upper)).as_str(),
"F4D756C74696261736520697320617765736F6D6521205C6F2F"
);
assert_eq!(
encode_multibase(data, Some(Base::Base32Upper)).as_str(),
"BJV2WY5DJMJQXGZJANFZSAYLXMVZW63LFEEQFY3ZP"
);
assert_eq!(
encode_multibase(data, Some(Base::Base58Btc)).as_str(),
"zYAjKoNbau5KiqmHPmSxYCvn66dA1vLmwbt"
);
assert_eq!(
encode_multibase(data, Some(Base::Base64Pad)).as_str(),
"MTXVsdGliYXNlIGlzIGF3ZXNvbWUhIFxvLw=="
);

let expected = data.as_bytes().to_vec();
assert_eq!(
decode_multibase("F4D756C74696261736520697320617765736F6D6521205C6F2F").unwrap(),
expected
);
assert_eq!(
decode_multibase("BJV2WY5DJMJQXGZJANFZSAYLXMVZW63LFEEQFY3ZP").unwrap(),
expected
);
assert_eq!(
decode_multibase("zYAjKoNbau5KiqmHPmSxYCvn66dA1vLmwbt").unwrap(),
expected
);
assert_eq!(
decode_multibase("MTXVsdGliYXNlIGlzIGF3ZXNvbWUhIFxvLw==").unwrap(),
expected
);
}
}
24 changes: 23 additions & 1 deletion identity-did/src/diff/diff_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ mod test {
}

#[test]
fn test_key_data() {
fn test_key_data_base58() {
let method = test_method();
let mut new = method.clone();
*new.key_data_mut() = MethodData::PublicKeyBase58("diff".into());
Expand All @@ -304,6 +304,28 @@ mod test {
assert_eq!(merge, new);
}

#[test]
fn test_key_data_multibase() {
let method = test_method();
let mut new = method.clone();
*new.key_data_mut() = MethodData::PublicKeyMultibase("diff".into());

let diff = method.diff(&new).unwrap();
assert!(diff.id.is_none());
assert!(diff.controller.is_none());
assert!(diff.key_type.is_none());
assert!(diff.properties.is_none());
assert_eq!(
diff.key_data,
Some(DiffMethodData::PublicKeyMultibase(Some(DiffString(Some(
"diff".to_string()
)))))
);

let merge = method.merge(diff).unwrap();
assert_eq!(merge, new);
}

#[test]
fn test_from_diff() {
let method = test_method();
Expand Down
Loading

0 comments on commit cb4bd72

Please sign in to comment.