Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ jobs:
}}

steps:


- name: git configure long path
if: matrix.os == 'windows-latest'
run: git config --global core.longpaths true
- uses: actions/checkout@v3

- name: Ensure, OpenSSL is available in Windows
Expand Down
9 changes: 9 additions & 0 deletions bindings/wasm/identity_wasm/examples/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import { CoreClientReadOnly } from "@iota/iota-interaction-ts/node/core_client";
import { IotaClient, TransactionEffects } from "@iota/iota-sdk/client";
import { getFaucetHost, requestIotaFromFaucetV0 } from "@iota/iota-sdk/faucet";
import { IotaEvent } from "@iota/iota-sdk/src/client/types/generated";
import { Transaction as SdkTransaction } from "@iota/iota-sdk/transactions";

export const IOTA_IDENTITY_PKG_ID = globalThis?.process?.env?.IOTA_IDENTITY_PKG_ID || "";
Expand Down Expand Up @@ -113,4 +114,12 @@ export class SendZeroCoinTx implements Transaction<string> {
async apply(effects: TransactionEffects, client: CoreClientReadOnly): Promise<string> {
return effects.created![0].reference.objectId;
}

async applyWithEvents(
effects: TransactionEffects,
_events: IotaEvent[],
client: CoreClientReadOnly,
): Promise<string> {
return this.apply(effects, client);
}
}
4 changes: 2 additions & 2 deletions bindings/wasm/identity_wasm/src/iota/iota_did.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ impl WasmIotaDID {
WasmDIDUrl::from(self.0.to_url())
}

/// Returns the hex-encoded AliasId with a '0x' prefix, from the DID tag.
#[wasm_bindgen(js_name = toAliasId)]
/// Returns the hex-encoded ObjectID with a '0x' prefix, from the DID tag.
#[wasm_bindgen(js_name = toObjectID)]
pub fn to_object_id(&self) -> String {
self.0.to_string()
}
Expand Down
4 changes: 2 additions & 2 deletions bindings/wasm/identity_wasm/src/rebased/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,8 @@ impl WasmOnChainIdentity {
transfer_map: Vec<StringCouple>,
expiration_epoch: Option<u64>,
) -> Result<WasmTransactionBuilder> {
let tx = WasmCreateSendProposal::new(self, controller_token, transfer_map, expiration_epoch)
.map_err(|e| WasmError::from(e))?;
let tx =
WasmCreateSendProposal::new(self, controller_token, transfer_map, expiration_epoch).map_err(WasmError::from)?;
Ok(WasmTransactionBuilder::new(JsValue::from(tx).unchecked_into()))
}

Expand Down
17 changes: 17 additions & 0 deletions bindings/wasm/identity_wasm/src/rebased/wasm_identity_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,23 @@ impl WasmIdentityClient {
Ok(WasmIotaDocument(Rc::new(IotaDocumentLock::new(document))))
}

/// Returns the list of DIDs the given address can access as a controller.
/// # Errors
/// @throws {QueryControlledDidsError} when the underlying RPC calls fail;
/// @throws {Error} when the passed `address` string is not a valid IOTA address.
#[wasm_bindgen(js_name = didsControlledBy)]
pub async fn dids_controlled_by(&self, address: &str) -> std::result::Result<Vec<WasmIotaDID>, js_sys::Error> {
self.read_only().dids_controlled_by(address).await
}

/// Returns the list of DIDs the address wrapped by this client can access as a controller.
/// # Errors
/// @throws {QueryControlledDidsError} when the underlying RPC calls fail;
#[wasm_bindgen(js_name = controlledDids)]
pub async fn controlled_dids(&self) -> std::result::Result<Vec<WasmIotaDID>, js_sys::Error> {
self.dids_controlled_by(&self.address().to_string()).await
}

#[wasm_bindgen(
js_name = publishDidDocument,
unchecked_return_type = "TransactionBuilder<PublishDidDocument>"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// Copyright 2020-2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use std::error::Error;
use std::rc::Rc;
use std::str::FromStr;

