From 8e23337068beb66af6eaa715c3bdffe6fd9db4e2 Mon Sep 17 00:00:00 2001 From: "Charles E. Lehner" Date: Wed, 2 Mar 2022 14:03:42 -0500 Subject: [PATCH 01/10] Update ssi --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2659ecd0..a114f51a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ on: env: CARGO_TERM_COLOR: always - SSI_REF: c66972290377c6b4bf9b63b2dfdc047dbdcfd989 + SSI_REF: cd6a760046c6d95b4f7eda1f79f8fd6d7e7e0f86 defaults: run: From 1c035e65b77a58ed43f98c4dc00123a5ec436a47 Mon Sep 17 00:00:00 2001 From: "Charles E. Lehner" Date: Mon, 14 Feb 2022 15:53:01 -0500 Subject: [PATCH 02/10] Use anyhow in didkit-cli --- cli/Cargo.toml | 1 + cli/src/main.rs | 4 +++- lib/src/error.rs | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 309c8ac1..76cca178 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -33,6 +33,7 @@ thiserror = "1.0" bytes = "1.0" base64 = "0.12" sshkeys = "0.3" +anyhow = "1.0" [dev-dependencies] tokio = { version = "1.0", features = ["macros", "process"] } diff --git a/cli/src/main.rs b/cli/src/main.rs index a58a4fa5..1ecb8b6c 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -3,6 +3,7 @@ use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; use std::path::PathBuf; use std::str::FromStr; +use anyhow::Result as AResult; use chrono::prelude::*; use clap::{AppSettings, ArgGroup, Parser, StructOpt}; use serde::Serialize; @@ -374,7 +375,7 @@ set. For more info, see the manual for ssh-agent(1) and ssh-add(1). } } -fn main() { +fn main() -> AResult<()> { let rt = runtime::get().unwrap(); let opt = DIDKit::parse(); let ssh_agent_sock; @@ -772,4 +773,5 @@ fn main() { } } } + Ok(()) } diff --git a/lib/src/error.rs b/lib/src/error.rs index 4cdad951..a9e8540b 100644 --- a/lib/src/error.rs +++ b/lib/src/error.rs @@ -7,6 +7,7 @@ use std::ptr; use serde_json::Error as JSONError; use ssi::error::Error as SSIError; +use std::error::Error as StdError; use std::io::Error as IOError; use std::str::Utf8Error; @@ -54,6 +55,8 @@ impl Error { } } +impl StdError for Error {} + #[no_mangle] /// Retrieve a human-readable description of the most recent error encountered by a DIDKit C /// function. The returned string is valid until the next call to a DIDKit function in the current From 170bd72b02d7509494fd5c55c8cba0a7e0a2e42b Mon Sep 17 00:00:00 2001 From: "Charles E. Lehner" Date: Wed, 2 Mar 2022 14:10:20 -0500 Subject: [PATCH 03/10] Document DID method transactions --- cli/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cli/README.md b/cli/README.md index cb9830d9..e4674c85 100644 --- a/cli/README.md +++ b/cli/README.md @@ -195,6 +195,12 @@ If the `-m` option is used, a JSON array is returned containing the following th Exit status is zero on success and nonzero on error. On error, if `-m` is used, the error message is returned in the `error` property of the DID dereferencing metadata object on standard output; if `-m` is not used, the error is printed on standard error. +## Concepts + +### DID method transaction + +DIDKit's DID method operation commands do not fully perform the respective operation; instead, they return a data structure representing the partially applied operation, called a **DID method transaction**. The transaction is a verifiable message created by a DID controller to perform a [DID method operation][method-operations]. The transaction can be submitted, published, and/or fully performed, per the DID method. + ## Examples See the included [shell script](tests/example.sh). @@ -234,3 +240,4 @@ See the included [shell script](tests/example.sh). [did-url-dereferencing-metadata]: https://w3c.github.io/did-core/#did-url-dereferencing-metadata-properties [did-url-dereferencing-input-metadata]: https://w3c.github.io/did-core/#did-url-dereferencing-input-metadata-properties [did-resolution-https-binding]: https://w3c-ccg.github.io/did-resolution/#bindings-https +[method-operations]: https://www.w3.org/TR/did-core/#method-operations From c190531b3c06dab170203a2971d217c5351ecf89 Mon Sep 17 00:00:00 2001 From: "Charles E. Lehner" Date: Mon, 14 Feb 2022 15:49:24 -0500 Subject: [PATCH 04/10] Add DID Create subcommand --- cli/README.md | 17 ++++++++++- cli/src/main.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++---- lib/src/lib.rs | 2 +- 3 files changed, 88 insertions(+), 7 deletions(-) diff --git a/cli/README.md b/cli/README.md index e4674c85..9435be7b 100644 --- a/cli/README.md +++ b/cli/README.md @@ -195,11 +195,26 @@ If the `-m` option is used, a JSON array is returned containing the following th Exit status is zero on success and nonzero on error. On error, if `-m` is used, the error message is returned in the `error` property of the DID dereferencing metadata object on standard output; if `-m` is not used, the error is printed on standard error. +### `didkit did-create ` + +Construct a [DID method transaction][] to create a DID with a given DID method. + +#### Options + +- `-o ` - Options for DID Create operation. +- `-r, --recovery-key ` - JWK file for DID recovery and/or deactivation purposes, as used in Sidetree DID methods (e.g. `did:ion`). +- `-u, --update-key ` - JWK file for DID update operations, as used in Sidetree DID Methods (e.g. `did:ion`). +- `-v, --verification-key ` - JWK file for default verification method + +#### Output + +A [DID method transaction][] for a DID Create operation. + ## Concepts ### DID method transaction -DIDKit's DID method operation commands do not fully perform the respective operation; instead, they return a data structure representing the partially applied operation, called a **DID method transaction**. The transaction is a verifiable message created by a DID controller to perform a [DID method operation][method-operations]. The transaction can be submitted, published, and/or fully performed, per the DID method. +DIDKit's DID method operation commands (e.g. [create](#didkit-did-create-did-method)) do not fully perform the respective operation; instead, they return a data structure representing the partially applied operation, called a **DID method transaction**. The transaction is a verifiable message created by a DID controller to perform a [DID method operation][method-operations]. The transaction can be submitted, published, and/or fully performed, per the DID method. ## Examples diff --git a/cli/src/main.rs b/cli/src/main.rs index 1ecb8b6c..74363af7 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -3,7 +3,7 @@ use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; use std::path::PathBuf; use std::str::FromStr; -use anyhow::Result as AResult; +use anyhow::{anyhow, Context, Result as AResult}; use chrono::prelude::*; use clap::{AppSettings, ArgGroup, Parser, StructOpt}; use serde::Serialize; @@ -13,7 +13,7 @@ use sshkeys::PublicKey; use did_method_key::DIDKey; use didkit::generate_proof; use didkit::{ - dereference, get_verification_method, runtime, DIDMethod, DIDResolver, + dereference, get_verification_method, runtime, DIDCreate, DIDMethod, DIDResolver, DereferencingInputMetadata, Error, LinkedDataProofOptions, Metadata, ProofFormat, ProofPurpose, ResolutionInputMetadata, ResolutionResult, Source, VerifiableCredential, VerifiablePresentation, DID_METHODS, JWK, URI, @@ -66,11 +66,33 @@ pub enum DIDKit { ssh_pk: PublicKey, }, - /* // DID Functionality /// Create new DID Document. - DIDCreate {}, - */ + // See also: https://identity.foundation/did-registration/#create + // (method), jobId, options, secret, didDocument + DIDCreate { + /// DID method name + method: String, + + /// JWK file for default verification method + #[clap(short, long, parse(from_os_str))] + verification_key: Option, + + /// JWK file for DID Update operations + #[clap(short, long, parse(from_os_str))] + update_key: Option, + + /// JWK file for DID Recovery and/or Deactivate operations + #[clap(short, long, parse(from_os_str))] + recovery_key: Option, + + #[clap(short = 'o', name = "name=value")] + /// Options for DID create operation + /// + /// More info: https://identity.foundation/did-registration/#options + options: Vec, + }, + /// Resolve a DID to a DID Document. DIDResolve { did: String, @@ -220,6 +242,17 @@ pub struct KeyArg { ssh_agent: bool, } +fn read_jwk_file_opt(pathbuf_opt: &Option) -> AResult> { + let pathbuf = match pathbuf_opt { + Some(pb) => pb, + None => return Ok(None), + }; + let key_file = File::open(pathbuf).context("Opening JWK file")?; + let key_reader = BufReader::new(key_file); + let jwk = serde_json::from_reader(key_reader).context("Reading JWK file")?; + Ok(Some(jwk)) +} + impl KeyArg { fn get_jwk(&self) -> JWK { self.get_jwk_opt() @@ -659,6 +692,39 @@ fn main() -> AResult<()> { stdout().write_all(normalized.as_bytes()).unwrap(); } + DIDKit::DIDCreate { + method, + verification_key, + update_key, + recovery_key, + options, + } => { + let method = DID_METHODS + .get(&method) + .ok_or(anyhow!("Unable to get DID method"))?; + let verification_key = read_jwk_file_opt(&verification_key) + .context("Read verification key for DID Create")?; + let update_key = + read_jwk_file_opt(&update_key).context("Read update key for DID Create")?; + let recovery_key = + read_jwk_file_opt(&recovery_key).context("Read recovery key for DID Create")?; + let options = + metadata_properties_to_value(options).context("Parse options for DID Create")?; + let options = serde_json::from_value(options).context("Unable to convert options")?; + + let tx = method + .create(DIDCreate { + recovery_key, + update_key, + verification_key, + options, + }) + .context("DID Create failed")?; + let stdout_writer = BufWriter::new(stdout()); + serde_json::to_writer_pretty(stdout_writer, &tx).unwrap(); + println!(""); + } + DIDKit::DIDResolve { did, with_metadata, diff --git a/lib/src/lib.rs b/lib/src/lib.rs index b4216ab7..9658a49f 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -14,7 +14,7 @@ extern crate lazy_static; pub use crate::did_methods::DID_METHODS; pub use crate::error::Error; -pub use ssi::did::{DIDMethod, Document, Source}; +pub use ssi::did::{DIDCreate, DIDMethod, Document, Source}; #[cfg(feature = "http-did")] pub use ssi::did_resolve::HTTPDIDResolver; pub use ssi::did_resolve::{ From c080b570663f9907c9d0a1e3b9eeb1f8ee1979bb Mon Sep 17 00:00:00 2001 From: "Charles E. Lehner" Date: Mon, 14 Feb 2022 15:59:40 -0500 Subject: [PATCH 05/10] Add DID Update subcommands --- cli/README.md | 83 ++++++++++- cli/src/main.rs | 382 ++++++++++++++++++++++++++++++++++++++++++++++-- lib/src/lib.rs | 4 +- 3 files changed, 458 insertions(+), 11 deletions(-) diff --git a/cli/README.md b/cli/README.md index 9435be7b..b7627b1e 100644 --- a/cli/README.md +++ b/cli/README.md @@ -210,11 +210,84 @@ Construct a [DID method transaction][] to create a DID with a given DID method. A [DID method transaction][] for a DID Create operation. +### `didkit did-update ` + +Construct a [DID method transaction][] to update a DID. + +#### Options +- `-o ` - Options for DID Update operation. +- `-u, --new-update-key ` - New JWK file for next DID update operations, as used in Sidetree DID Methods (e.g. `did:ion`). +- `-U, --update-key ` - JWK file for performing this DID update operation + +#### Subcommands + +Updates to a DID document may be done using these `did-update` subcommands. These correspond roughly to [`didDocumentOperation`][didDocumentOperation] values in DIF DID registration and/or [DID State Patches][did-state-patches] in Sidetree. + +- [`set-service`](#didkit-did-update-set-service-id) +- [`set-verification-method`](#didkit-did-update-set-verification-method-id) +- [`remove-service`](#didkit-did-update-remove-service-id) +- [`remove-verification-method`](#didkit-did-update-remove-verification-method-id) + +#### Output +A [DID method transaction][] for a DID Update operation. + +### `didkit did-update set-service ` + +Construct a [DID method transaction][] to add or modify a [service][services] in a DID document. + +The identified service object is created if it is exists, or reset if it does not exist, to contain the properties corresponding to the options passed, i.e. the `type` and `serviceEndpoint` properties. + +#### Options +- `-d, --did ` - DID whose DID document to update. Default: implied from `` +- `-t, --type ...` - Service type +- `-e, --endpoint ...` - serviceEndpoint URI or JSON object + +### `didkit did-update set-verification-method ` + +Construct a [DID method transaction][] to add or modify a [verification method][verification-methods] in a DID document. + +The identified verification method is created if it does not exist, or reset if it already exists. The verification method is constructed to contain properties as passed in the options. + +#### Options +- `-c, --controller ` - Verification method controller property +- `-d, --did ` - DID whose DID document to update. Default: implied from `` +- `-t, --type ` - Verification method type (required) + +##### Verification relationship options +At least one [verification relationship][verification-relationships] (proof purpose) must be provided. Each of these options creates the respective verification relationship in the DID document for the verification method object. + +- `-S, --assertionMethod` - [Assertion](https://www.w3.org/TR/did-core/#assertion) +- `-U, --authentication`- [Authentication](https://www.w3.org/TR/did-core/#authentication) +- `-D, --capabilityDelegation`- [Capability Delegation](https://www.w3.org/TR/did-core/#capability-delegation) +- `-I, --capabilityInvocation` - [Capability Invocation](https://www.w3.org/TR/did-core/#capability-invocation) +- `-K, --keyAgreement` - [keyAgreement](https://www.w3.org/TR/did-core/#key-agreement) + +##### Public key options +Exactly one public key property must be provided. Which properties are allowed depends on the verification method type. +- `-j, --publicKeyJwk ` - [publicKeyJwk][] value +- `-k, --publicKeyJwkPath ` - [publicKeyJwk][] value read from file +- `-m, --publicKeyMultibase ` - Multibase-encoded public key ([publicKeyMultibase][] value) +- `-b, --blockchainAccountId ` - [blockchainAccountId](https://w3c-ccg.github.io/security-vocab/#blockchainAccountId) (CAIP-10) value + +### `didkit did-update remove-service ` + +Construct a [DID method transaction][] to remove a [service][services] from a DID document. + +#### Options +- `-d, --did ` - DID whose DID document to update. Default: implied from `` + +### `didkit did-update remove-verification-method ` + +Construct a [DID method transaction][] to remove a [verification method][verification-methods] from a DID document. + +#### Options +- `-d, --did ` - DID whose DID document to update. Default: implied from `` + ## Concepts ### DID method transaction -DIDKit's DID method operation commands (e.g. [create](#didkit-did-create-did-method)) do not fully perform the respective operation; instead, they return a data structure representing the partially applied operation, called a **DID method transaction**. The transaction is a verifiable message created by a DID controller to perform a [DID method operation][method-operations]. The transaction can be submitted, published, and/or fully performed, per the DID method. +DIDKit's DID method operation commands ([create](#didkit-did-create-did-method), [update](#didkit-did-update-subcommand)) do not fully perform the respective operation; instead, they return a data structure representing the partially applied operation, called a **DID method transaction**. The transaction is a verifiable message created by a DID controller to perform a [DID method operation][method-operations]. The transaction can be submitted, published, and/or fully performed, per the DID method. ## Examples @@ -256,3 +329,11 @@ See the included [shell script](tests/example.sh). [did-url-dereferencing-input-metadata]: https://w3c.github.io/did-core/#did-url-dereferencing-input-metadata-properties [did-resolution-https-binding]: https://w3c-ccg.github.io/did-resolution/#bindings-https [method-operations]: https://www.w3.org/TR/did-core/#method-operations +[verification-methods]: https://www.w3.org/TR/did-core/#verification-methods +[verification-relationships]: https://www.w3.org/TR/did-core/#verification-relationships +[services]: https://www.w3.org/TR/did-core/#services +[publicKeyJwk]: https://www.w3.org/TR/did-core/#dfn-publickeyjwk +[publicKeyMultibase]: https://www.w3.org/TR/did-core/#dfn-publickeymultibase +[DID method transaction]: #did-method-transaction +[didDocumentOperation]: https://identity.foundation/did-registration/#diddocumentoperation +[did-state-patches]: https://identity.foundation/sidetree/spec/v1.0.0/#did-state-patches diff --git a/cli/src/main.rs b/cli/src/main.rs index 74363af7..58477ff3 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,9 +1,10 @@ +use std::convert::TryFrom; use std::fs::File; use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; use std::path::PathBuf; use std::str::FromStr; -use anyhow::{anyhow, Context, Result as AResult}; +use anyhow::{anyhow, bail, Context, Error as AError, Result as AResult}; use chrono::prelude::*; use clap::{AppSettings, ArgGroup, Parser, StructOpt}; use serde::Serialize; @@ -13,12 +14,14 @@ use sshkeys::PublicKey; use did_method_key::DIDKey; use didkit::generate_proof; use didkit::{ - dereference, get_verification_method, runtime, DIDCreate, DIDMethod, DIDResolver, - DereferencingInputMetadata, Error, LinkedDataProofOptions, Metadata, ProofFormat, ProofPurpose, - ResolutionInputMetadata, ResolutionResult, Source, VerifiableCredential, - VerifiablePresentation, DID_METHODS, JWK, URI, + dereference, get_verification_method, runtime, DIDCreate, DIDDocumentOperation, DIDMethod, + DIDResolver, DIDUpdate, DereferencingInputMetadata, Error, LinkedDataProofOptions, Metadata, + ProofFormat, ProofPurpose, ResolutionInputMetadata, ResolutionResult, Source, + VerifiableCredential, VerifiablePresentation, DIDURL, DID_METHODS, JWK, URI, }; use didkit_cli::opts::ResolverOptions; +use ssi::did::{Service, ServiceEndpoint, VerificationRelationship}; +use ssi::one_or_many::OneOrMany; #[derive(StructOpt, Debug)] pub enum DIDKit { @@ -93,6 +96,26 @@ pub enum DIDKit { options: Vec, }, + /// Update a DID. + DIDUpdate { + /// New JWK file for next DID Update operation + #[clap(short = 'u', long, parse(from_os_str))] + new_update_key: Option, + + /// JWK file for performing this DID update operation. + #[clap(short = 'U', long, parse(from_os_str))] + update_key: Option, + + #[clap(short = 'o', name = "name=value")] + /// Options for DID Update operation + /// + /// More info: https://identity.foundation/did-registration/#options + options: Vec, + + #[clap(subcommand)] + cmd: DIDUpdateCmd, + }, + /// Resolve a DID to a DID Document. DIDResolve { did: String, @@ -128,11 +151,8 @@ pub enum DIDKit { #[clap(flatten)] resolver_options: ResolverOptions, }, + /* - /// Update a DID Document’s authentication. - DIDUpdateAuthentication {}, - /// Update a DID Document’s service endpoint(s). - DIDUpdateServiceEndpoints {}, /// Deactivate a DID. DIDDeactivate {}, /// Create a Signed IETF JSON Patch to update a DID document. @@ -199,6 +219,143 @@ pub enum DIDKit { */ } +// An id and optionally a DID +// +// where the id may be present in the DID's DID document +// and may be a DID URL. +// +// Cannot put docstring here because it overwrites help text for did-update subcommands +#[derive(StructOpt, Debug)] +pub struct IdAndDid { + /// id (URI) of object to add/remove/update in DID document + id: DIDURL, + + /// DID whose DID document to update. Default: implied from + /// + /// Defaults to the DID that is the prefix from the argument. + #[clap(short, long)] + did: Option, +} + +impl IdAndDid { + pub fn parse<'a>(self) -> AResult<(&'a dyn DIDMethod, String, DIDURL)> { + let Self { id, did } = self; + let method = DID_METHODS + .get_method(&id.did) + .map_err(|e| anyhow!("Unable to get DID method: {}", e))?; + Ok((*method, did.unwrap_or_else(|| id.did.clone()), id)) + } +} + +fn parse_service_endpoint(uri_or_object: &str) -> AResult { + let s = uri_or_object.trim(); + if s.starts_with('{') { + let value = serde_json::from_str(s).context("Parse URI or Object")?; + Ok(ServiceEndpoint::Map(value)) + } else { + Ok(ServiceEndpoint::URI(s.to_string())) + } +} + +#[derive(StructOpt, Debug)] +#[clap(rename_all = "camelCase")] +#[clap(group = ArgGroup::new("verification_relationship").multiple(true).required(true))] +pub struct VerificationRelationships { + /// Allow using this verification method for authentication + #[clap(short = 'U', long, group = "verification_relationship")] + pub authentication: bool, + + /// Allow using this verification method for making assertions + #[clap(short = 'S', long, group = "verification_relationship")] + pub assertion_method: bool, + + /// Allow using this verification method for key agreement + #[clap(short = 'K', long, group = "verification_relationship")] + pub key_agreement: bool, + + /// Allow using this verification method for capability invocation + #[clap(short = 'I', long, group = "verification_relationship")] + pub capability_invocation: bool, + + /// Allow using this verification method for capability delegation + #[clap(short = 'D', long, group = "verification_relationship")] + pub capability_delegation: bool, +} + +impl From for Vec { + fn from(vrels: VerificationRelationships) -> Vec { + let mut vrels_vec = vec![]; + let VerificationRelationships { + authentication, + assertion_method, + capability_invocation, + capability_delegation, + key_agreement, + } = vrels; + if authentication { + vrels_vec.push(VerificationRelationship::Authentication); + } + if assertion_method { + vrels_vec.push(VerificationRelationship::AssertionMethod); + } + if key_agreement { + vrels_vec.push(VerificationRelationship::KeyAgreement); + } + if capability_invocation { + vrels_vec.push(VerificationRelationship::CapabilityInvocation); + } + if capability_delegation { + vrels_vec.push(VerificationRelationship::CapabilityDelegation); + } + vrels_vec + } +} + +#[derive(StructOpt, Debug)] +pub enum DIDUpdateCmd { + /// Add a verification method to the DID document + SetVerificationMethod { + #[clap(flatten)] + id_and_did: IdAndDid, + + /// Verification method type + #[clap(short, long)] + type_: String, + + /// Verification method controller property + /// + /// Defaults to the DID this update is for (the option) + #[clap(short, long)] + controller: Option, + + #[clap(flatten)] + verification_relationships: VerificationRelationships, + + #[clap(flatten)] + public_key: PublicKeyArg, + }, + + /// Add a service to the DID document + SetService { + #[clap(flatten)] + id_and_did: IdAndDid, + + /// Service type + #[clap(short, long)] + r#type: Vec, + + /// serviceEndpoint URI or JSON object + #[clap(short, long, parse(try_from_str = parse_service_endpoint))] + endpoint: Vec, + }, + + /// Remove a service endpoint from the DID document + RemoveService(IdAndDid), + + /// Remove a verification method from the DID document + RemoveVerificationMethod(IdAndDid), +} + #[derive(StructOpt, Debug)] #[non_exhaustive] pub struct ProofOptions { @@ -242,6 +399,102 @@ pub struct KeyArg { ssh_agent: bool, } +#[derive(StructOpt, Debug)] +#[clap(group = ArgGroup::new("public_key_group").required(true))] +#[clap(rename_all = "camelCase")] +pub struct PublicKeyArg { + /// Public key JSON Web Key (JWK) + #[clap(short = 'j', long, group = "public_key_group", parse(try_from_str = serde_json::from_str), name = "JWK")] + public_key_jwk: Option, + + /// Public key JWK read from file + #[clap(short = 'k', long, group = "public_key_group", name = "filename")] + public_key_jwk_path: Option, + + /// Multibase-encoded public key + #[clap(short = 'm', long, group = "public_key_group", name = "string")] + public_key_multibase: Option, + + /// Blockchain Account Id (CAIP-10) + #[clap(short = 'b', long, group = "public_key_group", name = "account")] + blockchain_account_id: Option, +} + +/// PublicKeyArg as an enum +enum PublicKeyArgEnum { + PublicKeyJwk(JWK), + PublicKeyJwkPath(PathBuf), + PublicKeyMultibase(String), + BlockchainAccountId(String), +} + +/// PublicKeyArgEnum after file reading. +/// Suitable for use a verification method map. +enum PublicKeyProperty { + JWK(JWK), + Multibase(String), + Account(String), +} + +/// Convert from struct with options, to enum, +/// until https://github.com/clap-rs/clap/issues/2621 +impl TryFrom for PublicKeyArgEnum { + type Error = AError; + fn try_from(pka: PublicKeyArg) -> AResult { + Ok(match pka { + PublicKeyArg { + public_key_jwk_path: Some(path), + public_key_jwk: None, + public_key_multibase: None, + blockchain_account_id: None, + } => PublicKeyArgEnum::PublicKeyJwkPath(path), + PublicKeyArg { + public_key_jwk_path: None, + public_key_jwk: Some(jwk), + public_key_multibase: None, + blockchain_account_id: None, + } => PublicKeyArgEnum::PublicKeyJwk(jwk), + PublicKeyArg { + public_key_jwk_path: None, + public_key_jwk: None, + public_key_multibase: Some(mb), + blockchain_account_id: None, + } => PublicKeyArgEnum::PublicKeyMultibase(mb), + PublicKeyArg { + public_key_jwk_path: None, + public_key_jwk: None, + public_key_multibase: None, + blockchain_account_id: Some(account), + } => PublicKeyArgEnum::BlockchainAccountId(account), + PublicKeyArg { + public_key_jwk_path: None, + public_key_jwk: None, + public_key_multibase: None, + blockchain_account_id: None, + } => bail!("Missing public key option"), + _ => bail!("Only one public key option may be used"), + }) + } +} + +/// Convert public key option to a property for a verification method +impl TryFrom for PublicKeyProperty { + type Error = AError; + fn try_from(pka: PublicKeyArgEnum) -> AResult { + Ok(match pka { + PublicKeyArgEnum::PublicKeyJwkPath(path) => { + let key_file = File::open(path).context("Open JWK file")?; + let key_reader = BufReader::new(key_file); + let jwk: JWK = serde_json::from_reader(key_reader).context("Read JWK file")?; + PublicKeyProperty::JWK(jwk.to_public()) + } + PublicKeyArgEnum::PublicKeyJwk(jwk) => PublicKeyProperty::JWK(jwk.to_public()), + PublicKeyArgEnum::PublicKeyMultibase(mb) => PublicKeyProperty::Multibase(mb), + PublicKeyArgEnum::BlockchainAccountId(account) => PublicKeyProperty::Account(account), + }) + } +} + fn read_jwk_file_opt(pathbuf_opt: &Option) -> AResult> { let pathbuf = match pathbuf_opt { Some(pb) => pb, @@ -725,6 +978,117 @@ fn main() -> AResult<()> { println!(""); } + DIDKit::DIDUpdate { + new_update_key, + update_key, + options, + cmd, + } => { + let new_update_key = + read_jwk_file_opt(&new_update_key).context("Read new update key for DID update")?; + let update_key = + read_jwk_file_opt(&update_key).context("Read update key for DID update")?; + let options = + metadata_properties_to_value(options).context("Parse options for DID update")?; + let options = serde_json::from_value(options).context("Unable to convert options")?; + + let (did, method, operation) = match cmd { + DIDUpdateCmd::SetVerificationMethod { + id_and_did, + type_, + controller, + public_key, + verification_relationships, + } => { + let (method, did, id) = id_and_did + .parse() + .context("Parse id/DID for set-verification-method subcommand")?; + let pk_enum = + PublicKeyArgEnum::try_from(public_key).context("Read public key option")?; + let public_key = + PublicKeyProperty::try_from(pk_enum).context("Read public key property")?; + let purposes = verification_relationships.into(); + let controller = controller.unwrap_or_else(|| did.clone()); + let mut vmm = ssi::did::VerificationMethodMap { + id: id.to_string(), + type_, + controller, + ..Default::default() + }; + match public_key { + PublicKeyProperty::JWK(jwk) => vmm.public_key_jwk = Some(jwk), + PublicKeyProperty::Multibase(mb) => { + let mut ps = std::collections::BTreeMap::::default(); + ps.insert("publicKeyMultibase".to_string(), Value::String(mb)); + vmm.property_set = Some(ps); + } + PublicKeyProperty::Account(account) => { + vmm.blockchain_account_id = Some(account); + } + } + let op = DIDDocumentOperation::SetVerificationMethod { vmm, purposes }; + (did, method, op) + } + DIDUpdateCmd::RemoveVerificationMethod(id_and_did) => { + let (method, did, id) = id_and_did.parse().context( + "Unable to parse id/DID for remove-verification-method subcommand", + )?; + let op = DIDDocumentOperation::RemoveVerificationMethod(id); + (did, method, op) + } + DIDUpdateCmd::SetService { + id_and_did, + endpoint, + r#type, + } => { + let (method, did, id) = id_and_did + .parse() + .context("Parse id/DID for set-verification-method subcommand")?; + let service_endpoint = match endpoint.len() { + 0 => None, + 1 => endpoint.into_iter().next().map(OneOrMany::One), + _ => Some(OneOrMany::Many(endpoint)), + }; + let type_ = match r#type.len() { + 1 => r#type + .into_iter() + .next() + .map(OneOrMany::One) + .ok_or(anyhow!("Missing service type"))?, + + _ => OneOrMany::Many(r#type), + }; + let service = Service { + id: id.to_string(), + type_, + service_endpoint, + property_set: None, + }; + let op = DIDDocumentOperation::SetService(service); + (did, method, op) + } + DIDUpdateCmd::RemoveService(id_and_did) => { + let (method, did, id) = id_and_did + .parse() + .context("Parse id/DID for set-verification-method subcommand")?; + let op = DIDDocumentOperation::RemoveService(id); + (did, method, op) + } + }; + let tx = method + .update(DIDUpdate { + did: did.clone(), + update_key, + new_update_key, + operation, + options, + }) + .context("DID Update failed")?; + let stdout_writer = BufWriter::new(stdout()); + serde_json::to_writer_pretty(stdout_writer, &tx).unwrap(); + println!(""); + } + DIDKit::DIDResolve { did, with_metadata, diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 9658a49f..73678d0d 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -14,7 +14,9 @@ extern crate lazy_static; pub use crate::did_methods::DID_METHODS; pub use crate::error::Error; -pub use ssi::did::{DIDCreate, DIDMethod, Document, Source}; +pub use ssi::did::{ + DIDCreate, DIDDocumentOperation, DIDMethod, DIDUpdate, Document, Source, DIDURL, +}; #[cfg(feature = "http-did")] pub use ssi::did_resolve::HTTPDIDResolver; pub use ssi::did_resolve::{ From c7288e517d45c630637e982dc55d3a9bab55a121 Mon Sep 17 00:00:00 2001 From: "Charles E. Lehner" Date: Mon, 14 Feb 2022 16:02:40 -0500 Subject: [PATCH 06/10] Add DID Recover subcommand --- cli/README.md | 18 ++++++++++++- cli/src/main.rs | 70 +++++++++++++++++++++++++++++++++++++++++++++++-- lib/src/lib.rs | 2 +- 3 files changed, 86 insertions(+), 4 deletions(-) diff --git a/cli/README.md b/cli/README.md index b7627b1e..2dfcaaab 100644 --- a/cli/README.md +++ b/cli/README.md @@ -283,11 +283,26 @@ Construct a [DID method transaction][] to remove a [verification method][verific #### Options - `-d, --did ` - DID whose DID document to update. Default: implied from `` +### `didkit did-recover ` + +Construct a [DID method transaction][] to perform a DID recover operation ([DID recovery][did-recovery]), if supported by the DID method. + +#### Options +- `-o ` - Options for DID Recover operation. +- `-r, --new-recovery-key ` - New JWK file for DID recovery and/or deactivation purposes, as used in Sidetree DID methods (e.g. `did:ion`). +- `-u, --new-update-key ` - New JWK file for next DID update operation, as used in Sidetree DID Methods (e.g. `did:ion`). +- `-v, --new-verification-key ` - New JWK file for default verification method +- `-R, --recovery-key ` - JWK file for performing this DID recover operation + +#### Output + +A [DID method transaction][] for a DID Recover operation. + ## Concepts ### DID method transaction -DIDKit's DID method operation commands ([create](#didkit-did-create-did-method), [update](#didkit-did-update-subcommand)) do not fully perform the respective operation; instead, they return a data structure representing the partially applied operation, called a **DID method transaction**. The transaction is a verifiable message created by a DID controller to perform a [DID method operation][method-operations]. The transaction can be submitted, published, and/or fully performed, per the DID method. +DIDKit's DID method operation commands ([create](#didkit-did-create-did-method), [update](#didkit-did-update-subcommand), [recover](#didkit-did-recover-did)) do not fully perform the respective operation; instead, they return a data structure representing the partially applied operation, called a **DID method transaction**. The transaction is a verifiable message created by a DID controller to perform a [DID method operation][method-operations]. The transaction can be submitted, published, and/or fully performed, per the DID method. ## Examples @@ -329,6 +344,7 @@ See the included [shell script](tests/example.sh). [did-url-dereferencing-input-metadata]: https://w3c.github.io/did-core/#did-url-dereferencing-input-metadata-properties [did-resolution-https-binding]: https://w3c-ccg.github.io/did-resolution/#bindings-https [method-operations]: https://www.w3.org/TR/did-core/#method-operations +[did-recovery]: https://www.w3.org/TR/did-core/#did-recovery [verification-methods]: https://www.w3.org/TR/did-core/#verification-methods [verification-relationships]: https://www.w3.org/TR/did-core/#verification-relationships [services]: https://www.w3.org/TR/did-core/#services diff --git a/cli/src/main.rs b/cli/src/main.rs index 58477ff3..987b5888 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -15,8 +15,8 @@ use did_method_key::DIDKey; use didkit::generate_proof; use didkit::{ dereference, get_verification_method, runtime, DIDCreate, DIDDocumentOperation, DIDMethod, - DIDResolver, DIDUpdate, DereferencingInputMetadata, Error, LinkedDataProofOptions, Metadata, - ProofFormat, ProofPurpose, ResolutionInputMetadata, ResolutionResult, Source, + DIDRecover, DIDResolver, DIDUpdate, DereferencingInputMetadata, Error, LinkedDataProofOptions, + Metadata, ProofFormat, ProofPurpose, ResolutionInputMetadata, ResolutionResult, Source, VerifiableCredential, VerifiablePresentation, DIDURL, DID_METHODS, JWK, URI, }; use didkit_cli::opts::ResolverOptions; @@ -116,6 +116,34 @@ pub enum DIDKit { cmd: DIDUpdateCmd, }, + /// Recover a DID. + DIDRecover { + /// DID to recover + did: String, + + /// New JWK file for default verification method + #[clap(short = 'v', long, parse(from_os_str))] + new_verification_key: Option, + + /// New JWK file for DID Update operations + #[clap(short = 'u', long, parse(from_os_str))] + new_update_key: Option, + + /// New JWK file for DID Recovery and/or Deactivate operations + #[clap(short = 'r', long, parse(from_os_str))] + new_recovery_key: Option, + + /// JWK file for performing this DID recover operation. + #[clap(short = 'R', long, parse(from_os_str))] + recovery_key: Option, + + #[clap(short = 'o', name = "name=value")] + /// Options for DID Recover operation + /// + /// More info: https://identity.foundation/did-registration/#options + options: Vec, + }, + /// Resolve a DID to a DID Document. DIDResolve { did: String, @@ -1089,6 +1117,44 @@ fn main() -> AResult<()> { println!(""); } + DIDKit::DIDRecover { + did, + new_verification_key, + new_update_key, + new_recovery_key, + recovery_key, + options, + } => { + let method = DID_METHODS + .get_method(&did) + .map_err(|e| anyhow!("Unable to get DID method: {}", e))?; + let new_verification_key = read_jwk_file_opt(&new_verification_key) + .context("Read new signing key for DID recovery")?; + let new_update_key = read_jwk_file_opt(&new_update_key) + .context("Read new update key for DID recovery")?; + let new_recovery_key = read_jwk_file_opt(&new_recovery_key) + .context("Read new recovery key for DID recovery")?; + let recovery_key = + read_jwk_file_opt(&recovery_key).context("Read recovery key for DID recovery")?; + let options = + metadata_properties_to_value(options).context("Parse options for DID recovery")?; + let options = serde_json::from_value(options).context("Unable to convert options")?; + + let tx = method + .recover(DIDRecover { + did: did.clone(), + recovery_key, + new_recovery_key, + new_update_key, + new_verification_key, + options, + }) + .context("DID Recover failed")?; + let stdout_writer = BufWriter::new(stdout()); + serde_json::to_writer_pretty(stdout_writer, &tx).unwrap(); + println!(""); + } + DIDKit::DIDResolve { did, with_metadata, diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 73678d0d..99506d7f 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -15,7 +15,7 @@ extern crate lazy_static; pub use crate::did_methods::DID_METHODS; pub use crate::error::Error; pub use ssi::did::{ - DIDCreate, DIDDocumentOperation, DIDMethod, DIDUpdate, Document, Source, DIDURL, + DIDCreate, DIDDocumentOperation, DIDMethod, DIDRecover, DIDUpdate, Document, Source, DIDURL, }; #[cfg(feature = "http-did")] pub use ssi::did_resolve::HTTPDIDResolver; From 2e5eae96ea0db7bf3bfbd4b90731a7ff8d3ca0ff Mon Sep 17 00:00:00 2001 From: "Charles E. Lehner" Date: Mon, 14 Feb 2022 16:03:58 -0500 Subject: [PATCH 07/10] Add DID deactivate subcommand --- cli/README.md | 13 ++++++++++++- cli/src/main.rs | 45 +++++++++++++++++++++++++++++++++++++++------ lib/src/lib.rs | 3 ++- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/cli/README.md b/cli/README.md index 2dfcaaab..00d7ef10 100644 --- a/cli/README.md +++ b/cli/README.md @@ -298,11 +298,22 @@ Construct a [DID method transaction][] to perform a DID recover operation ([DID A [DID method transaction][] for a DID Recover operation. +### `didkit did-deactivate ` + +Construct a [DID method transaction][] to deactivate a DID, if supported by the DID method. + +#### Options +- `-k, --key ` - JWK file to perform the DID Deactivate operation +- `-o ` - Options for DID deactivate operation. + +#### Output +A [DID method transaction][] for a DID Deactivate operation. + ## Concepts ### DID method transaction -DIDKit's DID method operation commands ([create](#didkit-did-create-did-method), [update](#didkit-did-update-subcommand), [recover](#didkit-did-recover-did)) do not fully perform the respective operation; instead, they return a data structure representing the partially applied operation, called a **DID method transaction**. The transaction is a verifiable message created by a DID controller to perform a [DID method operation][method-operations]. The transaction can be submitted, published, and/or fully performed, per the DID method. +DIDKit's DID method operation commands ([create](#didkit-did-create-did-method), [update](#didkit-did-update-subcommand), [recover](#didkit-did-recover-did), [deactivate](#didkit-did-deactivate-did)) do not fully perform the respective operation; instead, they return a data structure representing the partially applied operation, called a **DID method transaction**. The transaction is a verifiable message created by a DID controller to perform a [DID method operation][method-operations]. The transaction can be submitted, published, and/or fully performed, per the DID method. ## Examples diff --git a/cli/src/main.rs b/cli/src/main.rs index 987b5888..18bec902 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -14,10 +14,11 @@ use sshkeys::PublicKey; use did_method_key::DIDKey; use didkit::generate_proof; use didkit::{ - dereference, get_verification_method, runtime, DIDCreate, DIDDocumentOperation, DIDMethod, - DIDRecover, DIDResolver, DIDUpdate, DereferencingInputMetadata, Error, LinkedDataProofOptions, - Metadata, ProofFormat, ProofPurpose, ResolutionInputMetadata, ResolutionResult, Source, - VerifiableCredential, VerifiablePresentation, DIDURL, DID_METHODS, JWK, URI, + dereference, get_verification_method, runtime, DIDCreate, DIDDeactivate, DIDDocumentOperation, + DIDMethod, DIDRecover, DIDResolver, DIDUpdate, DereferencingInputMetadata, Error, + LinkedDataProofOptions, Metadata, ProofFormat, ProofPurpose, ResolutionInputMetadata, + ResolutionResult, Source, VerifiableCredential, VerifiablePresentation, DIDURL, DID_METHODS, + JWK, URI, }; use didkit_cli::opts::ResolverOptions; use ssi::did::{Service, ServiceEndpoint, VerificationRelationship}; @@ -180,9 +181,20 @@ pub enum DIDKit { resolver_options: ResolverOptions, }, - /* /// Deactivate a DID. - DIDDeactivate {}, + DIDDeactivate { + did: String, + + /// Filename of JWK to perform the DID Deactivate operation + #[clap(short, long, parse(from_os_str))] + key: Option, + + #[clap(short = 'o', name = "name=value")] + /// Options for DID deactivate operation + options: Vec, + }, + + /* /// Create a Signed IETF JSON Patch to update a DID document. DIDPatch {}, */ @@ -1155,6 +1167,27 @@ fn main() -> AResult<()> { println!(""); } + DIDKit::DIDDeactivate { did, key, options } => { + let method = DID_METHODS + .get_method(&did) + .map_err(|e| anyhow!("Unable to get DID method: {}", e))?; + let key = read_jwk_file_opt(&key).context("Read key for DID deactivation")?; + let options = metadata_properties_to_value(options) + .context("Parse options for DID deactivation")?; + let options = serde_json::from_value(options).context("Unable to convert options")?; + + let tx = method + .deactivate(DIDDeactivate { + did: did.clone(), + key, + options, + }) + .context("DID deactivation failed")?; + let stdout_writer = BufWriter::new(stdout()); + serde_json::to_writer_pretty(stdout_writer, &tx).unwrap(); + println!(""); + } + DIDKit::DIDResolve { did, with_metadata, diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 99506d7f..0a630404 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -15,7 +15,8 @@ extern crate lazy_static; pub use crate::did_methods::DID_METHODS; pub use crate::error::Error; pub use ssi::did::{ - DIDCreate, DIDDocumentOperation, DIDMethod, DIDRecover, DIDUpdate, Document, Source, DIDURL, + DIDCreate, DIDDeactivate, DIDDocumentOperation, DIDMethod, DIDRecover, DIDUpdate, Document, + Source, DIDURL, }; #[cfg(feature = "http-did")] pub use ssi::did_resolve::HTTPDIDResolver; From f456a385d3be723ed866e2acf7b9d25e43304976 Mon Sep 17 00:00:00 2001 From: "Charles E. Lehner" Date: Mon, 14 Feb 2022 16:07:16 -0500 Subject: [PATCH 08/10] Add did-from-tx and did-submit-tx --- cli/README.md | 18 +++++++++++++++++- cli/src/main.rs | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/cli/README.md b/cli/README.md index 00d7ef10..bfe4d731 100644 --- a/cli/README.md +++ b/cli/README.md @@ -309,11 +309,27 @@ Construct a [DID method transaction][] to deactivate a DID, if supported by the #### Output A [DID method transaction][] for a DID Deactivate operation. +### `didkit did-from-tx` + +Reads a [DID method transaction][] on standard input, and extracts or derives its DID. + +#### Output + +The DID corresponding to the input transaction. + +### `didkit did-submit-tx ` + +Reads a [DID method transaction][] on standard input, and submits it in a method-specific way. Returns exit status zero if the transaction was successfully submitted. + +#### Output + +A method-specific data structure. + ## Concepts ### DID method transaction -DIDKit's DID method operation commands ([create](#didkit-did-create-did-method), [update](#didkit-did-update-subcommand), [recover](#didkit-did-recover-did), [deactivate](#didkit-did-deactivate-did)) do not fully perform the respective operation; instead, they return a data structure representing the partially applied operation, called a **DID method transaction**. The transaction is a verifiable message created by a DID controller to perform a [DID method operation][method-operations]. The transaction can be submitted, published, and/or fully performed, per the DID method. +DIDKit's DID method operation commands ([create](#didkit-did-create-did-method), [update](#didkit-did-update-subcommand), [recover](#didkit-did-recover-did), [deactivate](#didkit-did-deactivate-did)) do not fully perform the respective operation; instead, they return a data structure representing the partially applied operation, called a **DID method transaction**. The transaction is a verifiable message created by a DID controller to perform a [DID method operation][method-operations]. The transaction can be submitted, published, and/or fully performed, per the DID method, using the [did-submit-tx](#didkit-did-submit-tx-did) subcommand. ## Examples diff --git a/cli/src/main.rs b/cli/src/main.rs index 18bec902..c3608446 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -21,7 +21,7 @@ use didkit::{ JWK, URI, }; use didkit_cli::opts::ResolverOptions; -use ssi::did::{Service, ServiceEndpoint, VerificationRelationship}; +use ssi::did::{DIDMethodTransaction, Service, ServiceEndpoint, VerificationRelationship}; use ssi::one_or_many::OneOrMany; #[derive(StructOpt, Debug)] @@ -97,6 +97,16 @@ pub enum DIDKit { options: Vec, }, + /// Get DID from DID method transaction + /// + /// Reads from standard input. Outputs DID on success. + DIDFromTx, + + /// Submit a DID method transaction + /// + /// Reads from standard input. + DIDSubmitTx, + /// Update a DID. DIDUpdate { /// New JWK file for next DID Update operation @@ -1018,6 +1028,32 @@ fn main() -> AResult<()> { println!(""); } + DIDKit::DIDFromTx => { + let stdin_reader = BufReader::new(stdin()); + let tx: DIDMethodTransaction = serde_json::from_reader(stdin_reader).unwrap(); + let method = DID_METHODS + .get(&tx.did_method) + .ok_or(anyhow!("Unable to get DID method"))?; + let did = method + .did_from_transaction(tx) + .context("Get DID from transaction")?; + println!("{}", did); + } + + DIDKit::DIDSubmitTx => { + let stdin_reader = BufReader::new(stdin()); + let tx: DIDMethodTransaction = serde_json::from_reader(stdin_reader).unwrap(); + let method = DID_METHODS + .get(&tx.did_method) + .ok_or(anyhow!("Unable to get DID method"))?; + let result = rt + .block_on(method.submit_transaction(tx)) + .context("Submit DID transaction")?; + let stdout_writer = BufWriter::new(stdout()); + serde_json::to_writer_pretty(stdout_writer, &result).unwrap(); + println!(""); + } + DIDKit::DIDUpdate { new_update_key, update_key, From 5d32f8736606379584c55bd4cf6b283c596595d3 Mon Sep 17 00:00:00 2001 From: "Charles E. Lehner" Date: Tue, 11 Jan 2022 15:11:44 -0500 Subject: [PATCH 09/10] Add did:ion DID method --- lib/Cargo.toml | 1 + lib/src/did_methods.rs | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 6c7b86ae..ddd801a7 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -37,6 +37,7 @@ did-pkh = { version = "0.1", path = "../../ssi/did-pkh" } did-web = { version = "^0.1.1", path = "../../ssi/did-web" } did-webkey = { version = "0.1", path = "../../ssi/did-webkey", default-features = false } did-onion = { version = "^0.1.1", path = "../../ssi/did-onion" } +did-ion = { version = "^0.1.0", path = "../../ssi/did-ion" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" # TODO feature-gate JNI, or extract it in another crate like we do for Python (and probably WASM as well) diff --git a/lib/src/did_methods.rs b/lib/src/did_methods.rs index c259eb44..5d683738 100644 --- a/lib/src/did_methods.rs +++ b/lib/src/did_methods.rs @@ -3,14 +3,26 @@ use did_method_key::DIDKey; use did_onion::DIDOnion; use did_pkh::DIDPKH; // use did_sol::DIDSol; +use did_ion::DIDION; use did_tz::DIDTz; use did_web::DIDWeb; use did_webkey::DIDWebKey; -use ssi::did::DIDMethods; +use ssi::did::{DIDMethod, DIDMethods}; +use std::env::VarError; lazy_static! { static ref DIDTZ: DIDTz = DIDTz::default(); static ref DIDONION: DIDOnion = DIDOnion::default(); + static ref ION: DIDION = DIDION::new( + match std::env::var("DID_ION_API_URL") { + Ok(string) => Some(string), + Err(VarError::NotPresent) => None, + Err(VarError::NotUnicode(err)) => { + eprintln!("Unable to parse DID_ION_API_URL: {:?}", err); + None + } + } + ); pub static ref DID_METHODS: DIDMethods<'static> = { let mut methods = DIDMethods::default(); methods.insert(&DIDKey); @@ -21,6 +33,7 @@ lazy_static! { methods.insert(&DIDWebKey); methods.insert(&DIDPKH); methods.insert(&*DIDONION); + methods.insert(&*ION); methods }; } From a6332d50cbba6cbf545effa28216a29228a0ac31 Mon Sep 17 00:00:00 2001 From: "Charles E. Lehner" Date: Fri, 11 Feb 2022 17:05:18 -0500 Subject: [PATCH 10/10] Add ION example scripts --- cli/README.md | 7 ++++ cli/tests/ion-create-deactivate.sh | 48 +++++++++++++++++++++++++ cli/tests/ion-create-recover.sh | 52 +++++++++++++++++++++++++++ cli/tests/ion-create-update-svc.sh | 55 ++++++++++++++++++++++++++++ cli/tests/ion-create-update-vm.sh | 57 ++++++++++++++++++++++++++++++ 5 files changed, 219 insertions(+) create mode 100755 cli/tests/ion-create-deactivate.sh create mode 100755 cli/tests/ion-create-recover.sh create mode 100755 cli/tests/ion-create-update-svc.sh create mode 100755 cli/tests/ion-create-update-vm.sh diff --git a/cli/README.md b/cli/README.md index bfe4d731..483b4aa9 100644 --- a/cli/README.md +++ b/cli/README.md @@ -335,6 +335,13 @@ DIDKit's DID method operation commands ([create](#didkit-did-create-did-method), See the included [shell script](tests/example.sh). +See also the following shell scripts demonstrating create, update, recover and deactivate operations: +- [create and update service](tests/ion-create-update-svc.sh) +- [create and update verification method](tests/ion-create-update-vm.sh) +- [create and recover](tests/ion-create-recover.sh) +- [create and deactivate](tests/ion-create-deactivate.sh) + +[ssi]: https://github.com/spruceid/ssi [JWK]: https://tools.ietf.org/html/rfc7517 [ld-proofs]: https://w3c-ccg.github.io/ld-proofs/ [vc-http-api]: https://w3c-ccg.github.io/vc-http-api/ diff --git a/cli/tests/ion-create-deactivate.sh b/cli/tests/ion-create-deactivate.sh new file mode 100755 index 00000000..1a956d63 --- /dev/null +++ b/cli/tests/ion-create-deactivate.sh @@ -0,0 +1,48 @@ +#!/bin/sh +set -e +cd "$(dirname "$0")" + +# Create and use temp directory if does not already exist +dir=did-ion-test +if test -d "$dir" +then + printf 'Directory already exists: %s\n' "$dir" >&2 + exit 1 +fi +mkdir "$dir" +cd "$dir" + +# Generate initial update keypair and initial recovery keypair +didkit key generate secp256k1 > 1-u.jwk +didkit key generate secp256k1 > 1-r.jwk +# Construct Create operation with initial public keys +didkit did-create ion -r 1-r.jwk -u 1-u.jwk | tee 1-create.json +# Get long-form and short-form DID from Create operation +longdid=$(didkit did-from-tx < 1-create.json) +shortdid=$(echo -n "$longdid" | sed 's/:[^:]*$//') +didsuffix=$(echo -n "$shortdid" | sed 's/.*://') +echo "Sidetree DID Suffix: $didsuffix" +echo "DID (long-form/unpublished): $longdid" +echo "DID (short-form/canonical): $shortdid" + +# Now that the DID suffix is set, rename the directory to it. +cd .. +mv "$dir" "$didsuffix" +cd "$didsuffix" + +# Submit Create operation +export DID_ION_API_URL=http://localhost:3000/ +read -p "Press enter to submit Create operation. " _ +didkit did-submit-tx < 1-create.json + +# Construct Deactivate operation +didkit did-deactivate "$shortdid" -k 1-r.jwk | tee 2-deactivate.json +# Submit operation +read -p 'Press enter to submit Deactivate operation. ' _ +if ! didkit did-submit-tx < 2-deactivate.json +then + # Provide retry in case submission failed, e.g. because the + # create operation has not yet been processed. + read -p 'Press enter to retry Deactivate operation. ' _ + didkit did-submit-tx < 2-deactivate.json +fi diff --git a/cli/tests/ion-create-recover.sh b/cli/tests/ion-create-recover.sh new file mode 100755 index 00000000..45efab64 --- /dev/null +++ b/cli/tests/ion-create-recover.sh @@ -0,0 +1,52 @@ +#!/bin/sh +set -e +cd "$(dirname "$0")" + +# Create and use temp directory if does not already exist +dir=did-ion-test +if test -d "$dir" +then + printf 'Directory already exists: %s\n' "$dir" >&2 + exit 1 +fi +mkdir "$dir" +cd "$dir" + +# Generate initial update keypair and initial recovery keypair +didkit key generate secp256k1 > 1-u.jwk +didkit key generate secp256k1 > 1-r.jwk +# Construct Create operation with initial public keys +didkit did-create ion -r 1-r.jwk -u 1-u.jwk | tee 1-create.json +# Get long-form and short-form DID from Create operation +longdid=$(didkit did-from-tx < 1-create.json) +shortdid=$(echo -n "$longdid" | sed 's/:[^:]*$//') +didsuffix=$(echo -n "$shortdid" | sed 's/.*://') +echo "Sidetree DID Suffix: $didsuffix" +echo "DID (long-form/unpublished): $longdid" +echo "DID (short-form/canonical): $shortdid" + +# Now that the DID suffix is set, rename the directory to it. +cd .. +mv "$dir" "$didsuffix" +cd "$didsuffix" + +# Submit Create operation +export DID_ION_API_URL=http://localhost:3000/ +read -p "Press enter to submit Create operation. " _ +didkit did-submit-tx < 1-create.json + +# Generate new update keypair and recovery keypair for recover operation +didkit key generate secp256k1 > 2-u.jwk +didkit key generate secp256k1 > 2-r.jwk + +# Construct Recover operation +didkit did-recover "$shortdid" -R 1-r.jwk -r 2-r.jwk -u 2-u.jwk | tee 2-recover.json +# Submit operation +read -p 'Press enter to submit Recover operation. ' _ +if ! didkit did-submit-tx < 2-recover.json +then + # Provide retry in case submission failed, e.g. because the + # create operation has not yet been processed. + read -p 'Press enter to retry Recover operation. ' _ + didkit did-submit-tx < 2-recover.json +fi diff --git a/cli/tests/ion-create-update-svc.sh b/cli/tests/ion-create-update-svc.sh new file mode 100755 index 00000000..a0c8482d --- /dev/null +++ b/cli/tests/ion-create-update-svc.sh @@ -0,0 +1,55 @@ +#!/bin/sh +set -e +cd "$(dirname "$0")" +export DID_ION_API_URL=http://localhost:3000/ + +# Create and use temp directory if does not already exist +dir=did-ion-test +if test -d "$dir" +then + printf 'Directory already exists: %s\n' "$dir" >&2 + exit 1 +fi +mkdir "$dir" +cd "$dir" + +# Generate initial update keypair and initial recovery keypair +didkit key generate secp256k1 > 1-u.jwk +didkit key generate secp256k1 > 1-r.jwk +# Construct Create operation with initial public keys +didkit did-create ion -r 1-r.jwk -u 1-u.jwk | tee 1-create.json +# Get long-form and short-form DID from Create operation +longdid=$(didkit did-from-tx < 1-create.json) +shortdid=$(echo -n "$longdid" | sed 's/:[^:]*$//') +didsuffix=$(echo -n "$shortdid" | sed 's/.*://') +echo "Sidetree DID Suffix: $didsuffix" +echo "DID (long-form/unpublished): $longdid" +echo "DID (short-form/canonical): $shortdid" + +# Now that the DID suffix is set, rename the directory to it. +cd .. +mv "$dir" "$didsuffix" +cd "$didsuffix" + + +# Submit Create operation +read -p "Press enter to submit Create operation. " _ +didkit did-submit-tx < 1-create.json + +# Construct DID URL for new service endpoint +svc="$shortdid#svc-1" + +# Construct Update operation to add verification key to DID document +didkit did-update -U 1-u.jwk -u 2-u.jwk \ + set-service "$svc" \ + -t DIDKitDemoService \ + -e https://demo.didkit.dev/ \ + | tee 2-update.json +# Submit Update operation +read -p 'Press enter to submit Update operation. ' _ +if ! didkit did-submit-tx < 2-update.json +then + read -p 'Press enter to retry submit operation. ' _ + didkit did-submit-tx < 2-update.json +fi + diff --git a/cli/tests/ion-create-update-vm.sh b/cli/tests/ion-create-update-vm.sh new file mode 100755 index 00000000..f105790b --- /dev/null +++ b/cli/tests/ion-create-update-vm.sh @@ -0,0 +1,57 @@ +#!/bin/sh +set -e +cd "$(dirname "$0")" + +# Create and use temp directory if does not already exist +dir=did-ion-test +if test -d "$dir" +then + printf 'Directory already exists: %s\n' "$dir" >&2 + exit 1 +fi +mkdir "$dir" +cd "$dir" + +# Generate initial update keypair and initial recovery keypair +didkit key generate secp256k1 > 1-u.jwk +didkit key generate secp256k1 > 1-r.jwk +# Construct Create operation with initial public keys +didkit did-create ion -r 1-r.jwk -u 1-u.jwk | tee 1-create.json +# Get long-form and short-form DID from Create operation +longdid=$(didkit did-from-tx < 1-create.json) +shortdid=$(echo -n "$longdid" | sed 's/:[^:]*$//') +didsuffix=$(echo -n "$shortdid" | sed 's/.*://') +echo "Sidetree DID Suffix: $didsuffix" +echo "DID (long-form/unpublished): $longdid" +echo "DID (short-form/canonical): $shortdid" + +# Now that the DID suffix is set, rename the directory to it. +cd .. +mv "$dir" "$didsuffix" +cd "$didsuffix" + +# Submit Create operation +export DID_ION_API_URL=http://localhost:3000/ +read -p "Press enter to submit Create operation. " _ +didkit did-submit-tx < 1-create.json + +# Generate verification key to be added to DID document +didkit key generate secp256k1 > 2-v.jwk +# Construct DID URL for new verification method +vm="$shortdid#key-1" + +# Generate new update key keypair +didkit key generate secp256k1 > 2-u.jwk +# Construct Update operation to add verification key to DID document +didkit did-update -U 1-u.jwk -u 2-u.jwk \ + set-verification-method "$vm" -k 2-v.jwk \ + --authentication --assertionMethod -t JsonWebKey2020 \ + | tee 2-update.json +# Submit Update operation +read -p 'Press enter to submit Update operation. ' _ +if ! didkit did-submit-tx < 2-update.json +then + read -p 'Press enter to retry submit operation. ' _ + didkit did-submit-tx < 2-update.json +fi +