Skip to content

Commit

Permalink
Fixes to addWeb3IdCredentials
Browse files Browse the repository at this point in the history
  • Loading branch information
Hjort committed Aug 10, 2023
1 parent 0b26029 commit a9459a8
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 153 deletions.
4 changes: 3 additions & 1 deletion examples/add-example-Web3Id/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
'ConcordiumVerifiableCredential',
'UniversityDegreeCredential',
],
issuer: 'did:ccd:testnet:sci:5463:0/issuer',
issuer: 'did:ccd:testnet:sci:' + issuerIndex.value + ':0/issuer',
credentialSubject: {
attributes: values,
},
Expand Down Expand Up @@ -97,6 +97,8 @@ <h3 id="accountAddress">Account address:</h3>
value="https://raw.githubusercontent.com/Concordium/concordium-web3id/credential-metadata-example/examples/json-schemas/metadata/credential-metadata.json"
/>
<br />
issuer Index: <input type="number" id="issuerIndex" value="5463" />
<br />
<h3>Attribute values:</h3>
degreeType: <input type="text" id="degreeType" value="BachelorDegree" />
<br />
Expand Down
20 changes: 10 additions & 10 deletions packages/browser-wallet-api-helpers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,20 +235,20 @@ const provider = await detectConcordiumProvider();
await provider.requestIdProof('2za2yAXbFiaB151oYqTteZfqiBzibHXizwjNbpdU8hodq9SfEk', ['AA', 'BB'], '1399', '0');
```

### Add WebId Credentials
### Add Web3Id Credentials

To add a Web3IdCredential, use the `addWeb3IdCredential` endpoint.
The credential itself and the url for the metadata must be provided. In addition, the function takes a callback function that takes the credentialHolderId as input, and which should return the randomness used to create the commitments on the values/properties in the credential, and the signature on the commitments and credentialHolderId. If the callback does return a valid signature, the credential is not added to the wallet.
The credential itself and the url for the metadata must be provided. In addition, the function takes a callback function that takes a DID for the credentialHolderId as input, and which should return the randomness used to create the commitments on the values/properties in the credential, and the signature on the commitments and credentialHolderId. If the callback does return a valid signature, the credential is not added to the wallet.

// TODO Add example.
Note that the id fields of the credential are omitted, and added by the wallet itself, as they require the credentialHolderId.

### Request Verifiable Presentation

It is possible to request a veriable presentation on a number statements about accounts and web3IdCredentials. The function takes 2 arguments. The statements to be proved and a challenge to ensure that the proof was not generated for a different context.

To build a statement, the Web3StatementBuilder, from the web-sdk, can be used. (// TODO link to it)

If the wallet is locked, or you have not connected with the wallet (or previously been allowlisted) or if the user rejects proving the statement, the `Promise` will reject.
```typescript
provider.addWeb3IdCredential(credential, metadataUrl, async (id) => {
const randomness = createRandomness(attributes); // Choose some randomness for the attribute commitments.
const proof = createSignature(credential, id, randomness); // Create a signature to prove that the commitments are created by the issuer.
return { proof, randomness };
});
```

## Events

Expand Down
22 changes: 15 additions & 7 deletions packages/browser-wallet-api-helpers/src/wallet-api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,26 @@ export interface MetadataUrl {
hash?: string;
}

interface CredentialSchema {
id: string;
type: string;
}

/**
* The expected form of a Web3IdCredential, with the id fields omitted.
*/
export interface APIVerifiableCredential {
$schema: string;
type: string[];
issuer: string;
issuanceDate: string;
credentialSubject: Omit<CredentialSubject, 'id'>;
credentialSchema: {
id: string;
type: string;
};
credentialSchema: CredentialSchema;
}

/**
* Expected format for the proof that the Web3IdCredential's attribute commitments are valid
*/
export interface CredentialProof {
proofPurpose: 'assertionMethod';
proofValue: HexString;
Expand Down Expand Up @@ -200,14 +208,14 @@ interface MainWalletApi {
* Note that this will throw an error if the dApp is not allowlisted, locked, or if the user rejects adding the credential.
* @param credential the web3IdCredential that should be added to the wallet
* @param metadataUrl the url where the metadata, to display the credential, is located.
* @param createSignature a callback function, which takes the credentialId as input and must return the randomness used for the commitment of the values and signature on the commitments and credentialId.
* @returns the credentialId, containing the publicKey that will be associated with the credential.
* @param createSignature a callback function, which takes a DID identifier for the credentialHolderId as input and must return the randomness used for the commitment of the values and signature on the commitments and credentialId.
* @returns the DID identifier for the credentialHolderId, i.e. the publicKey that will be associated with the credential.
*/
addWeb3IdCredential(
credential: APIVerifiableCredential,
metadataUrl: MetadataUrl,
createSignature: (
credentialId: string
credentialHolderIdDID: string
) => Promise<{ randomness: Record<string, string>; proof: CredentialProof }>
): Promise<string>;
}
Expand Down
12 changes: 6 additions & 6 deletions packages/browser-wallet-api/src/wallet-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ class WalletApi extends EventEmitter implements IWalletApi {
credential: APIVerifiableCredential,
metadataUrl: MetadataUrl,
createSignature: (
credentialId: string
credentialHolderIdDID: string
) => Promise<{ randomness: Record<string, string>; proof: CredentialProof }>
): Promise<string> {
const res = await this.messageHandler.sendMessage<MessageStatusWrapper<string>>(
Expand All @@ -266,14 +266,14 @@ class WalletApi extends EventEmitter implements IWalletApi {
throw new Error(res.message);
}

const credentialId = res.result;
const credentialHolderIdDID = res.result;

const { proof, randomness } = await createSignature(credentialId);
const { proof, randomness } = await createSignature(credentialHolderIdDID);

const saveSignatureResult = await this.messageHandler.sendMessage<MessageStatusWrapper<void>>(
MessageType.AddWeb3IdCredentialGiveSignature,
MessageType.AddWeb3IdCredentialFinish,
{
credentialId,
credentialHolderIdDID,
proof,
randomness,
}
Expand All @@ -283,7 +283,7 @@ class WalletApi extends EventEmitter implements IWalletApi {
throw new Error(saveSignatureResult.message);
}

return credentialId;
return credentialHolderIdDID;
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/browser-wallet-message-hub/src/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export enum MessageType {
IdProof = 'M_IdProof',
ConnectAccounts = 'M_ConnectAccounts',
AddWeb3IdCredential = 'M_AddWeb3IdCredential',
AddWeb3IdCredentialGiveSignature = 'M_AddWeb3IdCredentialGiveSignature',
AddWeb3IdCredentialFinish = 'M_AddWeb3IdCredentialFinish',
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/browser-wallet/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@concordium/browser-wallet",
"version": "1.1.0.0",
"version": "1.1.0",
"description": "Browser extension wallet for the Concordium blockchain",
"author": "Concordium Software",
"license": "Apache-2.0",
Expand Down
117 changes: 21 additions & 96 deletions packages/browser-wallet/src/background/index.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,35 @@
import {
createMessageTypeFilter,
InternalMessageType,
MessageType,
ExtensionMessageHandler,
InternalMessageType,
MessageStatusWrapper,
MessageType,
} from '@concordium/browser-wallet-message-hub';
import { deserializeTypeValue, HttpProvider } from '@concordium/web-sdk';
import {
createConcordiumClient,
deserializeTypeValue,
HttpProvider,
verifyWeb3IdCredentialSignature,
} from '@concordium/web-sdk';
import {
storedSelectedAccount,
storedCurrentNetwork,
sessionPasscode,
getGenesisHash,
sessionOpenPrompt,
sessionPasscode,
storedAcceptedTerms,
getGenesisHash,
storedAllowlist,
storedVerifiableCredentials,
sessionVerifiableCredentials,
useIndexedStorage,
storedCurrentNetwork,
storedSelectedAccount,
} from '@shared/storage/access';

import JSONBig from 'json-bigint';
import { ChromeStorageKey, NetworkConfiguration, VerifiableCredential } from '@shared/storage/types';
import { buildURLwithSearchParameters } from '@shared/utils/url-helpers';
import { mainnet, stagenet, testnet } from '@shared/constants/networkConfiguration';
import { ChromeStorageKey, NetworkConfiguration } from '@shared/storage/types';
import { getTermsAndConditionsConfig } from '@shared/utils/network-helpers';
import { Buffer } from 'buffer/';
import { BackgroundSendTransactionPayload } from '@shared/utils/types';
import { parsePayload } from '@shared/utils/payload-helpers';
import { GRPCTIMEOUT, mainnet, stagenet, testnet } from '@shared/constants/networkConfiguration';
import { addToList, web3IdCredentialLock } from '@shared/storage/update';
import { CredentialProof } from '@concordium/browser-wallet-api-helpers';
import {
getCredentialRegistryContractAddress,
getCredentialRegistryIssuerKey,
getPublicKeyfromPublicKeyIdentifierDID,
} from '@shared/utils/verifiable-credential-helpers';
import { BackgroundSendTransactionPayload } from '@shared/utils/types';
import { buildURLwithSearchParameters } from '@shared/utils/url-helpers';
import { Buffer } from 'buffer/';
import JSONBig from 'json-bigint';
import { startMonitoringPendingStatus } from './confirmation';
import { sendCredentialHandler } from './credential-deployment';
import { createIdProofHandler, runIfValidProof } from './id-proof';
import { addIdpListeners, identityIssuanceHandler } from './identity-issuance';
import bgMessageHandler from './message-handler';
import { setupRecoveryHandler, startRecovery } from './recovery';
import {
forwardToPopup,
HandleMessage,
Expand All @@ -49,11 +39,7 @@ import {
setPopupSize,
testPopupOpen,
} from './window-management';
import { addIdpListeners, identityIssuanceHandler } from './identity-issuance';
import { startMonitoringPendingStatus } from './confirmation';
import { sendCredentialHandler } from './credential-deployment';
import { startRecovery, setupRecoveryHandler } from './recovery';
import { createIdProofHandler, runIfValidProof } from './id-proof';
import { web3IdAddCredentialFinishHandler } from './web3Id';

const rpcCallNotAllowedMessage = 'RPC Call can only be performed by whitelisted sites';
const walletLockedMessage = 'The wallet is locked';
Expand Down Expand Up @@ -515,75 +501,14 @@ const getSelectedChainHandler: ExtensionMessageHandler = (_msg, sender, respond)

bgMessageHandler.handleMessage(createMessageTypeFilter(MessageType.GetSelectedChain), getSelectedChainHandler);

const NO_CREDENTIALS_FIT = 'No temporary credentials fit the given id';
const INVALID_CREDENTIAL_PROOF = 'Invalid credential proof given';

async function web3IdAddSignatureHandler(input: {
credentialId: string;
proof: CredentialProof;
randomness: Record<number, string>;
}): Promise<void> {
const { credentialId, proof, randomness } = input;

const network = await storedCurrentNetwork.get();

if (!network) {
throw new Error('No network chosen');
}

const { genesisHash } = network;
const tempCredentials = await sessionVerifiableCredentials.get(genesisHash);

if (!tempCredentials) {
throw new Error(NO_CREDENTIALS_FIT);
}

const saved = tempCredentials.find((cred) => cred.credentialSubject.id === credentialId);

if (!saved) {
throw new Error(NO_CREDENTIALS_FIT);
}

const client = createConcordiumClient(network.grpcUrl, network.grpcPort, { timeout: GRPCTIMEOUT });
const issuerContract = getCredentialRegistryContractAddress(saved.issuer);

if (
!proof?.proofValue ||
!verifyWeb3IdCredentialSignature({
globalContext: await client.getCryptographicParameters(),
signature: proof.proofValue,
randomness,
values: saved.credentialSubject.attributes,
issuerContract,
issuerPublicKey: await getCredentialRegistryIssuerKey(client, issuerContract),
holder: getPublicKeyfromPublicKeyIdentifierDID(saved.credentialSubject.id),
})
) {
throw new Error(INVALID_CREDENTIAL_PROOF);
}

const credential: VerifiableCredential = {
...saved,
signature: proof.proofValue,
randomness,
};

addToList(
web3IdCredentialLock,
credential,
useIndexedStorage(storedVerifiableCredentials, () => Promise.resolve(genesisHash))
);
// TODO remove temp in session
}

bgMessageHandler.handleMessage(
createMessageTypeFilter(MessageType.AddWeb3IdCredentialGiveSignature),
createMessageTypeFilter(MessageType.AddWeb3IdCredentialFinish),
(input, sender, respond) => {
if (!sender.url || !isAllowlisted(sender.url)) {
respond({ success: false, message: 'not allowlisted' });
}

web3IdAddSignatureHandler(input.payload)
web3IdAddCredentialFinishHandler(input.payload)
.then(() => respond({ success: true }))
.catch((error) => respond({ success: false, message: error.message }));

Expand Down
78 changes: 78 additions & 0 deletions packages/browser-wallet/src/background/web3Id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { createConcordiumClient, verifyWeb3IdCredentialSignature } from '@concordium/web-sdk';
import {
sessionVerifiableCredentials,
storedCurrentNetwork,
storedVerifiableCredentials,
useIndexedStorage,
} from '@shared/storage/access';

import { CredentialProof } from '@concordium/browser-wallet-api-helpers';
import { GRPCTIMEOUT } from '@shared/constants/networkConfiguration';
import { VerifiableCredential } from '@shared/storage/types';
import { addToList, web3IdCredentialLock } from '@shared/storage/update';
import {
getCredentialRegistryContractAddress,
getCredentialRegistryIssuerKey,
getPublicKeyfromPublicKeyIdentifierDID,
} from '@shared/utils/verifiable-credential-helpers';

const NO_CREDENTIALS_FIT = 'No temporary credentials fit the given id';
const INVALID_CREDENTIAL_PROOF = 'Invalid credential proof given';

export async function web3IdAddCredentialFinishHandler(input: {
credentialId: string;
proof: CredentialProof;
randomness: Record<number, string>;
}): Promise<void> {
const { credentialId, proof, randomness } = input;

const network = await storedCurrentNetwork.get();

if (!network) {
throw new Error('No network chosen');
}

const { genesisHash } = network;
const tempCredentials = await sessionVerifiableCredentials.get(genesisHash);

if (!tempCredentials) {
throw new Error(NO_CREDENTIALS_FIT);
}

const saved = tempCredentials.find((cred) => cred.credentialSubject.id === credentialId);

if (!saved) {
throw new Error(NO_CREDENTIALS_FIT);
}

const client = createConcordiumClient(network.grpcUrl, network.grpcPort, { timeout: GRPCTIMEOUT });
const issuerContract = getCredentialRegistryContractAddress(saved.issuer);

if (
!proof?.proofValue ||
!verifyWeb3IdCredentialSignature({
globalContext: await client.getCryptographicParameters(),
signature: proof.proofValue,
randomness,
values: saved.credentialSubject.attributes,
issuerContract,
issuerPublicKey: await getCredentialRegistryIssuerKey(client, issuerContract),
holder: getPublicKeyfromPublicKeyIdentifierDID(saved.credentialSubject.id),
})
) {
throw new Error(INVALID_CREDENTIAL_PROOF);
}

const credential: VerifiableCredential = {
...saved,
signature: proof.proofValue,
randomness,
};

addToList(
web3IdCredentialLock,
credential,
useIndexedStorage(storedVerifiableCredentials, () => Promise.resolve(genesisHash))
);
// TODO remove temp in session
}
Loading

0 comments on commit a9459a8

Please sign in to comment.