From 0c5ac037b8b42f6b2e83224a0cf53b130cb6025c Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Wed, 12 Apr 2023 11:36:24 +0900 Subject: [PATCH 01/27] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20change=20static=20?= =?UTF-8?q?library=20build=20to=20rust=20package?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 10 ++-- Makefile | 129 -------------------------------------------------- cbindgen.toml | 8 ---- src/lib.rs | 83 ++++++++------------------------ 4 files changed, 24 insertions(+), 206 deletions(-) delete mode 100644 Makefile delete mode 100644 cbindgen.toml diff --git a/Cargo.toml b/Cargo.toml index d379e64..22187d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,8 @@ [package] -name = "example-rs" +name = "infra-did" version = "0.1.0" -authors = ["Brick Pop "] -edition = "2018" - -[lib] -name = "example" -crate-type = ["staticlib", "cdylib"] +authors = ["InfraBlockchain"] +edition = "2021" [dependencies] hex = "0.4.3" diff --git a/Makefile b/Makefile deleted file mode 100644 index 52ca3b6..0000000 --- a/Makefile +++ /dev/null @@ -1,129 +0,0 @@ -.DEFAULT_GOAL := help -PROJECTNAME=$(shell basename "$(PWD)") -SOURCES=$(sort $(wildcard ./src/*.rs ./src/**/*.rs)) - -OS_NAME=$(shell uname | tr '[:upper:]' '[:lower:]') -PATH := $(ANDROID_NDK_HOME)/toolchains/llvm/prebuilt/$(OS_NAME)-x86_64/bin:$(PATH) - -ANDROID_AARCH64_LINKER=$(ANDROID_NDK_HOME)/toolchains/llvm/prebuilt/$(OS_NAME)-x86_64/bin/aarch64-linux-android29-clang -ANDROID_ARMV7_LINKER=$(ANDROID_NDK_HOME)/toolchains/llvm/prebuilt/$(OS_NAME)-x86_64/bin/armv7a-linux-androideabi29-clang -ANDROID_I686_LINKER=$(ANDROID_NDK_HOME)/toolchains/llvm/prebuilt/$(OS_NAME)-x86_64/bin/i686-linux-android29-clang -ANDROID_X86_64_LINKER=$(ANDROID_NDK_HOME)/toolchains/llvm/prebuilt/$(OS_NAME)-x86_64/bin/x86_64-linux-android29-clang - -SHELL := /bin/bash - -# ############################################################################## -# # GENERAL -# ############################################################################## - -.PHONY: help -help: makefile - @echo - @echo " Available actions in "$(PROJECTNAME)":" - @echo - @sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /' - @echo - -## init: Install missing dependencies. -.PHONY: init -init: - rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim - rustup target add aarch64-apple-darwin x86_64-apple-darwin - #rustup target add armv7-apple-ios armv7s-apple-ios i386-apple-ios ## deprecated - rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android - @if [ $$(uname) == "Darwin" ] ; then cargo install cargo-lipo ; fi - cargo install cbindgen - -## : - -# ############################################################################## -# # RECIPES -# ############################################################################## - -## all: Compile iOS, Android and bindings targets -all: ios macos android bindings - -## ios: Compile the iOS universal library -ios: target/universal/release/libexample.a - -target/universal/release/libexample.a: $(SOURCES) ndk-home - @if [ $$(uname) == "Darwin" ] ; then \ - cargo lipo --release ; \ - else echo "Skipping iOS compilation on $$(uname)" ; \ - fi - @echo "[DONE] $@" - -## macos: Compile the macOS libraries -macos: target/x86_64-apple-darwin/release/libexample.dylib target/aarch64-apple-darwin/release/libexample.dylib - -target/x86_64-apple-darwin/release/libexample.dylib: $(SOURCES) - @if [ $$(uname) == "Darwin" ] ; then \ - cargo lipo --release --targets x86_64-apple-darwin ; \ - else echo "Skipping macOS compilation on $$(uname)" ; \ - fi - @echo "[DONE] $@" - -target/aarch64-apple-darwin/release/libexample.dylib: $(SOURCES) - @if [ $$(uname) == "Darwin" ] ; then \ - cargo lipo --release --targets aarch64-apple-darwin ; \ - else echo "Skipping macOS compilation on $$(uname)" ; \ - fi - @echo "[DONE] $@" - -## android: Compile the android targets (arm64, armv7 and i686) -android: target/aarch64-linux-android/release/libexample.so target/armv7-linux-androideabi/release/libexample.so target/i686-linux-android/release/libexample.so target/x86_64-linux-android/release/libexample.so - -target/aarch64-linux-android/release/libexample.so: $(SOURCES) ndk-home - CC_aarch64_linux_android=$(ANDROID_AARCH64_LINKER) \ - CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=$(ANDROID_AARCH64_LINKER) \ - cargo build --target aarch64-linux-android --release - @echo "[DONE] $@" - -target/armv7-linux-androideabi/release/libexample.so: $(SOURCES) ndk-home - CC_armv7_linux_androideabi=$(ANDROID_ARMV7_LINKER) \ - CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER=$(ANDROID_ARMV7_LINKER) \ - cargo build --target armv7-linux-androideabi --release - @echo "[DONE] $@" - -target/i686-linux-android/release/libexample.so: $(SOURCES) ndk-home - CC_i686_linux_android=$(ANDROID_I686_LINKER) \ - CARGO_TARGET_I686_LINUX_ANDROID_LINKER=$(ANDROID_I686_LINKER) \ - cargo build --target i686-linux-android --release - @echo "[DONE] $@" - -target/x86_64-linux-android/release/libexample.so: $(SOURCES) ndk-home - CC_x86_64_linux_android=$(ANDROID_X86_64_LINKER) \ - CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER=$(ANDROID_X86_64_LINKER) \ - cargo build --target x86_64-linux-android --release - @echo "[DONE] $@" - -.PHONY: ndk-home -ndk-home: - @if [ ! -d "${ANDROID_NDK_HOME}" ] ; then \ - echo "Error: Please, set the ANDROID_NDK_HOME env variable to point to your NDK folder" ; \ - exit 1 ; \ - fi - -## bindings: Generate the .h file for iOS -bindings: target/bindings.h - -target/bindings.h: $(SOURCES) - cbindgen ./src/lib.rs -c cbindgen.toml | grep -v \#include | uniq > $@ - @echo "[DONE] $@" - -## : - -# ############################################################################## -# # OTHER -# ############################################################################## - -## clean: -.PHONY: clean -clean: - cargo clean - rm -f target/bindings.h target/bindings.src.h - -## test: -.PHONY: test -test: - cargo test diff --git a/cbindgen.toml b/cbindgen.toml deleted file mode 100644 index 296d3de..0000000 --- a/cbindgen.toml +++ /dev/null @@ -1,8 +0,0 @@ -language = "C" -autogen_warning = "// NOTE: Append the lines below to ios/Classes/Plugin.h" -#namespace = "ffi" -#include_guard = "CBINDGEN_BINDINGS_H" - -[defines] -"target_os = ios" = "TARGET_OS_IOS" -"target_os = macos" = "TARGET_OS_MACOS" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 4fa61b5..c2bac22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,50 +2,32 @@ use bip39::{Language, Mnemonic, MnemonicType, Seed}; use schnorrkel::ExpansionMode; use serde_json::json; use sr25519::KeyPair; -use std::ffi::{CStr, CString}; -use std::os::raw::c_char; use substrate_bip39::mini_secret_from_entropy; mod sr25519; -fn get_str(rust_ptr: *const c_char) -> String { - let c_str = unsafe { CStr::from_ptr(rust_ptr) }; - let result_string = match c_str.to_str() { - Err(_) => "input string error", - Ok(string) => string, - }; - return String::from(result_string); -} - -fn get_ptr(rust_string: &str) -> *mut c_char { - CString::new(rust_string).unwrap().into_raw() -} - -#[no_mangle] -pub extern "C" fn random_phrase(words_number: u32) -> *mut c_char { +pub fn random_phrase(words_number: u32) -> String { let mnemonic_type = match MnemonicType::for_word_count(words_number as usize) { Ok(t) => t, Err(_e) => MnemonicType::Words24, }; let mnemonic = Mnemonic::new(mnemonic_type, Language::English); - get_ptr(&mnemonic.into_phrase()) + mnemonic.into_phrase() } -#[no_mangle] -pub extern "C" fn substrate_address(suri: *const c_char, prefix: u8) -> *mut c_char { - let keypair_option = KeyPair::from_suri(&get_str(suri)); +pub fn substrate_address(suri: &str, prefix: u8) -> String { + let keypair_option = KeyPair::from_suri(suri); let keypair = match keypair_option { Some(c) => c, - _ => return get_ptr(""), + _ => return "".to_string(), }; let rust_string = keypair.ss58_address(prefix); - get_ptr(&rust_string) + rust_string } -#[no_mangle] -pub extern "C" fn generate_ss58_did(network_id: *const c_char) -> *mut c_char { +pub fn generate_ss58_did(network_id: &str) -> String { let mnemonic_type = MnemonicType::for_word_count(12).unwrap(); let mnemonic = Mnemonic::new(mnemonic_type, Language::English); @@ -53,14 +35,13 @@ pub extern "C" fn generate_ss58_did(network_id: *const c_char) -> *mut c_char { let keypair = match keypair_option { Some(c) => c, - _ => return get_ptr(""), + _ => return "".to_string(), }; let seed = Seed::new(&mnemonic, ""); - let network_id_string = get_str(network_id); let address = keypair.ss58_address(42); - let did = format!("did:infra:{}:{}", network_id_string, address.clone()); + let did = format!("did:infra:{}:{}", network_id, address.clone()); let mini_secret_key = mini_secret_from_entropy(mnemonic.entropy(), "").unwrap(); @@ -76,13 +57,12 @@ pub extern "C" fn generate_ss58_did(network_id: *const c_char) -> *mut c_char { "did": did })); - get_ptr(&result.unwrap()) + result.unwrap() } #[no_mangle] -pub extern "C" fn did_to_hex_public_key(did: *mut c_char) -> *mut c_char { - let did_string = get_str(did); - let splited_did: Vec<&str> = did_string.split(":").collect(); +pub fn did_to_hex_public_key(did: &str) -> String { + let splited_did: Vec<&str> = did.split(":").collect(); let address = splited_did[3]; let decoded_address = bs58::decode(address).into_vec().unwrap(); @@ -90,53 +70,32 @@ pub extern "C" fn did_to_hex_public_key(did: *mut c_char) -> *mut c_char { let public_key: schnorrkel::PublicKey = schnorrkel::PublicKey::from_bytes(&decoded_address[1..33]).unwrap(); - get_ptr(&hex::encode(public_key.to_bytes())) -} - -#[no_mangle] -pub extern "C" fn ss58_address_to_did( - address: *mut c_char, - network_id: *mut c_char, -) -> *mut c_char { - let address_string = get_str(address); - let network_id_string = get_str(network_id); - - let did = format!("did:infra:{}:{}", network_id_string, address_string); - get_ptr(&did) + hex::encode(public_key.to_bytes()) } #[no_mangle] -pub extern "C" fn rust_cstr_free(s: *mut c_char) { - unsafe { - if s.is_null() { - return; - } - CString::from_raw(s) - }; +pub fn ss58_address_to_did(address: &str, network_id: &str) -> String { + let did = format!("did:infra:{}:{}", network_id, address); + did } #[test] fn test_generate_ss58_did() { - println!("{:?}", generate_ss58_did(get_ptr("01"))); + println!("{:?}", generate_ss58_did("01")); } #[test] fn test_did_to_hex_public_key() { assert_eq!( - get_str(did_to_hex_public_key(get_ptr( - "did:infra:01:5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL" - ))), - "de7687abb0442514b3f765e17f6cde78227e3b5afa45627f12d805fb5c5e473a" + did_to_hex_public_key("did:infra:01:5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL"), + "de7687abb0442514b3f765e17f6cde78227e3b5afa45627f12d805fb5c5e473a".to_string() ); } #[test] fn test_ss58_address_to_did() { assert_eq!( - get_str(ss58_address_to_did( - get_ptr("5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL"), - get_ptr("01") - )), - "did:infra:01:5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL" + ss58_address_to_did("5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL", "01"), + "did:infra:01:5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL".to_string() ); } From 1b6e1c7180b0c69db8ff984144d41a99c1e59f7a Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Wed, 12 Apr 2023 11:42:30 +0900 Subject: [PATCH 02/27] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20change=20parameter?= =?UTF-8?q?=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c2bac22..6372179 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,8 +16,8 @@ pub fn random_phrase(words_number: u32) -> String { mnemonic.into_phrase() } -pub fn substrate_address(suri: &str, prefix: u8) -> String { - let keypair_option = KeyPair::from_suri(suri); +pub fn substrate_address(suri: String, prefix: u8) -> String { + let keypair_option = KeyPair::from_suri(suri.as_str()); let keypair = match keypair_option { Some(c) => c, _ => return "".to_string(), @@ -27,7 +27,7 @@ pub fn substrate_address(suri: &str, prefix: u8) -> String { rust_string } -pub fn generate_ss58_did(network_id: &str) -> String { +pub fn generate_ss58_did(network_id: String) -> String { let mnemonic_type = MnemonicType::for_word_count(12).unwrap(); let mnemonic = Mnemonic::new(mnemonic_type, Language::English); @@ -61,7 +61,7 @@ pub fn generate_ss58_did(network_id: &str) -> String { } #[no_mangle] -pub fn did_to_hex_public_key(did: &str) -> String { +pub fn did_to_hex_public_key(did: String) -> String { let splited_did: Vec<&str> = did.split(":").collect(); let address = splited_did[3]; @@ -74,20 +74,22 @@ pub fn did_to_hex_public_key(did: &str) -> String { } #[no_mangle] -pub fn ss58_address_to_did(address: &str, network_id: &str) -> String { +pub fn ss58_address_to_did(address: String, network_id: String) -> String { let did = format!("did:infra:{}:{}", network_id, address); did } #[test] fn test_generate_ss58_did() { - println!("{:?}", generate_ss58_did("01")); + println!("{:?}", generate_ss58_did("01".to_string())); } #[test] fn test_did_to_hex_public_key() { assert_eq!( - did_to_hex_public_key("did:infra:01:5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL"), + did_to_hex_public_key( + "did:infra:01:5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL".to_string() + ), "de7687abb0442514b3f765e17f6cde78227e3b5afa45627f12d805fb5c5e473a".to_string() ); } @@ -95,7 +97,10 @@ fn test_did_to_hex_public_key() { #[test] fn test_ss58_address_to_did() { assert_eq!( - ss58_address_to_did("5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL", "01"), + ss58_address_to_did( + "5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL".to_string(), + "01".to_string() + ), "did:infra:01:5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL".to_string() ); } From 807fb598048e4c2af3f4e7a40c2d1265fecee0b8 Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Wed, 12 Apr 2023 11:55:20 +0900 Subject: [PATCH 03/27] =?UTF-8?q?chore:=20=F0=9F=A4=96=20change=20function?= =?UTF-8?q?=20names?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit avoid same symbol name error in flutter package --- src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6372179..5200838 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ use substrate_bip39::mini_secret_from_entropy; mod sr25519; -pub fn random_phrase(words_number: u32) -> String { +pub fn _random_phrase(words_number: u32) -> String { let mnemonic_type = match MnemonicType::for_word_count(words_number as usize) { Ok(t) => t, Err(_e) => MnemonicType::Words24, @@ -16,7 +16,7 @@ pub fn random_phrase(words_number: u32) -> String { mnemonic.into_phrase() } -pub fn substrate_address(suri: String, prefix: u8) -> String { +pub fn _substrate_address(suri: String, prefix: u8) -> String { let keypair_option = KeyPair::from_suri(suri.as_str()); let keypair = match keypair_option { Some(c) => c, @@ -27,7 +27,7 @@ pub fn substrate_address(suri: String, prefix: u8) -> String { rust_string } -pub fn generate_ss58_did(network_id: String) -> String { +pub fn _generate_ss58_did(network_id: String) -> String { let mnemonic_type = MnemonicType::for_word_count(12).unwrap(); let mnemonic = Mnemonic::new(mnemonic_type, Language::English); @@ -61,7 +61,7 @@ pub fn generate_ss58_did(network_id: String) -> String { } #[no_mangle] -pub fn did_to_hex_public_key(did: String) -> String { +pub fn _did_to_hex_public_key(did: String) -> String { let splited_did: Vec<&str> = did.split(":").collect(); let address = splited_did[3]; @@ -74,20 +74,20 @@ pub fn did_to_hex_public_key(did: String) -> String { } #[no_mangle] -pub fn ss58_address_to_did(address: String, network_id: String) -> String { +pub fn _ss58_address_to_did(address: String, network_id: String) -> String { let did = format!("did:infra:{}:{}", network_id, address); did } #[test] fn test_generate_ss58_did() { - println!("{:?}", generate_ss58_did("01".to_string())); + println!("{:?}", _generate_ss58_did("01".to_string())); } #[test] fn test_did_to_hex_public_key() { assert_eq!( - did_to_hex_public_key( + _did_to_hex_public_key( "did:infra:01:5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL".to_string() ), "de7687abb0442514b3f765e17f6cde78227e3b5afa45627f12d805fb5c5e473a".to_string() @@ -97,7 +97,7 @@ fn test_did_to_hex_public_key() { #[test] fn test_ss58_address_to_did() { assert_eq!( - ss58_address_to_did( + _ss58_address_to_did( "5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL".to_string(), "01".to_string() ), From 4a81c75e786e177e459215266ab84b034e14e8ee Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Thu, 13 Apr 2023 09:30:21 +0900 Subject: [PATCH 04/27] =?UTF-8?q?chore:=20=F0=9F=A4=96=20add=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..f7c802f --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# infra-did-rust + +**This Library is rust version of ss58 based did in infra-did-js** + +- Infra DID Method Spec + + - https://github.com/InfraBlockchain/infra-did-method-specs/blob/main/docs/Infra-DID-method-spec.md + +- Infra DID Registry Smart Contract on InfraBlockchain + + - https://github.com/InfraBlockchain/infra-did-registry + +- Infra DID Resolver (DIF javascript universal resolver compatible) + - https://github.com/InfraBlockchain/infra-did-resolver + +Feature provided by infra-did-dart Library : + +- Infra DID Creation +- update DID attributes (service endpoint) +- update ss58 DID owner key +- revoke ss58 DID +- add/update/remove trusted ss58 DID +- get trusted ss58 DID +- VC/VP creation/verification + +## Installation + +- **Using [crates](https://crates.io/)**: + +```sh +cargo add infra-did +``` + +## Feature + +WIP + +## License + +**Infra-DID-Dart** is under MIT license. See the [LICENSE](https://github.com/InfraBlockchain/infra-did-dart/blob/master/LICENSE) file for more info. \ No newline at end of file From e4fa5bbdd202a265e5bb605903442e79f2daf84c Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Tue, 18 Apr 2023 14:21:41 +0900 Subject: [PATCH 05/27] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20crypto=20fun?= =?UTF-8?q?ctions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 6 ++ src/crypto/ed25519.rs | 133 ++++++++++++++++++++++++++++++++++++ src/crypto/keytype.rs | 6 ++ src/crypto/mod.rs | 3 + src/{ => crypto}/sr25519.rs | 101 +++++++++++++++++++++++++-- src/lib.rs | 107 +---------------------------- 6 files changed, 244 insertions(+), 112 deletions(-) create mode 100644 src/crypto/ed25519.rs create mode 100644 src/crypto/keytype.rs create mode 100644 src/crypto/mod.rs rename src/{ => crypto}/sr25519.rs (58%) diff --git a/Cargo.toml b/Cargo.toml index 22187d2..43ead3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,9 @@ blake2-rfc = "0.2.18" rustc-hex = "2.0.1" ed25519-dalek = "1.0.1" bs58 = "0.4.0" +rand = "0.7.0" +chrono = { version = "0.4", features = ["serde"] } +thiserror = "1.0.40" +iref = { version = "2.2.2", features = ["serde"] } +static-iref = "2.0.0" +multibase = "0.9.1" diff --git a/src/crypto/ed25519.rs b/src/crypto/ed25519.rs new file mode 100644 index 0000000..dd58fad --- /dev/null +++ b/src/crypto/ed25519.rs @@ -0,0 +1,133 @@ +use base58::ToBase58; +use ed25519_dalek::{ + Keypair, PublicKey, SecretKey, Signature, Signer, Verifier, KEYPAIR_LENGTH, SECRET_KEY_LENGTH, +}; +use rand::rngs::OsRng; + +pub struct Ed25519KeyPair(ed25519_dalek::Keypair); + +impl Ed25519KeyPair { + pub fn generate() -> Ed25519KeyPair { + let mut csprng = OsRng {}; + let keypair: Keypair = Keypair::generate(&mut csprng); + Ed25519KeyPair(keypair) + } + + pub fn from_secret_key_bytes(bytes: &[u8]) -> Ed25519KeyPair { + let secret_key: SecretKey = SecretKey::from_bytes(bytes).unwrap(); + let public_key: PublicKey = (&secret_key).into(); + + let secret = secret_key.to_bytes(); + let public = public_key.to_bytes(); + + let mut keypair_bytes: [u8; KEYPAIR_LENGTH] = [0u8; KEYPAIR_LENGTH]; + + keypair_bytes[..SECRET_KEY_LENGTH].copy_from_slice(&secret); + keypair_bytes[SECRET_KEY_LENGTH..].copy_from_slice(&public); + + let keypair = ed25519_dalek::Keypair::from_bytes(&keypair_bytes).ok(); + Ed25519KeyPair(keypair.unwrap()) + } + + pub fn ss58_address(&self, prefix: u8) -> String { + let mut v = vec![prefix]; + v.extend_from_slice(&self.0.public.to_bytes()); + let r = ss58hash(&v); + v.extend_from_slice(&r.as_bytes()[0..2]); + v.to_base58() + } + + pub fn sign(&self, message: &[u8]) -> Signature { + let signature: Signature = self.0.sign(message); + signature + } + + pub fn verify_signature(&self, message: &[u8], signature: &[u8]) -> bool { + let signature = ed25519_dalek::Signature::from_bytes(signature).unwrap(); + let public_key: PublicKey = self.0.public; + let verified: bool = public_key.verify(message, &signature).is_ok(); + verified + } +} + +fn ss58hash(data: &[u8]) -> blake2_rfc::blake2b::Blake2bResult { + const PREFIX: &[u8] = b"SS58PRE"; + + let mut context = blake2_rfc::blake2b::Blake2b::new(64); + context.update(PREFIX); + context.update(data); + context.finalize() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_generate() { + let keypair = Ed25519KeyPair::generate(); + match keypair { + Ed25519KeyPair(keypair) => { + let bytes = keypair.to_bytes(); + let secret_key_bytes = &bytes[..SECRET_KEY_LENGTH]; + let public_key_bytes = &bytes[SECRET_KEY_LENGTH..]; + println!("{:?}", keypair.to_bytes()); + println!("{:?}", hex::encode(secret_key_bytes)); + println!("{:?}", hex::encode(public_key_bytes)); + } + _ => assert!(false), + } + } + + #[test] + fn test_from_secret_key_bytes() { + let bytes = [ + 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, + 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, + ]; + let keypair = Ed25519KeyPair::from_secret_key_bytes(&bytes); + match keypair { + Ed25519KeyPair(keypair) => { + let bytes = keypair.to_bytes(); + let secret_key_bytes = &bytes[..SECRET_KEY_LENGTH]; + let public_key_bytes = &bytes[SECRET_KEY_LENGTH..]; + assert_eq!( + hex::encode(secret_key_bytes), + "cb534bf8dd15a901ee442cae510b246f5e94247d73570bea47e0aa859959c412" + ); + assert_eq!( + hex::encode(public_key_bytes), + "b86044c551e40dc1de84aa89c2dcf27657a43e0510f14e9388c1100a76f94e5c" + ); + } + _ => assert!(false), + } + } + + #[test] + fn test_sign() { + let bytes = [ + 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, + 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, + ]; + let keypair = Ed25519KeyPair::from_secret_key_bytes(&bytes); + let message = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let signature = keypair.sign(&message); + let sig_multibase = multibase::encode(multibase::Base::Base58Btc, signature); + assert_eq!(sig_multibase,"zmqquC4Hb5EK7L7JPQjzABJ8rK8dvpVDgWfN8d6JQ5F96sw91g2mz4m3iPSJ4tQ9jXYE3VmLPvaCBhqQETkEVtbJ"); + } + + #[test] + fn test_verify_signature() { + let bytes = [ + 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, + 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, + ]; + let keypair = Ed25519KeyPair::from_secret_key_bytes(&bytes); + let message = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let sig_multibase = "zmqquC4Hb5EK7L7JPQjzABJ8rK8dvpVDgWfN8d6JQ5F96sw91g2mz4m3iPSJ4tQ9jXYE3VmLPvaCBhqQETkEVtbJ"; + let (_base, sig) = multibase::decode(sig_multibase).unwrap(); + let verify = keypair.verify_signature(&message, &sig); + assert_eq!(verify, true); + } +} diff --git a/src/crypto/keytype.rs b/src/crypto/keytype.rs new file mode 100644 index 0000000..51f5d69 --- /dev/null +++ b/src/crypto/keytype.rs @@ -0,0 +1,6 @@ +use super::{ed25519::Ed25519KeyPair, sr25519::Sr25519KeyPair}; + +pub enum KeyType { + Ed25519(Ed25519KeyPair), + Sr25519(Sr25519KeyPair), +} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs new file mode 100644 index 0000000..e98cb7e --- /dev/null +++ b/src/crypto/mod.rs @@ -0,0 +1,3 @@ +pub mod ed25519; +pub mod keytype; +pub mod sr25519; diff --git a/src/sr25519.rs b/src/crypto/sr25519.rs similarity index 58% rename from src/sr25519.rs rename to src/crypto/sr25519.rs index 212cfbe..b269faa 100644 --- a/src/sr25519.rs +++ b/src/crypto/sr25519.rs @@ -8,25 +8,25 @@ use substrate_bip39::mini_secret_from_entropy; use lazy_static::lazy_static; -pub struct KeyPair(schnorrkel::Keypair); +pub struct Sr25519KeyPair(schnorrkel::Keypair); const SIGNING_CTX: &[u8] = b"substrate"; const JUNCTION_ID_LEN: usize = 32; const CHAIN_CODE_LENGTH: usize = 32; -impl KeyPair { - pub fn from_bip39_phrase(phrase: &str, password: Option<&str>) -> Option { +impl Sr25519KeyPair { + pub fn from_bip39_phrase(phrase: &str, password: Option<&str>) -> Option { let mnemonic = Mnemonic::from_phrase(phrase, Language::English).ok()?; let mini_secret_key = mini_secret_from_entropy(mnemonic.entropy(), password.unwrap_or("")).ok()?; - Some(KeyPair( + Some(Sr25519KeyPair( mini_secret_key.expand_to_keypair(ExpansionMode::Ed25519), )) } // Should match implementation at https://github.com/paritytech/substrate/blob/master/core/primitives/src/crypto.rs#L653-L682 - pub fn from_suri(suri: &str) -> Option { + pub fn from_suri(suri: &str) -> Option { lazy_static! { static ref RE_SURI: Regex = { Regex::new(r"^(?P\w+( \w+)*)?(?P(//?[^/]+)*)(///(?P.*))?$") @@ -56,7 +56,7 @@ impl KeyPair { DeriveJunction::Hard(cc) => derive_hard_junction(&acc, cc), }); - KeyPair(result.to_keypair()) + Sr25519KeyPair(result.to_keypair()) } pub fn ss58_address(&self, prefix: u8) -> String { @@ -163,3 +163,92 @@ fn ss58hash(data: &[u8]) -> blake2_rfc::blake2b::Blake2bResult { context.update(data); context.finalize() } + +#[cfg(test)] +mod tests { + use schnorrkel::SECRET_KEY_LENGTH; + + use super::*; + + #[test] + fn test_from_bip39_phrase() { + let keypair = Sr25519KeyPair::from_bip39_phrase( + "true crowd stereo border country ocean mountain sadness term stumble media glory", + Some(""), + ) + .unwrap(); + match keypair { + Sr25519KeyPair(keypair) => { + let keypair_bytes = keypair.to_bytes(); + let secret_key_bytes = &keypair_bytes[..SECRET_KEY_LENGTH]; + let publuc_key_bytes = &keypair_bytes[SECRET_KEY_LENGTH..]; + assert_eq!(hex::encode(secret_key_bytes),"b3370307d69f13cece7c28b2fa6380bcd56e9f32c9daa5a7be545efb65bc370dbab0ac540259f83925afca9192fa73f99f3ec9ca1c8da3297b0e05a87fee3df3"); + assert_eq!( + hex::encode(publuc_key_bytes), + "f02283ff600d00613244e1e43dc88d56fec666223de7ebeb3f32e93a375fe12b" + ); + } + _ => assert!(false), + } + } + + #[test] + fn test_from_suri() { + let keypair = Sr25519KeyPair::from_suri( + "true crowd stereo border country ocean mountain sadness term stumble media glory", + ) + .unwrap(); + match keypair { + Sr25519KeyPair(keypair) => { + let keypair_bytes = keypair.to_bytes(); + let secret_key_bytes = &keypair_bytes[..SECRET_KEY_LENGTH]; + let publuc_key_bytes = &keypair_bytes[SECRET_KEY_LENGTH..]; + assert_eq!(hex::encode(secret_key_bytes),"b3370307d69f13cece7c28b2fa6380bcd56e9f32c9daa5a7be545efb65bc370dbab0ac540259f83925afca9192fa73f99f3ec9ca1c8da3297b0e05a87fee3df3"); + assert_eq!( + hex::encode(publuc_key_bytes), + "f02283ff600d00613244e1e43dc88d56fec666223de7ebeb3f32e93a375fe12b" + ); + } + _ => assert!(false), + } + } + + #[test] + fn test_ss58_address() { + let keypair = Sr25519KeyPair::from_suri( + "true crowd stereo border country ocean mountain sadness term stumble media glory", + ) + .unwrap(); + let address = keypair.ss58_address(42); + assert_eq!(address, "5HVZbuy7bpM8NX7VXTyxoL5dvk5W3496vkknoWtVhF7cRjc3"); + } + + #[test] + fn test_sign() { + let keypair = Sr25519KeyPair::from_suri( + "true crowd stereo border country ocean mountain sadness term stumble media glory", + ) + .unwrap(); + let message = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let signature = keypair.sign(&message); + // sr25519 signature is non-deterministic + assert_eq!(hex::encode(signature).len(), 128); + } + + #[test] + fn test_verify_signature() { + let keypair = Sr25519KeyPair::from_suri( + "true crowd stereo border country ocean mountain sadness term stumble media glory", + ) + .unwrap(); + let message = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let signature = [ + 80, 147, 218, 23, 52, 24, 12, 20, 87, 87, 240, 184, 36, 197, 125, 76, 121, 152, 133, + 133, 226, 196, 178, 32, 112, 254, 10, 160, 116, 123, 149, 57, 11, 223, 29, 28, 192, 78, + 190, 6, 248, 99, 45, 96, 43, 87, 164, 205, 213, 177, 62, 199, 240, 195, 50, 21, 209, + 155, 206, 38, 7, 23, 245, 143, + ]; + let verify = keypair.verify_signature(&message, &signature).unwrap(); + assert_eq!(verify, true); + } +} diff --git a/src/lib.rs b/src/lib.rs index 5200838..274f0ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,106 +1 @@ -use bip39::{Language, Mnemonic, MnemonicType, Seed}; -use schnorrkel::ExpansionMode; -use serde_json::json; -use sr25519::KeyPair; -use substrate_bip39::mini_secret_from_entropy; - -mod sr25519; - -pub fn _random_phrase(words_number: u32) -> String { - let mnemonic_type = match MnemonicType::for_word_count(words_number as usize) { - Ok(t) => t, - Err(_e) => MnemonicType::Words24, - }; - let mnemonic = Mnemonic::new(mnemonic_type, Language::English); - - mnemonic.into_phrase() -} - -pub fn _substrate_address(suri: String, prefix: u8) -> String { - let keypair_option = KeyPair::from_suri(suri.as_str()); - let keypair = match keypair_option { - Some(c) => c, - _ => return "".to_string(), - }; - - let rust_string = keypair.ss58_address(prefix); - rust_string -} - -pub fn _generate_ss58_did(network_id: String) -> String { - let mnemonic_type = MnemonicType::for_word_count(12).unwrap(); - let mnemonic = Mnemonic::new(mnemonic_type, Language::English); - - let keypair_option = KeyPair::from_suri(mnemonic.clone().into_phrase().as_str()); - - let keypair = match keypair_option { - Some(c) => c, - _ => return "".to_string(), - }; - - let seed = Seed::new(&mnemonic, ""); - - let address = keypair.ss58_address(42); - let did = format!("did:infra:{}:{}", network_id, address.clone()); - - let mini_secret_key = mini_secret_from_entropy(mnemonic.entropy(), "").unwrap(); - - let secret_key: schnorrkel::SecretKey = mini_secret_key.expand(ExpansionMode::Ed25519); - let public_key: schnorrkel::PublicKey = secret_key.to_public(); - - let result = serde_json::to_string(&json!({ - "mnemonic": mnemonic.into_phrase(), - "seed": hex::encode(seed.clone()), - "private_key": hex::encode(secret_key.to_bytes()), - "public_key": hex::encode(public_key.to_bytes()), - "address": address.clone(), - "did": did - })); - - result.unwrap() -} - -#[no_mangle] -pub fn _did_to_hex_public_key(did: String) -> String { - let splited_did: Vec<&str> = did.split(":").collect(); - let address = splited_did[3]; - - let decoded_address = bs58::decode(address).into_vec().unwrap(); - - let public_key: schnorrkel::PublicKey = - schnorrkel::PublicKey::from_bytes(&decoded_address[1..33]).unwrap(); - - hex::encode(public_key.to_bytes()) -} - -#[no_mangle] -pub fn _ss58_address_to_did(address: String, network_id: String) -> String { - let did = format!("did:infra:{}:{}", network_id, address); - did -} - -#[test] -fn test_generate_ss58_did() { - println!("{:?}", _generate_ss58_did("01".to_string())); -} - -#[test] -fn test_did_to_hex_public_key() { - assert_eq!( - _did_to_hex_public_key( - "did:infra:01:5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL".to_string() - ), - "de7687abb0442514b3f765e17f6cde78227e3b5afa45627f12d805fb5c5e473a".to_string() - ); -} - -#[test] -fn test_ss58_address_to_did() { - assert_eq!( - _ss58_address_to_did( - "5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL".to_string(), - "01".to_string() - ), - "did:infra:01:5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL".to_string() - ); -} +pub mod crypto; From e6fedaa89c19a8328eb909b1dc918aaad4f3920f Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Tue, 18 Apr 2023 14:22:01 +0900 Subject: [PATCH 06/27] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20did=20functi?= =?UTF-8?q?ons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/did/mod.rs | 113 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 2 files changed, 114 insertions(+) create mode 100644 src/did/mod.rs diff --git a/src/did/mod.rs b/src/did/mod.rs new file mode 100644 index 0000000..fec3fa6 --- /dev/null +++ b/src/did/mod.rs @@ -0,0 +1,113 @@ +use bip39::{Language, Mnemonic, MnemonicType, Seed}; +use schnorrkel::ExpansionMode; +use serde_json::json; +use substrate_bip39::mini_secret_from_entropy; + +use crate::crypto::sr25519::Sr25519KeyPair; + +pub fn random_phrase(words_number: u32) -> String { + let mnemonic_type = match MnemonicType::for_word_count(words_number as usize) { + Ok(t) => t, + Err(_e) => MnemonicType::Words24, + }; + let mnemonic = Mnemonic::new(mnemonic_type, Language::English); + + mnemonic.into_phrase() +} + +pub fn substrate_address(suri: String, prefix: u8) -> String { + let keypair_option = Sr25519KeyPair::from_suri(suri.as_str()); + let keypair = match keypair_option { + Some(c) => c, + _ => return "".to_string(), + }; + + let rust_string = keypair.ss58_address(prefix); + rust_string +} + +pub fn generate_ss58_did(network_id: String) -> String { + let mnemonic_type = MnemonicType::for_word_count(12).unwrap(); + let mnemonic = Mnemonic::new(mnemonic_type, Language::English); + + let keypair_option = Sr25519KeyPair::from_suri(mnemonic.clone().into_phrase().as_str()); + + let keypair = match keypair_option { + Some(c) => c, + _ => return "".to_string(), + }; + + let seed = Seed::new(&mnemonic, ""); + + let address = keypair.ss58_address(42); + let did = format!("did:infra:{}:{}", network_id, address.clone()); + + let mini_secret_key = mini_secret_from_entropy(mnemonic.entropy(), "").unwrap(); + + let secret_key: schnorrkel::SecretKey = mini_secret_key.expand(ExpansionMode::Ed25519); + let public_key: schnorrkel::PublicKey = secret_key.to_public(); + + let result = serde_json::to_string(&json!({ + "mnemonic": mnemonic.into_phrase(), + "seed": hex::encode(seed.clone()), + "private_key": hex::encode(secret_key.to_bytes()), + "public_key": hex::encode(public_key.to_bytes()), + "address": address.clone(), + "did": did + })); + + result.unwrap() +} + +pub fn did_to_hex_public_key(did: String) -> String { + let splited_did: Vec<&str> = did.split(":").collect(); + let address = splited_did[3]; + + let decoded_address = bs58::decode(address).into_vec().unwrap(); + + let public_key: schnorrkel::PublicKey = + schnorrkel::PublicKey::from_bytes(&decoded_address[1..33]).unwrap(); + + hex::encode(public_key.to_bytes()) +} + +pub fn ss58_address_to_did(address: String, network_id: String) -> String { + let did = format!("did:infra:{}:{}", network_id, address); + did +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_generate_random_phrase() { + println!("{:?}", random_phrase(12)); + } + + #[test] + fn test_generate_ss58_did() { + println!("{:?}", generate_ss58_did("01".to_string())); + } + + #[test] + fn test_did_to_hex_public_key() { + assert_eq!( + did_to_hex_public_key( + "did:infra:01:5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL".to_string() + ), + "de7687abb0442514b3f765e17f6cde78227e3b5afa45627f12d805fb5c5e473a".to_string() + ); + } + + #[test] + fn test_ss58_address_to_did() { + assert_eq!( + ss58_address_to_did( + "5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL".to_string(), + "01".to_string() + ), + "did:infra:01:5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL".to_string() + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index 274f0ed..d3eb683 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,2 @@ pub mod crypto; +pub mod did; From 4e348cc70dfe2c64dad9d61c0b3f9ba48144d12e Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Tue, 18 Apr 2023 14:22:31 +0900 Subject: [PATCH 07/27] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20credential?= =?UTF-8?q?=20struct?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib.rs | 1 + src/verifiable/credential/contexts.rs | 76 ++++++ src/verifiable/credential/credential.rs | 235 ++++++++++++++++++ .../credential/credential_subject.rs | 31 +++ src/verifiable/credential/errors.rs | 43 ++++ src/verifiable/credential/issuer.rs | 26 ++ src/verifiable/credential/mod.rs | 12 + src/verifiable/credential/object_with_id.rs | 14 ++ src/verifiable/credential/one_or_many.rs | 109 ++++++++ src/verifiable/credential/proof.rs | 143 +++++++++++ src/verifiable/credential/schema.rs | 15 ++ src/verifiable/credential/string_or_uri.rs | 53 ++++ src/verifiable/credential/uri.rs | 57 +++++ src/verifiable/credential/vc_date_time.rs | 62 +++++ src/verifiable/mod.rs | 1 + 15 files changed, 878 insertions(+) create mode 100644 src/verifiable/credential/contexts.rs create mode 100644 src/verifiable/credential/credential.rs create mode 100644 src/verifiable/credential/credential_subject.rs create mode 100644 src/verifiable/credential/errors.rs create mode 100644 src/verifiable/credential/issuer.rs create mode 100644 src/verifiable/credential/mod.rs create mode 100644 src/verifiable/credential/object_with_id.rs create mode 100644 src/verifiable/credential/one_or_many.rs create mode 100644 src/verifiable/credential/proof.rs create mode 100644 src/verifiable/credential/schema.rs create mode 100644 src/verifiable/credential/string_or_uri.rs create mode 100644 src/verifiable/credential/uri.rs create mode 100644 src/verifiable/credential/vc_date_time.rs create mode 100644 src/verifiable/mod.rs diff --git a/src/lib.rs b/src/lib.rs index d3eb683..9cdd91d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod crypto; pub mod did; +pub mod verifiable; diff --git a/src/verifiable/credential/contexts.rs b/src/verifiable/credential/contexts.rs new file mode 100644 index 0000000..73fdde3 --- /dev/null +++ b/src/verifiable/credential/contexts.rs @@ -0,0 +1,76 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap as Map; + +use super::{ + credential::{ALT_DEFAULT_CONTEXT, DEFAULT_CONTEXT}, + one_or_many::OneOrMany, + uri::URI, +}; + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[serde(untagged)] +pub enum Context { + URI(URI), + Object(Map), +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(untagged)] +#[serde(try_from = "OneOrMany")] +pub enum Contexts { + One(Context), + Many(Vec), +} + +impl TryFrom> for Contexts { + type Error = String; + fn try_from(context: OneOrMany) -> Result { + let first_uri = match context.first() { + None => return Err("Missing Context".to_string()), + Some(Context::URI(URI::String(uri))) => uri, + Some(Context::Object(_)) => return Err("Invalid Context".to_string()), + }; + if first_uri != DEFAULT_CONTEXT && first_uri != ALT_DEFAULT_CONTEXT { + return Err("Invalid Context".to_string()); + } + Ok(match context { + OneOrMany::One(context) => Contexts::One(context), + OneOrMany::Many(contexts) => Contexts::Many(contexts), + }) + } +} + +impl From for OneOrMany { + fn from(contexts: Contexts) -> OneOrMany { + match contexts { + Contexts::One(context) => OneOrMany::One(context), + Contexts::Many(contexts) => OneOrMany::Many(contexts), + } + } +} + +impl Contexts { + /// Check if the contexts contains the given URI. + pub fn contains_uri(&self, uri: &str) -> bool { + match self { + Self::One(context) => { + if let Context::URI(URI::String(context_uri)) = context { + if context_uri == uri { + return true; + } + } + } + Self::Many(contexts) => { + for context in contexts { + if let Context::URI(URI::String(context_uri)) = context { + if context_uri == uri { + return true; + } + } + } + } + } + false + } +} diff --git a/src/verifiable/credential/credential.rs b/src/verifiable/credential/credential.rs new file mode 100644 index 0000000..8e77315 --- /dev/null +++ b/src/verifiable/credential/credential.rs @@ -0,0 +1,235 @@ +use std::ptr::null; + +use serde::{Deserialize, Serialize}; + +use crate::{ + crypto::{ed25519::Ed25519KeyPair, keytype::KeyType}, + verifiable::credential::proof::ProofSuiteType, +}; + +use super::{ + contexts::Contexts, + credential_subject::CredentialSubject, + errors::Error, + issuer::Issuer, + one_or_many::OneOrMany, + proof::{Proof, VerificationRelationship}, + schema::Schema, + string_or_uri::StringOrURI, + vc_date_time::VCDateTime, +}; + +pub const DEFAULT_CONTEXT: &str = "https://www.w3.org/2018/credentials/v1"; + +// work around https://github.com/w3c/vc-test-suite/issues/103 +pub const ALT_DEFAULT_CONTEXT: &str = "https://w3.org/2018/credentials/v1"; + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Credential { + #[serde(rename = "@context")] + pub context: Contexts, + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(rename = "type")] + pub type_: OneOrMany, + pub credential_subject: OneOrMany, + #[serde(skip_serializing_if = "Option::is_none")] + pub issuer: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub issuance_date: Option, + // This field is populated only when using + // embedded proofs such as LD-PROOF + // https://w3c-ccg.github.io/ld-proofs/ + #[serde(skip_serializing_if = "Option::is_none")] + pub proof: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub expiration_date: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub credential_schema: Option>, +} + +impl Credential { + pub fn from_json(s: &str) -> Result { + let vp: Self = serde_json::from_str(s)?; + vp.validate()?; + Ok(vp) + } + + pub fn from_json_unsigned(s: &str) -> Result { + let vp: Self = serde_json::from_str(s)?; + vp.validate_unsigned()?; + Ok(vp) + } + + pub fn validate(&self) -> Result<(), Error> { + self.validate_unsigned()?; + if self.proof.is_none() { + return Err(Error::MissingProof); + } + Ok(()) + } + + pub fn validate_unsigned(&self) -> Result<(), Error> { + if !self.type_.contains(&"VerifiableCredential".to_string()) { + return Err(Error::MissingTypeVerifiableCredential); + } + if self.issuer.is_none() { + return Err(Error::InvalidIssuer); + } + if self.credential_subject.is_empty() { + // https://www.w3.org/TR/vc-data-model/#credential-subject + // VC-Data-Model "defines a credentialSubject property for the expression of claims + // about one or more subjects." + // Therefore, zero credentialSubject values is considered invalid. + return Err(Error::EmptyCredentialSubject); + } + for subject in &self.credential_subject { + if subject.is_empty() { + return Err(Error::EmptyCredentialSubject); + } + } + if self.issuance_date.is_none() { + return Err(Error::MissingIssuanceDate); + } + + Ok(()) + } + + pub fn generate_proof(&self, keypair: KeyType) -> Result { + let message = serde_json::to_string(&self).unwrap(); + let mut proof: Proof = Proof::new(ProofSuiteType::Ed25519Signature2018); + let issuer = self.issuer.as_ref().unwrap().get_id_ref().clone(); + + match keypair { + KeyType::Ed25519(keypair) => { + let signature = keypair.sign(&message.as_bytes()); + let sig_multibase = multibase::encode(multibase::Base::Base58Btc, signature); + + proof.proof_purpose = Some(VerificationRelationship::AssertionMethod); + proof.verification_method = Some(issuer.to_string().to_owned() + "#keys-1"); + proof.proof_value = Some(sig_multibase); + } + KeyType::Sr25519(keypair) => { + let signature = keypair.sign(&message.as_bytes()); + let sig_multibase = multibase::encode(multibase::Base::Base58Btc, signature); + + proof.proof_purpose = Some(VerificationRelationship::AssertionMethod); + proof.verification_method = Some(issuer.to_string().to_owned() + "#keys-1"); + proof.proof_value = Some(sig_multibase); + } + } + + Ok(proof) + } + + pub fn add_proof(&mut self, proof: Proof) { + self.proof = match self.proof.take() { + None => Some(OneOrMany::One(proof)), + Some(OneOrMany::One(existing_proof)) => { + Some(OneOrMany::Many(vec![existing_proof, proof])) + } + Some(OneOrMany::Many(mut proofs)) => { + proofs.push(proof); + Some(OneOrMany::Many(proofs)) + } + } + } + + pub fn verify(&self, keypair: KeyType) -> Result { + if self.proof.is_none() { + return Err(Error::MissingProof); + } + + let mut vc_copy = self.clone(); + let proofs = vc_copy.proof.take().unwrap(); + vc_copy.proof = None; + let message = serde_json::to_string(&vc_copy).unwrap(); + + for proof in proofs { + let sig_multibase = proof.proof_value.unwrap(); + match &keypair { + KeyType::Ed25519(keypair) => { + let (_base, sig) = multibase::decode(sig_multibase).unwrap(); + let verify = keypair.verify_signature(&message.as_bytes(), &sig); + if !verify { + return Ok(false); + } + } + KeyType::Sr25519(keypair) => { + let (_base, sig) = multibase::decode(sig_multibase).unwrap(); + let verify = keypair.verify_signature(&message.as_bytes(), &sig).unwrap(); + if !verify { + return Ok(false); + } + } + } + } + + Ok(true) + } +} + +#[cfg(test)] +mod tests { + use serde_json::json; + + use super::*; + + #[test] + fn test_sign_credential() { + let keypair_bytes = [ + 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, + 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, + ]; + let keypair = KeyType::Ed25519(Ed25519KeyPair::from_secret_key_bytes(&keypair_bytes)); + + let vc_str = r###"{ + "@context": "https://www.w3.org/2018/credentials/v1", + "id": "http://example.org/credentials/3731", + "type": ["VerifiableCredential"], + "issuer": "did:example:foo", + "issuanceDate": "2020-08-19T21:41:50Z", + "credentialSubject": { + "id": "did:example:d23dd687a7dc6787646f2eb98d0" + } + }"###; + let mut vc: Credential = Credential::from_json_unsigned(vc_str).unwrap(); + let proof = vc.generate_proof(keypair).unwrap(); + vc.add_proof(proof); + println!("{:?}", serde_json::to_string(&vc).unwrap()); + } + + #[test] + fn test_verify_credential() { + let keypair_bytes = [ + 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, + 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, + ]; + let keypair = KeyType::Ed25519(Ed25519KeyPair::from_secret_key_bytes(&keypair_bytes)); + + let vc_str = r###"{ + "@context":"https://www.w3.org/2018/credentials/v1", + "id":"http://example.org/credentials/3731", + "type":[ + "VerifiableCredential" + ], + "credentialSubject":{ + "id":"did:example:d23dd687a7dc6787646f2eb98d0" + }, + "issuer":"did:example:foo", + "issuanceDate":"2020-08-19T21:41:50Z", + "proof":{ + "type":"Ed25519Signature2018", + "created":"2023-04-18T01:08:19.517433Z", + "proofPurpose":"assertionMethod", + "proofValue":"z2xmAbSm5FaXWhG8kMUb4rZenKx1SVR2R8R6Wdf9tnowBwJk3F4uaXN1Ufiqd2C85hmeXJhp9ScPC64mHCLwqXV2p", + "verificationMethod":"did:infra:01:5GETGN5ksMY586q4EdjQap6YeSbu8tKENJ58Wx3vBkgHs8B2#keys-1" + } + }"###; + + let mut vc: Credential = Credential::from_json(vc_str).unwrap(); + + assert_eq!(vc.verify(keypair).unwrap(), true); + } +} diff --git a/src/verifiable/credential/credential_subject.rs b/src/verifiable/credential/credential_subject.rs new file mode 100644 index 0000000..db17061 --- /dev/null +++ b/src/verifiable/credential/credential_subject.rs @@ -0,0 +1,31 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use super::uri::URI; +use std::collections::HashMap as Map; + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct CredentialSubject { + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(flatten)] + pub property_set: Option>, +} + +impl CredentialSubject { + /// Check if the credential subject is empty + /// + /// An empty credential subject (containing no properties, not even an id property) is + /// considered invalid, as the VC Data Model defines the value of the + /// [credentialSubject](https://www.w3.org/TR/vc-data-model/#credential-subject) property as + /// "a set of objects that contain one or more properties [...]" + pub fn is_empty(&self) -> bool { + self.id.is_none() + && match self.property_set { + Some(ref ps) => ps.is_empty(), + None => true, + } + } +} diff --git a/src/verifiable/credential/errors.rs b/src/verifiable/credential/errors.rs new file mode 100644 index 0000000..b1549cd --- /dev/null +++ b/src/verifiable/credential/errors.rs @@ -0,0 +1,43 @@ +use thiserror::Error; + +/// Error type for `ssi`. +#[derive(Error, Debug)] +#[non_exhaustive] +pub enum Error { + #[error("Missing proof")] + MissingProof, + #[error("Missing credential schema")] + MissingCredentialSchema, + #[error("Missing credential")] + MissingCredential, + #[error("Missing presentation")] + MissingPresentation, + #[error("Invalid issuer")] + InvalidIssuer, + #[error("Missing holder property")] + MissingHolder, + #[error("Unsupported Holder Binding")] + UnsupportedHolderBinding, + #[error("Missing issuance date")] + MissingIssuanceDate, + #[error("Missing type VerifiableCredential")] + MissingTypeVerifiableCredential, + #[error("Missing type VerifiablePresentation")] + MissingTypeVerifiablePresentation, + #[error("Invalid subject")] + InvalidSubject, + #[error("Unable to convert date/time")] + TimeError, + #[error("Unsupported verification relationship")] + UnsupportedVerificationRelationship, + #[error("Empty credential subject")] + EmptyCredentialSubject, + #[error(transparent)] + Json(#[from] serde_json::Error), +} + +impl From for String { + fn from(err: Error) -> String { + err.to_string() + } +} diff --git a/src/verifiable/credential/issuer.rs b/src/verifiable/credential/issuer.rs new file mode 100644 index 0000000..eed6673 --- /dev/null +++ b/src/verifiable/credential/issuer.rs @@ -0,0 +1,26 @@ +use serde::{Deserialize, Serialize}; + +use super::{object_with_id::ObjectWithId, uri::URI}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(untagged)] +pub enum Issuer { + URI(URI), + Object(ObjectWithId), +} + +impl Issuer { + /// Return this issuer's id URI + pub fn get_id(&self) -> String { + match self { + Self::URI(uri) => uri.to_string(), + Self::Object(object_with_id) => object_with_id.id.to_string(), + } + } + pub fn get_id_ref(&self) -> &str { + match self { + Self::URI(uri) => uri.as_str(), + Self::Object(object_with_id) => object_with_id.id.as_str(), + } + } +} diff --git a/src/verifiable/credential/mod.rs b/src/verifiable/credential/mod.rs new file mode 100644 index 0000000..7c322f0 --- /dev/null +++ b/src/verifiable/credential/mod.rs @@ -0,0 +1,12 @@ +pub mod contexts; +pub mod credential; +pub mod credential_subject; +pub mod errors; +pub mod issuer; +pub mod object_with_id; +pub mod one_or_many; +pub mod proof; +pub mod schema; +pub mod string_or_uri; +pub mod uri; +pub mod vc_date_time; diff --git a/src/verifiable/credential/object_with_id.rs b/src/verifiable/credential/object_with_id.rs new file mode 100644 index 0000000..f53abaa --- /dev/null +++ b/src/verifiable/credential/object_with_id.rs @@ -0,0 +1,14 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap as Map; + +use super::uri::URI; + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ObjectWithId { + pub id: URI, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(flatten)] + pub property_set: Option>, +} diff --git a/src/verifiable/credential/one_or_many.rs b/src/verifiable/credential/one_or_many.rs new file mode 100644 index 0000000..9912b07 --- /dev/null +++ b/src/verifiable/credential/one_or_many.rs @@ -0,0 +1,109 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[serde(untagged)] +pub enum OneOrMany { + One(T), + Many(Vec), +} + +impl OneOrMany { + pub fn any(&self, f: F) -> bool + where + F: Fn(&T) -> bool, + { + match self { + Self::One(value) => f(value), + Self::Many(values) => values.iter().any(f), + } + } + + pub fn len(&self) -> usize { + match self { + Self::One(_) => 1, + Self::Many(values) => values.len(), + } + } + + pub fn is_empty(&self) -> bool { + match self { + Self::One(_) => false, + Self::Many(values) => values.is_empty(), + } + } + + pub fn contains(&self, x: &T) -> bool + where + T: PartialEq, + { + match self { + Self::One(value) => x == value, + Self::Many(values) => values.contains(x), + } + } + + pub fn first(&self) -> Option<&T> { + match self { + Self::One(value) => Some(value), + Self::Many(values) => { + if !values.is_empty() { + Some(&values[0]) + } else { + None + } + } + } + } + + pub fn to_single(&self) -> Option<&T> { + match self { + Self::One(value) => Some(value), + Self::Many(values) => { + if values.len() == 1 { + Some(&values[0]) + } else { + None + } + } + } + } + + pub fn to_single_mut(&mut self) -> Option<&mut T> { + match self { + Self::One(value) => Some(value), + Self::Many(values) => { + if values.len() == 1 { + Some(&mut values[0]) + } else { + None + } + } + } + } +} + +// consuming iterator +impl IntoIterator for OneOrMany { + type Item = T; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + match self { + Self::One(value) => vec![value].into_iter(), + Self::Many(values) => values.into_iter(), + } + } +} + +// non-consuming iterator +impl<'a, T> IntoIterator for &'a OneOrMany { + type Item = &'a T; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + match self { + OneOrMany::One(value) => vec![value].into_iter(), + OneOrMany::Many(values) => values.iter().collect::>().into_iter(), + } + } +} diff --git a/src/verifiable/credential/proof.rs b/src/verifiable/credential/proof.rs new file mode 100644 index 0000000..5b48153 --- /dev/null +++ b/src/verifiable/credential/proof.rs @@ -0,0 +1,143 @@ +use iref::Iri; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use serde_json::Value; +use static_iref::iri; +use std::str::FromStr; + +use super::errors::Error; +use super::vc_date_time::VCDateTime; + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +// TODO use enum to separate betwen JWS and LD proofs? +// TODO create generics type to allow users to provide their own proof suite that implements ProofSuite +pub struct Proof { + #[serde(rename = "@context")] + // TODO: use consistent types for context + #[serde(default, skip_serializing_if = "Value::is_null")] + pub context: Value, + #[serde(rename = "type")] + pub type_: ProofSuiteType, + #[serde(skip_serializing_if = "Option::is_none")] + pub created: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub proof_purpose: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub proof_value: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub verification_method: Option, +} + +impl Proof { + pub fn new(type_: ProofSuiteType) -> Self { + let expected_utc_now = chrono::Utc::now(); + let vc_date_time_now = VCDateTime::from(expected_utc_now); + + Self { + type_, + context: Value::default(), + created: Some(vc_date_time_now), + proof_purpose: None, + proof_value: None, + verification_method: None, + } + } +} + +/// A [verification relationship](https://w3c.github.io/did-core/#dfn-verification-relationship). +/// +/// The relationship between a [verification method][VerificationMethod] and a DID +/// Subject (as described by a [DID Document][Document]) is considered analogous to a [proof +/// purpose](crate::vc::ProofPurpose). +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[serde(try_from = "String")] +#[serde(rename_all = "camelCase")] +pub enum VerificationRelationship { + AssertionMethod, + Authentication, + KeyAgreement, + ContractAgreement, + CapabilityInvocation, + CapabilityDelegation, +} + +impl Default for VerificationRelationship { + fn default() -> Self { + Self::AssertionMethod + } +} + +impl FromStr for VerificationRelationship { + type Err = Error; + fn from_str(purpose: &str) -> Result { + match purpose { + "authentication" => Ok(Self::Authentication), + "assertionMethod" => Ok(Self::AssertionMethod), + "keyAgreement" => Ok(Self::KeyAgreement), + "contractAgreement" => Ok(Self::ContractAgreement), + "capabilityInvocation" => Ok(Self::CapabilityInvocation), + "capabilityDelegation" => Ok(Self::CapabilityDelegation), + _ => Err(Error::UnsupportedVerificationRelationship), + } + } +} + +impl TryFrom for VerificationRelationship { + type Error = Error; + fn try_from(purpose: String) -> Result { + Self::from_str(&purpose) + } +} + +impl From for String { + fn from(purpose: VerificationRelationship) -> String { + match purpose { + VerificationRelationship::Authentication => "authentication".to_string(), + VerificationRelationship::AssertionMethod => "assertionMethod".to_string(), + VerificationRelationship::KeyAgreement => "keyAgreement".to_string(), + VerificationRelationship::ContractAgreement => "contractAgreement".to_string(), + VerificationRelationship::CapabilityInvocation => "capabilityInvocation".to_string(), + VerificationRelationship::CapabilityDelegation => "capabilityDelegation".to_string(), + } + } +} + +impl VerificationRelationship { + pub fn to_iri(&self) -> Iri<'static> { + match self { + VerificationRelationship::Authentication => { + iri!("https://w3id.org/security#authenticationMethod") + } + VerificationRelationship::AssertionMethod => { + iri!("https://w3id.org/security#assertionMethod") + } + VerificationRelationship::KeyAgreement => { + iri!("https://w3id.org/security#keyAgreementMethod") + } + VerificationRelationship::ContractAgreement => { + iri!("https://w3id.org/security#contractAgreementMethod") + } + VerificationRelationship::CapabilityInvocation => { + iri!("https://w3id.org/security#capabilityInvocationMethod") + } + VerificationRelationship::CapabilityDelegation => { + iri!("https://w3id.org/security#capabilityDelegationMethod") + } + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub enum ProofSuiteType { + Ed25519Signature2018, + Sr25519VerificationKey2020, +} + +impl FromStr for ProofSuiteType { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + serde_json::from_value(json!(format!("{s}"))) + } +} diff --git a/src/verifiable/credential/schema.rs b/src/verifiable/credential/schema.rs new file mode 100644 index 0000000..62563ae --- /dev/null +++ b/src/verifiable/credential/schema.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap as Map; + +use super::uri::URI; + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Schema { + pub id: URI, + #[serde(rename = "type")] + pub type_: String, + #[serde(flatten)] + pub property_set: Option>, +} diff --git a/src/verifiable/credential/string_or_uri.rs b/src/verifiable/credential/string_or_uri.rs new file mode 100644 index 0000000..c2d5d6c --- /dev/null +++ b/src/verifiable/credential/string_or_uri.rs @@ -0,0 +1,53 @@ +use serde::{Deserialize, Serialize}; + +use super::uri::{URIParseErr, URI}; + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[serde(untagged)] +#[serde(try_from = "String")] +pub enum StringOrURI { + String(String), + URI(URI), +} + +impl TryFrom for StringOrURI { + type Error = URIParseErr; + fn try_from(string: String) -> Result { + if string.contains(':') { + let uri = URI::try_from(string)?; + Ok(Self::URI(uri)) + } else { + Ok(Self::String(string)) + } + } +} +impl TryFrom<&str> for StringOrURI { + type Error = URIParseErr; + fn try_from(string: &str) -> Result { + string.to_string().try_into() + } +} + +impl From for StringOrURI { + fn from(uri: URI) -> Self { + StringOrURI::URI(uri) + } +} + +impl From for String { + fn from(id: StringOrURI) -> Self { + match id { + StringOrURI::URI(uri) => uri.into(), + StringOrURI::String(s) => s, + } + } +} + +impl StringOrURI { + fn as_str(&self) -> &str { + match self { + StringOrURI::URI(URI::String(string)) => string.as_str(), + StringOrURI::String(string) => string.as_str(), + } + } +} diff --git a/src/verifiable/credential/uri.rs b/src/verifiable/credential/uri.rs new file mode 100644 index 0000000..a48a24d --- /dev/null +++ b/src/verifiable/credential/uri.rs @@ -0,0 +1,57 @@ +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[serde(try_from = "String")] +#[serde(untagged)] +pub enum URI { + String(String), +} + +#[derive(thiserror::Error, Debug)] +pub enum URIParseErr { + #[error("Invalid URI: {0}")] + InvalidFormat(String), +} + +impl From for String { + fn from(uri: URI) -> String { + let URI::String(string) = uri; + string + } +} + +impl std::convert::TryFrom for URI { + type Error = URIParseErr; + fn try_from(uri: String) -> Result { + if uri.contains(':') { + Ok(URI::String(uri)) + } else { + Err(URIParseErr::InvalidFormat(uri)) + } + } +} + +impl URI { + /// Return the URI as a string slice + pub fn as_str(&self) -> &str { + match self { + URI::String(string) => string.as_str(), + } + } +} + +impl FromStr for URI { + type Err = URIParseErr; + fn from_str(uri: &str) -> Result { + URI::try_from(String::from(uri)) + } +} + +impl std::fmt::Display for URI { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::String(ref string) => write!(f, "{}", string), + } + } +} diff --git a/src/verifiable/credential/vc_date_time.rs b/src/verifiable/credential/vc_date_time.rs new file mode 100644 index 0000000..e8a4937 --- /dev/null +++ b/src/verifiable/credential/vc_date_time.rs @@ -0,0 +1,62 @@ +use std::str::FromStr; + +use chrono::{DateTime, FixedOffset}; +use serde::{Deserialize, Serialize}; + +/// RFC3339 date-time as used in VC Data Model +/// +/// +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +#[serde(try_from = "String")] +#[serde(into = "String")] +pub struct VCDateTime { + /// The date-time + date_time: DateTime, + /// Whether to use "Z" or "+00:00" when formatting the date-time in UTC + use_z: bool, +} + +impl FromStr for VCDateTime { + type Err = chrono::format::ParseError; + fn from_str(date_time: &str) -> Result { + let use_z = date_time.ends_with('Z'); + let date_time = DateTime::parse_from_rfc3339(date_time)?; + Ok(VCDateTime { date_time, use_z }) + } +} + +impl TryFrom for VCDateTime { + type Error = chrono::format::ParseError; + fn try_from(date_time: String) -> Result { + Self::from_str(&date_time) + } +} + +impl From for String { + fn from(z_date_time: VCDateTime) -> String { + let VCDateTime { date_time, use_z } = z_date_time; + date_time.to_rfc3339_opts(chrono::SecondsFormat::AutoSi, use_z) + } +} + +impl From> for VCDateTime +where + chrono::DateTime: From>, +{ + fn from(date_time: DateTime) -> Self { + Self { + date_time: date_time.into(), + use_z: true, + } + } +} + +impl From for DateTime +where + chrono::DateTime: From>, +{ + fn from(vc_date_time: VCDateTime) -> Self { + Self::from(vc_date_time.date_time) + } +} diff --git a/src/verifiable/mod.rs b/src/verifiable/mod.rs new file mode 100644 index 0000000..510c0f2 --- /dev/null +++ b/src/verifiable/mod.rs @@ -0,0 +1 @@ +pub mod credential; From e40403446b44cfbd9663682c639080cdb470d34d Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Wed, 19 Apr 2023 15:32:42 +0900 Subject: [PATCH 08/27] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20wrong=20data?= =?UTF-8?q?=20in=20generate=20did?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/did/mod.rs | 61 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/src/did/mod.rs b/src/did/mod.rs index fec3fa6..762e3d5 100644 --- a/src/did/mod.rs +++ b/src/did/mod.rs @@ -1,5 +1,6 @@ use bip39::{Language, Mnemonic, MnemonicType, Seed}; -use schnorrkel::ExpansionMode; +use ed25519_dalek::{PublicKey, SecretKey}; +use schnorrkel::{ExpansionMode, SECRET_KEY_LENGTH}; use serde_json::json; use substrate_bip39::mini_secret_from_entropy; @@ -37,19 +38,43 @@ pub fn generate_ss58_did(network_id: String) -> String { _ => return "".to_string(), }; - let seed = Seed::new(&mnemonic, ""); - let address = keypair.ss58_address(42); let did = format!("did:infra:{}:{}", network_id, address.clone()); let mini_secret_key = mini_secret_from_entropy(mnemonic.entropy(), "").unwrap(); - let secret_key: schnorrkel::SecretKey = mini_secret_key.expand(ExpansionMode::Ed25519); - let public_key: schnorrkel::PublicKey = secret_key.to_public(); + let secret_key = mini_secret_key; + let public_key = secret_key.expand_to_public(ExpansionMode::Ed25519); let result = serde_json::to_string(&json!({ "mnemonic": mnemonic.into_phrase(), - "seed": hex::encode(seed.clone()), + "private_key": hex::encode(secret_key.to_bytes()), + "public_key": hex::encode(public_key.to_bytes()), + "address": address.clone(), + "did": did + })); + + result.unwrap() +} + +pub fn generate_ss58_did_from_phrase(suri: String, network_id: String) -> String { + let keypair_option = Sr25519KeyPair::from_suri(suri.as_str()); + + let keypair = match keypair_option { + Some(c) => c, + _ => return "".to_string(), + }; + + let address = keypair.ss58_address(42); + let did = format!("did:infra:{}:{}", network_id, address.clone()); + + let mnemonic = Mnemonic::from_phrase(&suri, Language::English).unwrap(); + let mini_secret_key = mini_secret_from_entropy(mnemonic.entropy(), "").unwrap(); + + let secret_key = mini_secret_key; + let public_key = secret_key.expand_to_public(ExpansionMode::Ed25519); + + let result = serde_json::to_string(&json!({ "private_key": hex::encode(secret_key.to_bytes()), "public_key": hex::encode(public_key.to_bytes()), "address": address.clone(), @@ -80,6 +105,14 @@ pub fn ss58_address_to_did(address: String, network_id: String) -> String { mod tests { use super::*; + #[test] + fn testa() { + println!( + "{:?}", + hex::decode("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60") + ); + } + #[test] fn test_generate_random_phrase() { println!("{:?}", random_phrase(12)); @@ -90,13 +123,25 @@ mod tests { println!("{:?}", generate_ss58_did("01".to_string())); } + #[test] + fn test_generate_ss58_did_from_phrase() { + println!( + "{:?}", + generate_ss58_did_from_phrase( + "caution juice atom organ advance problem want pledge someone senior holiday very" + .to_string(), + "01".to_string() + ) + ); + } + #[test] fn test_did_to_hex_public_key() { assert_eq!( did_to_hex_public_key( - "did:infra:01:5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL".to_string() + "did:infra:01:5Gv8YYFu8H1btvmrJy9FjjAWfb99wrhV3uhPFoNEr918utyR".to_string() ), - "de7687abb0442514b3f765e17f6cde78227e3b5afa45627f12d805fb5c5e473a".to_string() + "d6a3105d6768e956e9e5d41050ac29843f98561410d3a47f9dd5b3b227ab8746".to_string() ); } From d7ed8b5f9bda10e66adf3cfcb4402861fb8cae9a Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Wed, 19 Apr 2023 15:33:27 +0900 Subject: [PATCH 09/27] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20presentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/verifiable/mod.rs | 1 + .../presentation/credential_or_jwt.rs | 11 + src/verifiable/presentation/holder.rs | 26 ++ src/verifiable/presentation/mod.rs | 3 + src/verifiable/presentation/presentation.rs | 255 ++++++++++++++++++ 5 files changed, 296 insertions(+) create mode 100644 src/verifiable/presentation/credential_or_jwt.rs create mode 100644 src/verifiable/presentation/holder.rs create mode 100644 src/verifiable/presentation/mod.rs create mode 100644 src/verifiable/presentation/presentation.rs diff --git a/src/verifiable/mod.rs b/src/verifiable/mod.rs index 510c0f2..1420934 100644 --- a/src/verifiable/mod.rs +++ b/src/verifiable/mod.rs @@ -1 +1,2 @@ pub mod credential; +pub mod presentation; diff --git a/src/verifiable/presentation/credential_or_jwt.rs b/src/verifiable/presentation/credential_or_jwt.rs new file mode 100644 index 0000000..b0dc1d6 --- /dev/null +++ b/src/verifiable/presentation/credential_or_jwt.rs @@ -0,0 +1,11 @@ +use serde::{Deserialize, Serialize}; + +use crate::verifiable::credential::credential::Credential; + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(untagged)] +#[allow(clippy::large_enum_variant)] +pub enum CredentialOrJWT { + Credential(Credential), + // JWT(String), +} diff --git a/src/verifiable/presentation/holder.rs b/src/verifiable/presentation/holder.rs new file mode 100644 index 0000000..8284407 --- /dev/null +++ b/src/verifiable/presentation/holder.rs @@ -0,0 +1,26 @@ +use serde::{Deserialize, Serialize}; + +use crate::verifiable::credential::{object_with_id::ObjectWithId, uri::URI}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(untagged)] +pub enum Holder { + URI(URI), + Object(ObjectWithId), +} + +impl Holder { + /// Return this holder's id URI + pub fn get_id(&self) -> String { + match self { + Self::URI(uri) => uri.to_string(), + Self::Object(object_with_id) => object_with_id.id.to_string(), + } + } + pub fn get_id_ref(&self) -> &str { + match self { + Self::URI(uri) => uri.as_str(), + Self::Object(object_with_id) => object_with_id.id.as_str(), + } + } +} diff --git a/src/verifiable/presentation/mod.rs b/src/verifiable/presentation/mod.rs new file mode 100644 index 0000000..2620e5e --- /dev/null +++ b/src/verifiable/presentation/mod.rs @@ -0,0 +1,3 @@ +pub mod credential_or_jwt; +pub mod holder; +pub mod presentation; diff --git a/src/verifiable/presentation/presentation.rs b/src/verifiable/presentation/presentation.rs new file mode 100644 index 0000000..0e7e8e5 --- /dev/null +++ b/src/verifiable/presentation/presentation.rs @@ -0,0 +1,255 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + crypto::keytype::KeyType, + verifiable::credential::{ + contexts::Contexts, + errors::Error, + one_or_many::OneOrMany, + proof::{Proof, ProofSuiteType, VerificationRelationship}, + string_or_uri::StringOrURI, + }, +}; + +use super::{credential_or_jwt::CredentialOrJWT, holder::Holder}; + +pub const DEFAULT_CONTEXT: &str = "https://www.w3.org/2018/credentials/v1"; + +// work around https://github.com/w3c/vc-test-suite/issues/103 +pub const ALT_DEFAULT_CONTEXT: &str = "https://w3.org/2018/credentials/v1"; + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Presentation { + #[serde(rename = "@context")] + pub context: Contexts, + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(rename = "type")] + pub type_: OneOrMany, + #[serde(skip_serializing_if = "Option::is_none")] + pub verifiable_credential: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub proof: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub holder: Option, +} + +impl Presentation { + pub fn from_json(s: &str) -> Result { + let vp: Self = serde_json::from_str(s)?; + vp.validate()?; + Ok(vp) + } + + pub fn from_json_unsigned(s: &str) -> Result { + let vp: Self = serde_json::from_str(s)?; + vp.validate_unsigned()?; + Ok(vp) + } + + pub fn validate_unsigned(&self) -> Result<(), Error> { + if !self.type_.contains(&"VerifiablePresentation".to_string()) { + return Err(Error::MissingTypeVerifiablePresentation); + } + + for ref vc in self.verifiable_credential.iter().flatten() { + match vc { + CredentialOrJWT::Credential(vc) => { + vc.validate_unsigned_embedded()?; + } + }; + } + Ok(()) + } + + pub fn validate(&self) -> Result<(), Error> { + self.validate_unsigned()?; + + if self.proof.is_none() { + return Err(Error::MissingProof); + } + + Ok(()) + } + + pub fn generate_proof(&self, keypair: KeyType) -> Result { + let message = serde_json::to_string(&self).unwrap(); + let mut proof: Proof = Proof::new(ProofSuiteType::Ed25519Signature2018); + let holder = self.holder.as_ref().unwrap().get_id_ref().clone(); + + match keypair { + KeyType::Ed25519(keypair) => { + let signature = keypair.sign(&message.as_bytes()); + let sig_multibase = multibase::encode(multibase::Base::Base58Btc, signature); + + proof.proof_purpose = Some(VerificationRelationship::AssertionMethod); + proof.verification_method = Some(holder.to_string().to_owned() + "#keys-1"); + proof.proof_value = Some(sig_multibase); + } + KeyType::Sr25519(keypair) => { + let signature = keypair.sign(&message.as_bytes()); + let sig_multibase = multibase::encode(multibase::Base::Base58Btc, signature); + + proof.proof_purpose = Some(VerificationRelationship::AssertionMethod); + proof.verification_method = Some(holder.to_string().to_owned() + "#keys-1"); + proof.proof_value = Some(sig_multibase); + } + } + + Ok(proof) + } + + pub fn add_proof(&mut self, proof: Proof) { + self.proof = match self.proof.take() { + None => Some(OneOrMany::One(proof)), + Some(OneOrMany::One(existing_proof)) => { + Some(OneOrMany::Many(vec![existing_proof, proof])) + } + Some(OneOrMany::Many(mut proofs)) => { + proofs.push(proof); + Some(OneOrMany::Many(proofs)) + } + } + } + + pub fn verify(&self, keypair: &KeyType) -> Result { + if self.proof.is_none() { + return Err(Error::MissingProof); + } + + let mut vp_copy = self.clone(); + let proofs = vp_copy.proof.take().unwrap(); + vp_copy.proof = None; + let message = serde_json::to_string(&vp_copy).unwrap(); + + let vcs = vp_copy.verifiable_credential.take().unwrap(); + + for vc in vcs { + match vc { + CredentialOrJWT::Credential(vc) => { + let verify = vc.verify(&keypair)?; + if !verify { + return Ok(false); + } + } + }; + } + + for proof in proofs { + let sig_multibase = proof.proof_value.unwrap(); + match &keypair { + KeyType::Ed25519(keypair) => { + let (_base, sig) = multibase::decode(sig_multibase).unwrap(); + let verify = keypair.verify_signature(&message.as_bytes(), &sig); + if !verify { + return Ok(false); + } + } + KeyType::Sr25519(keypair) => { + let (_base, sig) = multibase::decode(sig_multibase).unwrap(); + let verify = keypair.verify_signature(&message.as_bytes(), &sig).unwrap(); + if !verify { + return Ok(false); + } + } + } + } + + Ok(true) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + crypto::ed25519::Ed25519KeyPair, + verifiable::credential::{contexts::Context, credential::Credential, uri::URI}, + }; + + use super::*; + + #[test] + fn test_sign_credential() { + let keypair_bytes = [ + 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, + 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, + ]; + let keypair = KeyType::Ed25519(Ed25519KeyPair::from_secret_key_bytes(&keypair_bytes)); + + let vc_str = r###"{ + "@context": "https://www.w3.org/2018/credentials/v1", + "id": "http://example.org/credentials/3731", + "type": ["VerifiableCredential"], + "issuer": "did:example:foo", + "issuanceDate": "2020-08-19T21:41:50Z", + "credentialSubject": { + "id": "did:example:d23dd687a7dc6787646f2eb98d0" + } + }"###; + let mut vc: Credential = Credential::from_json_unsigned(vc_str).unwrap(); + let proof = vc.generate_proof(&keypair).unwrap(); + vc.add_proof(proof); + + let mut vp = Presentation { + context: Contexts::Many(vec![Context::URI(URI::String(DEFAULT_CONTEXT.to_string()))]), + id: Some("http://example.org/presentations/3731".try_into().unwrap()), + type_: OneOrMany::One("VerifiablePresentation".to_string()), + verifiable_credential: Some(OneOrMany::One(CredentialOrJWT::Credential(vc))), + proof: None, + holder: Some(Holder::URI(URI::String("did:example:foo".to_string()))), + }; + + let vp_proof = vp.generate_proof(keypair).unwrap(); + vp.add_proof(vp_proof); + println!("{:?}", serde_json::to_string(&vp).unwrap()); + } + + #[test] + fn test_verify_credential() { + let keypair_bytes = [ + 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, + 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, + ]; + let keypair = KeyType::Ed25519(Ed25519KeyPair::from_secret_key_bytes(&keypair_bytes)); + + let vp_str = r###"{ + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "id": "http://example.org/presentations/3731", + "type": "VerifiablePresentation", + "verifiableCredential": { + "@context": "https://www.w3.org/2018/credentials/v1", + "id": "http://example.org/credentials/3731", + "type": [ + "VerifiableCredential" + ], + "credentialSubject": { + "id": "did:example:d23dd687a7dc6787646f2eb98d0" + }, + "issuer": "did:example:foo", + "issuanceDate": "2020-08-19T21:41:50Z", + "proof": { + "type": "Ed25519Signature2018", + "created": "2023-04-18T06:26:31.355301Z", + "proofPurpose": "assertionMethod", + "proofValue": "z2xmAbSm5FaXWhG8kMUb4rZenKx1SVR2R8R6Wdf9tnowBwJk3F4uaXN1Ufiqd2C85hmeXJhp9ScPC64mHCLwqXV2p", + "verificationMethod": "did:example:foo#keys-1" + } + }, + "proof": { + "type": "Ed25519Signature2018", + "created": "2023-04-18T06:26:31.355864Z", + "proofPurpose": "assertionMethod", + "proofValue": "z3ccPeXQKuP3QsG1eFCmTV2V8uDmkZNwMqc5ipYHemH4txCfPqL5TbJpWqqbYgvNhXAppJBEAzz2BcNRzNumPgVjQ", + "verificationMethod": "did:example:foo#keys-1" + }, + "holder": "did:example:foo" + }"###; + + let mut vp: Presentation = Presentation::from_json(vp_str).unwrap(); + + assert_eq!(vp.verify(&keypair).unwrap(), true); + } +} From efeed645f0a408d55b05acd34c68a956c2157216 Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Thu, 20 Apr 2023 09:15:14 +0900 Subject: [PATCH 10/27] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20crypto=20rel?= =?UTF-8?q?ated=20parse=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- a.json | 19 ++++ src/crypto/ed25519.rs | 96 +++++++++++++++--- src/crypto/sr25519.rs | 125 +++++++++++++++++++++++- src/verifiable/credential/credential.rs | 100 +++++++++++++++---- 4 files changed, 302 insertions(+), 38 deletions(-) create mode 100644 a.json diff --git a/a.json b/a.json new file mode 100644 index 0000000..bfe1807 --- /dev/null +++ b/a.json @@ -0,0 +1,19 @@ +{ + "@context": "https://www.w3.org/2018/credentials/v1", + "id": "http://example.org/credentials/3731", + "type": [ + "VerifiableCredential" + ], + "credentialSubject": { + "id": "did:example:d23dd687a7dc6787646f2eb98d0" + }, + "issuer": "did:example:foo", + "issuanceDate": "2020-08-19T21:41:50Z", + "proof": { + "type": "Sr25519VerificationKey2020", + "created": "2023-04-19T23:53:37.394517Z", + "proofPurpose": "assertionMethod", + "proofValue": "zHk9DkopSPjHeJiDsVWLNHUMdgbMAvjt7MU9pjKTzi7tcw3eam7guUdiGjRQDjPDreAWaCuJSdhsYWuu2Ki2YZa8", + "verificationMethod": "did:example:foo#keys-1" + } +} \ No newline at end of file diff --git a/src/crypto/ed25519.rs b/src/crypto/ed25519.rs index dd58fad..6bc6955 100644 --- a/src/crypto/ed25519.rs +++ b/src/crypto/ed25519.rs @@ -1,8 +1,10 @@ use base58::ToBase58; +use bip39::{Language, Mnemonic}; use ed25519_dalek::{ Keypair, PublicKey, SecretKey, Signature, Signer, Verifier, KEYPAIR_LENGTH, SECRET_KEY_LENGTH, }; use rand::rngs::OsRng; +use substrate_bip39::mini_secret_from_entropy; pub struct Ed25519KeyPair(ed25519_dalek::Keypair); @@ -13,6 +15,26 @@ impl Ed25519KeyPair { Ed25519KeyPair(keypair) } + pub fn from_bip39_phrase(phrase: &str, password: Option<&str>) -> Ed25519KeyPair { + let mnemonic = Mnemonic::from_phrase(phrase, Language::English).unwrap(); + let mini_secret_key = + mini_secret_from_entropy(mnemonic.entropy(), password.unwrap_or("")).unwrap(); + + let secret_key: SecretKey = SecretKey::from_bytes(mini_secret_key.as_bytes()).unwrap(); + let public_key: PublicKey = PublicKey::from(&secret_key).into(); + + let secret = secret_key.to_bytes(); + let public = public_key.to_bytes(); + + let mut keypair_bytes: [u8; KEYPAIR_LENGTH] = [0u8; KEYPAIR_LENGTH]; + + keypair_bytes[..SECRET_KEY_LENGTH].copy_from_slice(&secret); + keypair_bytes[SECRET_KEY_LENGTH..].copy_from_slice(&public); + + let keypair = ed25519_dalek::Keypair::from_bytes(&keypair_bytes).ok(); + Ed25519KeyPair(keypair.unwrap()) + } + pub fn from_secret_key_bytes(bytes: &[u8]) -> Ed25519KeyPair { let secret_key: SecretKey = SecretKey::from_bytes(bytes).unwrap(); let public_key: PublicKey = (&secret_key).into(); @@ -29,6 +51,27 @@ impl Ed25519KeyPair { Ed25519KeyPair(keypair.unwrap()) } + pub fn from_public_key_bytes(bytes: &[u8]) -> Ed25519KeyPair { + let public_key: PublicKey = PublicKey::from_bytes(bytes).unwrap(); + + let public = public_key.to_bytes(); + + let mut keypair_bytes: [u8; KEYPAIR_LENGTH] = [0u8; KEYPAIR_LENGTH]; + + keypair_bytes[SECRET_KEY_LENGTH..].copy_from_slice(&public); + + let keypair = ed25519_dalek::Keypair::from_bytes(&keypair_bytes).ok(); + Ed25519KeyPair(keypair.unwrap()) + } + + pub fn to_public_key_bytes(&self) -> [u8; 32] { + self.0.public.to_bytes() + } + + pub fn to_secret_key_bytes(&self) -> [u8; 32] { + self.0.secret.to_bytes() + } + pub fn ss58_address(&self, prefix: u8) -> String { let mut v = vec![prefix]; v.extend_from_slice(&self.0.public.to_bytes()); @@ -79,11 +122,36 @@ mod tests { } } + #[test] + fn test_from_bip39_phrase() { + let keypair = Ed25519KeyPair::from_bip39_phrase( + "caution juice atom organ advance problem want pledge someone senior holiday very", + Some(""), + ); + match keypair { + Ed25519KeyPair(keypair) => { + let keypair_bytes = keypair.to_bytes(); + let secret_key_bytes = &keypair_bytes[..SECRET_KEY_LENGTH]; + let publuc_key_bytes = &keypair_bytes[SECRET_KEY_LENGTH..]; + assert_eq!( + hex::encode(secret_key_bytes), + "c8fa03532fb22ee1f7f6908b9c02b4e72483f0dbd66e4cd456b8f34c6230b849" + ); + assert_eq!( + hex::encode(publuc_key_bytes), + "bd7436a22571207d018ffe83f5dc77d0750b7777f1eb169053d40201d6c68d53" + ); + } + _ => assert!(false), + } + } + #[test] fn test_from_secret_key_bytes() { + // https://datatracker.ietf.org/doc/html/rfc8032#section-7.1 let bytes = [ - 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, - 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, + 157, 97, 177, 157, 239, 253, 90, 96, 186, 132, 74, 244, 146, 236, 44, 196, 68, 73, 197, + 105, 123, 50, 105, 25, 112, 59, 172, 3, 28, 174, 127, 96, ]; let keypair = Ed25519KeyPair::from_secret_key_bytes(&bytes); match keypair { @@ -93,11 +161,11 @@ mod tests { let public_key_bytes = &bytes[SECRET_KEY_LENGTH..]; assert_eq!( hex::encode(secret_key_bytes), - "cb534bf8dd15a901ee442cae510b246f5e94247d73570bea47e0aa859959c412" + "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60" ); assert_eq!( hex::encode(public_key_bytes), - "b86044c551e40dc1de84aa89c2dcf27657a43e0510f14e9388c1100a76f94e5c" + "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" ); } _ => assert!(false), @@ -107,25 +175,27 @@ mod tests { #[test] fn test_sign() { let bytes = [ - 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, - 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, + 157, 97, 177, 157, 239, 253, 90, 96, 186, 132, 74, 244, 146, 236, 44, 196, 68, 73, 197, + 105, 123, 50, 105, 25, 112, 59, 172, 3, 28, 174, 127, 96, ]; let keypair = Ed25519KeyPair::from_secret_key_bytes(&bytes); - let message = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + println!("{:?}", keypair.to_secret_key_bytes()); + println!("{:?}", keypair.to_public_key_bytes()); + let message = []; let signature = keypair.sign(&message); let sig_multibase = multibase::encode(multibase::Base::Base58Btc, signature); - assert_eq!(sig_multibase,"zmqquC4Hb5EK7L7JPQjzABJ8rK8dvpVDgWfN8d6JQ5F96sw91g2mz4m3iPSJ4tQ9jXYE3VmLPvaCBhqQETkEVtbJ"); + assert_eq!(sig_multibase,"z5awYiUvGiDFA33EJjj4TXJG44a5afJc8QjWRpGgQiu6b23jCr7yndW2fmp9ujwqJVe32J456wV3VF78Asb1obnTc"); } #[test] fn test_verify_signature() { let bytes = [ - 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, - 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, + 215, 90, 152, 1, 130, 177, 10, 183, 213, 75, 254, 211, 201, 100, 7, 58, 14, 225, 114, + 243, 218, 166, 35, 37, 175, 2, 26, 104, 247, 7, 81, 26, ]; - let keypair = Ed25519KeyPair::from_secret_key_bytes(&bytes); - let message = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - let sig_multibase = "zmqquC4Hb5EK7L7JPQjzABJ8rK8dvpVDgWfN8d6JQ5F96sw91g2mz4m3iPSJ4tQ9jXYE3VmLPvaCBhqQETkEVtbJ"; + let keypair = Ed25519KeyPair::from_public_key_bytes(&bytes); + let message = []; + let sig_multibase = "z5awYiUvGiDFA33EJjj4TXJG44a5afJc8QjWRpGgQiu6b23jCr7yndW2fmp9ujwqJVe32J456wV3VF78Asb1obnTc"; let (_base, sig) = multibase::decode(sig_multibase).unwrap(); let verify = keypair.verify_signature(&message, &sig); assert_eq!(verify, true); diff --git a/src/crypto/sr25519.rs b/src/crypto/sr25519.rs index b269faa..4e71f48 100644 --- a/src/crypto/sr25519.rs +++ b/src/crypto/sr25519.rs @@ -3,7 +3,10 @@ use bip39::{Language, Mnemonic}; use codec::{Decode, Encode}; use regex::Regex; use schnorrkel::derive::{ChainCode, Derivation}; -use schnorrkel::{ExpansionMode, SecretKey, Signature}; +use schnorrkel::{ + ExpansionMode, MiniSecretKey, PublicKey, SecretKey, Signature, KEYPAIR_LENGTH, + SECRET_KEY_LENGTH, +}; use substrate_bip39::mini_secret_from_entropy; use lazy_static::lazy_static; @@ -49,6 +52,53 @@ impl Sr25519KeyPair { Some(pair.derive(path)) } + pub fn from_mini_secret_key_bytes(bytes: &[u8; 32]) -> Option { + let mini_secret_key = MiniSecretKey::from_bytes(bytes).unwrap(); + let public_key = mini_secret_key.expand_to_public(ExpansionMode::Ed25519); + println!("{:?}", public_key); + Some(Sr25519KeyPair( + mini_secret_key.expand_to_keypair(ExpansionMode::Ed25519), + )) + } + + pub fn from_secret_key_bytes(bytes: &[u8; SECRET_KEY_LENGTH]) -> Option { + let secret_key: SecretKey = SecretKey::from_bytes(bytes).ok()?; + let public_key: PublicKey = secret_key.to_public(); + + let secret = secret_key.to_bytes(); + let public = public_key.to_bytes(); + + let mut keypair_bytes: [u8; KEYPAIR_LENGTH] = [0u8; KEYPAIR_LENGTH]; + keypair_bytes[..SECRET_KEY_LENGTH].copy_from_slice(&secret); + keypair_bytes[SECRET_KEY_LENGTH..].copy_from_slice(&public); + + let keypair = schnorrkel::Keypair::from_bytes(&keypair_bytes).ok(); + Some(Sr25519KeyPair(keypair.unwrap())) + } + + pub fn from_public_key_bytes(bytes: &[u8]) -> Option { + let public_key: PublicKey = PublicKey::from_bytes(bytes).unwrap(); + + let public = public_key.to_bytes(); + + let mut keypair_bytes: [u8; KEYPAIR_LENGTH] = [0u8; KEYPAIR_LENGTH]; + keypair_bytes[SECRET_KEY_LENGTH..].copy_from_slice(&public); + let keypair = schnorrkel::Keypair::from_bytes(&keypair_bytes).ok(); + Some(Sr25519KeyPair(keypair.unwrap())) + } + + pub fn to_public_key_bytes(&self) -> [u8; 32] { + self.0.public.to_bytes() + } + + pub fn to_secret_key_bytes(&self) -> [u8; 64] { + self.0.secret.to_bytes() + } + + pub fn to_ed25519_key_bytes(&self) -> [u8; KEYPAIR_LENGTH] { + self.0.to_half_ed25519_bytes() + } + fn derive(&self, path: impl Iterator) -> Self { let init = self.0.secret.clone(); let result = path.fold(init, |acc, j| match j { @@ -213,6 +263,70 @@ mod tests { } } + #[test] + fn test_from_mini_secret_key_bytes() { + let bytes = [ + 200, 227, 122, 92, 143, 199, 212, 106, 232, 198, 122, 58, 208, 6, 178, 26, 220, 207, + 30, 15, 194, 115, 193, 89, 75, 112, 249, 220, 241, 127, 234, 214, + ]; + let keypair = Sr25519KeyPair::from_mini_secret_key_bytes(&bytes); + match keypair { + Some(Sr25519KeyPair(keypair)) => { + let bytes = keypair.to_bytes(); + let secret_key_bytes = &bytes[..SECRET_KEY_LENGTH]; + let public_key_bytes = &bytes[SECRET_KEY_LENGTH..]; + assert_eq!( + hex::encode(secret_key_bytes), + "b3370307d69f13cece7c28b2fa6380bcd56e9f32c9daa5a7be545efb65bc370dbab0ac540259f83925afca9192fa73f99f3ec9ca1c8da3297b0e05a87fee3df3" + ); + assert_eq!( + hex::encode(public_key_bytes), + "f02283ff600d00613244e1e43dc88d56fec666223de7ebeb3f32e93a375fe12b" + ); + } + None => assert!(false), + _ => assert!(false), + } + } + + #[test] + fn test_from_secret_key_bytes() { + let bytes = [ + 179, 55, 3, 7, 214, 159, 19, 206, 206, 124, 40, 178, 250, 99, 128, 188, 213, 110, 159, + 50, 201, 218, 165, 167, 190, 84, 94, 251, 101, 188, 55, 13, 186, 176, 172, 84, 2, 89, + 248, 57, 37, 175, 202, 145, 146, 250, 115, 249, 159, 62, 201, 202, 28, 141, 163, 41, + 123, 14, 5, 168, 127, 238, 61, 243, + ]; + let keypair = Sr25519KeyPair::from_secret_key_bytes(&bytes); + match keypair { + Some(Sr25519KeyPair(keypair)) => { + let bytes = keypair.to_bytes(); + let secret_key_bytes = &bytes[..SECRET_KEY_LENGTH]; + let public_key_bytes = &bytes[SECRET_KEY_LENGTH..]; + assert_eq!( + hex::encode(secret_key_bytes), + "b3370307d69f13cece7c28b2fa6380bcd56e9f32c9daa5a7be545efb65bc370dbab0ac540259f83925afca9192fa73f99f3ec9ca1c8da3297b0e05a87fee3df3" + ); + assert_eq!( + hex::encode(public_key_bytes), + "f02283ff600d00613244e1e43dc88d56fec666223de7ebeb3f32e93a375fe12b" + ); + } + None => assert!(false), + _ => assert!(false), + } + } + + #[test] + fn test_from_public_key_bytes() { + let bytes = [ + 240, 34, 131, 255, 96, 13, 0, 97, 50, 68, 225, 228, 61, 200, 141, 86, 254, 198, 102, + 34, 61, 231, 235, 235, 63, 50, 233, 58, 55, 95, 225, 43, + ]; + let keypair = Sr25519KeyPair::from_public_key_bytes(&bytes).unwrap(); + assert_eq!(keypair.to_public_key_bytes(), bytes); + } + #[test] fn test_ss58_address() { let keypair = Sr25519KeyPair::from_suri( @@ -237,10 +351,11 @@ mod tests { #[test] fn test_verify_signature() { - let keypair = Sr25519KeyPair::from_suri( - "true crowd stereo border country ocean mountain sadness term stumble media glory", - ) - .unwrap(); + let public_key_bytes = [ + 240, 34, 131, 255, 96, 13, 0, 97, 50, 68, 225, 228, 61, 200, 141, 86, 254, 198, 102, + 34, 61, 231, 235, 235, 63, 50, 233, 58, 55, 95, 225, 43, + ]; + let keypair = Sr25519KeyPair::from_public_key_bytes(&public_key_bytes).unwrap(); let message = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let signature = [ 80, 147, 218, 23, 52, 24, 12, 20, 87, 87, 240, 184, 36, 197, 125, 76, 121, 152, 133, diff --git a/src/verifiable/credential/credential.rs b/src/verifiable/credential/credential.rs index 8e77315..b3434cb 100644 --- a/src/verifiable/credential/credential.rs +++ b/src/verifiable/credential/credential.rs @@ -1,11 +1,6 @@ -use std::ptr::null; - use serde::{Deserialize, Serialize}; -use crate::{ - crypto::{ed25519::Ed25519KeyPair, keytype::KeyType}, - verifiable::credential::proof::ProofSuiteType, -}; +use crate::{crypto::keytype::KeyType, verifiable::credential::proof::ProofSuiteType}; use super::{ contexts::Contexts, @@ -96,9 +91,13 @@ impl Credential { Ok(()) } - pub fn generate_proof(&self, keypair: KeyType) -> Result { + pub(crate) fn validate_unsigned_embedded(&self) -> Result<(), Error> { + self.validate_unsigned()?; + Ok(()) + } + + pub fn generate_proof(&self, keypair: &KeyType) -> Result { let message = serde_json::to_string(&self).unwrap(); - let mut proof: Proof = Proof::new(ProofSuiteType::Ed25519Signature2018); let issuer = self.issuer.as_ref().unwrap().get_id_ref().clone(); match keypair { @@ -106,21 +105,23 @@ impl Credential { let signature = keypair.sign(&message.as_bytes()); let sig_multibase = multibase::encode(multibase::Base::Base58Btc, signature); + let mut proof: Proof = Proof::new(ProofSuiteType::Ed25519Signature2018); proof.proof_purpose = Some(VerificationRelationship::AssertionMethod); proof.verification_method = Some(issuer.to_string().to_owned() + "#keys-1"); proof.proof_value = Some(sig_multibase); + Ok(proof) } KeyType::Sr25519(keypair) => { let signature = keypair.sign(&message.as_bytes()); let sig_multibase = multibase::encode(multibase::Base::Base58Btc, signature); + let mut proof: Proof = Proof::new(ProofSuiteType::Sr25519VerificationKey2020); proof.proof_purpose = Some(VerificationRelationship::AssertionMethod); proof.verification_method = Some(issuer.to_string().to_owned() + "#keys-1"); proof.proof_value = Some(sig_multibase); + Ok(proof) } } - - Ok(proof) } pub fn add_proof(&mut self, proof: Proof) { @@ -136,7 +137,7 @@ impl Credential { } } - pub fn verify(&self, keypair: KeyType) -> Result { + pub fn verify(&self, keypair: &KeyType) -> Result { if self.proof.is_none() { return Err(Error::MissingProof); } @@ -172,12 +173,12 @@ impl Credential { #[cfg(test)] mod tests { - use serde_json::json; + use crate::crypto::{ed25519::Ed25519KeyPair, sr25519::Sr25519KeyPair}; use super::*; #[test] - fn test_sign_credential() { + fn test_sign_credential_ed25519() { let keypair_bytes = [ 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, @@ -195,18 +196,18 @@ mod tests { } }"###; let mut vc: Credential = Credential::from_json_unsigned(vc_str).unwrap(); - let proof = vc.generate_proof(keypair).unwrap(); + let proof = vc.generate_proof(&keypair).unwrap(); vc.add_proof(proof); println!("{:?}", serde_json::to_string(&vc).unwrap()); } #[test] - fn test_verify_credential() { + fn test_verify_credential_ed25519() { let keypair_bytes = [ - 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, - 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, + 184, 96, 68, 197, 81, 228, 13, 193, 222, 132, 170, 137, 194, 220, 242, 118, 87, 164, + 62, 5, 16, 241, 78, 147, 136, 193, 16, 10, 118, 249, 78, 92, ]; - let keypair = KeyType::Ed25519(Ed25519KeyPair::from_secret_key_bytes(&keypair_bytes)); + let keypair = KeyType::Ed25519(Ed25519KeyPair::from_public_key_bytes(&keypair_bytes)); let vc_str = r###"{ "@context":"https://www.w3.org/2018/credentials/v1", @@ -228,8 +229,67 @@ mod tests { } }"###; - let mut vc: Credential = Credential::from_json(vc_str).unwrap(); + let vc: Credential = Credential::from_json(vc_str).unwrap(); + + assert_eq!(vc.verify(&keypair).unwrap(), true); + } + + #[test] + fn test_sign_credential_sr25519() { + let keypair_bytes = [ + 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, + 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, + ]; + let keypair = + KeyType::Sr25519(Sr25519KeyPair::from_mini_secret_key_bytes(&keypair_bytes).unwrap()); + + let vc_str = r###"{ + "@context": "https://www.w3.org/2018/credentials/v1", + "id": "http://example.org/credentials/3731", + "type": ["VerifiableCredential"], + "issuer": "did:example:foo", + "issuanceDate": "2020-08-19T21:41:50Z", + "credentialSubject": { + "id": "did:example:d23dd687a7dc6787646f2eb98d0" + } + }"###; + let mut vc: Credential = Credential::from_json_unsigned(vc_str).unwrap(); + let proof = vc.generate_proof(&keypair).unwrap(); + vc.add_proof(proof); + println!("{:?}", serde_json::to_string(&vc).unwrap()); + } + + #[test] + fn test_verify_credential_sr25519() { + let keypair_bytes = [ + 10, 134, 93, 127, 235, 233, 183, 168, 140, 74, 140, 108, 193, 62, 52, 75, 186, 199, 87, + 11, 57, 197, 167, 7, 79, 249, 198, 238, 217, 121, 191, 22, + ]; + let keypair = + KeyType::Sr25519(Sr25519KeyPair::from_public_key_bytes(&keypair_bytes).unwrap()); + + let vc_str = r###"{ + "@context": "https://www.w3.org/2018/credentials/v1", + "id": "http://example.org/credentials/3731", + "type": [ + "VerifiableCredential" + ], + "credentialSubject": { + "id": "did:example:d23dd687a7dc6787646f2eb98d0" + }, + "issuer": "did:example:foo", + "issuanceDate": "2020-08-19T21:41:50Z", + "proof": { + "type": "Sr25519VerificationKey2020", + "created": "2023-04-19T23:53:37.394517Z", + "proofPurpose": "assertionMethod", + "proofValue": "zHk9DkopSPjHeJiDsVWLNHUMdgbMAvjt7MU9pjKTzi7tcw3eam7guUdiGjRQDjPDreAWaCuJSdhsYWuu2Ki2YZa8", + "verificationMethod": "did:example:foo#keys-1" + } + }"###; + + let vc: Credential = Credential::from_json(vc_str).unwrap(); - assert_eq!(vc.verify(keypair).unwrap(), true); + assert_eq!(vc.verify(&keypair).unwrap(), true); } } From 3cf7e664a94a6e387b76a840a39ca3b224e5e777 Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Thu, 20 Apr 2023 09:38:57 +0900 Subject: [PATCH 11/27] =?UTF-8?q?test:=20=F0=9F=92=8D=20add=20presentation?= =?UTF-8?q?=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/verifiable/credential/credential.rs | 19 +-- src/verifiable/presentation/presentation.rs | 156 +++++++++++++++----- 2 files changed, 132 insertions(+), 43 deletions(-) diff --git a/src/verifiable/credential/credential.rs b/src/verifiable/credential/credential.rs index b3434cb..ca11f7e 100644 --- a/src/verifiable/credential/credential.rs +++ b/src/verifiable/credential/credential.rs @@ -179,11 +179,11 @@ mod tests { #[test] fn test_sign_credential_ed25519() { - let keypair_bytes = [ + let secret_key_bytes = [ 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, ]; - let keypair = KeyType::Ed25519(Ed25519KeyPair::from_secret_key_bytes(&keypair_bytes)); + let keypair = KeyType::Ed25519(Ed25519KeyPair::from_secret_key_bytes(&secret_key_bytes)); let vc_str = r###"{ "@context": "https://www.w3.org/2018/credentials/v1", @@ -203,11 +203,11 @@ mod tests { #[test] fn test_verify_credential_ed25519() { - let keypair_bytes = [ + let public_key_bytes = [ 184, 96, 68, 197, 81, 228, 13, 193, 222, 132, 170, 137, 194, 220, 242, 118, 87, 164, 62, 5, 16, 241, 78, 147, 136, 193, 16, 10, 118, 249, 78, 92, ]; - let keypair = KeyType::Ed25519(Ed25519KeyPair::from_public_key_bytes(&keypair_bytes)); + let keypair = KeyType::Ed25519(Ed25519KeyPair::from_public_key_bytes(&public_key_bytes)); let vc_str = r###"{ "@context":"https://www.w3.org/2018/credentials/v1", @@ -236,12 +236,13 @@ mod tests { #[test] fn test_sign_credential_sr25519() { - let keypair_bytes = [ + let secret_key_bytes = [ 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, ]; - let keypair = - KeyType::Sr25519(Sr25519KeyPair::from_mini_secret_key_bytes(&keypair_bytes).unwrap()); + let keypair = KeyType::Sr25519( + Sr25519KeyPair::from_mini_secret_key_bytes(&secret_key_bytes).unwrap(), + ); let vc_str = r###"{ "@context": "https://www.w3.org/2018/credentials/v1", @@ -261,12 +262,12 @@ mod tests { #[test] fn test_verify_credential_sr25519() { - let keypair_bytes = [ + let public_key_bytes = [ 10, 134, 93, 127, 235, 233, 183, 168, 140, 74, 140, 108, 193, 62, 52, 75, 186, 199, 87, 11, 57, 197, 167, 7, 79, 249, 198, 238, 217, 121, 191, 22, ]; let keypair = - KeyType::Sr25519(Sr25519KeyPair::from_public_key_bytes(&keypair_bytes).unwrap()); + KeyType::Sr25519(Sr25519KeyPair::from_public_key_bytes(&public_key_bytes).unwrap()); let vc_str = r###"{ "@context": "https://www.w3.org/2018/credentials/v1", diff --git a/src/verifiable/presentation/presentation.rs b/src/verifiable/presentation/presentation.rs index 0e7e8e5..6d6959f 100644 --- a/src/verifiable/presentation/presentation.rs +++ b/src/verifiable/presentation/presentation.rs @@ -75,7 +75,6 @@ impl Presentation { pub fn generate_proof(&self, keypair: KeyType) -> Result { let message = serde_json::to_string(&self).unwrap(); - let mut proof: Proof = Proof::new(ProofSuiteType::Ed25519Signature2018); let holder = self.holder.as_ref().unwrap().get_id_ref().clone(); match keypair { @@ -83,21 +82,23 @@ impl Presentation { let signature = keypair.sign(&message.as_bytes()); let sig_multibase = multibase::encode(multibase::Base::Base58Btc, signature); + let mut proof: Proof = Proof::new(ProofSuiteType::Ed25519Signature2018); proof.proof_purpose = Some(VerificationRelationship::AssertionMethod); proof.verification_method = Some(holder.to_string().to_owned() + "#keys-1"); proof.proof_value = Some(sig_multibase); + Ok(proof) } KeyType::Sr25519(keypair) => { let signature = keypair.sign(&message.as_bytes()); let sig_multibase = multibase::encode(multibase::Base::Base58Btc, signature); + let mut proof: Proof = Proof::new(ProofSuiteType::Sr25519VerificationKey2020); proof.proof_purpose = Some(VerificationRelationship::AssertionMethod); proof.verification_method = Some(holder.to_string().to_owned() + "#keys-1"); proof.proof_value = Some(sig_multibase); + Ok(proof) } } - - Ok(proof) } pub fn add_proof(&mut self, proof: Proof) { @@ -163,19 +164,19 @@ impl Presentation { #[cfg(test)] mod tests { use crate::{ - crypto::ed25519::Ed25519KeyPair, + crypto::{ed25519::Ed25519KeyPair, sr25519::Sr25519KeyPair}, verifiable::credential::{contexts::Context, credential::Credential, uri::URI}, }; use super::*; #[test] - fn test_sign_credential() { - let keypair_bytes = [ + fn test_sign_presentation_ed25519() { + let secret_key_bytes = [ 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, ]; - let keypair = KeyType::Ed25519(Ed25519KeyPair::from_secret_key_bytes(&keypair_bytes)); + let keypair = KeyType::Ed25519(Ed25519KeyPair::from_secret_key_bytes(&secret_key_bytes)); let vc_str = r###"{ "@context": "https://www.w3.org/2018/credentials/v1", @@ -206,49 +207,136 @@ mod tests { } #[test] - fn test_verify_credential() { - let keypair_bytes = [ - 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, - 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, + fn test_verify_presentation_ed25519() { + let public_key_bytes = [ + 184, 96, 68, 197, 81, 228, 13, 193, 222, 132, 170, 137, 194, 220, 242, 118, 87, 164, + 62, 5, 16, 241, 78, 147, 136, 193, 16, 10, 118, 249, 78, 92, ]; - let keypair = KeyType::Ed25519(Ed25519KeyPair::from_secret_key_bytes(&keypair_bytes)); + let keypair = KeyType::Ed25519(Ed25519KeyPair::from_public_key_bytes(&public_key_bytes)); let vp_str = r###"{ "@context": [ - "https://www.w3.org/2018/credentials/v1" + "https://www.w3.org/2018/credentials/v1" ], "id": "http://example.org/presentations/3731", "type": "VerifiablePresentation", "verifiableCredential": { - "@context": "https://www.w3.org/2018/credentials/v1", - "id": "http://example.org/credentials/3731", - "type": [ - "VerifiableCredential" - ], - "credentialSubject": { - "id": "did:example:d23dd687a7dc6787646f2eb98d0" - }, - "issuer": "did:example:foo", - "issuanceDate": "2020-08-19T21:41:50Z", - "proof": { + "@context": "https://www.w3.org/2018/credentials/v1", + "id": "http://example.org/credentials/3731", + "type": [ + "VerifiableCredential" + ], + "credentialSubject": { + "id": "did:example:d23dd687a7dc6787646f2eb98d0" + }, + "issuer": "did:example:foo", + "issuanceDate": "2020-08-19T21:41:50Z", + "proof": { + "type": "Ed25519Signature2018", + "created": "2023-04-20T00:34:38.630560Z", + "proofPurpose": "assertionMethod", + "proofValue": "z2xmAbSm5FaXWhG8kMUb4rZenKx1SVR2R8R6Wdf9tnowBwJk3F4uaXN1Ufiqd2C85hmeXJhp9ScPC64mHCLwqXV2p", + "verificationMethod": "did:example:foo#keys-1" + } + }, + "proof": { "type": "Ed25519Signature2018", - "created": "2023-04-18T06:26:31.355301Z", + "created": "2023-04-20T00:34:38.630648Z", "proofPurpose": "assertionMethod", - "proofValue": "z2xmAbSm5FaXWhG8kMUb4rZenKx1SVR2R8R6Wdf9tnowBwJk3F4uaXN1Ufiqd2C85hmeXJhp9ScPC64mHCLwqXV2p", + "proofValue": "z45VdxhaYSg4FtgdfJcaw4D44tSTZwCmJM5aKccj6qNLNCAuuLPc6nMEGvHwqYkvEUCZbHZX4eyyzfDxqt2ji1mKq", "verificationMethod": "did:example:foo#keys-1" - } + }, + "holder": "did:example:foo" + }"###; + + let vp: Presentation = Presentation::from_json(vp_str).unwrap(); + + assert_eq!(vp.verify(&keypair).unwrap(), true); + } + + #[test] + fn test_sign_presentation_sr25519() { + let secret_key_bytes = [ + 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, + 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, + ]; + let keypair = KeyType::Sr25519( + Sr25519KeyPair::from_mini_secret_key_bytes(&secret_key_bytes).unwrap(), + ); + + let vc_str = r###"{ + "@context": "https://www.w3.org/2018/credentials/v1", + "id": "http://example.org/credentials/3731", + "type": ["VerifiableCredential"], + "issuer": "did:example:foo", + "issuanceDate": "2020-08-19T21:41:50Z", + "credentialSubject": { + "id": "did:example:d23dd687a7dc6787646f2eb98d0" + } + }"###; + let mut vc: Credential = Credential::from_json_unsigned(vc_str).unwrap(); + let proof = vc.generate_proof(&keypair).unwrap(); + vc.add_proof(proof); + + let mut vp = Presentation { + context: Contexts::Many(vec![Context::URI(URI::String(DEFAULT_CONTEXT.to_string()))]), + id: Some("http://example.org/presentations/3731".try_into().unwrap()), + type_: OneOrMany::One("VerifiablePresentation".to_string()), + verifiable_credential: Some(OneOrMany::One(CredentialOrJWT::Credential(vc))), + proof: None, + holder: Some(Holder::URI(URI::String("did:example:foo".to_string()))), + }; + + let vp_proof = vp.generate_proof(keypair).unwrap(); + vp.add_proof(vp_proof); + println!("{:?}", serde_json::to_string(&vp).unwrap()); + } + + #[test] + fn test_verify_presentation_sr25519() { + let public_key_bytes = [ + 10, 134, 93, 127, 235, 233, 183, 168, 140, 74, 140, 108, 193, 62, 52, 75, 186, 199, 87, + 11, 57, 197, 167, 7, 79, 249, 198, 238, 217, 121, 191, 22, + ]; + let keypair = + KeyType::Sr25519(Sr25519KeyPair::from_public_key_bytes(&public_key_bytes).unwrap()); + + let vp_str = r###"{ + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "id": "http://example.org/presentations/3731", + "type": "VerifiablePresentation", + "verifiableCredential": { + "@context": "https://www.w3.org/2018/credentials/v1", + "id": "http://example.org/credentials/3731", + "type": [ + "VerifiableCredential" + ], + "credentialSubject": { + "id": "did:example:d23dd687a7dc6787646f2eb98d0" + }, + "issuer": "did:example:foo", + "issuanceDate": "2020-08-19T21:41:50Z", + "proof": { + "type": "Sr25519VerificationKey2020", + "created": "2023-04-20T00:36:34.144078Z", + "proofPurpose": "assertionMethod", + "proofValue": "z3cJ7r7iDM9vjTG3Z3F7Q5kJEUihrj9EL4Nj4Ms4rAC5M4UWi6zQfZ5iG8YjAqgvULGxD9YH3kV26knR3Uk2Ds1YQ", + "verificationMethod": "did:example:foo#keys-1" + } }, "proof": { - "type": "Ed25519Signature2018", - "created": "2023-04-18T06:26:31.355864Z", - "proofPurpose": "assertionMethod", - "proofValue": "z3ccPeXQKuP3QsG1eFCmTV2V8uDmkZNwMqc5ipYHemH4txCfPqL5TbJpWqqbYgvNhXAppJBEAzz2BcNRzNumPgVjQ", - "verificationMethod": "did:example:foo#keys-1" + "type": "Sr25519VerificationKey2020", + "created": "2023-04-20T00:36:34.144147Z", + "proofPurpose": "assertionMethod", + "proofValue": "zCz4tUxJc6PF5GdLGweaxxYgYWGLYhCcAEytGcmQkx2cdWtnmoGYeznwjRdBw7RhvCyQ7H41oFKwE9zRfVoyfPYu", + "verificationMethod": "did:example:foo#keys-1" }, "holder": "did:example:foo" - }"###; + }"###; - let mut vp: Presentation = Presentation::from_json(vp_str).unwrap(); + let vp: Presentation = Presentation::from_json(vp_str).unwrap(); assert_eq!(vp.verify(&keypair).unwrap(), true); } From fed68e3f32c0d8c39cba80dcd5a59c0420e1e7b1 Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Thu, 20 Apr 2023 10:40:12 +0900 Subject: [PATCH 12/27] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20address=20ty?= =?UTF-8?q?pe=20for=20parse=20did?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/did/mod.rs | 224 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 150 insertions(+), 74 deletions(-) diff --git a/src/did/mod.rs b/src/did/mod.rs index 762e3d5..59a28c6 100644 --- a/src/did/mod.rs +++ b/src/did/mod.rs @@ -1,11 +1,14 @@ -use bip39::{Language, Mnemonic, MnemonicType, Seed}; -use ed25519_dalek::{PublicKey, SecretKey}; -use schnorrkel::{ExpansionMode, SECRET_KEY_LENGTH}; +use bip39::{Language, Mnemonic, MnemonicType}; +use schnorrkel::ExpansionMode; use serde_json::json; use substrate_bip39::mini_secret_from_entropy; -use crate::crypto::sr25519::Sr25519KeyPair; +use crate::crypto::{ed25519::Ed25519KeyPair, sr25519::Sr25519KeyPair}; +pub enum AddressType { + Ed25519, + Sr25519, +} pub fn random_phrase(words_number: u32) -> String { let mnemonic_type = match MnemonicType::for_word_count(words_number as usize) { Ok(t) => t, @@ -16,84 +19,130 @@ pub fn random_phrase(words_number: u32) -> String { mnemonic.into_phrase() } -pub fn substrate_address(suri: String, prefix: u8) -> String { - let keypair_option = Sr25519KeyPair::from_suri(suri.as_str()); - let keypair = match keypair_option { - Some(c) => c, - _ => return "".to_string(), - }; - - let rust_string = keypair.ss58_address(prefix); - rust_string -} - -pub fn generate_ss58_did(network_id: String) -> String { +pub fn generate_ss58_did(network_id: String, address_type: AddressType) -> String { let mnemonic_type = MnemonicType::for_word_count(12).unwrap(); let mnemonic = Mnemonic::new(mnemonic_type, Language::English); - let keypair_option = Sr25519KeyPair::from_suri(mnemonic.clone().into_phrase().as_str()); - - let keypair = match keypair_option { - Some(c) => c, - _ => return "".to_string(), - }; - - let address = keypair.ss58_address(42); - let did = format!("did:infra:{}:{}", network_id, address.clone()); - - let mini_secret_key = mini_secret_from_entropy(mnemonic.entropy(), "").unwrap(); - - let secret_key = mini_secret_key; - let public_key = secret_key.expand_to_public(ExpansionMode::Ed25519); - - let result = serde_json::to_string(&json!({ - "mnemonic": mnemonic.into_phrase(), - "private_key": hex::encode(secret_key.to_bytes()), - "public_key": hex::encode(public_key.to_bytes()), - "address": address.clone(), - "did": did - })); - - result.unwrap() + match address_type { + AddressType::Ed25519 => { + let keypair: Ed25519KeyPair = Ed25519KeyPair::from_bip39_phrase( + mnemonic.clone().into_phrase().as_str(), + Some(""), + ); + + let address = keypair.ss58_address(42); + let did = format!("did:infra:{}:{}", network_id, address.clone()); + + let result = serde_json::to_string(&json!({ + "mnemonic": mnemonic.into_phrase(), + "private_key": hex::encode(keypair.to_secret_key_bytes()), + "public_key": hex::encode(keypair.to_public_key_bytes()), + "address": address.clone(), + "did": did + })); + result.unwrap() + } + AddressType::Sr25519 => { + let keypair_option: Option = + Sr25519KeyPair::from_suri(mnemonic.clone().into_phrase().as_str()); + + let keypair = match keypair_option { + Some(c) => c, + _ => return "".to_string(), + }; + + let address = keypair.ss58_address(42); + let did = format!("did:infra:{}:{}", network_id, address.clone()); + + let mini_secret_key = mini_secret_from_entropy(mnemonic.entropy(), "").unwrap(); + + let secret_key = mini_secret_key; + let public_key = secret_key.expand_to_public(ExpansionMode::Ed25519); + + let result = serde_json::to_string(&json!({ + "mnemonic": mnemonic.into_phrase(), + "private_key": hex::encode(secret_key.to_bytes()), + "public_key": hex::encode(public_key.to_bytes()), + "address": address.clone(), + "did": did + })); + result.unwrap() + } + } } -pub fn generate_ss58_did_from_phrase(suri: String, network_id: String) -> String { - let keypair_option = Sr25519KeyPair::from_suri(suri.as_str()); - - let keypair = match keypair_option { - Some(c) => c, - _ => return "".to_string(), - }; - - let address = keypair.ss58_address(42); - let did = format!("did:infra:{}:{}", network_id, address.clone()); - - let mnemonic = Mnemonic::from_phrase(&suri, Language::English).unwrap(); - let mini_secret_key = mini_secret_from_entropy(mnemonic.entropy(), "").unwrap(); - - let secret_key = mini_secret_key; - let public_key = secret_key.expand_to_public(ExpansionMode::Ed25519); - - let result = serde_json::to_string(&json!({ - "private_key": hex::encode(secret_key.to_bytes()), - "public_key": hex::encode(public_key.to_bytes()), - "address": address.clone(), - "did": did - })); - - result.unwrap() +pub fn generate_ss58_did_from_phrase( + suri: String, + network_id: String, + address_type: AddressType, +) -> String { + match address_type { + AddressType::Ed25519 => { + let keypair: Ed25519KeyPair = + Ed25519KeyPair::from_bip39_phrase(suri.clone().as_str(), Some("")); + + let address = keypair.ss58_address(42); + let did = format!("did:infra:{}:{}", network_id, address.clone()); + + let result = serde_json::to_string(&json!({ + "mnemonic": suri, + "private_key": hex::encode(keypair.to_secret_key_bytes()), + "public_key": hex::encode(keypair.to_public_key_bytes()), + "address": address.clone(), + "did": did + })); + result.unwrap() + } + AddressType::Sr25519 => { + let keypair_option: Option = + Sr25519KeyPair::from_suri(suri.clone().as_str()); + + let keypair = match keypair_option { + Some(c) => c, + _ => return "".to_string(), + }; + + let address = keypair.ss58_address(42); + let did = format!("did:infra:{}:{}", network_id, address.clone()); + + let mnemonic = Mnemonic::from_phrase(&suri, Language::English).unwrap(); + let mini_secret_key = mini_secret_from_entropy(mnemonic.entropy(), "").unwrap(); + + let secret_key = mini_secret_key; + let public_key = secret_key.expand_to_public(ExpansionMode::Ed25519); + + let result = serde_json::to_string(&json!({ + "mnemonic": suri, + "private_key": hex::encode(secret_key.to_bytes()), + "public_key": hex::encode(public_key.to_bytes()), + "address": address.clone(), + "did": did + })); + result.unwrap() + } + } } -pub fn did_to_hex_public_key(did: String) -> String { +pub fn did_to_hex_public_key(did: String, address_type: AddressType) -> String { let splited_did: Vec<&str> = did.split(":").collect(); let address = splited_did[3]; let decoded_address = bs58::decode(address).into_vec().unwrap(); - let public_key: schnorrkel::PublicKey = - schnorrkel::PublicKey::from_bytes(&decoded_address[1..33]).unwrap(); + let public_key_bytes: [u8; 32] = match address_type { + AddressType::Ed25519 => { + let public_key: ed25519_dalek::PublicKey = + ed25519_dalek::PublicKey::from_bytes(&decoded_address[1..33]).unwrap(); + public_key.to_bytes() + } + AddressType::Sr25519 => { + let public_key: schnorrkel::PublicKey = + schnorrkel::PublicKey::from_bytes(&decoded_address[1..33]).unwrap(); + public_key.to_bytes() + } + }; - hex::encode(public_key.to_bytes()) + hex::encode(public_key_bytes) } pub fn ss58_address_to_did(address: String, network_id: String) -> String { @@ -120,17 +169,35 @@ mod tests { #[test] fn test_generate_ss58_did() { - println!("{:?}", generate_ss58_did("01".to_string())); + println!( + "{:?}", + generate_ss58_did("01".to_string(), AddressType::Ed25519) + ); + println!( + "{:?}", + generate_ss58_did("01".to_string(), AddressType::Sr25519) + ); } #[test] fn test_generate_ss58_did_from_phrase() { - println!( - "{:?}", + assert_eq!( + r###"{"address":"5GM7RtekqU8cGiS4MKQ7tufoH4Q1itzmoFpVcvcPfjksyPrw","did":"did:infra:01:5GM7RtekqU8cGiS4MKQ7tufoH4Q1itzmoFpVcvcPfjksyPrw","mnemonic":"caution juice atom organ advance problem want pledge someone senior holiday very","private_key":"c8fa03532fb22ee1f7f6908b9c02b4e72483f0dbd66e4cd456b8f34c6230b849","public_key":"bd7436a22571207d018ffe83f5dc77d0750b7777f1eb169053d40201d6c68d53"}"###, generate_ss58_did_from_phrase( "caution juice atom organ advance problem want pledge someone senior holiday very" .to_string(), - "01".to_string() + "01".to_string(), + AddressType::Ed25519 + ) + ); + + assert_eq!( + r###"{"address":"5Gv8YYFu8H1btvmrJy9FjjAWfb99wrhV3uhPFoNEr918utyR","did":"did:infra:01:5Gv8YYFu8H1btvmrJy9FjjAWfb99wrhV3uhPFoNEr918utyR","mnemonic":"caution juice atom organ advance problem want pledge someone senior holiday very","private_key":"c8fa03532fb22ee1f7f6908b9c02b4e72483f0dbd66e4cd456b8f34c6230b849","public_key":"d6a3105d6768e956e9e5d41050ac29843f98561410d3a47f9dd5b3b227ab8746"}"###, + generate_ss58_did_from_phrase( + "caution juice atom organ advance problem want pledge someone senior holiday very" + .to_string(), + "01".to_string(), + AddressType::Sr25519 ) ); } @@ -139,7 +206,16 @@ mod tests { fn test_did_to_hex_public_key() { assert_eq!( did_to_hex_public_key( - "did:infra:01:5Gv8YYFu8H1btvmrJy9FjjAWfb99wrhV3uhPFoNEr918utyR".to_string() + "did:infra:01:5GM7RtekqU8cGiS4MKQ7tufoH4Q1itzmoFpVcvcPfjksyPrw".to_string(), + AddressType::Ed25519 + ), + "bd7436a22571207d018ffe83f5dc77d0750b7777f1eb169053d40201d6c68d53".to_string() + ); + + assert_eq!( + did_to_hex_public_key( + "did:infra:01:5Gv8YYFu8H1btvmrJy9FjjAWfb99wrhV3uhPFoNEr918utyR".to_string(), + AddressType::Sr25519 ), "d6a3105d6768e956e9e5d41050ac29843f98561410d3a47f9dd5b3b227ab8746".to_string() ); From 8c21dd2294e8c29b5535994da4a86f2c4d90cd04 Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Fri, 21 Apr 2023 07:25:19 +0900 Subject: [PATCH 13/27] =?UTF-8?q?chore:=20=F0=9F=A4=96=20remove=20unused?= =?UTF-8?q?=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/crypto/ed25519.rs | 3 --- src/crypto/sr25519.rs | 4 ---- 2 files changed, 7 deletions(-) diff --git a/src/crypto/ed25519.rs b/src/crypto/ed25519.rs index 6bc6955..9e65fc1 100644 --- a/src/crypto/ed25519.rs +++ b/src/crypto/ed25519.rs @@ -118,7 +118,6 @@ mod tests { println!("{:?}", hex::encode(secret_key_bytes)); println!("{:?}", hex::encode(public_key_bytes)); } - _ => assert!(false), } } @@ -142,7 +141,6 @@ mod tests { "bd7436a22571207d018ffe83f5dc77d0750b7777f1eb169053d40201d6c68d53" ); } - _ => assert!(false), } } @@ -168,7 +166,6 @@ mod tests { "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" ); } - _ => assert!(false), } } diff --git a/src/crypto/sr25519.rs b/src/crypto/sr25519.rs index 4e71f48..86b7661 100644 --- a/src/crypto/sr25519.rs +++ b/src/crypto/sr25519.rs @@ -238,7 +238,6 @@ mod tests { "f02283ff600d00613244e1e43dc88d56fec666223de7ebeb3f32e93a375fe12b" ); } - _ => assert!(false), } } @@ -259,7 +258,6 @@ mod tests { "f02283ff600d00613244e1e43dc88d56fec666223de7ebeb3f32e93a375fe12b" ); } - _ => assert!(false), } } @@ -285,7 +283,6 @@ mod tests { ); } None => assert!(false), - _ => assert!(false), } } @@ -313,7 +310,6 @@ mod tests { ); } None => assert!(false), - _ => assert!(false), } } From 598379b88a11fee5e8aa6f448c1c03e8be74d244 Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Fri, 21 Apr 2023 07:25:39 +0900 Subject: [PATCH 14/27] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20wrong=20signa?= =?UTF-8?q?ture=20type=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/verifiable/credential/credential.rs | 6 +++--- src/verifiable/credential/proof.rs | 2 +- src/verifiable/presentation/presentation.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/verifiable/credential/credential.rs b/src/verifiable/credential/credential.rs index ca11f7e..45ff643 100644 --- a/src/verifiable/credential/credential.rs +++ b/src/verifiable/credential/credential.rs @@ -30,9 +30,9 @@ pub struct Credential { pub type_: OneOrMany, pub credential_subject: OneOrMany, #[serde(skip_serializing_if = "Option::is_none")] - pub issuer: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub issuance_date: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub issuer: Option, // This field is populated only when using // embedded proofs such as LD-PROOF // https://w3c-ccg.github.io/ld-proofs/ @@ -115,7 +115,7 @@ impl Credential { let signature = keypair.sign(&message.as_bytes()); let sig_multibase = multibase::encode(multibase::Base::Base58Btc, signature); - let mut proof: Proof = Proof::new(ProofSuiteType::Sr25519VerificationKey2020); + let mut proof: Proof = Proof::new(ProofSuiteType::Sr25519Signature2020); proof.proof_purpose = Some(VerificationRelationship::AssertionMethod); proof.verification_method = Some(issuer.to_string().to_owned() + "#keys-1"); proof.proof_value = Some(sig_multibase); diff --git a/src/verifiable/credential/proof.rs b/src/verifiable/credential/proof.rs index 5b48153..cfff338 100644 --- a/src/verifiable/credential/proof.rs +++ b/src/verifiable/credential/proof.rs @@ -131,7 +131,7 @@ impl VerificationRelationship { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] pub enum ProofSuiteType { Ed25519Signature2018, - Sr25519VerificationKey2020, + Sr25519Signature2020, } impl FromStr for ProofSuiteType { diff --git a/src/verifiable/presentation/presentation.rs b/src/verifiable/presentation/presentation.rs index 6d6959f..c5787bd 100644 --- a/src/verifiable/presentation/presentation.rs +++ b/src/verifiable/presentation/presentation.rs @@ -92,7 +92,7 @@ impl Presentation { let signature = keypair.sign(&message.as_bytes()); let sig_multibase = multibase::encode(multibase::Base::Base58Btc, signature); - let mut proof: Proof = Proof::new(ProofSuiteType::Sr25519VerificationKey2020); + let mut proof: Proof = Proof::new(ProofSuiteType::Sr25519Signature2020); proof.proof_purpose = Some(VerificationRelationship::AssertionMethod); proof.verification_method = Some(holder.to_string().to_owned() + "#keys-1"); proof.proof_value = Some(sig_multibase); From 14f824466bd856872a097a87a2659712c266c6ee Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Tue, 25 Apr 2023 16:55:46 +0900 Subject: [PATCH 15/27] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20infra=20did?= =?UTF-8?q?=20resolver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 11 +++ src/lib.rs | 1 + src/resolver/mod.rs | 1 + src/resolver/resolver.rs | 186 +++++++++++++++++++++++++++++++++++++ tests/did-example-foo.json | 36 +++++++ tests/did-infra-space.json | 36 +++++++ 6 files changed, 271 insertions(+) create mode 100644 src/resolver/mod.rs create mode 100644 src/resolver/resolver.rs create mode 100644 tests/did-example-foo.json create mode 100644 tests/did-infra-space.json diff --git a/Cargo.toml b/Cargo.toml index 43ead3e..e426c0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,14 @@ thiserror = "1.0.40" iref = { version = "2.2.2", features = ["serde"] } static-iref = "2.0.0" multibase = "0.9.1" +ssi-vc = "0.1.1" +ssi = "0.6.0" +ssi-json-ld = "0.2.1" +ssi-ldp = "0.2.0" +base64 = "0.12.3" +ssi-jws = "0.1.0" +ssi-dids = { version = "0.1", features = ["example"] } +async-std = { version = "1.9", features = ["attributes"] } +async-trait = "0.1.68" +serde_urlencoded = "0.7.1" +percent-encoding = "2.2.0" diff --git a/src/lib.rs b/src/lib.rs index 9cdd91d..01fee8d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ pub mod crypto; pub mod did; +pub mod resolver; pub mod verifiable; diff --git a/src/resolver/mod.rs b/src/resolver/mod.rs new file mode 100644 index 0000000..e755804 --- /dev/null +++ b/src/resolver/mod.rs @@ -0,0 +1 @@ +pub mod resolver; diff --git a/src/resolver/resolver.rs b/src/resolver/resolver.rs new file mode 100644 index 0000000..66dd674 --- /dev/null +++ b/src/resolver/resolver.rs @@ -0,0 +1,186 @@ +use async_trait::async_trait; +use serde_json::json; +use ssi::did_resolve::{ + DIDResolver, DocumentMetadata, ResolutionInputMetadata, ResolutionMetadata, TYPE_DID_LD_JSON, +}; +use ssi_dids::{Document, VerificationMethod, VerificationMethodMap, DIDURL}; + +use crate::did::{did_to_hex_public_key, AddressType}; + +const DID_KEY_ED25519_PREFIX: [u8; 2] = [0xed, 0x01]; + +pub const ERROR_NOT_FOUND: &str = "notFound"; +const DOC_JSON_FOO: &str = include_str!("../../tests/did-example-foo.json"); +const DOC_JSON_INFRA: &str = include_str!("../../tests/did-infra-space.json"); + +/// A DID Resolver implementing a client for the [DID Resolution HTTP(S) +/// Binding](https://w3c-ccg.github.io/did-resolution/#bindings-https). +#[derive(Debug, Clone, Default)] +pub struct InfraDIDResolver { + /// HTTP(S) URL for DID resolver HTTP(S) endpoint. + pub endpoint: String, +} + +impl InfraDIDResolver { + /// Construct a new HTTP DID Resolver with a given [endpoint][InfraDIDResolver::endpoint] URL. + pub fn new(url: &str) -> Self { + Self { + endpoint: url.to_string(), + } + } +} + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl DIDResolver for InfraDIDResolver { + /// Resolve a DID over HTTP(S), using the [DID Resolution HTTP(S) Binding](https://w3c-ccg.github.io/did-resolution/#bindings-https). + async fn resolve( + &self, + did: &str, + _input_metadata: &ResolutionInputMetadata, + ) -> ( + ResolutionMetadata, + Option, + Option, + ) { + let hex_public_key = did_to_hex_public_key(did.to_string(), AddressType::Ed25519); + let public_key_bytes = hex::decode(hex_public_key).unwrap(); + + let vms = vec![ + VerificationMethod::Map(VerificationMethodMap { + id: did.to_string() + "#keys-1", + type_: "Ed25519VerificationKey2018".to_string(), + controller: did.to_string(), + public_key_base58: Some(bs58::encode(public_key_bytes.clone()).into_string()), + ..Default::default() + }), + VerificationMethod::Map(VerificationMethodMap { + id: did.to_string() + "#keys-2", + type_: "Ed25519VerificationKey2020".to_string(), + controller: did.to_string(), + property_set: serde_json::from_value(json!({ + "publicKeyMultibase": multibase::encode( + multibase::Base::Base58Btc, + [ + DID_KEY_ED25519_PREFIX.to_vec(), + public_key_bytes.clone() + ] + .concat() + ), + })) + .unwrap(), + ..Default::default() + }), + ]; + + let vm_urls = vec![ + VerificationMethod::DIDURL(DIDURL { + did: did.to_string() + "#keys-1", + ..Default::default() + }), + VerificationMethod::DIDURL(DIDURL { + did: did.to_string() + "#keys-2", + ..Default::default() + }), + ]; + + let doc = Document { + context: ssi_dids::Contexts::One(ssi_dids::Context::URI( + ssi_dids::DEFAULT_CONTEXT.into(), + )), + id: did.to_string(), + verification_method: Some(vms), + authentication: Some(vm_urls.clone()), + assertion_method: Some(vm_urls), + ..Default::default() + }; + ( + ResolutionMetadata { + error: None, + content_type: Some(TYPE_DID_LD_JSON.to_string()), + property_set: None, + }, + Some(doc), + Some(DocumentMetadata::default()), + ) + } +} + +/// A DID Resolver implementing a client for the [DID Resolution HTTP(S) +/// Binding](https://w3c-ccg.github.io/did-resolution/#bindings-https). +#[derive(Debug, Clone, Default)] +pub struct TestDIDResolver { + /// HTTP(S) URL for DID resolver HTTP(S) endpoint. + pub endpoint: String, +} + +impl TestDIDResolver { + /// Construct a new HTTP DID Resolver with a given [endpoint][HTTPDIDResolver::endpoint] URL. + pub fn new(url: &str) -> Self { + Self { + endpoint: url.to_string(), + } + } +} + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl DIDResolver for TestDIDResolver { + /// Resolve a DID over HTTP(S), using the [DID Resolution HTTP(S) Binding](https://w3c-ccg.github.io/did-resolution/#bindings-https). + async fn resolve( + &self, + did: &str, + _input_metadata: &ResolutionInputMetadata, + ) -> ( + ResolutionMetadata, + Option, + Option, + ) { + let doc_str = match did { + "did:example:foo" => DOC_JSON_FOO, + "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW" => DOC_JSON_INFRA, + _ => return (ResolutionMetadata::from_error(ERROR_NOT_FOUND), None, None), + }; + let doc: Document = match serde_json::from_str(doc_str) { + Ok(doc) => doc, + Err(err) => { + return (ResolutionMetadata::from_error(&err.to_string()), None, None); + } + }; + ( + ResolutionMetadata { + error: None, + content_type: Some(TYPE_DID_LD_JSON.to_string()), + property_set: None, + }, + Some(doc), + Some(DocumentMetadata::default()), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[async_std::test] + async fn test_resolve() { + let resolver = TestDIDResolver::default(); + let (_, doc, _) = resolver + .resolve("did:example:foo", &ResolutionInputMetadata::default()) + .await; + println!("{:?}", serde_json::to_string_pretty(&doc).unwrap()); + } + + #[async_std::test] + async fn test_infra_resolve() { + let resolver = InfraDIDResolver::default(); + let (_, doc, _) = resolver + .resolve( + "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + &ResolutionInputMetadata::default(), + ) + .await; + println!("{:?}", serde_json::to_string_pretty(&doc).unwrap()); + } +} diff --git a/tests/did-example-foo.json b/tests/did-example-foo.json new file mode 100644 index 0000000..54ed32e --- /dev/null +++ b/tests/did-example-foo.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://www.w3.org/ns/did/v1" + ], + "id": "did:example:foo", + "verificationMethod": [ + { + "id": "did:example:foo#keys-1", + "type": "Ed25519VerificationKey2018", + "controller": "did:example:foo", + "publicKeyBase58": "3mjBnLB2Sp2GGA3YhPHyzZXcShoAwtKaaq5pwgUcDQSe" + }, + { + "id": "did:example:foo#keys-2", + "type": "Ed25519VerificationKey2020", + "controller": "did:example:foo", + "publicKeyMultibase": "z6MkhDzENaRTnMWjNetFNxFpqf5cGH52MmZwGqzkmxSd8dE2" + } + ], + "assertionMethod": [ + "did:example:foo#keys-1", + "did:example:foo#keys-2" + ], + "authentication": [ + "did:example:foo#keys-1", + "did:example:foo#keys-2" + ], + "capabilityDelegation": [ + "did:example:foo#keys-1", + "did:example:foo#keys-2" + ], + "capabilityInvocation": [ + "did:example:foo#keys-1", + "did:example:foo#keys-2" + ] +} \ No newline at end of file diff --git a/tests/did-infra-space.json b/tests/did-infra-space.json new file mode 100644 index 0000000..2aef0c2 --- /dev/null +++ b/tests/did-infra-space.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://www.w3.org/ns/did/v1" + ], + "id": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "verificationMethod": [ + { + "id": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", + "type": "Ed25519VerificationKey2018", + "controller": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "publicKeyBase58": "F9JHKboDqg3tK9wnrt8z8xwZRnoZCJAHTdxXVuUMW8z2" + }, + { + "id": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", + "type": "Ed25519VerificationKey2020", + "controller": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "publicKeyMultibase": "z6MktbZKur3fBDYMRenVYT6pz4VZFN5QcBQe9esTLBSNRMmQ" + } + ], + "assertionMethod": [ + "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", + "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2" + ], + "authentication": [ + "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", + "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2" + ], + "capabilityDelegation": [ + "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", + "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2" + ], + "capabilityInvocation": [ + "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", + "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2" + ] +} \ No newline at end of file From ffcb3739068b7f65f05c5943ce2038b40e41f95e Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Wed, 26 Apr 2023 08:58:42 +0900 Subject: [PATCH 16/27] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20fix=20credential?= =?UTF-8?q?=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- a.json | 19 - src/did/mod.rs | 2 +- src/verifiable/credential/contexts.rs | 76 ---- src/verifiable/credential/credential.rs | 296 --------------- .../credential/credential_subject.rs | 31 -- src/verifiable/credential/errors.rs | 43 --- src/verifiable/credential/issuer.rs | 26 -- src/verifiable/credential/mod.rs | 157 +++++++- src/verifiable/credential/object_with_id.rs | 14 - src/verifiable/credential/one_or_many.rs | 109 ------ src/verifiable/credential/proof.rs | 143 -------- src/verifiable/credential/schema.rs | 15 - src/verifiable/credential/string_or_uri.rs | 53 --- src/verifiable/credential/uri.rs | 57 --- src/verifiable/credential/vc_date_time.rs | 62 ---- src/verifiable/mod.rs | 1 - .../presentation/credential_or_jwt.rs | 11 - src/verifiable/presentation/holder.rs | 26 -- src/verifiable/presentation/mod.rs | 3 - src/verifiable/presentation/presentation.rs | 343 ------------------ 20 files changed, 146 insertions(+), 1341 deletions(-) delete mode 100644 a.json delete mode 100644 src/verifiable/credential/contexts.rs delete mode 100644 src/verifiable/credential/credential.rs delete mode 100644 src/verifiable/credential/credential_subject.rs delete mode 100644 src/verifiable/credential/errors.rs delete mode 100644 src/verifiable/credential/issuer.rs delete mode 100644 src/verifiable/credential/object_with_id.rs delete mode 100644 src/verifiable/credential/one_or_many.rs delete mode 100644 src/verifiable/credential/proof.rs delete mode 100644 src/verifiable/credential/schema.rs delete mode 100644 src/verifiable/credential/string_or_uri.rs delete mode 100644 src/verifiable/credential/uri.rs delete mode 100644 src/verifiable/credential/vc_date_time.rs delete mode 100644 src/verifiable/presentation/credential_or_jwt.rs delete mode 100644 src/verifiable/presentation/holder.rs delete mode 100644 src/verifiable/presentation/mod.rs delete mode 100644 src/verifiable/presentation/presentation.rs diff --git a/a.json b/a.json deleted file mode 100644 index bfe1807..0000000 --- a/a.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "@context": "https://www.w3.org/2018/credentials/v1", - "id": "http://example.org/credentials/3731", - "type": [ - "VerifiableCredential" - ], - "credentialSubject": { - "id": "did:example:d23dd687a7dc6787646f2eb98d0" - }, - "issuer": "did:example:foo", - "issuanceDate": "2020-08-19T21:41:50Z", - "proof": { - "type": "Sr25519VerificationKey2020", - "created": "2023-04-19T23:53:37.394517Z", - "proofPurpose": "assertionMethod", - "proofValue": "zHk9DkopSPjHeJiDsVWLNHUMdgbMAvjt7MU9pjKTzi7tcw3eam7guUdiGjRQDjPDreAWaCuJSdhsYWuu2Ki2YZa8", - "verificationMethod": "did:example:foo#keys-1" - } -} \ No newline at end of file diff --git a/src/did/mod.rs b/src/did/mod.rs index 59a28c6..06b7d09 100644 --- a/src/did/mod.rs +++ b/src/did/mod.rs @@ -158,7 +158,7 @@ mod tests { fn testa() { println!( "{:?}", - hex::decode("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60") + hex::decode("8006aaa5985f1d72e916167bdcbc663232cef5823209b1246728f73137888170") ); } diff --git a/src/verifiable/credential/contexts.rs b/src/verifiable/credential/contexts.rs deleted file mode 100644 index 73fdde3..0000000 --- a/src/verifiable/credential/contexts.rs +++ /dev/null @@ -1,76 +0,0 @@ -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::collections::HashMap as Map; - -use super::{ - credential::{ALT_DEFAULT_CONTEXT, DEFAULT_CONTEXT}, - one_or_many::OneOrMany, - uri::URI, -}; - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(untagged)] -pub enum Context { - URI(URI), - Object(Map), -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(untagged)] -#[serde(try_from = "OneOrMany")] -pub enum Contexts { - One(Context), - Many(Vec), -} - -impl TryFrom> for Contexts { - type Error = String; - fn try_from(context: OneOrMany) -> Result { - let first_uri = match context.first() { - None => return Err("Missing Context".to_string()), - Some(Context::URI(URI::String(uri))) => uri, - Some(Context::Object(_)) => return Err("Invalid Context".to_string()), - }; - if first_uri != DEFAULT_CONTEXT && first_uri != ALT_DEFAULT_CONTEXT { - return Err("Invalid Context".to_string()); - } - Ok(match context { - OneOrMany::One(context) => Contexts::One(context), - OneOrMany::Many(contexts) => Contexts::Many(contexts), - }) - } -} - -impl From for OneOrMany { - fn from(contexts: Contexts) -> OneOrMany { - match contexts { - Contexts::One(context) => OneOrMany::One(context), - Contexts::Many(contexts) => OneOrMany::Many(contexts), - } - } -} - -impl Contexts { - /// Check if the contexts contains the given URI. - pub fn contains_uri(&self, uri: &str) -> bool { - match self { - Self::One(context) => { - if let Context::URI(URI::String(context_uri)) = context { - if context_uri == uri { - return true; - } - } - } - Self::Many(contexts) => { - for context in contexts { - if let Context::URI(URI::String(context_uri)) = context { - if context_uri == uri { - return true; - } - } - } - } - } - false - } -} diff --git a/src/verifiable/credential/credential.rs b/src/verifiable/credential/credential.rs deleted file mode 100644 index 45ff643..0000000 --- a/src/verifiable/credential/credential.rs +++ /dev/null @@ -1,296 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::{crypto::keytype::KeyType, verifiable::credential::proof::ProofSuiteType}; - -use super::{ - contexts::Contexts, - credential_subject::CredentialSubject, - errors::Error, - issuer::Issuer, - one_or_many::OneOrMany, - proof::{Proof, VerificationRelationship}, - schema::Schema, - string_or_uri::StringOrURI, - vc_date_time::VCDateTime, -}; - -pub const DEFAULT_CONTEXT: &str = "https://www.w3.org/2018/credentials/v1"; - -// work around https://github.com/w3c/vc-test-suite/issues/103 -pub const ALT_DEFAULT_CONTEXT: &str = "https://w3.org/2018/credentials/v1"; - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Credential { - #[serde(rename = "@context")] - pub context: Contexts, - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - #[serde(rename = "type")] - pub type_: OneOrMany, - pub credential_subject: OneOrMany, - #[serde(skip_serializing_if = "Option::is_none")] - pub issuance_date: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub issuer: Option, - // This field is populated only when using - // embedded proofs such as LD-PROOF - // https://w3c-ccg.github.io/ld-proofs/ - #[serde(skip_serializing_if = "Option::is_none")] - pub proof: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub expiration_date: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub credential_schema: Option>, -} - -impl Credential { - pub fn from_json(s: &str) -> Result { - let vp: Self = serde_json::from_str(s)?; - vp.validate()?; - Ok(vp) - } - - pub fn from_json_unsigned(s: &str) -> Result { - let vp: Self = serde_json::from_str(s)?; - vp.validate_unsigned()?; - Ok(vp) - } - - pub fn validate(&self) -> Result<(), Error> { - self.validate_unsigned()?; - if self.proof.is_none() { - return Err(Error::MissingProof); - } - Ok(()) - } - - pub fn validate_unsigned(&self) -> Result<(), Error> { - if !self.type_.contains(&"VerifiableCredential".to_string()) { - return Err(Error::MissingTypeVerifiableCredential); - } - if self.issuer.is_none() { - return Err(Error::InvalidIssuer); - } - if self.credential_subject.is_empty() { - // https://www.w3.org/TR/vc-data-model/#credential-subject - // VC-Data-Model "defines a credentialSubject property for the expression of claims - // about one or more subjects." - // Therefore, zero credentialSubject values is considered invalid. - return Err(Error::EmptyCredentialSubject); - } - for subject in &self.credential_subject { - if subject.is_empty() { - return Err(Error::EmptyCredentialSubject); - } - } - if self.issuance_date.is_none() { - return Err(Error::MissingIssuanceDate); - } - - Ok(()) - } - - pub(crate) fn validate_unsigned_embedded(&self) -> Result<(), Error> { - self.validate_unsigned()?; - Ok(()) - } - - pub fn generate_proof(&self, keypair: &KeyType) -> Result { - let message = serde_json::to_string(&self).unwrap(); - let issuer = self.issuer.as_ref().unwrap().get_id_ref().clone(); - - match keypair { - KeyType::Ed25519(keypair) => { - let signature = keypair.sign(&message.as_bytes()); - let sig_multibase = multibase::encode(multibase::Base::Base58Btc, signature); - - let mut proof: Proof = Proof::new(ProofSuiteType::Ed25519Signature2018); - proof.proof_purpose = Some(VerificationRelationship::AssertionMethod); - proof.verification_method = Some(issuer.to_string().to_owned() + "#keys-1"); - proof.proof_value = Some(sig_multibase); - Ok(proof) - } - KeyType::Sr25519(keypair) => { - let signature = keypair.sign(&message.as_bytes()); - let sig_multibase = multibase::encode(multibase::Base::Base58Btc, signature); - - let mut proof: Proof = Proof::new(ProofSuiteType::Sr25519Signature2020); - proof.proof_purpose = Some(VerificationRelationship::AssertionMethod); - proof.verification_method = Some(issuer.to_string().to_owned() + "#keys-1"); - proof.proof_value = Some(sig_multibase); - Ok(proof) - } - } - } - - pub fn add_proof(&mut self, proof: Proof) { - self.proof = match self.proof.take() { - None => Some(OneOrMany::One(proof)), - Some(OneOrMany::One(existing_proof)) => { - Some(OneOrMany::Many(vec![existing_proof, proof])) - } - Some(OneOrMany::Many(mut proofs)) => { - proofs.push(proof); - Some(OneOrMany::Many(proofs)) - } - } - } - - pub fn verify(&self, keypair: &KeyType) -> Result { - if self.proof.is_none() { - return Err(Error::MissingProof); - } - - let mut vc_copy = self.clone(); - let proofs = vc_copy.proof.take().unwrap(); - vc_copy.proof = None; - let message = serde_json::to_string(&vc_copy).unwrap(); - - for proof in proofs { - let sig_multibase = proof.proof_value.unwrap(); - match &keypair { - KeyType::Ed25519(keypair) => { - let (_base, sig) = multibase::decode(sig_multibase).unwrap(); - let verify = keypair.verify_signature(&message.as_bytes(), &sig); - if !verify { - return Ok(false); - } - } - KeyType::Sr25519(keypair) => { - let (_base, sig) = multibase::decode(sig_multibase).unwrap(); - let verify = keypair.verify_signature(&message.as_bytes(), &sig).unwrap(); - if !verify { - return Ok(false); - } - } - } - } - - Ok(true) - } -} - -#[cfg(test)] -mod tests { - use crate::crypto::{ed25519::Ed25519KeyPair, sr25519::Sr25519KeyPair}; - - use super::*; - - #[test] - fn test_sign_credential_ed25519() { - let secret_key_bytes = [ - 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, - 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, - ]; - let keypair = KeyType::Ed25519(Ed25519KeyPair::from_secret_key_bytes(&secret_key_bytes)); - - let vc_str = r###"{ - "@context": "https://www.w3.org/2018/credentials/v1", - "id": "http://example.org/credentials/3731", - "type": ["VerifiableCredential"], - "issuer": "did:example:foo", - "issuanceDate": "2020-08-19T21:41:50Z", - "credentialSubject": { - "id": "did:example:d23dd687a7dc6787646f2eb98d0" - } - }"###; - let mut vc: Credential = Credential::from_json_unsigned(vc_str).unwrap(); - let proof = vc.generate_proof(&keypair).unwrap(); - vc.add_proof(proof); - println!("{:?}", serde_json::to_string(&vc).unwrap()); - } - - #[test] - fn test_verify_credential_ed25519() { - let public_key_bytes = [ - 184, 96, 68, 197, 81, 228, 13, 193, 222, 132, 170, 137, 194, 220, 242, 118, 87, 164, - 62, 5, 16, 241, 78, 147, 136, 193, 16, 10, 118, 249, 78, 92, - ]; - let keypair = KeyType::Ed25519(Ed25519KeyPair::from_public_key_bytes(&public_key_bytes)); - - let vc_str = r###"{ - "@context":"https://www.w3.org/2018/credentials/v1", - "id":"http://example.org/credentials/3731", - "type":[ - "VerifiableCredential" - ], - "credentialSubject":{ - "id":"did:example:d23dd687a7dc6787646f2eb98d0" - }, - "issuer":"did:example:foo", - "issuanceDate":"2020-08-19T21:41:50Z", - "proof":{ - "type":"Ed25519Signature2018", - "created":"2023-04-18T01:08:19.517433Z", - "proofPurpose":"assertionMethod", - "proofValue":"z2xmAbSm5FaXWhG8kMUb4rZenKx1SVR2R8R6Wdf9tnowBwJk3F4uaXN1Ufiqd2C85hmeXJhp9ScPC64mHCLwqXV2p", - "verificationMethod":"did:infra:01:5GETGN5ksMY586q4EdjQap6YeSbu8tKENJ58Wx3vBkgHs8B2#keys-1" - } - }"###; - - let vc: Credential = Credential::from_json(vc_str).unwrap(); - - assert_eq!(vc.verify(&keypair).unwrap(), true); - } - - #[test] - fn test_sign_credential_sr25519() { - let secret_key_bytes = [ - 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, - 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, - ]; - let keypair = KeyType::Sr25519( - Sr25519KeyPair::from_mini_secret_key_bytes(&secret_key_bytes).unwrap(), - ); - - let vc_str = r###"{ - "@context": "https://www.w3.org/2018/credentials/v1", - "id": "http://example.org/credentials/3731", - "type": ["VerifiableCredential"], - "issuer": "did:example:foo", - "issuanceDate": "2020-08-19T21:41:50Z", - "credentialSubject": { - "id": "did:example:d23dd687a7dc6787646f2eb98d0" - } - }"###; - let mut vc: Credential = Credential::from_json_unsigned(vc_str).unwrap(); - let proof = vc.generate_proof(&keypair).unwrap(); - vc.add_proof(proof); - println!("{:?}", serde_json::to_string(&vc).unwrap()); - } - - #[test] - fn test_verify_credential_sr25519() { - let public_key_bytes = [ - 10, 134, 93, 127, 235, 233, 183, 168, 140, 74, 140, 108, 193, 62, 52, 75, 186, 199, 87, - 11, 57, 197, 167, 7, 79, 249, 198, 238, 217, 121, 191, 22, - ]; - let keypair = - KeyType::Sr25519(Sr25519KeyPair::from_public_key_bytes(&public_key_bytes).unwrap()); - - let vc_str = r###"{ - "@context": "https://www.w3.org/2018/credentials/v1", - "id": "http://example.org/credentials/3731", - "type": [ - "VerifiableCredential" - ], - "credentialSubject": { - "id": "did:example:d23dd687a7dc6787646f2eb98d0" - }, - "issuer": "did:example:foo", - "issuanceDate": "2020-08-19T21:41:50Z", - "proof": { - "type": "Sr25519VerificationKey2020", - "created": "2023-04-19T23:53:37.394517Z", - "proofPurpose": "assertionMethod", - "proofValue": "zHk9DkopSPjHeJiDsVWLNHUMdgbMAvjt7MU9pjKTzi7tcw3eam7guUdiGjRQDjPDreAWaCuJSdhsYWuu2Ki2YZa8", - "verificationMethod": "did:example:foo#keys-1" - } - }"###; - - let vc: Credential = Credential::from_json(vc_str).unwrap(); - - assert_eq!(vc.verify(&keypair).unwrap(), true); - } -} diff --git a/src/verifiable/credential/credential_subject.rs b/src/verifiable/credential/credential_subject.rs deleted file mode 100644 index db17061..0000000 --- a/src/verifiable/credential/credential_subject.rs +++ /dev/null @@ -1,31 +0,0 @@ -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -use super::uri::URI; -use std::collections::HashMap as Map; - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct CredentialSubject { - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(flatten)] - pub property_set: Option>, -} - -impl CredentialSubject { - /// Check if the credential subject is empty - /// - /// An empty credential subject (containing no properties, not even an id property) is - /// considered invalid, as the VC Data Model defines the value of the - /// [credentialSubject](https://www.w3.org/TR/vc-data-model/#credential-subject) property as - /// "a set of objects that contain one or more properties [...]" - pub fn is_empty(&self) -> bool { - self.id.is_none() - && match self.property_set { - Some(ref ps) => ps.is_empty(), - None => true, - } - } -} diff --git a/src/verifiable/credential/errors.rs b/src/verifiable/credential/errors.rs deleted file mode 100644 index b1549cd..0000000 --- a/src/verifiable/credential/errors.rs +++ /dev/null @@ -1,43 +0,0 @@ -use thiserror::Error; - -/// Error type for `ssi`. -#[derive(Error, Debug)] -#[non_exhaustive] -pub enum Error { - #[error("Missing proof")] - MissingProof, - #[error("Missing credential schema")] - MissingCredentialSchema, - #[error("Missing credential")] - MissingCredential, - #[error("Missing presentation")] - MissingPresentation, - #[error("Invalid issuer")] - InvalidIssuer, - #[error("Missing holder property")] - MissingHolder, - #[error("Unsupported Holder Binding")] - UnsupportedHolderBinding, - #[error("Missing issuance date")] - MissingIssuanceDate, - #[error("Missing type VerifiableCredential")] - MissingTypeVerifiableCredential, - #[error("Missing type VerifiablePresentation")] - MissingTypeVerifiablePresentation, - #[error("Invalid subject")] - InvalidSubject, - #[error("Unable to convert date/time")] - TimeError, - #[error("Unsupported verification relationship")] - UnsupportedVerificationRelationship, - #[error("Empty credential subject")] - EmptyCredentialSubject, - #[error(transparent)] - Json(#[from] serde_json::Error), -} - -impl From for String { - fn from(err: Error) -> String { - err.to_string() - } -} diff --git a/src/verifiable/credential/issuer.rs b/src/verifiable/credential/issuer.rs deleted file mode 100644 index eed6673..0000000 --- a/src/verifiable/credential/issuer.rs +++ /dev/null @@ -1,26 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use super::{object_with_id::ObjectWithId, uri::URI}; - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(untagged)] -pub enum Issuer { - URI(URI), - Object(ObjectWithId), -} - -impl Issuer { - /// Return this issuer's id URI - pub fn get_id(&self) -> String { - match self { - Self::URI(uri) => uri.to_string(), - Self::Object(object_with_id) => object_with_id.id.to_string(), - } - } - pub fn get_id_ref(&self) -> &str { - match self { - Self::URI(uri) => uri.as_str(), - Self::Object(object_with_id) => object_with_id.id.as_str(), - } - } -} diff --git a/src/verifiable/credential/mod.rs b/src/verifiable/credential/mod.rs index 7c322f0..38a1610 100644 --- a/src/verifiable/credential/mod.rs +++ b/src/verifiable/credential/mod.rs @@ -1,12 +1,145 @@ -pub mod contexts; -pub mod credential; -pub mod credential_subject; -pub mod errors; -pub mod issuer; -pub mod object_with_id; -pub mod one_or_many; -pub mod proof; -pub mod schema; -pub mod string_or_uri; -pub mod uri; -pub mod vc_date_time; +use ssi::jwk::{Base64urlUInt, OctetParams, Params, JWK}; +use ssi_ldp::{ProofSuite, ProofSuiteType}; +use ssi_vc::{Credential, LinkedDataProofOptions, ProofPurpose, URI}; + +use crate::{crypto::ed25519::Ed25519KeyPair, resolver::resolver::InfraDIDResolver}; + +mod lib; + +pub async fn issue_credential( + did: String, + hex_secret_key: String, + credential_string: String, +) -> String { + let secret_key_bytes = match hex::decode(hex_secret_key) { + Ok(bytes) => bytes, + Err(error) => { + panic!("There was a problem convert secret key bytes: {:?}", error) + } + }; + + let keypair = Ed25519KeyPair::from_secret_key_bytes(&secret_key_bytes); + + let key: JWK = JWK::from(Params::OKP(OctetParams { + curve: "Ed25519".to_string(), + public_key: Base64urlUInt(keypair.to_public_key_bytes().to_vec()), + private_key: Some(Base64urlUInt(keypair.to_secret_key_bytes().to_vec())), + })); + + let mut vc: Credential = Credential::from_json_unsigned(credential_string.as_str()).unwrap(); + + let resolver = InfraDIDResolver::default(); + + let mut context_loader = ssi_json_ld::ContextLoader::default(); + let issue_options: LinkedDataProofOptions = LinkedDataProofOptions { + type_: Some(ProofSuiteType::Ed25519Signature2020), + proof_purpose: Some(ProofPurpose::AssertionMethod), + verification_method: Some(URI::String(did + "#keys-2")), + ..Default::default() + }; + + let proof = ProofSuiteType::Ed25519Signature2020 + .sign( + &vc, + &issue_options, + &resolver, + &mut context_loader, + &key, + None, + ) + .await + .unwrap(); + vc.add_proof(proof); + vc.validate().unwrap(); + + let verification_result = vc.verify(None, &resolver, &mut context_loader).await; + assert!(verification_result.errors.is_empty()); + serde_json::to_string_pretty(&vc).unwrap() +} + +pub async fn verify_credential(did: String, credential_string: String) -> String { + let vc: Credential = Credential::from_json(credential_string.as_str()).unwrap(); + let resolver = InfraDIDResolver::default(); + + let mut context_loader = ssi_json_ld::ContextLoader::default(); + + let options: LinkedDataProofOptions = LinkedDataProofOptions { + proof_purpose: Some(ProofPurpose::AssertionMethod), + verification_method: Some(URI::String(did + "#keys-2")), + ..Default::default() + }; + + let verification_result = vc + .verify(Some(options), &resolver, &mut context_loader) + .await; + if verification_result.errors.is_empty() { + "true".to_string() + } else { + "false".to_string() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[async_std::test] + async fn test_sign_credential_ed25519() { + let did = "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW".to_string(); + let hex_secret_key = + "8006aaa5985f1d72e916167bdcbc663232cef5823209b1246728f73137888170".to_string(); + let vc_str = r###"{ + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "id": "did:infra:space:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", + "type": [ + "VerifiableCredential" + ], + "credentialSubject": [ + { + "id": "did:example:d23dd687a7dc6787646f2eb98d0" + } + ], + "issuanceDate": "2023-04-24T06:08:03.039Z", + "issuer": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW" + }"###; + + let vc = issue_credential(did, hex_secret_key, vc_str.to_string()).await; + println!("{:?}", vc); + } + + #[async_std::test] + async fn test_verify_credential_ed25519() { + let did = "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW".to_string(); + let vc_str = r###"{ + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "id": "did:infra:space:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", + "type": [ + "VerifiableCredential" + ], + "credentialSubject": [ + { + "id": "did:example:d23dd687a7dc6787646f2eb98d0" + } + ], + "issuer": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "issuanceDate": "2023-04-24T06:08:03.039Z", + "proof": { + "@context": [ + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "type": "Ed25519Signature2020", + "proofPurpose": "assertionMethod", + "proofValue": "z3gFJvCvNYTVQJ7R7tXzbmAyZ62g3ZymbzwTrWJhgwatJouope5GnQmz7NW2zAVVYbor5KUW8TUa1V5KADPp8kBog", + "verificationMethod": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", + "created": "2023-04-25T23:52:13.770Z" + } + }"###; + + let verify = verify_credential(did, vc_str.to_string()).await; + assert_eq!(verify, "true".to_string()); + } +} diff --git a/src/verifiable/credential/object_with_id.rs b/src/verifiable/credential/object_with_id.rs deleted file mode 100644 index f53abaa..0000000 --- a/src/verifiable/credential/object_with_id.rs +++ /dev/null @@ -1,14 +0,0 @@ -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::collections::HashMap as Map; - -use super::uri::URI; - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct ObjectWithId { - pub id: URI, - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(flatten)] - pub property_set: Option>, -} diff --git a/src/verifiable/credential/one_or_many.rs b/src/verifiable/credential/one_or_many.rs deleted file mode 100644 index 9912b07..0000000 --- a/src/verifiable/credential/one_or_many.rs +++ /dev/null @@ -1,109 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(untagged)] -pub enum OneOrMany { - One(T), - Many(Vec), -} - -impl OneOrMany { - pub fn any(&self, f: F) -> bool - where - F: Fn(&T) -> bool, - { - match self { - Self::One(value) => f(value), - Self::Many(values) => values.iter().any(f), - } - } - - pub fn len(&self) -> usize { - match self { - Self::One(_) => 1, - Self::Many(values) => values.len(), - } - } - - pub fn is_empty(&self) -> bool { - match self { - Self::One(_) => false, - Self::Many(values) => values.is_empty(), - } - } - - pub fn contains(&self, x: &T) -> bool - where - T: PartialEq, - { - match self { - Self::One(value) => x == value, - Self::Many(values) => values.contains(x), - } - } - - pub fn first(&self) -> Option<&T> { - match self { - Self::One(value) => Some(value), - Self::Many(values) => { - if !values.is_empty() { - Some(&values[0]) - } else { - None - } - } - } - } - - pub fn to_single(&self) -> Option<&T> { - match self { - Self::One(value) => Some(value), - Self::Many(values) => { - if values.len() == 1 { - Some(&values[0]) - } else { - None - } - } - } - } - - pub fn to_single_mut(&mut self) -> Option<&mut T> { - match self { - Self::One(value) => Some(value), - Self::Many(values) => { - if values.len() == 1 { - Some(&mut values[0]) - } else { - None - } - } - } - } -} - -// consuming iterator -impl IntoIterator for OneOrMany { - type Item = T; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - match self { - Self::One(value) => vec![value].into_iter(), - Self::Many(values) => values.into_iter(), - } - } -} - -// non-consuming iterator -impl<'a, T> IntoIterator for &'a OneOrMany { - type Item = &'a T; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - match self { - OneOrMany::One(value) => vec![value].into_iter(), - OneOrMany::Many(values) => values.iter().collect::>().into_iter(), - } - } -} diff --git a/src/verifiable/credential/proof.rs b/src/verifiable/credential/proof.rs deleted file mode 100644 index cfff338..0000000 --- a/src/verifiable/credential/proof.rs +++ /dev/null @@ -1,143 +0,0 @@ -use iref::Iri; -use serde::{Deserialize, Serialize}; -use serde_json::json; -use serde_json::Value; -use static_iref::iri; -use std::str::FromStr; - -use super::errors::Error; -use super::vc_date_time::VCDateTime; - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -// TODO use enum to separate betwen JWS and LD proofs? -// TODO create generics type to allow users to provide their own proof suite that implements ProofSuite -pub struct Proof { - #[serde(rename = "@context")] - // TODO: use consistent types for context - #[serde(default, skip_serializing_if = "Value::is_null")] - pub context: Value, - #[serde(rename = "type")] - pub type_: ProofSuiteType, - #[serde(skip_serializing_if = "Option::is_none")] - pub created: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub proof_purpose: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub proof_value: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub verification_method: Option, -} - -impl Proof { - pub fn new(type_: ProofSuiteType) -> Self { - let expected_utc_now = chrono::Utc::now(); - let vc_date_time_now = VCDateTime::from(expected_utc_now); - - Self { - type_, - context: Value::default(), - created: Some(vc_date_time_now), - proof_purpose: None, - proof_value: None, - verification_method: None, - } - } -} - -/// A [verification relationship](https://w3c.github.io/did-core/#dfn-verification-relationship). -/// -/// The relationship between a [verification method][VerificationMethod] and a DID -/// Subject (as described by a [DID Document][Document]) is considered analogous to a [proof -/// purpose](crate::vc::ProofPurpose). -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(try_from = "String")] -#[serde(rename_all = "camelCase")] -pub enum VerificationRelationship { - AssertionMethod, - Authentication, - KeyAgreement, - ContractAgreement, - CapabilityInvocation, - CapabilityDelegation, -} - -impl Default for VerificationRelationship { - fn default() -> Self { - Self::AssertionMethod - } -} - -impl FromStr for VerificationRelationship { - type Err = Error; - fn from_str(purpose: &str) -> Result { - match purpose { - "authentication" => Ok(Self::Authentication), - "assertionMethod" => Ok(Self::AssertionMethod), - "keyAgreement" => Ok(Self::KeyAgreement), - "contractAgreement" => Ok(Self::ContractAgreement), - "capabilityInvocation" => Ok(Self::CapabilityInvocation), - "capabilityDelegation" => Ok(Self::CapabilityDelegation), - _ => Err(Error::UnsupportedVerificationRelationship), - } - } -} - -impl TryFrom for VerificationRelationship { - type Error = Error; - fn try_from(purpose: String) -> Result { - Self::from_str(&purpose) - } -} - -impl From for String { - fn from(purpose: VerificationRelationship) -> String { - match purpose { - VerificationRelationship::Authentication => "authentication".to_string(), - VerificationRelationship::AssertionMethod => "assertionMethod".to_string(), - VerificationRelationship::KeyAgreement => "keyAgreement".to_string(), - VerificationRelationship::ContractAgreement => "contractAgreement".to_string(), - VerificationRelationship::CapabilityInvocation => "capabilityInvocation".to_string(), - VerificationRelationship::CapabilityDelegation => "capabilityDelegation".to_string(), - } - } -} - -impl VerificationRelationship { - pub fn to_iri(&self) -> Iri<'static> { - match self { - VerificationRelationship::Authentication => { - iri!("https://w3id.org/security#authenticationMethod") - } - VerificationRelationship::AssertionMethod => { - iri!("https://w3id.org/security#assertionMethod") - } - VerificationRelationship::KeyAgreement => { - iri!("https://w3id.org/security#keyAgreementMethod") - } - VerificationRelationship::ContractAgreement => { - iri!("https://w3id.org/security#contractAgreementMethod") - } - VerificationRelationship::CapabilityInvocation => { - iri!("https://w3id.org/security#capabilityInvocationMethod") - } - VerificationRelationship::CapabilityDelegation => { - iri!("https://w3id.org/security#capabilityDelegationMethod") - } - } - } -} - -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] -pub enum ProofSuiteType { - Ed25519Signature2018, - Sr25519Signature2020, -} - -impl FromStr for ProofSuiteType { - type Err = serde_json::Error; - - fn from_str(s: &str) -> Result { - serde_json::from_value(json!(format!("{s}"))) - } -} diff --git a/src/verifiable/credential/schema.rs b/src/verifiable/credential/schema.rs deleted file mode 100644 index 62563ae..0000000 --- a/src/verifiable/credential/schema.rs +++ /dev/null @@ -1,15 +0,0 @@ -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::collections::HashMap as Map; - -use super::uri::URI; - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Schema { - pub id: URI, - #[serde(rename = "type")] - pub type_: String, - #[serde(flatten)] - pub property_set: Option>, -} diff --git a/src/verifiable/credential/string_or_uri.rs b/src/verifiable/credential/string_or_uri.rs deleted file mode 100644 index c2d5d6c..0000000 --- a/src/verifiable/credential/string_or_uri.rs +++ /dev/null @@ -1,53 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use super::uri::{URIParseErr, URI}; - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(untagged)] -#[serde(try_from = "String")] -pub enum StringOrURI { - String(String), - URI(URI), -} - -impl TryFrom for StringOrURI { - type Error = URIParseErr; - fn try_from(string: String) -> Result { - if string.contains(':') { - let uri = URI::try_from(string)?; - Ok(Self::URI(uri)) - } else { - Ok(Self::String(string)) - } - } -} -impl TryFrom<&str> for StringOrURI { - type Error = URIParseErr; - fn try_from(string: &str) -> Result { - string.to_string().try_into() - } -} - -impl From for StringOrURI { - fn from(uri: URI) -> Self { - StringOrURI::URI(uri) - } -} - -impl From for String { - fn from(id: StringOrURI) -> Self { - match id { - StringOrURI::URI(uri) => uri.into(), - StringOrURI::String(s) => s, - } - } -} - -impl StringOrURI { - fn as_str(&self) -> &str { - match self { - StringOrURI::URI(URI::String(string)) => string.as_str(), - StringOrURI::String(string) => string.as_str(), - } - } -} diff --git a/src/verifiable/credential/uri.rs b/src/verifiable/credential/uri.rs deleted file mode 100644 index a48a24d..0000000 --- a/src/verifiable/credential/uri.rs +++ /dev/null @@ -1,57 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::str::FromStr; - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -#[serde(try_from = "String")] -#[serde(untagged)] -pub enum URI { - String(String), -} - -#[derive(thiserror::Error, Debug)] -pub enum URIParseErr { - #[error("Invalid URI: {0}")] - InvalidFormat(String), -} - -impl From for String { - fn from(uri: URI) -> String { - let URI::String(string) = uri; - string - } -} - -impl std::convert::TryFrom for URI { - type Error = URIParseErr; - fn try_from(uri: String) -> Result { - if uri.contains(':') { - Ok(URI::String(uri)) - } else { - Err(URIParseErr::InvalidFormat(uri)) - } - } -} - -impl URI { - /// Return the URI as a string slice - pub fn as_str(&self) -> &str { - match self { - URI::String(string) => string.as_str(), - } - } -} - -impl FromStr for URI { - type Err = URIParseErr; - fn from_str(uri: &str) -> Result { - URI::try_from(String::from(uri)) - } -} - -impl std::fmt::Display for URI { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Self::String(ref string) => write!(f, "{}", string), - } - } -} diff --git a/src/verifiable/credential/vc_date_time.rs b/src/verifiable/credential/vc_date_time.rs deleted file mode 100644 index e8a4937..0000000 --- a/src/verifiable/credential/vc_date_time.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::str::FromStr; - -use chrono::{DateTime, FixedOffset}; -use serde::{Deserialize, Serialize}; - -/// RFC3339 date-time as used in VC Data Model -/// -/// -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -#[serde(try_from = "String")] -#[serde(into = "String")] -pub struct VCDateTime { - /// The date-time - date_time: DateTime, - /// Whether to use "Z" or "+00:00" when formatting the date-time in UTC - use_z: bool, -} - -impl FromStr for VCDateTime { - type Err = chrono::format::ParseError; - fn from_str(date_time: &str) -> Result { - let use_z = date_time.ends_with('Z'); - let date_time = DateTime::parse_from_rfc3339(date_time)?; - Ok(VCDateTime { date_time, use_z }) - } -} - -impl TryFrom for VCDateTime { - type Error = chrono::format::ParseError; - fn try_from(date_time: String) -> Result { - Self::from_str(&date_time) - } -} - -impl From for String { - fn from(z_date_time: VCDateTime) -> String { - let VCDateTime { date_time, use_z } = z_date_time; - date_time.to_rfc3339_opts(chrono::SecondsFormat::AutoSi, use_z) - } -} - -impl From> for VCDateTime -where - chrono::DateTime: From>, -{ - fn from(date_time: DateTime) -> Self { - Self { - date_time: date_time.into(), - use_z: true, - } - } -} - -impl From for DateTime -where - chrono::DateTime: From>, -{ - fn from(vc_date_time: VCDateTime) -> Self { - Self::from(vc_date_time.date_time) - } -} diff --git a/src/verifiable/mod.rs b/src/verifiable/mod.rs index 1420934..510c0f2 100644 --- a/src/verifiable/mod.rs +++ b/src/verifiable/mod.rs @@ -1,2 +1 @@ pub mod credential; -pub mod presentation; diff --git a/src/verifiable/presentation/credential_or_jwt.rs b/src/verifiable/presentation/credential_or_jwt.rs deleted file mode 100644 index b0dc1d6..0000000 --- a/src/verifiable/presentation/credential_or_jwt.rs +++ /dev/null @@ -1,11 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::verifiable::credential::credential::Credential; - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(untagged)] -#[allow(clippy::large_enum_variant)] -pub enum CredentialOrJWT { - Credential(Credential), - // JWT(String), -} diff --git a/src/verifiable/presentation/holder.rs b/src/verifiable/presentation/holder.rs deleted file mode 100644 index 8284407..0000000 --- a/src/verifiable/presentation/holder.rs +++ /dev/null @@ -1,26 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::verifiable::credential::{object_with_id::ObjectWithId, uri::URI}; - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(untagged)] -pub enum Holder { - URI(URI), - Object(ObjectWithId), -} - -impl Holder { - /// Return this holder's id URI - pub fn get_id(&self) -> String { - match self { - Self::URI(uri) => uri.to_string(), - Self::Object(object_with_id) => object_with_id.id.to_string(), - } - } - pub fn get_id_ref(&self) -> &str { - match self { - Self::URI(uri) => uri.as_str(), - Self::Object(object_with_id) => object_with_id.id.as_str(), - } - } -} diff --git a/src/verifiable/presentation/mod.rs b/src/verifiable/presentation/mod.rs deleted file mode 100644 index 2620e5e..0000000 --- a/src/verifiable/presentation/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod credential_or_jwt; -pub mod holder; -pub mod presentation; diff --git a/src/verifiable/presentation/presentation.rs b/src/verifiable/presentation/presentation.rs deleted file mode 100644 index c5787bd..0000000 --- a/src/verifiable/presentation/presentation.rs +++ /dev/null @@ -1,343 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::{ - crypto::keytype::KeyType, - verifiable::credential::{ - contexts::Contexts, - errors::Error, - one_or_many::OneOrMany, - proof::{Proof, ProofSuiteType, VerificationRelationship}, - string_or_uri::StringOrURI, - }, -}; - -use super::{credential_or_jwt::CredentialOrJWT, holder::Holder}; - -pub const DEFAULT_CONTEXT: &str = "https://www.w3.org/2018/credentials/v1"; - -// work around https://github.com/w3c/vc-test-suite/issues/103 -pub const ALT_DEFAULT_CONTEXT: &str = "https://w3.org/2018/credentials/v1"; - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Presentation { - #[serde(rename = "@context")] - pub context: Contexts, - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - #[serde(rename = "type")] - pub type_: OneOrMany, - #[serde(skip_serializing_if = "Option::is_none")] - pub verifiable_credential: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub proof: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub holder: Option, -} - -impl Presentation { - pub fn from_json(s: &str) -> Result { - let vp: Self = serde_json::from_str(s)?; - vp.validate()?; - Ok(vp) - } - - pub fn from_json_unsigned(s: &str) -> Result { - let vp: Self = serde_json::from_str(s)?; - vp.validate_unsigned()?; - Ok(vp) - } - - pub fn validate_unsigned(&self) -> Result<(), Error> { - if !self.type_.contains(&"VerifiablePresentation".to_string()) { - return Err(Error::MissingTypeVerifiablePresentation); - } - - for ref vc in self.verifiable_credential.iter().flatten() { - match vc { - CredentialOrJWT::Credential(vc) => { - vc.validate_unsigned_embedded()?; - } - }; - } - Ok(()) - } - - pub fn validate(&self) -> Result<(), Error> { - self.validate_unsigned()?; - - if self.proof.is_none() { - return Err(Error::MissingProof); - } - - Ok(()) - } - - pub fn generate_proof(&self, keypair: KeyType) -> Result { - let message = serde_json::to_string(&self).unwrap(); - let holder = self.holder.as_ref().unwrap().get_id_ref().clone(); - - match keypair { - KeyType::Ed25519(keypair) => { - let signature = keypair.sign(&message.as_bytes()); - let sig_multibase = multibase::encode(multibase::Base::Base58Btc, signature); - - let mut proof: Proof = Proof::new(ProofSuiteType::Ed25519Signature2018); - proof.proof_purpose = Some(VerificationRelationship::AssertionMethod); - proof.verification_method = Some(holder.to_string().to_owned() + "#keys-1"); - proof.proof_value = Some(sig_multibase); - Ok(proof) - } - KeyType::Sr25519(keypair) => { - let signature = keypair.sign(&message.as_bytes()); - let sig_multibase = multibase::encode(multibase::Base::Base58Btc, signature); - - let mut proof: Proof = Proof::new(ProofSuiteType::Sr25519Signature2020); - proof.proof_purpose = Some(VerificationRelationship::AssertionMethod); - proof.verification_method = Some(holder.to_string().to_owned() + "#keys-1"); - proof.proof_value = Some(sig_multibase); - Ok(proof) - } - } - } - - pub fn add_proof(&mut self, proof: Proof) { - self.proof = match self.proof.take() { - None => Some(OneOrMany::One(proof)), - Some(OneOrMany::One(existing_proof)) => { - Some(OneOrMany::Many(vec![existing_proof, proof])) - } - Some(OneOrMany::Many(mut proofs)) => { - proofs.push(proof); - Some(OneOrMany::Many(proofs)) - } - } - } - - pub fn verify(&self, keypair: &KeyType) -> Result { - if self.proof.is_none() { - return Err(Error::MissingProof); - } - - let mut vp_copy = self.clone(); - let proofs = vp_copy.proof.take().unwrap(); - vp_copy.proof = None; - let message = serde_json::to_string(&vp_copy).unwrap(); - - let vcs = vp_copy.verifiable_credential.take().unwrap(); - - for vc in vcs { - match vc { - CredentialOrJWT::Credential(vc) => { - let verify = vc.verify(&keypair)?; - if !verify { - return Ok(false); - } - } - }; - } - - for proof in proofs { - let sig_multibase = proof.proof_value.unwrap(); - match &keypair { - KeyType::Ed25519(keypair) => { - let (_base, sig) = multibase::decode(sig_multibase).unwrap(); - let verify = keypair.verify_signature(&message.as_bytes(), &sig); - if !verify { - return Ok(false); - } - } - KeyType::Sr25519(keypair) => { - let (_base, sig) = multibase::decode(sig_multibase).unwrap(); - let verify = keypair.verify_signature(&message.as_bytes(), &sig).unwrap(); - if !verify { - return Ok(false); - } - } - } - } - - Ok(true) - } -} - -#[cfg(test)] -mod tests { - use crate::{ - crypto::{ed25519::Ed25519KeyPair, sr25519::Sr25519KeyPair}, - verifiable::credential::{contexts::Context, credential::Credential, uri::URI}, - }; - - use super::*; - - #[test] - fn test_sign_presentation_ed25519() { - let secret_key_bytes = [ - 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, - 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, - ]; - let keypair = KeyType::Ed25519(Ed25519KeyPair::from_secret_key_bytes(&secret_key_bytes)); - - let vc_str = r###"{ - "@context": "https://www.w3.org/2018/credentials/v1", - "id": "http://example.org/credentials/3731", - "type": ["VerifiableCredential"], - "issuer": "did:example:foo", - "issuanceDate": "2020-08-19T21:41:50Z", - "credentialSubject": { - "id": "did:example:d23dd687a7dc6787646f2eb98d0" - } - }"###; - let mut vc: Credential = Credential::from_json_unsigned(vc_str).unwrap(); - let proof = vc.generate_proof(&keypair).unwrap(); - vc.add_proof(proof); - - let mut vp = Presentation { - context: Contexts::Many(vec![Context::URI(URI::String(DEFAULT_CONTEXT.to_string()))]), - id: Some("http://example.org/presentations/3731".try_into().unwrap()), - type_: OneOrMany::One("VerifiablePresentation".to_string()), - verifiable_credential: Some(OneOrMany::One(CredentialOrJWT::Credential(vc))), - proof: None, - holder: Some(Holder::URI(URI::String("did:example:foo".to_string()))), - }; - - let vp_proof = vp.generate_proof(keypair).unwrap(); - vp.add_proof(vp_proof); - println!("{:?}", serde_json::to_string(&vp).unwrap()); - } - - #[test] - fn test_verify_presentation_ed25519() { - let public_key_bytes = [ - 184, 96, 68, 197, 81, 228, 13, 193, 222, 132, 170, 137, 194, 220, 242, 118, 87, 164, - 62, 5, 16, 241, 78, 147, 136, 193, 16, 10, 118, 249, 78, 92, - ]; - let keypair = KeyType::Ed25519(Ed25519KeyPair::from_public_key_bytes(&public_key_bytes)); - - let vp_str = r###"{ - "@context": [ - "https://www.w3.org/2018/credentials/v1" - ], - "id": "http://example.org/presentations/3731", - "type": "VerifiablePresentation", - "verifiableCredential": { - "@context": "https://www.w3.org/2018/credentials/v1", - "id": "http://example.org/credentials/3731", - "type": [ - "VerifiableCredential" - ], - "credentialSubject": { - "id": "did:example:d23dd687a7dc6787646f2eb98d0" - }, - "issuer": "did:example:foo", - "issuanceDate": "2020-08-19T21:41:50Z", - "proof": { - "type": "Ed25519Signature2018", - "created": "2023-04-20T00:34:38.630560Z", - "proofPurpose": "assertionMethod", - "proofValue": "z2xmAbSm5FaXWhG8kMUb4rZenKx1SVR2R8R6Wdf9tnowBwJk3F4uaXN1Ufiqd2C85hmeXJhp9ScPC64mHCLwqXV2p", - "verificationMethod": "did:example:foo#keys-1" - } - }, - "proof": { - "type": "Ed25519Signature2018", - "created": "2023-04-20T00:34:38.630648Z", - "proofPurpose": "assertionMethod", - "proofValue": "z45VdxhaYSg4FtgdfJcaw4D44tSTZwCmJM5aKccj6qNLNCAuuLPc6nMEGvHwqYkvEUCZbHZX4eyyzfDxqt2ji1mKq", - "verificationMethod": "did:example:foo#keys-1" - }, - "holder": "did:example:foo" - }"###; - - let vp: Presentation = Presentation::from_json(vp_str).unwrap(); - - assert_eq!(vp.verify(&keypair).unwrap(), true); - } - - #[test] - fn test_sign_presentation_sr25519() { - let secret_key_bytes = [ - 203, 83, 75, 248, 221, 21, 169, 1, 238, 68, 44, 174, 81, 11, 36, 111, 94, 148, 36, 125, - 115, 87, 11, 234, 71, 224, 170, 133, 153, 89, 196, 18, - ]; - let keypair = KeyType::Sr25519( - Sr25519KeyPair::from_mini_secret_key_bytes(&secret_key_bytes).unwrap(), - ); - - let vc_str = r###"{ - "@context": "https://www.w3.org/2018/credentials/v1", - "id": "http://example.org/credentials/3731", - "type": ["VerifiableCredential"], - "issuer": "did:example:foo", - "issuanceDate": "2020-08-19T21:41:50Z", - "credentialSubject": { - "id": "did:example:d23dd687a7dc6787646f2eb98d0" - } - }"###; - let mut vc: Credential = Credential::from_json_unsigned(vc_str).unwrap(); - let proof = vc.generate_proof(&keypair).unwrap(); - vc.add_proof(proof); - - let mut vp = Presentation { - context: Contexts::Many(vec![Context::URI(URI::String(DEFAULT_CONTEXT.to_string()))]), - id: Some("http://example.org/presentations/3731".try_into().unwrap()), - type_: OneOrMany::One("VerifiablePresentation".to_string()), - verifiable_credential: Some(OneOrMany::One(CredentialOrJWT::Credential(vc))), - proof: None, - holder: Some(Holder::URI(URI::String("did:example:foo".to_string()))), - }; - - let vp_proof = vp.generate_proof(keypair).unwrap(); - vp.add_proof(vp_proof); - println!("{:?}", serde_json::to_string(&vp).unwrap()); - } - - #[test] - fn test_verify_presentation_sr25519() { - let public_key_bytes = [ - 10, 134, 93, 127, 235, 233, 183, 168, 140, 74, 140, 108, 193, 62, 52, 75, 186, 199, 87, - 11, 57, 197, 167, 7, 79, 249, 198, 238, 217, 121, 191, 22, - ]; - let keypair = - KeyType::Sr25519(Sr25519KeyPair::from_public_key_bytes(&public_key_bytes).unwrap()); - - let vp_str = r###"{ - "@context": [ - "https://www.w3.org/2018/credentials/v1" - ], - "id": "http://example.org/presentations/3731", - "type": "VerifiablePresentation", - "verifiableCredential": { - "@context": "https://www.w3.org/2018/credentials/v1", - "id": "http://example.org/credentials/3731", - "type": [ - "VerifiableCredential" - ], - "credentialSubject": { - "id": "did:example:d23dd687a7dc6787646f2eb98d0" - }, - "issuer": "did:example:foo", - "issuanceDate": "2020-08-19T21:41:50Z", - "proof": { - "type": "Sr25519VerificationKey2020", - "created": "2023-04-20T00:36:34.144078Z", - "proofPurpose": "assertionMethod", - "proofValue": "z3cJ7r7iDM9vjTG3Z3F7Q5kJEUihrj9EL4Nj4Ms4rAC5M4UWi6zQfZ5iG8YjAqgvULGxD9YH3kV26knR3Uk2Ds1YQ", - "verificationMethod": "did:example:foo#keys-1" - } - }, - "proof": { - "type": "Sr25519VerificationKey2020", - "created": "2023-04-20T00:36:34.144147Z", - "proofPurpose": "assertionMethod", - "proofValue": "zCz4tUxJc6PF5GdLGweaxxYgYWGLYhCcAEytGcmQkx2cdWtnmoGYeznwjRdBw7RhvCyQ7H41oFKwE9zRfVoyfPYu", - "verificationMethod": "did:example:foo#keys-1" - }, - "holder": "did:example:foo" - }"###; - - let vp: Presentation = Presentation::from_json(vp_str).unwrap(); - - assert_eq!(vp.verify(&keypair).unwrap(), true); - } -} From 9b17674158e5a99be6a7ad010c2b618d49465cc2 Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Wed, 26 Apr 2023 09:50:14 +0900 Subject: [PATCH 17/27] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20presentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/verifiable/credential/mod.rs | 2 - src/verifiable/mod.rs | 1 + src/verifiable/presentation/mod.rs | 200 +++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 src/verifiable/presentation/mod.rs diff --git a/src/verifiable/credential/mod.rs b/src/verifiable/credential/mod.rs index 38a1610..0643f72 100644 --- a/src/verifiable/credential/mod.rs +++ b/src/verifiable/credential/mod.rs @@ -4,8 +4,6 @@ use ssi_vc::{Credential, LinkedDataProofOptions, ProofPurpose, URI}; use crate::{crypto::ed25519::Ed25519KeyPair, resolver::resolver::InfraDIDResolver}; -mod lib; - pub async fn issue_credential( did: String, hex_secret_key: String, diff --git a/src/verifiable/mod.rs b/src/verifiable/mod.rs index 510c0f2..1420934 100644 --- a/src/verifiable/mod.rs +++ b/src/verifiable/mod.rs @@ -1 +1,2 @@ pub mod credential; +pub mod presentation; diff --git a/src/verifiable/presentation/mod.rs b/src/verifiable/presentation/mod.rs new file mode 100644 index 0000000..a50c098 --- /dev/null +++ b/src/verifiable/presentation/mod.rs @@ -0,0 +1,200 @@ +use ssi::jwk::{Base64urlUInt, OctetParams, Params, JWK}; +use ssi_ldp::ProofSuiteType; +use ssi_vc::{ + Credential, CredentialOrJWT, LinkedDataProofOptions, OneOrMany, Presentation, ProofPurpose, + StringOrURI, DEFAULT_CONTEXT, URI, +}; + +use crate::{ + crypto::ed25519::Ed25519KeyPair, did::random_phrase, resolver::resolver::InfraDIDResolver, +}; + +pub async fn issue_presentation( + did: String, + hex_secret_key: String, + credential_string: String, +) -> String { + let secret_key_bytes = match hex::decode(hex_secret_key) { + Ok(bytes) => bytes, + Err(error) => { + panic!("There was a problem convert secret key bytes: {:?}", error) + } + }; + + let keypair = Ed25519KeyPair::from_secret_key_bytes(&secret_key_bytes); + + let key: JWK = JWK::from(Params::OKP(OctetParams { + curve: "Ed25519".to_string(), + public_key: Base64urlUInt(keypair.to_public_key_bytes().to_vec()), + private_key: Some(Base64urlUInt(keypair.to_secret_key_bytes().to_vec())), + })); + + let vc: Credential = Credential::from_json(credential_string.as_str()).unwrap(); + + let resolver = InfraDIDResolver::default(); + + let id = { + let mnemonic = random_phrase(12); + let keypair: Ed25519KeyPair = + Ed25519KeyPair::from_bip39_phrase(mnemonic.as_str(), Some("")); + let address = keypair.ss58_address(42); + let did = format!("did:infra:{}:{}", "01", address.clone()); + did + }; + + let mut vp = Presentation { + context: ssi_vc::Contexts::Many(vec![ssi_vc::Context::URI(ssi_vc::URI::String( + DEFAULT_CONTEXT.to_string(), + ))]), + id: Some(StringOrURI::String(id.to_string())), + type_: OneOrMany::One("VerifiablePresentation".to_string()), + verifiable_credential: Some(OneOrMany::One(CredentialOrJWT::Credential(vc))), + proof: None, + holder: Some(URI::String(did.to_string())), + property_set: None, + holder_binding: None, + }; + + let vp_issue_options: LinkedDataProofOptions = LinkedDataProofOptions { + type_: Some(ProofSuiteType::Ed25519Signature2020), + proof_purpose: Some(ProofPurpose::AssertionMethod), + verification_method: Some(URI::String(did + "#keys-2")), + ..Default::default() + }; + + let mut context_loader = ssi_json_ld::ContextLoader::default(); + + let vp_proof = vp + .generate_proof(&key, &vp_issue_options, &resolver, &mut context_loader) + .await + .unwrap(); + vp.add_proof(vp_proof); + vp.validate().unwrap(); + + let vp_verification_result = vp + .verify( + Some(vp_issue_options.clone()), + &resolver, + &mut context_loader, + ) + .await; + assert!(vp_verification_result.errors.is_empty()); + + serde_json::to_string_pretty(&vp).unwrap() +} + +pub async fn verify_presentation(did: String, presentation_string: String) -> String { + let vp: Presentation = Presentation::from_json(presentation_string.as_str()).unwrap(); + let resolver = InfraDIDResolver::default(); + + let mut context_loader = ssi_json_ld::ContextLoader::default(); + + let options: LinkedDataProofOptions = LinkedDataProofOptions { + proof_purpose: Some(ProofPurpose::AssertionMethod), + verification_method: Some(URI::String(did + "#keys-2")), + ..Default::default() + }; + + let vp_verification_result = vp + .verify(Some(options), &resolver, &mut context_loader) + .await; + + if vp_verification_result.errors.is_empty() { + "true".to_string() + } else { + "false".to_string() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[async_std::test] + async fn test_sign_presentation_ed25519() { + let did = "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW".to_string(); + let hex_secret_key = + "8006aaa5985f1d72e916167bdcbc663232cef5823209b1246728f73137888170".to_string(); + let vc_str = r###"{ + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "id": "did:infra:space:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", + "type": [ + "VerifiableCredential" + ], + "credentialSubject": [ + { + "id": "did:example:d23dd687a7dc6787646f2eb98d0" + } + ], + "issuer": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "issuanceDate": "2023-04-24T06:08:03.039Z", + "proof": { + "@context": [ + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "type": "Ed25519Signature2020", + "proofPurpose": "assertionMethod", + "proofValue": "z3gFJvCvNYTVQJ7R7tXzbmAyZ62g3ZymbzwTrWJhgwatJouope5GnQmz7NW2zAVVYbor5KUW8TUa1V5KADPp8kBog", + "verificationMethod": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", + "created": "2023-04-25T23:52:13.770Z" + } + }"###; + + let vc = issue_presentation(did, hex_secret_key, vc_str.to_string()).await; + println!("{:?}", vc); + } + + #[async_std::test] + async fn test_verify_presentation_ed25519() { + let did = "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW".to_string(); + let vc_str = r###"{ + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "id": "did:infra:01:5C65pZF9tBxUgRarjqNQRLUYaxhjDatawtJsPUFLsv8HrRDs", + "type": "VerifiablePresentation", + "verifiableCredential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "id": "did:infra:space:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", + "type": [ + "VerifiableCredential" + ], + "credentialSubject": [ + { + "id": "did:example:d23dd687a7dc6787646f2eb98d0" + } + ], + "issuer": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "issuanceDate": "2023-04-24T06:08:03.039Z", + "proof": { + "@context": [ + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "type": "Ed25519Signature2020", + "proofPurpose": "assertionMethod", + "proofValue": "z3gFJvCvNYTVQJ7R7tXzbmAyZ62g3ZymbzwTrWJhgwatJouope5GnQmz7NW2zAVVYbor5KUW8TUa1V5KADPp8kBog", + "verificationMethod": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", + "created": "2023-04-25T23:52:13.770Z" + } + }, + "proof": { + "@context": [ + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "type": "Ed25519Signature2020", + "proofPurpose": "assertionMethod", + "proofValue": "z5fwTSAD25dFq62rYCzeNUD5TfYwpgSB7HLa9eLtDMpPVB2hb83rbvDmydwPmwubiswWYvcZVmxqG34GxkkLkypDY", + "verificationMethod": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", + "created": "2023-04-26T00:21:22.810Z" + }, + "holder": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW" + }"###; + + let verify = verify_presentation(did, vc_str.to_string()).await; + assert_eq!(verify, "true".to_string()); + } +} From 6591829023d44bcc31220cf38e153aed238e6003 Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Wed, 26 Apr 2023 10:24:11 +0900 Subject: [PATCH 18/27] =?UTF-8?q?ci:=20=F0=9F=8E=A1=20add=20cargo=20ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cargo.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/cargo.yaml diff --git a/.github/workflows/cargo.yaml b/.github/workflows/cargo.yaml new file mode 100644 index 0000000..45396e4 --- /dev/null +++ b/.github/workflows/cargo.yaml @@ -0,0 +1,15 @@ +on: [push] + +name: CI + +jobs: + build_and_test: + name: Rust project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + - run: cargo build --release + - run: cargo test \ No newline at end of file From 6f614331f384d569e155e34c1d41b7db1f1e1588 Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Wed, 26 Apr 2023 11:03:58 +0900 Subject: [PATCH 19/27] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20parameter=20i?= =?UTF-8?q?n=20verifying=20credential=20and=20presentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/verifiable/credential/mod.rs | 8 ++++---- src/verifiable/presentation/mod.rs | 11 ++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/verifiable/credential/mod.rs b/src/verifiable/credential/mod.rs index 0643f72..512eb78 100644 --- a/src/verifiable/credential/mod.rs +++ b/src/verifiable/credential/mod.rs @@ -55,15 +55,16 @@ pub async fn issue_credential( serde_json::to_string_pretty(&vc).unwrap() } -pub async fn verify_credential(did: String, credential_string: String) -> String { +pub async fn verify_credential(credential_string: String) -> String { let vc: Credential = Credential::from_json(credential_string.as_str()).unwrap(); + let issuer = vc.clone().issuer.unwrap(); let resolver = InfraDIDResolver::default(); let mut context_loader = ssi_json_ld::ContextLoader::default(); let options: LinkedDataProofOptions = LinkedDataProofOptions { proof_purpose: Some(ProofPurpose::AssertionMethod), - verification_method: Some(URI::String(did + "#keys-2")), + verification_method: Some(URI::String(issuer.get_id() + "#keys-2")), ..Default::default() }; @@ -109,7 +110,6 @@ mod tests { #[async_std::test] async fn test_verify_credential_ed25519() { - let did = "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW".to_string(); let vc_str = r###"{ "@context": [ "https://www.w3.org/2018/credentials/v1" @@ -137,7 +137,7 @@ mod tests { } }"###; - let verify = verify_credential(did, vc_str.to_string()).await; + let verify = verify_credential(vc_str.to_string()).await; assert_eq!(verify, "true".to_string()); } } diff --git a/src/verifiable/presentation/mod.rs b/src/verifiable/presentation/mod.rs index a50c098..8d19de0 100644 --- a/src/verifiable/presentation/mod.rs +++ b/src/verifiable/presentation/mod.rs @@ -83,15 +83,17 @@ pub async fn issue_presentation( serde_json::to_string_pretty(&vp).unwrap() } -pub async fn verify_presentation(did: String, presentation_string: String) -> String { +pub async fn verify_presentation(presentation_string: String) -> String { let vp: Presentation = Presentation::from_json(presentation_string.as_str()).unwrap(); + let holder = vp.clone().holder.unwrap(); + let resolver = InfraDIDResolver::default(); let mut context_loader = ssi_json_ld::ContextLoader::default(); let options: LinkedDataProofOptions = LinkedDataProofOptions { proof_purpose: Some(ProofPurpose::AssertionMethod), - verification_method: Some(URI::String(did + "#keys-2")), + verification_method: Some(URI::String(holder.as_str().to_string() + "#keys-2")), ..Default::default() }; @@ -148,8 +150,7 @@ mod tests { #[async_std::test] async fn test_verify_presentation_ed25519() { - let did = "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW".to_string(); - let vc_str = r###"{ + let vp_str = r###"{ "@context": [ "https://www.w3.org/2018/credentials/v1" ], @@ -194,7 +195,7 @@ mod tests { "holder": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW" }"###; - let verify = verify_presentation(did, vc_str.to_string()).await; + let verify = verify_presentation(vp_str.to_string()).await; assert_eq!(verify, "true".to_string()); } } From 5bbd44ba28c62c3a69ddcf67fda50f5c7d2e563b Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Thu, 27 Apr 2023 10:05:30 +0900 Subject: [PATCH 20/27] =?UTF-8?q?fix:=20=F0=9F=90=9B=20remove=20panics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/verifiable/credential/mod.rs | 7 +------ src/verifiable/presentation/mod.rs | 7 +------ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/verifiable/credential/mod.rs b/src/verifiable/credential/mod.rs index 512eb78..28abda1 100644 --- a/src/verifiable/credential/mod.rs +++ b/src/verifiable/credential/mod.rs @@ -9,12 +9,7 @@ pub async fn issue_credential( hex_secret_key: String, credential_string: String, ) -> String { - let secret_key_bytes = match hex::decode(hex_secret_key) { - Ok(bytes) => bytes, - Err(error) => { - panic!("There was a problem convert secret key bytes: {:?}", error) - } - }; + let secret_key_bytes = hex::decode(hex_secret_key).unwrap(); let keypair = Ed25519KeyPair::from_secret_key_bytes(&secret_key_bytes); diff --git a/src/verifiable/presentation/mod.rs b/src/verifiable/presentation/mod.rs index 8d19de0..9af8a22 100644 --- a/src/verifiable/presentation/mod.rs +++ b/src/verifiable/presentation/mod.rs @@ -14,12 +14,7 @@ pub async fn issue_presentation( hex_secret_key: String, credential_string: String, ) -> String { - let secret_key_bytes = match hex::decode(hex_secret_key) { - Ok(bytes) => bytes, - Err(error) => { - panic!("There was a problem convert secret key bytes: {:?}", error) - } - }; + let secret_key_bytes = hex::decode(hex_secret_key).unwrap(); let keypair = Ed25519KeyPair::from_secret_key_bytes(&secret_key_bytes); From 8129989526f7d1abfd563f5387aa51f81b332dd1 Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Thu, 27 Apr 2023 14:15:58 +0900 Subject: [PATCH 21/27] =?UTF-8?q?fix:=20=F0=9F=90=9B=20change=20return=20t?= =?UTF-8?q?ype=20to=20result=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 1 + src/did/mod.rs | 70 +++++++++++++++--------------- src/error.rs | 66 ++++++++++++++++++++++++++++ src/lib.rs | 3 ++ src/resolver/resolver.rs | 2 +- src/verifiable/credential/mod.rs | 32 +++++++------- src/verifiable/presentation/mod.rs | 33 +++++++------- 7 files changed, 142 insertions(+), 65 deletions(-) create mode 100644 src/error.rs diff --git a/Cargo.toml b/Cargo.toml index e426c0c..c2e55ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,3 +36,4 @@ async-std = { version = "1.9", features = ["attributes"] } async-trait = "0.1.68" serde_urlencoded = "0.7.1" percent-encoding = "2.2.0" +anyhow = "1.0.70" diff --git a/src/did/mod.rs b/src/did/mod.rs index 06b7d09..11f8bb1 100644 --- a/src/did/mod.rs +++ b/src/did/mod.rs @@ -3,7 +3,10 @@ use schnorrkel::ExpansionMode; use serde_json::json; use substrate_bip39::mini_secret_from_entropy; -use crate::crypto::{ed25519::Ed25519KeyPair, sr25519::Sr25519KeyPair}; +use crate::{ + crypto::{ed25519::Ed25519KeyPair, sr25519::Sr25519KeyPair}, + Error, +}; pub enum AddressType { Ed25519, @@ -19,8 +22,8 @@ pub fn random_phrase(words_number: u32) -> String { mnemonic.into_phrase() } -pub fn generate_ss58_did(network_id: String, address_type: AddressType) -> String { - let mnemonic_type = MnemonicType::for_word_count(12).unwrap(); +pub fn generate_ss58_did(network_id: String, address_type: AddressType) -> Result { + let mnemonic_type = MnemonicType::for_word_count(12)?; let mnemonic = Mnemonic::new(mnemonic_type, Language::English); match address_type { @@ -39,24 +42,21 @@ pub fn generate_ss58_did(network_id: String, address_type: AddressType) -> Strin "public_key": hex::encode(keypair.to_public_key_bytes()), "address": address.clone(), "did": did - })); - result.unwrap() + }))?; + Ok(result) } AddressType::Sr25519 => { let keypair_option: Option = Sr25519KeyPair::from_suri(mnemonic.clone().into_phrase().as_str()); - let keypair = match keypair_option { - Some(c) => c, - _ => return "".to_string(), - }; + let keypair = keypair_option.ok_or(Error::InvalidKeypair)?; let address = keypair.ss58_address(42); let did = format!("did:infra:{}:{}", network_id, address.clone()); - let mini_secret_key = mini_secret_from_entropy(mnemonic.entropy(), "").unwrap(); + let mini_secret_key = mini_secret_from_entropy(mnemonic.entropy(), "").ok(); - let secret_key = mini_secret_key; + let secret_key = mini_secret_key.ok_or(Error::InvalidSecretKey)?; let public_key = secret_key.expand_to_public(ExpansionMode::Ed25519); let result = serde_json::to_string(&json!({ @@ -65,8 +65,8 @@ pub fn generate_ss58_did(network_id: String, address_type: AddressType) -> Strin "public_key": hex::encode(public_key.to_bytes()), "address": address.clone(), "did": did - })); - result.unwrap() + }))?; + Ok(result) } } } @@ -75,7 +75,7 @@ pub fn generate_ss58_did_from_phrase( suri: String, network_id: String, address_type: AddressType, -) -> String { +) -> Result { match address_type { AddressType::Ed25519 => { let keypair: Ed25519KeyPair = @@ -90,25 +90,22 @@ pub fn generate_ss58_did_from_phrase( "public_key": hex::encode(keypair.to_public_key_bytes()), "address": address.clone(), "did": did - })); - result.unwrap() + }))?; + Ok(result) } AddressType::Sr25519 => { let keypair_option: Option = Sr25519KeyPair::from_suri(suri.clone().as_str()); - let keypair = match keypair_option { - Some(c) => c, - _ => return "".to_string(), - }; + let keypair: Sr25519KeyPair = keypair_option.ok_or(Error::InvalidKeypair)?; let address = keypair.ss58_address(42); let did = format!("did:infra:{}:{}", network_id, address.clone()); - let mnemonic = Mnemonic::from_phrase(&suri, Language::English).unwrap(); - let mini_secret_key = mini_secret_from_entropy(mnemonic.entropy(), "").unwrap(); + let mnemonic = Mnemonic::from_phrase(&suri, Language::English)?; + let mini_secret_key = mini_secret_from_entropy(mnemonic.entropy(), "").ok(); - let secret_key = mini_secret_key; + let secret_key = mini_secret_key.ok_or(Error::InvalidSecretKey)?; let public_key = secret_key.expand_to_public(ExpansionMode::Ed25519); let result = serde_json::to_string(&json!({ @@ -117,22 +114,22 @@ pub fn generate_ss58_did_from_phrase( "public_key": hex::encode(public_key.to_bytes()), "address": address.clone(), "did": did - })); - result.unwrap() + }))?; + Ok(result) } } } -pub fn did_to_hex_public_key(did: String, address_type: AddressType) -> String { +pub fn did_to_hex_public_key(did: String, address_type: AddressType) -> Result { let splited_did: Vec<&str> = did.split(":").collect(); let address = splited_did[3]; - let decoded_address = bs58::decode(address).into_vec().unwrap(); + let decoded_address = bs58::decode(address).into_vec()?; let public_key_bytes: [u8; 32] = match address_type { AddressType::Ed25519 => { let public_key: ed25519_dalek::PublicKey = - ed25519_dalek::PublicKey::from_bytes(&decoded_address[1..33]).unwrap(); + ed25519_dalek::PublicKey::from_bytes(&decoded_address[1..33])?; public_key.to_bytes() } AddressType::Sr25519 => { @@ -142,12 +139,12 @@ pub fn did_to_hex_public_key(did: String, address_type: AddressType) -> String { } }; - hex::encode(public_key_bytes) + Ok(hex::encode(public_key_bytes)) } -pub fn ss58_address_to_did(address: String, network_id: String) -> String { +pub fn ss58_address_to_did(address: String, network_id: String) -> Result { let did = format!("did:infra:{}:{}", network_id, address); - did + Ok(did) } #[cfg(test)] @@ -189,6 +186,7 @@ mod tests { "01".to_string(), AddressType::Ed25519 ) + .unwrap() ); assert_eq!( @@ -199,6 +197,7 @@ mod tests { "01".to_string(), AddressType::Sr25519 ) + .unwrap() ); } @@ -208,7 +207,8 @@ mod tests { did_to_hex_public_key( "did:infra:01:5GM7RtekqU8cGiS4MKQ7tufoH4Q1itzmoFpVcvcPfjksyPrw".to_string(), AddressType::Ed25519 - ), + ) + .unwrap(), "bd7436a22571207d018ffe83f5dc77d0750b7777f1eb169053d40201d6c68d53".to_string() ); @@ -216,7 +216,8 @@ mod tests { did_to_hex_public_key( "did:infra:01:5Gv8YYFu8H1btvmrJy9FjjAWfb99wrhV3uhPFoNEr918utyR".to_string(), AddressType::Sr25519 - ), + ) + .unwrap(), "d6a3105d6768e956e9e5d41050ac29843f98561410d3a47f9dd5b3b227ab8746".to_string() ); } @@ -227,7 +228,8 @@ mod tests { ss58_address_to_did( "5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL".to_string(), "01".to_string() - ), + ) + .unwrap(), "did:infra:01:5H6PhTQ1ukXBE1pqYVt2BMLjiKD9pqVsoppp2g8eM4EENAfL".to_string() ); } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..60bd640 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,66 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +#[non_exhaustive] +pub enum Error { + #[error(transparent)] + LDP(#[from] ssi_ldp::Error), + #[error(transparent)] + JWS(#[from] ssi_jws::Error), + #[error(transparent)] + DID(#[from] ssi_dids::Error), + #[error(transparent)] + VC(#[from] ssi_vc::Error), + #[error(transparent)] + Hex(#[from] hex::FromHexError), + #[error(transparent)] + Base64(#[from] base64::DecodeError), + #[error(transparent)] + Mnemonic(#[from] anyhow::Error), + #[error(transparent)] + Ed25519(#[from] ed25519_dalek::ed25519::Error), + #[error(transparent)] + Base58(#[from] bs58::decode::Error), + #[error("Invalid DID")] + InvalidDID, + #[error("Invalid Keypair")] + InvalidKeypair, + #[error("Invalid Secret key")] + InvalidSecretKey, + #[error("Invalid Proof")] + InvalidProof, + #[error("Missing proof")] + MissingProof, + #[error("Missing credential schema")] + MissingCredentialSchema, + #[error("Missing credential")] + MissingCredential, + #[error("Missing presentation")] + MissingPresentation, + #[error("Invalid issuer")] + InvalidIssuer, + #[error("Missing holder property")] + MissingHolder, + #[error("Unsupported Holder Binding")] + UnsupportedHolderBinding, + #[error("Missing issuance date")] + MissingIssuanceDate, + #[error("Missing type VerifiableCredential")] + MissingTypeVerifiableCredential, + #[error("Missing type VerifiablePresentation")] + MissingTypeVerifiablePresentation, + #[error("Invalid subject")] + InvalidSubject, + #[error("Unable to convert date/time")] + TimeError, + #[error("Empty credential subject")] + EmptyCredentialSubject, + /// Verification method id does not match JWK id + #[error("Verification method id does not match JWK id. VM id: {0}, JWK key id: {1}")] + KeyIdVMMismatch(String, String), + /// Linked data proof option unencodable as JWT claim + #[error("Linked data proof option unencodable as JWT claim: {0}")] + UnencodableOptionClaim(String), + #[error(transparent)] + Json(#[from] serde_json::Error), +} diff --git a/src/lib.rs b/src/lib.rs index 01fee8d..443c77e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,3 +2,6 @@ pub mod crypto; pub mod did; pub mod resolver; pub mod verifiable; + +pub mod error; +pub use error::Error; diff --git a/src/resolver/resolver.rs b/src/resolver/resolver.rs index 66dd674..830fc72 100644 --- a/src/resolver/resolver.rs +++ b/src/resolver/resolver.rs @@ -43,7 +43,7 @@ impl DIDResolver for InfraDIDResolver { Option, Option, ) { - let hex_public_key = did_to_hex_public_key(did.to_string(), AddressType::Ed25519); + let hex_public_key = did_to_hex_public_key(did.to_string(), AddressType::Ed25519).unwrap(); let public_key_bytes = hex::decode(hex_public_key).unwrap(); let vms = vec![ diff --git a/src/verifiable/credential/mod.rs b/src/verifiable/credential/mod.rs index 28abda1..f74b272 100644 --- a/src/verifiable/credential/mod.rs +++ b/src/verifiable/credential/mod.rs @@ -2,14 +2,14 @@ use ssi::jwk::{Base64urlUInt, OctetParams, Params, JWK}; use ssi_ldp::{ProofSuite, ProofSuiteType}; use ssi_vc::{Credential, LinkedDataProofOptions, ProofPurpose, URI}; -use crate::{crypto::ed25519::Ed25519KeyPair, resolver::resolver::InfraDIDResolver}; +use crate::{crypto::ed25519::Ed25519KeyPair, resolver::resolver::InfraDIDResolver, Error}; pub async fn issue_credential( did: String, hex_secret_key: String, credential_string: String, -) -> String { - let secret_key_bytes = hex::decode(hex_secret_key).unwrap(); +) -> Result { + let secret_key_bytes = hex::decode(hex_secret_key)?; let keypair = Ed25519KeyPair::from_secret_key_bytes(&secret_key_bytes); @@ -19,7 +19,7 @@ pub async fn issue_credential( private_key: Some(Base64urlUInt(keypair.to_secret_key_bytes().to_vec())), })); - let mut vc: Credential = Credential::from_json_unsigned(credential_string.as_str()).unwrap(); + let mut vc: Credential = Credential::from_json_unsigned(credential_string.as_str())?; let resolver = InfraDIDResolver::default(); @@ -40,19 +40,21 @@ pub async fn issue_credential( &key, None, ) - .await - .unwrap(); + .await?; vc.add_proof(proof); - vc.validate().unwrap(); + vc.validate()?; let verification_result = vc.verify(None, &resolver, &mut context_loader).await; - assert!(verification_result.errors.is_empty()); - serde_json::to_string_pretty(&vc).unwrap() + if verification_result.errors.is_empty() { + Ok(serde_json::to_string_pretty(&vc)?) + } else { + Err(Error::InvalidProof) + } } -pub async fn verify_credential(credential_string: String) -> String { - let vc: Credential = Credential::from_json(credential_string.as_str()).unwrap(); - let issuer = vc.clone().issuer.unwrap(); +pub async fn verify_credential(credential_string: String) -> Result { + let vc: Credential = Credential::from_json(credential_string.as_str())?; + let issuer = vc.clone().issuer.ok_or(Error::InvalidIssuer)?; let resolver = InfraDIDResolver::default(); let mut context_loader = ssi_json_ld::ContextLoader::default(); @@ -67,9 +69,9 @@ pub async fn verify_credential(credential_string: String) -> String { .verify(Some(options), &resolver, &mut context_loader) .await; if verification_result.errors.is_empty() { - "true".to_string() + Ok("true".to_string()) } else { - "false".to_string() + Ok("false".to_string()) } } @@ -132,7 +134,7 @@ mod tests { } }"###; - let verify = verify_credential(vc_str.to_string()).await; + let verify = verify_credential(vc_str.to_string()).await.unwrap(); assert_eq!(verify, "true".to_string()); } } diff --git a/src/verifiable/presentation/mod.rs b/src/verifiable/presentation/mod.rs index 9af8a22..5f6ee02 100644 --- a/src/verifiable/presentation/mod.rs +++ b/src/verifiable/presentation/mod.rs @@ -6,15 +6,16 @@ use ssi_vc::{ }; use crate::{ - crypto::ed25519::Ed25519KeyPair, did::random_phrase, resolver::resolver::InfraDIDResolver, + crypto::ed25519::Ed25519KeyPair, did::random_phrase, error::Error, + resolver::resolver::InfraDIDResolver, }; pub async fn issue_presentation( did: String, hex_secret_key: String, credential_string: String, -) -> String { - let secret_key_bytes = hex::decode(hex_secret_key).unwrap(); +) -> Result { + let secret_key_bytes = hex::decode(hex_secret_key)?; let keypair = Ed25519KeyPair::from_secret_key_bytes(&secret_key_bytes); @@ -24,7 +25,7 @@ pub async fn issue_presentation( private_key: Some(Base64urlUInt(keypair.to_secret_key_bytes().to_vec())), })); - let vc: Credential = Credential::from_json(credential_string.as_str()).unwrap(); + let vc: Credential = Credential::from_json(credential_string.as_str())?; let resolver = InfraDIDResolver::default(); @@ -61,10 +62,9 @@ pub async fn issue_presentation( let vp_proof = vp .generate_proof(&key, &vp_issue_options, &resolver, &mut context_loader) - .await - .unwrap(); + .await?; vp.add_proof(vp_proof); - vp.validate().unwrap(); + vp.validate()?; let vp_verification_result = vp .verify( @@ -73,14 +73,17 @@ pub async fn issue_presentation( &mut context_loader, ) .await; - assert!(vp_verification_result.errors.is_empty()); - serde_json::to_string_pretty(&vp).unwrap() + if vp_verification_result.errors.is_empty() { + Ok(serde_json::to_string_pretty(&vp)?) + } else { + Err(Error::InvalidProof) + } } -pub async fn verify_presentation(presentation_string: String) -> String { - let vp: Presentation = Presentation::from_json(presentation_string.as_str()).unwrap(); - let holder = vp.clone().holder.unwrap(); +pub async fn verify_presentation(presentation_string: String) -> Result { + let vp: Presentation = Presentation::from_json(presentation_string.as_str())?; + let holder = vp.clone().holder.ok_or(Error::MissingHolder)?; let resolver = InfraDIDResolver::default(); @@ -97,9 +100,9 @@ pub async fn verify_presentation(presentation_string: String) -> String { .await; if vp_verification_result.errors.is_empty() { - "true".to_string() + Ok("true".to_string()) } else { - "false".to_string() + Ok("false".to_string()) } } @@ -190,7 +193,7 @@ mod tests { "holder": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW" }"###; - let verify = verify_presentation(vp_str.to_string()).await; + let verify: String = verify_presentation(vp_str.to_string()).await.unwrap(); assert_eq!(verify, "true".to_string()); } } From 893ca62a4fc803b9e9ffc651d04323fb302e690b Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Mon, 1 May 2023 09:28:51 +0900 Subject: [PATCH 22/27] =?UTF-8?q?chore:=20=F0=9F=A4=96=20update=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 214 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 202 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index f7c802f..e2f2b04 100644 --- a/README.md +++ b/README.md @@ -13,15 +13,11 @@ - Infra DID Resolver (DIF javascript universal resolver compatible) - https://github.com/InfraBlockchain/infra-did-resolver -Feature provided by infra-did-dart Library : +Feature provided by infra-did-rust Library : -- Infra DID Creation -- update DID attributes (service endpoint) -- update ss58 DID owner key -- revoke ss58 DID -- add/update/remove trusted ss58 DID -- get trusted ss58 DID -- VC/VP creation/verification +- Infra DID Creation (SS58) +- Resolve Infra DID (SS58) +- JSON-LD VC/VP creation/verification ## Installation @@ -31,10 +27,204 @@ Feature provided by infra-did-dart Library : cargo add infra-did ``` -## Feature +### Infra DID Creation -WIP +currently ed25519 curve is supported -## License +```rust + println!("{:?}", generate_ss58_did("01".to_string(), AddressType::Ed25519)); + /* + { + "address":"5FEWTgcxvefibCpoy7rfPx7WKimuWAuRx7JZmhwRTvQosZse","did":"did:infra:01:5FEWTgcxvefibCpoy7rfPx7WKimuWAuRx7JZmhwRTvQosZse", + "mnemonic":"second lucky rifle size spray advance approve view melody carpet offer thumb","private_key":"0177ced8efef49f17fec276d56de1b2037fbcc6348693d22436633043247a942","public_key":"8c2eca839176ba7b2ee50aa8aa1dc406abb89a4ebe2d90fcda489fee29795c94" + } + */ +``` + +### Issuing and Verifying W3C Verifiable Credential (VC), Verifiable Presentation (VP) + +#### DID Resolver + +```rust + let resolver = InfraDIDResolver::default(); + let (_, doc, _) = resolver + .resolve( + "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + &ResolutionInputMetadata::default(), + ) + .await; + println!("{:?}", serde_json::to_string_pretty(&doc).unwrap()); + /* + { + "@context": "https://www.w3.org/ns/did/v1", + "id": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "verificationMethod": [ + { + "id": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", + "type": "Ed25519VerificationKey2018", + "controller": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "publicKeyBase58": "F9JHKboDqg3tK9wnrt8z8xwZRnoZCJAHTdxXVuUMW8z2" + }, + { + "id": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", + "type": "Ed25519VerificationKey2020", + "controller": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "publicKeyMultibase": "z6MktbZKur3fBDYMRenVYT6pz4VZFN5QcBQe9esTLBSNRMmQ" + } + ], + "authentication": [ + "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", + "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2" + ], + "assertionMethod": [ + "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", + "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2" + ] + } + */ +``` + +#### Create and Verify Verifiable Credential JSON-LD + +```rust + let did = "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW".to_string(); + let hex_secret_key = + "8006aaa5985f1d72e916167bdcbc663232cef5823209b1246728f73137888170".to_string(); + let vc_str = r###"{ + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "id": "did:infra:space:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", + "type": [ + "VerifiableCredential" + ], + "credentialSubject": [ + { + "id": "did:example:d23dd687a7dc6787646f2eb98d0" + } + ], + "issuanceDate": "2023-04-24T06:08:03.039Z", + "issuer": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW" + }"###; + + let vc = issue_credential(did, hex_secret_key, vc_str.to_string()).await; + let verify = verify_credential(vc.to_string()).await.unwrap(); + assert_eq!(verify, "true".to_string()); +``` + +Verified Credential Result + +```json + { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "id": "did:infra:space:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", + "type": [ + "VerifiableCredential" + ], + "credentialSubject": [ + { + "id": "did:example:d23dd687a7dc6787646f2eb98d0" + } + ], + "issuer": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "issuanceDate": "2023-04-24T06:08:03.039Z", + "proof": { + "@context": [ + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "type": "Ed25519Signature2020", + "proofPurpose": "assertionMethod", + "proofValue": "z5LPkbsnBbYTAJJ3fcwkEBtbfkT2wnLhLNmcSwj2e8FSYfMrrWoFey6958gm7G93UfTu6qkLkD1nwgzbzSihbu3jw", + "verificationMethod": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", + "created": "2023-04-30T23:53:20.028Z" + } + } +``` + +#### Create and Verify Verifiable Presentation JSON-LD + +```rust + let did = "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW".to_string(); + let hex_secret_key = "8006aaa5985f1d72e916167bdcbc663232cef5823209b1246728f73137888170".to_string(); + let vc_str = r###"{ + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "id": "did:infra:space:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", + "type": [ + "VerifiableCredential" + ], + "credentialSubject": [ + { + "id": "did:example:d23dd687a7dc6787646f2eb98d0" + } + ], + "issuer": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "issuanceDate": "2023-04-24T06:08:03.039Z", + "proof": { + "@context": [ + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "type": "Ed25519Signature2020", + "proofPurpose": "assertionMethod", + "proofValue": "z3gFJvCvNYTVQJ7R7tXzbmAyZ62g3ZymbzwTrWJhgwatJouope5GnQmz7NW2zAVVYbor5KUW8TUa1V5KADPp8kBog", + "verificationMethod": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", + "created": "2023-04-25T23:52:13.770Z" + } + }"###; + + let vp = issue_presentation(did, hex_secret_key, vc_str.to_string()).await; + let verify: String = verify_presentation(vp.to_string()).await.unwrap(); + assert_eq!(verify, "true".to_string()); +``` + +Verified Presentation Result -**Infra-DID-Dart** is under MIT license. See the [LICENSE](https://github.com/InfraBlockchain/infra-did-dart/blob/master/LICENSE) file for more info. \ No newline at end of file +```json + { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "id": "did:infra:01:5D7nQ3WTPtJx79ywCbLum7fyVt1DKcg32jB6SdABhhwpzT9a", + "type": "VerifiablePresentation", + "verifiableCredential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "id": "did:infra:space:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", + "type": [ + "VerifiableCredential" + ], + "credentialSubject": [ + { + "id": "did:example:d23dd687a7dc6787646f2eb98d0" + } + ], + "issuer": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "issuanceDate": "2023-04-24T06:08:03.039Z", + "proof": { + "@context": [ + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "type": "Ed25519Signature2020", + "proofPurpose": "assertionMethod", + "proofValue": "z3gFJvCvNYTVQJ7R7tXzbmAyZ62g3ZymbzwTrWJhgwatJouope5GnQmz7NW2zAVVYbor5KUW8TUa1V5KADPp8kBog", + "verificationMethod": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", + "created": "2023-04-25T23:52:13.770Z" + } + }, + "proof": { + "@context": [ + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "type": "Ed25519Signature2020", + "proofPurpose": "assertionMethod", + "proofValue": "zWPTW2TC7WcEUk1F25saJxHKKt2HjsdSW3GEk12d2mJbUN2dJntEBng9N1RmZz6XuHqNuh7Dq1d4DTpyZ1GEokRq", + "verificationMethod": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", + "created": "2023-05-01T00:03:59.511Z" + }, + "holder": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW" + } +``` \ No newline at end of file From ce1156db9f6a2641118498658a8cabc4392b7072 Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Fri, 21 Jul 2023 09:13:18 +0900 Subject: [PATCH 23/27] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20jsonwebkey?= =?UTF-8?q?=20in=20verification=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/resolver/resolver.rs | 18 ++++++++++++++++++ tests/did-example-foo.json | 26 ++++++++++++++++++++------ tests/did-infra-space.json | 30 ++++++++++++++++-------------- 3 files changed, 54 insertions(+), 20 deletions(-) diff --git a/src/resolver/resolver.rs b/src/resolver/resolver.rs index 830fc72..bed2635 100644 --- a/src/resolver/resolver.rs +++ b/src/resolver/resolver.rs @@ -3,6 +3,7 @@ use serde_json::json; use ssi::did_resolve::{ DIDResolver, DocumentMetadata, ResolutionInputMetadata, ResolutionMetadata, TYPE_DID_LD_JSON, }; +use ssi::jwk::{Base64urlUInt, OctetParams, Params, JWK}; use ssi_dids::{Document, VerificationMethod, VerificationMethodMap, DIDURL}; use crate::did::{did_to_hex_public_key, AddressType}; @@ -46,6 +47,12 @@ impl DIDResolver for InfraDIDResolver { let hex_public_key = did_to_hex_public_key(did.to_string(), AddressType::Ed25519).unwrap(); let public_key_bytes = hex::decode(hex_public_key).unwrap(); + let jwk: JWK = JWK::from(Params::OKP(OctetParams { + curve: "Ed25519".to_string(), + public_key: Base64urlUInt(public_key_bytes.clone()), + private_key: None, + })); + let vms = vec![ VerificationMethod::Map(VerificationMethodMap { id: did.to_string() + "#keys-1", @@ -71,6 +78,13 @@ impl DIDResolver for InfraDIDResolver { .unwrap(), ..Default::default() }), + VerificationMethod::Map(VerificationMethodMap { + id: did.to_string() + "#keys-3", + type_: "JsonWebKey2020".to_string(), + controller: did.to_string(), + public_key_jwk: Some(jwk), + ..Default::default() + }), ]; let vm_urls = vec![ @@ -82,6 +96,10 @@ impl DIDResolver for InfraDIDResolver { did: did.to_string() + "#keys-2", ..Default::default() }), + VerificationMethod::DIDURL(DIDURL { + did: did.to_string() + "#keys-3", + ..Default::default() + }), ]; let doc = Document { diff --git a/tests/did-example-foo.json b/tests/did-example-foo.json index 54ed32e..91f5b88 100644 --- a/tests/did-example-foo.json +++ b/tests/did-example-foo.json @@ -8,29 +8,43 @@ "id": "did:example:foo#keys-1", "type": "Ed25519VerificationKey2018", "controller": "did:example:foo", - "publicKeyBase58": "3mjBnLB2Sp2GGA3YhPHyzZXcShoAwtKaaq5pwgUcDQSe" + "publicKeyBase58": "F9JHKboDqg3tK9wnrt8z8xwZRnoZCJAHTdxXVuUMW8z2" }, { "id": "did:example:foo#keys-2", "type": "Ed25519VerificationKey2020", "controller": "did:example:foo", - "publicKeyMultibase": "z6MkhDzENaRTnMWjNetFNxFpqf5cGH52MmZwGqzkmxSd8dE2" + "publicKeyMultibase": "z6MktbZKur3fBDYMRenVYT6pz4VZFN5QcBQe9esTLBSNRMmQ" + }, + { + "id": "did:example:foo#keys-3", + "type": "JsonWebKey2020", + "controller": "did:example:foo", + "publicKeyJwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "0iPNtvMisdUWHTxJlYR9w0cvQ2NmEQ-2cu4B0x60648" + } } ], "assertionMethod": [ "did:example:foo#keys-1", - "did:example:foo#keys-2" + "did:example:foo#keys-2", + "did:example:foo#keys-3" ], "authentication": [ "did:example:foo#keys-1", - "did:example:foo#keys-2" + "did:example:foo#keys-2", + "did:example:foo#keys-3" ], "capabilityDelegation": [ "did:example:foo#keys-1", - "did:example:foo#keys-2" + "did:example:foo#keys-2", + "did:example:foo#keys-3" ], "capabilityInvocation": [ "did:example:foo#keys-1", - "did:example:foo#keys-2" + "did:example:foo#keys-2", + "did:example:foo#keys-3" ] } \ No newline at end of file diff --git a/tests/did-infra-space.json b/tests/did-infra-space.json index 2aef0c2..69b43e5 100644 --- a/tests/did-infra-space.json +++ b/tests/did-infra-space.json @@ -1,7 +1,5 @@ { - "@context": [ - "https://www.w3.org/ns/did/v1" - ], + "@context": "https://www.w3.org/ns/did/v1", "id": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", "verificationMethod": [ { @@ -15,22 +13,26 @@ "type": "Ed25519VerificationKey2020", "controller": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", "publicKeyMultibase": "z6MktbZKur3fBDYMRenVYT6pz4VZFN5QcBQe9esTLBSNRMmQ" + }, + { + "id": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-3", + "type": "JsonWebKey2020", + "controller": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "publicKeyJwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "0iPNtvMisdUWHTxJlYR9w0cvQ2NmEQ-2cu4B0x60648" + } } ], - "assertionMethod": [ - "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", - "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2" - ], "authentication": [ "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", - "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2" + "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", + "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-3" ], - "capabilityDelegation": [ - "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", - "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2" - ], - "capabilityInvocation": [ + "assertionMethod": [ "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", - "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2" + "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", + "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-3" ] } \ No newline at end of file From ce15461e665fae948247c23730909c33f3638fbd Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Fri, 21 Jul 2023 09:17:52 +0900 Subject: [PATCH 24/27] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20change=20base=20si?= =?UTF-8?q?gnature=20type=20to=20Ed25519Signature2018?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/verifiable/credential/mod.rs | 19 ++++++-------- src/verifiable/presentation/mod.rs | 41 ++++++++++++------------------ 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/src/verifiable/credential/mod.rs b/src/verifiable/credential/mod.rs index f74b272..57b8c08 100644 --- a/src/verifiable/credential/mod.rs +++ b/src/verifiable/credential/mod.rs @@ -25,13 +25,13 @@ pub async fn issue_credential( let mut context_loader = ssi_json_ld::ContextLoader::default(); let issue_options: LinkedDataProofOptions = LinkedDataProofOptions { - type_: Some(ProofSuiteType::Ed25519Signature2020), + type_: Some(ProofSuiteType::Ed25519Signature2018), proof_purpose: Some(ProofPurpose::AssertionMethod), - verification_method: Some(URI::String(did + "#keys-2")), + verification_method: Some(URI::String(did + "#keys-1")), ..Default::default() }; - let proof = ProofSuiteType::Ed25519Signature2020 + let proof = ProofSuiteType::Ed25519Signature2018 .sign( &vc, &issue_options, @@ -61,7 +61,7 @@ pub async fn verify_credential(credential_string: String) -> Result Result Date: Mon, 11 Mar 2024 15:50:17 +0900 Subject: [PATCH 25/27] =?UTF-8?q?chore:=20=F0=9F=A4=96=20update=20dependen?= =?UTF-8?q?cies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c2e55ae..13db738 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,13 +25,13 @@ thiserror = "1.0.40" iref = { version = "2.2.2", features = ["serde"] } static-iref = "2.0.0" multibase = "0.9.1" -ssi-vc = "0.1.1" -ssi = "0.6.0" -ssi-json-ld = "0.2.1" -ssi-ldp = "0.2.0" +ssi-vc = "0.2.1" +ssi = "0.7.0" +ssi-json-ld = "0.2.2" +ssi-ldp = "0.3.2" base64 = "0.12.3" -ssi-jws = "0.1.0" -ssi-dids = { version = "0.1", features = ["example"] } +ssi-jws = "0.1.1" +ssi-dids = { version = "0.1.1", features = ["example"] } async-std = { version = "1.9", features = ["attributes"] } async-trait = "0.1.68" serde_urlencoded = "0.7.1" From 18476e61c936a97a98b228acf16bfd3dfc81c47d Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Wed, 3 Apr 2024 10:18:07 +0900 Subject: [PATCH 26/27] =?UTF-8?q?test:=20=F0=9F=92=8D=20fix=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 50 ++++++++--------- src/resolver/resolver.rs | 4 +- src/verifiable/credential/mod.rs | 32 +++++------ src/verifiable/presentation/mod.rs | 86 +++++++++++++++--------------- tests/did-infra-space.json | 26 ++++----- 5 files changed, 99 insertions(+), 99 deletions(-) diff --git a/README.md b/README.md index e2f2b04..dbcb853 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ currently ed25519 curve is supported let resolver = InfraDIDResolver::default(); let (_, doc, _) = resolver .resolve( - "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", &ResolutionInputMetadata::default(), ) .await; @@ -57,28 +57,28 @@ currently ed25519 curve is supported /* { "@context": "https://www.w3.org/ns/did/v1", - "id": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "id": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", "verificationMethod": [ { - "id": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", + "id": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", "type": "Ed25519VerificationKey2018", - "controller": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "controller": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", "publicKeyBase58": "F9JHKboDqg3tK9wnrt8z8xwZRnoZCJAHTdxXVuUMW8z2" }, { - "id": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", + "id": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", "type": "Ed25519VerificationKey2020", - "controller": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "controller": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", "publicKeyMultibase": "z6MktbZKur3fBDYMRenVYT6pz4VZFN5QcBQe9esTLBSNRMmQ" } ], "authentication": [ - "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", - "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2" + "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", + "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2" ], "assertionMethod": [ - "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", - "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2" + "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", + "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2" ] } */ @@ -87,14 +87,14 @@ currently ed25519 curve is supported #### Create and Verify Verifiable Credential JSON-LD ```rust - let did = "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW".to_string(); + let did = "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW".to_string(); let hex_secret_key = "8006aaa5985f1d72e916167bdcbc663232cef5823209b1246728f73137888170".to_string(); let vc_str = r###"{ "@context": [ "https://www.w3.org/2018/credentials/v1" ], - "id": "did:infra:space:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", + "id": "did:infra:01:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", "type": [ "VerifiableCredential" ], @@ -104,7 +104,7 @@ currently ed25519 curve is supported } ], "issuanceDate": "2023-04-24T06:08:03.039Z", - "issuer": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW" + "issuer": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW" }"###; let vc = issue_credential(did, hex_secret_key, vc_str.to_string()).await; @@ -119,7 +119,7 @@ Verified Credential Result "@context": [ "https://www.w3.org/2018/credentials/v1" ], - "id": "did:infra:space:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", + "id": "did:infra:01:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", "type": [ "VerifiableCredential" ], @@ -128,7 +128,7 @@ Verified Credential Result "id": "did:example:d23dd687a7dc6787646f2eb98d0" } ], - "issuer": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "issuer": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", "issuanceDate": "2023-04-24T06:08:03.039Z", "proof": { "@context": [ @@ -137,7 +137,7 @@ Verified Credential Result "type": "Ed25519Signature2020", "proofPurpose": "assertionMethod", "proofValue": "z5LPkbsnBbYTAJJ3fcwkEBtbfkT2wnLhLNmcSwj2e8FSYfMrrWoFey6958gm7G93UfTu6qkLkD1nwgzbzSihbu3jw", - "verificationMethod": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", + "verificationMethod": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", "created": "2023-04-30T23:53:20.028Z" } } @@ -146,13 +146,13 @@ Verified Credential Result #### Create and Verify Verifiable Presentation JSON-LD ```rust - let did = "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW".to_string(); + let did = "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW".to_string(); let hex_secret_key = "8006aaa5985f1d72e916167bdcbc663232cef5823209b1246728f73137888170".to_string(); let vc_str = r###"{ "@context": [ "https://www.w3.org/2018/credentials/v1" ], - "id": "did:infra:space:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", + "id": "did:infra:01:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", "type": [ "VerifiableCredential" ], @@ -161,7 +161,7 @@ Verified Credential Result "id": "did:example:d23dd687a7dc6787646f2eb98d0" } ], - "issuer": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "issuer": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", "issuanceDate": "2023-04-24T06:08:03.039Z", "proof": { "@context": [ @@ -170,7 +170,7 @@ Verified Credential Result "type": "Ed25519Signature2020", "proofPurpose": "assertionMethod", "proofValue": "z3gFJvCvNYTVQJ7R7tXzbmAyZ62g3ZymbzwTrWJhgwatJouope5GnQmz7NW2zAVVYbor5KUW8TUa1V5KADPp8kBog", - "verificationMethod": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", + "verificationMethod": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", "created": "2023-04-25T23:52:13.770Z" } }"###; @@ -193,7 +193,7 @@ Verified Presentation Result "@context": [ "https://www.w3.org/2018/credentials/v1" ], - "id": "did:infra:space:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", + "id": "did:infra:01:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", "type": [ "VerifiableCredential" ], @@ -202,7 +202,7 @@ Verified Presentation Result "id": "did:example:d23dd687a7dc6787646f2eb98d0" } ], - "issuer": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "issuer": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", "issuanceDate": "2023-04-24T06:08:03.039Z", "proof": { "@context": [ @@ -211,7 +211,7 @@ Verified Presentation Result "type": "Ed25519Signature2020", "proofPurpose": "assertionMethod", "proofValue": "z3gFJvCvNYTVQJ7R7tXzbmAyZ62g3ZymbzwTrWJhgwatJouope5GnQmz7NW2zAVVYbor5KUW8TUa1V5KADPp8kBog", - "verificationMethod": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", + "verificationMethod": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", "created": "2023-04-25T23:52:13.770Z" } }, @@ -222,9 +222,9 @@ Verified Presentation Result "type": "Ed25519Signature2020", "proofPurpose": "assertionMethod", "proofValue": "zWPTW2TC7WcEUk1F25saJxHKKt2HjsdSW3GEk12d2mJbUN2dJntEBng9N1RmZz6XuHqNuh7Dq1d4DTpyZ1GEokRq", - "verificationMethod": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", + "verificationMethod": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", "created": "2023-05-01T00:03:59.511Z" }, - "holder": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW" + "holder": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW" } ``` \ No newline at end of file diff --git a/src/resolver/resolver.rs b/src/resolver/resolver.rs index bed2635..e2c41f6 100644 --- a/src/resolver/resolver.rs +++ b/src/resolver/resolver.rs @@ -156,7 +156,7 @@ impl DIDResolver for TestDIDResolver { ) { let doc_str = match did { "did:example:foo" => DOC_JSON_FOO, - "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW" => DOC_JSON_INFRA, + "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW" => DOC_JSON_INFRA, _ => return (ResolutionMetadata::from_error(ERROR_NOT_FOUND), None, None), }; let doc: Document = match serde_json::from_str(doc_str) { @@ -195,7 +195,7 @@ mod tests { let resolver = InfraDIDResolver::default(); let (_, doc, _) = resolver .resolve( - "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", &ResolutionInputMetadata::default(), ) .await; diff --git a/src/verifiable/credential/mod.rs b/src/verifiable/credential/mod.rs index 57b8c08..7581a7b 100644 --- a/src/verifiable/credential/mod.rs +++ b/src/verifiable/credential/mod.rs @@ -81,14 +81,14 @@ mod tests { #[async_std::test] async fn test_sign_credential_ed25519() { - let did = "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW".to_string(); + let did = "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW".to_string(); let hex_secret_key = "8006aaa5985f1d72e916167bdcbc663232cef5823209b1246728f73137888170".to_string(); let vc_str = r###"{ "@context": [ "https://www.w3.org/2018/credentials/v1" ], - "id": "did:infra:space:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", + "id": "did:infra:01:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", "type": [ "VerifiableCredential" ], @@ -98,7 +98,7 @@ mod tests { } ], "issuanceDate": "2023-04-24T06:08:03.039Z", - "issuer": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW" + "issuer": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW" }"###; let vc = issue_credential(did, hex_secret_key, vc_str.to_string()).await; @@ -109,27 +109,27 @@ mod tests { async fn test_verify_credential_ed25519() { let vc_str = r###"{ "@context": [ - "https://www.w3.org/2018/credentials/v1" + "https://www.w3.org/2018/credentials/v1" ], - "id": "did:infra:space:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", + "id": "did:infra:01:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", "type": [ - "VerifiableCredential" + "VerifiableCredential" ], "credentialSubject": [ - { - "id": "did:example:d23dd687a7dc6787646f2eb98d0" - } + { + "id": "did:example:d23dd687a7dc6787646f2eb98d0" + } ], - "issuer": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "issuer": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", "issuanceDate": "2023-04-24T06:08:03.039Z", "proof": { - "type": "Ed25519Signature2018", - "proofPurpose": "assertionMethod", - "verificationMethod": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", - "created": "2023-07-21T00:14:01.797Z", - "jws": "eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..QlVHquEY_yMSZTKEl7IzgjSz2cV2rykkPlT7ojcf6q7859ErV5reLs1nH_5XMTLVY9LTwQOQsc1a8Lz4RFNbCQ" + "type": "Ed25519Signature2018", + "proofPurpose": "assertionMethod", + "verificationMethod": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", + "created": "2024-04-03T01:13:18.220667Z", + "jws": "eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..VZU_0mj3fD-Nrcq1Zu4r_tqOhQERfI8RMpPeDHX3dQkmTyvOG5AUFtgebrr-wS1RqHIRgvxqIBaSE51dHwUtBA" } - }"###; + }"###; let verify = verify_credential(vc_str.to_string()).await.unwrap(); assert_eq!(verify, "true".to_string()); diff --git a/src/verifiable/presentation/mod.rs b/src/verifiable/presentation/mod.rs index dfc4695..f826539 100644 --- a/src/verifiable/presentation/mod.rs +++ b/src/verifiable/presentation/mod.rs @@ -112,32 +112,32 @@ mod tests { #[async_std::test] async fn test_sign_presentation_ed25519() { - let did = "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW".to_string(); + let did = "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW".to_string(); let hex_secret_key = "8006aaa5985f1d72e916167bdcbc663232cef5823209b1246728f73137888170".to_string(); let vc_str = r###"{ "@context": [ - "https://www.w3.org/2018/credentials/v1" + "https://www.w3.org/2018/credentials/v1" ], - "id": "did:infra:space:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", + "id": "did:infra:01:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", "type": [ - "VerifiableCredential" + "VerifiableCredential" ], "credentialSubject": [ - { - "id": "did:example:d23dd687a7dc6787646f2eb98d0" - } + { + "id": "did:example:d23dd687a7dc6787646f2eb98d0" + } ], - "issuer": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "issuer": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", "issuanceDate": "2023-04-24T06:08:03.039Z", "proof": { - "type": "Ed25519Signature2018", - "proofPurpose": "assertionMethod", - "verificationMethod": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", - "created": "2023-07-21T00:14:01.797Z", - "jws": "eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..QlVHquEY_yMSZTKEl7IzgjSz2cV2rykkPlT7ojcf6q7859ErV5reLs1nH_5XMTLVY9LTwQOQsc1a8Lz4RFNbCQ" + "type": "Ed25519Signature2018", + "proofPurpose": "assertionMethod", + "verificationMethod": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", + "created": "2024-04-03T01:13:18.220667Z", + "jws": "eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..VZU_0mj3fD-Nrcq1Zu4r_tqOhQERfI8RMpPeDHX3dQkmTyvOG5AUFtgebrr-wS1RqHIRgvxqIBaSE51dHwUtBA" } - }"###; + }"###; let vc = issue_presentation(did, hex_secret_key, vc_str.to_string()).await; println!("{:?}", vc); @@ -147,42 +147,42 @@ mod tests { async fn test_verify_presentation_ed25519() { let vp_str = r###"{ "@context": [ - "https://www.w3.org/2018/credentials/v1" + "https://www.w3.org/2018/credentials/v1" ], - "id": "did:infra:01:5CG2qsBQoUR4tXfK2mmGEHdb61wF7Dy6NUQtLgrguBZAFT5E", + "id": "did:infra:01:5F9myCAKW52XUU38Z4uhttmYYLoLFWe9AnEVpv1aGpx9Q3Bp", "type": "VerifiablePresentation", "verifiableCredential": { - "@context": [ - "https://www.w3.org/2018/credentials/v1" - ], - "id": "did:infra:space:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", - "type": [ - "VerifiableCredential" - ], - "credentialSubject": [ - { - "id": "did:example:d23dd687a7dc6787646f2eb98d0" - } - ], - "issuer": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", - "issuanceDate": "2023-04-24T06:08:03.039Z", - "proof": { - "type": "Ed25519Signature2018", - "proofPurpose": "assertionMethod", - "verificationMethod": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", - "created": "2023-07-21T00:14:01.797Z", - "jws": "eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..QlVHquEY_yMSZTKEl7IzgjSz2cV2rykkPlT7ojcf6q7859ErV5reLs1nH_5XMTLVY9LTwQOQsc1a8Lz4RFNbCQ" + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "id": "did:infra:01:5FDseiC76zPek2YYkuyenu4ZgxZ7PUWXt9d19HNB5CaQXt5U", + "type": [ + "VerifiableCredential" + ], + "credentialSubject": [ + { + "id": "did:example:d23dd687a7dc6787646f2eb98d0" } - }, - "proof": { + ], + "issuer": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "issuanceDate": "2023-04-24T06:08:03.039Z", + "proof": { "type": "Ed25519Signature2018", "proofPurpose": "assertionMethod", - "verificationMethod": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", - "created": "2023-07-21T00:16:39.571Z", - "jws": "eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..5NFKxvzG-mZZzzQ7uE2UAuZ4te6pJhjmzx8RI-OrMsZSXrMHQjjvagFr_IYkCpxllrn9Elp1hUAE31SBMPCTAA" + "verificationMethod": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", + "created": "2024-04-03T01:13:18.220667Z", + "jws": "eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..VZU_0mj3fD-Nrcq1Zu4r_tqOhQERfI8RMpPeDHX3dQkmTyvOG5AUFtgebrr-wS1RqHIRgvxqIBaSE51dHwUtBA" + } + }, + "proof": { + "type": "Ed25519Signature2018", + "proofPurpose": "assertionMethod", + "verificationMethod": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", + "created": "2024-04-03T01:16:09.837873Z", + "jws": "eyJhbGciOiJFZERTQSIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..XMUnK1nLJI3jahunuS-ooEVWAKgN3VwiUc0cm2xiFNMdgnBqYi6-n-uPdpDJls6-7BXlLhR4W4nGlPrptQFTBA" }, - "holder": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW" - }"###; + "holder": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW" + }"###; let verify: String = verify_presentation(vp_str.to_string()).await.unwrap(); assert_eq!(verify, "true".to_string()); diff --git a/tests/did-infra-space.json b/tests/did-infra-space.json index 69b43e5..f8ddc4d 100644 --- a/tests/did-infra-space.json +++ b/tests/did-infra-space.json @@ -1,23 +1,23 @@ { "@context": "https://www.w3.org/ns/did/v1", - "id": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "id": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", "verificationMethod": [ { - "id": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", + "id": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", "type": "Ed25519VerificationKey2018", - "controller": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "controller": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", "publicKeyBase58": "F9JHKboDqg3tK9wnrt8z8xwZRnoZCJAHTdxXVuUMW8z2" }, { - "id": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", + "id": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", "type": "Ed25519VerificationKey2020", - "controller": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "controller": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", "publicKeyMultibase": "z6MktbZKur3fBDYMRenVYT6pz4VZFN5QcBQe9esTLBSNRMmQ" }, { - "id": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-3", + "id": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-3", "type": "JsonWebKey2020", - "controller": "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", + "controller": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", "publicKeyJwk": { "kty": "OKP", "crv": "Ed25519", @@ -26,13 +26,13 @@ } ], "authentication": [ - "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", - "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", - "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-3" + "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", + "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", + "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-3" ], "assertionMethod": [ - "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", - "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", - "did:infra:space:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-3" + "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-1", + "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-2", + "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW#keys-3" ] } \ No newline at end of file From 5f93ed031fd3160f31026153a216498b04fffe87 Mon Sep 17 00:00:00 2001 From: Cute_Wisp Date: Tue, 16 Apr 2024 10:39:40 +0900 Subject: [PATCH 27/27] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20did=20documen?= =?UTF-8?q?t=20context=20to=20array?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/resolver/resolver.rs | 4 ++-- tests/did-infra-space.json | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/resolver/resolver.rs b/src/resolver/resolver.rs index e2c41f6..7206165 100644 --- a/src/resolver/resolver.rs +++ b/src/resolver/resolver.rs @@ -103,9 +103,9 @@ impl DIDResolver for InfraDIDResolver { ]; let doc = Document { - context: ssi_dids::Contexts::One(ssi_dids::Context::URI( + context: ssi_dids::Contexts::Many(vec![ssi_dids::Context::URI( ssi_dids::DEFAULT_CONTEXT.into(), - )), + )]), id: did.to_string(), verification_method: Some(vms), authentication: Some(vm_urls.clone()), diff --git a/tests/did-infra-space.json b/tests/did-infra-space.json index f8ddc4d..e7f82f7 100644 --- a/tests/did-infra-space.json +++ b/tests/did-infra-space.json @@ -1,5 +1,7 @@ { - "@context": "https://www.w3.org/ns/did/v1", + "@context": [ + "https://www.w3.org/ns/did/v1" + ], "id": "did:infra:01:5GpEYnXBoLgvzyWe4Defitp5UV25xZUiUCJM2xNgkDXkM4NW", "verificationMethod": [ {