Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Borrow an Identity's ControllerCap in order to perform operations on sub-Identities #1454

Merged
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion identity_iota_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ iota-sdk = { git = "https://github.com/iotaledger/iota.git", package = "iota-sdk
itertools = { version = "0.13.0", optional = true }
move-core-types = { git = "https://github.com/iotaledger/iota.git", package = "move-core-types", rev = "39c83ddcf07894cdee2abd146381d8704205e6e9", optional = true }
rand = { version = "0.8.5", optional = true }
secret-storage = { git = "https://github.com/iotaledger/secret-storage.git", branch = "main", optional = true }
secret-storage = { git = "https://github.com/iotaledger/secret-storage.git", tag = "v0.1.0", optional = true }
serde-aux = { version = "4.5.0", optional = true }
shared-crypto = { git = "https://github.com/iotaledger/iota.git", package = "shared-crypto", rev = "39c83ddcf07894cdee2abd146381d8704205e6e9", optional = true }
tokio = { version = "1.29.0", default-features = false, optional = true, features = ["macros", "sync", "rt", "process"] }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module iota_identity::controller {

public use fun delete_controller_cap as ControllerCap.delete;
public use fun delete_delegation_token as DelegationToken.delete;
public use fun delegation_token_id as DelegationToken.id;

/// This `ControllerCap` cannot delegate access.
const ECannotDelegate: u64 = 0;
Expand Down Expand Up @@ -61,6 +62,11 @@ module iota_identity::controller {
controller: ID,
}

/// Returns the ID of this `DelegationToken`.
public fun delegation_token_id(self: &DelegationToken): ID {
self.id.to_inner()
}

/// Returns the controller's ID of this `DelegationToken`.
public fun controller(self: &DelegationToken): ID {
self.controller
Expand Down
55 changes: 44 additions & 11 deletions identity_iota_core/packages/iota_identity/sources/identity.move
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module iota_identity::identity {
transfer_proposal::{Self, Send},
borrow_proposal::{Self, Borrow},
did_deactivation_proposal::{Self, DidDeactivation},
controller_proposal::{Self, ControllerExecution},
};

const ENotADidDocument: u64 = 0;
Expand Down Expand Up @@ -181,7 +182,7 @@ module iota_identity::identity {
self.execute_deactivation(cap, proposal_id, clock, ctx);
option::none()
} else {
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, false);
emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, false);
option::some(proposal_id)
}
}
Expand All @@ -202,7 +203,37 @@ module iota_identity::identity {
self.did_doc.set_controlled_value(vector[]);
self.updated = clock.timestamp_ms();

emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, true);
emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, true);
}

/// Creates a new `ControllerExecution` proposal.
public fun propose_controller_execution(
self: &mut Identity,
cap: &DelegationToken,
controller_cap_id: ID,
expiration: Option<u64>,
ctx: &mut TxContext,
): ID {
let identity_address = self.id().to_address();
let proposal_id = self.did_doc.create_proposal(
cap,
controller_proposal::new(controller_cap_id, identity_address),
expiration,
ctx,
);

emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, false);
proposal_id
}

/// Borrow the identity-owned controller cap specified in `ControllerExecution`.
/// The borrowed cap must be put back by calling `controller_proposal::put_back`.
public fun borrow_controller_cap(
self: &mut Identity,
action: &mut Action<ControllerExecution>,
receiving: Receiving<ControllerCap>,
): ControllerCap {
controller_proposal::receive(action, &mut self.id, receiving)
}

/// Proposes an update to the DID Document contained in this `Identity`.
Expand Down Expand Up @@ -232,7 +263,7 @@ module iota_identity::identity {
self.execute_update(cap, proposal_id, clock, ctx);
option::none()
} else {
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, false);
emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, false);
option::some(proposal_id)
}
}
Expand All @@ -253,7 +284,7 @@ module iota_identity::identity {
);

self.updated = clock.timestamp_ms();
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, true);
emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, true);
}

/// Proposes to update this `Identity`'s AC.
Expand Down Expand Up @@ -287,7 +318,7 @@ module iota_identity::identity {
self.execute_config_change(cap, proposal_id, ctx);
option::none()
} else {
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, false);
emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, false);
option::some(proposal_id)
}
}
Expand All @@ -305,7 +336,7 @@ module iota_identity::identity {
proposal_id,
ctx,
);
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, true);
emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, true);
}

