Skip to content

Commit

Permalink
feat: light (offchain) DIDs (#408)
Browse files Browse the repository at this point in the history
* feat: add new LightDidDetails class and rename DidDetails to FullDidDetails

* feat: add function to create an off-chain DID from a seed or mnemonic

* chore: add comment as reminder to include encryption key and service endpoints as part of light DID identifier

* feat: add encryption keys and service endpoints encoding as part of light DID URI

* fix: compilation errors

* wip: add comments after sparring session

* feat: remove getNextTxIndex() from IDidDetails interface

* test: unit tests for off-chain DID creation complete

* feat: add versioning

* fix: add node types to compile DID package using cbor module

* fix: adjust cbor version to be compatible with Node 14

* chore: update SDK to 0.24.0-0

* feat: add off-chain DIDs to DID resolver (wip)

* test: unit tests for Full and Light DidDetails passing

* test: integration tests passing

* wip: add key encoding to light DID identifier

* test: unit tests for light DID resolution passing

* test: all unit tests are passing

* feat: change service di to only contain the service ID and not the DID as well

* test: adjust test cases and code in general (wip)

* chore: refactor some code, currently compiling

* test: all unit tests passing

* test: all integration tests passing

* chore: add documentation

* test: all unit tests passing after refactor

* test: add light DID unit tests to attested claims

* chore: lint

* fix: error lint

* chore: remove useless print statements in tests

* chore: cleaning up some stuff

* fix: remove v1 as default for DID version 1

* chore: apply fixes from review

* chore: re-add VC export unit tests

* chore: fix lint errors

* test: fix failing unit test

* fix: increase jest timeout to 15s due to parachain 12s slot
  • Loading branch information
ntn-x2 authored Sep 7, 2021
1 parent 0b0918e commit 7f0343c
Show file tree
Hide file tree
Showing 44 changed files with 1,495 additions and 515 deletions.
3 changes: 2 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ module.exports = {
testEnvironment: 'node',
clearMocks: true,
runner: 'groups',
testTimeout: 10000,
// Parachain block time is 12s
testTimeout: 15000,
setupFilesAfterEnv: ['../testingTools/setup.js'],
transformIgnorePatterns:['/node_modules/(?!@polkadot|@babel/runtime/helpers/esm/)'],
coverageThreshold: {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,6 @@
"typedoc-plugin-external-module-name": "^4.0.6",
"typescript": "^4.2.2"
},
"version": "0.22.2-2",
"version": "0.24.0-0",
"packageManager": "yarn@2.4.2"
}
2 changes: 1 addition & 1 deletion packages/chain-helpers/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@kiltprotocol/chain-helpers",
"version": "0.22.2-2",
"version": "0.24.0-0",
"description": "",
"main": "./lib/index.js",
"typings": "./lib/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/config/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@kiltprotocol/config",
"version": "0.22.2-2",
"version": "0.24.0-0",
"description": "",
"main": "./lib/index.js",
"typings": "./lib/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@kiltprotocol/core",
"version": "0.22.2-2",
"version": "0.24.0-0",
"description": "",
"main": "./lib/index.js",
"typings": "./lib/index.d.ts",
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/__integrationtests__/Attestation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { BlockchainUtils, ExtrinsicErrors } from '@kiltprotocol/chain-helpers'
import {
createOnChainDidFromSeed,
DemoKeystore,
DidDetails,
FullDidDetails,
} from '@kiltprotocol/did'
import { Crypto } from '@kiltprotocol/utils'
import { randomAsHex } from '@polkadot/util-crypto'
Expand All @@ -39,9 +39,9 @@ import '../../../../testingTools/jestErrorCodeMatcher'

let tokenHolder: KeyringPair
let signer: DemoKeystore
let attester: DidDetails
let anotherAttester: DidDetails
let claimer: DidDetails
let attester: FullDidDetails
let anotherAttester: FullDidDetails
let claimer: FullDidDetails

beforeAll(async () => {
await init({ address: WS_ADDRESS })
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/__integrationtests__/Ctypes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type { ICType } from '@kiltprotocol/types'
import { BlockchainUtils, ExtrinsicErrors } from '@kiltprotocol/chain-helpers'
import { KeyringPair } from '@polkadot/keyring/types'
import {
DidDetails,
FullDidDetails,
DemoKeystore,
createOnChainDidFromSeed,
} from '@kiltprotocol/did'
Expand All @@ -31,7 +31,7 @@ beforeAll(async () => {
})

describe('When there is an CtypeCreator and a verifier', () => {
let ctypeCreator: DidDetails
let ctypeCreator: FullDidDetails
let paymentAccount: KeyringPair
let ctypeCounter = 0
const keystore = new DemoKeystore()
Expand Down
14 changes: 7 additions & 7 deletions packages/core/src/__integrationtests__/Delegation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type { KeyringPair } from '@polkadot/keyring/types'
import {
createOnChainDidFromSeed,
DemoKeystore,
DidDetails,
FullDidDetails,
} from '@kiltprotocol/did'
import { randomAsHex } from '@polkadot/util-crypto'
import Attestation from '../attestation/Attestation'
Expand All @@ -30,12 +30,12 @@ import { getAttestationHashes } from '../delegation/DelegationNode.chain'

let paymentAccount: KeyringPair
let signer: DemoKeystore
let root: DidDetails
let claimer: DidDetails
let attester: DidDetails
let root: FullDidDetails
let claimer: FullDidDetails
let attester: FullDidDetails

async function writeHierarchy(
delegator: DidDetails,
delegator: FullDidDetails,
ctypeHash: ICType['hash']
): Promise<DelegationNode> {
const rootNode = DelegationNode.newRoot({
Expand All @@ -60,8 +60,8 @@ async function writeHierarchy(
async function addDelegation(
hierarchyId: IDelegationNode['id'],
parentId: DelegationNode['id'],
delegator: DidDetails,
delegee: DidDetails,
delegator: FullDidDetails,
delegee: FullDidDetails,
permissions: Permission[] = [Permission.ATTEST, Permission.DELEGATE]
): Promise<DelegationNode> {
const delegationNode = DelegationNode.newNode({
Expand Down
14 changes: 7 additions & 7 deletions packages/core/src/__integrationtests__/Did.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,15 @@ describe('write and didDeleteTx', () => {
await expect(DidChain.queryById(didIdentifier)).resolves.toMatchObject<
Partial<DidTypes.IDidChainRecordJSON>
>({
did: DidUtils.getKiltDidFromIdentifier(didIdentifier),
did: DidUtils.getKiltDidFromIdentifier(didIdentifier, 'full'),
})
}, 30_000)

it('deletes DID from previous step', async () => {
await expect(DidChain.queryById(didIdentifier)).resolves.toMatchObject<
Partial<DidTypes.IDidChainRecordJSON>
>({
did: DidUtils.getKiltDidFromIdentifier(didIdentifier),
did: DidUtils.getKiltDidFromIdentifier(didIdentifier, 'full'),
})

const call = await DidChain.getDeleteDidExtrinsic()
Expand Down Expand Up @@ -132,7 +132,7 @@ it('creates and updates DID', async () => {
await expect(DidChain.queryById(didIdentifier)).resolves.toMatchObject<
Partial<DidTypes.IDidChainRecordJSON>
>({
did: DidUtils.getKiltDidFromIdentifier(didIdentifier),
did: DidUtils.getKiltDidFromIdentifier(didIdentifier, 'full'),
endpointData: {
urls: ['https://example.com'],
contentType: 'application/json',
Expand Down Expand Up @@ -164,7 +164,7 @@ it('creates and updates DID', async () => {
await expect(DidChain.queryById(didIdentifier)).resolves.toMatchObject<
Partial<DidTypes.IDidChainRecordJSON>
>({
did: DidUtils.getKiltDidFromIdentifier(didIdentifier),
did: DidUtils.getKiltDidFromIdentifier(didIdentifier, 'full'),
endpointData: {
urls: ['ftp://example.com/abc'],
contentType: 'application/ld+json',
Expand All @@ -181,7 +181,7 @@ describe('DID authorization', () => {
const { publicKey, alg } = await keystore.generateKeypair({
alg: SigningAlgorithms.Ed25519,
})
didIdentifier = encodeAddress(publicKey)
didIdentifier = encodeAddress(publicKey, 38)
key = { publicKey, type: alg }
const tx = await DidChain.generateCreateTx({
didIdentifier,
Expand All @@ -202,13 +202,13 @@ describe('DID authorization', () => {
await expect(DidChain.queryById(didIdentifier)).resolves.toMatchObject<
Partial<DidTypes.IDidChainRecordJSON>
>({
did: DidUtils.getKiltDidFromIdentifier(didIdentifier),
did: DidUtils.getKiltDidFromIdentifier(didIdentifier, 'full'),
})
}, 30_000)

beforeEach(async () => {
lastTxIndex = await DidChain.queryLastTxIndex(
DidUtils.getKiltDidFromIdentifier(didIdentifier)
DidUtils.getKiltDidFromIdentifier(didIdentifier, 'full')
)
})

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/__integrationtests__/ErrorHandler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { KeyringPair } from '@polkadot/keyring/types'
import {
createOnChainDidFromSeed,
DemoKeystore,
DidDetails,
FullDidDetails,
} from '@kiltprotocol/did'
import { randomAsHex } from '@polkadot/util-crypto'
import { Attestation } from '..'
Expand All @@ -26,7 +26,7 @@ import { addressFromRandom, devAlice, WS_ADDRESS } from './utils'
import '../../../../testingTools/jestErrorCodeMatcher'

let paymentAccount: KeyringPair
let someDid: DidDetails
let someDid: FullDidDetails
const keystore = new DemoKeystore()

beforeAll(async () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/attestation/Attestation.chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ function decode(
claimHash,
cTypeHash: chainAttestation.ctypeHash.toString(),
owner: DidUtils.getKiltDidFromIdentifier(
chainAttestation.attester.toString()
chainAttestation.attester.toString(),
'full'
),
delegationId:
chainAttestation.delegationId.unwrapOr(null)?.toString() || null,
Expand Down
73 changes: 70 additions & 3 deletions packages/core/src/attestedclaim/AttestedClaim.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ import type {
IDidDetails,
IDidResolver,
} from '@kiltprotocol/types'
import { DemoKeystore, createLocalDemoDidFromSeed } from '@kiltprotocol/did'
import {
DemoKeystore,
createLocalDemoDidFromSeed,
createLightDidFromSeed,
} from '@kiltprotocol/did'
import { UUID } from '@kiltprotocol/utils'
import Attestation from '../attestation/Attestation'
import Claim from '../claim/Claim'
Expand Down Expand Up @@ -120,7 +124,7 @@ describe('RequestForAttestation', () => {
]
})

it('verify attested claims', async () => {
it('verify attested claims signed by a full DID', async () => {
const attestedClaim = await buildAttestedClaim(
identityCharlie,
identityAlice,
Expand All @@ -141,6 +145,28 @@ describe('RequestForAttestation', () => {
AttestedClaim.verify(attestedClaim, { claimerDid: identityCharlie })
).resolves.toBe(true)
})
it('verify attested claims signed by a light DID', async () => {
const identityDave = await createLightDidFromSeed(keystore, '//Dave')
const attestedClaim = await buildAttestedClaim(
identityDave,
identityAlice,
{
a: 'a',
b: 'b',
c: 'c',
},
[legitimation],
keystore
)

;(query as jest.Mock).mockResolvedValue(attestedClaim.attestation)

// check proof on complete data
expect(AttestedClaim.verifyData(attestedClaim)).toBeTruthy()
await expect(
AttestedClaim.verify(attestedClaim, { claimerDid: identityDave })
).resolves.toBe(true)
})

it('compresses and decompresses the attested claims object', () => {
expect(AttestedClaimUtils.compress(legitimation)).toEqual(
Expand Down Expand Up @@ -269,7 +295,7 @@ describe('create presentation', () => {
expect(cred).toBeDefined()
})

it('should create presentation and exclude specific attributes', async () => {
it('should create presentation and exclude specific attributes using a full DID', async () => {
const mockResolver: IDidResolver = {
resolveDoc: async (did: string) => {
if (did.startsWith(claimer.did)) return claimer
Expand All @@ -293,6 +319,47 @@ describe('create presentation', () => {
).resolves.toBe(true)
expect(att.request.claimerSignature?.challenge).toEqual(challenge)
})
it('should create presentation and exclude specific attributes using a light DID', async () => {
claimer = await createLightDidFromSeed(keystore, '//Attester')
const rawCType: ICType['schema'] = {
$id: 'kilt:ctype:0x1',
$schema: 'http://kilt-protocol.org/draft-01/ctype#',
title: 'credential',
properties: {
name: { type: 'string' },
},
type: 'object',
}
ctype = CType.fromSchema(rawCType, attester.did)

// cannot be used since the variable needs to be established in the outer scope
reqForAtt = RequestForAttestation.fromClaim(
Claim.fromCTypeAndClaimContents(
ctype,
{
name: 'Peter',
age: 12,
},
claimer.did
)
)

attestation = Attestation.fromRequestAndDid(reqForAtt, attester.did)
;(query as jest.Mock).mockResolvedValue(attestation)

const cred = AttestedClaim.fromRequestAndAttestation(reqForAtt, attestation)

const challenge = UUID.generate()
const att = await cred.createPresentation({
selectedAttributes: ['name'],
signer: keystore,
claimerDid: claimer,
challenge,
})
expect(att.getAttributes()).toEqual(new Set(['name']))
await expect(AttestedClaim.verify(att)).resolves.toBe(true)
expect(att.request.claimerSignature?.challenge).toEqual(challenge)
})

it('should get attribute keys', async () => {
const cred = AttestedClaim.fromRequestAndAttestation(reqForAtt, attestation)
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/ctype/CType.chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export async function store(ctype: ICType): Promise<SubmittableExtrinsic> {
export function decode(encoded: Option<AccountId>): IDidDetails['did'] | null {
DecoderUtils.assertCodecIsType(encoded, ['Option<CtypeCreatorOf>'])
return encoded.isSome
? DidUtils.getKiltDidFromIdentifier(encoded.unwrap().toString())
? DidUtils.getKiltDidFromIdentifier(encoded.unwrap().toString(), 'full')
: null
}

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/ctype/CType.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ describe('CType', () => {
`)
expect(() =>
CType.fromCType(faultyAddressTypeCType)
).toThrowErrorMatchingInlineSnapshot(`"Not a KILT did: 4262626426"`)
).toThrowErrorMatchingInlineSnapshot(`"Not a valid KILT did: 4262626426"`)
expect(() =>
CType.fromCType(wrongSchemaIdCType)
).toThrowErrorMatchingInlineSnapshot(
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/delegation/DelegationDecoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ export function decodeDelegationNode(
: undefined,
childrenIds: [...delegationNode.children.keys()].map((id) => id.toHex()),
account: DidUtils.getKiltDidFromIdentifier(
delegationNode.details.owner.toString()
delegationNode.details.owner.toString(),
'full'
),
permissions: decodePermissions(
delegationNode.details.permissions.toNumber()
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/delegation/DelegationNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
} from '@kiltprotocol/types'
import { Crypto, SDKErrors, UUID } from '@kiltprotocol/utils'
import { ConfigService } from '@kiltprotocol/config'
import { DidTypes, DidDetailsUtils } from '@kiltprotocol/did'
import { DidTypes, DidUtils } from '@kiltprotocol/did'
import type { DelegationHierarchyDetailsRecord } from './DelegationDecoder'
import { query as queryAttestation } from '../attestation/Attestation.chain'
import {
Expand Down Expand Up @@ -275,7 +275,7 @@ export default class DelegationNode implements IDelegationNode {
delegeeDid: IDidDetails,
signer: KeystoreSigner
): Promise<DidTypes.SignatureEnum> {
const { alg, signature } = await DidDetailsUtils.signWithDid(
const { alg, signature } = await DidUtils.signWithDid(
Crypto.coToUInt8(this.generateHash()),
delegeeDid,
signer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import type {
} from '@kiltprotocol/types'
import { KeyRelationship } from '@kiltprotocol/types'
import { Crypto, SDKErrors } from '@kiltprotocol/utils'
import { DefaultResolver, DidUtils, DidDetailsUtils } from '@kiltprotocol/did'
import { DefaultResolver, DidUtils } from '@kiltprotocol/did'
import ClaimUtils from '../claim/Claim.utils'
import AttestedClaim from '../attestedclaim/AttestedClaim'
import RequestForAttestationUtils from './RequestForAttestation.utils'
Expand Down Expand Up @@ -320,7 +320,7 @@ export default class RequestForAttestation implements IRequestForAttestation {
did: IDidDetails,
challenge?: string
): Promise<this> {
const { signature, keyId } = await DidDetailsUtils.signWithDid(
const { signature, keyId } = await DidUtils.signWithDid(
makeSigningData(this, challenge),
did,
signer,
Expand All @@ -334,7 +334,7 @@ export default class RequestForAttestation implements IRequestForAttestation {
key: IDidKeyDetails,
challenge?: string
): Promise<this> {
const { signature } = await DidDetailsUtils.signWithKey(
const { signature } = await DidUtils.signWithKey(
makeSigningData(this, challenge),
key,
signer
Expand Down
Loading

0 comments on commit 7f0343c

Please sign in to comment.