Newer
Older
use crate::{
commands::revocation::generate_revoc_doc,
runtime::runtime_types::{
common_runtime::entities::IdtyData, pallet_identity::types::*,
pallet_smith_members::types::SmithMeta, pallet_smith_members::SmithStatus,
sp_runtime::MultiSignature,
/// define identity subcommands
#[derive(Clone, Default, Debug, clap::Parser)]
pub enum Subcommand {
/// Show identity
/// (same as get but without arg)
#[default]
Show,
/// Fetch identity
Get {
#[clap(short = 'a', long = "address")]
account_id: Option<AccountId>,
#[clap(short = 'i', long = "identity")]
#[clap(short = 'u', long = "username")]
username: Option<String>,
},
/// Create and certify an identity
///
/// Caller must be member, and the target account must exist.
Create { target: AccountId },
/// Confirm an identity
///
/// To be called by the certified not-yet-member account, to become member.
Confirm { name: String },
/// Request distance evaluation
/// make sure that it's ok otherwise currency is slashed
RequestDistanceEvaluation,
/// Request distance evaluation for unvalidated identity
RequestDistanceEvaluationFor { target: String },
#[clap(alias = "cert")]
Certify { target: String },
/// Renew a certification
#[clap(alias = "renew")]
RenewCert { target: String },
/// Revoke an identity immediately
Revoke,
/// Generate a revocation document for the provided account
GenRevocDoc,
/// Display member count
MemberCount,
/// Link an account to the identity
LinkAccount {
/// Secret key format (seed, substrate)
#[clap(short = 'S', long, default_value = SecretFormat::Substrate)]
secret_format: SecretFormat,
/// Secret of account to link
/// most likely different from the one owning the identity
#[clap(short, long)]
secret: Option<String>,
/// Migrate identity to another account
/// Change Owner Key
ChangeOwnerKey {
/// Secret key format (seed, substrate)
#[clap(short = 'S', long, default_value = SecretFormat::Substrate)]
secret_format: SecretFormat,
/// Secret of account to link
/// most likely different from the one owning the identity
#[clap(short, long)]
secret: Option<String>,
},
}
/// handle identity commands
pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> {
Subcommand::Show => {
data = data.build_indexer().await?;
get_identity(&data, Some(data.address()), None, None).await?
}
Subcommand::Get {
ref account_id,
identity_id,
ref username,
} => {
get_identity(&data, account_id.clone(), identity_id, username.clone()).await?
}
Subcommand::Create { target } => {
create_identity(&data, target).await?;
}
Subcommand::Confirm { name } => {
confirm_identity(&data, name).await?;
}
Subcommand::RequestDistanceEvaluation => {
commands::distance::request_distance_evaluation(&data).await?;
}
Subcommand::RequestDistanceEvaluationFor { target } => {
let target = try_get_idty_index_by_name(&data, &target).await?;
commands::distance::request_distance_evaluation_for(&data, target).await?;
let targetid = try_get_idty_index_by_name(&data, &target).await?;
// ask user to confirm certification
if let Ok(true) = inquire::Confirm::new(&format!(
"Are you sure you want to certify {target} ({targetid})?"
))
.with_default(false)
.prompt()
{
commands::certification::certify(&data, targetid).await?;
};
}
Subcommand::RenewCert { target } => {
let target = try_get_idty_index_by_name(&data, &target).await?;
commands::certification::renew(&data, target).await?;
data = data.fetch_idty_index().await?;
data = data.fetch_idty_index().await?;
commands::revocation::print_revoc_sig(&data)
}
Subcommand::MemberCount => {
println!(
"member count: {}",
data.client()
.storage()
.at_latest()
.await?
.fetch(&runtime::storage().membership().counter_for_membership(),)
Subcommand::LinkAccount {
secret_format,
secret,
} => {
let keypair = get_keypair(secret_format, secret.as_deref())?;
let address = keypair.address();
data = data.fetch_idty_index().await?; // idty index required for payload
Subcommand::ChangeOwnerKey {
secret_format,
secret,
} => {
let keypair = get_keypair(secret_format, secret.as_deref())?;
let address = keypair.address();
data = data.fetch_idty_index().await?; // idty index required for payload
change_owner_key(&data, address, keypair).await?;
}
};
Ok(())
}
// ======================
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
// TODO derive this automatically
impl Serialize for IdtyStatus {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&format!("{:?}", self))
}
}
impl Serialize for SmithStatus {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&format!("{:?}", self))
}
}
// for why Arc<[T]> instead of Vec<T> see
// https://www.youtube.com/watch?v=A4cKi7PTJSs&pp=ygULVmVjPFN0cmluZz4%3D
/// struct to represent details of identity request
#[derive(Serialize)]
struct IdtyView {
index: IdtyId,
status: IdtyStatus,
pseudo: String,
owner_key: AccountId,
old_owner_key: Vec<AccountId>,
expire_on: BlockNumber,
cert_issued: Vec<String>,
cert_received: Vec<String>,
smith: Option<SmithView>,
linked_account: Vec<AccountId>,
}
impl std::fmt::Display for IdtyView {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
writeln!(f, "Identity index: {}", self.index)?;
writeln!(f, "Username: {}", self.pseudo)?;
writeln!(
f,
"Address: {}",
AccountId::to_string(&self.owner_key)
)?;
writeln!(f, "Status: {:?}", self.status)?;
writeln!(
f,
"Certifications: issued {}, received {}",
self.cert_issued.len(),
self.cert_received.len()
)?;
if let Some(smith) = &self.smith {
writeln!(f, "Smith status: {:?}", smith.status)?;
writeln!(
f,
"Smith certs: issued {}, received {}",
smith.cert_issued.len(),
smith.cert_received.len()
)?;
}
let a = self.linked_account.len();
if a > 1 {
writeln!(f, "Linked accounts count: {a}")?;
}
Ok(())
}
}
#[derive(Serialize)]
struct SmithView {
status: SmithStatus,
cert_issued: Vec<String>,
cert_received: Vec<String>,
}
account_id: Option<AccountId>,
identity_id: Option<IdtyId>,
pseudo: Option<String>,
) -> Result<(), GcliError> {
let client = data.client();
let indexer = data.indexer.clone();
let index =
match (identity_id, &account_id, &pseudo) {
(Some(index), None, None) => index,
// account_id → idty_id
(None, Some(account_id), None) => get_idty_index_by_account_id(client, account_id)
.ok_or_else(|| anyhow!("no identity for account '{account_id}'"))?,
// pseudo → idty_id
(None, None, Some(pseudo)) => get_idty_index_by_name(client, pseudo)
.ok_or_else(|| anyhow!("no identity for name '{pseudo}'"))?,
_ => {
return Err(GcliError::Logic(
"One and only one argument is needed to fetch the identity.".to_string(),
));
}
};
// idty_id → value
let value = get_identity_by_index(client, index)
.ok_or_else(|| anyhow!("no identity value for index {index}"))?;
// pseudo
let pseudo = pseudo.unwrap_or(if let Some(indexer) = &indexer {
.username_by_index(index)
.ok_or_else(|| anyhow!("indexer does not have username for this index {index}"))?
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
// get certs if possible
let (cert_issued, cert_received, linked_account, smith_cert_issued, smith_cert_received) =
if let Some(indexer) = &indexer {
let info = indexer.identity_info(index).await.expect("no info");
(
info.cert_issued
.into_iter()
.map(|i| i.receiver.name.to_string())
.collect(),
info.cert_received
.into_iter()
.map(|i| i.issuer.name.to_string())
.collect(),
info.linked_account
.into_iter()
.map(|i| AccountId::from_str(&i.id).unwrap())
.collect(),
info.smith_cert_issued
.into_iter()
.map(|i| i.receiver.name.to_string())
.collect(),
info.smith_cert_received
.into_iter()
.map(|i| i.issuer.name.to_string())
.collect(),
)
} else {
(vec![], vec![], vec![], vec![], vec![])
};
// get smith info
let smith = get_smith(client, index).await?;
let smith = smith.map(|s| SmithView {
status: s.status,
cert_issued: smith_cert_issued,
cert_received: smith_cert_received,
});
// build view
let view = IdtyView {
index,
status: value.status,
pseudo,
owner_key: value.owner_key,
old_owner_key: vec![], // TODO fetch history of owner key change
expire_on: value.next_scheduled, // TODO if zero use membership instead
cert_issued,
cert_received,
smith,
linked_account,
};
// TODO generic way to do this shared between function
match data.args.output_format {
OutputFormat::Human => {
println!("{view}");
}
OutputFormat::Json => {
println!("{}", serde_json::to_string(&view).map_err(|e| anyhow!(e))?);
/// get identity index by account id
) -> Result<Option<IdtyId>, subxt::Error> {
client
.at_latest()
.await?
.fetch(&runtime::storage().identity().identity_index_of(account_id))
/// get smith info by index
pub async fn get_smith(
client: &Client,
index: IdtyId,
) -> Result<Option<SmithMeta<IdtyId>>, subxt::Error> {
client
.storage()
.at_latest()
.await?
.fetch(&runtime::storage().smith_members().smiths(index))
.await
}
/// get identity index by name
pub async fn get_idty_index_by_name(
client: &Client,
name: &str,
) -> Result<Option<IdtyId>, subxt::Error> {
client
.storage()
.at_latest()
.await?
.fetch(
&runtime::storage()
.identity()
.identities_names(IdtyName(name.into())),
)
pub async fn try_get_idty_index_by_name(data: &Data, name: &str) -> Result<IdtyId, GcliError> {
get_idty_index_by_name(data.client(), name)
.await?
.ok_or_else(|| GcliError::Input(format!("no identity with name {name}")))
}
) -> Result<Option<IdtyValue<IdtyId, AccountId, IdtyData>>, subxt::Error> {
client
.at_latest()
.await?
.fetch(&runtime::storage().identity().identities(idty_index))
pub async fn create_identity(data: &Data, target: AccountId) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::identity::events::IdtyCreated,
Payload<runtime::identity::calls::types::CreateIdentity>,
>(data, &runtime::tx().identity().create_identity(target))
.await
pub async fn confirm_identity(data: &Data, name: String) -> Result<(), subxt::Error> {
submit_call_and_look_event::<
runtime::identity::events::IdtyConfirmed,
Payload<runtime::identity::calls::types::ConfirmIdentity>,
&runtime::tx()
.identity()
.confirm_identity(IdtyName(name.into())),
pub async fn revoke_identity(data: &Data) -> Result<(), subxt::Error> {
let (_payload, signature) = generate_revoc_doc(data);
// Transform signature to MultiSignature
// TODO: this is a hack, we should be able to use the signature directly
let signature = runtime::runtime_types::sp_core::sr25519::Signature(signature.0);
let multisign = MultiSignature::Sr25519(signature);
submit_call_and_look_event::<
runtime::identity::events::IdtyRemoved,
Payload<runtime::identity::calls::types::RevokeIdentity>,
>(
data,
&runtime::tx()
.identity()
.revoke_identity(data.idty_index(), data.address(), multisign),
)
.await
}
type LinkAccountPayload = Vec<u8>;
/// generate link account document
pub fn generate_link_account(
data: &Data,
address: AccountId,
) -> (LinkAccountPayload, Signature) {
let payload = (b"link", data.genesis_hash, data.idty_index(), address).encode();
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
match keypair {
KeyPair::Sr25519(keypair) => {
let signature = keypair.sign(&payload);
(payload, Signature::Sr25519(signature))
}
KeyPair::Nacl(keypair) => {
let signature = nacl::sign::signature(&payload, &keypair.skey).expect("could not sign");
(payload, Signature::Nacl(signature))
}
}
}
type ChOkPayload = Vec<u8>;
/// generate link account document
pub fn generate_chok_payload(
data: &Data,
_address: AccountId,
keypair: KeyPair,
) -> (ChOkPayload, Signature) {
let payload = (
b"icok",
data.genesis_hash,
data.idty_index(),
data.address(),
)
.encode();
match keypair {
KeyPair::Sr25519(keypair) => {
let signature = keypair.sign(&payload);
(payload, Signature::Sr25519(signature))
}
KeyPair::Nacl(keypair) => {
// should not migrate to Nacl
let signature = nacl::sign::signature(&payload, &keypair.skey).expect("could not sign");
(payload, Signature::Nacl(signature))
}
}
}
/// link an account to the identity
pub async fn link_account(
data: &Data,
address: AccountId,
keypair: KeyPair,
) -> Result<(), subxt::Error> {
let (_payload, signature) = generate_link_account(data, address.clone(), keypair);
// this is a hack, see
// https://substrate.stackexchange.com/questions/10309/how-to-use-core-crypto-types-instead-of-runtime-types
let signature = match signature {
Signature::Sr25519(signature) => MultiSignature::Sr25519(
runtime::runtime_types::sp_core::sr25519::Signature(signature.0),
),
Signature::Nacl(signature) => MultiSignature::Ed25519(
runtime::runtime_types::sp_core::ed25519::Signature(signature.try_into().unwrap()),
),
};
submit_call_and_look_event::<
runtime::account::events::AccountLinked,
Payload<runtime::identity::calls::types::LinkAccount>,
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
>(
data,
&runtime::tx().identity().link_account(address, signature),
)
.await
}
/// change owner key
pub async fn change_owner_key(
data: &Data,
address: AccountId,
keypair: KeyPair,
) -> Result<(), subxt::Error> {
let (_payload, signature) = generate_chok_payload(data, address.clone(), keypair);
// this is a hack, see
// https://substrate.stackexchange.com/questions/10309/how-to-use-core-crypto-types-instead-of-runtime-types
let signature = match signature {
Signature::Sr25519(signature) => MultiSignature::Sr25519(
runtime::runtime_types::sp_core::sr25519::Signature(signature.0),
),
Signature::Nacl(signature) => MultiSignature::Ed25519(
runtime::runtime_types::sp_core::ed25519::Signature(signature.try_into().unwrap()),
),
};
submit_call_and_look_event::<
runtime::identity::events::IdtyChangedOwnerKey,
Payload<runtime::identity::calls::types::ChangeOwnerKey>,
>(
data,
&runtime::tx()
.identity()
.change_owner_key(address, signature),