/// Proposes the transfer of a set of objects owned by this `Identity`.
Expand All @@ -316,7 +347,7 @@ module iota_identity::identity {
objects: vector<ID>,
recipients: vector<address>,
ctx: &mut TxContext,
) {
): ID {
let proposal_id = transfer_proposal::propose_send(
&mut self.did_doc,
cap,
Expand All @@ -325,7 +356,8 @@ module iota_identity::identity {
recipients,
ctx
);
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, false);
emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, false);
proposal_id
}

/// Sends one object among the one specified in a `Send` proposal.
Expand All @@ -345,7 +377,7 @@ module iota_identity::identity {
expiration: Option<u64>,
objects: vector<ID>,
ctx: &mut TxContext,
) {
): ID {
let identity_address = self.id().to_address();
let proposal_id = borrow_proposal::propose_borrow(
&mut self.did_doc,
Expand All @@ -355,7 +387,8 @@ module iota_identity::identity {
identity_address,
ctx,
);
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, false);
emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, false);
proposal_id
}

/// Takes one of the borrowed assets.
Expand Down Expand Up @@ -390,7 +423,7 @@ module iota_identity::identity {
proposal_id: ID,
ctx: &mut TxContext,
): Action<T> {
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, true);
emit_proposal_event(self.id().to_inner(), cap.id(), proposal_id, true);
self.did_doc.execute_proposal(cap, proposal_id, ctx)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ module iota_identity::multicontroller {
/// Destroys a `ControllerCap`. Can only be used after a controller has been removed from
/// the controller committee.
public fun destroy_controller_cap<V>(self: &Multicontroller<V>, cap: ControllerCap) {
assert!(self.controllers.contains(&cap.id().to_inner()), EInvalidController);
assert!(!self.controllers.contains(&cap.id().to_inner()), EInvalidController);

cap.delete();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ module iota_identity::borrow_proposal {
): ID {
let action = Borrow { objects, objects_to_return: vector::empty(), owner };

multi.create_proposal(cap, action,expiration, ctx)
multi.create_proposal(cap, action, expiration, ctx)
}

/// Borrows an asset from this action. This function will fail if:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
module iota_identity::controller_proposal {
use iota::transfer::Receiving;
use iota_identity::controller::{Self, ControllerCap};
use iota_identity::multicontroller::Action;

/// The received `ControllerCap` does not match the one
/// specified in the `ControllerExecution` action.
const EControllerCapMismatch: u64 = 0;
/// The provided `UID` is not the `UID` of the `Identity`
/// specified in the action.
const EInvalidIdentityUID: u64 = 1;

/// Borrow a given `ControllerCap` from an `Identity` for
/// a single transaction.
public struct ControllerExecution has store {
/// ID of the `ControllerCap` to borrow.
controller_cap: ID,
/// The address of the `Identity` that owns
/// the `ControllerCap` we are borrowing.
identity: address,
}

/// Returns a new `ControllerExecution` that - in a Proposal - allows whoever
/// executes it to receive `identity`'s `ControllerCap` (the one that has ID `controller_cap`)
/// for the duration of a single transaction.
public fun new(controller_cap: ID, identity: address): ControllerExecution {
ControllerExecution {
controller_cap,
identity,
}
}

/// Returns the `ControllerCap` specified in this action.
public fun receive(
self: &mut Action<ControllerExecution>,
identity: &mut UID,
cap: Receiving<ControllerCap>
): ControllerCap {
assert!(identity.to_address() == self.borrow().identity, EInvalidIdentityUID);
assert!(cap.receiving_object_id() == self.borrow().controller_cap, EControllerCapMismatch);

controller::receive(identity, cap)
}

/// Consumes a `ControllerExecution` action by returning the borrowed `ControllerCap`
/// to the corresponding `Identity`.
public fun put_back(
action: Action<ControllerExecution>,
cap: ControllerCap,
) {
let ControllerExecution { identity, controller_cap } = action.unwrap();
assert!(object::id(&cap) == controller_cap, EControllerCapMismatch);

cap.transfer(identity);
}
}
4 changes: 2 additions & 2 deletions identity_iota_core/src/rebased/client/read_only.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ impl IdentityClientReadOnly {
.map_err(|e| Error::ObjectLookup(e.to_string()))
}

#[allow(dead_code)]
pub(crate) async fn get_object_ref_by_id(&self, obj: ObjectID) -> Result<Option<OwnedObjectRef>, Error> {
/// Returns an object's [`OwnedObjectRef`], if any.
pub async fn get_object_ref_by_id(&self, obj: ObjectID) -> Result<Option<OwnedObjectRef>, Error> {
self
.read_api()
.get_object_with_options(obj, IotaObjectDataOptions::default().with_owner())
Expand Down
82 changes: 70 additions & 12 deletions identity_iota_core/src/rebased/migration/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ use crate::rebased::proposals::DeactivateDid;
use crate::rebased::proposals::ProposalBuilder;
use crate::rebased::proposals::SendAction;
use crate::rebased::proposals::UpdateDidDocument;
use crate::rebased::proposals::ControllerExecution;
use crate::rebased::sui::move_calls;
use crate::rebased::transaction::Transaction;
use crate::rebased::transaction::TransactionOutput;
Expand Down Expand Up @@ -175,13 +176,18 @@ impl OnChainIdentity {
}

/// Borrows assets owned by this [`OnChainIdentity`] to use them in a custom transaction.
/// # Notes
/// Make sure to call [`super::Proposal::with_intent`] before executing the proposal.
/// Failing to do so will make [`crate::proposals::ProposalT::execute`] return an error.
pub fn borrow_assets(&mut self) -> ProposalBuilder<'_, BorrowAction> {
ProposalBuilder::new(self, BorrowAction::default())
}

/// Borrows a `ControllerCap` with ID `controller_cap` owned by this identity in a transaction.
/// This proposal is used to perform operation on a sub-identity controlled
/// by this one.
pub fn controller_execution(&mut self, controller_cap: ObjectID) -> ProposalBuilder<'_, ControllerExecution> {
let action = ControllerExecution::new(controller_cap, self);
ProposalBuilder::new(self, action)
}

/// Returns historical data for this [`OnChainIdentity`].
pub async fn get_history(
&self,
Expand Down Expand Up @@ -343,17 +349,69 @@ pub async fn get_identity(
return Ok(None);
};

let did = IotaDID::from_alias_id(&object_id.to_string(), client.network());
let (id, multi_controller, created, updated) = match unpack_identity_data(&did, &data)? {
Some(data) => data,
None => {
return Ok(None);
}
let content = data
UMR1352 marked this conversation as resolved.
Show resolved Hide resolved
.content
.ok_or_else(|| Error::ObjectLookup(format!("no content in retrieved object in object id {object_id}")))?;

let IotaParsedData::MoveObject(value) = content else {
return Err(Error::ObjectLookup(format!(
"found data at object id {object_id} is not an object"
)));
};

if !is_identity(&value) {
return Ok(None);
}

#[derive(Deserialize)]
struct TempOnChainIdentity {
id: UID,
did_doc: Multicontroller<Vec<u8>>,
created: Number<u64>,
updated: Number<u64>,
}

let TempOnChainIdentity {
id,
did_doc: multi_controller,
created,
updated,
} = serde_json::from_value::<TempOnChainIdentity>(value.fields.to_json_value()).map_err(|err| {
Error::ObjectLookup(format!(
"could not parse identity document with object id {object_id}; {err}"
))
})?;
let original_did = IotaDID::from_alias_id(id.object_id().to_string().as_str(), client.network());
let controlled_value = multi_controller.controlled_value();
// Parse DID document timestamps
let created = {
let timestamp_ms: u64 = created.try_into().expect("Move string-encoded u64 are valid u64");
// `Timestamp` requires a timestamp expessed in seconds.
Timestamp::from_unix(timestamp_ms as i64 / 1000).expect("On-chain clock produses valid timestamps")
};
let updated = {
let timestamp_ms: u64 = updated.try_into().expect("Move string-encoded u64 are valid u64");
// `Timestamp` requires a timestamp expessed in seconds.
Timestamp::from_unix(timestamp_ms as i64 / 1000).expect("On-chain clock produses valid timestamps")
};

// check if DID has been deactivated
let mut did_doc = if controlled_value.is_empty() {
// DID has been deactivated by setting controlled value empty, therefore craft an empty document
let mut empty_document = IotaDocument::new_with_id(original_did.clone());
empty_document.metadata.deactivated = Some(true);

empty_document
} else {
// we have a value, therefore unpack it
StateMetadataDocument::unpack(controlled_value)
.and_then(|state_metadata_doc| state_metadata_doc.into_iota_document(&original_did))
.map_err(|e| Error::DidDocParsingFailed(e.to_string()))?
};

let did_doc =
IotaDocument::from_iota_document_data(multi_controller.controlled_value(), true, &did, created, updated)
.map_err(|e| Error::DidDocParsingFailed(e.to_string()))?;
// Overwrite `created` and `updated` with trusted value coming from the on-chain `Identity` object.
did_doc.metadata.created = Some(created);
did_doc.metadata.updated = Some(updated);

Ok(Some(OnChainIdentity {
id,
Expand Down
Loading