-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Stardust DID Method Proof-of-Concept (#940)
* Stardust Alias Output DID Method proof-of-concept * Add helper functions * Add resolution proof-of-concept, use state metadata instead * Simplify builder usage * Use state index as sentinel value * Add TODO * Rename identity-stardust to identity_stardust * Update to latest iota-client commit, remove unused dependencies * Fix toml formatting * Add identity_stardust to GitHub Actions CI * Fix build-and-test-stardust run condition
- Loading branch information
Showing
10 changed files
with
501 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
[package] | ||
name = "identity_stardust" | ||
version = "0.6.0" | ||
authors = ["IOTA Stiftung"] | ||
edition = "2021" | ||
homepage = "https://www.iota.org" | ||
keywords = ["iota", "tangle", "stardust", "identity"] | ||
license = "Apache-2.0" | ||
readme = "../README.md" | ||
repository = "https://github.com/iotaledger/identity.rs" | ||
description = "An IOTA Ledger integration for the identity.rs library." | ||
|
||
[workspace] | ||
|
||
[dependencies] | ||
identity_core = { version = "=0.6.0", path = "../identity_core", default-features = false } | ||
identity_credential = { version = "=0.6.0", path = "../identity_credential", default-features = false } | ||
identity_did = { version = "=0.6.0", path = "../identity_did", default-features = false } | ||
lazy_static = { version = "1.4", default-features = false } | ||
serde = { version = "1.0", default-features = false, features = ["std", "derive"] } | ||
strum = { version = "0.21", features = ["derive"] } | ||
thiserror = { version = "1.0", default-features = false } | ||
|
||
[dependencies.iota-client] | ||
git = "https://github.com/iotaledger/iota.rs" | ||
rev = "4e7db070a05321c4bd7579acdcc74436865235c0" # develop branch, 2022-07-11 | ||
features = ["tls"] | ||
default-features = false | ||
|
||
[dev-dependencies] | ||
anyhow = { version = "1.0.57" } | ||
iota-crypto = { version = "0.11.0", default-features = false, features = ["bip39", "bip39-en"] } | ||
proptest = { version = "1.0.0", default-features = false, features = ["std"] } | ||
tokio = { version = "1.17.0", default-features = false, features = ["macros"] } | ||
|
||
[package.metadata.docs.rs] | ||
# To build locally: | ||
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps --workspace --open | ||
all-features = true | ||
rustdoc-args = ["--cfg", "docsrs"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# IOTA Stardust Identity Library | ||
|
||
This is a work-in-progress intended to replace the `did:iota` DID Method. | ||
|
||
`cargo run --example create_did` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
use identity_core::convert::ToJson; | ||
use iota_client::bee_block::output::feature::IssuerFeature; | ||
use iota_client::bee_block::output::feature::MetadataFeature; | ||
use iota_client::bee_block::output::feature::SenderFeature; | ||
use iota_client::bee_block::output::unlock_condition::GovernorAddressUnlockCondition; | ||
use iota_client::bee_block::output::unlock_condition::StateControllerAddressUnlockCondition; | ||
use iota_client::bee_block::output::unlock_condition::UnlockCondition; | ||
use iota_client::bee_block::output::AliasId; | ||
use iota_client::bee_block::output::AliasOutputBuilder; | ||
use iota_client::bee_block::output::ByteCostConfig; | ||
use iota_client::bee_block::output::Feature; | ||
use iota_client::bee_block::output::Output; | ||
use iota_client::constants::SHIMMER_TESTNET_BECH32_HRP; | ||
use iota_client::secret::mnemonic::MnemonicSecretManager; | ||
use iota_client::secret::SecretManager; | ||
use iota_client::Client; | ||
|
||
use identity_stardust::StardustDocument; | ||
|
||
// PROBLEMS SO FAR: | ||
// 1) Alias Id is inferred from the block, so we have to use a placeholder DID for creation. | ||
// 2) Cannot get an Output Id back from an Alias Id (hash of Output Id), need to use Indexer API. | ||
// 3) The Output response from the Indexer is an Output, not a Block, so cannot infer Alias ID from it (fine since we | ||
// use the ID to retrieve the Output in the first place). The OutputDto conversion is annoying too. | ||
// 4) The pieces needed to publish an update are fragmented (Output ID for input, amount, document), bit annoying to | ||
// reconstruct. Use a holder struct like Holder { AliasOutput, StardustDocument } with convenience functions? | ||
// 5) Inferred fields such as the controller and governor need to reflect in the (JSON) Document but excluded from the | ||
// StardustDocument serialization when published. Handle with a separate `pack` function like before? | ||
|
||
/// Demonstrate how to embed a DID Document in an Alias Output. | ||
/// | ||
/// iota.rs alias example: | ||
/// https://github.com/iotaledger/iota.rs/blob/f945ccf326829a418334942ae9cf53b8fab3dbe5/examples/outputs/alias.rs | ||
/// | ||
/// iota.js mint-nft example: | ||
/// https://github.com/iotaledger/iota.js/blob/79a71d3a2ad03be5bd6148689d083947f3b98476/packages/iota/examples/mint-nft/src/index.ts | ||
#[tokio::main] | ||
async fn main() -> anyhow::Result<()> { | ||
// let endpoint = "http://localhost:14265"; | ||
let endpoint = "https://api.alphanet.iotaledger.net"; | ||
let faucet_manual = "https://faucet.alphanet.iotaledger.net"; | ||
|
||
// =========================================================================== | ||
// Step 1: Create or load your wallet. | ||
// =========================================================================== | ||
|
||
// let keypair = identity_core::crypto::KeyPair::new(identity_core::crypto::KeyType::Ed25519).unwrap(); | ||
// println!("PrivateKey: {}", keypair.private().to_string()); | ||
// let mnemonic = | ||
// iota_client::crypto::keys::bip39::wordlist::encode(keypair.private().as_ref(),&bip39::wordlist::ENGLISH).unwrap(); | ||
|
||
// NOTE: this is just a randomly generated mnemonic, REMOVE THIS, never actually commit your seed or mnemonic. | ||
let mnemonic = "veteran provide abstract express quick another fee dragon trend extend cotton tail dog truly angle napkin lunch dinosaur shrimp odor gain bag media mountain"; | ||
println!("Mnemonic: {}", mnemonic); | ||
let secret_manager = SecretManager::Mnemonic(MnemonicSecretManager::try_from_mnemonic(mnemonic)?); | ||
|
||
// Create a client instance. | ||
let client = Client::builder() | ||
.with_node(endpoint)? | ||
.with_node_sync_disabled() | ||
.finish() | ||
.await?; | ||
|
||
let address = client.get_addresses(&secret_manager).with_range(0..1).get_raw().await?[0]; | ||
let address_bech32 = address.to_bech32(SHIMMER_TESTNET_BECH32_HRP); | ||
println!("Wallet address: {address_bech32}"); | ||
|
||
println!("INTERACTION REQUIRED: request faucet funds to the above wallet from {faucet_manual}"); | ||
// let faucet_auto = format!("{endpoint}/api/plugins/faucet/v1/enqueue"); | ||
// iota_client::request_funds_from_faucet(&faucet_auto, &address_bech32).await?; | ||
// tokio::time::sleep(std::time::Duration::from_secs(15)).await; | ||
|
||
// =========================================================================== | ||
// Step 2: Create and publish a DID Document in an Alias Output. | ||
// =========================================================================== | ||
|
||
// Create an empty DID Document. | ||
// All new Stardust DID Documents initially use a placeholder DID, | ||
// "did:stardust:0x00000000000000000000000000000000". | ||
let document: StardustDocument = StardustDocument::new(); | ||
println!("DID Document {:#}", document); | ||
|
||
// Create a new Alias Output with the DID Document as state metadata. | ||
let byte_cost_config: ByteCostConfig = client.get_byte_cost_config().await?; | ||
let alias_output: Output = AliasOutputBuilder::new_with_minimum_storage_deposit(byte_cost_config, AliasId::null())? | ||
.with_state_index(0) | ||
.with_foundry_counter(0) | ||
.with_state_metadata(document.to_json_vec()?) | ||
.add_feature(Feature::Sender(SenderFeature::new(address))) | ||
.add_feature(Feature::Metadata(MetadataFeature::new(vec![1, 2, 3])?)) | ||
.add_immutable_feature(Feature::Issuer(IssuerFeature::new(address))) | ||
.add_unlock_condition(UnlockCondition::StateControllerAddress( | ||
StateControllerAddressUnlockCondition::new(address), | ||
)) | ||
.add_unlock_condition(UnlockCondition::GovernorAddress(GovernorAddressUnlockCondition::new( | ||
address, | ||
))) | ||
.finish_output()?; | ||
println!("Deposit amount: {}", alias_output.amount()); | ||
|
||
// Publish to the Tangle ledger. | ||
let block = client | ||
.block() | ||
.with_secret_manager(&secret_manager) | ||
.with_outputs(vec![alias_output])? | ||
.finish() | ||
.await?; | ||
println!( | ||
"Transaction with new alias output sent: {endpoint}/api/v2/blocks/{}", | ||
block.id() | ||
); | ||
let _ = client.retry_until_included(&block.id(), None, None).await?; | ||
|
||
// Infer DID from Alias Output block. | ||
let did = StardustDocument::did_from_block(&block)?; | ||
println!("DID: {did}"); | ||
|
||
// =========================================================================== | ||
// Step 3: Resolve a DID Document. | ||
// =========================================================================== | ||
// iota.rs indexer example: | ||
// https://github.com/iotaledger/iota.rs/blob/f945ccf326829a418334942ae9cf53b8fab3dbe5/examples/indexer.rs | ||
|
||
// Extract Alias ID from DID. | ||
let alias_id: AliasId = StardustDocument::did_to_alias_id(&did)?; | ||
println!("Alias ID: {alias_id}"); | ||
|
||
// Query Indexer INX Plugin for the Output of the Alias ID. | ||
let output_id = client.alias_output_id(alias_id).await?; | ||
println!("Output ID: {output_id}"); | ||
let response = client.get_output(&output_id).await?; | ||
let output = Output::try_from(&response.output)?; | ||
println!("Output: {output:?}"); | ||
|
||
// The resolved DID Document replaces the placeholder DID with the correct one. | ||
let resolved_document = StardustDocument::deserialize_from_output(&alias_id, &output)?; | ||
println!("Resolved Document: {resolved_document:#}"); | ||
|
||
let alias_output = match output { | ||
Output::Alias(output) => Ok(output), | ||
_ => Err(anyhow::anyhow!("not an alias output")), | ||
}?; | ||
|
||
// =========================================================================== | ||
// Step 4: Publish an updated Alias ID. (optional) | ||
// =========================================================================== | ||
// TODO: we could always publish twice on creation to populate the DID (could fail), | ||
// or just infer the DID during resolution (safer). | ||
|
||
// Update the Alias Output to contain an explicit ID and DID. | ||
let updated_alias_output = AliasOutputBuilder::from(&alias_output) // Not adding any content, previous amount will cover the deposit. | ||
// Set the explicit Alias ID. | ||
.with_alias_id(alias_id) | ||
// Update the DID Document content to replace the placeholder DID. | ||
.with_state_metadata(resolved_document.to_json_vec()?) | ||
// State controller updates increment the state index. | ||
.with_state_index(alias_output.state_index() + 1) | ||
.finish_output()?; | ||
|
||
println!("Updated output: {updated_alias_output:?}"); | ||
|
||
let block = client | ||
.block() | ||
.with_secret_manager(&secret_manager) | ||
.with_input(output_id.into())? | ||
.with_outputs(vec![updated_alias_output])? | ||
.finish() | ||
.await?; | ||
|
||
println!( | ||
"Transaction with alias id set sent: {endpoint}/api/v2/blocks/{}", | ||
block.id() | ||
); | ||
let _ = client.retry_until_included(&block.id(), None, None).await?; | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// Copyright 2020-2022 IOTA Stiftung | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
pub type Result<T, E = Error> = core::result::Result<T, E>; | ||
|
||
// TODO: replace all variants with specific errors? | ||
#[derive(Debug, thiserror::Error, strum::IntoStaticStr)] | ||
pub enum Error { | ||
#[error("{0}")] | ||
CoreError(#[from] identity_core::Error), | ||
#[error("{0}")] | ||
CredError(#[from] identity_credential::Error), | ||
#[error("{0}")] | ||
InvalidDID(#[from] identity_did::did::DIDError), | ||
#[error("{0}")] | ||
InvalidDoc(#[from] identity_did::Error), | ||
#[error("{0}")] | ||
ClientError(#[from] iota_client::error::Error), | ||
#[error("{0}")] | ||
BeeError(#[from] iota_client::bee_block::Error), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Copyright 2020-2022 IOTA Stiftung | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
#![forbid(unsafe_code)] | ||
#![allow(clippy::upper_case_acronyms)] | ||
|
||
pub use self::error::Error; | ||
pub use self::error::Result; | ||
|
||
pub use stardust_document::StardustDocument; | ||
|
||
mod error; | ||
mod stardust_document; |
Oops, something went wrong.