use identity_iota::iota::rebased::client::IdentityClientReadOnly;
use identity_iota::iota::rebased::migration::Identity;
use identity_iota::iota_interaction::types::base_types::ObjectID;
use iota_interaction::types::base_types::IotaAddress;
use iota_interaction_ts::bindings::WasmIotaClient;
use product_common::core_client::CoreClientReadOnly as _;
use wasm_bindgen::prelude::*;
Expand Down Expand Up @@ -113,4 +115,53 @@ impl WasmIdentityClientReadOnly {
.map_err(|err| JsError::new(&format!("failed to resolve identity by object id; {err:?}")))?;
Ok(IdentityContainer(inner_value))
}

/// Returns the list of DIDs the given address can access as a controller.
/// # Errors
/// @throws {QueryControlledDidsError} when the underlying RPC calls fail;
/// @throws {Error} when the passed `address` string is not a valid IOTA address.
#[wasm_bindgen(js_name = didsControlledBy)]
pub async fn dids_controlled_by(&self, address: &str) -> Result<Vec<WasmIotaDID>, js_sys::Error> {
let address = IotaAddress::from_str(address).map_err(|e| js_sys::Error::new(&format!("{e:#}")))?;
let dids = self
.0
.dids_controlled_by(address)
.await
.map_err(|e| {
let address = e.address.to_string();
let source = js_sys::Error::new(&format!("{:#}", e.source().unwrap()));
WasmQueryControlledDidsError::new(&address, source)
})?
.into_iter()
.map(WasmIotaDID)
.collect();

Ok(dids)
}
}

#[wasm_bindgen(typescript_custom_section)]
const WASM_QUERY_CONTROLLED_DIDS_ERROR: &str = r#"
/**
* Error that may occur when querying the DIDs controlled by a given address.
* @extends Error
*/
export class QueryControlledDidsError extends Error {
/** The IOTA address that was being queried */
address: string;
/** @costructor */
constructor(address: string, source: Error) {
const msg = `failed to query the DIDs controlled by address \`${address}\``;
this.address = address;
super(msg, { cause: source });
}
}
"#;

