diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 15f729d4b..eaf1428d9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,6 +18,11 @@ jobs: with: submodules: true + - name: Install additional build dependencies + run: | + sudo apt-get update + sudo apt-get install nettle-dev capnproto + - name: Cache Cargo registry and build artifacts uses: actions/cache@v2 with: diff --git a/.gitignore b/.gitignore index 96ef6c0b9..c185eafdc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /target Cargo.lock + +.vscode diff --git a/Cargo.toml b/Cargo.toml index 5c3054ed8..1073469a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,16 +7,14 @@ license = "Apache-2.0" description = "Core library for Verifiable Credentials and Decentralized Identifiers." repository = "https://github.com/spruceid/ssi/" documentation = "https://docs.rs/ssi/" +resolver = "2" -exclude = [ - "json-ld-api/*", - "json-ld-normalization/*", -] +exclude = ["json-ld-api/*", "json-ld-normalization/*"] [features] default = ["ring"] http-did = ["hyper", "hyper-tls", "http", "percent-encoding", "tokio"] -libsecp256k1 = ["secp256k1"] # backward compatibility +libsecp256k1 = ["secp256k1"] # backward compatibility secp256k1 = ["k256", "rand", "k256/keccak256"] secp256r1 = ["p256", "rand"] ripemd-160 = ["ripemd160", "secp256k1"] @@ -58,7 +56,12 @@ lazy_static = "1.4" combination = "0.1" sha2 = { version = "0.9", optional = true } sha2_old = { package = "sha2", version = "0.8" } -hyper = { version = "0.14", optional = true, features = ["server", "client", "http1", "stream"] } +hyper = { version = "0.14", optional = true, features = [ + "server", + "client", + "http1", + "stream", +] } hyper-tls = { version = "0.5", optional = true } http = { version = "0.2", optional = true } hex = "0.4" @@ -106,7 +109,7 @@ members = [ ] [dev-dependencies] -blake2 = "0.8" # for bbs doctest +blake2 = "0.8" # for bbs doctest uuid = { version = "0.8", features = ["v4", "serde"] } difference = "2.0" did-method-key = { path = "./did-key" } diff --git a/README.md b/README.md index 21e0a0c63..25a848b0a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ -[![](https://img.shields.io/github/workflow/status/spruceid/ssi/ci)](https://github.com/spruceid/ssi/actions?query=workflow%3Aci+branch%3Amain) [![](https://img.shields.io/badge/Rust-v1.51.0-orange)](https://www.rust-lang.org/) [![](https://img.shields.io/badge/License-Apache--2.0-green)](https://github.com/spruceid/didkit/blob/main/LICENSE) [![](https://img.shields.io/twitter/follow/sprucesystems?label=Follow&style=social)](https://twitter.com/sprucesystems) +[![](https://img.shields.io/github/workflow/status/spruceid/ssi/ci)](https://github.com/spruceid/ssi/actions?query=workflow%3Aci+branch%3Amain) +[![](https://img.shields.io/badge/Rust-v1.51.0-orange)](https://www.rust-lang.org/) +[![](https://img.shields.io/badge/License-Apache--2.0-green)](https://github.com/spruceid/didkit/blob/main/LICENSE) +[![](https://img.shields.io/twitter/follow/sprucesystems?label=Follow&style=social)](https://twitter.com/sprucesystems) SSI's documentation is currently packaged with the DIDKit documentation [here](https://spruceid.dev/docs/didkit/). @@ -14,8 +17,9 @@ including embedded systems. This library is embedded in the the cross-platform ![DIDKit core components](https://user-images.githubusercontent.com/37127325/132885372-9cdf586e-ba6f-44c8-8b83-f72f16d86107.png) ## Maturity Disclaimer -In the v0.1 release on January 27th, 2021, SSI has not yet undergone a -formal security audit and to desired levels of confidence for suitable use in + +In the v0.1 release on January 27th, 2021, SSI has not yet undergone a formal +security audit and to desired levels of confidence for suitable use in production systems. This implementation is currently suitable for exploratory work and experimentation only. We welcome feedback on the usability, architecture, and security of this implementation and are committed to a @@ -36,6 +40,18 @@ clang openssl-devel ``` +If using feature `did-webkey/sequoia-openpgp` for PGP support, the following +dependencies are also needed: + +``` +nettle-dev +capnproto +``` + +If using feature +[`did-webkey/crypto-cng`](https://gitlab.com/sequoia-pgp/sequoia#cryptography), +only `capnproto` is needed. + ## Install ### Crates.io diff --git a/did-webkey/Cargo.toml b/did-webkey/Cargo.toml index 782704abc..56d4ed2e9 100644 --- a/did-webkey/Cargo.toml +++ b/did-webkey/Cargo.toml @@ -12,23 +12,48 @@ homepage = "https://github.com/spruceid/ssi/tree/main/did-webkey/" documentation = "https://docs.rs/did-webkey/" [features] +default = ["ssi/ring", "crypto-nettle"] +crypto-cng = ["sequoia-openpgp/crypto-cng"] +crypto-nettle = ["sequoia-openpgp/crypto-nettle"] p256 = ["ssi/p256"] [dependencies] -ssi = { version = "0.3", path = "../", default-features = false } -async-trait = "0.1" -reqwest = { version = "0.11", features = ["json"] } -http = "0.2" -serde_json = "1.0" -serde = { version = "1.0", features = ["derive"] } -sshkeys = "0.3" +ssi = { version = "0.3", path = "../", default-features = false, features = [ + "secp256r1", +] } +anyhow = "1.0.52" +async-trait = "0.1.52" +reqwest = { version = "0.11.9", features = ["json"] } +hex = "0.4.3" +http = "0.2.6" +serde_json = "1.0.75" +serde = { version = "1.0.134", features = ["derive"] } +sshkeys = "0.3.1" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +sequoia-openpgp = { version = "1.7.0", features = [ + "compression-deflate", +], default-features = false, optional = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +sequoia-openpgp = { version = "1.7.0", default-features = false, features = [ + "crypto-rust", + "allow-experimental-crypto", + "allow-variable-time-crypto", +] } + [target.'cfg(target_os = "android")'.dependencies.reqwest] -version = "0.11" +version = "0.11.9" features = ["json", "native-tls-vendored"] [dev-dependencies] -tokio = { version = "1.0", features = ["macros"] } -async-std = { version = "1.9", features = ["attributes"] } -futures = "0.3" -hyper = { version = "0.14", features = ["server", "client", "http1", "stream"] } +tokio = { version = "1.15.0", features = ["macros"] } +async-std = { version = "1.10.0", features = ["attributes"] } +futures = "0.3.19" +hyper = { version = "0.14.16", features = [ + "server", + "client", + "http1", + "stream", +] } diff --git a/did-webkey/src/lib.rs b/did-webkey/src/lib.rs index 973ccc1c3..60ac5f5c1 100644 --- a/did-webkey/src/lib.rs +++ b/did-webkey/src/lib.rs @@ -3,6 +3,15 @@ use core::str::FromStr; use async_trait::async_trait; use serde::{Deserialize, Serialize}; +use anyhow::{anyhow, Context, Result}; +#[cfg(feature = "sequoia-openpgp")] +use openpgp::{ + cert::prelude::*, + parse::{PacketParser, Parse}, + serialize::SerializeInto, +}; +#[cfg(feature = "sequoia-openpgp")] +use sequoia_openpgp as openpgp; use sshkeys::PublicKeyKind; use ssi::did::{DIDMethod, Document, VerificationMethod, VerificationMethodMap, DIDURL}; use ssi::did_resolve::{ @@ -38,26 +47,53 @@ impl FromStr for DIDWebKeyType { } } +#[cfg(feature = "sequoia-openpgp")] fn parse_pubkeys_gpg( - _did: &str, - _bytes: Vec, -) -> Result<(Vec, Vec), String> { - // TODO - Err(String::from("GPG Key Type Not Implemented")) + did: &str, + bytes: Vec, +) -> Result<(Vec, Vec)> { + let mut did_urls = Vec::new(); + let mut vm_maps = Vec::new(); + + let ppr = PacketParser::from_bytes(&bytes)?; + for certo in CertParser::from(ppr) { + let cert = certo?; + let (vm_map, did_url) = gpg_pk_to_vm(did, cert)?; + vm_maps.push(vm_map); + did_urls.push(did_url); + } + + Ok((vm_maps, did_urls)) +} + +#[cfg(feature = "sequoia-openpgp")] +fn gpg_pk_to_vm(did: &str, cert: Cert) -> Result<(VerificationMethodMap, DIDURL)> { + let vm_url = DIDURL { + did: did.to_string(), + fragment: Some(cert.fingerprint().to_string()), + ..Default::default() + }; + + let armored_pgp = String::from_utf8(cert.armored().to_vec()?)?; + + let vm_map = VerificationMethodMap { + id: vm_url.to_string(), + type_: "PgpVerificationKey2021".to_string(), + public_key_pgp: Some(armored_pgp), + controller: did.to_string(), + ..Default::default() + }; + Ok((vm_map, vm_url)) } fn pk_to_vm_ed25519( did: &str, pk: sshkeys::Ed25519PublicKey, -) -> Result<(VerificationMethodMap, DIDURL), String> { - let jwk = match ssh_pkk_to_jwk(&PublicKeyKind::Ed25519(pk)) { - Err(err) => return Err(format!("Unable to convert SSH key to JWK: {}", err)), - Ok(jwk) => jwk, - }; - let thumbprint = match jwk.thumbprint() { - Err(err) => return Err(format!("Unable to calculate JWK thumbprint: {}", err)), - Ok(t) => t, - }; +) -> Result<(VerificationMethodMap, DIDURL)> { + let jwk = ssh_pkk_to_jwk(&PublicKeyKind::Ed25519(pk))?; + let thumbprint = jwk + .thumbprint() + .context("Unable to calculate JWK thumbprint")?; let vm_url = DIDURL { did: did.to_string(), fragment: Some(thumbprint), @@ -76,15 +112,11 @@ fn pk_to_vm_ed25519( fn pk_to_vm_ecdsa( did: &str, pk: sshkeys::EcdsaPublicKey, -) -> Result<(VerificationMethodMap, DIDURL), String> { - let jwk = match ssh_pkk_to_jwk(&PublicKeyKind::Ecdsa(pk)) { - Err(err) => return Err(format!("Unable to convert SSH key to JWK: {}", err)), - Ok(jwk) => jwk, - }; - let thumbprint = match jwk.thumbprint() { - Err(err) => return Err(format!("Unable to calculate JWK thumbprint: {}", err)), - Ok(t) => t, - }; +) -> Result<(VerificationMethodMap, DIDURL)> { + let jwk = ssh_pkk_to_jwk(&PublicKeyKind::Ecdsa(pk))?; + let thumbprint = jwk + .thumbprint() + .context("Unable to calculate JWK thumbprint")?; let vm_url = DIDURL { did: did.to_string(), fragment: Some(thumbprint), @@ -100,18 +132,11 @@ fn pk_to_vm_ecdsa( Ok((vm_map, vm_url)) } -fn pk_to_vm_rsa( - did: &str, - pk: sshkeys::RsaPublicKey, -) -> Result<(VerificationMethodMap, DIDURL), String> { - let jwk = match ssh_pkk_to_jwk(&PublicKeyKind::Rsa(pk)) { - Err(err) => return Err(format!("Unable to convert SSH key to JWK: {}", err)), - Ok(jwk) => jwk, - }; - let thumbprint = match jwk.thumbprint() { - Err(err) => return Err(format!("Unable to calculate JWK thumbprint: {}", err)), - Ok(t) => t, - }; +fn pk_to_vm_rsa(did: &str, pk: sshkeys::RsaPublicKey) -> Result<(VerificationMethodMap, DIDURL)> { + let jwk = ssh_pkk_to_jwk(&PublicKeyKind::Rsa(pk))?; + let thumbprint = jwk + .thumbprint() + .context("Unable to calculate JWK thumbprint")?; let vm_url = DIDURL { did: did.to_string(), fragment: Some(thumbprint), @@ -127,14 +152,11 @@ fn pk_to_vm_rsa( Ok((vm_map, vm_url)) } -fn pk_to_vm_dsa( - _did: &str, - _pk: sshkeys::DsaPublicKey, -) -> Result<(VerificationMethodMap, DIDURL), String> { - Err(String::from("Unsupported DSA Key")) +fn pk_to_vm_dsa(_did: &str, _pk: sshkeys::DsaPublicKey) -> Result<(VerificationMethodMap, DIDURL)> { + Err(anyhow!("Unsupported DSA Key")) } -fn pk_to_vm(did: &str, pk: sshkeys::PublicKey) -> Result<(VerificationMethodMap, DIDURL), String> { +fn pk_to_vm(did: &str, pk: sshkeys::PublicKey) -> Result<(VerificationMethodMap, DIDURL)> { match pk.kind { PublicKeyKind::Rsa(pk) => pk_to_vm_rsa(did, pk), PublicKeyKind::Dsa(pk) => pk_to_vm_dsa(did, pk), @@ -146,28 +168,14 @@ fn pk_to_vm(did: &str, pk: sshkeys::PublicKey) -> Result<(VerificationMethodMap, fn parse_pubkeys_ssh( did: &str, bytes: Vec, -) -> Result<(Vec, Vec), String> { - let lines = match String::from_utf8(bytes) { - Ok(string) => string, - Err(err) => return Err(format!("Unable to parse SSH keys: {}", err)), - }; +) -> Result<(Vec, Vec)> { + let lines = String::from_utf8(bytes)?; let mut did_urls = Vec::new(); let mut vm_maps = Vec::new(); let lines = lines.trim().split('\n'); for line in lines { - let pk = match sshkeys::PublicKey::from_string(line) { - Ok(pk) => pk, - Err(err) => return Err(format!("Unable to parse SSH key: {}", err)), - }; - let (vm_map, did_url) = match pk_to_vm(did, pk) { - Ok(pk) => pk, - Err(err) => { - return Err(format!( - "Unable to convert SSH public key to verification method: {}", - err - )) - } - }; + let pk = sshkeys::PublicKey::from_string(line)?; + let (vm_map, did_url) = pk_to_vm(did, pk)?; vm_maps.push(vm_map); did_urls.push(did_url); } @@ -178,7 +186,7 @@ fn parse_pubkeys( did: &str, type_: DIDWebKeyType, bytes: Vec, -) -> Result<(Vec, Vec), String> { +) -> Result<(Vec, Vec)> { match type_ { DIDWebKeyType::Gpg => parse_pubkeys_gpg(did, bytes), DIDWebKeyType::Ssh => parse_pubkeys_ssh(did, bytes), @@ -348,23 +356,22 @@ mod tests { ); } - // TODO: use JWK fingerprint - const DID_URL: &str = "https://localhost/user.keys"; - const PUBKEYS: &str = include_str!("../tests/ssh_keys"); // localhost web server for serving did:web DID documents. - // TODO: pass arguments here instead of using const - fn web_server() -> Result<(String, impl FnOnce() -> Result<(), ()>), hyper::Error> { + fn web_server( + did_url: &'static str, + pubkeys: &'static str, + ) -> Result<(String, impl FnOnce() -> Result<(), ()>), hyper::Error> { use http::header::{HeaderValue, CONTENT_TYPE}; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Response, Server}; let addr = ([127, 0, 0, 1], 0).into(); - let make_svc = make_service_fn(|_| async move { - Ok::<_, hyper::Error>(service_fn(|req| async move { + let make_svc = make_service_fn(move |_| async move { + Ok::<_, hyper::Error>(service_fn(move |req| async move { let uri = req.uri(); // Skip leading slash let proxied_url: String = uri.path().chars().skip(1).collect(); - if proxied_url == DID_URL { - let body = Body::from(PUBKEYS); + if proxied_url == did_url { + let body = Body::from(pubkeys); let mut response = Response::new(body); response .headers_mut() @@ -375,7 +382,7 @@ mod tests { let (mut parts, body) = Response::::default().into_parts(); parts.status = hyper::StatusCode::NOT_FOUND; let response = Response::from_parts(parts, body); - return Ok::<_, hyper::Error>(response); + Ok::<_, hyper::Error>(response) })) }); let server = Server::try_bind(&addr)?.serve(make_svc); @@ -392,8 +399,12 @@ mod tests { } #[tokio::test] - async fn from_did_webkey() { - let (url, shutdown) = web_server().unwrap(); + async fn from_did_webkey_ssh() { + // TODO: use JWK fingerprint + let did_url: &str = "https://localhost/user.keys"; + let pubkeys: &str = include_str!("../tests/ssh_keys"); + + let (url, shutdown) = web_server(did_url, pubkeys).unwrap(); PROXY.with(|proxy| { proxy.replace(Some(url)); }); @@ -460,4 +471,67 @@ mod tests { }); shutdown().ok(); } + + #[tokio::test] + #[cfg(feature = "sequoia-openpgp")] + async fn from_did_webkey_gpg() { + let did_url: &str = "https://localhost/user.gpg"; + let pubkeys: &str = include_str!("../tests/user.gpg"); + + let (url, shutdown) = web_server(did_url, pubkeys).unwrap(); + PROXY.with(|proxy| { + proxy.replace(Some(url)); + }); + let (res_meta, doc_opt, _doc_meta) = DIDWebKey + .resolve( + "did:webkey:gpg:localhost:user.gpg", + &ResolutionInputMetadata::default(), + ) + .await; + assert_eq!(res_meta.error, None); + + let value_expected = json!({ + "@context": "https://www.w3.org/ns/did/v1", + "assertionMethod": [ + "did:webkey:gpg:localhost:user.gpg#0CEE8B84B25C0A3C554A9EC1F8FEE972E2A1D935", + "did:webkey:gpg:localhost:user.gpg#6BABBD68A84D5FE3CEEB986EB77927AE619B8EB6", + "did:webkey:gpg:localhost:user.gpg#DCB1FF1899328C0EBB5DF07BD41BBBD1FE58006E" + ], + "authentication": [ + "did:webkey:gpg:localhost:user.gpg#0CEE8B84B25C0A3C554A9EC1F8FEE972E2A1D935", + "did:webkey:gpg:localhost:user.gpg#6BABBD68A84D5FE3CEEB986EB77927AE619B8EB6", + "did:webkey:gpg:localhost:user.gpg#DCB1FF1899328C0EBB5DF07BD41BBBD1FE58006E" + ], + "id": "did:webkey:gpg:localhost:user.gpg", + "verificationMethod": [ + { + "controller": "did:webkey:gpg:localhost:user.gpg", + "id": "did:webkey:gpg:localhost:user.gpg#0CEE8B84B25C0A3C554A9EC1F8FEE972E2A1D935", + "publicKeyPgp": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nComment: 0CEE 8B84 B25C 0A3C 554A 9EC1 F8FE E972 E2A1 D935\nComment: Foobar \n\n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\n2YdBRVOAnw==\n=4Vh8\n-----END PGP PUBLIC KEY BLOCK-----\n", + "type": "PgpVerificationKey2021" + }, + { + "controller": "did:webkey:gpg:localhost:user.gpg", + "id": "did:webkey:gpg:localhost:user.gpg#6BABBD68A84D5FE3CEEB986EB77927AE619B8EB6", + "publicKeyPgp": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nComment: 6BAB BD68 A84D 5FE3 CEEB 986E B779 27AE 619B 8EB6\nComment: Foobar \n\nxlIEYd3nnBMIKoZIzj0DAQcCAwRhnJmDiD35LzJXstn4zBMfpavUCSkYzyJKIYHe\nOwW4BFe+AF/ZdczzJnx8O1xndvYOFccVNAz7HMb7xPB7MDcEzRtGb29iYXIgPGZv\nb2JhckBleGFtcGxlLm9yZz7CkAQTEwgAOBYhBGurvWioTV/jzuuYbrd5J65hm462\nBQJh3eecAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJELd5J65hm462BNgB\nAKzxt0M3BpEGlAGjz4czrWX8zRdo6XiKeby5yeORfKDEAP4uOuIwE9ics9XICXUg\n1IZhOVNB2cUS6p7Q5ApaqwE3Wc5WBGHd55wSCCqGSM49AwEHAgMEN0OVHjy6Pwyp\nfTci+EKIc486T1EGeYBs/1FErq3bB44Vqr3EsOcdscSqyj3dcxXb47d0kOkiDPKm\nKTy/6ZPWsAMBCAfCeAQYEwgAIBYhBGurvWioTV/jzuuYbrd5J65hm462BQJh3eec\nAhsMAAoJELd5J65hm462KTsA/3vbivQARQMsZfGKptW/SVaKwszMQm2SE+jOESoH\ntk3MAQCjUD7O3CzMX2rCDgLBLh6hwgB3zjn8uaHM1zO9Z48HhQ==\n=Erc7\n-----END PGP PUBLIC KEY BLOCK-----\n", + "type": "PgpVerificationKey2021" + }, + { + "controller": "did:webkey:gpg:localhost:user.gpg", + "id": "did:webkey:gpg:localhost:user.gpg#DCB1FF1899328C0EBB5DF07BD41BBBD1FE58006E", + "publicKeyPgp": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nComment: DCB1 FF18 9932 8C0E BB5D F07B D41B BBD1 FE58 006E\nComment: Foobar \n\nxjMEYd3nyxYJKwYBBAHaRw8BAQdAp756gWZbZB66yTjjn52DyUvCxUgFG7aSKqYY\n7KG2KvDNG0Zvb2JhciA8Zm9vYmFyQGV4YW1wbGUub3JnPsKQBBMWCAA4FiEE3LH/\nGJkyjA67XfB71Bu70f5YAG4FAmHd58sCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC\nF4AACgkQ1Bu70f5YAG7IMQD7BEg3vAqinv1wllBpXfQov7b4+haxcADWXgmc+06D\nx1QBAMWd6Oa71iKafJKKL3Vgk5q/Sns5+xDvMJmcGbMemckMzjgEYd3nyxIKKwYB\nBAGXVQEFAQEHQECEkuj4GJuUKC0nKvyXoEA1DxJPnASFt2GPC0trMcMoAwEIB8J4\nBBgWCAAgFiEE3LH/GJkyjA67XfB71Bu70f5YAG4FAmHd58sCGwwACgkQ1Bu70f5Y\nAG6eUAEA8vwHBMR4ownA069pQ2EqGhueMoU7YQX0IQBosDf7NrMBAJCoLmuc2dGQ\nT4/C2SFSd3mgOqJXpumOyBFj6hoYkyAI\n=LgN5\n-----END PGP PUBLIC KEY BLOCK-----\n", + "type": "PgpVerificationKey2021" + } + ] + }); + + let doc = doc_opt.unwrap(); + let doc_value = serde_json::to_value(doc).unwrap(); + eprintln!("doc {}", serde_json::to_string_pretty(&doc_value).unwrap()); + assert_eq!(doc_value, value_expected); + PROXY.with(|proxy| { + proxy.replace(None); + }); + shutdown().ok(); + } } diff --git a/did-webkey/tests/user.gpg b/did-webkey/tests/user.gpg new file mode 100644 index 000000000..af6f7246a --- /dev/null +++ b/did-webkey/tests/user.gpg @@ -0,0 +1,59 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +IVJ3eaA6olem6Y7IEWPqGhiTIAg= +=yjPH +-----END PGP PUBLIC KEY BLOCK----- diff --git a/src/did.rs b/src/did.rs index 93fde1661..3c74d110d 100644 --- a/src/did.rs +++ b/src/did.rs @@ -159,6 +159,8 @@ pub struct VerificationMethodMap { // TODO: make sure this JWK does not have private key material pub public_key_jwk: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub public_key_pgp: Option, + #[serde(skip_serializing_if = "Option::is_none")] // TODO: make Base58 type like Base64urlUIntString pub public_key_base58: Option, // TODO: ensure that not both key parameters are set