From 0d0f3eea0808a6b13c6c95f8f066e0fd3a6aae3e Mon Sep 17 00:00:00 2001 From: Kai <7630809+Kailai-Wang@users.noreply.github.com> Date: Fri, 15 Dec 2023 11:03:43 +0100 Subject: [PATCH 1/4] Add support for BTC account linking (#2331) * add bitcoin identity * add bitcoin sig verification * some update * add bitoin prime identity test * small clippy fix * fix service name --------- Co-authored-by: Zhouhui Tian <125243011+zhouhuitian@users.noreply.github.com> (cherry picked from commit b5a555c355d4deb8eb7c7940d682ac0980a40869) --- primitives/core/src/network.rs | 35 ++ .../commands/litentry/link_identity.rs | 4 +- .../substrate-sgx/sp-io/src/lib.rs | 9 +- .../docker/lit-di-bitcoin-identity-test.yml | 24 ++ .../core/data-providers/src/achainable.rs | 1 + .../litentry/primitives/src/identity.rs | 116 +++++- tee-worker/litentry/primitives/src/lib.rs | 24 +- .../primitives/src/validation_data.rs | 4 + tee-worker/service/src/prometheus_metrics.rs | 1 + .../di_bitcoin_identity.test.ts | 359 ++++++++++++++++++ .../interfaces/identity/definitions.ts | 13 +- ts-tests/tests/base-filter.test.ts | 1 - 12 files changed, 567 insertions(+), 24 deletions(-) create mode 100644 tee-worker/docker/lit-di-bitcoin-identity-test.yml create mode 100644 tee-worker/ts-tests/integration-tests/di_bitcoin_identity.test.ts diff --git a/primitives/core/src/network.rs b/primitives/core/src/network.rs index 751cc7b432..4754ee07cb 100644 --- a/primitives/core/src/network.rs +++ b/primitives/core/src/network.rs @@ -59,6 +59,10 @@ pub enum Web3Network { // evm Ethereum, Bsc, + + // btc + #[codec(index = 9)] + Bitcoin, } impl Web3Network { @@ -89,6 +93,10 @@ impl Web3Network { pub fn is_evm(&self) -> bool { matches!(self, Self::Ethereum | Self::Bsc) } + + pub fn is_bitcoin(&self) -> bool { + matches!(self, Self::Bitcoin) + } } pub fn all_web3networks() -> Vec { @@ -103,6 +111,10 @@ pub fn all_evm_web3networks() -> Vec { Web3Network::iter().filter(|n| n.is_evm()).collect() } +pub fn all_bitcoin_web3networks() -> Vec { + Web3Network::iter().filter(|n| n.is_bitcoin()).collect() +} + #[cfg(test)] mod tests { use super::*; @@ -128,6 +140,7 @@ mod tests { Web3Network::SubstrateTestnet => false, Web3Network::Ethereum => true, Web3Network::Bsc => true, + Web3Network::Bitcoin => false, } ) }) @@ -148,6 +161,28 @@ mod tests { Web3Network::SubstrateTestnet => true, Web3Network::Ethereum => false, Web3Network::Bsc => false, + Web3Network::Bitcoin => false, + } + ) + }) + } + + #[test] + fn is_bitcoin_works() { + Web3Network::iter().for_each(|network| { + assert_eq!( + network.is_bitcoin(), + match network { + Web3Network::Polkadot => false, + Web3Network::Kusama => false, + Web3Network::Litentry => false, + Web3Network::Litmus => false, + Web3Network::LitentryRococo => false, + Web3Network::Khala => false, + Web3Network::SubstrateTestnet => false, + Web3Network::Ethereum => false, + Web3Network::Bsc => false, + Web3Network::Bitcoin => true, } ) }) diff --git a/tee-worker/cli/src/base_cli/commands/litentry/link_identity.rs b/tee-worker/cli/src/base_cli/commands/litentry/link_identity.rs index 83f4430339..d5115df56c 100644 --- a/tee-worker/cli/src/base_cli/commands/litentry/link_identity.rs +++ b/tee-worker/cli/src/base_cli/commands/litentry/link_identity.rs @@ -34,7 +34,7 @@ use substrate_api_client::{compose_extrinsic, CallIndex, UncheckedExtrinsicV4, X pub struct LinkIdentityCommand { /// AccountId in ss58check format account: String, - /// Identity to be created + /// Identity to be created, in did form identity: String, /// Shard identifier shard: String, @@ -64,7 +64,7 @@ impl LinkIdentityCommand { } let tee_shielding_key = get_shielding_key(cli).unwrap(); - let encrypted_identity = tee_shielding_key.encrypt(&identity.unwrap().encode()).unwrap(); + let encrypted_identity = tee_shielding_key.encrypt(&identity.encode()).unwrap(); // TODO: the params are incorrect - and need to be reworked too let vdata: Option> = None; diff --git a/tee-worker/core-primitives/substrate-sgx/sp-io/src/lib.rs b/tee-worker/core-primitives/substrate-sgx/sp-io/src/lib.rs index 9e39143376..6962f6c164 100644 --- a/tee-worker/core-primitives/substrate-sgx/sp-io/src/lib.rs +++ b/tee-worker/core-primitives/substrate-sgx/sp-io/src/lib.rs @@ -668,8 +668,13 @@ pub mod crypto { sig: &[u8; 65], msg: &[u8; 32], ) -> Result<[u8; 33], EcdsaVerifyError> { - warn!("crypto::secp256k1_ecdsa_recover unimplemented"); - Ok([0; 33]) + let rs = libsecp256k1::Signature::parse_standard_slice(&sig[0..64]) + .map_err(|_| EcdsaVerifyError::BadRS)?; + let v = libsecp256k1::RecoveryId::parse(if sig[64] > 26 { sig[64] - 27 } else { sig[64] }) + .map_err(|_| EcdsaVerifyError::BadV)?; + let pubkey = libsecp256k1::recover(&libsecp256k1::Message::parse(msg), &rs, &v) + .map_err(|_| EcdsaVerifyError::BadSignature)?; + Ok(pubkey.serialize_compressed()) } } diff --git a/tee-worker/docker/lit-di-bitcoin-identity-test.yml b/tee-worker/docker/lit-di-bitcoin-identity-test.yml new file mode 100644 index 0000000000..8a4b5a2c59 --- /dev/null +++ b/tee-worker/docker/lit-di-bitcoin-identity-test.yml @@ -0,0 +1,24 @@ +services: + lit-di-bitcoin-identity-test: + image: litentry/litentry-cli:latest + container_name: litentry-di-bitcoin-identity-test + volumes: + - ../ts-tests:/ts-tests + - ../client-api:/client-api + - ../cli:/usr/local/worker-cli + build: + context: .. + dockerfile: build.Dockerfile + target: deployed-client + depends_on: + litentry-node: + condition: service_healthy + litentry-worker-1: + condition: service_healthy + networks: + - litentry-test-network + entrypoint: "bash -c '/usr/local/worker-cli/lit_ts_test.sh test-di-bitcoin-identity 2>&1' " + restart: "no" +networks: + litentry-test-network: + driver: bridge diff --git a/tee-worker/litentry/core/data-providers/src/achainable.rs b/tee-worker/litentry/core/data-providers/src/achainable.rs index 423437ffce..9faf9d845b 100644 --- a/tee-worker/litentry/core/data-providers/src/achainable.rs +++ b/tee-worker/litentry/core/data-providers/src/achainable.rs @@ -186,6 +186,7 @@ pub fn web3_network_to_chain(network: &Web3Network) -> String { Web3Network::SubstrateTestnet => "substrate_testnet".into(), Web3Network::Ethereum => "ethereum".into(), Web3Network::Bsc => "bsc".into(), + Web3Network::Bitcoin => "bitcoin".into(), } } diff --git a/tee-worker/litentry/primitives/src/identity.rs b/tee-worker/litentry/primitives/src/identity.rs index 74d8dfd4ac..4f8a3526a4 100644 --- a/tee-worker/litentry/primitives/src/identity.rs +++ b/tee-worker/litentry/primitives/src/identity.rs @@ -25,6 +25,7 @@ use pallet_evm::{AddressMapping, HashedAddressMapping as GenericHashedAddressMap use parentchain_primitives::{AccountId, Web3Network}; use scale_info::TypeInfo; use sp_core::{crypto::AccountId32, ed25519, sr25519, ByteArray, H160}; +use sp_io::hashing::blake2_256; use sp_runtime::{ traits::{BlakeTwo256, ConstU32}, BoundedVec, @@ -99,11 +100,63 @@ impl From for Address32 { } } +// TODO: maybe use macros to reduce verbosity +#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct Address33([u8; 33]); +impl AsRef<[u8; 33]> for Address33 { + fn as_ref(&self) -> &[u8; 33] { + &self.0 + } +} + +impl Default for Address33 { + fn default() -> Self { + Address33([0u8; 33]) + } +} + +impl From<[u8; 33]> for Address33 { + fn from(value: [u8; 33]) -> Self { + Self(value) + } +} + +impl<'a> TryFrom<&'a [u8]> for Address33 { + type Error = (); + fn try_from(x: &'a [u8]) -> Result { + if x.len() == 33 { + let mut data = [0; 33]; + data.copy_from_slice(x); + Ok(Address33(data)) + } else { + Err(()) + } + } +} + +impl From for ecdsa::Public { + fn from(value: Address33) -> Self { + let raw: [u8; 33] = *value.as_ref(); + ecdsa::Public::from_raw(raw) + } +} + +impl From<&Address33> for ecdsa::Public { + fn from(value: &Address33) -> Self { + (*value).into() + } +} + +impl From for Address33 { + fn from(k: ecdsa::Public) -> Self { + k.0.into() + } +} + /// Web2 and Web3 Identity based on handle/public key /// We only include the network categories (substrate/evm) without concrete types /// see https://github.com/litentry/litentry-parachain/issues/1841 #[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen, EnumIter)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum Identity { // web2 Twitter(IdentityString), @@ -113,6 +166,11 @@ pub enum Identity { // web3 Substrate(Address32), Evm(Address20), + // bitcoin addresses are derived (one-way hash) from the pubkey + // by using `Address33` as the Identity handle, it requires that pubkey + // is retrievable by the wallet API when verifying the bitcoin account. + // e.g. unisat-wallet: https://docs.unisat.io/dev/unisat-developer-service/unisat-wallet#getpublickey + Bitcoin(Address33), } impl Identity { @@ -121,7 +179,7 @@ impl Identity { } pub fn is_web3(&self) -> bool { - matches!(self, Self::Substrate(..) | Self::Evm(..)) + matches!(self, Self::Substrate(..) | Self::Evm(..) | Self::Bitcoin(..)) } pub fn is_substrate(&self) -> bool { @@ -132,26 +190,27 @@ impl Identity { matches!(self, Self::Evm(..)) } + pub fn is_bitcoin(&self) -> bool { + matches!(self, Self::Bitcoin(..)) + } + // check if the given web3networks match the identity pub fn matches_web3networks(&self, networks: &Vec) -> bool { (self.is_substrate() && !networks.is_empty() && networks.iter().all(|n| n.is_substrate())) || (self.is_evm() && !networks.is_empty() && networks.iter().all(|n| n.is_evm())) + || (self.is_bitcoin() + && !networks.is_empty() + && networks.iter().all(|n| n.is_bitcoin())) || (self.is_web2() && networks.is_empty()) } /// Currently we only support mapping from Address32/Address20 to AccountId, not opposite. pub fn to_account_id(&self) -> Option { match self { - Identity::Substrate(address) => { - let mut data = [0u8; 32]; - data.copy_from_slice(address.as_ref()); - Some(AccountId32::from(data)) - }, - Identity::Evm(address) => { - let substrate_version = - HashedAddressMapping::into_account_id(H160::from_slice(address.as_ref())); - Some(AccountId32::from(Into::<[u8; 32]>::into(substrate_version))) - }, + Identity::Substrate(address) => Some(address.into()), + Identity::Evm(address) => + Some(HashedAddressMapping::into_account_id(H160::from_slice(address.as_ref()))), + Identity::Bitcoin(address) => Some(blake2_256(address.as_ref()).into()), _ => None, } } @@ -187,6 +246,12 @@ impl From for Identity { } } +impl From for Identity { + fn from(value: Address33) -> Self { + Identity::Bitcoin(value) + } +} + impl From<[u8; 32]> for Identity { fn from(value: [u8; 32]) -> Self { Identity::Substrate(value.into()) @@ -199,6 +264,12 @@ impl From<[u8; 20]> for Identity { } } +impl From<[u8; 33]> for Identity { + fn from(value: [u8; 33]) -> Self { + Identity::Bitcoin(value.into()) + } +} + #[cfg(test)] mod tests { use super::*; @@ -216,6 +287,7 @@ mod tests { Identity::Github(..) => true, Identity::Substrate(..) => false, Identity::Evm(..) => false, + Identity::Bitcoin(..) => false, } ) }) @@ -232,6 +304,7 @@ mod tests { Identity::Github(..) => false, Identity::Substrate(..) => true, Identity::Evm(..) => true, + Identity::Bitcoin(..) => true, } ) }) @@ -248,6 +321,7 @@ mod tests { Identity::Github(..) => false, Identity::Substrate(..) => true, Identity::Evm(..) => false, + Identity::Bitcoin(..) => false, } ) }) @@ -264,6 +338,24 @@ mod tests { Identity::Github(..) => false, Identity::Substrate(..) => false, Identity::Evm(..) => true, + Identity::Bitcoin(..) => false, + } + ) + }) + } + + #[test] + fn is_bitcoin_works() { + Identity::iter().for_each(|identity| { + assert_eq!( + identity.is_bitcoin(), + match identity { + Identity::Twitter(..) => false, + Identity::Discord(..) => false, + Identity::Github(..) => false, + Identity::Substrate(..) => false, + Identity::Evm(..) => false, + Identity::Bitcoin(..) => true, } ) }) diff --git a/tee-worker/litentry/primitives/src/lib.rs b/tee-worker/litentry/primitives/src/lib.rs index 9104470d6e..6916d52ebb 100644 --- a/tee-worker/litentry/primitives/src/lib.rs +++ b/tee-worker/litentry/primitives/src/lib.rs @@ -52,7 +52,10 @@ pub use parentchain_primitives::{ }; use scale_info::TypeInfo; use sp_core::{ecdsa, ed25519, sr25519, ByteArray}; -use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256}; +use sp_io::{ + crypto::secp256k1_ecdsa_recover, + hashing::{blake2_256, keccak_256}, +}; use sp_runtime::traits::Verify; use std::string::ToString; @@ -81,6 +84,7 @@ impl LitentryMultiSignature { self.verify_substrate(substrate_wrap(msg).as_slice(), address) || self.verify_substrate(msg, address), Identity::Evm(address) => self.verify_evm(msg, address), + Identity::Bitcoin(address) => self.verify_bitcoin(msg, address), _ => false, } } @@ -96,11 +100,10 @@ impl LitentryMultiSignature { Err(()) => false, }, (Self::Ecdsa(ref sig), who) => { - let m = sp_io::hashing::blake2_256(msg); + let m = blake2_256(msg); match sp_io::crypto::secp256k1_ecdsa_recover_compressed(sig.as_ref(), &m) { Ok(pubkey) => - &sp_io::hashing::blake2_256(pubkey.as_ref()) - == >::as_ref(who), + &blake2_256(pubkey.as_ref()) == >::as_ref(who), _ => false, } }, @@ -125,6 +128,19 @@ impl LitentryMultiSignature { _ => false, } } + + fn verify_bitcoin(&self, msg: &[u8], signer: &Address33) -> bool { + match self { + Self::Ecdsa(ref sig) => { + let m = blake2_256(msg); + match sp_io::crypto::secp256k1_ecdsa_recover_compressed(sig.as_ref(), &m) { + Ok(pubkey) => pubkey.as_ref() == signer.as_ref(), + _ => false, + } + }, + _ => false, + } + } } fn verify_evm_signature(data: &[u8], sig: &EthereumSignature, who: &Address20) -> bool { diff --git a/tee-worker/litentry/primitives/src/validation_data.rs b/tee-worker/litentry/primitives/src/validation_data.rs index f1764a3839..8c3c7e2f2c 100644 --- a/tee-worker/litentry/primitives/src/validation_data.rs +++ b/tee-worker/litentry/primitives/src/validation_data.rs @@ -60,6 +60,8 @@ pub enum Web2ValidationData { pub enum Web3ValidationData { Substrate(Web3CommonValidationData), Evm(Web3CommonValidationData), + #[codec(index = 2)] + Bitcoin(Web3CommonValidationData), } impl Web3ValidationData { @@ -67,6 +69,7 @@ impl Web3ValidationData { match self { Self::Substrate(data) => &data.message, Self::Evm(data) => &data.message, + Self::Bitcoin(data) => &data.message, } } @@ -74,6 +77,7 @@ impl Web3ValidationData { match self { Self::Substrate(data) => &data.signature, Self::Evm(data) => &data.signature, + Self::Bitcoin(data) => &data.signature, } } } diff --git a/tee-worker/service/src/prometheus_metrics.rs b/tee-worker/service/src/prometheus_metrics.rs index e07797345e..dbf4263260 100644 --- a/tee-worker/service/src/prometheus_metrics.rs +++ b/tee-worker/service/src/prometheus_metrics.rs @@ -194,6 +194,7 @@ fn handle_stf_call_request(req: RequestType, _time: f64) { Identity::Github(_) => "Github", Identity::Substrate(_) => "Substrate", Identity::Evm(_) => "Evm", + Identity::Bitcoin(_) => "Bitcoin", }, RequestType::AssertionVerification(request) => match request.assertion { Assertion::A1 => "A1", diff --git a/tee-worker/ts-tests/integration-tests/di_bitcoin_identity.test.ts b/tee-worker/ts-tests/integration-tests/di_bitcoin_identity.test.ts new file mode 100644 index 0000000000..3aff1d02cc --- /dev/null +++ b/tee-worker/ts-tests/integration-tests/di_bitcoin_identity.test.ts @@ -0,0 +1,359 @@ +import { randomBytes, KeyObject } from 'crypto'; +import { step } from 'mocha-steps'; +import { assert } from 'chai'; +import { u8aToHex, bufferToU8a } from '@polkadot/util'; +import { + buildIdentityFromKeypair, + buildIdentityHelper, + buildValidations, + initIntegrationTestContext, + EthersSigner, + BitcoinSigner, + assertIdGraphMutationResult, + assertIdGraphHash, +} from './common/utils'; +import { assertIsInSidechainBlock, assertIdGraphMutation } from './common/utils/assertion'; +import { + createSignedTrustedCallLinkIdentity, + createSignedTrustedGetterIdGraph, + createSignedTrustedCallDeactivateIdentity, + createSignedTrustedCallActivateIdentity, + decodeIdGraph, + getSidechainNonce, + getTeeShieldingKey, + sendRequestFromGetter, + sendRequestFromTrustedCall, +} from './common/di-utils'; // @fixme move to a better place +import type { IntegrationTestContext } from './common/common-types'; +import { aesKey } from './common/call'; +import { LitentryValidationData, Web3Network } from 'parachain-api'; +import { LitentryPrimitivesIdentity } from 'sidechain-api'; +import { Vec } from '@polkadot/types'; +import { subscribeToEventsWithExtHash } from './common/transactions'; + +describe('Test Identity (bitcoin direct invocation)', function () { + let context: IntegrationTestContext = undefined as any; + let teeShieldingKey: KeyObject = undefined as any; + let aliceBitcoinIdentity: LitentryPrimitivesIdentity = undefined as any; + let aliceEvmIdentity: LitentryPrimitivesIdentity; + + // Alice links: + // - alice's evm identity + const linkIdentityRequestParams: { + nonce: number; + identity: LitentryPrimitivesIdentity; + validation: LitentryValidationData; + networks: Vec; + }[] = []; + this.timeout(6000000); + + before(async () => { + context = await initIntegrationTestContext( + process.env.WORKER_ENDPOINT!, // @fixme evil assertion; centralize env access + process.env.NODE_ENDPOINT! // @fixme evil assertion; centralize env access + ); + teeShieldingKey = await getTeeShieldingKey(context); + aliceBitcoinIdentity = await buildIdentityHelper( + u8aToHex(bufferToU8a(context.bitcoinWallet.alice.publicKey)), + 'Bitcoin', + context + ); + aliceEvmIdentity = await buildIdentityFromKeypair(new EthersSigner(context.ethersWallet.alice), context); + }); + + step('check idGraph from sidechain storage before linking', async function () { + const idGraphGetter = await createSignedTrustedGetterIdGraph( + context.api, + new BitcoinSigner(context.bitcoinWallet.alice), + aliceBitcoinIdentity + ); + const res = await sendRequestFromGetter(context, teeShieldingKey, idGraphGetter); + const idGraph = decodeIdGraph(context.sidechainRegistry, res.value); + assert.lengthOf(idGraph, 0); + }); + + step('linking identities (alice bitcoin account)', async function () { + let currentNonce = (await getSidechainNonce(context, teeShieldingKey, aliceBitcoinIdentity)).toNumber(); + const getNextNonce = () => currentNonce++; + + const aliceNonce = getNextNonce(); + const [aliceEvmValidation] = await buildValidations( + context, + [aliceBitcoinIdentity], + [aliceEvmIdentity], + aliceNonce, + 'ethereum', + undefined, + [context.ethersWallet.alice] + ); + const aliceEvmNetworks = context.api.createType('Vec', [ + 'Ethereum', + 'Bsc', + ]) as unknown as Vec; // @fixme #1878 + linkIdentityRequestParams.push({ + nonce: aliceNonce, + identity: aliceEvmIdentity, + validation: aliceEvmValidation, + networks: aliceEvmNetworks, + }); + + const identityLinkedEvents: any[] = []; + const idGraphHashResults: any[] = []; + let expectedIdGraphs: [LitentryPrimitivesIdentity, boolean][][] = [ + [ + [aliceBitcoinIdentity, true], + [aliceEvmIdentity, true], + ], + ]; + + for (const { nonce, identity, validation, networks } of linkIdentityRequestParams) { + const requestIdentifier = `0x${randomBytes(32).toString('hex')}`; + const eventsPromise = subscribeToEventsWithExtHash(requestIdentifier, context); + const linkIdentityCall = await createSignedTrustedCallLinkIdentity( + context.api, + context.mrEnclave, + context.api.createType('Index', nonce), + new BitcoinSigner(context.bitcoinWallet.alice), + aliceBitcoinIdentity, + identity.toHex(), + validation.toHex(), + networks.toHex(), + context.api.createType('Option', aesKey).toHex(), + requestIdentifier + ); + + const res = await sendRequestFromTrustedCall(context, teeShieldingKey, linkIdentityCall); + idGraphHashResults.push( + assertIdGraphMutationResult(context, res, 'LinkIdentityResult', expectedIdGraphs[0]) + ); + expectedIdGraphs = expectedIdGraphs.slice(1, expectedIdGraphs.length); + await assertIsInSidechainBlock('linkIdentityCall', res); + + const events = (await eventsPromise).map(({ event }) => event); + events.forEach((event) => { + if (context.api.events.identityManagement.LinkIdentityFailed.is(event)) { + assert.fail(JSON.stringify(event.toHuman(), null, 4)); + } + if (context.api.events.identityManagement.IdentityLinked.is(event)) { + identityLinkedEvents.push(event); + } + }); + } + + await assertIdGraphMutation( + new BitcoinSigner(context.bitcoinWallet.alice), + identityLinkedEvents, + idGraphHashResults, + 1 + ); + }); + + step('check user sidechain storage after linking', async function () { + const idGraphGetter = await createSignedTrustedGetterIdGraph( + context.api, + new BitcoinSigner(context.bitcoinWallet.alice), + aliceBitcoinIdentity + ); + const res = await sendRequestFromGetter(context, teeShieldingKey, idGraphGetter); + + const idGraph = decodeIdGraph(context.sidechainRegistry, res.value); + + // according to the order of linkIdentityRequestParams + const expectedWeb3Networks = [['Ethereum', 'Bsc']]; + let currentIndex = 0; + + for (const { identity } of linkIdentityRequestParams) { + const identityDump = JSON.stringify(identity.toHuman(), null, 4); + console.debug(`checking identity: ${identityDump}`); + const idGraphNode = idGraph.find(([idGraphNodeIdentity]) => idGraphNodeIdentity.eq(identity)); + assert.isDefined(idGraphNode, `identity not found in idGraph: ${identityDump}`); + const [, idGraphNodeContext] = idGraphNode!; + + const web3networks = idGraphNode![1].web3networks.toHuman(); + assert.deepEqual(web3networks, expectedWeb3Networks[currentIndex]); + + assert.equal( + idGraphNodeContext.status.toString(), + 'Active', + `status should be active for identity: ${identityDump}` + ); + console.debug('active ✅'); + + currentIndex++; + } + + await assertIdGraphHash(context, new BitcoinSigner(context.bitcoinWallet.alice), idGraph); + }); + step('deactivating identity(alice bitcoin account)', async function () { + let currentNonce = (await getSidechainNonce(context, teeShieldingKey, aliceBitcoinIdentity)).toNumber(); + const getNextNonce = () => currentNonce++; + + const deactivateIdentityRequestParams: { + nonce: number; + identity: LitentryPrimitivesIdentity; + }[] = []; + + const aliceEvmNonce = getNextNonce(); + + deactivateIdentityRequestParams.push({ + nonce: aliceEvmNonce, + identity: aliceEvmIdentity, + }); + + const identityDeactivatedEvents: any[] = []; + const idGraphHashResults: any[] = []; + let expectedIdGraphs: [LitentryPrimitivesIdentity, boolean][][] = [[[aliceEvmIdentity, false]]]; + + for (const { nonce, identity } of deactivateIdentityRequestParams) { + const requestIdentifier = `0x${randomBytes(32).toString('hex')}`; + const eventsPromise = subscribeToEventsWithExtHash(requestIdentifier, context); + const deactivateIdentityCall = await createSignedTrustedCallDeactivateIdentity( + context.api, + context.mrEnclave, + context.api.createType('Index', nonce), + new BitcoinSigner(context.bitcoinWallet.alice), + aliceBitcoinIdentity, + identity.toHex(), + context.api.createType('Option', aesKey).toHex(), + requestIdentifier + ); + + const res = await sendRequestFromTrustedCall(context, teeShieldingKey, deactivateIdentityCall); + idGraphHashResults.push( + assertIdGraphMutationResult(context, res, 'DeactivateIdentityResult', expectedIdGraphs[0]) + ); + expectedIdGraphs = expectedIdGraphs.slice(1, expectedIdGraphs.length); + await assertIsInSidechainBlock('deactivateIdentityCall', res); + + const events = (await eventsPromise).map(({ event }) => event); + events.forEach((event) => { + if (context.api.events.identityManagement.DeactivateIdentityFailed.is(event)) { + assert.fail(JSON.stringify(event.toHuman(), null, 4)); + } + if (context.api.events.identityManagement.IdentityDeactivated.is(event)) { + identityDeactivatedEvents.push(event); + } + }); + } + + await assertIdGraphMutation( + new BitcoinSigner(context.bitcoinWallet.alice), + identityDeactivatedEvents, + idGraphHashResults, + 1 + ); + }); + + step('check idGraph from sidechain storage after deactivating', async function () { + const idGraphGetter = await createSignedTrustedGetterIdGraph( + context.api, + new BitcoinSigner(context.bitcoinWallet.alice), + aliceBitcoinIdentity + ); + const res = await sendRequestFromGetter(context, teeShieldingKey, idGraphGetter); + const idGraph = decodeIdGraph(context.sidechainRegistry, res.value); + + for (const { identity } of linkIdentityRequestParams) { + const identityDump = JSON.stringify(identity.toHuman(), null, 4); + console.debug(`checking identity: ${identityDump}`); + const idGraphNode = idGraph.find(([idGraphNodeIdentity]) => idGraphNodeIdentity.eq(identity)); + assert.isDefined(idGraphNode, `identity not found in idGraph: ${identityDump}`); + const [, idGraphNodeContext] = idGraphNode!; + + assert.equal( + idGraphNodeContext.status.toString(), + 'Inactive', + `status should be Inactive for identity: ${identityDump}` + ); + console.debug('inactive ✅'); + } + + await assertIdGraphHash(context, new BitcoinSigner(context.bitcoinWallet.alice), idGraph); + }); + step('activating identity(alice bitcoin account)', async function () { + let currentNonce = (await getSidechainNonce(context, teeShieldingKey, aliceBitcoinIdentity)).toNumber(); + const getNextNonce = () => currentNonce++; + + const activateIdentityRequestParams: { + nonce: number; + identity: LitentryPrimitivesIdentity; + }[] = []; + + const aliceEvmNonce = getNextNonce(); + + activateIdentityRequestParams.push({ + nonce: aliceEvmNonce, + identity: aliceEvmIdentity, + }); + + const identityActivatedEvents: any[] = []; + const idGraphHashResults: any[] = []; + let expectedIdGraphs: [LitentryPrimitivesIdentity, boolean][][] = [[[aliceEvmIdentity, true]]]; + + for (const { nonce, identity } of activateIdentityRequestParams) { + const requestIdentifier = `0x${randomBytes(32).toString('hex')}`; + const eventsPromise = subscribeToEventsWithExtHash(requestIdentifier, context); + const activateIdentityCall = await createSignedTrustedCallActivateIdentity( + context.api, + context.mrEnclave, + context.api.createType('Index', nonce), + new BitcoinSigner(context.bitcoinWallet.alice), + aliceBitcoinIdentity, + identity.toHex(), + context.api.createType('Option', aesKey).toHex(), + requestIdentifier + ); + + const res = await sendRequestFromTrustedCall(context, teeShieldingKey, activateIdentityCall); + idGraphHashResults.push( + assertIdGraphMutationResult(context, res, 'ActivateIdentityResult', expectedIdGraphs[0]) + ); + expectedIdGraphs = expectedIdGraphs.slice(1, expectedIdGraphs.length); + await assertIsInSidechainBlock('activateIdentityCall', res); + + const events = (await eventsPromise).map(({ event }) => event); + events.forEach((event) => { + if (context.api.events.identityManagement.ActivateIdentityFailed.is(event)) { + assert.fail(JSON.stringify(event.toHuman(), null, 4)); + } + if (context.api.events.identityManagement.IdentityActivated.is(event)) { + identityActivatedEvents.push(event); + } + }); + } + + await assertIdGraphMutation( + new BitcoinSigner(context.bitcoinWallet.alice), + identityActivatedEvents, + idGraphHashResults, + 1 + ); + }); + + step('check idGraph from sidechain storage after activating', async function () { + const idGraphGetter = await createSignedTrustedGetterIdGraph( + context.api, + new BitcoinSigner(context.bitcoinWallet.alice), + aliceBitcoinIdentity + ); + const res = await sendRequestFromGetter(context, teeShieldingKey, idGraphGetter); + const idGraph = decodeIdGraph(context.sidechainRegistry, res.value); + + for (const { identity } of linkIdentityRequestParams) { + const identityDump = JSON.stringify(identity.toHuman(), null, 4); + console.debug(`checking identity: ${identityDump}`); + const idGraphNode = idGraph.find(([idGraphNodeIdentity]) => idGraphNodeIdentity.eq(identity)); + assert.isDefined(idGraphNode, `identity not found in idGraph: ${identityDump}`); + const [, idGraphNodeContext] = idGraphNode!; + + assert.equal( + idGraphNodeContext.status.toString(), + 'Active', + `status should be active for identity: ${identityDump}` + ); + console.debug('active ✅'); + } + + await assertIdGraphHash(context, new BitcoinSigner(context.bitcoinWallet.alice), idGraph); + }); +}); diff --git a/tee-worker/ts-tests/parachain-api/prepare-build/interfaces/identity/definitions.ts b/tee-worker/ts-tests/parachain-api/prepare-build/interfaces/identity/definitions.ts index 5b31e0b697..b5d7008a30 100644 --- a/tee-worker/ts-tests/parachain-api/prepare-build/interfaces/identity/definitions.ts +++ b/tee-worker/ts-tests/parachain-api/prepare-build/interfaces/identity/definitions.ts @@ -94,10 +94,12 @@ export default { Github: "IdentityString", Substrate: "Address32", Evm: "Address20", + Bitcoin: "Address33", }, }, Address32: "[u8;32]", Address20: "[u8;20]", + Address33: "[u8;33]", IdentityString: "Vec", Web3Network: { _enum: [ @@ -110,6 +112,7 @@ export default { "SubstrateTestnet", "Ethereum", "Bsc", + "Bitcoin", ], }, LitentryValidationData: { @@ -136,6 +139,7 @@ export default { _enum: { Substrate: "Web3CommonValidationData", Evm: "Web3CommonValidationData", + Bitcoin: "Web3CommonValidationData", }, }, Web3CommonValidationData: { @@ -145,13 +149,16 @@ export default { LitentryMultiSignature: { _enum: { - Ed25519: "ed25519::Signature", - Sr25519: "sr25519::Signature", - Ecdsa: "ecdsa::Signature", + Ed25519: "Ed25519Signature", + Sr25519: "Sr25519Signature", + Ecdsa: "EcdsaSignature", Ethereum: "EthereumSignature", EthereumPrettified: "EthereumSignature", }, }, + Ed25519Signature: "([u8; 64])", + Sr25519Signature: "([u8; 64])", + EcdsaSignature: "([u8; 65])", EthereumSignature: "([u8; 65])", IdentityGenericEvent: { diff --git a/ts-tests/tests/base-filter.test.ts b/ts-tests/tests/base-filter.test.ts index e3bcba7b0b..e5b43f7077 100644 --- a/ts-tests/tests/base-filter.test.ts +++ b/ts-tests/tests/base-filter.test.ts @@ -1,6 +1,5 @@ import { expect } from 'chai'; import { step } from 'mocha-steps'; -import { assert } from 'chai'; import { signAndSend, describeLitentry, loadConfig, sleep } from './utils'; describeLitentry('Test Base Filter', ``, (context) => { From ebfa728ad5ad86dfbc907bed09511f12fdd6a29b Mon Sep 17 00:00:00 2001 From: Kailai Wang Date: Fri, 15 Dec 2023 10:45:55 +0000 Subject: [PATCH 2/4] fmt --- .../core/assertion-build/src/achainable/amount_token.rs | 5 ++++- tee-worker/litentry/core/credentials/src/achainable/mod.rs | 2 +- tee-worker/litentry/core/credentials/src/lib.rs | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tee-worker/litentry/core/assertion-build/src/achainable/amount_token.rs b/tee-worker/litentry/core/assertion-build/src/achainable/amount_token.rs index 01947fed75..6b69ace80d 100644 --- a/tee-worker/litentry/core/assertion-build/src/achainable/amount_token.rs +++ b/tee-worker/litentry/core/assertion-build/src/achainable/amount_token.rs @@ -24,7 +24,10 @@ use crate::{ achainable::{query_lit_holding_amount, request_achainable_balance}, *, }; -use lc_credentials::{achainable::lit_holding_amount::AchainableLitHoldingAmountUpdate, litentry_profile::token_balance::TokenBalanceInfo}; +use lc_credentials::{ + achainable::lit_holding_amount::AchainableLitHoldingAmountUpdate, + litentry_profile::token_balance::TokenBalanceInfo, +}; use lc_data_providers::{ achainable_names::{AchainableNameAmountToken, GetAchainableName}, ETokenAddress, TokenFromString, diff --git a/tee-worker/litentry/core/credentials/src/achainable/mod.rs b/tee-worker/litentry/core/credentials/src/achainable/mod.rs index bb0a2bb10d..3b04e23ddb 100644 --- a/tee-worker/litentry/core/credentials/src/achainable/mod.rs +++ b/tee-worker/litentry/core/credentials/src/achainable/mod.rs @@ -14,4 +14,4 @@ // You should have received a copy of the GNU General Public License // along with Litentry. If not, see . -pub mod lit_holding_amount; \ No newline at end of file +pub mod lit_holding_amount; diff --git a/tee-worker/litentry/core/credentials/src/lib.rs b/tee-worker/litentry/core/credentials/src/lib.rs index f5c4dafecb..397514b736 100644 --- a/tee-worker/litentry/core/credentials/src/lib.rs +++ b/tee-worker/litentry/core/credentials/src/lib.rs @@ -77,8 +77,8 @@ pub use error::Error; pub mod oneblock; pub mod schema; -pub mod assertion_logic; pub mod achainable; +pub mod assertion_logic; use assertion_logic::{AssertionLogic, Op}; pub mod generic_discord_role; pub mod litentry_profile; From a050c3595a9da96490b22fa79171af25d93ebb64 Mon Sep 17 00:00:00 2001 From: Kailai Wang Date: Fri, 15 Dec 2023 11:26:28 +0000 Subject: [PATCH 3/4] fix compile error --- primitives/core/src/error.rs | 1 + primitives/core/src/network.rs | 2 +- runtime/litentry/src/lib.rs | 2 +- runtime/litmus/src/lib.rs | 2 +- runtime/rococo/src/lib.rs | 2 +- .../commands/litentry/link_identity.rs | 8 +- .../identity-verification/src/web3/mod.rs | 2 + .../litentry/primitives/src/identity.rs | 91 ++++++++++++++++++- 8 files changed, 98 insertions(+), 12 deletions(-) diff --git a/primitives/core/src/error.rs b/primitives/core/src/error.rs index 2cfed5e6fc..e13fdbab42 100644 --- a/primitives/core/src/error.rs +++ b/primitives/core/src/error.rs @@ -50,6 +50,7 @@ pub enum ErrorDetail { VerifyEvmSignatureFailed, RecoverEvmAddressFailed, Web3NetworkOutOfBounds, + VerifyBitcoinSignatureFailed, } // We could have used Into, but we want it to be more explicit, similar to `into_iter` diff --git a/primitives/core/src/network.rs b/primitives/core/src/network.rs index 4754ee07cb..93cb95c87a 100644 --- a/primitives/core/src/network.rs +++ b/primitives/core/src/network.rs @@ -61,7 +61,6 @@ pub enum Web3Network { Bsc, // btc - #[codec(index = 9)] Bitcoin, } @@ -77,6 +76,7 @@ impl Web3Network { Web3Network::SubstrateTestnet => "SubstrateTestnet", Web3Network::Ethereum => "Ethereum", Web3Network::Bsc => "Bsc", + Web3Network::Bitcoin => "Bitcoin", } } diff --git a/runtime/litentry/src/lib.rs b/runtime/litentry/src/lib.rs index 23760d708f..3013a7e5cb 100644 --- a/runtime/litentry/src/lib.rs +++ b/runtime/litentry/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_name: create_runtime_str!("litentry-parachain"), authoring_version: 1, // same versioning-mechanism as polkadot: use last digit for minor updates - spec_version: 9167, + spec_version: 9168, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/runtime/litmus/src/lib.rs b/runtime/litmus/src/lib.rs index 217ae996f2..b36b96f2d3 100644 --- a/runtime/litmus/src/lib.rs +++ b/runtime/litmus/src/lib.rs @@ -150,7 +150,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_name: create_runtime_str!("litmus-parachain"), authoring_version: 1, // same versioning-mechanism as polkadot: use last digit for minor updates - spec_version: 9167, + spec_version: 9168, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index 956c4b58ea..3e707aa3d8 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -149,7 +149,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_name: create_runtime_str!("rococo-parachain"), authoring_version: 1, // same versioning-mechanism as polkadot: use last digit for minor updates - spec_version: 9167, + spec_version: 9168, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/tee-worker/cli/src/base_cli/commands/litentry/link_identity.rs b/tee-worker/cli/src/base_cli/commands/litentry/link_identity.rs index d5115df56c..ce53b704b2 100644 --- a/tee-worker/cli/src/base_cli/commands/litentry/link_identity.rs +++ b/tee-worker/cli/src/base_cli/commands/litentry/link_identity.rs @@ -35,7 +35,7 @@ pub struct LinkIdentityCommand { /// AccountId in ss58check format account: String, /// Identity to be created, in did form - identity: String, + did: String, /// Shard identifier shard: String, } @@ -57,11 +57,7 @@ impl LinkIdentityCommand { let who = sr25519_core::Pair::from_string(&self.account, None).unwrap(); let chain_api = chain_api.set_signer(who.clone()); - let identity: Result = serde_json::from_str(self.identity.as_str()); - if let Err(e) = identity { - warn!("Deserialize Identity error: {:?}", e.to_string()); - return - } + let identity = Identity::from_did(self.did.as_str()).unwrap(); let tee_shielding_key = get_shielding_key(cli).unwrap(); let encrypted_identity = tee_shielding_key.encrypt(&identity.encode()).unwrap(); diff --git a/tee-worker/litentry/core/identity-verification/src/web3/mod.rs b/tee-worker/litentry/core/identity-verification/src/web3/mod.rs index 182359024d..da13126965 100644 --- a/tee-worker/litentry/core/identity-verification/src/web3/mod.rs +++ b/tee-worker/litentry/core/identity-verification/src/web3/mod.rs @@ -50,6 +50,8 @@ pub fn verify( return Err(Error::LinkIdentityFailed(ErrorDetail::VerifySubstrateSignatureFailed)), Web3ValidationData::Evm(_) => return Err(Error::LinkIdentityFailed(ErrorDetail::VerifyEvmSignatureFailed)), + Web3ValidationData::Bitcoin(_) => + return Err(Error::LinkIdentityFailed(ErrorDetail::VerifyBitcoinSignatureFailed)), } } diff --git a/tee-worker/litentry/primitives/src/identity.rs b/tee-worker/litentry/primitives/src/identity.rs index 4f8a3526a4..1bfcdd05f1 100644 --- a/tee-worker/litentry/primitives/src/identity.rs +++ b/tee-worker/litentry/primitives/src/identity.rs @@ -21,10 +21,11 @@ extern crate sgx_tstd as std; use serde::{Deserialize, Serialize}; use codec::{Decode, Encode, MaxEncodedLen}; +use itp_utils::hex::decode_hex; use pallet_evm::{AddressMapping, HashedAddressMapping as GenericHashedAddressMapping}; use parentchain_primitives::{AccountId, Web3Network}; use scale_info::TypeInfo; -use sp_core::{crypto::AccountId32, ed25519, sr25519, ByteArray, H160}; +use sp_core::{crypto::AccountId32, ecdsa, ed25519, sr25519, ByteArray, H160}; use sp_io::hashing::blake2_256; use sp_runtime::{ traits::{BlakeTwo256, ConstU32}, @@ -53,6 +54,19 @@ impl From<[u8; 20]> for Address20 { } } +impl<'a> TryFrom<&'a [u8]> for Address20 { + type Error = (); + fn try_from(x: &'a [u8]) -> Result { + if x.len() == 20 { + let mut data = [0; 20]; + data.copy_from_slice(x); + Ok(Address20(data)) + } else { + Err(()) + } + } +} + #[derive(Encode, Decode, Copy, Clone, Debug, Default, PartialEq, Eq, TypeInfo, MaxEncodedLen)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct Address32([u8; 32]); @@ -75,6 +89,19 @@ impl From for Address32 { } } +impl<'a> TryFrom<&'a [u8]> for Address32 { + type Error = (); + fn try_from(x: &'a [u8]) -> Result { + if x.len() == 32 { + let mut data = [0; 32]; + data.copy_from_slice(x); + Ok(Address32(data)) + } else { + Err(()) + } + } +} + impl From for AccountId32 { fn from(value: Address32) -> Self { let raw: [u8; 32] = *value.as_ref(); @@ -101,7 +128,7 @@ impl From for Address32 { } // TODO: maybe use macros to reduce verbosity -#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[derive(Encode, Decode, Copy, Clone, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub struct Address33([u8; 33]); impl AsRef<[u8; 33]> for Address33 { fn as_ref(&self) -> &[u8; 33] { @@ -214,6 +241,66 @@ impl Identity { _ => None, } } + + #[cfg(any(feature = "std", feature = "sgx"))] + pub fn from_did(s: &str) -> Result> { + let did_prefix = std::string::String::from("did:litentry:"); + if s.starts_with(&did_prefix) { + let did_suffix = &s[did_prefix.len()..]; + let v: Vec<&str> = did_suffix.split(':').collect(); + if v.len() == 2 { + if v[0] == "substrate" { + let handle = decode_hex(v[1]) + .unwrap() + .as_slice() + .try_into() + .map_err(|_| "Address32 conversion error")?; + return Ok(Identity::Substrate(handle)) + } else if v[0] == "evm" { + let handle = decode_hex(v[1]) + .unwrap() + .as_slice() + .try_into() + .map_err(|_| "Address20 conversion error")?; + return Ok(Identity::Evm(handle)) + } else if v[0] == "bitcoin" { + let handle = decode_hex(v[1]) + .unwrap() + .as_slice() + .try_into() + .map_err(|_| "Address33 conversion error")?; + return Ok(Identity::Bitcoin(handle)) + } else if v[0] == "github" { + return Ok(Identity::Github( + v[1].as_bytes() + .to_vec() + .try_into() + .map_err(|_| "github conversion error")?, + )) + } else if v[0] == "discord" { + return Ok(Identity::Discord( + v[1].as_bytes() + .to_vec() + .try_into() + .map_err(|_| "discord conversion error")?, + )) + } else if v[0] == "twitter" { + return Ok(Identity::Twitter( + v[1].as_bytes() + .to_vec() + .try_into() + .map_err(|_| "twitter conversion error")?, + )) + } else { + return Err("Unknown did type".into()) + } + } else { + return Err("Wrong did suffix".into()) + } + } + + Err("Wrong did prefix".into()) + } } impl From for Identity { From 7c7466a0265560487fc4ebf75982815340b9d797 Mon Sep 17 00:00:00 2001 From: Kailai Wang Date: Fri, 15 Dec 2023 11:32:32 +0000 Subject: [PATCH 4/4] do not include the tests --- .../docker/lit-di-bitcoin-identity-test.yml | 24 -- .../di_bitcoin_identity.test.ts | 359 ------------------ 2 files changed, 383 deletions(-) delete mode 100644 tee-worker/docker/lit-di-bitcoin-identity-test.yml delete mode 100644 tee-worker/ts-tests/integration-tests/di_bitcoin_identity.test.ts diff --git a/tee-worker/docker/lit-di-bitcoin-identity-test.yml b/tee-worker/docker/lit-di-bitcoin-identity-test.yml deleted file mode 100644 index 8a4b5a2c59..0000000000 --- a/tee-worker/docker/lit-di-bitcoin-identity-test.yml +++ /dev/null @@ -1,24 +0,0 @@ -services: - lit-di-bitcoin-identity-test: - image: litentry/litentry-cli:latest - container_name: litentry-di-bitcoin-identity-test - volumes: - - ../ts-tests:/ts-tests - - ../client-api:/client-api - - ../cli:/usr/local/worker-cli - build: - context: .. - dockerfile: build.Dockerfile - target: deployed-client - depends_on: - litentry-node: - condition: service_healthy - litentry-worker-1: - condition: service_healthy - networks: - - litentry-test-network - entrypoint: "bash -c '/usr/local/worker-cli/lit_ts_test.sh test-di-bitcoin-identity 2>&1' " - restart: "no" -networks: - litentry-test-network: - driver: bridge diff --git a/tee-worker/ts-tests/integration-tests/di_bitcoin_identity.test.ts b/tee-worker/ts-tests/integration-tests/di_bitcoin_identity.test.ts deleted file mode 100644 index 3aff1d02cc..0000000000 --- a/tee-worker/ts-tests/integration-tests/di_bitcoin_identity.test.ts +++ /dev/null @@ -1,359 +0,0 @@ -import { randomBytes, KeyObject } from 'crypto'; -import { step } from 'mocha-steps'; -import { assert } from 'chai'; -import { u8aToHex, bufferToU8a } from '@polkadot/util'; -import { - buildIdentityFromKeypair, - buildIdentityHelper, - buildValidations, - initIntegrationTestContext, - EthersSigner, - BitcoinSigner, - assertIdGraphMutationResult, - assertIdGraphHash, -} from './common/utils'; -import { assertIsInSidechainBlock, assertIdGraphMutation } from './common/utils/assertion'; -import { - createSignedTrustedCallLinkIdentity, - createSignedTrustedGetterIdGraph, - createSignedTrustedCallDeactivateIdentity, - createSignedTrustedCallActivateIdentity, - decodeIdGraph, - getSidechainNonce, - getTeeShieldingKey, - sendRequestFromGetter, - sendRequestFromTrustedCall, -} from './common/di-utils'; // @fixme move to a better place -import type { IntegrationTestContext } from './common/common-types'; -import { aesKey } from './common/call'; -import { LitentryValidationData, Web3Network } from 'parachain-api'; -import { LitentryPrimitivesIdentity } from 'sidechain-api'; -import { Vec } from '@polkadot/types'; -import { subscribeToEventsWithExtHash } from './common/transactions'; - -describe('Test Identity (bitcoin direct invocation)', function () { - let context: IntegrationTestContext = undefined as any; - let teeShieldingKey: KeyObject = undefined as any; - let aliceBitcoinIdentity: LitentryPrimitivesIdentity = undefined as any; - let aliceEvmIdentity: LitentryPrimitivesIdentity; - - // Alice links: - // - alice's evm identity - const linkIdentityRequestParams: { - nonce: number; - identity: LitentryPrimitivesIdentity; - validation: LitentryValidationData; - networks: Vec; - }[] = []; - this.timeout(6000000); - - before(async () => { - context = await initIntegrationTestContext( - process.env.WORKER_ENDPOINT!, // @fixme evil assertion; centralize env access - process.env.NODE_ENDPOINT! // @fixme evil assertion; centralize env access - ); - teeShieldingKey = await getTeeShieldingKey(context); - aliceBitcoinIdentity = await buildIdentityHelper( - u8aToHex(bufferToU8a(context.bitcoinWallet.alice.publicKey)), - 'Bitcoin', - context - ); - aliceEvmIdentity = await buildIdentityFromKeypair(new EthersSigner(context.ethersWallet.alice), context); - }); - - step('check idGraph from sidechain storage before linking', async function () { - const idGraphGetter = await createSignedTrustedGetterIdGraph( - context.api, - new BitcoinSigner(context.bitcoinWallet.alice), - aliceBitcoinIdentity - ); - const res = await sendRequestFromGetter(context, teeShieldingKey, idGraphGetter); - const idGraph = decodeIdGraph(context.sidechainRegistry, res.value); - assert.lengthOf(idGraph, 0); - }); - - step('linking identities (alice bitcoin account)', async function () { - let currentNonce = (await getSidechainNonce(context, teeShieldingKey, aliceBitcoinIdentity)).toNumber(); - const getNextNonce = () => currentNonce++; - - const aliceNonce = getNextNonce(); - const [aliceEvmValidation] = await buildValidations( - context, - [aliceBitcoinIdentity], - [aliceEvmIdentity], - aliceNonce, - 'ethereum', - undefined, - [context.ethersWallet.alice] - ); - const aliceEvmNetworks = context.api.createType('Vec', [ - 'Ethereum', - 'Bsc', - ]) as unknown as Vec; // @fixme #1878 - linkIdentityRequestParams.push({ - nonce: aliceNonce, - identity: aliceEvmIdentity, - validation: aliceEvmValidation, - networks: aliceEvmNetworks, - }); - - const identityLinkedEvents: any[] = []; - const idGraphHashResults: any[] = []; - let expectedIdGraphs: [LitentryPrimitivesIdentity, boolean][][] = [ - [ - [aliceBitcoinIdentity, true], - [aliceEvmIdentity, true], - ], - ]; - - for (const { nonce, identity, validation, networks } of linkIdentityRequestParams) { - const requestIdentifier = `0x${randomBytes(32).toString('hex')}`; - const eventsPromise = subscribeToEventsWithExtHash(requestIdentifier, context); - const linkIdentityCall = await createSignedTrustedCallLinkIdentity( - context.api, - context.mrEnclave, - context.api.createType('Index', nonce), - new BitcoinSigner(context.bitcoinWallet.alice), - aliceBitcoinIdentity, - identity.toHex(), - validation.toHex(), - networks.toHex(), - context.api.createType('Option', aesKey).toHex(), - requestIdentifier - ); - - const res = await sendRequestFromTrustedCall(context, teeShieldingKey, linkIdentityCall); - idGraphHashResults.push( - assertIdGraphMutationResult(context, res, 'LinkIdentityResult', expectedIdGraphs[0]) - ); - expectedIdGraphs = expectedIdGraphs.slice(1, expectedIdGraphs.length); - await assertIsInSidechainBlock('linkIdentityCall', res); - - const events = (await eventsPromise).map(({ event }) => event); - events.forEach((event) => { - if (context.api.events.identityManagement.LinkIdentityFailed.is(event)) { - assert.fail(JSON.stringify(event.toHuman(), null, 4)); - } - if (context.api.events.identityManagement.IdentityLinked.is(event)) { - identityLinkedEvents.push(event); - } - }); - } - - await assertIdGraphMutation( - new BitcoinSigner(context.bitcoinWallet.alice), - identityLinkedEvents, - idGraphHashResults, - 1 - ); - }); - - step('check user sidechain storage after linking', async function () { - const idGraphGetter = await createSignedTrustedGetterIdGraph( - context.api, - new BitcoinSigner(context.bitcoinWallet.alice), - aliceBitcoinIdentity - ); - const res = await sendRequestFromGetter(context, teeShieldingKey, idGraphGetter); - - const idGraph = decodeIdGraph(context.sidechainRegistry, res.value); - - // according to the order of linkIdentityRequestParams - const expectedWeb3Networks = [['Ethereum', 'Bsc']]; - let currentIndex = 0; - - for (const { identity } of linkIdentityRequestParams) { - const identityDump = JSON.stringify(identity.toHuman(), null, 4); - console.debug(`checking identity: ${identityDump}`); - const idGraphNode = idGraph.find(([idGraphNodeIdentity]) => idGraphNodeIdentity.eq(identity)); - assert.isDefined(idGraphNode, `identity not found in idGraph: ${identityDump}`); - const [, idGraphNodeContext] = idGraphNode!; - - const web3networks = idGraphNode![1].web3networks.toHuman(); - assert.deepEqual(web3networks, expectedWeb3Networks[currentIndex]); - - assert.equal( - idGraphNodeContext.status.toString(), - 'Active', - `status should be active for identity: ${identityDump}` - ); - console.debug('active ✅'); - - currentIndex++; - } - - await assertIdGraphHash(context, new BitcoinSigner(context.bitcoinWallet.alice), idGraph); - }); - step('deactivating identity(alice bitcoin account)', async function () { - let currentNonce = (await getSidechainNonce(context, teeShieldingKey, aliceBitcoinIdentity)).toNumber(); - const getNextNonce = () => currentNonce++; - - const deactivateIdentityRequestParams: { - nonce: number; - identity: LitentryPrimitivesIdentity; - }[] = []; - - const aliceEvmNonce = getNextNonce(); - - deactivateIdentityRequestParams.push({ - nonce: aliceEvmNonce, - identity: aliceEvmIdentity, - }); - - const identityDeactivatedEvents: any[] = []; - const idGraphHashResults: any[] = []; - let expectedIdGraphs: [LitentryPrimitivesIdentity, boolean][][] = [[[aliceEvmIdentity, false]]]; - - for (const { nonce, identity } of deactivateIdentityRequestParams) { - const requestIdentifier = `0x${randomBytes(32).toString('hex')}`; - const eventsPromise = subscribeToEventsWithExtHash(requestIdentifier, context); - const deactivateIdentityCall = await createSignedTrustedCallDeactivateIdentity( - context.api, - context.mrEnclave, - context.api.createType('Index', nonce), - new BitcoinSigner(context.bitcoinWallet.alice), - aliceBitcoinIdentity, - identity.toHex(), - context.api.createType('Option', aesKey).toHex(), - requestIdentifier - ); - - const res = await sendRequestFromTrustedCall(context, teeShieldingKey, deactivateIdentityCall); - idGraphHashResults.push( - assertIdGraphMutationResult(context, res, 'DeactivateIdentityResult', expectedIdGraphs[0]) - ); - expectedIdGraphs = expectedIdGraphs.slice(1, expectedIdGraphs.length); - await assertIsInSidechainBlock('deactivateIdentityCall', res); - - const events = (await eventsPromise).map(({ event }) => event); - events.forEach((event) => { - if (context.api.events.identityManagement.DeactivateIdentityFailed.is(event)) { - assert.fail(JSON.stringify(event.toHuman(), null, 4)); - } - if (context.api.events.identityManagement.IdentityDeactivated.is(event)) { - identityDeactivatedEvents.push(event); - } - }); - } - - await assertIdGraphMutation( - new BitcoinSigner(context.bitcoinWallet.alice), - identityDeactivatedEvents, - idGraphHashResults, - 1 - ); - }); - - step('check idGraph from sidechain storage after deactivating', async function () { - const idGraphGetter = await createSignedTrustedGetterIdGraph( - context.api, - new BitcoinSigner(context.bitcoinWallet.alice), - aliceBitcoinIdentity - ); - const res = await sendRequestFromGetter(context, teeShieldingKey, idGraphGetter); - const idGraph = decodeIdGraph(context.sidechainRegistry, res.value); - - for (const { identity } of linkIdentityRequestParams) { - const identityDump = JSON.stringify(identity.toHuman(), null, 4); - console.debug(`checking identity: ${identityDump}`); - const idGraphNode = idGraph.find(([idGraphNodeIdentity]) => idGraphNodeIdentity.eq(identity)); - assert.isDefined(idGraphNode, `identity not found in idGraph: ${identityDump}`); - const [, idGraphNodeContext] = idGraphNode!; - - assert.equal( - idGraphNodeContext.status.toString(), - 'Inactive', - `status should be Inactive for identity: ${identityDump}` - ); - console.debug('inactive ✅'); - } - - await assertIdGraphHash(context, new BitcoinSigner(context.bitcoinWallet.alice), idGraph); - }); - step('activating identity(alice bitcoin account)', async function () { - let currentNonce = (await getSidechainNonce(context, teeShieldingKey, aliceBitcoinIdentity)).toNumber(); - const getNextNonce = () => currentNonce++; - - const activateIdentityRequestParams: { - nonce: number; - identity: LitentryPrimitivesIdentity; - }[] = []; - - const aliceEvmNonce = getNextNonce(); - - activateIdentityRequestParams.push({ - nonce: aliceEvmNonce, - identity: aliceEvmIdentity, - }); - - const identityActivatedEvents: any[] = []; - const idGraphHashResults: any[] = []; - let expectedIdGraphs: [LitentryPrimitivesIdentity, boolean][][] = [[[aliceEvmIdentity, true]]]; - - for (const { nonce, identity } of activateIdentityRequestParams) { - const requestIdentifier = `0x${randomBytes(32).toString('hex')}`; - const eventsPromise = subscribeToEventsWithExtHash(requestIdentifier, context); - const activateIdentityCall = await createSignedTrustedCallActivateIdentity( - context.api, - context.mrEnclave, - context.api.createType('Index', nonce), - new BitcoinSigner(context.bitcoinWallet.alice), - aliceBitcoinIdentity, - identity.toHex(), - context.api.createType('Option', aesKey).toHex(), - requestIdentifier - ); - - const res = await sendRequestFromTrustedCall(context, teeShieldingKey, activateIdentityCall); - idGraphHashResults.push( - assertIdGraphMutationResult(context, res, 'ActivateIdentityResult', expectedIdGraphs[0]) - ); - expectedIdGraphs = expectedIdGraphs.slice(1, expectedIdGraphs.length); - await assertIsInSidechainBlock('activateIdentityCall', res); - - const events = (await eventsPromise).map(({ event }) => event); - events.forEach((event) => { - if (context.api.events.identityManagement.ActivateIdentityFailed.is(event)) { - assert.fail(JSON.stringify(event.toHuman(), null, 4)); - } - if (context.api.events.identityManagement.IdentityActivated.is(event)) { - identityActivatedEvents.push(event); - } - }); - } - - await assertIdGraphMutation( - new BitcoinSigner(context.bitcoinWallet.alice), - identityActivatedEvents, - idGraphHashResults, - 1 - ); - }); - - step('check idGraph from sidechain storage after activating', async function () { - const idGraphGetter = await createSignedTrustedGetterIdGraph( - context.api, - new BitcoinSigner(context.bitcoinWallet.alice), - aliceBitcoinIdentity - ); - const res = await sendRequestFromGetter(context, teeShieldingKey, idGraphGetter); - const idGraph = decodeIdGraph(context.sidechainRegistry, res.value); - - for (const { identity } of linkIdentityRequestParams) { - const identityDump = JSON.stringify(identity.toHuman(), null, 4); - console.debug(`checking identity: ${identityDump}`); - const idGraphNode = idGraph.find(([idGraphNodeIdentity]) => idGraphNodeIdentity.eq(identity)); - assert.isDefined(idGraphNode, `identity not found in idGraph: ${identityDump}`); - const [, idGraphNodeContext] = idGraphNode!; - - assert.equal( - idGraphNodeContext.status.toString(), - 'Active', - `status should be active for identity: ${identityDump}` - ); - console.debug('active ✅'); - } - - await assertIdGraphHash(context, new BitcoinSigner(context.bitcoinWallet.alice), idGraph); - }); -});