#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(typescript_type = QueryControlledDidsError, extends = js_sys::Error)]
pub type WasmQueryControlledDidsError;
#[wasm_bindgen(constructor)]
pub fn new(address: &str, source: js_sys::Error) -> WasmQueryControlledDidsError;
}
15 changes: 11 additions & 4 deletions identity_credential/src/sd_jwt_vc/presentation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ impl SdJwtVcPresentationBuilder {
/// Prepare a presentation for a given [`SdJwtVc`].
pub fn new(token: SdJwtVc, hasher: &dyn Hasher) -> Result<Self> {
let SdJwtVc {
sd_jwt,
parsed_claims: vc_claims,
mut sd_jwt,
parsed_claims: mut vc_claims,
} = token;
// Make sure to set the parsed claims back into the SD-JWT Token.
// The reason we do this is to make sure that the underlying SdJwtPresetationBuilder
// that operates on the wrapped SdJwt token can handle the claims.
std::mem::swap(sd_jwt.claims_mut(), &mut vc_claims.sd_jwt_claims);
let builder = sd_jwt.into_presentation(hasher).map_err(Error::SdJwt)?;

Ok(Self { vc_claims, builder })
Expand All @@ -47,8 +51,11 @@ impl SdJwtVcPresentationBuilder {
}

/// Returns the resulting [`SdJwtVc`] together with all removed disclosures.
pub fn finish(self) -> Result<(SdJwtVc, Vec<Disclosure>)> {
let (sd_jwt, disclosures) = self.builder.finish()?;
pub fn finish(mut self) -> Result<(SdJwtVc, Vec<Disclosure>)> {
let (mut sd_jwt, disclosures) = self.builder.finish()?;
// Move the token's claim back into parsed VC claims.
std::mem::swap(sd_jwt.claims_mut(), &mut self.vc_claims.sd_jwt_claims);

Ok((SdJwtVc::new(sd_jwt, self.vc_claims), disclosures))
}
}
1 change: 1 addition & 0 deletions identity_credential/src/sd_jwt_vc/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use serde_json::Value;
use super::resolver;
use super::Resolver;

mod presentation;
mod validation;

pub(crate) const ISSUER_SECRET: &[u8] = b"0123456789ABCDEF0123456789ABCDEF";
Expand Down
48 changes: 48 additions & 0 deletions identity_credential/src/sd_jwt_vc/tests/presentation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2020-2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use identity_core::common::Timestamp;
use identity_core::common::Url;
use sd_jwt_payload_rework::Sha256Hasher;
use serde_json::json;

use crate::sd_jwt_vc::tests::TestSigner;
use crate::sd_jwt_vc::SdJwtVcBuilder;

#[tokio::test]
async fn test_sd_jwt_presentation_builder() -> anyhow::Result<()> {
let credential = SdJwtVcBuilder::new(json!({
"name": "John Doe",
"address": {
"street_address": "A random street",
"number": "3a"
},
"degree": []
}))?
.header(std::iter::once(("kid".to_string(), serde_json::Value::String("key1".to_string()))).collect())
.vct("https://example.com/education_credential".parse::<Url>()?)
.iat(Timestamp::now_utc())
.iss("https://example.com".parse()?)
.make_concealable("/address/street_address")?
.make_concealable("/address")?
.finish(&TestSigner, "HS256")
.await?;

let (concealed_address_credential, conceiled_disclosures) = credential
.into_presentation(&Sha256Hasher::new())?
.conceal("/address")?
.finish()?;

// Object "address" has been omitted from the credential.
assert!(!concealed_address_credential.claims().contains_key("address"));
// Concealable "address" and its sub-property "street_address" have both been concealed.
assert_eq!(
conceiled_disclosures
.iter()
.map(|disclosure| disclosure.claim_name.as_deref().unwrap())
.collect::<Vec<_>>(),
vec!["street_address", "address"]
);

Ok(())
}
2 changes: 2 additions & 0 deletions identity_iota_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ iota-crypto = { version = "0.23", optional = true }
itertools = { version = "0.13.0", optional = true }
phf = { version = "0.11.2", features = ["macros"] }

async-stream = { version = "0.3", optional = true }
rand = { version = "0.8.5", optional = true }
secret-storage = { git = "https://github.com/iotaledger/secret-storage.git", tag = "v0.3.0", default-features = false, optional = true }
serde-aux = { version = "4.5.0", optional = true }
Expand Down Expand Up @@ -98,6 +99,7 @@ iota-client = [
"dep:secret-storage",
"dep:serde-aux",
"product_common/transaction",
"dep:async-stream",
]
# Enables an high level integration with IOTA Gas Station.
gas-station = ["product_common/gas-station"]
Expand Down
18 changes: 18 additions & 0 deletions identity_iota_core/src/rebased/client/full_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use std::ops::Deref;

use crate::iota_interaction_adapter::IotaClientAdapter;
use crate::rebased::client::QueryControlledDidsError;
use crate::rebased::iota::move_calls;
use crate::rebased::iota::package::identity_package_id;
use crate::rebased::migration::CreateIdentity;
Expand All @@ -12,6 +13,7 @@ use crate::IotaDocument;
use crate::StateMetadataDocument;
use crate::StateMetadataEncoding;
use async_trait::async_trait;
use futures::Stream;
use identity_verification::jwk::Jwk;
use iota_interaction::move_types::language_storage::StructTag;
use iota_interaction::rpc_types::IotaObjectData;
Expand Down Expand Up @@ -122,6 +124,22 @@ impl<S> IdentityClient<S> {
{
AuthenticatedAssetBuilder::new(content)
}

/// Returns the [IotaAddress] wrapped by this client.
#[inline(always)]
pub fn address(&self) -> IotaAddress {
IotaAddress::from(&self.public_key)
}

/// Returns the list of **all** unique DIDs the address wrapped by this client can access as a controller.
pub async fn controlled_dids(&self) -> Result<Vec<IotaDID>, QueryControlledDidsError> {
self.dids_controlled_by(self.address()).await
}

/// Returns a stream yielding the unique DIDs the address wrapped by this client can access as a controller.
pub fn controlled_dids_streamed(&self) -> impl Stream<Item = Result<IotaDID, QueryControlledDidsError>> + use<'_, S> {
self.streamed_dids_controlled_by(self.address())
}
}

impl<S> IdentityClient<S>
Expand Down
Loading