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 restore-identity-special-display
  • Loading branch information
orhoj committed Sep 4, 2023
2 parents 4b0eff7 + 34df8b5 commit 7fe7d7b
Show file tree
Hide file tree
Showing 30 changed files with 379 additions and 144 deletions.
2 changes: 1 addition & 1 deletion packages/browser-wallet-api-helpers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"url": "https://concordium.com"
},
"dependencies": {
"@concordium/web-sdk": "^6.1.1"
"@concordium/web-sdk": "^6.2.0"
},
"devDependencies": {
"@babel/core": "^7.17.10",
Expand Down
2 changes: 1 addition & 1 deletion packages/browser-wallet-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"license": "Apache-2.0",
"dependencies": {
"@concordium/browser-wallet-api-helpers": "workspace:^",
"@concordium/common-sdk": "^9.1.1",
"@concordium/common-sdk": "^9.2.0",
"@protobuf-ts/grpcweb-transport": "^2.8.2",
"@protobuf-ts/runtime-rpc": "^2.8.2",
"buffer": "^6.0.3",
Expand Down
11 changes: 11 additions & 0 deletions packages/browser-wallet/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## Unreleased

### Added

- Indicator on proof request page, to show how many credential statements are requested, and the current position.

### Fixed

- An issue where Date attributes were saved as strings when exported. This would mean that they would lose typing and the credential would be broken.
- An issue where statement parameters were not validated according to the attribute bounds.

## 1.1.4

### Added
Expand Down
2 changes: 1 addition & 1 deletion packages/browser-wallet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"dependencies": {
"@concordium/browser-wallet-api": "workspace:^",
"@concordium/browser-wallet-api-helpers": "workspace:^",
"@concordium/web-sdk": "^6.1.1",
"@concordium/web-sdk": "^6.2.0",
"@noble/ed25519": "^1.7.0",
"@protobuf-ts/runtime-rpc": "^2.8.2",
"@scure/bip39": "^1.1.0",
Expand Down
3 changes: 3 additions & 0 deletions packages/browser-wallet/src/assets/svg/check_small.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions packages/browser-wallet/src/assets/svg/help.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
105 changes: 88 additions & 17 deletions packages/browser-wallet/src/background/web3Id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
IDENTITY_SUBJECT_SCHEMA,
verifyIdstatement,
IdStatement,
StatementTypes,
AttributeType,
} from '@concordium/web-sdk';
import {
sessionVerifiableCredentials,
Expand Down Expand Up @@ -124,6 +126,52 @@ function rejectRequest(message: string): { run: false; response: MessageStatusWr
};
}

function validateTimestampAttribute(attributeTag: string, attributeValue: AttributeType) {
if (
attributeValue instanceof Date &&
(attributeValue.getTime() < MIN_DATE_TIMESTAMP || attributeValue.getTime() > MAX_DATE_TIMESTAMP)
) {
return `The attribute [${attributeValue}] for key [${attributeTag}] is out of bounds for a Date. The Date must be between ${MIN_DATE_ISO} and ${MAX_DATE_ISO}`;
}
return undefined;
}

function validateIntegerAttribute(attributeTag: string, attributeValue: AttributeType): string | undefined {
if (typeof attributeValue === 'bigint' && (attributeValue < 0 || attributeValue > MAX_U64)) {
return `The attribute [${attributeValue}] for key [${attributeTag}] is out of bounds for a u64 integer.`;
}
return undefined;
}

function validateStringAttribute(attributeTag: string, attributeValue: AttributeType): string | undefined {
if (typeof attributeValue === 'string' && Buffer.from(attributeValue, 'utf-8').length > 31) {
return `The attribute [${attributeValue}] for key [${attributeTag}] is greater than 31 bytes.`;
}
return undefined;
}

function validateAttributeBounds(
attributeTag: string,
attributeValue: AttributeType
): { error: false } | { error: true; message: string } {
const stringError = validateStringAttribute(attributeTag, attributeValue);
if (stringError) {
return { error: true, message: stringError };
}

const integerError = validateIntegerAttribute(attributeTag, attributeValue);
if (integerError) {
return { error: true, message: integerError };
}

const timestampError = validateTimestampAttribute(attributeTag, attributeValue);
if (timestampError) {
return { error: true, message: timestampError };
}

return { error: false };
}

/**
* Run condition which ensures that the web3IdCredential request is valid.
*/
Expand Down Expand Up @@ -151,23 +199,9 @@ export const runIfValidWeb3IdCredentialRequest: RunCondition<MessageStatusWrappe
}

for (const [attributeKey, attributeValue] of Object.entries(credential.credentialSubject.attributes)) {
if (typeof attributeValue === 'string' && Buffer.from(attributeValue, 'utf-8').length > 31) {
return rejectRequest(
`The attribute [${attributeValue}] for key [${attributeKey}] is greater than 31 bytes.`
);
}
if (typeof attributeValue === 'bigint' && (attributeValue < 0 || attributeValue > MAX_U64)) {
return rejectRequest(
`The attribute [${attributeValue}] for key [${attributeKey}] is out of bounds for a u64 integer.`
);
}
if (
attributeValue instanceof Date &&
(attributeValue.getTime() < MIN_DATE_TIMESTAMP || attributeValue.getTime() > MAX_DATE_TIMESTAMP)
) {
return rejectRequest(
`The attribute [${attributeValue}] for key [${attributeKey}] is out of bounds for a Date. The Date must be between ${MIN_DATE_ISO} and ${MAX_DATE_ISO}`
);
const validationResult = validateAttributeBounds(attributeKey, attributeValue);
if (validationResult.error) {
return rejectRequest(validationResult.message);
}
}

Expand Down Expand Up @@ -198,6 +232,43 @@ export const runIfValidWeb3IdProof: RunCondition<MessageStatusWrapper<undefined>
}
try {
const statements: CredentialStatements = parse(msg.payload.statements);

// The `verifyAtomicStatements` method only verifies the bounds of the statement variables when it has the
// schema available. So we manually do this check here, even though it ideally be moved to the SDK.
for (const stat of statements) {
for (const atomicStatement of stat.statement) {
if (atomicStatement.type === StatementTypes.AttributeInRange) {
const lowerValidationResult = validateAttributeBounds(
atomicStatement.attributeTag,
atomicStatement.lower
);
if (lowerValidationResult.error) {
return rejectRequest(lowerValidationResult.message);
}

const upperValidationResult = validateAttributeBounds(
atomicStatement.attributeTag,
atomicStatement.upper
);
if (upperValidationResult.error) {
return rejectRequest(upperValidationResult.message);
}
}

if (
StatementTypes.AttributeInSet === atomicStatement.type ||
StatementTypes.AttributeNotInSet === atomicStatement.type
) {
for (const setItem of atomicStatement.set) {
const validationResult = validateAttributeBounds(atomicStatement.attributeTag, setItem);
if (validationResult.error) {
return rejectRequest(validationResult.message);
}
}
}
}
}

// If a statement does not verify, an error is thrown.
statements.every((credStatement) =>
isAccountCredentialStatement(credStatement)
Expand Down
16 changes: 10 additions & 6 deletions packages/browser-wallet/src/background/window-management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
WalletMessage,
} from '@concordium/browser-wallet-message-hub';

import { Dimensions, small } from '@popup/constants/dimensions';
import { Dimensions, large, small } from '@popup/constants/dimensions';
import { spawnedPopupUrl } from '@shared/constants/url';
import { noOp } from 'wallet-common-helpers';
import bgMessageHandler, { onMessage } from './message-handler';
Expand All @@ -25,15 +25,19 @@ const getTopLeft = async (): Promise<{ top: number; left: number }> => {
/**
* Spawns a new popup on screen. Returning promise resolves when it receives a ready event from the popup
*/
export const spawnPopup = async (): Promise<chrome.windows.Window> => {
export const spawnPopup = async (messageType?: MessageType | InternalMessageType): Promise<chrome.windows.Window> => {
const { top, left } = await getTopLeft();

const window = chrome.windows.create({
url: spawnedPopupUrl,
// The Web3 ID proof popup has a separate size from other windows. Convery this by adjusting the URL, so that
// the scaling adjusts the window correctly.
url: messageType === MessageType.Web3IdProof ? `${spawnedPopupUrl}&web3idproof` : spawnedPopupUrl,
type: 'popup',
...small,
top,
left,
width: large.width,
height: large.height,
});

// As the react app needs a chance to bootstrap, we need to wait for the ready signal.
Expand Down Expand Up @@ -79,11 +83,11 @@ export const setPopupSize = async ({ width, height }: Dimensions) => {
}
};

export const openWindow = async () => {
export const openWindow = async (messageType?: MessageType | InternalMessageType) => {
const isOpen = await testPopupOpen();

if (!isOpen) {
const { id } = await spawnPopup();
const { id } = await spawnPopup(messageType);
popupId = id;
} else {
focusExisting();
Expand All @@ -97,7 +101,7 @@ const ensureAvailableWindow =
(handler: ExtensionMessageHandler): ExtensionMessageHandler =>
(...args) => {
(async () => {
await openWindow();
await openWindow(args[0].messageType);
handler(...args);
})();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { AttributeType, CredentialSubject } from '@concordium/web-sdk';
import { VerifiableCredentialStatus, MetadataUrl, VerifiableCredentialSchema } from '@shared/storage/types';
import { useTranslation } from 'react-i18next';
import StatusIcon from './VerifiableCredentialStatus';
import { defaultFormatAttribute } from '../Web3ProofRequest/Display/utils';

function Logo({ logo }: { logo: MetadataUrl }) {
return <Img className="verifiable-credential__header__logo" src={logo.url} withDefaults />;
Expand Down Expand Up @@ -38,7 +39,9 @@ export function DisplayAttribute({
return (
<div key={attributeKey} className="verifiable-credential__body-attributes-row">
<label>{attributeTitle.toLowerCase()}</label>
<div className="verifiable-credential__body-attributes-row-value">{attributeValue.toString()}</div>
<div className="verifiable-credential__body-attributes-row-value">
{defaultFormatAttribute(attributeKey, attributeValue)}
</div>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { decrypt } from '@shared/utils/crypto';
import { useHdWallet } from '@popup/shared/utils/account-helpers';
import JSONBigInt from 'json-bigint';
import { FileInput } from '@popup/shared/Form/FileInput';
import { reviveDateFromTimeStampAttribute } from '@concordium/web-sdk';
import { VerifiableCredentialCardWithStatusFromChain } from '../VerifiableCredential/VerifiableCredentialList';
import { ExportFormat, VerifiableCredentialExport } from './utils';

Expand Down Expand Up @@ -46,7 +47,7 @@ async function parseExport(data: EncryptedData, encryptionKey: string): Promise<
const backup: ExportFormat = JSONBigInt({
alwaysParseAsBig: true,
useNativeBigInt: true,
}).parse(decrypted);
}).parse(decrypted, reviveDateFromTimeStampAttribute);
// Change index to number, due to parse changing all numbers to bigints.
backup.value.verifiableCredentials = backup.value.verifiableCredentials.map((v) => ({
...v,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { networkConfigurationAtom } from '@popup/store/settings';
import { saveData } from '@popup/shared/utils/file-helpers';
import { stringify } from 'json-bigint';
import { replaceDateWithTimeStampAttribute } from '@concordium/web-sdk';

export type VerifiableCredentialExport = {
verifiableCredentials: VerifiableCredential[];
Expand Down Expand Up @@ -45,7 +46,8 @@ function createExport(
};

// Use json-bigint to serialize bigints as json numbers.
return encrypt(stringify(exportContent), encryptionKey);
// Ensure that Dates are stored as timestamps to not lose typing (otherwise they are serialized as strings).
return encrypt(stringify(exportContent, replaceDateWithTimeStampAttribute), encryptionKey);
}

export function useVerifiableCredentialExport() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import {
AccountCredentialStatement,
createAccountDID,
CredentialSchemaSubject,
IDENTITY_SUBJECT_SCHEMA,
RevealStatementV2,
StatementTypes,
} from '@concordium/web-sdk';
import { displaySplitAddress, useIdentityName, useIdentityOf } from '@popup/shared/utils/account-helpers';
import { useDisplayAttributeValue } from '@popup/shared/utils/identity-helpers';
import { ConfirmedIdentity, CredentialSchemaSubject, WalletCredential } from '@shared/storage/types';
import { ConfirmedIdentity, WalletCredential } from '@shared/storage/types';
import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { SecretStatement, useStatementName, useStatementValue } from '../IdProofRequest/DisplayStatement/utils';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Modal from '@popup/shared/Modal';
import clsx from 'clsx';
import React, { ReactNode, useState } from 'react';
import { ClassName } from 'wallet-common-helpers';
import InfoTooltipIcon from '@assets/svg/info-tooltip.svg';
import InfoTooltipIcon from '@assets/svg/help.svg';

type DisplayBoxProps = ClassName & {
header: string;
Expand All @@ -25,7 +25,7 @@ export function DisplayBox({ className, children, header, infoBox }: DisplayBoxP
onOpen={() => setOpen(true)}
onClose={() => setOpen(false)}
trigger={
<Button clear className="flex">
<Button clear className="flex display-box__tooltip-button">
<InfoTooltipIcon className="display-box__tooltip-icon" />
</Button>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Trans, useTranslation } from 'react-i18next';
import WarningTriangleIcon from '@assets/svg/warning-triangle.svg';
import { DisplayStatementLine } from './DisplayStatementLine';
import { DisplayBox } from './DisplayBox';
import { DisplayProps, getPropertyTitle } from './utils';
import { DisplayProps, defaultFormatAttribute, getPropertyTitle } from './utils';

type Props<Attribute> = DisplayProps<RevealStatementV2, Attribute> & {
dappName: string;
Expand All @@ -16,7 +16,7 @@ export function DisplayRevealStatements<Attribute extends AttributeType>({
attributes,
dappName,
schema,
formatAttribute = (_, value) => value?.toString(),
formatAttribute = defaultFormatAttribute,
}: Props<Attribute>) {
const { t } = useTranslation('web3IdProofRequest', { keyPrefix: 'displayStatement' });
const header = t('headers.reveal');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { AttributeType, canProveAtomicStatement, StatementTypes } from '@concordium/web-sdk';
import { CredentialSchemaSubject } from '@shared/storage/types';
import { AttributeType, canProveAtomicStatement, CredentialSchemaSubject, StatementTypes } from '@concordium/web-sdk';
import React from 'react';
import { TFunction, useTranslation } from 'react-i18next';
import { DisplayStatementLine } from './DisplayStatementLine';
import { DisplayBox } from './DisplayBox';
import { SecretStatementV2 } from '../utils';
import { DisplayProps, getPropertyTitle } from './utils';
import { DisplayProps, defaultFormatAttribute, getPropertyTitle } from './utils';

function getStatementValue<Attribute extends AttributeType>(
statement: SecretStatementV2,
Expand Down Expand Up @@ -62,8 +61,8 @@ export function DisplaySecretStatements<Attribute extends AttributeType>({
statements,
attributes,
className,
formatAttribute = (_, value) => value.toString(),
overwriteSecretLine = () => ({}),
formatAttribute = defaultFormatAttribute,
}: DisplayProps<SecretStatementV2, Attribute>) {
const { t } = useTranslation('web3IdProofRequest', { keyPrefix: 'displayStatement' });
const header = t('headers.secret');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import clsx from 'clsx';
import React from 'react';
import { ClassName } from 'wallet-common-helpers';

import CheckmarkIcon from '@assets/svg/checkmark-dark-green.svg';
import CheckmarkIcon from '@assets/svg/check_small.svg';
import CrossIcon from '@assets/svg/cross.svg';

export type StatementLine = {
Expand All @@ -20,7 +20,7 @@ export function DisplayStatementLine({ attribute, value, isRequirementMet, class
<div className="display-statement__line-value bodyM">
{value}
{isRequirementMet ? (
<CheckmarkIcon className="display-statement__line-check" />
<CheckmarkIcon className="display-statement__line-check display-statement-checkmark " />
) : (
<CrossIcon className="display-statement__line-cross" />
)}
Expand Down
Loading

0 comments on commit 7fe7d7b

Please sign in to comment.