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: IdtyId },
/// 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 } => {
commands::distance::request_distance_evaluation_for(&data, target).await?;
Subcommand::Certify { target } => {
data = data.fetch_idty_index().await?;
// TODO fetch target username / key / index
// and ask user to confirm certification
commands::certification::certify(&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(())
}
// ======================
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
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
// 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}"))?
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
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
// 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())),
)
) -> 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();
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
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>,
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
>(
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),