Skip to content

Commit

Permalink
Merge branch 'feature/web3-id' of github.com:Concordium/concordium-br…
Browse files Browse the repository at this point in the history
…owser-wallet into revoke-credentials
  • Loading branch information
orhoj committed Aug 17, 2023
2 parents dfbc0bd + 68d6a7c commit e4f23b3
Show file tree
Hide file tree
Showing 36 changed files with 1,387 additions and 47 deletions.
57 changes: 56 additions & 1 deletion examples/add-example-Web3Id/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,64 @@
let currentAccountAddress = '';
async function setupPage() {
const provider = await concordiumHelpers.detectConcordiumProvider();

document.getElementById('requestAccounts').addEventListener('click', () => {
provider.requestAccounts().then((accountAddresses) => {
currentAccountAddress = accountAddresses[0];
document.getElementById('accountAddress').innerHTML = currentAccountAddress;
});
});

function sendStatement(statement) {
// Should be not be hardcoded
const challenge = '94d3e85bbc8ff0091e562ad8ef6c30d57f29b19f17c98ce155df2a30100dAAAA';
provider
.requestVerifiablePresentation(challenge, statement)
.then((proof) => {
console.log(proof);
alert('Proof received! (check the console)');
})
.catch((error) => {
console.log(error);
alert(error);
});
}

provider.on('accountDisconnected', (accountAddress) => (currentAccountAddress = undefined));
provider.on('accountChanged', (accountAddress) => (currentAccountAddress = accountAddress));
provider.on('chainChanged', (chain) => alert(chain));
// Request proofs
document.getElementById('web3ProofWeb3IdOnly').addEventListener('click', () => {
const statement = new concordiumSDK.Web3StatementBuilder()
.addForVerifiableCredentials([{ index: 5463n, subindex: 0n }], (b) =>
b
.revealAttribute('degreeType')
.addMembership('degreeName', ['Bachelor of Science and Arts', 'Bachelor of Finance'])
)
.getStatements();
sendStatement(statement);
});
document.getElementById('web3ProofIdOnly').addEventListener('click', () => {
const statement = new concordiumSDK.Web3StatementBuilder()
.addForIdentityCredentials([0, 1, 2], (b) =>
b.revealAttribute('firstName').addRange('dob', '08000101', '20000101')
)
.getStatements();
sendStatement(statement);
});
document.getElementById('web3ProofMixed').addEventListener('click', () => {
const statement = new concordiumSDK.Web3StatementBuilder()
.addForIdentityCredentials([0, 1, 2], (b) =>
b.revealAttribute('firstName').addRange('dob', '08000101', '20000101')
)
.addForVerifiableCredentials([{ index: 5463n, subindex: 0n }], (b) =>
b
.revealAttribute('degreeType')
.addMembership('degreeName', ['Bachelor of Science and Arts', 'Bachelor of Finance'])
)
.getStatements();
sendStatement(statement);
});
// Add credential
document.getElementById('addWeb3Id').addEventListener('click', () => {
const values = {
degreeType: degreeType.value,
Expand Down Expand Up @@ -108,5 +156,12 @@ <h3>Attribute values:</h3>
<br />
<br />
<button id="addWeb3Id">Add web3IdCredential Example to wallet</button>
<br />
<h3>Request Proofs:</h3>
<button id="web3ProofWeb3IdOnly">Request a Proof using web3Id</button>
<br />
<button id="web3ProofIdOnly">Request a Proof using account credentials</button>
<br />
<button id="web3ProofMixed">Request a Proof using both web3Id and account credentials</button>
</body>
</html>
23 changes: 22 additions & 1 deletion examples/two-step-transfer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -214,14 +214,34 @@
})
.catch(alert);
});
document.getElementById('web3Proof').addEventListener('click', () => {
const statement = new concordiumSDK.Web3StatementBuilder()
.addForIdentityCredentials([0, 1, 2], (b) =>
b.revealAttribute('firstName').addRange('dob', '08000101', '20000101')
)
.addForVerifiableCredentials([{ index: 5463, subindex: 0 }], (b) =>
b.revealAttribute('degreeName').addMembership('graduationDate', ['2010-06-01T00:00:00Z'])
)
.getStatements();
const challenge = '94d3e85bbc8ff0091e562ad8ef6c30d57f29b19f17c98ce155df2a30100dAAAA';
provider
.requestVerifiablePresentation(challenge, statement)
.then((proof) => {
console.log(proof);
alert('Proof received! (check the console)');
})
.catch((error) => {
console.log(error);
alert(error);
});
});
document.getElementById('addWCCD').addEventListener('click', () => {
provider
.addCIS2Tokens(currentAccountAddress, [''], 2059n, 0n)
.then((added) => alert('Added tokens: ' + added.map((id) => '"' + id + '"')))
.catch(alert);
});
}

