From add24564e1bbb1dd7cbb49a620fb139e784fafc4 Mon Sep 17 00:00:00 2001 From: Hamish Peebles Date: Thu, 18 Jul 2024 21:17:03 +0100 Subject: [PATCH 1/5] Accept the proof of uniqueness credential when joining group/community --- Cargo.lock | 15 ++++- Cargo.toml | 1 + .../api/src/lifecycle/init.rs | 2 + .../local_user_index/impl/Cargo.toml | 2 + .../local_user_index/impl/src/lib.rs | 56 +++++++++++++++++-- .../impl/src/lifecycle/init.rs | 2 + .../impl/src/queries/chat_events.rs | 2 +- .../src/updates/invite_users_to_channel.rs | 2 +- .../src/updates/invite_users_to_community.rs | 2 +- .../impl/src/updates/invite_users_to_group.rs | 2 +- .../impl/src/updates/join_channel.rs | 3 +- .../impl/src/updates/join_community.rs | 3 +- .../impl/src/updates/join_group.rs | 3 +- .../impl/src/updates/report_message_v2.rs | 2 +- backend/canisters/user_index/impl/Cargo.toml | 2 +- .../updates/add_local_user_index_canister.rs | 2 + .../submit_proof_of_unique_personhood.rs | 40 +++---------- .../proof_of_unique_personhood/Cargo.toml | 11 ++++ .../proof_of_unique_personhood/src/lib.rs | 47 ++++++++++++++++ 19 files changed, 152 insertions(+), 47 deletions(-) create mode 100644 backend/libraries/proof_of_unique_personhood/Cargo.toml create mode 100644 backend/libraries/proof_of_unique_personhood/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 388e15bd3d..4a16a444e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5126,10 +5126,12 @@ dependencies = [ "ledger_utils", "local_user_index_canister", "msgpack", + "proof_of_unique_personhood", "rand 0.8.5", "satoshi_dice_canister", "satoshi_dice_canister_c2c_client", "serde", + "serde_bytes", "serde_json", "serializer", "sha256", @@ -6269,6 +6271,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proof_of_unique_personhood" +version = "0.1.0" +dependencies = [ + "candid", + "ic-verifiable-credentials", + "types", +] + [[package]] name = "proposal_validation_canister" version = "0.1.0" @@ -6378,7 +6389,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.10.5", "proc-macro2", "quote", "syn 2.0.65", @@ -8810,7 +8821,6 @@ dependencies = [ "ic-cdk-timers", "ic-ledger-types", "ic-stable-structures 0.6.4", - "ic-verifiable-credentials", "icrc-ledger-types", "icrc_ledger_canister_c2c_client", "identity_canister", @@ -8825,6 +8835,7 @@ dependencies = [ "nns_governance_canister", "nns_governance_canister_c2c_client", "p256_key_pair", + "proof_of_unique_personhood", "pulldown-cmark", "rand 0.8.5", "serde", diff --git a/Cargo.toml b/Cargo.toml index 8078b28ada..8a55386693 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -139,6 +139,7 @@ members = [ "backend/libraries/ledger_utils", "backend/libraries/msgpack", "backend/libraries/p256_key_pair", + "backend/libraries/proof_of_unique_personhood", "backend/libraries/storage_bucket_client", "backend/libraries/search", "backend/libraries/serializer", diff --git a/backend/canisters/local_user_index/api/src/lifecycle/init.rs b/backend/canisters/local_user_index/api/src/lifecycle/init.rs index 1cc22f71d0..233d792777 100644 --- a/backend/canisters/local_user_index/api/src/lifecycle/init.rs +++ b/backend/canisters/local_user_index/api/src/lifecycle/init.rs @@ -18,7 +18,9 @@ pub struct Args { pub cycles_dispenser_canister_id: CanisterId, pub escrow_canister_id: CanisterId, pub event_relay_canister_id: CanisterId, + pub internet_identity_canister_id: CanisterId, pub video_call_operators: Vec, pub oc_secret_key_der: Option>, + pub ic_root_key: Vec, pub test_mode: bool, } diff --git a/backend/canisters/local_user_index/impl/Cargo.toml b/backend/canisters/local_user_index/impl/Cargo.toml index ba7b828ef9..5e1af401af 100644 --- a/backend/canisters/local_user_index/impl/Cargo.toml +++ b/backend/canisters/local_user_index/impl/Cargo.toml @@ -36,8 +36,10 @@ jwt = { path = "../../../libraries/jwt" } ledger_utils = { path = "../../../libraries/ledger_utils" } local_user_index_canister = { path = "../api" } msgpack = { path = "../../../libraries/msgpack" } +proof_of_unique_personhood = { path = "../../../libraries/proof_of_unique_personhood" } rand = { workspace = true } serde = { workspace = true } +serde_bytes = { workspace = true } serde_json = { workspace = true } serializer = { path = "../../../libraries/serializer" } satoshi_dice_canister = { path = "../../../bots/examples/satoshi_dice/api" } diff --git a/backend/canisters/local_user_index/impl/src/lib.rs b/backend/canisters/local_user_index/impl/src/lib.rs index 4caf89ebf0..87f2b197fb 100644 --- a/backend/canisters/local_user_index/impl/src/lib.rs +++ b/backend/canisters/local_user_index/impl/src/lib.rs @@ -7,6 +7,7 @@ use event_store_utils::EventDeduper; use local_user_index_canister::GlobalUser; use model::global_user_map::GlobalUserMap; use model::local_user_map::LocalUserMap; +use proof_of_unique_personhood::verify_proof_of_unique_personhood; use serde::{Deserialize, Serialize}; use std::cell::RefCell; use std::collections::{HashMap, VecDeque}; @@ -14,14 +15,14 @@ use std::time::Duration; use types::{ BuildVersion, CanisterId, CanisterWasm, ChannelLatestMessageIndex, ChatId, ChunkedCanisterWasm, CommunityCanisterChannelSummary, CommunityCanisterCommunitySummary, CommunityId, Cycles, MessageContent, ReferralType, - TimestampMillis, Timestamped, User, UserId, + TimestampMillis, Timestamped, UniquePersonProof, User, UserId, }; use user_canister::Event as UserEvent; use user_index_canister::Event as UserIndexEvent; use utils::canister; use utils::canister::{CanistersRequiringUpgrade, FailedUpgradeCount}; use utils::canister_event_sync_queue::CanisterEventSyncQueue; -use utils::consts::CYCLES_REQUIRED_FOR_UPGRADE; +use utils::consts::{CYCLES_REQUIRED_FOR_UPGRADE, IC_ROOT_KEY}; use utils::env::Environment; use utils::time::MINUTE_IN_MS; @@ -52,9 +53,24 @@ impl RuntimeState { RuntimeState { env, data } } - pub fn calling_user(&self) -> GlobalUser { + pub fn calling_user_id(&self) -> UserId { let caller = self.env.caller(); - self.data.global_users.get(&caller).unwrap() + self.data.global_users.get(&caller).unwrap().user_id + } + + pub fn calling_user(&self, credential_jwts: Option<&[String]>) -> GlobalUser { + let caller = self.env.caller(); + let mut user_details = self.data.global_users.get(&caller).unwrap(); + + if user_details.unique_person_proof.is_none() { + user_details.unique_person_proof = credential_jwts.as_ref().and_then(|jwts| { + let now = self.env.now(); + self.data + .extract_proof_of_unique_personhood(user_details.principal, &jwts, now) + }); + } + + user_details } pub fn is_caller_user_index_canister(&self) -> bool { @@ -196,6 +212,7 @@ impl RuntimeState { cycles_dispenser: self.data.cycles_dispenser_canister_id, escrow: self.data.escrow_canister_id, event_relay: event_relay_canister_id, + internet_identity: self.data.internet_identity_canister_id, }, oc_secret_key_initialized: self.data.oc_secret_key_der.is_some(), canister_upgrades_failed: canister_upgrades_metrics.failed, @@ -216,6 +233,8 @@ struct Data { pub proposals_bot_canister_id: CanisterId, pub cycles_dispenser_canister_id: CanisterId, pub escrow_canister_id: CanisterId, + #[serde(default = "internet_identity_canister_id")] + pub internet_identity_canister_id: CanisterId, pub canisters_requiring_upgrade: CanistersRequiringUpgrade, pub canister_pool: canister::Pool, pub total_cycles_spent_on_canisters: Cycles, @@ -232,6 +251,16 @@ struct Data { pub event_store_client: EventStoreClient, pub event_deduper: EventDeduper, pub users_to_delete_queue: VecDeque, + #[serde(with = "serde_bytes", default = "ic_root_key")] + pub ic_root_key: Vec, +} + +fn ic_root_key() -> Vec { + IC_ROOT_KEY.to_vec() +} + +fn internet_identity_canister_id() -> CanisterId { + CanisterId::from_text("rdmx6-jaaaa-aaaaa-aaadq-cai").unwrap() } #[derive(Serialize, Deserialize)] @@ -259,9 +288,11 @@ impl Data { cycles_dispenser_canister_id: CanisterId, escrow_canister_id: CanisterId, event_relay_canister_id: CanisterId, + internet_identity_canister_id: CanisterId, canister_pool_target_size: u16, video_call_operators: Vec, oc_secret_key_der: Option>, + ic_root_key: Vec, test_mode: bool, ) -> Self { Data { @@ -276,6 +307,7 @@ impl Data { proposals_bot_canister_id, cycles_dispenser_canister_id, escrow_canister_id, + internet_identity_canister_id, canisters_requiring_upgrade: CanistersRequiringUpgrade::default(), canister_pool: canister::Pool::new(canister_pool_target_size), total_cycles_spent_on_canisters: 0, @@ -294,8 +326,23 @@ impl Data { .build(), event_deduper: EventDeduper::default(), users_to_delete_queue: VecDeque::new(), + ic_root_key, } } + + pub fn extract_proof_of_unique_personhood( + &self, + principal: Principal, + credential_jwts: &[String], + now: TimestampMillis, + ) -> Option { + credential_jwts + .iter() + .filter_map(|jwt| { + verify_proof_of_unique_personhood(principal, self.identity_canister_id, jwt, &self.ic_root_key, now).ok() + }) + .next() + } } #[derive(Serialize, Debug)] @@ -335,4 +382,5 @@ pub struct CanisterIds { pub cycles_dispenser: CanisterId, pub escrow: CanisterId, pub event_relay: CanisterId, + pub internet_identity: CanisterId, } diff --git a/backend/canisters/local_user_index/impl/src/lifecycle/init.rs b/backend/canisters/local_user_index/impl/src/lifecycle/init.rs index 630ba41e79..675be547c7 100644 --- a/backend/canisters/local_user_index/impl/src/lifecycle/init.rs +++ b/backend/canisters/local_user_index/impl/src/lifecycle/init.rs @@ -27,9 +27,11 @@ fn init(args: Args) { args.cycles_dispenser_canister_id, args.escrow_canister_id, args.event_relay_canister_id, + args.internet_identity_canister_id, canister_pool_target_size, args.video_call_operators, args.oc_secret_key_der, + args.ic_root_key, args.test_mode, ); diff --git a/backend/canisters/local_user_index/impl/src/queries/chat_events.rs b/backend/canisters/local_user_index/impl/src/queries/chat_events.rs index 9158972259..f560109b61 100644 --- a/backend/canisters/local_user_index/impl/src/queries/chat_events.rs +++ b/backend/canisters/local_user_index/impl/src/queries/chat_events.rs @@ -8,7 +8,7 @@ use types::UserId; #[query(composite = true, guard = "caller_is_openchat_user")] async fn chat_events(args: Args) -> Response { - let (user, now) = read_state(|state| (state.calling_user(), state.env.now())); + let (user, now) = read_state(|state| (state.calling_user(None), state.env.now())); let futures: Vec<_> = args .requests diff --git a/backend/canisters/local_user_index/impl/src/updates/invite_users_to_channel.rs b/backend/canisters/local_user_index/impl/src/updates/invite_users_to_channel.rs index 26ae7d6216..ae4a3cad38 100644 --- a/backend/canisters/local_user_index/impl/src/updates/invite_users_to_channel.rs +++ b/backend/canisters/local_user_index/impl/src/updates/invite_users_to_channel.rs @@ -17,7 +17,7 @@ async fn invite_users_to_channel(args: Args) -> Response { .map(|u| (u.user_id, u.principal)) .collect(); - (state.calling_user().user_id, users) + (state.calling_user_id(), users) }); let c2c_args = c2c_invite_users_to_channel::Args { diff --git a/backend/canisters/local_user_index/impl/src/updates/invite_users_to_community.rs b/backend/canisters/local_user_index/impl/src/updates/invite_users_to_community.rs index c4595de2a0..0ce5193b37 100644 --- a/backend/canisters/local_user_index/impl/src/updates/invite_users_to_community.rs +++ b/backend/canisters/local_user_index/impl/src/updates/invite_users_to_community.rs @@ -47,7 +47,7 @@ struct PrepareResult { } fn prepare(args: &Args, state: &RuntimeState) -> PrepareResult { - let invited_by = state.calling_user().user_id; + let invited_by = state.calling_user_id(); let users = args .user_ids .iter() diff --git a/backend/canisters/local_user_index/impl/src/updates/invite_users_to_group.rs b/backend/canisters/local_user_index/impl/src/updates/invite_users_to_group.rs index ace5b14339..560dd0ca74 100644 --- a/backend/canisters/local_user_index/impl/src/updates/invite_users_to_group.rs +++ b/backend/canisters/local_user_index/impl/src/updates/invite_users_to_group.rs @@ -47,7 +47,7 @@ struct PrepareResult { } fn prepare(args: &Args, state: &RuntimeState) -> PrepareResult { - let invited_by = state.calling_user().user_id; + let invited_by = state.calling_user_id(); let users = args .user_ids .iter() diff --git a/backend/canisters/local_user_index/impl/src/updates/join_channel.rs b/backend/canisters/local_user_index/impl/src/updates/join_channel.rs index 38b48f05f1..71a3852083 100644 --- a/backend/canisters/local_user_index/impl/src/updates/join_channel.rs +++ b/backend/canisters/local_user_index/impl/src/updates/join_channel.rs @@ -7,7 +7,8 @@ use local_user_index_canister::join_channel::{Response::*, *}; #[update(guard = "caller_is_openchat_user")] #[trace] async fn join_channel(args: Args) -> Response { - let user_details = read_state(|state| state.calling_user()); + let user_details = + read_state(|state| state.calling_user(args.verified_credential_args.as_ref().map(|c| c.credential_jwts.as_slice()))); let c2c_args = community_canister::c2c_join_channel::Args { user_id: user_details.user_id, diff --git a/backend/canisters/local_user_index/impl/src/updates/join_community.rs b/backend/canisters/local_user_index/impl/src/updates/join_community.rs index 4416693ce0..a20b522425 100644 --- a/backend/canisters/local_user_index/impl/src/updates/join_community.rs +++ b/backend/canisters/local_user_index/impl/src/updates/join_community.rs @@ -7,7 +7,8 @@ use local_user_index_canister::join_community::{Response::*, *}; #[update(guard = "caller_is_openchat_user")] #[trace] async fn join_community(args: Args) -> Response { - let user_details = read_state(|state| state.calling_user()); + let user_details = + read_state(|state| state.calling_user(args.verified_credential_args.as_ref().map(|c| c.credential_jwts.as_slice()))); let c2c_args = community_canister::c2c_join_community::Args { user_id: user_details.user_id, diff --git a/backend/canisters/local_user_index/impl/src/updates/join_group.rs b/backend/canisters/local_user_index/impl/src/updates/join_group.rs index e7628ef7ec..442b487491 100644 --- a/backend/canisters/local_user_index/impl/src/updates/join_group.rs +++ b/backend/canisters/local_user_index/impl/src/updates/join_group.rs @@ -10,7 +10,8 @@ use user_index_canister::Event as UserIndexEvent; #[update(guard = "caller_is_openchat_user")] #[trace] async fn join_group(args: Args) -> Response { - let user_details = read_state(|state| state.calling_user()); + let user_details = + read_state(|state| state.calling_user(args.verified_credential_args.as_ref().map(|c| c.credential_jwts.as_slice()))); let c2c_args = group_canister::c2c_join_group::Args { user_id: user_details.user_id, diff --git a/backend/canisters/local_user_index/impl/src/updates/report_message_v2.rs b/backend/canisters/local_user_index/impl/src/updates/report_message_v2.rs index cabd9f0119..1c8758854c 100644 --- a/backend/canisters/local_user_index/impl/src/updates/report_message_v2.rs +++ b/backend/canisters/local_user_index/impl/src/updates/report_message_v2.rs @@ -47,7 +47,7 @@ struct PrepareResult { } fn prepare(state: &RuntimeState) -> PrepareResult { - let user_id = state.calling_user().user_id; + let user_id = state.calling_user_id(); PrepareResult { user_id, diff --git a/backend/canisters/user_index/impl/Cargo.toml b/backend/canisters/user_index/impl/Cargo.toml index 12b1dd0118..12f15ae2aa 100644 --- a/backend/canisters/user_index/impl/Cargo.toml +++ b/backend/canisters/user_index/impl/Cargo.toml @@ -32,7 +32,6 @@ ic-cdk = { workspace = true } ic-cdk-timers = { workspace = true } ic-ledger-types = { workspace = true } ic-stable-structures = { workspace = true } -ic-verifiable-credentials = { workspace = true } icrc_ledger_canister_c2c_client = { path = "../../../external_canisters/icrc_ledger/c2c_client" } icrc-ledger-types = { workspace = true } identity_canister = { path = "../../identity/api" } @@ -47,6 +46,7 @@ modclub_canister_c2c_client = { path = "../../../external_canisters/modclub/c2c_ msgpack = { path = "../../../libraries/msgpack" } nns_governance_canister = { path = "../../../external_canisters/nns_governance/api" } nns_governance_canister_c2c_client = { path = "../../../external_canisters/nns_governance/c2c_client" } +proof_of_unique_personhood = { path = "../../../libraries/proof_of_unique_personhood" } pulldown-cmark = { workspace = true } rand = { workspace = true } serde = { workspace = true } diff --git a/backend/canisters/user_index/impl/src/updates/add_local_user_index_canister.rs b/backend/canisters/user_index/impl/src/updates/add_local_user_index_canister.rs index 0d5e9bfd73..1b1644b449 100644 --- a/backend/canisters/user_index/impl/src/updates/add_local_user_index_canister.rs +++ b/backend/canisters/user_index/impl/src/updates/add_local_user_index_canister.rs @@ -59,12 +59,14 @@ fn prepare(args: &Args, state: &RuntimeState) -> Result cycles_dispenser_canister_id: state.data.cycles_dispenser_canister_id, escrow_canister_id: state.data.escrow_canister_id, event_relay_canister_id: state.data.event_store_client.info().event_store_canister_id, + internet_identity_canister_id: state.data.internet_identity_canister_id, video_call_operators: state.data.video_call_operators.clone(), oc_secret_key_der: state .data .oc_key_pair .is_initialised() .then_some(state.data.oc_key_pair.secret_key_der().to_vec()), + ic_root_key: state.data.ic_root_key.clone(), test_mode: state.data.test_mode, }, }) diff --git a/backend/canisters/user_index/impl/src/updates/submit_proof_of_unique_personhood.rs b/backend/canisters/user_index/impl/src/updates/submit_proof_of_unique_personhood.rs index 3e9faa1762..4129973c6b 100644 --- a/backend/canisters/user_index/impl/src/updates/submit_proof_of_unique_personhood.rs +++ b/backend/canisters/user_index/impl/src/updates/submit_proof_of_unique_personhood.rs @@ -2,15 +2,10 @@ use crate::{mutate_state, RuntimeState}; use canister_tracing_macros::trace; use event_store_producer::EventBuilder; use ic_cdk::update; -use ic_verifiable_credentials::issuer_api::CredentialSpec; -use ic_verifiable_credentials::VcFlowSigners; +use proof_of_unique_personhood::verify_proof_of_unique_personhood; use serde::Serialize; -use types::{CanisterId, UniquePersonProof, UniquePersonProofProvider}; +use types::UniquePersonProofProvider; use user_index_canister::submit_proof_of_unique_personhood::{Response::*, *}; -use utils::time::NANOS_PER_MILLISECOND; - -const ISSUER_CANISTER_ID: CanisterId = CanisterId::from_slice(&[0, 0, 0, 0, 0, 240, 24, 173, 1, 1]); -const ISSUER_ORIGIN: &str = "id.decideai.xyz"; #[update] #[trace] @@ -25,27 +20,14 @@ fn submit_proof_of_unique_personhood_impl(args: Args, state: &mut RuntimeState) }; let now = state.env.now(); - match ic_verifiable_credentials::validate_ii_presentation_and_claims( - &args.credential_jwt, + match verify_proof_of_unique_personhood( caller, - &VcFlowSigners { - ii_canister_id: state.data.internet_identity_canister_id, - ii_origin: "identity.ic0.app".to_string(), - issuer_canister_id: ISSUER_CANISTER_ID, - issuer_origin: ISSUER_ORIGIN.to_string(), - }, - &CredentialSpec { - credential_type: "ProofOfUniqueness".to_string(), - arguments: None, - }, + state.data.internet_identity_canister_id, + &args.credential_jwt, &state.data.ic_root_key, - (now * NANOS_PER_MILLISECOND) as u128, + now, ) { - Ok(_) => { - let proof = UniquePersonProof { - timestamp: now, - provider: UniquePersonProofProvider::DecideAI, - }; + Ok(proof) => { state.data.users.record_proof_of_unique_personhood(user_id, proof.clone()); state.push_event_to_all_local_user_indexes( local_user_index_canister::Event::NotifyUniquePersonProof(user_id, proof), @@ -62,7 +44,7 @@ fn submit_proof_of_unique_personhood_impl(args: Args, state: &mut RuntimeState) ); Success } - Err(error) => Invalid(format!("{error:?}")), + Err(error) => Invalid(error), } } @@ -70,9 +52,3 @@ fn submit_proof_of_unique_personhood_impl(args: Args, state: &mut RuntimeState) struct EventPayload { provider: UniquePersonProofProvider, } - -#[test] -fn signing_canister_id() { - let canister_id = CanisterId::from_text("qgxyr-pyaaa-aaaah-qdcwq-cai").unwrap(); - assert_eq!(canister_id, ISSUER_CANISTER_ID); -} diff --git a/backend/libraries/proof_of_unique_personhood/Cargo.toml b/backend/libraries/proof_of_unique_personhood/Cargo.toml new file mode 100644 index 0000000000..5692e6eec1 --- /dev/null +++ b/backend/libraries/proof_of_unique_personhood/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "proof_of_unique_personhood" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +candid = { workspace = true } +ic-verifiable-credentials = { workspace = true } +types = { path = "../types" } diff --git a/backend/libraries/proof_of_unique_personhood/src/lib.rs b/backend/libraries/proof_of_unique_personhood/src/lib.rs new file mode 100644 index 0000000000..8c37452649 --- /dev/null +++ b/backend/libraries/proof_of_unique_personhood/src/lib.rs @@ -0,0 +1,47 @@ +use candid::Principal; +use ic_verifiable_credentials::issuer_api::CredentialSpec; +use ic_verifiable_credentials::VcFlowSigners; +use types::{CanisterId, TimestampMillis, UniquePersonProof, UniquePersonProofProvider}; + +const ISSUER_CANISTER_ID: CanisterId = CanisterId::from_slice(&[0, 0, 0, 0, 0, 240, 24, 173, 1, 1]); +const ISSUER_ORIGIN: &str = "id.decideai.xyz"; +const NANOS_PER_MILLISECOND: u64 = 1_000_000; + +pub fn verify_proof_of_unique_personhood( + principal: Principal, + internet_identity_canister_id: CanisterId, + credential_jwt: &str, + ic_root_key: &[u8], + now: TimestampMillis, +) -> Result { + match ic_verifiable_credentials::validate_ii_presentation_and_claims( + &credential_jwt, + principal, + &VcFlowSigners { + ii_canister_id: internet_identity_canister_id, + ii_origin: "identity.ic0.app".to_string(), + issuer_canister_id: ISSUER_CANISTER_ID, + issuer_origin: ISSUER_ORIGIN.to_string(), + }, + &CredentialSpec { + credential_type: "ProofOfUniqueness".to_string(), + arguments: None, + }, + ic_root_key, + (now * NANOS_PER_MILLISECOND) as u128, + ) { + Ok(_) => Ok(UniquePersonProof { + timestamp: now, + provider: UniquePersonProofProvider::DecideAI, + }), + Err(error) => Err(format!("{error:?}")), + } +} + +#[test] +fn signing_canister_id() { + assert_eq!( + ISSUER_CANISTER_ID, + CanisterId::from_text("qgxyr-pyaaa-aaaah-qdcwq-cai").unwrap() + ); +} From c6533357fcb6c73f2c7a07ffb65468929ad151d0 Mon Sep 17 00:00:00 2001 From: Hamish Peebles Date: Thu, 18 Jul 2024 21:18:12 +0100 Subject: [PATCH 2/5] Update CHANGELOG --- backend/canisters/local_user_index/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/canisters/local_user_index/CHANGELOG.md b/backend/canisters/local_user_index/CHANGELOG.md index ae9758742f..038294f4b8 100644 --- a/backend/canisters/local_user_index/CHANGELOG.md +++ b/backend/canisters/local_user_index/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - Clear old data from the failed upgrades log ([#6062](https://github.com/open-chat-labs/open-chat/pull/6062)) +- Accept the proof of uniqueness credential when joining group/community ([#6068](https://github.com/open-chat-labs/open-chat/pull/6068)) ### Removed From 480a59696dff68b441011e8ecedd473b0454edc6 Mon Sep 17 00:00:00 2001 From: Hamish Peebles Date: Thu, 18 Jul 2024 21:39:55 +0100 Subject: [PATCH 3/5] clippy --- backend/canisters/local_user_index/impl/src/lib.rs | 2 +- backend/libraries/proof_of_unique_personhood/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/canisters/local_user_index/impl/src/lib.rs b/backend/canisters/local_user_index/impl/src/lib.rs index 87f2b197fb..d83b5cb265 100644 --- a/backend/canisters/local_user_index/impl/src/lib.rs +++ b/backend/canisters/local_user_index/impl/src/lib.rs @@ -66,7 +66,7 @@ impl RuntimeState { user_details.unique_person_proof = credential_jwts.as_ref().and_then(|jwts| { let now = self.env.now(); self.data - .extract_proof_of_unique_personhood(user_details.principal, &jwts, now) + .extract_proof_of_unique_personhood(user_details.principal, jwts, now) }); } diff --git a/backend/libraries/proof_of_unique_personhood/src/lib.rs b/backend/libraries/proof_of_unique_personhood/src/lib.rs index 8c37452649..f9310ad135 100644 --- a/backend/libraries/proof_of_unique_personhood/src/lib.rs +++ b/backend/libraries/proof_of_unique_personhood/src/lib.rs @@ -15,7 +15,7 @@ pub fn verify_proof_of_unique_personhood( now: TimestampMillis, ) -> Result { match ic_verifiable_credentials::validate_ii_presentation_and_claims( - &credential_jwt, + credential_jwt, principal, &VcFlowSigners { ii_canister_id: internet_identity_canister_id, From cf8b7511f2ddbb2c54bc4b8e67f8326254f7169c Mon Sep 17 00:00:00 2001 From: Hamish Peebles Date: Thu, 18 Jul 2024 21:36:21 +0100 Subject: [PATCH 4/5] Push unique person proof from LocalUserIndex to UserIndex --- .../local_user_index/impl/src/lib.rs | 25 ++++++++++++++++--- .../impl/src/queries/chat_events.rs | 2 +- .../impl/src/updates/join_channel.rs | 9 ++++--- .../impl/src/updates/join_community.rs | 9 ++++--- .../impl/src/updates/join_group.rs | 9 ++++--- backend/canisters/user_index/api/src/lib.rs | 3 ++- .../impl/src/updates/c2c_notify_events.rs | 8 ++++++ 7 files changed, 50 insertions(+), 15 deletions(-) diff --git a/backend/canisters/local_user_index/impl/src/lib.rs b/backend/canisters/local_user_index/impl/src/lib.rs index d83b5cb265..8fecf1ec3c 100644 --- a/backend/canisters/local_user_index/impl/src/lib.rs +++ b/backend/canisters/local_user_index/impl/src/lib.rs @@ -58,16 +58,33 @@ impl RuntimeState { self.data.global_users.get(&caller).unwrap().user_id } - pub fn calling_user(&self, credential_jwts: Option<&[String]>) -> GlobalUser { + pub fn calling_user(&self) -> GlobalUser { let caller = self.env.caller(); - let mut user_details = self.data.global_users.get(&caller).unwrap(); + self.data.global_users.get(&caller).unwrap() + } + + pub fn get_calling_user_and_process_credentials(&mut self, credential_jwts: Option<&[String]>) -> GlobalUser { + let mut user_details = self.calling_user(); if user_details.unique_person_proof.is_none() { - user_details.unique_person_proof = credential_jwts.as_ref().and_then(|jwts| { + if let Some(unique_person_proof) = credential_jwts.as_ref().and_then(|jwts| { let now = self.env.now(); self.data .extract_proof_of_unique_personhood(user_details.principal, jwts, now) - }); + }) { + let user_id = user_details.user_id; + self.push_event_to_user_index(UserIndexEvent::NotifyUniquePersonProof(Box::new(( + user_id, + unique_person_proof.clone(), + )))); + if self.data.local_users.contains(&user_id) { + self.push_event_to_user( + user_id, + UserEvent::NotifyUniquePersonProof(Box::new(unique_person_proof.clone())), + ); + } + user_details.unique_person_proof = Some(unique_person_proof); + } } user_details diff --git a/backend/canisters/local_user_index/impl/src/queries/chat_events.rs b/backend/canisters/local_user_index/impl/src/queries/chat_events.rs index f560109b61..9158972259 100644 --- a/backend/canisters/local_user_index/impl/src/queries/chat_events.rs +++ b/backend/canisters/local_user_index/impl/src/queries/chat_events.rs @@ -8,7 +8,7 @@ use types::UserId; #[query(composite = true, guard = "caller_is_openchat_user")] async fn chat_events(args: Args) -> Response { - let (user, now) = read_state(|state| (state.calling_user(None), state.env.now())); + let (user, now) = read_state(|state| (state.calling_user(), state.env.now())); let futures: Vec<_> = args .requests diff --git a/backend/canisters/local_user_index/impl/src/updates/join_channel.rs b/backend/canisters/local_user_index/impl/src/updates/join_channel.rs index 71a3852083..62e6c39890 100644 --- a/backend/canisters/local_user_index/impl/src/updates/join_channel.rs +++ b/backend/canisters/local_user_index/impl/src/updates/join_channel.rs @@ -1,5 +1,5 @@ use crate::guards::caller_is_openchat_user; -use crate::{mutate_state, read_state}; +use crate::mutate_state; use canister_tracing_macros::trace; use ic_cdk::update; use local_user_index_canister::join_channel::{Response::*, *}; @@ -7,8 +7,11 @@ use local_user_index_canister::join_channel::{Response::*, *}; #[update(guard = "caller_is_openchat_user")] #[trace] async fn join_channel(args: Args) -> Response { - let user_details = - read_state(|state| state.calling_user(args.verified_credential_args.as_ref().map(|c| c.credential_jwts.as_slice()))); + let user_details = mutate_state(|state| { + state.get_calling_user_and_process_credentials( + args.verified_credential_args.as_ref().map(|c| c.credential_jwts.as_slice()), + ) + }); let c2c_args = community_canister::c2c_join_channel::Args { user_id: user_details.user_id, diff --git a/backend/canisters/local_user_index/impl/src/updates/join_community.rs b/backend/canisters/local_user_index/impl/src/updates/join_community.rs index a20b522425..91e4443869 100644 --- a/backend/canisters/local_user_index/impl/src/updates/join_community.rs +++ b/backend/canisters/local_user_index/impl/src/updates/join_community.rs @@ -1,5 +1,5 @@ use crate::guards::caller_is_openchat_user; -use crate::{mutate_state, read_state}; +use crate::mutate_state; use canister_tracing_macros::trace; use ic_cdk::update; use local_user_index_canister::join_community::{Response::*, *}; @@ -7,8 +7,11 @@ use local_user_index_canister::join_community::{Response::*, *}; #[update(guard = "caller_is_openchat_user")] #[trace] async fn join_community(args: Args) -> Response { - let user_details = - read_state(|state| state.calling_user(args.verified_credential_args.as_ref().map(|c| c.credential_jwts.as_slice()))); + let user_details = mutate_state(|state| { + state.get_calling_user_and_process_credentials( + args.verified_credential_args.as_ref().map(|c| c.credential_jwts.as_slice()), + ) + }); let c2c_args = community_canister::c2c_join_community::Args { user_id: user_details.user_id, diff --git a/backend/canisters/local_user_index/impl/src/updates/join_group.rs b/backend/canisters/local_user_index/impl/src/updates/join_group.rs index 442b487491..133bcda189 100644 --- a/backend/canisters/local_user_index/impl/src/updates/join_group.rs +++ b/backend/canisters/local_user_index/impl/src/updates/join_group.rs @@ -1,5 +1,5 @@ use crate::guards::caller_is_openchat_user; -use crate::{mutate_state, read_state, RuntimeState}; +use crate::{mutate_state, RuntimeState}; use canister_tracing_macros::trace; use ic_cdk::update; use local_user_index_canister::join_group::{Response::*, *}; @@ -10,8 +10,11 @@ use user_index_canister::Event as UserIndexEvent; #[update(guard = "caller_is_openchat_user")] #[trace] async fn join_group(args: Args) -> Response { - let user_details = - read_state(|state| state.calling_user(args.verified_credential_args.as_ref().map(|c| c.credential_jwts.as_slice()))); + let user_details = mutate_state(|state| { + state.get_calling_user_and_process_credentials( + args.verified_credential_args.as_ref().map(|c| c.credential_jwts.as_slice()), + ) + }); let c2c_args = group_canister::c2c_join_group::Args { user_id: user_details.user_id, diff --git a/backend/canisters/user_index/api/src/lib.rs b/backend/canisters/user_index/api/src/lib.rs index cc593674e7..0c75efcd69 100644 --- a/backend/canisters/user_index/api/src/lib.rs +++ b/backend/canisters/user_index/api/src/lib.rs @@ -2,7 +2,7 @@ use candid::Principal; use serde::{Deserialize, Serialize}; use types::{ CanisterId, ChannelLatestMessageIndex, ChatId, CommunityId, MessageContent, MessageContentInitial, MessageId, MessageIndex, - User, UserId, + UniquePersonProof, User, UserId, }; mod lifecycle; @@ -22,6 +22,7 @@ pub enum Event { OpenChatBotMessage(Box), OpenChatBotMessageV2(Box), UserDeleted(Box), + NotifyUniquePersonProof(Box<(UserId, UniquePersonProof)>), } #[derive(Serialize, Deserialize, Clone, Debug)] diff --git a/backend/canisters/user_index/impl/src/updates/c2c_notify_events.rs b/backend/canisters/user_index/impl/src/updates/c2c_notify_events.rs index f66ea73df0..423a3d8255 100644 --- a/backend/canisters/user_index/impl/src/updates/c2c_notify_events.rs +++ b/backend/canisters/user_index/impl/src/updates/c2c_notify_events.rs @@ -97,6 +97,14 @@ fn handle_event(event: Event, state: &mut RuntimeState) { Some(caller), ); } + Event::NotifyUniquePersonProof(ev) => { + let (user_id, proof) = *ev; + state.data.users.record_proof_of_unique_personhood(user_id, proof.clone()); + state.push_event_to_all_local_user_indexes( + LocalUserIndexEvent::NotifyUniquePersonProof(user_id, proof), + Some(caller), + ); + } } } From 06a8f1760015c5c61788ceddd3e1dedc72c3779e Mon Sep 17 00:00:00 2001 From: Hamish Peebles Date: Thu, 18 Jul 2024 21:48:44 +0100 Subject: [PATCH 5/5] Update CHANGELOGs --- backend/canisters/local_user_index/CHANGELOG.md | 5 ++++- backend/canisters/user_index/CHANGELOG.md | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/canisters/local_user_index/CHANGELOG.md b/backend/canisters/local_user_index/CHANGELOG.md index 038294f4b8..00196d264c 100644 --- a/backend/canisters/local_user_index/CHANGELOG.md +++ b/backend/canisters/local_user_index/CHANGELOG.md @@ -6,10 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [unreleased] +### Added + +- Support submitting proof of uniqueness to LocalUserIndex ([#6068](https://github.com/open-chat-labs/open-chat/pull/6068)) + ### Changed - Clear old data from the failed upgrades log ([#6062](https://github.com/open-chat-labs/open-chat/pull/6062)) -- Accept the proof of uniqueness credential when joining group/community ([#6068](https://github.com/open-chat-labs/open-chat/pull/6068)) ### Removed diff --git a/backend/canisters/user_index/CHANGELOG.md b/backend/canisters/user_index/CHANGELOG.md index 867f40b4e4..81a4b66c1d 100644 --- a/backend/canisters/user_index/CHANGELOG.md +++ b/backend/canisters/user_index/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Clear old data from the failed upgrades log ([#6062](https://github.com/open-chat-labs/open-chat/pull/6062)) - Fix fee then retry transfer if fee too high ([#6063](https://github.com/open-chat-labs/open-chat/pull/6063)) - Handle transfer fee changing in either direction ([#6064](https://github.com/open-chat-labs/open-chat/pull/6064)) +- Accept proofs of uniqueness from LocalUserIndexes ([#6068](https://github.com/open-chat-labs/open-chat/pull/6068)) ## [[2.0.1242](https://github.com/open-chat-labs/open-chat/releases/tag/v2.0.1242-user_index)] - 2024-07-17