Skip to content

Commit

Permalink
fix: WIP: signature generation
Browse files Browse the repository at this point in the history
FIXME: potential extra CR character \r prependded before newline in signed_metadata
FIXME: hardcoded CameraSource.Camera
FIXME: Proof tests
  • Loading branch information
shc261392 committed Nov 21, 2023
1 parent 42ff172 commit e4a6c87
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 65 deletions.
36 changes: 22 additions & 14 deletions src/app/shared/collector/collector.service.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { Injectable } from '@angular/core';
import { CameraSource } from '@capacitor/camera';
import { generateIntegritySha } from '../../utils/nit/nit';
import { MediaStore } from '../media/media-store/media-store.service';
import {
Assets,
getSerializedSortedSignedMessage,
Proof,
Signatures,
SignedMessage,
ProofMetadata,
SignResult,
Truth,
getSerializedSortedProofMetadata,
} from '../repositories/proof/proof';
import { FactsProvider } from './facts/facts-provider';
import { CaptureAppWebCryptoApiSignatureProvider } from './signature/capture-app-web-crypto-api-signature-provider/capture-app-web-crypto-api-signature-provider.service';
import { SignatureProvider } from './signature/signature-provider';
import { generateIntegritySha } from '../../utils/nit/nit';

@Injectable({
providedIn: 'root',
Expand All @@ -34,10 +34,18 @@ export class CollectorService {
async generateSignature(proof: Proof, source: CameraSource) {
const recorder =
CaptureAppWebCryptoApiSignatureProvider.recorderFor(source);
const signedMessage = await proof.generateSignedMessage(recorder);
const signatures = await this.signMessage(signedMessage, source);
const proofMetadata = await proof.generateProofMetadata(recorder);
const { signatures, integritySha } = await this.signProofMetadata(
proofMetadata,
source
);
console.log(

Check failure on line 42 in src/app/shared/collector/collector.service.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
'sign message ProofMetadata',
await getSerializedSortedProofMetadata(proofMetadata)
);
console.log('generated signatures', signatures);

Check failure on line 46 in src/app/shared/collector/collector.service.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
console.log('generated integritySha', integritySha);

Check failure on line 47 in src/app/shared/collector/collector.service.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
proof.setSignatures(signatures);
const integritySha = await generateIntegritySha(signedMessage);
proof.setIntegritySha(integritySha);
return proof;
}
Expand All @@ -59,20 +67,20 @@ export class CollectorService {
};
}

private async signMessage(
message: SignedMessage,
private async signProofMetadata(
proofMetadata: ProofMetadata,
source: CameraSource
): Promise<Signatures> {
const serializedSortedSignedMessage =
getSerializedSortedSignedMessage(message);
return Object.fromEntries(
): Promise<SignResult> {
const integritySha = await generateIntegritySha(proofMetadata);
const signatures = Object.fromEntries(
await Promise.all(
[...this.signatureProviders].map(async provider => [
provider.idFor(source),
await provider.provide(serializedSortedSignedMessage),
await provider.provide(integritySha),
])
)
);
return { signatures, integritySha };
}

addFactsProvider(provider: FactsProvider) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { defer } from 'rxjs';
import { concatMapTo } from 'rxjs/operators';
import { sortObjectDeeplyByKey } from '../../../../utils/immutable/immutable';
import {
isSignature,
ProofMetadata,
RecorderType,
SignedMessage,
isSignature,
} from '../../../repositories/proof/proof';
import { SharedTestingModule } from '../../../shared-testing.module';
import { CaptureAppWebCryptoApiSignatureProvider } from './capture-app-web-crypto-api-signature-provider.service';
Expand Down Expand Up @@ -60,7 +60,7 @@ describe('CaptureAppWebCryptoApiSignatureProvider', () => {
});

it('should provide signature', async () => {
const signedMessage: SignedMessage = {
const ProofMetadata: ProofMetadata = {
spec_version: '',
recorder: RecorderType.Capture,
created_at: 0,
Expand All @@ -69,10 +69,10 @@ describe('CaptureAppWebCryptoApiSignatureProvider', () => {
caption: '',
information: {},
};
const serializedSortedSignedMessage = JSON.stringify(
sortObjectDeeplyByKey(signedMessage as any).toJSON()
const serializedSortedProofMetadata = JSON.stringify(
sortObjectDeeplyByKey(ProofMetadata as any).toJSON()
);
const signature = await provider.provide(serializedSortedSignedMessage);
const signature = await provider.provide(serializedSortedProofMetadata);

expect(isSignature(signature)).toBeTrue();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { Injectable } from '@angular/core';
import { CameraSource } from '@capacitor/camera';
import {
createEthAccount,
loadEthAccount,
} from '../../../../utils/crypto/crypto';
import { createEthAccount } from '../../../../utils/crypto/crypto';
import { signWithIntegritySha } from '../../../../utils/nit/nit';
import { PreferenceManager } from '../../../preference-manager/preference-manager.service';
import { RecorderType, Signature } from '../../../repositories/proof/proof';
import { SignatureProvider } from '../signature-provider';
Expand Down Expand Up @@ -72,12 +70,14 @@ export class CaptureAppWebCryptoApiSignatureProvider
}
}

async provide(serializedSortedSignedTargets: string): Promise<Signature> {
async provide(signMessage: string): Promise<Signature> {
await this.initialize();
const account = loadEthAccount(await this.getPrivateKey());
const sign = account.sign(serializedSortedSignedTargets);
const signature = await signWithIntegritySha(
signMessage,
await this.getPrivateKey()
);
const publicKey = await this.getPublicKey();
return { signature: sign.signature, publicKey };
return { signature, publicKey };
}

async getPublicKey() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
HttpParams,
} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { CameraSource } from '@capacitor/camera';
import {
BehaviorSubject,
ReplaySubject,
Expand All @@ -28,6 +29,7 @@ import {
import { base64ToBlob } from '../../../utils/encoding/encoding';
import { MimeType, toExtension } from '../../../utils/mime-type';
import { VOID$, isNonNullable } from '../../../utils/rx-operators/rx-operators';
import { CaptureAppWebCryptoApiSignatureProvider } from '../../collector/signature/capture-app-web-crypto-api-signature-provider/capture-app-web-crypto-api-signature-provider.service';
import { Tuple } from '../../database/table/table';
import {
OldSignature,
Expand All @@ -38,7 +40,7 @@ import {
} from '../../repositories/proof/old-proof-adapter';
import {
Proof,
getSerializedSortedSignedMessage,
getSerializedSortedProofMetadata,
} from '../../repositories/proof/proof';
import { DiaBackendAuthService } from '../auth/dia-backend-auth.service';
import { PaginatedResponse } from '../pagination';
Expand Down Expand Up @@ -386,11 +388,14 @@ async function buildFormDataToCreateAsset(proof: Proof) {
const formData = new FormData();

const info = await getSortedProofInformation(proof);
const signedMessage = await proof.generateSignedMessage();
const serializedSignedMessage =
getSerializedSortedSignedMessage(signedMessage);
const recorder = CaptureAppWebCryptoApiSignatureProvider.recorderFor(
CameraSource.Camera // FIXME: should read actual CameraSource
);
const proofMetadata = await proof.generateProofMetadata(recorder);
const serializedSortedProofMetadata =
getSerializedSortedProofMetadata(proofMetadata);
formData.set('meta', JSON.stringify(info));
formData.set('signed_metadata', serializedSignedMessage);
formData.set('signed_metadata', serializedSortedProofMetadata);
formData.set('signature', JSON.stringify(getOldSignatures(proof)));

const fileBase64 = Object.keys(await proof.getAssets())[0];
Expand All @@ -408,10 +413,11 @@ async function buildFormDataToCreateAsset(proof: Proof) {

async function buildFormDataToUpdateSignature(proof: Proof) {
const formData = new FormData();
const signedMessage = await proof.generateSignedMessage();
const serializedSignedMessage =
getSerializedSortedSignedMessage(signedMessage);
formData.set('signed_metadata', serializedSignedMessage);

const ProofMetadata = await proof.generateProofMetadata();
const serializedSortedProofMetadata =
getSerializedSortedProofMetadata(ProofMetadata);
formData.set('signed_metadata', serializedSortedProofMetadata);
formData.set('signature', JSON.stringify(getOldSignatures(proof)));
return formData;
}
12 changes: 6 additions & 6 deletions src/app/shared/repositories/proof/proof.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import {
Assets,
DefaultFactId,
Facts,
getSerializedSortedSignedMessage,
isFacts,
isSignature,
Proof,
Signatures,
Truth,
getSerializedSortedProofMetadata,
isFacts,
isSignature,
} from './proof';

describe('Proof', () => {
Expand Down Expand Up @@ -270,9 +270,9 @@ describe('Proof utils', () => {

it('should get serialized sorted SignedTargets', async () => {
proof = await Proof.from(mediaStore, ASSETS, TRUTH);
const signedMessage = await proof.generateSignedMessage();
const expected = `{"asset_mime_type":"${ASSET1_MIMETYPE}","caption":"","created_at":${TIMESTAMP},"device_name":"${DEVICE_NAME_VALUE1}","information":{"device.device_name":"${DEVICE_NAME_VALUE2}","device.humidity":0.8,"geolocation.geolocation_latitude":${GEOLOCATION_LATITUDE2},"geolocation.geolocation_longitude":${GEOLOCATION_LONGITUDE2}},"location_latitude":${GEOLOCATION_LATITUDE1},"location_longitude":${GEOLOCATION_LONGITUDE1},"proof_hash":"${ASSET1_SHA256SUM}","recorder":"Capture","spec_version":"2.0.0"}`;
expect(getSerializedSortedSignedMessage(signedMessage)).toEqual(expected);
const ProofMetadata = await proof.generateProofMetadata();
const expected = `{\n "asset_mime_type": "${ASSET1_MIMETYPE}",\n "caption": "",\n "created_at": ${TIMESTAMP},\n "device_name": "${DEVICE_NAME_VALUE1}",\n "information": {\n "device.device_name": "${DEVICE_NAME_VALUE2}",\n "device.humidity": 0.8,\n "geolocation.geolocation_latitude": ${GEOLOCATION_LATITUDE2},\n "geolocation.geolocation_longitude": ${GEOLOCATION_LONGITUDE2}},\n "location_latitude": ${GEOLOCATION_LATITUDE1},\n "location_longitude": ${GEOLOCATION_LONGITUDE1},\n "proof_hash": "${ASSET1_SHA256SUM}",\n "recorder": "Capture",\n "spec_version":"2.0.0"\n}`;
expect(getSerializedSortedProofMetadata(ProofMetadata)).toEqual(expected);
});
});

Expand Down
44 changes: 29 additions & 15 deletions src/app/shared/repositories/proof/proof.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { CameraSource } from '@capacitor/camera';
import { snakeCase } from 'lodash';
import { defer, iif, of } from 'rxjs';
import { concatMap } from 'rxjs/operators';
import { sha256WithString } from '../../../utils/crypto/crypto';
import { sortObjectDeeplyByKey } from '../../../utils/immutable/immutable';
import { MimeType } from '../../../utils/mime-type';
import { generateIntegritySha } from '../../../utils/nit/nit';
import { isNonNullable } from '../../../utils/rx-operators/rx-operators';
import { CaptureAppWebCryptoApiSignatureProvider } from '../../collector/signature/capture-app-web-crypto-api-signature-provider/capture-app-web-crypto-api-signature-provider.service';
import { Tuple } from '../../database/table/table';
import {
MediaStore,
Expand Down Expand Up @@ -221,10 +224,10 @@ export class Proof {
* - https://app.asana.com/0/0/1204012493522134/1204289040001270/f
* @returns A promise that resolves to the generated signed message
*/
async generateSignedMessage(
async generateProofMetadata(
recorder: RecorderType = RecorderType.Capture
): Promise<SignedMessage> {
const signedMessage: SignedMessage = {
): Promise<ProofMetadata> {
const ProofMetadata: ProofMetadata = {
spec_version: SIGNATURE_VERSION,
recorder: recorder,
created_at: this.truth.timestamp,
Expand All @@ -236,7 +239,7 @@ export class Proof {
caption: '',
information: this.getInformation(),
};
return signedMessage;
return ProofMetadata;
}

/**
Expand All @@ -255,18 +258,19 @@ export class Proof {
}

async isVerified() {
const signedMessage: SignedMessage = await this.generateSignedMessage();
const serializedSortedSignedMessage =
getSerializedSortedSignedMessage(signedMessage);
// FIXME: Read CameraSource
const recorder = CaptureAppWebCryptoApiSignatureProvider.recorderFor(
CameraSource.Camera
);
const proofMetadata: ProofMetadata = await this.generateProofMetadata(
recorder
);
const integritySha = await generateIntegritySha(proofMetadata);
const results = await Promise.all(
Object.entries(this.signatures).map(([id, signature]) =>
Proof.signatureProviders
.get(id)
?.verify(
serializedSortedSignedMessage,
signature.signature,
signature.publicKey
)
?.verify(integritySha, signature.signature, signature.publicKey)
)
);
return results.every(result => result);
Expand Down Expand Up @@ -347,6 +351,11 @@ export const enum FactCategory {
GEOLOCATION = 'geolocation',
}

export interface SignResult extends Tuple {
readonly signatures: Signatures;
readonly integritySha: string;
}

export interface Signatures extends Tuple {
readonly [id: string]: Signature;
}
Expand Down Expand Up @@ -384,8 +393,13 @@ export function getSerializedSortedSignedTargets(signedTargets: SignedTargets) {
return JSON.stringify(sortObjectDeeplyByKey(signedTargets as any).toJSON());
}

export function getSerializedSortedSignedMessage(signedMessage: SignedMessage) {
return JSON.stringify(sortObjectDeeplyByKey(signedMessage as any).toJSON());
export function getSerializedSortedProofMetadata(ProofMetadata: ProofMetadata) {
const indent = 2;
return JSON.stringify(
sortObjectDeeplyByKey(ProofMetadata as any).toJSON(),
null,
indent
);
}

interface SignatureVerifier {
Expand All @@ -410,7 +424,7 @@ export interface IndexedProofView extends Tuple {
* The new signed message schema as discussed in
* https://github.com/numbersprotocol/capture-lite/issues/779
*/
export interface SignedMessage {
export interface ProofMetadata {
spec_version: string;
recorder: RecorderType;
created_at: number;
Expand Down
13 changes: 6 additions & 7 deletions src/app/utils/nit/nit.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { ethers, sha256 } from 'ethers';
import {
SignedMessage,
getSerializedSortedSignedMessage,
ProofMetadata,
getSerializedSortedProofMetadata,
} from '../../shared/repositories/proof/proof';

export async function generateIntegritySha(message: SignedMessage) {
const serializedSignedMessage = getSerializedSortedSignedMessage(message);
export async function generateIntegritySha(proofMetadata: ProofMetadata) {
const serializedSortedProofMetadata =
getSerializedSortedProofMetadata(proofMetadata);

// eslint-disable-next-line @typescript-eslint/no-magic-numbers
const data = JSON.stringify(JSON.stringify(serializedSignedMessage, null, 2));
const dataBytes = ethers.toUtf8Bytes(data);
const dataBytes = ethers.toUtf8Bytes(serializedSortedProofMetadata);

/**
* WORKAROUND: <TODO-paste-related-url-link>
Expand Down

0 comments on commit e4a6c87

Please sign in to comment.