setupPage();
</script>
</head>
Expand All @@ -244,6 +264,7 @@ <h3 id="accountAddress">Account address:</h3>
<option value="parameter">Parameter</option>
</select>
<button id="idProof">Request an ID Proof</button>
<button id="web3Proof">Request an ID 3 Proof</button>
<br />
Message: <input type="text" id="message" value="I believe in miracles" />
<button id="signMessage">SignMessage</button>
Expand Down
29 changes: 22 additions & 7 deletions packages/browser-wallet-api-helpers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,14 @@ declare global {

### connect

To request a connection to the wallet from the user, the `connect` method has to be invoked. The method returns a `Promise` resolving with information related to the most recently selected account, which has whitelisted the dApp, or rejecting if the request is rejected in the wallet. If the wallet is locked, then this call prompts the user to first unlock the wallet before accepting or rejecting the connection request.
To request a connection to the wallet from the user, the `connect` method has to be invoked. The method returns a `Promise` resolving with information related to the most recently selected account, which has allowlisted the dApp, or rejecting if the request is rejected in the wallet. If the wallet is locked, then this call prompts the user to first unlock the wallet before accepting or rejecting the connection request.

```typescript
const provider = await detectConcordiumProvider();
const accountAddress = await provider.connect();
```

N.B. In the current version, if the dApp is already whitelisted, but not by the currently selected account, the returned account will not actually be the most recently selected account, but instead the oldest account that has whitelisted the dApp.
N.B. In the current version, if the dApp is already allowlisted, but not by the currently selected account, the returned account will not actually be the most recently selected account, but instead the oldest account that has allowlisted the dApp.

### getMostRecentlySelectedAccount

Expand Down Expand Up @@ -94,13 +94,13 @@ const provider = await detectConcordiumProvider();
const genesisHash = await provider.getSelectedChain();
```

N.B. In the current version, if the currently selected account has not whitelisted the dApp, the returned account will not actually be the most recently selected account, but instead the oldest account that has whitelisted the dApp.
N.B. In the current version, if the currently selected account has not allowlisted the dApp, the returned account will not actually be the most recently selected account, but instead the oldest account that has allowlisted the dApp.

### sendTransaction

To send a transaction, three arguments need to be provided: The account address for the account in the wallet that should sign the transaction, a transaction type and a corresponding payload. Invoking `sendTransaction` returns a `Promise`, which resolves with the transaction hash for the submitted transaction.

If the wallet is locked, or you have not connected with the wallet (or previously been whitelisted) or if the user rejects signing the transaction, the `Promise` will reject.
If the wallet is locked, or you have not connected with the wallet (or previously been allowlisted) or if the user rejects signing the transaction, the `Promise` will reject.

The following exemplifies how to create a simple transfer of funds from one account to another. Please note that [@concordium/web-sdk](https://github.com/Concordium/concordium-node-sdk-js/tree/main/packages/web) is used to provide the correct formats and types for the transaction payload.

Expand Down Expand Up @@ -160,7 +160,7 @@ const parameterSchema = {

It is possible to sign arbitrary messages using the keys for an account stored in the wallet, by invoking the `signMessage` method. The first parameter is the account to be used for signing the message. This method returns a `Promise` resolving with a signature of the message.

If the wallet is locked, or you have not connected with the wallet (or previously been whitelisted) or if the user rejects signing the meesage, the `Promise` will reject.
If the wallet is locked, or you have not connected with the wallet (or previously been allowlisted) or if the user rejects signing the meesage, the `Promise` will reject.

The message should be either a utf8 string or an object with the following fields:

Expand Down Expand Up @@ -206,7 +206,7 @@ In this example the user will be shown:

It is possible to suggest CIS-2 tokens to be added to an account's display. This method returns a `Promise` resolving with a list containing the ids of the tokens that were added.

If the wallet is locked, or you have not connected with the wallet (or previously been whitelisted) or if the user rejects signing the meesage, the `Promise` will reject.
If the wallet is locked, or you have not connected with the wallet (or previously been allowlisted) or if the user rejects signing the meesage, the `Promise` will reject.

The following exemplifies requesting tokens with id AA and BB from the contract on index 1399, and subindex 0 to the account `2za2yAXbFiaB151oYqTteZfqiBzibHXizwjNbpdU8hodq9SfEk`.

Expand All @@ -232,6 +232,21 @@ provider.addWeb3IdCredential(credential, metadataUrl, async (id) => {
});
```

### Request Verifiable Presentation for web3Id statements

It is possible to request a verifiable presentation for a given set of web3Id statements. The function takes 2 arguments. A challenge to ensure that the proof was not generated for a different context, and the statements to be proven. This method returns a Promise resolving with the verifiable presentation for the statements.

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.

The following exemplifies requesting a verifiable presentation for a statement named myIdStatement (To see how to create a statement check out our documentation) with a challenge of "12346789ABCD".

```typescript
const statement = myIdStatement;
const challenge = '12346789ABCD';
const provider = await detectConcordiumProvider();
const verifiablePresentation = await provider.requestVerifiablePresentation(challenge, statement);
```

## Events

### Account changed
Expand Down Expand Up @@ -271,7 +286,7 @@ provider.connect().then((accountAddress) => (selectedAccountAddress = accountAdd
The wallet API exposes access to a JSON-RPC client. This allows a dApp to communicate with the same node as the wallet is connected to, and enables dApps to access the JSON-RPC interface without being connected to a separate server itself. The client is accessed as shown in the example below.
The dApp does not need to recreate the client again when the wallet changes node or network, the client will always use the wallet's current connected JSON-RPC server.
If you have not connected with the wallet (or previously been whitelisted), the commands will not be executed and the method will throw an error.
If you have not connected with the wallet (or previously been allowlisted), the commands will not be executed and the method will throw an error.
```typescript
const provider = await detectConcordiumProvider();
Expand Down
11 changes: 11 additions & 0 deletions packages/browser-wallet-api-helpers/src/wallet-api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import type {
IdStatement,
IdProofOutput,
ConcordiumGRPCClient,
CredentialStatements,
VerifiablePresentation,
CredentialSubject,
HexString,
} from '@concordium/web-sdk';
Expand Down Expand Up @@ -196,6 +198,7 @@ interface MainWalletApi {

/**
* Request that the user provides a proof for the given statement.
* @deprecated Please use { @link requestVerifiablePresentation} instead.
* @param accountAddress the address of the account that should prove the statement.
* @param statement the id statement that should be proven.
* @param challenge bytes chosen by the verifier. Should be HEX encoded.
Expand All @@ -218,6 +221,14 @@ interface MainWalletApi {
credentialHolderIdDID: string
) => Promise<{ randomness: Record<string, string>; proof: CredentialProof }>
): Promise<string>;

/**
* Request that the user provides a proof for the given statements.
* @param challenge bytes chosen by the verifier. Should be HEX encoded.
* @param statement the web3Id statements that should be proven.
* @returns The presentation for the statements.
*/
requestVerifiablePresentation(challenge: string, statements: CredentialStatements): Promise<VerifiablePresentation>;
}

export type WalletApi = MainWalletApi & EventListeners;
16 changes: 16 additions & 0 deletions packages/browser-wallet-api/src/wallet-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
AccountTransactionSignature,
AccountTransactionType,
DeployModulePayload,
HexString,
InitContractPayload,
SchemaVersion,
UpdateContractPayload,
Expand All @@ -28,6 +29,7 @@ import {
import EventEmitter from 'events';
import type { JsonRpcRequest } from '@concordium/common-sdk/lib/providers/provider';
import { IdProofOutput, IdStatement } from '@concordium/common-sdk/lib/idProofTypes';
import { CredentialStatements, VerifiablePresentation } from '@concordium/common-sdk/lib/web3ProofTypes';
import { ConcordiumGRPCClient } from '@concordium/common-sdk/lib/GRPCClient';
import JSONBig from 'json-bigint';
import { stringify } from './util';
Expand Down Expand Up @@ -285,6 +287,20 @@ class WalletApi extends EventEmitter implements IWalletApi {

return credentialHolderIdDID;
}

public async requestVerifiablePresentation(challenge: HexString, statements: CredentialStatements) {
const res = await this.messageHandler.sendMessage<MessageStatusWrapper<string>>(MessageType.Web3IdProof, {
// We have to stringify the statements because they can contain bigints
statements: stringify(statements),
challenge,
});

if (!res.success) {
throw new Error(res.message);
}

return VerifiablePresentation.fromString(res.result);
}
}

export const walletApi = new WalletApi();
3 changes: 3 additions & 0 deletions packages/browser-wallet-message-hub/src/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export enum MessageType {
GrpcRequest = 'M_GrpcRequest',
AddTokens = 'M_AddTokens',
IdProof = 'M_IdProof',
Web3IdProof = 'M_Web3Proof',
ConnectAccounts = 'M_ConnectAccounts',
AddWeb3IdCredential = 'M_AddWeb3IdCredential',
AddWeb3IdCredentialFinish = 'M_AddWeb3IdCredentialFinish',
Expand All @@ -40,6 +41,8 @@ export enum InternalMessageType {
AddTokens = 'I_AddTokens',
IdProof = 'I_IdProof',
CreateIdProof = 'I_CreateIdProof',
Web3IdProof = 'I_Web3IdProof',
CreateWeb3IdProof = 'I_CreateWeb3IdProof',
ConnectAccounts = 'I_ConnectAccounts',
AddWeb3IdCredential = 'I_AddWeb3IdCredential',
}
Expand Down
6 changes: 3 additions & 3 deletions packages/browser-wallet/src/background/id-proof.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { getIdProof, IdProofInput, verifyIdstatement } from '@concordium/web-sdk';
import { BackgroundResponseStatus, IdProofBackgroundResponse } from '@shared/utils/types';
import { getIdProof, IdProofInput, IdProofOutput, verifyIdstatement } from '@concordium/web-sdk';
import { BackgroundResponseStatus, ProofBackgroundResponse } from '@shared/utils/types';
import { ExtensionMessageHandler, MessageStatusWrapper } from '@concordium/browser-wallet-message-hub';
import { isHex } from 'wallet-common-helpers';
import { RunCondition } from './window-management';

async function createIdProof(input: IdProofInput): Promise<IdProofBackgroundResponse> {
async function createIdProof(input: IdProofInput): Promise<ProofBackgroundResponse<IdProofOutput>> {
const proof = getIdProof(input);
return {
status: BackgroundResponseStatus.Success,
Expand Down
21 changes: 20 additions & 1 deletion packages/browser-wallet/src/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ import {
setPopupSize,
testPopupOpen,
} from './window-management';
import { runIfValidWeb3IdCredentialRequest, web3IdAddCredentialFinishHandler } from './web3Id';
import {
runIfValidWeb3IdCredentialRequest,
web3IdAddCredentialFinishHandler,
createWeb3IdProofHandler,
runIfValidWeb3IdProof,
} from './web3Id';

const rpcCallNotAllowedMessage = 'RPC Call can only be performed by whitelisted sites';
const walletLockedMessage = 'The wallet is locked';
Expand Down Expand Up @@ -253,6 +258,11 @@ bgMessageHandler.handleMessage(createMessageTypeFilter(MessageType.GrpcRequest),

bgMessageHandler.handleMessage(createMessageTypeFilter(InternalMessageType.CreateIdProof), createIdProofHandler);

bgMessageHandler.handleMessage(
createMessageTypeFilter(InternalMessageType.CreateWeb3IdProof),
createWeb3IdProofHandler
);

const NOT_WHITELISTED = 'Site is not whitelisted';

/**
Expand Down Expand Up @@ -588,3 +598,12 @@ forwardToPopup(
undefined,
withPromptEnd
);

forwardToPopup(
MessageType.Web3IdProof,
InternalMessageType.Web3IdProof,
runConditionComposer(runIfAllowlisted, runIfValidWeb3IdProof, withPromptStart()),
appendUrlToPayload,
undefined,
withPromptEnd
);
Loading

0 comments on commit e4f23b3

Please sign in to comment.