Skip to content

Commit

Permalink
Update ODIS SDK to accept any type of identifier (#9985)
Browse files Browse the repository at this point in the history
* wip

* general identifier odis sdk

* update tests

* add fn back in for backwards compatability

* no salt only pepper

* fix tests

* rename identifiers

* fix tests

* PR comments

* Merge branch 'master' of github.com:celo-org/celo-monorepo into isabellewei/allIdentifiers

* update test

* get rid of erroneously committed file

* add prefix when blinding

* add backwards compatibility test and fix existing tests

* update dependency graph

* add comment

* PR comments

* PR comments

* ensure backwards compatibility

* update dep graph

* hardcode expected values in backwards compatibility test

* nit: remove newline

Co-authored-by: Victor Graf <victor@clabs.co>

* make comments TSDoc compatible

Co-authored-by: Victor Graf <victor@clabs.co>

* PR comments

* update docstrings and enforce prefix type

* add yarn.lock

* revert identity pkg version in attestation-service

* use latest identity pkg version pre identifier change in attestation-service

* yarn.lock

* update test value for nested hashing

Co-authored-by: Victor Graf <victor@clabs.co>
Co-authored-by: Alec Schaefer <alec@cLabs.co>
  • Loading branch information
3 people authored Dec 8, 2022
1 parent 80a7390 commit 4041432
Show file tree
Hide file tree
Showing 8 changed files with 736 additions and 94 deletions.
2 changes: 1 addition & 1 deletion packages/attestation-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
},
"dependencies": {
"@celo/contractkit": "3.0.1-dev",
"@celo/identity": "3.0.1-dev",
"@celo/identity": "3.0.0",
"@celo/keystores": "3.0.1-dev",
"@celo/phone-utils": "3.0.1-dev",
"bignumber.js": "^9.0.0",
Expand Down
5 changes: 3 additions & 2 deletions packages/sdk/identity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"docs": "typedoc",
"test:reset": "yarn --cwd ../../protocol devchain generate-tar .tmp/devchain.tar.gz --migration_override ../../dev-utils/src/migration-override.json --upto 28",
"test:livechain": "yarn --cwd ../../protocol devchain run-tar .tmp/devchain.tar.gz",
"test": "jest --runInBand",
"test": "jest --runInBand --testPathIgnorePatterns src/odis/identifier-backwards-compatibility.test.ts",
"lint": "tslint -c tslint.json --project .",
"prepublishOnly": "yarn build"
},
Expand All @@ -44,7 +44,8 @@
"fetch-mock": "9.10.4",
"@types/elliptic": "^6.4.12",
"@celo/flake-tracker": "0.0.1-dev",
"@celo/ganache-cli": "git+https://github.com/celo-org/ganache-cli.git#21652da"
"@celo/ganache-cli": "git+https://github.com/celo-org/ganache-cli.git#21652da",
"old-identity-sdk": "npm:@celo/identity@1.5.2"
},
"engines": {
"node": ">=12.9.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { getPhoneHash } from '@celo/base'
import { soliditySha3 } from '@celo/utils/lib/solidity'
import { OdisUtils } from 'old-identity-sdk'
import { WasmBlsBlindingClient } from './bls-blinding-client'
import {
getBlindedIdentifier,
getIdentifierHash,
getObfuscatedIdentifier,
IdentifierPrefix,
} from './identifier'
import { AuthenticationMethod, AuthSigner, getServiceContext } from './query'

const mockE164Number = '+14155550000'
const mockAccount = '0x755dB5fF7B82e9a96e0dDDD143293dc2ADeC0050'
// const mockPrivateKey = '0x2cacaf965ae80da49d5b1fc4b4c9b08ffc35971a584aedcc1cb8322b9d5fd9c9'

// this DEK has been registered to the above account on alfajores
const dekPrivateKey = '0xc2bbdabb440141efed205497a41d5fb6114e0435fd541e368dc628a8e086bfee'
// const dekPublicKey = '0xc2bbdabb440141efed205497a41d5fb6114e0435fd541e368dc628a8e086bfee'

const authSigner: AuthSigner = {
authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY,
rawKey: dekPrivateKey,
}
const oldServiceContext = OdisUtils.Query.getServiceContext('alfajores')
const currentServiceContext = getServiceContext('alfajores')

const expectedObfuscatedIdentifier =
'0xf82c6272fd57d3e5d4e291be16b3ebac5c616084a5e6f3730c73f62efd39c6ae'
const expectedPepper = 'Pi4Z1NQnfsdvJ'

describe('backwards compatibility of phone number identifiers', () => {
beforeAll(() => {
fetchMock.reset()
// disables the mock, lets all calls fall through to the actual network
fetchMock.spy()
})

it('should match when using EncryptionSigner', async () => {
const oldRes = await OdisUtils.PhoneNumberIdentifier.getPhoneNumberIdentifier(
mockE164Number,
mockAccount,
authSigner,
oldServiceContext
)

const currRes = await getObfuscatedIdentifier(
mockE164Number,
IdentifierPrefix.PHONE_NUMBER,
mockAccount,
authSigner,
currentServiceContext
)

expect(oldRes.e164Number).toEqual(currRes.plaintextIdentifier)
expect(oldRes.phoneHash).toEqual(expectedObfuscatedIdentifier)
expect(currRes.obfuscatedIdentifier).toEqual(expectedObfuscatedIdentifier)
expect(oldRes.pepper).toEqual(expectedPepper)
expect(currRes.pepper).toEqual(expectedPepper)
}, 20000)

it('blinded identifier should match', async () => {
const blsBlindingClient = new WasmBlsBlindingClient('')
const seed = Buffer.from(
'44714c0a2b2bacec757a67822a4fbbdfe043cca8c6ae936545ef992f246df1a9',
'hex'
)
const oldRes = await OdisUtils.PhoneNumberIdentifier.getBlindedPhoneNumber(
mockE164Number,
blsBlindingClient,
seed
)
const currentRes = await getBlindedIdentifier(
mockE164Number,
IdentifierPrefix.PHONE_NUMBER,
blsBlindingClient,
seed
)

const expectedBlindedIdentifier =
'fuN6SmbxkYBqVbKZu4SizdyDjavNLK/XguIlwsWUhsWA0hQDoZtsZjQCbXqTnUiA'

expect(oldRes).toEqual(expectedBlindedIdentifier)
expect(currentRes).toEqual(expectedBlindedIdentifier)
})

it('obfuscated identifier should match', async () => {
const sha3 = (v: string) => soliditySha3({ type: 'string', value: v })
const oldRes = getPhoneHash(sha3, mockE164Number, expectedPepper)

const currRes = getIdentifierHash(mockE164Number, IdentifierPrefix.PHONE_NUMBER, expectedPepper)

expect(oldRes).toEqual(expectedObfuscatedIdentifier)
expect(currRes).toEqual(expectedObfuscatedIdentifier)
})

it('should not match when different prefix used', async () => {
const oldRes = await OdisUtils.PhoneNumberIdentifier.getPhoneNumberIdentifier(
mockE164Number,
mockAccount,
authSigner,
oldServiceContext
)

const currRes = await getObfuscatedIdentifier(
mockE164Number,
'badPrefix',
mockAccount,
authSigner,
currentServiceContext
)

expect(oldRes.e164Number).toEqual(currRes.plaintextIdentifier)
expect(oldRes.phoneHash).not.toEqual(currRes.obfuscatedIdentifier)
expect(oldRes.pepper).not.toEqual(currRes.pepper)
})
})
156 changes: 156 additions & 0 deletions packages/sdk/identity/src/odis/identifier.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { CombinerEndpoint } from '@celo/phone-number-privacy-common'
import { WasmBlsBlindingClient } from './bls-blinding-client'
import {
getBlindedIdentifier,
getBlindedIdentifierSignature,
getObfuscatedIdentifier,
getObfuscatedIdentifierFromSignature,
getPepperFromThresholdSignature,
IdentifierPrefix,
} from './identifier'
import { AuthenticationMethod, EncryptionKeySigner, ErrorMessages, ServiceContext } from './query'

jest.mock('./bls-blinding-client', () => {
// tslint:disable-next-line:no-shadowed-variable
class WasmBlsBlindingClient {
blindMessage = (m: string) => m
unblindAndVerifyMessage = (m: string) => m
}
return {
WasmBlsBlindingClient,
}
})

const mockOffchainIdentifier = 'twitterHandle'
const mockAccount = '0x0000000000000000000000000000000000007E57'
const expectedIdentifierHash = '0x8d1f580d4e49568883df9092285c0f8336e50d592b944607a613aff804e0b48f'
const expectedPepper = 'nHIvMC9B4j2+H'

const serviceContext: ServiceContext = {
odisUrl: 'https://mockodis.com',
odisPubKey:
'7FsWGsFnmVvRfMDpzz95Np76wf/1sPaK0Og9yiB+P8QbjiC8FV67NBans9hzZEkBaQMhiapzgMR6CkZIZPvgwQboAxl65JWRZecGe5V3XO4sdKeNemdAZ2TzQuWkuZoA',
}
const endpoint = serviceContext.odisUrl + CombinerEndpoint.PNP_SIGN
const rawKey = '41e8e8593108eeedcbded883b8af34d2f028710355c57f4c10a056b72486aa04'

const authSigner: EncryptionKeySigner = {
authenticationMethod: AuthenticationMethod.ENCRYPTION_KEY,
rawKey,
}

describe(getObfuscatedIdentifier, () => {
afterEach(() => {
fetchMock.reset()
})

describe('Retrieves a pepper correctly', () => {
it('Using EncryptionKeySigner', async () => {
fetchMock.mock(endpoint, {
success: true,
signature: '0Uj+qoAu7ASMVvm6hvcUGx2eO/cmNdyEgGn0mSoZH8/dujrC1++SZ1N6IP6v2I8A',
performedQueryCount: 5,
totalQuota: 10,
version: '',
})

const blsBlindingClient = new WasmBlsBlindingClient(serviceContext.odisPubKey)
const base64BlindedMessage = await getBlindedIdentifier(
mockOffchainIdentifier,
IdentifierPrefix.TWITTER,
blsBlindingClient
)
const base64BlindSig = await getBlindedIdentifierSignature(
mockAccount,
authSigner,
serviceContext,
base64BlindedMessage
)
const base64UnblindedSig = await blsBlindingClient.unblindAndVerifyMessage(base64BlindSig)

await expect(
getObfuscatedIdentifier(
mockOffchainIdentifier,
IdentifierPrefix.TWITTER,
mockAccount,
authSigner,
serviceContext
)
).resolves.toMatchObject({
plaintextIdentifier: mockOffchainIdentifier,
pepper: expectedPepper,
obfuscatedIdentifier: expectedIdentifierHash,
unblindedSignature: base64UnblindedSig,
})
})

it('Preblinding the off-chain identifier', async () => {
fetchMock.mock(endpoint, {
success: true,
signature: '0Uj+qoAu7ASMVvm6hvcUGx2eO/cmNdyEgGn0mSoZH8/dujrC1++SZ1N6IP6v2I8A',
performedQueryCount: 5,
totalQuota: 10,
version: '',
})

const blsBlindingClient = new WasmBlsBlindingClient(serviceContext.odisPubKey)
const base64BlindedMessage = await getBlindedIdentifier(
mockOffchainIdentifier,
IdentifierPrefix.TWITTER,
blsBlindingClient
)

const base64BlindSig = await getBlindedIdentifierSignature(
mockAccount,
authSigner,
serviceContext,
base64BlindedMessage
)

const obfuscatedIdentifierDetails = await getObfuscatedIdentifierFromSignature(
mockOffchainIdentifier,
IdentifierPrefix.TWITTER,
base64BlindSig,
blsBlindingClient
)

expect(obfuscatedIdentifierDetails.obfuscatedIdentifier).toEqual(expectedIdentifierHash)
expect(obfuscatedIdentifierDetails.pepper).toEqual(expectedPepper)
})
})

it('Throws quota error', async () => {
fetchMock.mock(endpoint, 403)

await expect(
getObfuscatedIdentifier(
mockOffchainIdentifier,
IdentifierPrefix.PHONE_NUMBER,
mockAccount,
authSigner,
serviceContext
)
).rejects.toThrow(ErrorMessages.ODIS_QUOTA_ERROR)
})

it('Throws auth error', async () => {
fetchMock.mock(endpoint, 401)
await expect(
getObfuscatedIdentifier(
mockOffchainIdentifier,
IdentifierPrefix.PHONE_NUMBER,
mockAccount,
authSigner,
serviceContext
)
).rejects.toThrow(ErrorMessages.ODIS_AUTH_ERROR)
})
})

describe(getPepperFromThresholdSignature, () => {
it('Hashes sigs correctly', () => {
const base64Sig = 'vJeFZJ3MY5KlpI9+kIIozKkZSR4cMymLPh2GHZUatWIiiLILyOcTiw2uqK/LBReA'
const signature = Buffer.from(base64Sig, 'base64')
expect(getPepperFromThresholdSignature(signature)).toBe('piWqRHHYWtfg9')
})
})
Loading

0 comments on commit 4041432

Please sign in to comment.