diff --git a/.github/workflows/circleci.yml b/.github/workflows/circleci.yml index 6d904c72d5b..441740d5d16 100644 --- a/.github/workflows/circleci.yml +++ b/.github/workflows/circleci.yml @@ -460,9 +460,9 @@ jobs: package-json-checksum: ${{ needs.install-dependencies.outputs.package-json-checksum }} - name: Get changed files id: changed-files - uses: tj-actions/changed-files@v35 + uses: tj-actions/changed-files@v37 with: - fetch_depth: '100' + fetch_depth: '150' - name: Generate DevChain run: | cd packages/sdk/identity diff --git a/packages/phone-number-privacy/combiner/src/common/sign.ts b/packages/phone-number-privacy/combiner/src/common/sign.ts index 49b766bb57a..3a82f0dc012 100644 --- a/packages/phone-number-privacy/combiner/src/common/sign.ts +++ b/packages/phone-number-privacy/combiner/src/common/sign.ts @@ -2,7 +2,6 @@ import { DomainRestrictedSignatureRequest, ErrorMessage, ErrorType, - LegacySignMessageRequest, OdisResponse, responseHasExpectedKeyVersion, SignMessageRequest, @@ -18,8 +17,8 @@ import { IO } from './io' // prettier-ignore export type OdisSignatureRequest = | SignMessageRequest - | LegacySignMessageRequest | DomainRestrictedSignatureRequest + export type ThresholdStateService = R extends SignMessageRequest ? PnpThresholdStateService : never | R extends DomainRestrictedSignatureRequest diff --git a/packages/phone-number-privacy/combiner/src/pnp/endpoints/quota/io.ts b/packages/phone-number-privacy/combiner/src/pnp/endpoints/quota/io.ts index 993c4bbc817..6356db909f9 100644 --- a/packages/phone-number-privacy/combiner/src/pnp/endpoints/quota/io.ts +++ b/packages/phone-number-privacy/combiner/src/pnp/endpoints/quota/io.ts @@ -5,7 +5,6 @@ import { ErrorType, getSignerEndpoint, hasValidAccountParam, - identifierIsValidIfExists, isBodyReasonablySized, PnpQuotaRequest, PnpQuotaRequestSchema, @@ -57,7 +56,6 @@ export class PnpQuotaIO extends IO { return ( super.validateClientRequest(request) && hasValidAccountParam(request.body) && - identifierIsValidIfExists(request.body) && isBodyReasonablySized(request.body) ) } diff --git a/packages/phone-number-privacy/combiner/src/pnp/endpoints/sign/action.ts b/packages/phone-number-privacy/combiner/src/pnp/endpoints/sign/action.ts index ed011827808..e75ddb8e727 100644 --- a/packages/phone-number-privacy/combiner/src/pnp/endpoints/sign/action.ts +++ b/packages/phone-number-privacy/combiner/src/pnp/endpoints/sign/action.ts @@ -1,7 +1,6 @@ import { ErrorMessage, ErrorType, - LegacySignMessageRequest, SignMessageRequest, WarningMessage, } from '@celo/phone-number-privacy-common' @@ -12,7 +11,7 @@ import { PnpSignerResponseLogger } from '../../services/log-responses' export class PnpSignAction extends SignAction { readonly responseLogger: PnpSignerResponseLogger = new PnpSignerResponseLogger() - combine(session: CryptoSession): void { + combine(session: CryptoSession): void { this.responseLogger.logResponseDiscrepancies(session) this.responseLogger.logFailOpenResponses(session) @@ -41,7 +40,7 @@ export class PnpSignAction extends SignAction { this.handleMissingSignatures(session) } - protected parseBlindedMessage(req: SignMessageRequest | LegacySignMessageRequest): string { + protected parseBlindedMessage(req: SignMessageRequest): string { return req.blindedQueryPhoneNumber } diff --git a/packages/phone-number-privacy/combiner/src/pnp/endpoints/sign/io.legacy.ts b/packages/phone-number-privacy/combiner/src/pnp/endpoints/sign/io.legacy.ts deleted file mode 100644 index c838de5c3f2..00000000000 --- a/packages/phone-number-privacy/combiner/src/pnp/endpoints/sign/io.legacy.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { ContractKit } from '@celo/contractkit' -import { - authenticateUser, - CombinerEndpoint, - ErrorType, - getSignerEndpoint, - hasValidAccountParam, - hasValidBlindedPhoneNumberParam, - identifierIsValidIfExists, - isBodyReasonablySized, - LegacySignMessageRequest, - LegacySignMessageRequestSchema, - PnpQuotaStatus, - send, - SignerEndpoint, - SignMessageResponse, - SignMessageResponseFailure, - SignMessageResponseSchema, - SignMessageResponseSuccess, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { Request, Response } from 'express' -import * as t from 'io-ts' -import { BLSCryptographyClient } from '../../../common/crypto-clients/bls-crypto-client' -import { CryptoSession } from '../../../common/crypto-session' -import { IO } from '../../../common/io' -import { getCombinerVersion, OdisConfig } from '../../../config' - -export class LegacyPnpSignIO extends IO { - readonly endpoint: CombinerEndpoint = CombinerEndpoint.LEGACY_PNP_SIGN - readonly signerEndpoint: SignerEndpoint = getSignerEndpoint(this.endpoint) - readonly requestSchema: t.Type = - LegacySignMessageRequestSchema - readonly responseSchema: t.Type = - SignMessageResponseSchema - - constructor(readonly config: OdisConfig, readonly kit: ContractKit) { - super(config) - } - - async init( - request: Request<{}, {}, unknown>, - response: Response - ): Promise | null> { - if (!super.inputChecks(request, response)) { - return null - } - if (!this.requestHasSupportedKeyVersion(request, response.locals.logger)) { - this.sendFailure(WarningMessage.INVALID_KEY_VERSION_REQUEST, 400, response) - return null - } - if (!(await this.authenticate(request, response.locals.logger))) { - this.sendFailure(WarningMessage.UNAUTHENTICATED_USER, 401, response) - return null - } - const keyVersionInfo = this.getKeyVersionInfo(request, response.locals.logger) - return new CryptoSession( - request, - response, - keyVersionInfo, - new BLSCryptographyClient(keyVersionInfo) - ) - } - - validateClientRequest( - request: Request<{}, {}, unknown> - ): request is Request<{}, {}, LegacySignMessageRequest> { - return ( - super.validateClientRequest(request) && - hasValidAccountParam(request.body) && - hasValidBlindedPhoneNumberParam(request.body) && - identifierIsValidIfExists(request.body) && - isBodyReasonablySized(request.body) - ) - } - - async authenticate( - request: Request<{}, {}, LegacySignMessageRequest>, - logger: Logger - ): Promise { - return authenticateUser( - request, - this.kit, - logger, - this.config.shouldFailOpen, - [], - this.config.fullNodeTimeoutMs, - this.config.fullNodeRetryCount, - this.config.fullNodeRetryDelayMs - ) - } - - sendSuccess( - status: number, - response: Response, - signature: string, - quotaStatus: PnpQuotaStatus, - warnings: string[] - ) { - send( - response, - { - success: true, - version: getCombinerVersion(), - signature, - ...quotaStatus, - warnings, - }, - status, - response.locals.logger - ) - } - - sendFailure(error: ErrorType, status: number, response: Response) { - send( - response, - { - success: false, - version: getCombinerVersion(), - error, - }, - status, - response.locals.logger - ) - } -} diff --git a/packages/phone-number-privacy/combiner/src/server.ts b/packages/phone-number-privacy/combiner/src/server.ts index 19ad593db14..e5360d9eaea 100644 --- a/packages/phone-number-privacy/combiner/src/server.ts +++ b/packages/phone-number-privacy/combiner/src/server.ts @@ -24,7 +24,6 @@ import { PnpQuotaAction } from './pnp/endpoints/quota/action' import { PnpQuotaIO } from './pnp/endpoints/quota/io' import { PnpSignAction } from './pnp/endpoints/sign/action' import { PnpSignIO } from './pnp/endpoints/sign/io' -import { LegacyPnpSignIO } from './pnp/endpoints/sign/io.legacy' import { PnpThresholdStateService } from './pnp/services/threshold-state' require('events').EventEmitter.defaultMaxListeners = 15 @@ -61,23 +60,6 @@ export function startCombiner(config: CombinerConfig, kit: ContractKit) { const pnpThresholdStateService = new PnpThresholdStateService() - const legacyPnpSign = new Controller( - new PnpSignAction( - config.phoneNumberPrivacy, - pnpThresholdStateService, - new LegacyPnpSignIO(config.phoneNumberPrivacy, kit) - ) - ) - app.post(CombinerEndpoint.LEGACY_PNP_SIGN, (req, res) => - meterResponse( - legacyPnpSign.handle.bind(legacyPnpSign), - req, - res, - CombinerEndpoint.LEGACY_PNP_SIGN, - config - ) - ) - const pnpQuota = new Controller( new PnpQuotaAction( config.phoneNumberPrivacy, diff --git a/packages/phone-number-privacy/combiner/test/end-to-end/legacypnp.test.ts b/packages/phone-number-privacy/combiner/test/end-to-end/legacypnp.test.ts deleted file mode 100644 index 6d8cd160148..00000000000 --- a/packages/phone-number-privacy/combiner/test/end-to-end/legacypnp.test.ts +++ /dev/null @@ -1,376 +0,0 @@ -import { newKit } from '@celo/contractkit' -import { OdisUtils } from '@celo/identity' -import { PhoneNumberHashDetails } from '@celo/identity/lib/odis/phone-number-identifier' -import { - ErrorMessages, - getOdisPnpRequestAuth, - getServiceContext, - OdisAPI, - WalletKeySigner, -} from '@celo/identity/lib/odis/query' -import { - AuthenticationMethod, - CombinerEndpoint, - LegacySignMessageRequest, - SignMessageResponseSchema, - SignMessageResponseSuccess, -} from '@celo/phone-number-privacy-common' -import threshold_bls from 'blind-threshold-bls' -import { randomBytes } from 'crypto' -import 'isomorphic-fetch' -import { getCombinerVersion } from '../../src' -import { getBlindedPhoneNumber } from '../utils' -import { - ACCOUNT_ADDRESS, - ACCOUNT_ADDRESS_NO_QUOTA, - DEFAULT_FORNO_URL, - dekAuthSigner, - getTestContextName, - PHONE_NUMBER, - walletAuthSigner, -} from './resources' - -require('dotenv').config() - -jest.setTimeout(60000) - -const SERVICE_CONTEXT = getServiceContext(getTestContextName(), OdisAPI.PNP) -const combinerUrl = SERVICE_CONTEXT.odisUrl -const fullNodeUrl = process.env.ODIS_BLOCKCHAIN_PROVIDER - -const expectedVersion = getCombinerVersion() - -describe(`Running against service deployed at ${combinerUrl} w/ blockchain provider ${fullNodeUrl}`, () => { - it('Service is deployed at correct version', async () => { - const response = await fetch(combinerUrl + CombinerEndpoint.STATUS, { - method: 'GET', - }) - const body = await response.json() - // This checks against local package.json version, change if necessary - expect(body.version).toBe(expectedVersion) - }) - - describe(`${CombinerEndpoint.LEGACY_PNP_SIGN}`, () => { - it('Should succeed when authenticated with WALLET_KEY', async () => { - const res = await OdisUtils.PhoneNumberIdentifier.getPhoneNumberIdentifier( - PHONE_NUMBER, - ACCOUNT_ADDRESS, - walletAuthSigner, - SERVICE_CONTEXT, - undefined, - undefined, - undefined, - undefined, - undefined, - CombinerEndpoint.LEGACY_PNP_SIGN - ) - threshold_bls.verify( - Buffer.from(SERVICE_CONTEXT.odisPubKey, 'base64'), - Buffer.from(PHONE_NUMBER), - Buffer.from(res.unblindedSignature!, 'base64') - ) - }) - - it('Should succeed when authenticated with DEK', async () => { - const res = await OdisUtils.PhoneNumberIdentifier.getPhoneNumberIdentifier( - PHONE_NUMBER, - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT, - undefined, - undefined, - undefined, - undefined, - undefined, - CombinerEndpoint.LEGACY_PNP_SIGN - ) - threshold_bls.verify( - Buffer.from(SERVICE_CONTEXT.odisPubKey, 'base64'), - Buffer.from(PHONE_NUMBER), - Buffer.from(res.unblindedSignature!, 'base64') - ) - }) - - it('Should succeed on repeated valid requests', async () => { - const res1 = await OdisUtils.PhoneNumberIdentifier.getPhoneNumberIdentifier( - PHONE_NUMBER, - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT, - undefined, - undefined, - undefined, - undefined, - undefined, - CombinerEndpoint.LEGACY_PNP_SIGN - ) - threshold_bls.verify( - Buffer.from(SERVICE_CONTEXT.odisPubKey, 'base64'), - Buffer.from(PHONE_NUMBER), - Buffer.from(res1.unblindedSignature!, 'base64') - ) - const res2 = await OdisUtils.PhoneNumberIdentifier.getPhoneNumberIdentifier( - PHONE_NUMBER, - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT, - undefined, - undefined, - undefined, - undefined, - undefined, - CombinerEndpoint.LEGACY_PNP_SIGN - ) - expect(res2).toStrictEqual(res1) - }) - - it('Should increment performedQueryCount on success', async () => { - const req: LegacySignMessageRequest = { - account: ACCOUNT_ADDRESS, - blindedQueryPhoneNumber: getBlindedPhoneNumber(PHONE_NUMBER, randomBytes(32)), - authenticationMethod: dekAuthSigner(0).authenticationMethod, - } - const res1 = (await OdisUtils.Query.queryOdis( - req, - SERVICE_CONTEXT, - CombinerEndpoint.LEGACY_PNP_SIGN, - SignMessageResponseSchema, - { - Authorization: await getOdisPnpRequestAuth(req, dekAuthSigner(0)), - } - )) as SignMessageResponseSuccess - expect(res1.success).toBe(true) - const req2 = req - req2.blindedQueryPhoneNumber = getBlindedPhoneNumber(PHONE_NUMBER, randomBytes(32)) - const res2 = (await OdisUtils.Query.queryOdis( - req2, - SERVICE_CONTEXT, - CombinerEndpoint.LEGACY_PNP_SIGN, - SignMessageResponseSchema, - { - Authorization: await getOdisPnpRequestAuth(req, dekAuthSigner(0)), - } - )) as SignMessageResponseSuccess - - expect(res2.success).toBe(true) - // There may be warnings but that's ok - expect(res2).toMatchObject({ - success: true, - version: expectedVersion, - signature: res2.signature, - performedQueryCount: res1.performedQueryCount + 1, - totalQuota: res1.totalQuota, - blockNumber: res2.blockNumber, - }) - }) - - it('Should not increment performedQueryCount on replayed request when using DEK auth', async () => { - const req: LegacySignMessageRequest = { - account: ACCOUNT_ADDRESS, - blindedQueryPhoneNumber: getBlindedPhoneNumber(PHONE_NUMBER, randomBytes(32)), - authenticationMethod: dekAuthSigner(0).authenticationMethod, - } - const res1 = (await OdisUtils.Query.queryOdis( - req, - SERVICE_CONTEXT, - CombinerEndpoint.LEGACY_PNP_SIGN, - SignMessageResponseSchema, - { - Authorization: await getOdisPnpRequestAuth(req, dekAuthSigner(0)), - } - )) as SignMessageResponseSuccess - expect(res1.success).toBe(true) - const res2 = (await OdisUtils.Query.queryOdis( - req, - SERVICE_CONTEXT, - CombinerEndpoint.LEGACY_PNP_SIGN, - SignMessageResponseSchema, - { - Authorization: await getOdisPnpRequestAuth(req, dekAuthSigner(0)), - } - )) as SignMessageResponseSuccess - expect(res2.success).toBe(true) - // There may be warnings but that's ok - expect(res2).toMatchObject({ - success: true, - version: expectedVersion, - signature: res2.signature, - performedQueryCount: res1.performedQueryCount, - totalQuota: res1.totalQuota, - blockNumber: res2.blockNumber, - }) - }) - - for (let i = 1; i <= 2; i++) { - it(`Should succeed on valid request with key version header ${i}`, async () => { - const res = await OdisUtils.PhoneNumberIdentifier.getPhoneNumberIdentifier( - PHONE_NUMBER, - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT, - undefined, - undefined, - undefined, - undefined, - i, - CombinerEndpoint.LEGACY_PNP_SIGN - ) - threshold_bls.verify( - Buffer.from(SERVICE_CONTEXT.odisPubKey, 'base64'), - Buffer.from(PHONE_NUMBER), - Buffer.from(res.unblindedSignature!, 'base64') - ) - }) - } - - it(`Should succeed on invalid key version`, async () => { - const res = await OdisUtils.PhoneNumberIdentifier.getPhoneNumberIdentifier( - PHONE_NUMBER, - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT, - undefined, - undefined, - undefined, - undefined, - 1.5, - CombinerEndpoint.LEGACY_PNP_SIGN - ) - threshold_bls.verify( - Buffer.from(SERVICE_CONTEXT.odisPubKey, 'base64'), - Buffer.from(PHONE_NUMBER), - Buffer.from(res.unblindedSignature!, 'base64') - ) - }) - - it(`Should reject to throw ${ErrorMessages.ODIS_INPUT_ERROR} on unsupported key version`, async () => { - await expect( - OdisUtils.PhoneNumberIdentifier.getPhoneNumberIdentifier( - PHONE_NUMBER, - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT, - undefined, - undefined, - undefined, - undefined, - 10, - CombinerEndpoint.LEGACY_PNP_SIGN - ) - ).rejects.toThrow(ErrorMessages.ODIS_INPUT_ERROR) - }) - - it(`Should reject to throw ${ErrorMessages.ODIS_INPUT_ERROR} on invalid address`, async () => { - await expect( - OdisUtils.PhoneNumberIdentifier.getPhoneNumberIdentifier( - PHONE_NUMBER, - 'not an address', - dekAuthSigner(0), - SERVICE_CONTEXT, - undefined, - undefined, - undefined, - undefined, - 1, - CombinerEndpoint.LEGACY_PNP_SIGN - ) - ).rejects.toThrow(ErrorMessages.ODIS_INPUT_ERROR) - }) - - it(`Should reject to throw ${ErrorMessages.ODIS_INPUT_ERROR} on invalid phone number`, async () => { - await expect( - OdisUtils.PhoneNumberIdentifier.getPhoneNumberIdentifier( - 'not a phone number', - ACCOUNT_ADDRESS, - dekAuthSigner(0), - SERVICE_CONTEXT, - undefined, - undefined, - undefined, - undefined, - 1, - CombinerEndpoint.LEGACY_PNP_SIGN - ) - ).rejects.toThrow('Invalid phone number: not a phone number') - }) - - it(`Should reject to throw 'unknown account' with invalid WALLET_KEY auth`, async () => { - const badWalletAuthSigner: WalletKeySigner = { - authenticationMethod: AuthenticationMethod.WALLET_KEY, - contractKit: newKit(DEFAULT_FORNO_URL), // doesn't have any private keys - } - await expect( - OdisUtils.PhoneNumberIdentifier.getPhoneNumberIdentifier( - PHONE_NUMBER, - ACCOUNT_ADDRESS, - badWalletAuthSigner, - SERVICE_CONTEXT, - undefined, - undefined, - undefined, - undefined, - undefined, - CombinerEndpoint.LEGACY_PNP_SIGN - ) - ).rejects.toThrow('unknown account') - }) - - it(`Should reject to throw ${ErrorMessages.ODIS_AUTH_ERROR} with invalid WALLET_KEY auth`, async () => { - const req: LegacySignMessageRequest = { - account: ACCOUNT_ADDRESS, - blindedQueryPhoneNumber: getBlindedPhoneNumber(PHONE_NUMBER, randomBytes(32)), - authenticationMethod: walletAuthSigner.authenticationMethod, - } - await expect( - OdisUtils.Query.queryOdis( - req, - SERVICE_CONTEXT, - CombinerEndpoint.LEGACY_PNP_SIGN, - SignMessageResponseSchema, - { - Authorization: await walletAuthSigner.contractKit.connection.sign( - JSON.stringify(req), - ACCOUNT_ADDRESS_NO_QUOTA - ), - } - ) - ).rejects.toThrow(ErrorMessages.ODIS_AUTH_ERROR) - }) - - it(`Should reject to throw ${ErrorMessages.ODIS_AUTH_ERROR} with invalid DEK auth`, async () => { - await expect( - OdisUtils.PhoneNumberIdentifier.getPhoneNumberIdentifier( - PHONE_NUMBER, - ACCOUNT_ADDRESS, - dekAuthSigner(1), - SERVICE_CONTEXT, - undefined, - undefined, - undefined, - undefined, - undefined, - CombinerEndpoint.LEGACY_PNP_SIGN - ) - ).rejects.toThrow(ErrorMessages.ODIS_AUTH_ERROR) - }) - - it(`Should reject to throw ${ErrorMessages.ODIS_QUOTA_ERROR} when account has no quota`, async () => { - // Ensure it's not a replayed request. - const unusedPN = `+1${Date.now()}` - await expect( - OdisUtils.PhoneNumberIdentifier.getPhoneNumberIdentifier( - unusedPN, - ACCOUNT_ADDRESS_NO_QUOTA, - dekAuthSigner(0), - SERVICE_CONTEXT, - undefined, - undefined, - undefined, - undefined, - undefined, - CombinerEndpoint.LEGACY_PNP_SIGN - ) - ).rejects.toThrow(ErrorMessages.ODIS_QUOTA_ERROR) - }) - }) -}) diff --git a/packages/phone-number-privacy/combiner/test/end-to-end/tmpBackwardsCompatibility.test.ts b/packages/phone-number-privacy/combiner/test/end-to-end/tmpBackwardsCompatibility.test.ts index 9db2ebe2ed1..bccf01dfc02 100644 --- a/packages/phone-number-privacy/combiner/test/end-to-end/tmpBackwardsCompatibility.test.ts +++ b/packages/phone-number-privacy/combiner/test/end-to-end/tmpBackwardsCompatibility.test.ts @@ -1,16 +1,12 @@ import { newKit } from '@celo/contractkit' import { OdisUtils } from '@celo/identity-prev' -import { getServiceContext, SignMessageRequest } from '@celo/identity-prev/lib/odis/query' +import { getServiceContext } from '@celo/identity-prev/lib/odis/query' import { ErrorMessages } from '@celo/identity/lib/odis/query' -import { AuthenticationMethod, Endpoint } from '@celo/phone-number-privacy-common' -import { replenishQuota } from '@celo/phone-number-privacy-common/lib/test/utils' -import { genSessionID } from '@celo/phone-number-privacy-common/lib/utils/logger' import { ensureLeading0x } from '@celo/utils/lib/address' import 'isomorphic-fetch' import { ACCOUNT_ADDRESS, ACCOUNT_ADDRESS_NO_QUOTA, - BLINDED_PHONE_NUMBER, DEFAULT_FORNO_URL, dekAuthSigner, deks, @@ -48,50 +44,6 @@ describe(`Running against service deployed at ${SERVICE_CONTEXT.odisUrl} w/ bloc .sendAndWaitForReceipt({ from: ACCOUNT_ADDRESS_NO_QUOTA }) } }) - - describe('Returns status ODIS_INPUT_ERROR', () => { - it('With invalid address', async () => { - const body: SignMessageRequest = { - account: '0x1234', - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - blindedQueryPhoneNumber: BLINDED_PHONE_NUMBER, - version: 'ignore', - sessionID: genSessionID(), - } - - await expect( - OdisUtils.Query.queryOdis(dekAuthSigner(0), body, SERVICE_CONTEXT, Endpoint.LEGACY_PNP_SIGN) - ).rejects.toThrow(ErrorMessages.ODIS_INPUT_ERROR) - }) - - it('With missing blindedQueryPhoneNumber', async () => { - const body: SignMessageRequest = { - account: ACCOUNT_ADDRESS, - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - blindedQueryPhoneNumber: '', - version: 'ignore', - sessionID: genSessionID(), - } - await expect( - OdisUtils.Query.queryOdis(dekAuthSigner(0), body, SERVICE_CONTEXT, Endpoint.LEGACY_PNP_SIGN) - ).rejects.toThrow(ErrorMessages.ODIS_INPUT_ERROR) - }) - }) - - describe('Returns ODIS_AUTH_ERROR', () => { - it('With invalid authentication', async () => { - const body: SignMessageRequest = { - account: ACCOUNT_ADDRESS, - authenticationMethod: AuthenticationMethod.WALLET_KEY, - blindedQueryPhoneNumber: BLINDED_PHONE_NUMBER, - version: 'ignore', - } - await expect( - OdisUtils.Query.queryOdis(dekAuthSigner(0), body, SERVICE_CONTEXT, Endpoint.LEGACY_PNP_SIGN) - ).rejects.toThrow(ErrorMessages.ODIS_AUTH_ERROR) - }) - }) - describe('Returns ODIS_QUOTA_ERROR', () => { it('When querying out of quota', async () => { await expect( @@ -104,29 +56,4 @@ describe(`Running against service deployed at ${SERVICE_CONTEXT.odisUrl} w/ bloc ).rejects.toThrow(ErrorMessages.ODIS_QUOTA_ERROR) }) }) - - describe('With enough quota', () => { - // if these tests are failing, it may just be that the address needs to be fauceted: - // celotooljs account faucet --account 0x1be31a94361a391bbafb2a4ccd704f57dc04d4bb --dollar 1 --gold 1 -e --verbose - it('Returns sig when querying with unused and used request', async () => { - await replenishQuota(ACCOUNT_ADDRESS, contractKit) - const body: SignMessageRequest = { - account: ACCOUNT_ADDRESS, - authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY, - blindedQueryPhoneNumber: BLINDED_PHONE_NUMBER, - version: 'ignore', - sessionID: genSessionID(), - } - // Query twice to test reusing the request - for (let i = 0; i < 2; i++) { - const result = OdisUtils.Query.queryOdis( - dekAuthSigner(0), - body, - SERVICE_CONTEXT, - Endpoint.LEGACY_PNP_SIGN - ) - await expect(result).resolves.toMatchObject({ success: true }) - } - }) - }) }) diff --git a/packages/phone-number-privacy/combiner/test/integration/domain.test.ts b/packages/phone-number-privacy/combiner/test/integration/domain.test.ts index f56b765df97..143038262d4 100644 --- a/packages/phone-number-privacy/combiner/test/integration/domain.test.ts +++ b/packages/phone-number-privacy/combiner/test/integration/domain.test.ts @@ -92,10 +92,6 @@ const signerConfig: SignerConfig = { enabled: false, shouldFailOpen: false, }, - legacyPhoneNumberPrivacy: { - enabled: false, - shouldFailOpen: false, - }, }, attestations: { numberAttestationsRequired: 3, diff --git a/packages/phone-number-privacy/combiner/test/integration/legacypnp.test.ts b/packages/phone-number-privacy/combiner/test/integration/legacypnp.test.ts deleted file mode 100644 index c75286184d5..00000000000 --- a/packages/phone-number-privacy/combiner/test/integration/legacypnp.test.ts +++ /dev/null @@ -1,799 +0,0 @@ -import { newKit } from '@celo/contractkit' -import { - AuthenticationMethod, - CombinerEndpoint, - DB_TIMEOUT, - ErrorMessage, - FULL_NODE_TIMEOUT_IN_MS, - genSessionID, - KEY_VERSION_HEADER, - LegacySignMessageRequest, - RETRY_COUNT, - RETRY_DELAY_IN_MS, - SignMessageResponseFailure, - SignMessageResponseSuccess, - TestUtils, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { AttestationsStatus } from '@celo/phone-number-privacy-common/lib/test/utils' -import { IDENTIFIER } from '@celo/phone-number-privacy-common/lib/test/values' -import { - initDatabase as initSignerDatabase, - startSigner, - SupportedDatabase, - SupportedKeystore, -} from '@celo/phone-number-privacy-signer' -import { - DefaultKeyName, - KeyProvider, -} from '@celo/phone-number-privacy-signer/dist/common/key-management/key-provider-base' -import { MockKeyProvider } from '@celo/phone-number-privacy-signer/dist/common/key-management/mock-key-provider' -import { SignerConfig } from '@celo/phone-number-privacy-signer/dist/config' -import BigNumber from 'bignumber.js' -import threshold_bls from 'blind-threshold-bls' -import { Server as HttpsServer } from 'https' -import { Knex } from 'knex' -import { Server } from 'net' -import request from 'supertest' -import config, { getCombinerVersion } from '../../src/config' -import { startCombiner } from '../../src/server' - -const { - ContractRetrieval, - createMockContractKit, - createMockAccounts, - createMockToken, - createMockWeb3, - getPnpRequestAuthorization, - createMockAttestation, -} = TestUtils.Utils -const { - PRIVATE_KEY1, - ACCOUNT_ADDRESS1, - mockAccount, - DEK_PRIVATE_KEY, - DEK_PUBLIC_KEY, - PNP_THRESHOLD_DEV_PK_SHARE_1_V1, - PNP_THRESHOLD_DEV_PK_SHARE_1_V2, - PNP_THRESHOLD_DEV_PK_SHARE_1_V3, - PNP_THRESHOLD_DEV_PK_SHARE_2_V1, - PNP_THRESHOLD_DEV_PK_SHARE_2_V2, - PNP_THRESHOLD_DEV_PK_SHARE_2_V3, - PNP_THRESHOLD_DEV_PK_SHARE_3_V1, - PNP_THRESHOLD_DEV_PK_SHARE_3_V2, - PNP_THRESHOLD_DEV_PK_SHARE_3_V3, -} = TestUtils.Values - -// create deep copy -const combinerConfig: typeof config = JSON.parse(JSON.stringify(config)) -combinerConfig.phoneNumberPrivacy.enabled = true - -const signerConfig: SignerConfig = { - serviceName: 'odis-signer', - server: { - port: undefined, - sslKeyPath: undefined, - sslCertPath: undefined, - }, - quota: { - unverifiedQueryMax: 10, - additionalVerifiedQueryMax: 30, - queryPerTransaction: 2, - // Min balance is .01 cUSD - minDollarBalance: new BigNumber(1e16), - // Min balance is .01 cEUR - minEuroBalance: new BigNumber(1e16), - // Min balance is .005 CELO - minCeloBalance: new BigNumber(5e15), - // Equivalent to 0.001 cUSD/query - queryPriceInCUSD: new BigNumber(0.001), - }, - api: { - domains: { - enabled: false, - }, - phoneNumberPrivacy: { - enabled: false, - shouldFailOpen: true, - }, - legacyPhoneNumberPrivacy: { - enabled: true, - shouldFailOpen: true, - }, - }, - attestations: { - numberAttestationsRequired: 3, - }, - blockchain: { - provider: 'https://alfajores-forno.celo-testnet.org', - apiKey: undefined, - }, - db: { - type: SupportedDatabase.Sqlite, - user: '', - password: '', - database: '', - host: 'http://localhost', - port: undefined, - ssl: true, - poolMaxSize: 50, - timeout: DB_TIMEOUT, - }, - keystore: { - type: SupportedKeystore.MOCK_SECRET_MANAGER, - keys: { - phoneNumberPrivacy: { - name: 'phoneNumberPrivacy', - latest: 2, - }, - domains: { - name: 'domains', - latest: 1, - }, - }, - azure: { - clientID: '', - clientSecret: '', - tenant: '', - vaultName: '', - }, - google: { - projectId: '', - }, - aws: { - region: '', - secretKey: '', - }, - }, - timeout: 5000, - test_quota_bypass_percentage: 0, - fullNodeTimeoutMs: FULL_NODE_TIMEOUT_IN_MS, - fullNodeRetryCount: RETRY_COUNT, - fullNodeRetryDelayMs: RETRY_DELAY_IN_MS, -} - -const testBlockNumber = 1000000 - -const mockTokenBalance = jest.fn() -const mockGetVerifiedStatus = jest.fn() -const mockGetWalletAddress = jest.fn() -const mockGetDataEncryptionKey = jest.fn() - -const mockContractKit = createMockContractKit( - { - // getWalletAddress stays constant across all old query-quota.test.ts unit tests - [ContractRetrieval.getAccounts]: createMockAccounts( - mockGetWalletAddress, - mockGetDataEncryptionKey - ), - [ContractRetrieval.getStableToken]: createMockToken(mockTokenBalance), - [ContractRetrieval.getGoldToken]: createMockToken(mockTokenBalance), - [ContractRetrieval.getAttestations]: createMockAttestation(mockGetVerifiedStatus), - }, - createMockWeb3(5, testBlockNumber) -) - -// Mock newKit as opposed to the CK constructor -// Returns an object of type ContractKit that can be passed into the signers + combiner -jest.mock('@celo/contractkit', () => ({ - ...jest.requireActual('@celo/contractkit'), - newKit: jest.fn().mockImplementation(() => mockContractKit), -})) - -describe(`legacyPnpService: ${CombinerEndpoint.LEGACY_PNP_SIGN}`, () => { - let keyProvider1: KeyProvider - let keyProvider2: KeyProvider - let keyProvider3: KeyProvider - let signerDB1: Knex - let signerDB2: Knex - let signerDB3: Knex - let signer1: Server | HttpsServer - let signer2: Server | HttpsServer - let signer3: Server | HttpsServer - let app: any - - // Used by PNP_SIGN tests for various configurations of signers - let userSeed: Uint8Array - let blindedMsgResult: threshold_bls.BlindedMessage - - const signerMigrationsPath = '../signer/dist/common/database/migrations' - const expectedVersion = getCombinerVersion() - - const message = Buffer.from('test message', 'utf8') - const expectedQuota = 410 - const expectedSignatures: string[] = [ - 'xgFMQtcgAMHJAEX/m9B4VFopYtxqPFSw0024sWzRYvQDvnmFqhXOPdnRDfa8WCEA', - 'wUuFV8yFBXGyEzKbyWjBChG6dER264nwjOsqErd/UZieVKE0oDMZcMDG+qObu4QB', - 'PJHqBGavcQG3NGFl3hiR8GymeDNumxbl1DnCJzWz+Ik5yCN2ZpAITBe24RTX0iMA', - ] - const expectedSignature = expectedSignatures[config.phoneNumberPrivacy.keys.currentVersion - 1] - - const expectedUnblindedSigs: string[] = [ - 'lOASnDJNbJBTMYfkbU4fMiK7FcNwSyqZo8iQSM95X8YK+/158be4S1A+jcQsCUYA', - 'QIT7HtHTe/d0Tq40Mf3rpHCT8qY20+8q7ZW9PXHFMWGvwSGhk7l3Pfwnx8YdXomB', - 'XW//DolLzaXYS/gk9WBHfeKy5HKrGjuF/OpCok/i6fprE4AGFH2PjE7zeKTfOQ+A', - ] - const expectedUnblindedSig = - expectedUnblindedSigs[config.phoneNumberPrivacy.keys.currentVersion - 1] - - // In current setup, the same mocked kit is used for the combiner and signers - const mockKit = newKit('dummyKit') - - const sendLegacyPnpSignRequest = async ( - req: LegacySignMessageRequest, - authorization: string, - app: any, - keyVersionHeader?: string - ) => { - let reqWithHeaders = request(app) - .post(CombinerEndpoint.LEGACY_PNP_SIGN) - .set('Authorization', authorization) - - if (keyVersionHeader) { - reqWithHeaders = reqWithHeaders.set(KEY_VERSION_HEADER, keyVersionHeader) - } - return reqWithHeaders.send(req) - } - - const getLegacySignRequest = ( - _blindedMsgResult: threshold_bls.BlindedMessage - ): LegacySignMessageRequest => { - return { - account: ACCOUNT_ADDRESS1, - blindedQueryPhoneNumber: Buffer.from(_blindedMsgResult.message).toString('base64'), - sessionID: genSessionID(), - } - } - - const prepMocks = (hasQuota: boolean) => { - const [transactionCount, isVerified, balanceToken] = hasQuota - ? [100, true, new BigNumber(200000000000000000)] - : [0, false, new BigNumber(0)] - ;[ - mockContractKit.connection.getTransactionCount, - mockGetVerifiedStatus, - mockGetVerifiedStatus, - mockTokenBalance, - mockGetDataEncryptionKey, - mockGetWalletAddress, - ].forEach((mockFn) => mockFn.mockReset()) - - mockContractKit.connection.getTransactionCount.mockReturnValue(transactionCount) - mockGetVerifiedStatus.mockReturnValue( - // only the isVerified value below matters - { isVerified, completed: 1, total: 1, numAttestationsRemaining: 1 } - ) - mockTokenBalance.mockReturnValue(balanceToken) - mockGetDataEncryptionKey.mockReturnValue(DEK_PUBLIC_KEY) - mockGetWalletAddress.mockReturnValue(mockAccount) - } - - beforeAll(async () => { - keyProvider1 = new MockKeyProvider( - new Map([ - [`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-1`, PNP_THRESHOLD_DEV_PK_SHARE_1_V1], - [`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-2`, PNP_THRESHOLD_DEV_PK_SHARE_1_V2], - [`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-3`, PNP_THRESHOLD_DEV_PK_SHARE_1_V3], - ]) - ) - keyProvider2 = new MockKeyProvider( - new Map([ - [`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-1`, PNP_THRESHOLD_DEV_PK_SHARE_2_V1], - [`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-2`, PNP_THRESHOLD_DEV_PK_SHARE_2_V2], - [`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-3`, PNP_THRESHOLD_DEV_PK_SHARE_2_V3], - ]) - ) - keyProvider3 = new MockKeyProvider( - new Map([ - [`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-1`, PNP_THRESHOLD_DEV_PK_SHARE_3_V1], - [`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-2`, PNP_THRESHOLD_DEV_PK_SHARE_3_V2], - [`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-3`, PNP_THRESHOLD_DEV_PK_SHARE_3_V3], - ]) - ) - - app = startCombiner(combinerConfig, mockKit) - }) - - let req: LegacySignMessageRequest - beforeEach(async () => { - signerDB1 = await initSignerDatabase(signerConfig, signerMigrationsPath) - signerDB2 = await initSignerDatabase(signerConfig, signerMigrationsPath) - signerDB3 = await initSignerDatabase(signerConfig, signerMigrationsPath) - - // this needs to be defined here to avoid errors - userSeed = new Uint8Array(32) - for (let i = 0; i < userSeed.length - 1; i++) { - userSeed[i] = i - } - - blindedMsgResult = threshold_bls.blind(message, userSeed) - - req = getLegacySignRequest(blindedMsgResult) - prepMocks(true) - }) - - afterEach(async () => { - await signerDB1?.destroy() - await signerDB2?.destroy() - await signerDB3?.destroy() - signer1?.close() - signer2?.close() - signer3?.close() - }) - - describe('when signers are operating correctly', () => { - beforeEach(async () => { - signer1 = startSigner(signerConfig, signerDB1, keyProvider1, mockKit).listen(3001) - signer2 = startSigner(signerConfig, signerDB2, keyProvider2, mockKit).listen(3002) - signer3 = startSigner(signerConfig, signerDB3, keyProvider3, mockKit).listen(3003) - }) - - it('Should respond with 200 on valid request', async () => { - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendLegacyPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - const unblindedSig = threshold_bls.unblind( - Buffer.from(res.body.signature, 'base64'), - blindedMsgResult.blindingFactor - ) - - expect(Buffer.from(unblindedSig).toString('base64')).toEqual(expectedUnblindedSig) - }) - - for (let i = 1; i <= 3; i++) { - it(`Should respond with 200 on valid request with key version header ${i}`, async () => { - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendLegacyPnpSignRequest(req, authorization, app, i.toString()) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignatures[i - 1], - performedQueryCount: 1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - const unblindedSig = threshold_bls.unblind( - Buffer.from(res.body.signature, 'base64'), - blindedMsgResult.blindingFactor - ) - - expect(Buffer.from(unblindedSig).toString('base64')).toEqual(expectedUnblindedSigs[i - 1]) - }) - } - - it('Should respond with 200 on valid request with identifier', async () => { - // Ensure that this gets passed through the combiner to the signer - req.hashedPhoneNumber = IDENTIFIER - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendLegacyPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: 440, // Additional quota gets unlocked with an identifier - blockNumber: testBlockNumber, - warnings: [], - }) - }) - - it('Should respond with 200 on repeated valid requests', async () => { - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res1 = await sendLegacyPnpSignRequest(req, authorization, app) - - expect(res1.status).toBe(200) - expect(res1.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - - // performedQueryCount should remain the same; same request should not - // consume any quota - const res2 = await sendLegacyPnpSignRequest(req, authorization, app) - expect(res2.status).toBe(200) - expect(res2.body).toStrictEqual(res1.body) - }) - - it('Should increment performedQueryCount on request from the same account with a new message', async () => { - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res1 = await sendLegacyPnpSignRequest(req, authorization, app) - - const expectedResponse: SignMessageResponseSuccess = { - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [], - } - - expect(res1.status).toBe(200) - expect(res1.body).toStrictEqual(expectedResponse) - - // Second request for the same account but with new message - const message2 = Buffer.from('second test message', 'utf8') - const blindedMsg2 = threshold_bls.blind(message2, userSeed) - const req2 = getLegacySignRequest(blindedMsg2) - const authorization2 = getPnpRequestAuthorization(req2, PRIVATE_KEY1) - - // Expect performedQueryCount to increase - expectedResponse.performedQueryCount++ - expectedResponse.signature = - 'PWvuSYIA249x1dx+qzgl6PKSkoulXXE/P4WHJvGmtw77pCRilEWTn3xSp+6JS9+A' - const res2 = await sendLegacyPnpSignRequest(req2, authorization2, app) - expect(res2.status).toBe(200) - expect(res2.body).toStrictEqual(expectedResponse) - }) - - it('Should respond with 200 on extra request fields', async () => { - // @ts-ignore Intentionally adding an extra field to the request type - req.extraField = 'dummyString' - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendLegacyPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - }) - - it('Should respond with 200 when authenticated with DEK', async () => { - req.authenticationMethod = AuthenticationMethod.ENCRYPTION_KEY - const authorization = getPnpRequestAuthorization(req, DEK_PRIVATE_KEY) - const res = await sendLegacyPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - }) - - it('Should get the same unblinded signatures from the same message (different seed)', async () => { - const authorization1 = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res1 = await sendLegacyPnpSignRequest(req, authorization1, app) - - expect(res1.status).toBe(200) - expect(res1.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - - const secondUserSeed = new Uint8Array(userSeed) - secondUserSeed[0]++ - // Ensure message is identical except for message - const req2 = { ...req } - const blindedMsgResult2 = threshold_bls.blind(message, secondUserSeed) - req2.blindedQueryPhoneNumber = Buffer.from(blindedMsgResult2.message).toString('base64') - - // Sanity check - expect(req2.blindedQueryPhoneNumber).not.toEqual(req.blindedQueryPhoneNumber) - - const authorization2 = getPnpRequestAuthorization(req2, PRIVATE_KEY1) - const res2 = await sendLegacyPnpSignRequest(req2, authorization2, app) - expect(res2.status).toBe(200) - const unblindedSig1 = threshold_bls.unblind( - Buffer.from(res1.body.signature, 'base64'), - blindedMsgResult.blindingFactor - ) - const unblindedSig2 = threshold_bls.unblind( - Buffer.from(res2.body.signature, 'base64'), - blindedMsgResult2.blindingFactor - ) - expect(Buffer.from(unblindedSig1).toString('base64')).toEqual(expectedUnblindedSig) - expect(unblindedSig1).toEqual(unblindedSig2) - }) - - it('Should respond with 400 on missing request fields', async () => { - // @ts-ignore Intentionally deleting required field - delete req.account - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendLegacyPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on unsupported key version', async () => { - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendLegacyPnpSignRequest(req, authorization, app, '4') - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_KEY_VERSION_REQUEST, - }) - }) - - it('Should respond with 400 on request with invalid identifier', async () => { - // Ensure that this gets passed through the combiner to the signer - req.hashedPhoneNumber = '+1234567890' - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendLegacyPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 401 on failed WALLET_KEY auth', async () => { - req.account = mockAccount - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendLegacyPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 401 on failed DEK auth', async () => { - req.account = mockAccount - req.authenticationMethod = AuthenticationMethod.ENCRYPTION_KEY - const differentPk = '0x00000000000000000000000000000000000000000000000000000000ddddbbbb' - const authorization = getPnpRequestAuthorization(req, differentPk) - const res = await sendLegacyPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 403 on out of quota', async () => { - prepMocks(false) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendLegacyPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(403) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.EXCEEDED_QUOTA, - }) - }) - - it('Should respond with 503 on disabled api', async () => { - const configWithApiDisabled: typeof combinerConfig = JSON.parse( - JSON.stringify(combinerConfig) - ) - configWithApiDisabled.phoneNumberPrivacy.enabled = false - const appWithApiDisabled = startCombiner(configWithApiDisabled, mockKit) - - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendLegacyPnpSignRequest(req, authorization, appWithApiDisabled) - - expect(res.status).toBe(503) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.API_UNAVAILABLE, - }) - }) - - describe('functionality in case of errors', () => { - it('Should respond with 200 on failure to fetch DEK when shouldFailOpen is true', async () => { - mockGetDataEncryptionKey.mockImplementation(() => { - throw new Error() - }) - - // Would fail authentication if getDataEncryptionKey succeeded - const differentPk = '0x00000000000000000000000000000000000000000000000000000000ddddbbbb' - req.authenticationMethod = AuthenticationMethod.ENCRYPTION_KEY - const authorization = getPnpRequestAuthorization(req, differentPk) - - const combinerConfigWithFailOpenEnabled: typeof combinerConfig = JSON.parse( - JSON.stringify(combinerConfig) - ) - combinerConfigWithFailOpenEnabled.phoneNumberPrivacy.shouldFailOpen = true - const appWithFailOpenEnabled = startCombiner(combinerConfigWithFailOpenEnabled, mockKit) - const res = await sendLegacyPnpSignRequest(req, authorization, appWithFailOpenEnabled) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - const unblindedSig = threshold_bls.unblind( - Buffer.from(res.body.signature, 'base64'), - blindedMsgResult.blindingFactor - ) - expect(Buffer.from(unblindedSig).toString('base64')).toEqual(expectedUnblindedSig) - }) - }) - }) - - // For testing combiner code paths when signers do not behave as expected - describe('when signers are not operating correctly', () => { - let authorization: string - - beforeEach(() => { - authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - }) - - describe('when 2/3 signers return correct signatures', () => { - beforeEach(async () => { - const badBlsShare1 = - '000000002e50aa714ef6b865b5de89c56969ef9f8f27b6b0a6d157c9cc01c574ac9df604' - const badKeyProvider1 = new MockKeyProvider( - new Map([[`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-1`, badBlsShare1]]) - ) - - signer1 = startSigner(signerConfig, signerDB1, badKeyProvider1, mockKit).listen(3001) - signer2 = startSigner(signerConfig, signerDB2, keyProvider2, mockKit).listen(3002) - signer3 = startSigner(signerConfig, signerDB3, keyProvider3, mockKit).listen(3003) - }) - - it('Should respond with 200 on valid request', async () => { - const res = await sendLegacyPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - const unblindedSig = threshold_bls.unblind( - Buffer.from(res.body.signature, 'base64'), - blindedMsgResult.blindingFactor - ) - expect(Buffer.from(unblindedSig).toString('base64')).toEqual(expectedUnblindedSig) - }) - }) - - describe('when 1/3 signers return correct signatures', () => { - beforeEach(async () => { - const badBlsShare1 = - '000000002e50aa714ef6b865b5de89c56969ef9f8f27b6b0a6d157c9cc01c574ac9df604' - const badBlsShare2 = - '01000000b8f0ef841dcf8d7bd1da5e8025e47d729eb67f513335784183b8fa227a0b9a0b' - - const badKeyProvider1 = new MockKeyProvider( - new Map([[`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-1`, badBlsShare1]]) - ) - const badKeyProvider2 = new MockKeyProvider( - new Map([[`${DefaultKeyName.PHONE_NUMBER_PRIVACY}-1`, badBlsShare2]]) - ) - - signer1 = startSigner(signerConfig, signerDB1, keyProvider1, mockKit).listen(3001) - signer2 = startSigner(signerConfig, signerDB2, badKeyProvider1, mockKit).listen(3002) - signer3 = startSigner(signerConfig, signerDB3, badKeyProvider2, mockKit).listen(3003) - }) - - it('Should respond with 500 even if request is valid', async () => { - const res = await sendLegacyPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.NOT_ENOUGH_PARTIAL_SIGNATURES, - }) - }) - }) - - describe('when 2/3 of signers are disabled', () => { - beforeEach(async () => { - const configWithApiDisabled: SignerConfig = JSON.parse(JSON.stringify(signerConfig)) - configWithApiDisabled.api.legacyPhoneNumberPrivacy.enabled = false - signer1 = startSigner(signerConfig, signerDB1, keyProvider1, mockKit).listen(3001) - signer2 = startSigner(configWithApiDisabled, signerDB2, keyProvider2, mockKit).listen(3002) - signer3 = startSigner(configWithApiDisabled, signerDB3, keyProvider3, mockKit).listen(3003) - }) - - it('Should fail to reach threshold of signers on valid request', async () => { - const res = await sendLegacyPnpSignRequest(req, authorization, app) - - expect(res.status).toBe(503) // majority error code in this case - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.NOT_ENOUGH_PARTIAL_SIGNATURES, - }) - }) - }) - - describe('when 1/3 of signers are disabled', () => { - beforeEach(async () => { - const configWithApiDisabled: SignerConfig = JSON.parse(JSON.stringify(signerConfig)) - configWithApiDisabled.api.legacyPhoneNumberPrivacy.enabled = false - signer1 = startSigner(signerConfig, signerDB1, keyProvider1, mockKit).listen(3001) - signer2 = startSigner(signerConfig, signerDB2, keyProvider2, mockKit).listen(3002) - signer3 = startSigner(configWithApiDisabled, signerDB3, keyProvider3, mockKit).listen(3003) - }) - - it('Should respond with 200 on valid request', async () => { - const res = await sendLegacyPnpSignRequest(req, authorization, app) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - }) - }) - - describe('when signers timeout', () => { - beforeEach(async () => { - const testTimeoutMS = 0 - - const configWithShortTimeout: SignerConfig = JSON.parse(JSON.stringify(signerConfig)) - configWithShortTimeout.timeout = testTimeoutMS - // Test this with all signers timing out to decrease possibility of race conditions - signer1 = startSigner(configWithShortTimeout, signerDB1, keyProvider1, mockKit).listen(3001) - signer2 = startSigner(configWithShortTimeout, signerDB2, keyProvider2, mockKit).listen(3002) - signer3 = startSigner(configWithShortTimeout, signerDB3, keyProvider3, mockKit).listen(3003) - }) - it('Should fail to reach threshold of signers on valid request', async () => { - const res = await sendLegacyPnpSignRequest(req, authorization, app) - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.NOT_ENOUGH_PARTIAL_SIGNATURES, - }) - }) - }) - }) -}) diff --git a/packages/phone-number-privacy/combiner/test/integration/pnp.test.ts b/packages/phone-number-privacy/combiner/test/integration/pnp.test.ts index fcea12b1827..473938ab77c 100644 --- a/packages/phone-number-privacy/combiner/test/integration/pnp.test.ts +++ b/packages/phone-number-privacy/combiner/test/integration/pnp.test.ts @@ -102,10 +102,6 @@ const signerConfig: SignerConfig = { enabled: true, shouldFailOpen: true, }, - legacyPhoneNumberPrivacy: { - enabled: false, - shouldFailOpen: true, - }, }, attestations: { numberAttestationsRequired: 3, diff --git a/packages/phone-number-privacy/common/src/interfaces/endpoints.ts b/packages/phone-number-privacy/common/src/interfaces/endpoints.ts index d7c7d998e0d..060853c5423 100644 --- a/packages/phone-number-privacy/common/src/interfaces/endpoints.ts +++ b/packages/phone-number-privacy/common/src/interfaces/endpoints.ts @@ -4,8 +4,6 @@ export enum SignerEndpointCommon { } export enum SignerEndpointPNP { - LEGACY_PNP_SIGN = '/getBlindedMessagePartialSig', - LEGACY_PNP_QUOTA = '/getQuota', PNP_QUOTA = '/quotaStatus', PNP_SIGN = '/sign', } @@ -15,7 +13,6 @@ export enum CombinerEndpointCommon { } export enum CombinerEndpointPNP { - LEGACY_PNP_SIGN = '/getBlindedMessageSig', PNP_QUOTA = '/quotaStatus', PNP_SIGN = '/sign', STATUS = '/status', @@ -52,8 +49,6 @@ export function getSignerEndpoint(endpoint: CombinerEndpoint): SignerEndpoint { return SignerEndpoint.PNP_QUOTA case CombinerEndpoint.PNP_SIGN: return SignerEndpoint.PNP_SIGN - case CombinerEndpoint.LEGACY_PNP_SIGN: - return SignerEndpoint.LEGACY_PNP_SIGN default: throw new Error(`No corresponding signer endpoint exists for combiner endpoint ${endpoint}`) } @@ -71,8 +66,6 @@ export function getCombinerEndpoint(endpoint: SignerEndpoint): CombinerEndpoint return CombinerEndpoint.PNP_QUOTA case SignerEndpoint.PNP_SIGN: return CombinerEndpoint.PNP_SIGN - case SignerEndpoint.LEGACY_PNP_SIGN: - return CombinerEndpoint.LEGACY_PNP_SIGN default: throw new Error(`No corresponding combiner endpoint exists for signer endpoint ${endpoint}`) } diff --git a/packages/phone-number-privacy/common/src/interfaces/requests.ts b/packages/phone-number-privacy/common/src/interfaces/requests.ts index 164a4e97ec6..d76a125323c 100644 --- a/packages/phone-number-privacy/common/src/interfaces/requests.ts +++ b/packages/phone-number-privacy/common/src/interfaces/requests.ts @@ -42,12 +42,6 @@ export interface SignMessageRequest { version?: string } -/** previously known as GetBlindedMessageSigRequest */ -export interface LegacySignMessageRequest extends SignMessageRequest { - /** Optional on-chain identifier. Unlocks additional quota if the account is verified as an owner of the identifier. */ - hashedPhoneNumber?: string -} - export const SignMessageRequestSchema: t.Type = t.intersection([ t.type({ account: t.string, @@ -60,13 +54,6 @@ export const SignMessageRequestSchema: t.Type = t.intersecti }), ]) -export const LegacySignMessageRequestSchema: t.Type = t.intersection([ - SignMessageRequestSchema, - t.partial({ - hashedPhoneNumber: t.union([t.string, t.undefined]), - }), -]) - export interface PnpQuotaRequest { account: string /** Authentication method to use for verifying the signature in the Authorization header */ @@ -76,13 +63,6 @@ export interface PnpQuotaRequest { /** Client-specified version string */ version?: string } -export interface LegacyPnpQuotaRequest extends PnpQuotaRequest { - /** User's ODIS generated on-chain identifier */ - hashedPhoneNumber?: string -} - -// Backwards compatibility -export declare type GetQuotaRequest = LegacyPnpQuotaRequest export const PnpQuotaRequestSchema: t.Type = t.intersection([ t.type({ @@ -95,18 +75,7 @@ export const PnpQuotaRequestSchema: t.Type = t.intersection([ }), ]) -export const LegacyPnpQuotaRequestSchema: t.Type = t.intersection([ - PnpQuotaRequestSchema, - t.partial({ - hashedPhoneNumber: t.union([t.string, t.undefined]), - }), -]) - -export type PhoneNumberPrivacyRequest = - | SignMessageRequest - | LegacySignMessageRequest - | PnpQuotaRequest - | LegacyPnpQuotaRequest +export type PhoneNumberPrivacyRequest = SignMessageRequest | PnpQuotaRequest export enum DomainRequestTypeTag { SIGN = 'DomainRestrictedSignatureRequest', @@ -525,13 +494,12 @@ export type SignMessageRequestHeader = KeyVersionHeader & PnpAuthHeader export type PnpQuotaRequestHeader = PnpAuthHeader -export type PhoneNumberPrivacyRequestHeader = R extends - | SignMessageRequest - | LegacySignMessageRequest - ? SignMessageRequestHeader - : never | R extends PnpQuotaRequest - ? PnpQuotaRequestHeader - : never +export type PhoneNumberPrivacyRequestHeader = + R extends SignMessageRequest + ? SignMessageRequestHeader + : never | R extends PnpQuotaRequest + ? PnpQuotaRequestHeader + : never export type OdisRequestHeader = R extends DomainRequest ? DomainRequestHeader diff --git a/packages/phone-number-privacy/common/src/interfaces/responses.ts b/packages/phone-number-privacy/common/src/interfaces/responses.ts index 70e60ad867d..181d6740f60 100644 --- a/packages/phone-number-privacy/common/src/interfaces/responses.ts +++ b/packages/phone-number-privacy/common/src/interfaces/responses.ts @@ -4,8 +4,6 @@ import { DomainQuotaStatusRequest, DomainRequest, DomainRestrictedSignatureRequest, - LegacyPnpQuotaRequest, - LegacySignMessageRequest, OdisRequest, PhoneNumberPrivacyRequest, PnpQuotaRequest, @@ -116,8 +114,8 @@ export const PnpQuotaResponseSchema: t.Type = t.union([ export type PhoneNumberPrivacyResponse< R extends PhoneNumberPrivacyRequest = PhoneNumberPrivacyRequest > = - | R extends SignMessageRequest | LegacySignMessageRequest ? SignMessageResponse : never - | R extends PnpQuotaRequest | LegacyPnpQuotaRequest ? PnpQuotaResponse : never + | R extends SignMessageRequest ? SignMessageResponse : never + | R extends PnpQuotaRequest ? PnpQuotaResponse : never // Domains diff --git a/packages/phone-number-privacy/common/src/test/utils.ts b/packages/phone-number-privacy/common/src/test/utils.ts index a2e57365ec3..28ace147687 100644 --- a/packages/phone-number-privacy/common/src/test/utils.ts +++ b/packages/phone-number-privacy/common/src/test/utils.ts @@ -4,8 +4,6 @@ import BigNumber from 'bignumber.js' import Web3 from 'web3' import { AuthenticationMethod, - LegacyPnpQuotaRequest, - LegacySignMessageRequest, PhoneNumberPrivacyRequest, PnpQuotaRequest, SignMessageRequest, @@ -129,33 +127,6 @@ export function getPnpQuotaRequest( sessionID: genSessionID(), } } -export function getLegacyPnpQuotaRequest( - account: string, - authenticationMethod?: string, - hashedPhoneNumber?: string -): LegacyPnpQuotaRequest { - return { - account, - authenticationMethod, - hashedPhoneNumber, - sessionID: genSessionID(), - } -} - -export function getLegacyPnpSignRequest( - account: string, - blindedQueryPhoneNumber: string, - authenticationMethod?: string, - hashedPhoneNumber?: string -): LegacySignMessageRequest { - return { - account, - blindedQueryPhoneNumber, - authenticationMethod, - hashedPhoneNumber, - sessionID: genSessionID(), - } -} export function getPnpSignRequest( account: string, diff --git a/packages/phone-number-privacy/common/src/utils/authentication.ts b/packages/phone-number-privacy/common/src/utils/authentication.ts index 366176a0a16..7e223f47211 100644 --- a/packages/phone-number-privacy/common/src/utils/authentication.ts +++ b/packages/phone-number-privacy/common/src/utils/authentication.ts @@ -138,7 +138,7 @@ export async function getDataEncryptionKey( address: string, contractKit: ContractKit, logger: Logger, - timeoutMs: number, + fullNodeTimeoutMs: number, fullNodeRetryCount: number, fullNodeRetryDelayMs: number ): Promise { @@ -152,7 +152,7 @@ export async function getDataEncryptionKey( [], fullNodeRetryDelayMs, 1.5, - timeoutMs + fullNodeTimeoutMs ) return res } catch (error) { diff --git a/packages/phone-number-privacy/common/src/utils/input-validation.ts b/packages/phone-number-privacy/common/src/utils/input-validation.ts index 6ce052d0304..514d6dd58ff 100644 --- a/packages/phone-number-privacy/common/src/utils/input-validation.ts +++ b/packages/phone-number-privacy/common/src/utils/input-validation.ts @@ -1,18 +1,12 @@ -import { isValidAddress, trimLeading0x } from '@celo/utils/lib/address' +import { isValidAddress } from '@celo/utils/lib/address' import isBase64 from 'is-base64' -import { - GetQuotaRequest, - LegacySignMessageRequest, - PnpQuotaRequest, - SignMessageRequest, -} from '../interfaces' +import { PnpQuotaRequest, SignMessageRequest } from '../interfaces' import { REASONABLE_BODY_CHAR_LIMIT } from './constants' export function hasValidAccountParam(requestBody: { account: string }): boolean { return !!requestBody.account && isValidAddress(requestBody.account) } -// Legacy message signing & quota requests extend the new types export function isBodyReasonablySized(requestBody: SignMessageRequest | PnpQuotaRequest): boolean { return JSON.stringify(requestBody).length <= REASONABLE_BODY_CHAR_LIMIT } @@ -24,15 +18,3 @@ export function hasValidBlindedPhoneNumberParam(requestBody: SignMessageRequest) isBase64(requestBody.blindedQueryPhoneNumber) ) } - -export function identifierIsValidIfExists( - requestBody: GetQuotaRequest | LegacySignMessageRequest -): boolean { - return !requestBody.hashedPhoneNumber || isByte32(requestBody.hashedPhoneNumber) -} - -const hexString = new RegExp(/[0-9A-Fa-f]{32}/, 'i') - -function isByte32(hashedData: string): boolean { - return hexString.test(trimLeading0x(hashedData)) -} diff --git a/packages/phone-number-privacy/common/test/utils/input-validation.test.ts b/packages/phone-number-privacy/common/test/utils/input-validation.test.ts index 1ec3ba1fd53..f697dfe7dba 100644 --- a/packages/phone-number-privacy/common/test/utils/input-validation.test.ts +++ b/packages/phone-number-privacy/common/test/utils/input-validation.test.ts @@ -1,32 +1,6 @@ -import { GetQuotaRequest, LegacySignMessageRequest } from '../../src/interfaces' -import { REASONABLE_BODY_CHAR_LIMIT } from '../../src/utils/constants' import * as utils from '../../src/utils/input-validation' describe('Input Validation test suite', () => { - describe('isBodyReasonablySized utility', () => { - it('Should return true with small body', () => { - const sampleData: GetQuotaRequest = { - account: 'account', - hashedPhoneNumber: 'x'.repeat(10), - } - - const result = utils.isBodyReasonablySized(sampleData) - - expect(result).toBeTruthy() - }) - - it('Should return false with giant body', () => { - const sampleData: GetQuotaRequest = { - account: 'account', - hashedPhoneNumber: 'x'.repeat(REASONABLE_BODY_CHAR_LIMIT * 2), - } - - const result = utils.isBodyReasonablySized(sampleData) - - expect(result).toBeFalsy() - }) - }) - describe('hasValidAccountParam utility', () => { it('Should return true for proper address', () => { const sampleData = { @@ -58,54 +32,4 @@ describe('Input Validation test suite', () => { expect(result).toBeFalsy() }) }) - - describe('hasValidBlindedPhoneNumberParam utility', () => { - it('Should return true for blinded query', () => { - const sampleData: LegacySignMessageRequest = { - blindedQueryPhoneNumber: Buffer.from( - '1912fee45d61c87cc5ea59dae31190ff1912fee45d61c8' - ).toString('base64'), - account: 'acc', - } - - const result = utils.hasValidBlindedPhoneNumberParam(sampleData) - - expect(result).toBeTruthy() - }) - - it('Should return false for not base64 query', () => { - const sampleData: LegacySignMessageRequest = { - blindedQueryPhoneNumber: Buffer.from( - 'JanAdamMickiewicz1234!@JanAdamMickiewicz1234!@123412345678901234' - ).toString('utf-8'), - account: 'acc', - } - - const result = utils.hasValidBlindedPhoneNumberParam(sampleData) - - expect(result).toBeFalsy() - }) - - it('Should return false for too short blinded query', () => { - const sampleData: LegacySignMessageRequest = { - blindedQueryPhoneNumber: Buffer.from('1912fee45d61c87cc5e').toString('base64'), - account: 'acc', - } - - const result = utils.hasValidBlindedPhoneNumberParam(sampleData) - - expect(result).toBeFalsy() - }) - - it('Should return false for missing param in query', () => { - const sampleData: LegacySignMessageRequest = { - blindedQueryPhoneNumber: '', - account: 'acc', - } - - const result = utils.hasValidBlindedPhoneNumberParam(sampleData) - - expect(result).toBeFalsy() - }) - }) }) diff --git a/packages/phone-number-privacy/monitor/src/index.ts b/packages/phone-number-privacy/monitor/src/index.ts index 3ee9bb4567d..ead157604f7 100644 --- a/packages/phone-number-privacy/monitor/src/index.ts +++ b/packages/phone-number-privacy/monitor/src/index.ts @@ -8,14 +8,6 @@ if (!contextName || !blockchainProvider) { throw new Error('blockchain provider and context name must be set in function config') } -// New functions do not overwrite ODIS 1.0 monitor function. -export const odisMonitorScheduleFunctionLegacyPNP = functions - .region('us-central1') - .pubsub.schedule('every 5 minutes') - .onRun(async () => - testPNPSignQuery(blockchainProvider, contextName, CombinerEndpointPNP.LEGACY_PNP_SIGN) - ) - export const odisMonitorScheduleFunctionPNP = functions .region('us-central1') .pubsub.schedule('every 5 minutes') diff --git a/packages/phone-number-privacy/monitor/src/query.ts b/packages/phone-number-privacy/monitor/src/query.ts index 646d5d199e1..b069879b162 100644 --- a/packages/phone-number-privacy/monitor/src/query.ts +++ b/packages/phone-number-privacy/monitor/src/query.ts @@ -13,7 +13,7 @@ import { OdisAPI, OdisContextName, } from '@celo/identity/lib/odis/query' -import { CombinerEndpointPNP, fetchEnv } from '@celo/phone-number-privacy-common' +import { fetchEnv } from '@celo/phone-number-privacy-common' import { genSessionID } from '@celo/phone-number-privacy-common/lib/utils/logger' import { normalizeAddressWith0x, privateKeyToAddress } from '@celo/utils/lib/address' import { defined } from '@celo/utils/lib/sign-typed-data-utils' @@ -29,7 +29,6 @@ const newPrivateKey = async () => { export const queryOdisForSalt = async ( blockchainProvider: string, contextName: OdisContextName, - endpoint: CombinerEndpointPNP.LEGACY_PNP_SIGN | CombinerEndpointPNP.PNP_SIGN, timeoutMs: number = 10000 ) => { console.log(`contextName: ${contextName}`) // tslint:disable-line:no-console @@ -64,7 +63,6 @@ export const queryOdisForSalt = async ( undefined, genSessionID(), undefined, - endpoint, abortController ) clearTimeout(timeout) diff --git a/packages/phone-number-privacy/monitor/src/test.ts b/packages/phone-number-privacy/monitor/src/test.ts index 0e10c2c243f..33cdebd8662 100644 --- a/packages/phone-number-privacy/monitor/src/test.ts +++ b/packages/phone-number-privacy/monitor/src/test.ts @@ -12,7 +12,7 @@ const logger = rootLogger('odis-monitor') export async function testPNPSignQuery( blockchainProvider: string, contextName: OdisContextName, - endpoint: CombinerEndpointPNP.LEGACY_PNP_SIGN | CombinerEndpointPNP.PNP_SIGN, + endpoint: CombinerEndpointPNP.PNP_SIGN, timeoutMs?: number ) { logger.info(`Performing test PNP query for ${endpoint}`) @@ -20,7 +20,6 @@ export async function testPNPSignQuery( const odisResponse: IdentifierHashDetails = await queryOdisForSalt( blockchainProvider, contextName, - endpoint, timeoutMs ) logger.info({ odisResponse }, 'ODIS salt request successful. System is healthy.') @@ -81,7 +80,6 @@ export async function serialLoadTest( blockchainProvider: string, contextName: OdisContextName, endpoint: - | CombinerEndpointPNP.LEGACY_PNP_SIGN | CombinerEndpointPNP.PNP_QUOTA | CombinerEndpointPNP.PNP_SIGN = CombinerEndpointPNP.PNP_SIGN, timeoutMs?: number @@ -89,7 +87,6 @@ export async function serialLoadTest( for (let i = 0; i < n; i++) { try { switch (endpoint) { - case CombinerEndpointPNP.LEGACY_PNP_SIGN: case CombinerEndpointPNP.PNP_SIGN: await testPNPSignQuery(blockchainProvider, contextName, endpoint, timeoutMs) break @@ -105,7 +102,6 @@ export async function concurrentLoadTest( blockchainProvider: string, contextName: OdisContextName, endpoint: - | CombinerEndpointPNP.LEGACY_PNP_SIGN | CombinerEndpointPNP.PNP_QUOTA | CombinerEndpointPNP.PNP_SIGN = CombinerEndpointPNP.PNP_SIGN, timeoutMs?: number @@ -120,7 +116,6 @@ export async function concurrentLoadTest( while (true) { try { switch (endpoint) { - case CombinerEndpointPNP.LEGACY_PNP_SIGN: case CombinerEndpointPNP.PNP_SIGN: await testPNPSignQuery(blockchainProvider, contextName, endpoint, timeoutMs) break diff --git a/packages/phone-number-privacy/signer/jest.config.js b/packages/phone-number-privacy/signer/jest.config.js index 511496b9c32..e2a0bf634a6 100644 --- a/packages/phone-number-privacy/signer/jest.config.js +++ b/packages/phone-number-privacy/signer/jest.config.js @@ -5,7 +5,7 @@ module.exports = { collectCoverageFrom: ['./src/**'], coverageThreshold: { global: { - lines: 80, + lines: 76, }, }, } diff --git a/packages/phone-number-privacy/signer/src/common/database/models/account.ts b/packages/phone-number-privacy/signer/src/common/database/models/account.ts index aab8bd3444b..e3afb6aa911 100644 --- a/packages/phone-number-privacy/signer/src/common/database/models/account.ts +++ b/packages/phone-number-privacy/signer/src/common/database/models/account.ts @@ -1,6 +1,6 @@ export enum ACCOUNTS_TABLE { ONCHAIN = 'accountsOnChain', - LEGACY = 'accounts', + LEGACY = 'accounts', // TODO figure out right way to drop this table now that it's no longer in use } export enum ACCOUNTS_COLUMNS { diff --git a/packages/phone-number-privacy/signer/src/config.ts b/packages/phone-number-privacy/signer/src/config.ts index 57b9da654ac..7c1a1623c4c 100644 --- a/packages/phone-number-privacy/signer/src/config.ts +++ b/packages/phone-number-privacy/signer/src/config.ts @@ -55,10 +55,6 @@ export interface SignerConfig { enabled: boolean shouldFailOpen: boolean } - legacyPhoneNumberPrivacy: { - enabled: boolean - shouldFailOpen: boolean - } } attestations: { numberAttestationsRequired: number @@ -137,10 +133,6 @@ export const config: SignerConfig = { enabled: toBool(env.PHONE_NUMBER_PRIVACY_API_ENABLED, false), shouldFailOpen: toBool(env.FULL_NODE_ERRORS_SHOULD_FAIL_OPEN, false), }, - legacyPhoneNumberPrivacy: { - enabled: toBool(env.LEGACY_PHONE_NUMBER_PRIVACY_API_ENABLED, false), - shouldFailOpen: toBool(env.LEGACY_FULL_NODE_ERRORS_SHOULD_FAIL_OPEN, false), - }, }, attestations: { numberAttestationsRequired: Number(env.ATTESTATIONS_NUMBER_ATTESTATIONS_REQUIRED ?? 3), diff --git a/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/action.ts b/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/action.ts index cb114f53858..f0d0b57af4b 100644 --- a/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/action.ts +++ b/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/action.ts @@ -1,27 +1,19 @@ import { timeout } from '@celo/base' -import { - ErrorMessage, - LegacyPnpQuotaRequest, - PnpQuotaRequest, -} from '@celo/phone-number-privacy-common' +import { ErrorMessage, PnpQuotaRequest } from '@celo/phone-number-privacy-common' import { Action } from '../../../common/action' import { SignerConfig } from '../../../config' import { PnpQuotaService } from '../../services/quota' import { PnpSession } from '../../session' import { PnpQuotaIO } from './io' -import { LegacyPnpQuotaIO } from './io.legacy' -export class PnpQuotaAction implements Action { +export class PnpQuotaAction implements Action { constructor( readonly config: SignerConfig, readonly quota: PnpQuotaService, - readonly io: PnpQuotaIO | LegacyPnpQuotaIO + readonly io: PnpQuotaIO ) {} - public async perform( - session: PnpSession, - timeoutError: symbol - ): Promise { + public async perform(session: PnpSession, timeoutError: symbol): Promise { const quotaStatus = await timeout( () => this.quota.getQuotaStatus(session), [], diff --git a/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/io.legacy.ts b/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/io.legacy.ts deleted file mode 100644 index b35c5e26626..00000000000 --- a/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/io.legacy.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { ContractKit } from '@celo/contractkit' -import { - authenticateUser, - ErrorType, - hasValidAccountParam, - identifierIsValidIfExists, - isBodyReasonablySized, - LegacyPnpQuotaRequest, - LegacyPnpQuotaRequestSchema, - PnpQuotaResponse, - PnpQuotaResponseFailure, - PnpQuotaResponseSuccess, - PnpQuotaStatus, - send, - SignerEndpoint, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { Request, Response } from 'express' -import { IO } from '../../../common/io' -import { Counters } from '../../../common/metrics' -import { getSignerVersion } from '../../../config' -import { PnpSession } from '../../session' - -export class LegacyPnpQuotaIO extends IO { - readonly endpoint = SignerEndpoint.LEGACY_PNP_QUOTA - - constructor( - readonly enabled: boolean, - readonly shouldFailOpen: boolean, - readonly timeoutMs: number, - readonly fullNodeRetryCount: number, - readonly fullNodeRetryDelayMs: number, - readonly kit: ContractKit - ) { - super(enabled) - } - - async init( - request: Request<{}, {}, unknown>, - response: Response - ): Promise | null> { - const warnings: ErrorType[] = [] - if (!super.inputChecks(request, response)) { - return null - } - if (!(await this.authenticate(request, warnings, response.locals.logger))) { - this.sendFailure(WarningMessage.UNAUTHENTICATED_USER, 401, response) - return null - } - const session = new PnpSession(request, response) - session.errors.push(...warnings) - return session - } - - validate(request: Request<{}, {}, unknown>): request is Request<{}, {}, LegacyPnpQuotaRequest> { - return ( - LegacyPnpQuotaRequestSchema.is(request.body) && - hasValidAccountParam(request.body) && - identifierIsValidIfExists(request.body) && - isBodyReasonablySized(request.body) - ) - } - - async authenticate( - request: Request<{}, {}, LegacyPnpQuotaRequest>, - warnings: ErrorType[], - logger: Logger - ): Promise { - return authenticateUser( - request, - this.kit, - logger, - this.shouldFailOpen, - warnings, - this.timeoutMs, - this.fullNodeRetryCount, - this.fullNodeRetryDelayMs - ) - } - - sendSuccess( - status: number, - response: Response, - quotaStatus: PnpQuotaStatus, - warnings: string[] - ) { - send( - response, - { - success: true, - version: getSignerVersion(), - ...quotaStatus, - warnings, - }, - status, - response.locals.logger - ) - Counters.responses.labels(this.endpoint, status.toString()).inc() - } - - sendFailure(error: ErrorType, status: number, response: Response) { - send( - response, - { - success: false, - version: getSignerVersion(), - error, - }, - status, - response.locals.logger - ) - Counters.responses.labels(this.endpoint, status.toString()).inc() - } -} diff --git a/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/io.ts b/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/io.ts index 99900a4a70f..3495cdcf22b 100644 --- a/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/io.ts +++ b/packages/phone-number-privacy/signer/src/pnp/endpoints/quota/io.ts @@ -27,7 +27,7 @@ export class PnpQuotaIO extends IO { constructor( readonly enabled: boolean, readonly shouldFailOpen: boolean, - readonly timeoutMs: number, + readonly fullNodeTimeoutMs: number, readonly fullNodeRetryCount: number, readonly fullNodeRetryDelayMs: number, readonly kit: ContractKit @@ -71,7 +71,7 @@ export class PnpQuotaIO extends IO { logger, this.shouldFailOpen, warnings, - this.timeoutMs, + this.fullNodeTimeoutMs, this.fullNodeRetryCount, this.fullNodeRetryDelayMs ) diff --git a/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/action.legacy.ts b/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/action.legacy.ts deleted file mode 100644 index 642ad9781cd..00000000000 --- a/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/action.legacy.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Knex } from 'knex' -import { REQUESTS_TABLE } from '../../../common/database/models/request' -import { KeyProvider } from '../../../common/key-management/key-provider-base' -import { SignerConfig } from '../../../config' -import { PnpQuotaService } from '../../services/quota' -import { PnpSignAction } from './action' -import { LegacyPnpSignIO } from './io.legacy' - -export class LegacyPnpSignAction extends PnpSignAction { - protected readonly requestsTable = REQUESTS_TABLE.LEGACY - - constructor( - readonly db: Knex, - readonly config: SignerConfig, - readonly quota: PnpQuotaService, - readonly keyProvider: KeyProvider, - readonly io: LegacyPnpSignIO - ) { - super(db, config, quota, keyProvider, io) - } -} diff --git a/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/action.onchain.ts b/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/action.onchain.ts deleted file mode 100644 index 5cd93b41962..00000000000 --- a/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/action.onchain.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Knex } from 'knex' -import { REQUESTS_TABLE } from '../../../common/database/models/request' -import { KeyProvider } from '../../../common/key-management/key-provider-base' -import { SignerConfig } from '../../../config' -import { PnpQuotaService } from '../../services/quota' -import { PnpSignAction } from './action' -import { PnpSignIO } from './io' - -export class OnChainPnpSignAction extends PnpSignAction { - protected readonly requestsTable = REQUESTS_TABLE.ONCHAIN - - constructor( - readonly db: Knex, - readonly config: SignerConfig, - readonly quota: PnpQuotaService, - readonly keyProvider: KeyProvider, - readonly io: PnpSignIO - ) { - super(db, config, quota, keyProvider, io) - } -} diff --git a/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/action.ts b/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/action.ts index af636279d80..19982f13306 100644 --- a/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/action.ts +++ b/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/action.ts @@ -2,7 +2,6 @@ import { timeout } from '@celo/base' import { ErrorMessage, getRequestKeyVersion, - LegacySignMessageRequest, SignMessageRequest, WarningMessage, } from '@celo/phone-number-privacy-common' @@ -17,23 +16,20 @@ import { SignerConfig } from '../../../config' import { PnpQuotaService } from '../../services/quota' import { PnpSession } from '../../session' import { PnpSignIO } from './io' -import { LegacyPnpSignIO } from './io.legacy' -export abstract class PnpSignAction - implements Action -{ - protected abstract readonly requestsTable: REQUESTS_TABLE +export class PnpSignAction implements Action { + protected readonly requestsTable: REQUESTS_TABLE = REQUESTS_TABLE.ONCHAIN constructor( readonly db: Knex, readonly config: SignerConfig, readonly quota: PnpQuotaService, readonly keyProvider: KeyProvider, - readonly io: PnpSignIO | LegacyPnpSignIO + readonly io: PnpSignIO ) {} public async perform( - session: PnpSession, + session: PnpSession, timeoutError: symbol ): Promise { // Compute quota lookup, update, and signing within transaction @@ -145,7 +141,7 @@ export abstract class PnpSignAction private async sign( blindedMessage: string, key: Key, - session: Session + session: Session ): Promise { let privateKey: string try { diff --git a/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/io.legacy.ts b/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/io.legacy.ts deleted file mode 100644 index 68a3920c381..00000000000 --- a/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/io.legacy.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { ContractKit } from '@celo/contractkit' -import { - authenticateUser, - ErrorType, - hasValidAccountParam, - hasValidBlindedPhoneNumberParam, - identifierIsValidIfExists, - isBodyReasonablySized, - KEY_VERSION_HEADER, - LegacySignMessageRequest, - LegacySignMessageRequestSchema, - PnpQuotaStatus, - requestHasValidKeyVersion, - send, - SignerEndpoint, - SignMessageResponse, - SignMessageResponseFailure, - SignMessageResponseSuccess, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { Request, Response } from 'express' -import { IO } from '../../../common/io' -import { Key } from '../../../common/key-management/key-provider-base' -import { Counters } from '../../../common/metrics' -import { getSignerVersion } from '../../../config' -import { PnpSession } from '../../session' - -export class LegacyPnpSignIO extends IO { - readonly endpoint = SignerEndpoint.LEGACY_PNP_SIGN - - constructor( - readonly enabled: boolean, - readonly shouldFailOpen: boolean, - readonly timeoutMs: number, - readonly fullNodeRetryCount: number, - readonly fullNodeRetryDelayMs: number, - readonly kit: ContractKit - ) { - super(enabled) - } - - async init( - request: Request<{}, {}, unknown>, - response: Response - ): Promise | null> { - const logger = response.locals.logger - const warnings: ErrorType[] = [] - if (!super.inputChecks(request, response)) { - return null - } - if (!requestHasValidKeyVersion(request, logger)) { - this.sendFailure(WarningMessage.INVALID_KEY_VERSION_REQUEST, 400, response) - return null - } - if (!(await this.authenticate(request, warnings, logger))) { - this.sendFailure(WarningMessage.UNAUTHENTICATED_USER, 401, response) - return null - } - const session = new PnpSession(request, response) - session.errors.push(...warnings) - return session - } - - validate( - request: Request<{}, {}, unknown> - ): request is Request<{}, {}, LegacySignMessageRequest> { - return ( - LegacySignMessageRequestSchema.is(request.body) && - hasValidAccountParam(request.body) && - hasValidBlindedPhoneNumberParam(request.body) && - identifierIsValidIfExists(request.body) && - isBodyReasonablySized(request.body) - ) - } - - async authenticate( - request: Request<{}, {}, LegacySignMessageRequest>, - warnings: ErrorType[], - logger: Logger - ): Promise { - return authenticateUser( - request, - this.kit, - logger, - this.shouldFailOpen, - warnings, - this.timeoutMs, - this.fullNodeRetryCount, - this.fullNodeRetryDelayMs - ) - } - - sendSuccess( - status: number, - response: Response, - key: Key, - signature: string, - quotaStatus: PnpQuotaStatus, - warnings: string[] - ) { - response.set(KEY_VERSION_HEADER, key.version.toString()) - send( - response, - { - success: true, - version: getSignerVersion(), - signature, - ...quotaStatus, - warnings, - }, - status, - response.locals.logger - ) - Counters.responses.labels(this.endpoint, status.toString()).inc() - } - - sendFailure( - error: string, - status: number, - response: Response, - quotaStatus?: PnpQuotaStatus - ) { - send( - response, - { - success: false, - version: getSignerVersion(), - error, - ...quotaStatus, - }, - status, - response.locals.logger - ) - Counters.responses.labels(this.endpoint, status.toString()).inc() - } -} diff --git a/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/io.ts b/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/io.ts index c8833344ee6..dfc4dfd3345 100644 --- a/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/io.ts +++ b/packages/phone-number-privacy/signer/src/pnp/endpoints/sign/io.ts @@ -31,7 +31,7 @@ export class PnpSignIO extends IO { constructor( readonly enabled: boolean, readonly shouldFailOpen: boolean, - readonly timeoutMs: number, + readonly fullNodeTimeoutMs: number, readonly fullNodeRetryCount: number, readonly fullNodeRetryDelayMs: number, readonly kit: ContractKit @@ -81,7 +81,7 @@ export class PnpSignIO extends IO { logger, this.shouldFailOpen, warnings, - this.timeoutMs, + this.fullNodeTimeoutMs, this.fullNodeRetryCount, this.fullNodeRetryDelayMs ) diff --git a/packages/phone-number-privacy/signer/src/pnp/services/quota.legacy.ts b/packages/phone-number-privacy/signer/src/pnp/services/quota.legacy.ts deleted file mode 100644 index 0900437ef04..00000000000 --- a/packages/phone-number-privacy/signer/src/pnp/services/quota.legacy.ts +++ /dev/null @@ -1,280 +0,0 @@ -import { NULL_ADDRESS } from '@celo/base' -import { StableToken } from '@celo/contractkit' -import { - ErrorMessage, - isVerified, - LegacyPnpQuotaRequest, - LegacySignMessageRequest, -} from '@celo/phone-number-privacy-common' -import Logger from 'bunyan' -import { ACCOUNTS_TABLE } from '../../common/database/models/account' -import { REQUESTS_TABLE } from '../../common/database/models/request' -import { Counters, Histograms, meter } from '../../common/metrics' -import { QuotaService } from '../../common/quota' -import { - getCeloBalance, - getStableTokenBalance, - getTransactionCount, - getWalletAddress, -} from '../../common/web3/contracts' -import { config } from '../../config' -import { PnpSession } from '../session' -import { PnpQuotaService } from './quota' - -export class LegacyPnpQuotaService - extends PnpQuotaService - implements QuotaService -{ - protected readonly requestsTable = REQUESTS_TABLE.LEGACY - protected readonly accountsTable = ACCOUNTS_TABLE.LEGACY - - protected async getWalletAddressAndIsVerified( - session: PnpSession - ): Promise<{ walletAddress: string; isAccountVerified: boolean }> { - const { account, hashedPhoneNumber } = session.request.body - const [walletAddressResult, isVerifiedResult] = await meter( - (_session: PnpSession) => - Promise.allSettled([ - getWalletAddress(this.kit, session.logger, account, session.request.url), - hashedPhoneNumber - ? isVerified(account, hashedPhoneNumber, this.kit, session.logger) - : Promise.resolve(false), - ]), - [session], - (err: any) => { - throw err - }, - Histograms.getRemainingQueryCountInstrumentation, - ['getWalletAddressAndIsVerified', session.request.url] - ) - let hadFullNodeError = false, - isAccountVerified = false, - walletAddress = NULL_ADDRESS - if (walletAddressResult.status === 'fulfilled') { - walletAddress = walletAddressResult.value - } else { - session.logger.error(walletAddressResult.reason) - hadFullNodeError = true - } - if (isVerifiedResult.status === 'fulfilled') { - isAccountVerified = isVerifiedResult.value - } else { - session.logger.error(isVerifiedResult.reason) - hadFullNodeError = true - } - if (hadFullNodeError) { - session.errors.push(ErrorMessage.FULL_NODE_ERROR) - } - - if (account.toLowerCase() === walletAddress.toLowerCase()) { - session.logger.debug('walletAddress is the same as accountAddress') - walletAddress = NULL_ADDRESS // So we don't double count quota - } - - return { isAccountVerified, walletAddress } - } - - protected async getBalances( - session: PnpSession, - ...addresses: string[] - ) { - const [cUSDAccountBalanceResult, cEURAccountBalanceResult, celoAccountBalanceResult] = - await meter( - (logger: Logger, ..._addresses: string[]) => - Promise.allSettled([ - getStableTokenBalance( - this.kit, - StableToken.cUSD, - logger, - session.request.url, - ..._addresses - ), - getStableTokenBalance( - this.kit, - StableToken.cEUR, - logger, - session.request.url, - ..._addresses - ), - getCeloBalance(this.kit, logger, session.request.url, ..._addresses), - ]), - [session.logger, ...addresses], - (err: any) => { - throw err - }, - Histograms.getRemainingQueryCountInstrumentation, - ['getBalances', session.request.url] - ) - - let hadFullNodeError = false - let cUSDAccountBalance, cEURAccountBalance, celoAccountBalance - if (cUSDAccountBalanceResult.status === 'fulfilled') { - cUSDAccountBalance = cUSDAccountBalanceResult.value - } else { - session.logger.error(cUSDAccountBalanceResult.reason) - hadFullNodeError = true - } - if (cEURAccountBalanceResult.status === 'fulfilled') { - cEURAccountBalance = cEURAccountBalanceResult.value - } else { - session.logger.error(cEURAccountBalanceResult.reason) - hadFullNodeError = true - } - if (celoAccountBalanceResult.status === 'fulfilled') { - celoAccountBalance = celoAccountBalanceResult.value - } else { - session.logger.error(celoAccountBalanceResult.reason) - hadFullNodeError = true - } - if (hadFullNodeError) { - session.errors.push(ErrorMessage.FULL_NODE_ERROR) - } - - return { cUSDAccountBalance, cEURAccountBalance, celoAccountBalance } - } - - /* - * Calculates how many queries the caller has unlocked based on the algorithm - * unverifiedQueryCount + verifiedQueryCount + (queryPerTransaction * transactionCount) - * If the caller is not verified, they must have a minimum balance to get the unverifiedQueryMax. - */ - protected async getTotalQuotaWithoutMeter( - session: PnpSession - ): Promise { - const { - unverifiedQueryMax, - additionalVerifiedQueryMax, - queryPerTransaction, - minDollarBalance, - minEuroBalance, - minCeloBalance, - } = config.quota - - const { account } = session.request.body - - const { walletAddress, isAccountVerified } = await this.getWalletAddressAndIsVerified(session) - - if (walletAddress !== NULL_ADDRESS) { - Counters.requestsWithWalletAddress.inc() - } - - const transactionCount = await getTransactionCount( - this.kit, - session.logger, - session.request.url, - account, - walletAddress - ) - session.logger.debug({ account, transactionCount }) - - if (isAccountVerified) { - Counters.requestsWithVerifiedAccount.inc() - session.logger.debug({ account }, 'Account is verified') - return this.calculateQuotaForVerifiedAccount( - account, - unverifiedQueryMax, - additionalVerifiedQueryMax, - queryPerTransaction, - transactionCount, - session.logger - ) - } - - session.logger.debug({ account }, 'Account is not verified. Checking if min balance is met.') - - const { cUSDAccountBalance, cEURAccountBalance, celoAccountBalance } = await this.getBalances( - session, - account, - walletAddress - ) - - // Min balance can be in either cUSD, cEUR or CELO - if ( - cUSDAccountBalance?.isGreaterThanOrEqualTo(minDollarBalance) || - cEURAccountBalance?.isGreaterThanOrEqualTo(minEuroBalance) || - celoAccountBalance?.isGreaterThanOrEqualTo(minCeloBalance) - ) { - Counters.requestsWithUnverifiedAccountWithMinBalance.inc() - session.logger.debug( - { - account, - cUSDAccountBalance, - cEURAccountBalance, - celoAccountBalance, - minDollarBalance, - minEuroBalance, - minCeloBalance, - }, - 'Account is not verified but meets min balance' - ) - - return this.calculateQuotaForUnverifiedAccountWithMinBalance( - account, - unverifiedQueryMax, - queryPerTransaction, - transactionCount, - session.logger - ) - } - - session.logger.debug({ account }, 'Account is not verified and does not meet min balance') - - const quota = 0 - - session.logger.trace({ - account, - cUSDAccountBalance, - cEURAccountBalance, - celoAccountBalance, - minDollarBalance, - minEuroBalance, - minCeloBalance, - quota, - }) - - return quota - } - - private calculateQuotaForVerifiedAccount( - account: string, - unverifiedQueryMax: number, - additionalVerifiedQueryMax: number, - queryPerTransaction: number, - transactionCount: number, - logger: Logger - ): number { - const quota = - unverifiedQueryMax + additionalVerifiedQueryMax + queryPerTransaction * transactionCount - - logger.trace({ - account, - unverifiedQueryMax, - additionalVerifiedQueryMax, - queryPerTransaction, - transactionCount, - quota, - }) - - return quota - } - - private calculateQuotaForUnverifiedAccountWithMinBalance( - account: string, - unverifiedQueryMax: number, - queryPerTransaction: number, - transactionCount: number, - logger: Logger - ): number { - const quota = unverifiedQueryMax + queryPerTransaction * transactionCount - - logger.trace({ - account, - unverifiedQueryMax, - queryPerTransaction, - transactionCount, - quota, - }) - - return quota - } -} diff --git a/packages/phone-number-privacy/signer/src/pnp/services/quota.onchain.ts b/packages/phone-number-privacy/signer/src/pnp/services/quota.onchain.ts deleted file mode 100644 index d995f1b316b..00000000000 --- a/packages/phone-number-privacy/signer/src/pnp/services/quota.onchain.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { PnpQuotaRequest, SignMessageRequest } from '@celo/phone-number-privacy-common' -import BigNumber from 'bignumber.js' -import { ACCOUNTS_TABLE } from '../../common/database/models/account' -import { REQUESTS_TABLE } from '../../common/database/models/request' -import { QuotaService } from '../../common/quota' -import { getOnChainOdisPayments } from '../../common/web3/contracts' -import { config } from '../../config' -import { PnpSession } from '../session' -import { PnpQuotaService } from './quota' - -export class OnChainPnpQuotaService - extends PnpQuotaService - implements QuotaService -{ - protected readonly requestsTable = REQUESTS_TABLE.ONCHAIN - protected readonly accountsTable = ACCOUNTS_TABLE.ONCHAIN - /* - * Calculates how many queries the caller has unlocked based on the total - * amount of funds paid to the OdisPayments.sol contract on-chain. - */ - protected async getTotalQuotaWithoutMeter( - session: PnpSession - ): Promise { - const { queryPriceInCUSD } = config.quota - const { account } = session.request.body - const totalPaidInWei = await getOnChainOdisPayments( - this.kit, - session.logger, - account, - session.request.url - ) - const totalQuota = totalPaidInWei - .div(queryPriceInCUSD.times(new BigNumber(1e18))) - .integerValue(BigNumber.ROUND_DOWN) - // If any account hits an overflow here, we need to redesign how - // quota/queries are computed anyways. - return totalQuota.toNumber() - } -} diff --git a/packages/phone-number-privacy/signer/src/pnp/services/quota.ts b/packages/phone-number-privacy/signer/src/pnp/services/quota.ts index c815cdd5e76..8d227573c67 100644 --- a/packages/phone-number-privacy/signer/src/pnp/services/quota.ts +++ b/packages/phone-number-privacy/signer/src/pnp/services/quota.ts @@ -5,6 +5,7 @@ import { PnpQuotaStatus, SignMessageRequest, } from '@celo/phone-number-privacy-common' +import BigNumber from 'bignumber.js' import { Knex } from 'knex' import { ACCOUNTS_TABLE } from '../../common/database/models/account' import { REQUESTS_TABLE } from '../../common/database/models/request' @@ -12,15 +13,13 @@ import { getPerformedQueryCount, incrementQueryCount } from '../../common/databa import { storeRequest } from '../../common/database/wrappers/request' import { Counters, Histograms, meter } from '../../common/metrics' import { OdisQuotaStatusResult, QuotaService } from '../../common/quota' -import { getBlockNumber } from '../../common/web3/contracts' +import { getBlockNumber, getOnChainOdisPayments } from '../../common/web3/contracts' import { config } from '../../config' import { PnpSession } from '../session' -export abstract class PnpQuotaService - implements QuotaService -{ - protected abstract readonly requestsTable: REQUESTS_TABLE - protected abstract readonly accountsTable: ACCOUNTS_TABLE +export class PnpQuotaService implements QuotaService { + protected readonly requestsTable: REQUESTS_TABLE = REQUESTS_TABLE.ONCHAIN + protected readonly accountsTable: ACCOUNTS_TABLE = ACCOUNTS_TABLE.ONCHAIN constructor(readonly db: Knex, readonly kit: ContractKit) {} @@ -146,9 +145,24 @@ export abstract class PnpQuotaService * Calculates how many queries the caller has unlocked; * must be implemented by subclasses. */ - protected abstract getTotalQuotaWithoutMeter( + protected async getTotalQuotaWithoutMeter( session: PnpSession - ): Promise + ): Promise { + const { queryPriceInCUSD } = config.quota + const { account } = session.request.body + const totalPaidInWei = await getOnChainOdisPayments( + this.kit, + session.logger, + account, + session.request.url + ) + const totalQuota = totalPaidInWei + .div(queryPriceInCUSD.times(new BigNumber(1e18))) + .integerValue(BigNumber.ROUND_DOWN) + // If any account hits an overflow here, we need to redesign how + // quota/queries are computed anyways. + return totalQuota.toNumber() + } private bypassQuotaForE2ETesting(requestBody: SignMessageRequest): boolean { const sessionID = Number(requestBody.sessionID) diff --git a/packages/phone-number-privacy/signer/src/server.ts b/packages/phone-number-privacy/signer/src/server.ts index cf54cacd1bf..c0671500365 100644 --- a/packages/phone-number-privacy/signer/src/server.ts +++ b/packages/phone-number-privacy/signer/src/server.ts @@ -25,13 +25,9 @@ import { DomainSignIO } from './domain/endpoints/sign/io' import { DomainQuotaService } from './domain/services/quota' import { PnpQuotaAction } from './pnp/endpoints/quota/action' import { PnpQuotaIO } from './pnp/endpoints/quota/io' -import { LegacyPnpQuotaIO } from './pnp/endpoints/quota/io.legacy' -import { LegacyPnpSignAction } from './pnp/endpoints/sign/action.legacy' -import { OnChainPnpSignAction } from './pnp/endpoints/sign/action.onchain' +import { PnpSignAction } from './pnp/endpoints/sign/action' import { PnpSignIO } from './pnp/endpoints/sign/io' -import { LegacyPnpSignIO } from './pnp/endpoints/sign/io.legacy' -import { LegacyPnpQuotaService } from './pnp/services/quota.legacy' -import { OnChainPnpQuotaService } from './pnp/services/quota.onchain' +import { PnpQuotaService } from './pnp/services/quota' require('events').EventEmitter.defaultMaxListeners = 15 @@ -87,8 +83,7 @@ export function startSigner( } }) - const pnpQuotaService = new OnChainPnpQuotaService(db, kit) - const legacyPnpQuotaService = new LegacyPnpQuotaService(db, kit) + const pnpQuotaService = new PnpQuotaService(db, kit) const domainQuotaService = new DomainQuotaService(db) const pnpQuota = new Controller( @@ -106,7 +101,7 @@ export function startSigner( ) ) const pnpSign = new Controller( - new OnChainPnpSignAction( + new PnpSignAction( db, config, pnpQuotaService, @@ -121,36 +116,7 @@ export function startSigner( ) ) ) - const legacyPnpSign = new Controller( - new LegacyPnpSignAction( - db, - config, - legacyPnpQuotaService, - keyProvider, - new LegacyPnpSignIO( - config.api.legacyPhoneNumberPrivacy.enabled, - config.api.legacyPhoneNumberPrivacy.shouldFailOpen, - config.fullNodeTimeoutMs, - config.fullNodeRetryCount, - config.fullNodeRetryDelayMs, - kit - ) - ) - ) - const legacyPnpQuota = new Controller( - new PnpQuotaAction( - config, - legacyPnpQuotaService, - new LegacyPnpQuotaIO( - config.api.legacyPhoneNumberPrivacy.enabled, - config.api.legacyPhoneNumberPrivacy.shouldFailOpen, - config.fullNodeTimeoutMs, - config.fullNodeRetryCount, - config.fullNodeRetryDelayMs, - kit - ) - ) - ) + const domainQuota = new Controller( new DomainQuotaAction(config, domainQuotaService, new DomainQuotaIO(config.api.domains.enabled)) ) @@ -173,9 +139,6 @@ export function startSigner( addEndpoint(SignerEndpoint.DOMAIN_SIGN, domainSign.handle.bind(domainSign)) addEndpoint(SignerEndpoint.DISABLE_DOMAIN, domainDisable.handle.bind(domainDisable)) - addEndpoint(SignerEndpoint.LEGACY_PNP_SIGN, legacyPnpSign.handle.bind(legacyPnpSign)) - addEndpoint(SignerEndpoint.LEGACY_PNP_QUOTA, legacyPnpQuota.handle.bind(legacyPnpQuota)) - const sslOptions = getSslOptions(config) if (sslOptions) { return https.createServer(sslOptions, app) diff --git a/packages/phone-number-privacy/signer/test/end-to-end/disabled-apis.test.ts b/packages/phone-number-privacy/signer/test/end-to-end/disabled-apis.test.ts index 735ae257ba4..1014df19aa4 100644 --- a/packages/phone-number-privacy/signer/test/end-to-end/disabled-apis.test.ts +++ b/packages/phone-number-privacy/signer/test/end-to-end/disabled-apis.test.ts @@ -207,55 +207,4 @@ describe('Running against a deployed service with disabled APIs', () => { }) }) }) - - describe('when LEGACY_PNP API is disabled', () => { - it(`${SignerEndpoint.LEGACY_PNP_QUOTA} should respond with 503`, async () => { - const req: PnpQuotaRequest = { - account: ACCOUNT_ADDRESS1, - } - const body = JSON.stringify(req) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const response = await fetch(ODIS_SIGNER + SignerEndpoint.LEGACY_PNP_QUOTA, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: authorization, - }, - body, - }) - expect(response.status).toBe(503) - const responseBody: PnpQuotaResponse = await response.json() - expect(responseBody).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.API_UNAVAILABLE, - }) - }) - - it(`${SignerEndpoint.LEGACY_PNP_SIGN} should respond with 503`, async () => { - const req: SignMessageRequest = { - account: ACCOUNT_ADDRESS1, - blindedQueryPhoneNumber: BLINDED_PHONE_NUMBER, - } - const body = JSON.stringify(req) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const response = await fetch(ODIS_SIGNER + SignerEndpoint.LEGACY_PNP_SIGN, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: authorization, - }, - body, - }) - expect(response.status).toBe(503) - const responseBody: PnpQuotaResponse = await response.json() - expect(responseBody).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.API_UNAVAILABLE, - }) - }) - }) }) diff --git a/packages/phone-number-privacy/signer/test/end-to-end/get-blinded-sig.test.ts b/packages/phone-number-privacy/signer/test/end-to-end/get-blinded-sig.test.ts deleted file mode 100644 index 3586763a0ef..00000000000 --- a/packages/phone-number-privacy/signer/test/end-to-end/get-blinded-sig.test.ts +++ /dev/null @@ -1,350 +0,0 @@ -import { newKitFromWeb3 } from '@celo/contractkit' -import { - KEY_VERSION_HEADER, - PnpQuotaResponse, - rootLogger, - SignerEndpoint, - SignMessageResponseFailure, - SignMessageResponseSuccess, - TestUtils, -} from '@celo/phone-number-privacy-common' -import { serializeSignature, signMessage } from '@celo/utils/lib/signatureUtils' -import threshold_bls from 'blind-threshold-bls' -import { randomBytes } from 'crypto' -import 'isomorphic-fetch' -import Web3 from 'web3' -import { getWalletAddress } from '../../src/common/web3/contracts' -import { config } from '../../src/config' -import { getBlindedPhoneNumber, getTestParamsForContext } from './utils' - -require('dotenv').config() - -const { - ACCOUNT_ADDRESS1, - ACCOUNT_ADDRESS2, - ACCOUNT_ADDRESS3, - BLINDED_PHONE_NUMBER, - IDENTIFIER, - PHONE_NUMBER, - PRIVATE_KEY1, - PRIVATE_KEY2, - PRIVATE_KEY3, -} = TestUtils.Values -const { replenishQuota, registerWalletAddress } = TestUtils.Utils - -const ODIS_SIGNER = process.env.ODIS_SIGNER_SERVICE_URL -const expectedVersion = process.env.DEPLOYED_SIGNER_SERVICE_VERSION! - -// Keep these checks as is to ensure backwards compatibility -const SIGN_MESSAGE_ENDPOINT = '/getBlindedMessagePartialSig' -const GET_QUOTA_ENDPOINT = '/getQuota' - -const contextSpecificParams = getTestParamsForContext() - -const web3 = new Web3(new Web3.providers.HttpProvider(contextSpecificParams.blockchainProviderURL)) -const contractkit = newKitFromWeb3(web3) -contractkit.addAccount(PRIVATE_KEY1) -contractkit.addAccount(PRIVATE_KEY2) -contractkit.addAccount(PRIVATE_KEY3) - -jest.setTimeout(30000) - -const getRandomBlindedPhoneNumber = () => { - return getBlindedPhoneNumber(PHONE_NUMBER, randomBytes(32)) -} - -describe('Running against a deployed service', () => { - beforeAll(() => { - console.log('Blockchain Provider URL: ' + contextSpecificParams.blockchainProviderURL) - console.log('ODIS_SIGNER: ' + ODIS_SIGNER) - console.log('PNP Public Polynomial: ' + contextSpecificParams.pnpPolynomial) - console.log('Key Version:' + contextSpecificParams.pnpKeyVersion) - }) - - it('Service is deployed at correct version', async () => { - const response = await fetch(ODIS_SIGNER + SignerEndpoint.STATUS, { method: 'GET' }) - const body = await response.json() - // This checks against local package.json version, change if necessary - expect(response.status).toBe(200) - expect(body.version).toBe(expectedVersion) - }) - - describe('Returns status 400 with invalid input', () => { - it('With invalid address', async () => { - const response = await postToSignMessage(BLINDED_PHONE_NUMBER, '0x1234', Date.now(), 'ignore') - expect(response.status).toBe(400) - }) - - it('With missing blindedQueryPhoneNumber', async () => { - const response = await postToSignMessage('', ACCOUNT_ADDRESS1, Date.now()) - expect(response.status).toBe(400) - }) - - it('With invalid blindedQueryPhoneNumber', async () => { - const response = await postToSignMessage('invalid', ACCOUNT_ADDRESS1, Date.now()) - expect(response.status).toBe(400) - }) - }) - - describe('Returns status 401 with invalid authentication headers', () => { - it('With invalid auth header', async () => { - const response = await postToSignMessage( - BLINDED_PHONE_NUMBER, - ACCOUNT_ADDRESS1, - Date.now(), - 'invalid' - ) - expect(response.status).toBe(401) - }) - - it('With auth header signer mismatch', async () => { - // Sign body with different account - const body = JSON.stringify({ - hashedPhoneNumber: '+1455556600', - blindedQueryPhoneNumber: BLINDED_PHONE_NUMBER.trim(), - ACCOUNT_ADDRESS1, - }) - const signature = signMessage(JSON.stringify(body), PRIVATE_KEY2, ACCOUNT_ADDRESS2) - const authHeader = serializeSignature(signature) - - const response = await postToSignMessage( - BLINDED_PHONE_NUMBER, - ACCOUNT_ADDRESS1, - undefined, - authHeader - ) - expect(response.status).toBe(401) - }) - }) - - it('Returns 403 error when querying out of quota', async () => { - const response = await postToSignMessage( - getRandomBlindedPhoneNumber(), - ACCOUNT_ADDRESS1, - Date.now() - ) - expect(response.status).toBe(403) - }) - - describe('When account address has enough quota', () => { - // if these tests are failing, it may just be that the address needs to be fauceted: - // celotooljs account faucet --account ACCOUNT_ADDRESS2 --dollar 1 --gold 1 -e --verbose - - beforeAll(async () => { - console.log('ACCOUNT_ADDRESS1 ' + ACCOUNT_ADDRESS1) - console.log('ACCOUNT_ADDRESS2 ' + ACCOUNT_ADDRESS2) - console.log('ACCOUNT_ADDRESS3 ' + ACCOUNT_ADDRESS3) - - contractkit.defaultAccount = ACCOUNT_ADDRESS2 - }) - - it('Returns sig when querying succeeds', async () => { - await replenishQuota(ACCOUNT_ADDRESS2, contractkit) - const response = await postToSignMessage(BLINDED_PHONE_NUMBER, ACCOUNT_ADDRESS2) - expect(response.status).toBe(200) - }) - - // Backwards compatibility check - it('Returns sig when querying succeeds w/ expired timestamp', async () => { - await replenishQuota(ACCOUNT_ADDRESS2, contractkit) - const response = await postToSignMessage( - BLINDED_PHONE_NUMBER, - ACCOUNT_ADDRESS2, - Date.now() - 10 * 60 * 1000 - ) // 10 minutes ago - expect(response.status).toBe(200) - }) - - it('Increments query count when querying succeeds w/ unused request', async () => { - await replenishQuota(ACCOUNT_ADDRESS2, contractkit) - const initialQueryCount = await getQueryCount(ACCOUNT_ADDRESS2, IDENTIFIER) - await postToSignMessage(getRandomBlindedPhoneNumber(), ACCOUNT_ADDRESS2) - expect(initialQueryCount).toEqual((await getQueryCount(ACCOUNT_ADDRESS2, IDENTIFIER)) - 1) - }) - - // Backwards compatibility check - it('Increments query count when querying succeeds w/ timestamp', async () => { - await replenishQuota(ACCOUNT_ADDRESS2, contractkit) - const initialQueryCount = await getQueryCount(ACCOUNT_ADDRESS2, IDENTIFIER) - await postToSignMessage(getRandomBlindedPhoneNumber(), ACCOUNT_ADDRESS2, Date.now()) - expect(initialQueryCount).toEqual((await getQueryCount(ACCOUNT_ADDRESS2, IDENTIFIER)) - 1) - }) - - it('Returns sig when querying succeeds with replayed request without incrementing query count', async () => { - await replenishQuota(ACCOUNT_ADDRESS2, contractkit) - const blindedPhoneNumber = getRandomBlindedPhoneNumber() - const res1 = await postToSignMessage(blindedPhoneNumber, ACCOUNT_ADDRESS2) - expect(res1.status).toBe(200) - const queryCount = await getQueryCount(ACCOUNT_ADDRESS2, IDENTIFIER) - const res2 = await postToSignMessage(blindedPhoneNumber, ACCOUNT_ADDRESS2) - expect(res2.status).toBe(200) - expect(queryCount).toEqual(await getQueryCount(ACCOUNT_ADDRESS2, IDENTIFIER)) - }) - - // Backwards compatibility check - it('Returns sig when querying succeeds with replayed request without incrementing query count w/ timestamp', async () => { - await replenishQuota(ACCOUNT_ADDRESS2, contractkit) - const res1 = await postToSignMessage(BLINDED_PHONE_NUMBER, ACCOUNT_ADDRESS2, Date.now()) - expect(res1.status).toBe(200) - const queryCount = await getQueryCount(ACCOUNT_ADDRESS2, IDENTIFIER) - const res2 = await postToSignMessage(BLINDED_PHONE_NUMBER, ACCOUNT_ADDRESS2, Date.now()) - expect(res2.status).toBe(200) - expect(queryCount).toEqual(await getQueryCount(ACCOUNT_ADDRESS2, IDENTIFIER)) - }) - }) - - describe('When walletAddress has enough quota', () => { - // if these tests are failing, it may just be that the address needs to be fauceted: - // celotooljs account faucet --account ACCOUNT_ADDRESS2 --dollar 1 --gold 1 -e --verbose - // NOTE: DO NOT FAUCET ACCOUNT_ADDRESS3 - let initialQuota: number - let initialQueryCount: number - beforeAll(async () => { - contractkit.defaultAccount = ACCOUNT_ADDRESS3 - await registerWalletAddress(ACCOUNT_ADDRESS3, ACCOUNT_ADDRESS2, PRIVATE_KEY2, contractkit) - // ACCOUNT_ADDRESS2 is now the wallet address (has quota) - // and ACCOUNT_ADDRESS3 is account address (does not have quota on it's own, only bc of walletAddress) - initialQuota = await getQuota(ACCOUNT_ADDRESS3, IDENTIFIER) - initialQueryCount = await getQueryCount(ACCOUNT_ADDRESS3, IDENTIFIER) - }) - - it('Check that accounts are set up correctly', async () => { - expect(await getQuota(ACCOUNT_ADDRESS2, IDENTIFIER)).toBeLessThan(initialQuota) - expect( - await getWalletAddress( - contractkit, - rootLogger(config.serviceName), - ACCOUNT_ADDRESS3, - SignerEndpoint.LEGACY_PNP_SIGN - ) - ).toBe(ACCOUNT_ADDRESS2) - }) - - // Note: Use this test to check the signers' key configuration. Modify .env to try out different - // key/version combinations - it('[Signer configuration test] Returns sig when querying succeeds with unused request', async () => { - await replenishQuota(ACCOUNT_ADDRESS2, contractkit) - const blindedPhoneNumber = getRandomBlindedPhoneNumber() - const response = await postToSignMessage(blindedPhoneNumber, ACCOUNT_ADDRESS3) - expect(response.status).toBe(200) - - // Validate signature - type SignerResponse = SignMessageResponseSuccess | SignMessageResponseFailure - const data = await response.text() - const signResponse = JSON.parse(data) as SignerResponse - expect(signResponse.success).toBeTruthy() - if (signResponse.success) { - const sigBuffer = Buffer.from(signResponse.signature as string, 'base64') - const isValid = isValidSignature( - sigBuffer, - blindedPhoneNumber, - contextSpecificParams.pnpPolynomial - ) - expect(isValid).toBeTruthy() - } - }) - - it('Returns count when querying with unused request increments query count', async () => { - const queryCount = await getQueryCount(ACCOUNT_ADDRESS3, IDENTIFIER) - expect(queryCount).toEqual(initialQueryCount + 1) - }) - - it('Returns sig when querying succeeds with used request', async () => { - await replenishQuota(ACCOUNT_ADDRESS2, contractkit) - const response = await postToSignMessage(BLINDED_PHONE_NUMBER, ACCOUNT_ADDRESS3) - expect(response.status).toBe(200) - }) - - it('Returns count when querying with used request does not increment query count', async () => { - const queryCount = await getQueryCount(ACCOUNT_ADDRESS3, IDENTIFIER) - expect(queryCount).toEqual(initialQueryCount + 1) - }) - }) -}) - -async function getQuota( - account: string, - hashedPhoneNumber?: string, - authHeader?: string -): Promise { - const res = await queryQuotaEndpoint(account, hashedPhoneNumber, authHeader) - return res.success ? res.totalQuota ?? 0 : 0 -} - -async function getQueryCount( - account: string, - hashedPhoneNumber?: string, - authHeader?: string -): Promise { - const res = await queryQuotaEndpoint(account, hashedPhoneNumber, authHeader) - return res.success ? res.performedQueryCount ?? 0 : 0 -} - -async function queryQuotaEndpoint( - account: string, - hashedPhoneNumber?: string, - authHeader?: string -): Promise { - const body = JSON.stringify({ - account, - hashedPhoneNumber, - }) - - const authorization = authHeader || (await contractkit.connection.sign(body, account)) - - const res = await fetch(ODIS_SIGNER + GET_QUOTA_ENDPOINT, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: authorization, - }, - body, - }) - - return res.json() -} - -async function postToSignMessage( - base64BlindedMessage: string, - account: string, - timestamp?: number, - authHeader?: string, - keyVersion: string = contextSpecificParams.pnpKeyVersion -): Promise { - const body = JSON.stringify({ - hashedPhoneNumber: IDENTIFIER, - blindedQueryPhoneNumber: base64BlindedMessage.trim(), - account, - timestamp, - }) - - const authorization = authHeader || (await contractkit.connection.sign(body, account)) - - const res = await fetch(ODIS_SIGNER + SIGN_MESSAGE_ENDPOINT, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: authorization, - [KEY_VERSION_HEADER]: keyVersion, - }, - body, - }) - - return res -} - -function isValidSignature(signature: Buffer, blindedMessage: string, polynomial: string) { - try { - threshold_bls.partialVerifyBlindSignature( - Buffer.from(polynomial, 'hex'), - Buffer.from(blindedMessage, 'base64'), - signature - ) - return true - } catch (err) { - console.log(err) - return false - } -} diff --git a/packages/phone-number-privacy/signer/test/integration/legacypnp.test.ts b/packages/phone-number-privacy/signer/test/integration/legacypnp.test.ts deleted file mode 100644 index e72bc1f7a7d..00000000000 --- a/packages/phone-number-privacy/signer/test/integration/legacypnp.test.ts +++ /dev/null @@ -1,1795 +0,0 @@ -import { newKit, StableToken } from '@celo/contractkit' -import { - AuthenticationMethod, - ErrorMessage, - KEY_VERSION_HEADER, - PhoneNumberPrivacyRequest, - PnpQuotaResponseFailure, - PnpQuotaResponseSuccess, - rootLogger, - SignerEndpoint, - SignMessageResponseFailure, - SignMessageResponseSuccess, - TestUtils, - WarningMessage, -} from '@celo/phone-number-privacy-common' -import { - AttestationsStatus, - createMockOdisPayments, - getPnpSignRequest, -} from '@celo/phone-number-privacy-common/lib/test/utils' -import BigNumber from 'bignumber.js' -import { Knex } from 'knex' -import request from 'supertest' -import { initDatabase } from '../../src/common/database/database' -import { ACCOUNTS_TABLE } from '../../src/common/database/models/account' -import { REQUESTS_TABLE } from '../../src/common/database/models/request' -import { countAndThrowDBError } from '../../src/common/database/utils' -import { - getPerformedQueryCount, - incrementQueryCount, -} from '../../src/common/database/wrappers/account' -import { getRequestExists } from '../../src/common/database/wrappers/request' -import { initKeyProvider } from '../../src/common/key-management/key-provider' -import { KeyProvider } from '../../src/common/key-management/key-provider-base' -import { config, getSignerVersion, SupportedDatabase, SupportedKeystore } from '../../src/config' -import { startSigner } from '../../src/server' - -const { - ContractRetrieval, - createMockContractKit, - createMockAccounts, - createMockToken, - createMockWeb3, - getLegacyPnpQuotaRequest, - getPnpRequestAuthorization, - createMockAttestation, - getLegacyPnpSignRequest, -} = TestUtils.Utils -const { - IDENTIFIER, - PRIVATE_KEY1, - ACCOUNT_ADDRESS1, - mockAccount, - BLINDED_PHONE_NUMBER, - DEK_PRIVATE_KEY, - DEK_PUBLIC_KEY, -} = TestUtils.Values - -jest.setTimeout(20000) - -const testBlockNumber = 1000000 - -const mockBalanceOfCUSD = jest.fn() -const mockBalanceOfCEUR = jest.fn() -const mockBalanceOfCELO = jest.fn() -const mockGetVerifiedStatus = jest.fn() -const mockGetWalletAddress = jest.fn() -const mockGetDataEncryptionKey = jest.fn() -const mockOdisPaymentsTotalPaidCUSD = jest.fn() - -const mockContractKit = createMockContractKit( - { - // getWalletAddress stays constant across all old query-quota.test.ts unit tests - [ContractRetrieval.getAccounts]: createMockAccounts( - mockGetWalletAddress, - mockGetDataEncryptionKey - ), - [ContractRetrieval.getStableToken]: jest.fn(), - [ContractRetrieval.getGoldToken]: createMockToken(mockBalanceOfCELO), - [ContractRetrieval.getAttestations]: createMockAttestation(mockGetVerifiedStatus), - [ContractRetrieval.getOdisPayments]: createMockOdisPayments(mockOdisPaymentsTotalPaidCUSD), - }, - createMockWeb3(5, testBlockNumber) -) - -// Necessary for distinguishing between mocked stable tokens -mockContractKit.contracts[ContractRetrieval.getStableToken] = jest.fn( - (stableToken: StableToken) => { - switch (stableToken) { - case StableToken.cUSD: - return createMockToken(mockBalanceOfCUSD) - case StableToken.cEUR: - return createMockToken(mockBalanceOfCEUR) - default: - return createMockToken(jest.fn().mockReturnValue(new BigNumber(0))) - } - } -) - -jest.mock('@celo/contractkit', () => ({ - ...jest.requireActual('@celo/contractkit'), - newKit: jest.fn().mockImplementation(() => mockContractKit), -})) - -// Indexes correspond to keyVersion - 1 -const expectedSignatures: string[] = [ - 'MAAAAAAAAACEVdw1ULDwAiTcZuPnZxHHh38PNa+/g997JgV10QnEq9yeuLxbM9l7vk0EAicV7IAAAAAA', - 'MAAAAAAAAAAmUJY0s9p7fMfs7GIoSiGJoObAN8ZpA7kRqeC9j/Q23TBrG3Jtxc8xWibhNVZhbYEAAAAA', - 'MAAAAAAAAAC4aBbzhHvt6l/b+8F7cILmWxZZ5Q7S6R4RZ/IgZR7Pfb9B1Wg9fsDybgxVTSv5BYEAAAAA', -] - -describe('legacyPNP', () => { - let keyProvider: KeyProvider - let app: any - let db: Knex - - const expectedVersion = getSignerVersion() - - // create deep copy - const _config: typeof config = JSON.parse(JSON.stringify(config)) - _config.db.type = SupportedDatabase.Sqlite - _config.keystore.type = SupportedKeystore.MOCK_SECRET_MANAGER - _config.api.legacyPhoneNumberPrivacy.enabled = true - - const expectedSignature = expectedSignatures[_config.keystore.keys.phoneNumberPrivacy.latest - 1] - - beforeAll(async () => { - keyProvider = await initKeyProvider(_config) - }) - - beforeEach(async () => { - // Create a new in-memory database for each test. - db = await initDatabase(_config) - app = startSigner(_config, db, keyProvider, newKit('dummyKit')) - }) - - afterEach(async () => { - // Close and destroy the in-memory database. - // Note: If tests start to be too slow, this could be replaced with more complicated logic to - // reset the database state without destroying and recreating it for each test. - await db?.destroy() - }) - - describe(`${SignerEndpoint.STATUS}`, () => { - it('Should return 200 and correct version', async () => { - const res = await request(app).get(SignerEndpoint.STATUS) - expect(res.status).toBe(200) - expect(res.body.version).toBe(expectedVersion) - }) - }) - - const zeroBalance = new BigNumber(0) - const twentyCents = new BigNumber(200000000000000000) - - type legacyPnpQuotaCalculationTestCase = { - it: string - account: string - performedQueryCount: number - transactionCount: number - balanceCUSD: BigNumber - balanceCEUR: BigNumber - balanceCELO: BigNumber - isVerified: boolean - identifier: string | undefined - expectedPerformedQueryCount: number - expectedTotalQuota: number - } // To be re-used against both the signature and quota endpoints - const quotaCalculationTestCases: legacyPnpQuotaCalculationTestCase[] = [ - { - it: 'should calculate correct quota for verified account', - account: ACCOUNT_ADDRESS1, - performedQueryCount: 2, - transactionCount: 5, - balanceCUSD: zeroBalance, - balanceCEUR: zeroBalance, - balanceCELO: zeroBalance, - isVerified: true, - identifier: IDENTIFIER, - expectedPerformedQueryCount: 2, - expectedTotalQuota: 60, - }, - { - it: 'should calculate correct quota for unverified account with no transactions or balance', - account: ACCOUNT_ADDRESS1, - performedQueryCount: 0, - transactionCount: 0, - balanceCUSD: zeroBalance, - balanceCEUR: zeroBalance, - balanceCELO: zeroBalance, - isVerified: false, - identifier: IDENTIFIER, - expectedPerformedQueryCount: 0, - expectedTotalQuota: 0, - }, - { - it: 'should calculate correct quota for unverified account with balance but no transactions', - account: ACCOUNT_ADDRESS1, - performedQueryCount: 1, - transactionCount: 0, - balanceCUSD: twentyCents, - balanceCEUR: twentyCents, - balanceCELO: twentyCents, - isVerified: false, - identifier: IDENTIFIER, - expectedPerformedQueryCount: 1, - expectedTotalQuota: 10, - }, - { - it: 'should calculate correct quota for verified account with many txs and balance', - account: ACCOUNT_ADDRESS1, - performedQueryCount: 10, - transactionCount: 100, - balanceCUSD: twentyCents, - balanceCEUR: twentyCents, - balanceCELO: twentyCents, - isVerified: true, - identifier: IDENTIFIER, - expectedPerformedQueryCount: 10, - expectedTotalQuota: 440, - }, - { - it: 'should calculate correct quota for unverified account with many txs and balance', - account: ACCOUNT_ADDRESS1, - performedQueryCount: 0, - transactionCount: 100, - balanceCUSD: twentyCents, - balanceCEUR: twentyCents, - balanceCELO: twentyCents, - isVerified: false, - identifier: IDENTIFIER, - expectedPerformedQueryCount: 0, - expectedTotalQuota: 410, - }, - { - it: 'should calculate correct quota for unverified account without any balance (with txs)', - account: ACCOUNT_ADDRESS1, - performedQueryCount: 0, - transactionCount: 100, - balanceCUSD: zeroBalance, - balanceCEUR: zeroBalance, - balanceCELO: zeroBalance, - isVerified: false, - identifier: IDENTIFIER, - expectedPerformedQueryCount: 0, - expectedTotalQuota: 0, - }, - { - it: 'should calculate correct quota for unverified account with only cUSD balance (no txs)', - account: ACCOUNT_ADDRESS1, - performedQueryCount: 1, - transactionCount: 0, - balanceCUSD: twentyCents, - balanceCEUR: zeroBalance, - balanceCELO: zeroBalance, - isVerified: false, - identifier: IDENTIFIER, - expectedPerformedQueryCount: 1, - expectedTotalQuota: 10, - }, - { - it: 'should calculate correct quota for unverified account with only cEUR balance (no txs)', - account: ACCOUNT_ADDRESS1, - performedQueryCount: 1, - transactionCount: 0, - balanceCUSD: zeroBalance, - balanceCEUR: twentyCents, - balanceCELO: zeroBalance, - isVerified: false, - identifier: IDENTIFIER, - expectedPerformedQueryCount: 1, - expectedTotalQuota: 10, - }, - { - it: 'should calculate correct quota for unverified account with only CELO balance (no txs)', - account: ACCOUNT_ADDRESS1, - performedQueryCount: 1, - transactionCount: 0, - balanceCUSD: zeroBalance, - balanceCEUR: zeroBalance, - balanceCELO: twentyCents, - isVerified: false, - identifier: IDENTIFIER, - expectedPerformedQueryCount: 1, - expectedTotalQuota: 10, - }, - { - it: 'should calculate correct quota for account with min balance when no phone number hash is provided', - account: ACCOUNT_ADDRESS1, - performedQueryCount: 1, - transactionCount: 0, - balanceCUSD: twentyCents, - balanceCEUR: twentyCents, - balanceCELO: twentyCents, - isVerified: false, - identifier: IDENTIFIER, - expectedPerformedQueryCount: 1, - expectedTotalQuota: 10, - }, - ] - - const prepMocks = async ( - account: string, - performedQueryCount: number, - transactionCount: number, - isVerified: boolean, - balanceCUSD: BigNumber, - balanceCEUR: BigNumber, - balanceCELO: BigNumber, - dekPubKey: string = DEK_PUBLIC_KEY, - walletAddress: string = mockAccount - ) => { - ;[ - mockContractKit.connection.getTransactionCount, - mockGetVerifiedStatus, - mockBalanceOfCUSD, - mockBalanceOfCEUR, - mockBalanceOfCELO, - mockGetWalletAddress, - mockGetDataEncryptionKey, - ].forEach((mockFn) => mockFn.mockReset()) - - await db.transaction(async (trx) => { - for (let i = 0; i < performedQueryCount; i++) { - await incrementQueryCount( - db, - ACCOUNTS_TABLE.LEGACY, - account, - rootLogger(_config.serviceName), - trx - ) - } - }) - - mockContractKit.connection.getTransactionCount.mockReturnValue(transactionCount) - mockGetVerifiedStatus.mockReturnValue( - // only the isVerified value below matters - { isVerified, completed: 1, total: 1, numAttestationsRemaining: 1 } - ) - mockBalanceOfCUSD.mockReturnValue(balanceCUSD) - mockBalanceOfCEUR.mockReturnValue(balanceCEUR) - mockBalanceOfCELO.mockReturnValue(balanceCELO) - mockGetWalletAddress.mockReturnValue(walletAddress) - mockGetDataEncryptionKey.mockReturnValue(dekPubKey) - } - - const sendRequest = async ( - req: PhoneNumberPrivacyRequest, - authorization: string, - endpoint: SignerEndpoint, - keyVersionHeader?: string, - signerApp: any = app - ) => { - const _req = request(signerApp).post(endpoint).set('Authorization', authorization) - - if (keyVersionHeader !== undefined) { - _req.set(KEY_VERSION_HEADER, keyVersionHeader) - } - - return _req.send(req) - } - - describe(`${SignerEndpoint.LEGACY_PNP_QUOTA}`, () => { - describe('quota calculation logic', () => { - const runLegacyQuotaTestCase = async (testCase: legacyPnpQuotaCalculationTestCase) => { - await prepMocks( - testCase.account, - testCase.performedQueryCount, - testCase.transactionCount, - testCase.isVerified, - testCase.balanceCUSD, - testCase.balanceCEUR, - testCase.balanceCELO - ) - - const req = getLegacyPnpQuotaRequest( - testCase.account, - AuthenticationMethod.WALLET_KEY, - testCase.identifier - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_QUOTA) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - performedQueryCount: testCase.expectedPerformedQueryCount, - totalQuota: testCase.expectedTotalQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - } - - quotaCalculationTestCases.forEach((testCase) => { - it(testCase.it, async () => { - await runLegacyQuotaTestCase(testCase) - }) - }) - }) - - describe('endpoint functionality', () => { - // Use values from 'unverified account with no transactions' logic test case - const performedQueryCount = 1 - const expectedQuota = 10 - - beforeEach(async () => { - await prepMocks( - ACCOUNT_ADDRESS1, - performedQueryCount, - 0, - false, - twentyCents, - twentyCents, - twentyCents - ) - }) - - it('Should respond with 200 on valid request', async () => { - const req = getLegacyPnpQuotaRequest(ACCOUNT_ADDRESS1, IDENTIFIER) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - - const res = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_QUOTA) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - performedQueryCount: performedQueryCount, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - }) - - it('Should respond with 200 on valid request when authenticated with DEK', async () => { - const req = getLegacyPnpQuotaRequest( - ACCOUNT_ADDRESS1, - AuthenticationMethod.ENCRYPTION_KEY, - IDENTIFIER - ) - const authorization = getPnpRequestAuthorization(req, DEK_PRIVATE_KEY) - - const res = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_QUOTA) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - performedQueryCount: performedQueryCount, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - }) - - it('Should respond with 200 on repeated valid requests', async () => { - const req = getLegacyPnpQuotaRequest(ACCOUNT_ADDRESS1, IDENTIFIER) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - - const res1 = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_QUOTA) - expect(res1.status).toBe(200) - expect(res1.body).toStrictEqual({ - success: true, - version: res1.body.version, - performedQueryCount: performedQueryCount, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - const res2 = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_QUOTA) - expect(res2.status).toBe(200) - expect(res2.body).toStrictEqual(res1.body) - }) - - it('Should respond with 200 on extra request fields', async () => { - const req = getLegacyPnpQuotaRequest(ACCOUNT_ADDRESS1, IDENTIFIER) - // @ts-ignore Intentionally adding an extra field to the request type - req.extraField = 'dummyString' - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_QUOTA) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - performedQueryCount: performedQueryCount, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - }) - - it('Should respond with 200 if performedQueryCount is greater than totalQuota', async () => { - const expectedRemainingQuota = expectedQuota - performedQueryCount - await db.transaction(async (trx) => { - for (let i = 0; i <= expectedRemainingQuota; i++) { - await incrementQueryCount( - db, - ACCOUNTS_TABLE.LEGACY, - ACCOUNT_ADDRESS1, - rootLogger(_config.serviceName), - trx - ) - } - }) - const req = getLegacyPnpQuotaRequest(ACCOUNT_ADDRESS1) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_QUOTA) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - performedQueryCount: expectedQuota + 1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - }) - - it('Should respond with 400 on missing request fields', async () => { - const badRequest = getLegacyPnpQuotaRequest(ACCOUNT_ADDRESS1, IDENTIFIER) - // @ts-ignore Intentionally deleting required field - delete badRequest.account - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await sendRequest(badRequest, authorization, SignerEndpoint.LEGACY_PNP_QUOTA) - - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 401 on failed WALLET_KEY auth', async () => { - const badRequest = getLegacyPnpQuotaRequest( - ACCOUNT_ADDRESS1, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const differentPk = '0x00000000000000000000000000000000000000000000000000000000ddddbbbb' - const authorization = getPnpRequestAuthorization(badRequest, differentPk) - const res = await sendRequest(badRequest, authorization, SignerEndpoint.LEGACY_PNP_QUOTA) - - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 401 on failed DEK auth', async () => { - const badRequest = getLegacyPnpQuotaRequest( - ACCOUNT_ADDRESS1, - AuthenticationMethod.ENCRYPTION_KEY, - IDENTIFIER - ) - const differentPk = '0x00000000000000000000000000000000000000000000000000000000ddddbbbb' - const authorization = getPnpRequestAuthorization(badRequest, differentPk) - const res = await sendRequest(badRequest, authorization, SignerEndpoint.LEGACY_PNP_QUOTA) - - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 503 on disabled api', async () => { - const configWithApiDisabled: typeof _config = JSON.parse(JSON.stringify(_config)) - configWithApiDisabled.api.legacyPhoneNumberPrivacy.enabled = false - const appWithApiDisabled = startSigner( - configWithApiDisabled, - db, - keyProvider, - newKit('dummyKit') - ) - const req = getLegacyPnpQuotaRequest(ACCOUNT_ADDRESS1, IDENTIFIER) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest( - req, - authorization, - SignerEndpoint.LEGACY_PNP_QUOTA, - undefined, - appWithApiDisabled - ) - expect.assertions(2) - expect(res.status).toBe(503) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.API_UNAVAILABLE, - }) - }) - - describe('functionality in case of errors', () => { - it('Should respond with 200 on failure to fetch DEK when shouldFailOpen is true', async () => { - const configWithFailOpenEnabled: typeof _config = JSON.parse(JSON.stringify(_config)) - configWithFailOpenEnabled.api.legacyPhoneNumberPrivacy.shouldFailOpen = true - const appWithFailOpenEnabled = startSigner( - configWithFailOpenEnabled, - db, - keyProvider, - newKit('dummyKit') - ) - mockGetDataEncryptionKey.mockImplementation(() => { - throw new Error() - }) - - const req = getLegacyPnpQuotaRequest( - ACCOUNT_ADDRESS1, - AuthenticationMethod.ENCRYPTION_KEY, - IDENTIFIER - ) - - // NOT the dek private key, so authentication would fail if getDataEncryptionKey succeeded - const differentPk = '0x00000000000000000000000000000000000000000000000000000000ddddbbbb' - const authorization = getPnpRequestAuthorization(req, differentPk) - const res = await sendRequest( - req, - authorization, - SignerEndpoint.LEGACY_PNP_QUOTA, - '1', - appWithFailOpenEnabled - ) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: res.body.version, - performedQueryCount: performedQueryCount, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [ErrorMessage.FAILURE_TO_GET_DEK, ErrorMessage.FAILING_OPEN], - }) - }) - - it('Should respond with 500 on DB performedQueryCount query failure', async () => { - const spy = jest - .spyOn( - jest.requireActual('../../src/common/database/wrappers/account'), - 'getPerformedQueryCount' - ) - .mockRejectedValueOnce(new Error()) - - const req = getLegacyPnpQuotaRequest(ACCOUNT_ADDRESS1, IDENTIFIER) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_QUOTA) - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.FAILURE_TO_GET_PERFORMED_QUERY_COUNT, - }) - - spy.mockRestore() - }) - - it('Should respond with 500 on blockchain totalQuota query failure', async () => { - mockContractKit.connection.getTransactionCount.mockRejectedValue(new Error()) - - const req = getLegacyPnpQuotaRequest(ACCOUNT_ADDRESS1, IDENTIFIER) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_QUOTA) - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.FAILURE_TO_GET_TOTAL_QUOTA, - }) - }) - - it('Should respond with 500 on signer timeout', async () => { - const testTimeoutMS = 0 - const delay = 100 - const spy = jest - .spyOn( - jest.requireActual('../../src/common/database/wrappers/account'), - 'getPerformedQueryCount' - ) - .mockImplementation(async () => { - await new Promise((resolve) => setTimeout(resolve, testTimeoutMS + delay)) - return expectedQuota - }) - - const configWithShortTimeout = JSON.parse(JSON.stringify(_config)) - configWithShortTimeout.timeout = testTimeoutMS - const appWithShortTimeout = startSigner( - configWithShortTimeout, - db, - keyProvider, - newKit('dummyKit') - ) - const req = getLegacyPnpQuotaRequest(ACCOUNT_ADDRESS1) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest( - req, - authorization, - SignerEndpoint.LEGACY_PNP_QUOTA, - undefined, - appWithShortTimeout - ) - // Ensure that this is restored before test can fail on assertions - // to prevent failures in other tests - spy.mockRestore() - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - error: ErrorMessage.TIMEOUT_FROM_SIGNER, - version: expectedVersion, - }) - // Allow time for non-killed processes to finish - await new Promise((resolve) => setTimeout(resolve, delay)) - }) - }) - }) - }) - - describe(`${SignerEndpoint.LEGACY_PNP_SIGN}`, () => { - describe('quota calculation logic', () => { - const runLegacyPnpSignQuotaTestCase = async (testCase: legacyPnpQuotaCalculationTestCase) => { - await prepMocks( - testCase.account, - testCase.performedQueryCount, - testCase.transactionCount, - testCase.isVerified, - testCase.balanceCUSD, - testCase.balanceCEUR, - testCase.balanceCELO - ) - - const req = getLegacyPnpSignRequest( - testCase.account, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - testCase.identifier - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_SIGN) - - const { expectedPerformedQueryCount, expectedTotalQuota } = testCase - const shouldSucceed = expectedPerformedQueryCount < expectedTotalQuota - - if (shouldSucceed) { - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: expectedPerformedQueryCount + 1, // incremented for signature request - totalQuota: expectedTotalQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - } else { - expect(res.status).toBe(403) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - performedQueryCount: expectedPerformedQueryCount, - totalQuota: expectedTotalQuota, - blockNumber: testBlockNumber, - error: WarningMessage.EXCEEDED_QUOTA, - }) - } - } - - quotaCalculationTestCases.forEach((testCase) => { - it(testCase.it, async () => { - await runLegacyPnpSignQuotaTestCase(testCase) - }) - }) - }) - - describe('endpoint functionality', () => { - // Use values from 'unverified account with balance but no transactions' logic test case - const performedQueryCount = 1 - const expectedQuota = 10 - - beforeEach(async () => { - await prepMocks( - ACCOUNT_ADDRESS1, - performedQueryCount, - 0, - false, - twentyCents, - twentyCents, - twentyCents - ) - }) - - it('Should respond with 200 on valid request', async () => { - const req = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_SIGN) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: performedQueryCount + 1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - expect(res.get(KEY_VERSION_HEADER)).toEqual( - _config.keystore.keys.phoneNumberPrivacy.latest.toString() - ) - }) - - it('Should respond with 200 on valid request when authenticated with DEK', async () => { - const req = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.ENCRYPTION_KEY, - IDENTIFIER - ) - const authorization = getPnpRequestAuthorization(req, DEK_PRIVATE_KEY) - const res = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_SIGN) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: performedQueryCount + 1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - }) - - for (let i = 1; i <= 3; i++) { - it(`Should respond with 200 on valid request with key version header ${i}`, async () => { - const req = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest( - req, - authorization, - SignerEndpoint.LEGACY_PNP_SIGN, - i.toString() - ) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignatures[i - 1], - performedQueryCount: performedQueryCount + 1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - expect(res.get(KEY_VERSION_HEADER)).toEqual(i.toString()) - }) - } - - it('Should respond with 200 and warning on repeated valid requests', async () => { - const req = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res1 = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_SIGN) - expect(res1.status).toBe(200) - expect(res1.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: performedQueryCount + 1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - const res2 = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_SIGN) - expect(res2.status).toBe(200) - res1.body.warnings.push(WarningMessage.DUPLICATE_REQUEST_TO_GET_PARTIAL_SIG) - expect(res2.body).toStrictEqual(res1.body) - }) - - it('Should respond with 200 on extra request fields', async () => { - const req = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - // @ts-ignore Intentionally adding an extra field to the request type - req.extraField = 'dummyString' - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_SIGN) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: performedQueryCount + 1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - }) - - it('Should respond with 400 on missing request fields', async () => { - const badRequest = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - // @ts-ignore Intentionally deleting required field - delete badRequest.account - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await sendRequest(badRequest, authorization, SignerEndpoint.LEGACY_PNP_SIGN) - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on invalid key version', async () => { - const badRequest = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await sendRequest( - badRequest, - authorization, - SignerEndpoint.LEGACY_PNP_SIGN, - 'a' - ) - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_KEY_VERSION_REQUEST, - }) - }) - - it('Should respond with 400 on invalid identifier', async () => { - const badRequest = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - '+1234567890' - ) - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await sendRequest(badRequest, authorization, SignerEndpoint.LEGACY_PNP_SIGN) - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on invalid blinded message', async () => { - const badRequest = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - '+1234567890', - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await sendRequest(badRequest, authorization, SignerEndpoint.LEGACY_PNP_SIGN) - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 400 on invalid address', async () => { - const badRequest = getLegacyPnpSignRequest( - '0xnotanaddress', - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await sendRequest(badRequest, authorization, SignerEndpoint.LEGACY_PNP_SIGN) - expect(res.status).toBe(400) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.INVALID_INPUT, - }) - }) - - it('Should respond with 401 on failed WALLET_KEY auth', async () => { - const badRequest = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const differentPk = '0x00000000000000000000000000000000000000000000000000000000ddddbbbb' - const authorization = getPnpRequestAuthorization(badRequest, differentPk) - const res = await sendRequest(badRequest, authorization, SignerEndpoint.LEGACY_PNP_SIGN) - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 401 on failed DEK auth', async () => { - const badRequest = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.ENCRYPTION_KEY, - IDENTIFIER - ) - const differentPk = '0x00000000000000000000000000000000000000000000000000000000ddddbbbb' - const authorization = getPnpRequestAuthorization(badRequest, differentPk) - const res = await sendRequest(badRequest, authorization, SignerEndpoint.LEGACY_PNP_SIGN) - expect(res.status).toBe(401) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.UNAUTHENTICATED_USER, - }) - }) - - it('Should respond with 403 on out of quota', async () => { - // deplete user's quota - const remainingQuota = expectedQuota - performedQueryCount - await db.transaction(async (trx) => { - for (let i = 0; i < remainingQuota; i++) { - await incrementQueryCount( - db, - ACCOUNTS_TABLE.LEGACY, - ACCOUNT_ADDRESS1, - rootLogger(_config.serviceName), - trx - ) - } - }) - const req = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_SIGN) - expect(res.status).toBe(403) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - performedQueryCount: expectedQuota, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - error: WarningMessage.EXCEEDED_QUOTA, - }) - }) - - it('Should respond with 403 if totalQuota and performedQueryCount are zero', async () => { - await prepMocks(ACCOUNT_ADDRESS1, 0, 0, false, zeroBalance, zeroBalance, zeroBalance) - - const spy = jest // for convenience so we don't have to refactor or reset the db just for this test - .spyOn( - jest.requireActual('../../src/common/database/wrappers/account'), - 'getPerformedQueryCount' - ) - .mockResolvedValueOnce(0) - - const req = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_SIGN) - expect(res.status).toBe(403) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - performedQueryCount: 0, - totalQuota: 0, - blockNumber: testBlockNumber, - error: WarningMessage.EXCEEDED_QUOTA, - }) - - spy.mockRestore() - }) - - it('Should respond with 403 if performedQueryCount is greater than totalQuota', async () => { - const expectedRemainingQuota = expectedQuota - performedQueryCount - await db.transaction(async (trx) => { - for (let i = 0; i <= expectedRemainingQuota; i++) { - await incrementQueryCount( - db, - ACCOUNTS_TABLE.LEGACY, - ACCOUNT_ADDRESS1, - rootLogger(_config.serviceName), - trx - ) - } - }) - - // It is possible to reach this state due to our fail-open logic - - const req = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_SIGN) - expect(res.status).toBe(403) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - performedQueryCount: expectedQuota + 1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - error: WarningMessage.EXCEEDED_QUOTA, - }) - }) - - it('Should respond with 500 on unsupported key version', async () => { - const badRequest = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(badRequest, PRIVATE_KEY1) - const res = await sendRequest( - badRequest, - authorization, - SignerEndpoint.LEGACY_PNP_SIGN, - '4' - ) - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - performedQueryCount: performedQueryCount, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - error: ErrorMessage.SIGNATURE_COMPUTATION_FAILURE, - }) - }) - - it('Should respond with 503 on disabled api', async () => { - const configWithApiDisabled: typeof _config = JSON.parse(JSON.stringify(_config)) - configWithApiDisabled.api.legacyPhoneNumberPrivacy.enabled = false - const appWithApiDisabled = startSigner( - configWithApiDisabled, - db, - keyProvider, - newKit('dummyKit') - ) - - const req = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest( - req, - authorization, - SignerEndpoint.LEGACY_PNP_SIGN, - undefined, - appWithApiDisabled - ) - expect(res.status).toBe(503) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: WarningMessage.API_UNAVAILABLE, - }) - }) - - describe('interactions between legacy and new endpoints', () => { - const configWithPNPEnabled: typeof _config = JSON.parse(JSON.stringify(_config)) - configWithPNPEnabled.api.phoneNumberPrivacy.enabled = true - let appWithPNPEnabled: any - - beforeEach(() => { - appWithPNPEnabled = startSigner(configWithPNPEnabled, db, keyProvider, newKit('dummyKit')) - }) - - // Keep both of these cases with the legacy test suite - // since once this endpoint is deprecated, these tests will no longer be needed - it('Should not be affected by requests and queries from the new endpoint', async () => { - mockOdisPaymentsTotalPaidCUSD.mockReturnValue(new BigNumber(1e18)) - const expectedQuotaOnChain = 1000 - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest( - req, - authorization, - SignerEndpoint.PNP_SIGN, - undefined, - appWithPNPEnabled - ) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedQuotaOnChain, - blockNumber: testBlockNumber, - warnings: [], - }) - expect(res.get(KEY_VERSION_HEADER)).toEqual( - _config.keystore.keys.phoneNumberPrivacy.latest.toString() - ) - const legacyReq = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const legacyAuthorization = getPnpRequestAuthorization(legacyReq, PRIVATE_KEY1) - const legacyRes = await sendRequest( - legacyReq, - legacyAuthorization, - SignerEndpoint.LEGACY_PNP_SIGN, - undefined, - appWithPNPEnabled - ) - expect(legacyRes.status).toBe(200) - expect(legacyRes.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: performedQueryCount + 1, - totalQuota: legacyRes.body.totalQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - expect(legacyRes.get(KEY_VERSION_HEADER)).toEqual( - _config.keystore.keys.phoneNumberPrivacy.latest.toString() - ) - }) - - it('Should not affect the requests and queries to the new endpoint', async () => { - const legacyReq = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const legacyAuthorization = getPnpRequestAuthorization(legacyReq, PRIVATE_KEY1) - const legacyRes = await sendRequest( - legacyReq, - legacyAuthorization, - SignerEndpoint.LEGACY_PNP_SIGN, - undefined, - appWithPNPEnabled - ) - expect(legacyRes.status).toBe(200) - expect(legacyRes.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: performedQueryCount + 1, - totalQuota: legacyRes.body.totalQuota, - blockNumber: testBlockNumber, - warnings: [], - }) - expect(legacyRes.get(KEY_VERSION_HEADER)).toEqual( - _config.keystore.keys.phoneNumberPrivacy.latest.toString() - ) - mockOdisPaymentsTotalPaidCUSD.mockReturnValue(new BigNumber(1e18)) - const expectedQuotaOnChain = 1000 - const req = getPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest( - req, - authorization, - SignerEndpoint.PNP_SIGN, - undefined, - appWithPNPEnabled - ) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: 1, - totalQuota: expectedQuotaOnChain, - blockNumber: testBlockNumber, - warnings: [], - }) - expect(res.get(KEY_VERSION_HEADER)).toEqual( - _config.keystore.keys.phoneNumberPrivacy.latest.toString() - ) - }) - }) - - describe('functionality in case of errors', () => { - it('Should return 500 on DB performedQueryCount query failure', async () => { - // deplete user's quota - const remainingQuota = expectedQuota - performedQueryCount - await db.transaction(async (trx) => { - for (let i = 0; i < remainingQuota; i++) { - await incrementQueryCount( - db, - ACCOUNTS_TABLE.LEGACY, - ACCOUNT_ADDRESS1, - rootLogger(_config.serviceName), - trx - ) - } - }) - // sanity check - expect( - await getPerformedQueryCount( - db, - ACCOUNTS_TABLE.LEGACY, - ACCOUNT_ADDRESS1, - rootLogger(_config.serviceName) - ) - ).toBe(expectedQuota) - - const spy = jest - .spyOn( - jest.requireActual('../../src/common/database/wrappers/account'), - 'getPerformedQueryCount' - ) - .mockRejectedValueOnce(new Error()) - - const req = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_SIGN) - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - performedQueryCount: -1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - error: ErrorMessage.DATABASE_GET_FAILURE, - }) - - spy.mockRestore() - }) - - it('Should return 200 w/ warning on blockchain totalQuota query failure when shouldFailOpen is true', async () => { - const configWithFailOpenEnabled: typeof _config = JSON.parse(JSON.stringify(_config)) - configWithFailOpenEnabled.api.legacyPhoneNumberPrivacy.shouldFailOpen = true - const appWithFailOpenEnabled = startSigner( - configWithFailOpenEnabled, - db, - keyProvider, - newKit('dummyKit') - ) - - // deplete user's quota - const remainingQuota = expectedQuota - performedQueryCount - await db.transaction(async (trx) => { - for (let i = 0; i < remainingQuota; i++) { - await incrementQueryCount( - db, - ACCOUNTS_TABLE.LEGACY, - ACCOUNT_ADDRESS1, - rootLogger(_config.serviceName), - trx - ) - } - }) - // sanity check - expect( - await getPerformedQueryCount( - db, - ACCOUNTS_TABLE.LEGACY, - ACCOUNT_ADDRESS1, - rootLogger(_config.serviceName) - ) - ).toBe(expectedQuota) - - mockContractKit.connection.getTransactionCount.mockRejectedValue(new Error()) - - const req = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest( - req, - authorization, - SignerEndpoint.LEGACY_PNP_SIGN, - '1', - appWithFailOpenEnabled - ) - - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: expectedQuota + 1, // bc we depleted the user's quota above - totalQuota: Number.MAX_SAFE_INTEGER, - blockNumber: testBlockNumber, - warnings: [ErrorMessage.FAILURE_TO_GET_TOTAL_QUOTA, ErrorMessage.FULL_NODE_ERROR], - }) - - // check DB state: performedQueryCount was incremented and request was stored - expect( - await getPerformedQueryCount( - db, - ACCOUNTS_TABLE.LEGACY, - ACCOUNT_ADDRESS1, - rootLogger(_config.serviceName) - ) - ).toBe(expectedQuota + 1) - expect( - await getRequestExists( - db, - REQUESTS_TABLE.LEGACY, - req.account, - req.blindedQueryPhoneNumber, - rootLogger(_config.serviceName) - ) - ).toBe(true) - }) - - it('Should return 500 on blockchain totalQuota query failure when shouldFailOpen is false', async () => { - mockContractKit.connection.getTransactionCount.mockRejectedValue(new Error()) - - const configWithFailOpenDisabled: typeof _config = JSON.parse(JSON.stringify(_config)) - configWithFailOpenDisabled.api.legacyPhoneNumberPrivacy.shouldFailOpen = false - const appWithFailOpenDisabled = startSigner( - configWithFailOpenDisabled, - db, - keyProvider, - newKit('dummyKit') - ) - - const req = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest( - req, - authorization, - SignerEndpoint.LEGACY_PNP_SIGN, - '1', - appWithFailOpenDisabled - ) - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - performedQueryCount: performedQueryCount, - totalQuota: -1, - blockNumber: testBlockNumber, - error: ErrorMessage.FULL_NODE_ERROR, - }) - }) - - it('Should return 500 on failure to increment query count', async () => { - const logger = rootLogger(_config.serviceName) - const spy = jest - .spyOn( - jest.requireActual('../../src/common/database/wrappers/account'), - 'incrementQueryCount' - ) - .mockImplementationOnce(() => { - countAndThrowDBError(new Error(), logger, ErrorMessage.DATABASE_UPDATE_FAILURE) - }) - - const req = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_SIGN) - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.DATABASE_UPDATE_FAILURE, - }) - - spy.mockRestore() - - // check DB state: performedQueryCount was not incremented and request was not stored - expect( - await getPerformedQueryCount(db, ACCOUNTS_TABLE.LEGACY, ACCOUNT_ADDRESS1, logger) - ).toBe(performedQueryCount) - expect( - await getRequestExists( - db, - REQUESTS_TABLE.LEGACY, - req.account, - req.blindedQueryPhoneNumber, - logger - ) - ).toBe(false) - }) - - it('Should return 500 on failure to store request', async () => { - const logger = rootLogger(_config.serviceName) - const spy = jest - .spyOn(jest.requireActual('../../src/common/database/wrappers/request'), 'storeRequest') - .mockImplementationOnce(() => { - countAndThrowDBError(new Error(), logger, ErrorMessage.DATABASE_INSERT_FAILURE) - }) - - const req = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_SIGN) - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - error: ErrorMessage.DATABASE_INSERT_FAILURE, - }) - - spy.mockRestore() - - // check DB state: performedQueryCount was not incremented and request was not stored - expect( - await getPerformedQueryCount(db, ACCOUNTS_TABLE.LEGACY, ACCOUNT_ADDRESS1, logger) - ).toBe(performedQueryCount) - expect( - await getRequestExists( - db, - REQUESTS_TABLE.LEGACY, - req.account, - req.blindedQueryPhoneNumber, - logger - ) - ).toBe(false) - }) - - it('Should return 200 on failure to fetch DEK when shouldFailOpen is true', async () => { - mockGetDataEncryptionKey.mockImplementation(() => { - throw new Error() - }) - - const req = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.ENCRYPTION_KEY, - IDENTIFIER - ) - - const configWithFailOpenEnabled: typeof _config = JSON.parse(JSON.stringify(_config)) - configWithFailOpenEnabled.api.legacyPhoneNumberPrivacy.shouldFailOpen = true - const appWithFailOpenEnabled = startSigner( - configWithFailOpenEnabled, - db, - keyProvider, - newKit('dummyKit') - ) - - // NOT the dek private key, so authentication would fail if getDataEncryptionKey succeeded - const differentPk = '0x00000000000000000000000000000000000000000000000000000000ddddbbbb' - const authorization = getPnpRequestAuthorization(req, differentPk) - const res = await sendRequest( - req, - authorization, - SignerEndpoint.LEGACY_PNP_SIGN, - '1', - appWithFailOpenEnabled - ) - expect(res.status).toBe(200) - expect(res.body).toStrictEqual({ - success: true, - version: expectedVersion, - signature: expectedSignature, - performedQueryCount: performedQueryCount + 1, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - warnings: [ErrorMessage.FAILURE_TO_GET_DEK, ErrorMessage.FAILING_OPEN], - }) - }) - - it('Should return 500 on bls signing error', async () => { - const spy = jest - .spyOn(jest.requireActual('blind-threshold-bls'), 'partialSignBlindedMessage') - .mockImplementationOnce(() => { - throw new Error() - }) - - const req = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_SIGN) - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - performedQueryCount: performedQueryCount, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - error: ErrorMessage.SIGNATURE_COMPUTATION_FAILURE, - }) - - spy.mockRestore() - - // check DB state: performedQueryCount was not incremented and request was not stored - expect( - await getPerformedQueryCount( - db, - ACCOUNTS_TABLE.LEGACY, - ACCOUNT_ADDRESS1, - rootLogger(_config.serviceName) - ) - ).toBe(performedQueryCount) - expect( - await getRequestExists( - db, - REQUESTS_TABLE.LEGACY, - req.account, - req.blindedQueryPhoneNumber, - rootLogger(_config.serviceName) - ) - ).toBe(false) - }) - - it('Should return 500 on generic error in sign', async () => { - const spy = jest - .spyOn( - jest.requireActual('../../src/common/bls/bls-cryptography-client'), - 'computeBlindedSignature' - ) - .mockImplementationOnce(() => { - // Trigger a generic error in .sign to trigger the default error returned. - throw new Error() - }) - - const req = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY, - IDENTIFIER - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest(req, authorization, SignerEndpoint.LEGACY_PNP_SIGN) - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - version: expectedVersion, - performedQueryCount: performedQueryCount, - totalQuota: expectedQuota, - blockNumber: testBlockNumber, - error: ErrorMessage.SIGNATURE_COMPUTATION_FAILURE, - }) - - spy.mockRestore() - - // check DB state: performedQueryCount was not incremented and request was not stored - expect( - await getPerformedQueryCount( - db, - ACCOUNTS_TABLE.LEGACY, - ACCOUNT_ADDRESS1, - rootLogger(config.serviceName) - ) - ).toBe(performedQueryCount) - expect( - await getRequestExists( - db, - REQUESTS_TABLE.LEGACY, - req.account, - req.blindedQueryPhoneNumber, - rootLogger(config.serviceName) - ) - ).toBe(false) - }) - - it('Should respond with 500 on signer timeout', async () => { - const testTimeoutMS = 0 - const delay = 200 - const spy = jest - .spyOn( - jest.requireActual('../../src/common/database/wrappers/account'), - 'getPerformedQueryCount' - ) - .mockImplementationOnce(async () => { - await new Promise((resolve) => setTimeout(resolve, testTimeoutMS + delay)) - return performedQueryCount - }) - - const configWithShortTimeout = JSON.parse(JSON.stringify(_config)) - configWithShortTimeout.timeout = testTimeoutMS - const appWithShortTimeout = startSigner( - configWithShortTimeout, - db, - keyProvider, - newKit('dummyKit') - ) - - const req = getLegacyPnpSignRequest( - ACCOUNT_ADDRESS1, - BLINDED_PHONE_NUMBER, - AuthenticationMethod.WALLET_KEY - ) - const authorization = getPnpRequestAuthorization(req, PRIVATE_KEY1) - const res = await sendRequest( - req, - authorization, - SignerEndpoint.LEGACY_PNP_SIGN, - undefined, - appWithShortTimeout - ) - - expect(res.status).toBe(500) - expect(res.body).toStrictEqual({ - success: false, - error: ErrorMessage.TIMEOUT_FROM_SIGNER, - version: expectedVersion, - }) - spy.mockRestore() - // Allow time for non-killed processes to finish - await new Promise((resolve) => setTimeout(resolve, delay)) - // Check that DB was not updated - expect( - await getPerformedQueryCount( - db, - ACCOUNTS_TABLE.LEGACY, - ACCOUNT_ADDRESS1, - rootLogger(config.serviceName) - ) - ).toBe(performedQueryCount) - expect( - await getRequestExists( - db, - REQUESTS_TABLE.LEGACY, - req.account, - req.blindedQueryPhoneNumber, - rootLogger(config.serviceName) - ) - ).toBe(false) - }) - }) - }) - }) -}) diff --git a/packages/sdk/identity/src/odis/identifier.ts b/packages/sdk/identity/src/odis/identifier.ts index ac8be6e91a1..9f2e8ddb2ca 100644 --- a/packages/sdk/identity/src/odis/identifier.ts +++ b/packages/sdk/identity/src/odis/identifier.ts @@ -100,7 +100,6 @@ export interface IdentifierHashDetails { * @param blsBlindingClient Optional Performs blinding and unblinding, defaults to WasmBlsBlindingClient * @param sessionID Optional Used to track user sessions across the client and ODIS * @param keyVersion Optional For testing. Specifies which version key ODIS should use - * @param endpoint Optional Allows client to specify the legacy endpoint if they desire (will be deprecated) * @param abortController Optional Allows client to specify a timeout for the ODIS request */ export async function getObfuscatedIdentifier( @@ -114,7 +113,6 @@ export async function getObfuscatedIdentifier( blsBlindingClient?: BlsBlindingClient, sessionID?: string, keyVersion?: number, - endpoint?: CombinerEndpointPNP.LEGACY_PNP_SIGN | CombinerEndpointPNP.PNP_SIGN, abortController?: AbortController ): Promise { debug('Getting identifier pepper') @@ -147,7 +145,6 @@ export async function getObfuscatedIdentifier( clientVersion, sessionID, keyVersion, - endpoint ?? CombinerEndpointPNP.PNP_SIGN, abortController ) @@ -202,7 +199,6 @@ export async function getBlindedIdentifier( * @param clientVersion Optional Specifies the client software version * @param sessionID Optional Used to track user sessions across the client and ODIS * @param keyVersion Optional For testing. Specifies which version key ODIS should use - * @param endpoint Optional Allows client to specify the legacy endpoint if they desire (will be deprecated) * @param abortController Optional Allows client to specify a timeout for the ODIS request */ export async function getBlindedIdentifierSignature( @@ -213,7 +209,6 @@ export async function getBlindedIdentifierSignature( clientVersion?: string, sessionID?: string, keyVersion?: number, - endpoint?: CombinerEndpointPNP.LEGACY_PNP_SIGN | CombinerEndpointPNP.PNP_SIGN, abortControlller?: AbortController ): Promise { const body: SignMessageRequest = { @@ -227,7 +222,7 @@ export async function getBlindedIdentifierSignature( const response = await queryOdis( body, context, - endpoint ?? CombinerEndpointPNP.PNP_SIGN, + CombinerEndpointPNP.PNP_SIGN, SignMessageResponseSchema, { [KEY_VERSION_HEADER]: keyVersion?.toString(), diff --git a/packages/sdk/identity/src/odis/phone-number-identifier.ts b/packages/sdk/identity/src/odis/phone-number-identifier.ts index cc4871dbfa7..97b32d82ae1 100644 --- a/packages/sdk/identity/src/odis/phone-number-identifier.ts +++ b/packages/sdk/identity/src/odis/phone-number-identifier.ts @@ -1,4 +1,3 @@ -import { CombinerEndpointPNP } from '@celo/phone-number-privacy-common' import BigNumber from 'bignumber.js' import debugFactory from 'debug' import { BlsBlindingClient } from './bls-blinding-client' @@ -39,8 +38,7 @@ export async function getPhoneNumberIdentifier( clientVersion?: string, blsBlindingClient?: BlsBlindingClient, sessionID?: string, - keyVersion?: number, - endpoint?: CombinerEndpointPNP.LEGACY_PNP_SIGN | CombinerEndpointPNP.PNP_SIGN + keyVersion?: number ): Promise { debug('Getting phone number pepper') @@ -55,8 +53,7 @@ export async function getPhoneNumberIdentifier( clientVersion, blsBlindingClient, sessionID, - keyVersion, - endpoint + keyVersion ) return { e164Number: plaintextIdentifier, @@ -92,8 +89,7 @@ export async function getBlindedPhoneNumberSignature( base64BlindedMessage: string, clientVersion?: string, sessionID?: string, - keyVersion?: number, - endpoint?: CombinerEndpointPNP.LEGACY_PNP_SIGN | CombinerEndpointPNP.PNP_SIGN + keyVersion?: number ): Promise { return getBlindedIdentifierSignature( account, @@ -102,8 +98,7 @@ export async function getBlindedPhoneNumberSignature( base64BlindedMessage, clientVersion, sessionID, - keyVersion, - endpoint + keyVersion ) }