diff --git a/tests/bundle/bundle-test.ts b/tests/bundle/bundle-test.ts
index ca433f0c6..53a72289f 100644
--- a/tests/bundle/bundle-test.ts
+++ b/tests/bundle/bundle-test.ts
@@ -7,117 +7,17 @@
///
-import type { ApiPromise } from '@polkadot/api'
-import type {
- Did,
- DidDocument,
- DidUrl,
- KiltAddress,
- SignerInterface,
- SubmittableExtrinsic,
-} from '@kiltprotocol/types'
-
-const { kilt } = window
-
-const {
- ConfigService,
- Issuer,
- Verifier,
- Holder,
- DidResolver,
- signAndSubmitTx,
- getSignersForKeypair,
-} = kilt
-
-async function authorizeTx(
- api: ApiPromise,
- call: SubmittableExtrinsic,
- did: string,
- signer: SignerInterface,
- submitter: string,
- nonce = 1
-) {
- let authorized = api.tx.did.submitDidCall(
- {
- did: did.slice(9),
- call,
- blockNumber: await api.query.system.number(),
- submitter,
- txCounter: nonce,
- },
- { ed25519: new Uint8Array(64) }
- )
-
- const signature = await signer.sign({ data: authorized.args[0].toU8a() })
-
- authorized = api.tx.did.submitDidCall(authorized.args[0].toU8a(), {
- ed25519: signature,
- })
+import type { KiltAddress, SignerInterface } from '@kiltprotocol/types'
- return authorized
-}
-
-async function createFullDid(
- payer: SignerInterface<'Ed25519' | 'Sr25519', KiltAddress>,
- keypair: { publicKey: Uint8Array; secretKey: Uint8Array }
-) {
- const api = ConfigService.get('api')
-
- const [signer] = await getSignersForKeypair({
- keypair,
- type: 'Ed25519',
- })
- const address = signer.id
- const getSigners: (
- didDocument: DidDocument
- ) => Array> = (didDocument) => {
- return (
- didDocument.verificationMethod?.map<
- Array>
- >(({ id }) => [
- {
- ...signer,
- id,
- },
- ]) ?? []
- ).flat()
- }
+const { kilt: Kilt } = window
- let tx = api.tx.did.create(
- {
- did: address,
- submitter: payer.id,
- newAttestationKey: { ed25519: keypair.publicKey },
- },
- { ed25519: new Uint8Array(64) }
- )
-
- const signature = await signer.sign({ data: tx.args[0].toU8a() })
- tx = api.tx.did.create(tx.args[0].toU8a(), { ed25519: signature })
-
- await signAndSubmitTx(tx, payer)
-
- const { didDocument } = await DidResolver.resolve(
- `did:kilt:${address}` as Did,
- {}
- )
- if (!didDocument) {
- throw new Error(`failed to create did for account ${address}`)
- }
+async function runAll() {
+ const api = await Kilt.connect('ws://127.0.0.1:9944')
- return {
- didDocument,
- getSigners,
- address,
- }
-}
+ console.log('connected')
-async function runAll() {
- // init sdk kilt config and connect to chain
- const api = await kilt.connect('ws://127.0.0.1:9944')
+ const authenticationKeyPair = Kilt.generateKeypair({ type: 'ed25519' })
- // Accounts
- console.log('Account setup started')
const faucet = {
publicKey: new Uint8Array([
238, 93, 102, 137, 215, 142, 38, 187, 91, 53, 176, 68, 23, 64, 160, 101,
@@ -129,214 +29,344 @@ async function runAll() {
3,
]),
}
- const [payerSigner] = (await getSignersForKeypair({
+
+ const [submitter] = (await Kilt.getSignersForKeypair({
keypair: faucet,
type: 'Ed25519',
})) as Array>
- console.log('faucet signer created')
+ console.log('keypair generation complete')
+
+ // ┏━━━━━━━━━━━━┓
+ // ┃ create DID ┃
+ // ┗━━━━━━━━━━━━┛
+ //
+ // Generate the DID-signed creation tx and submit it to the blockchain with the specified account.
+ // The DID Document will have one Verification Key with an authentication relationship.
+ //
+ // Note the following parameters:
+ // - `api`: The connected blockchain api.
+ // - `signers`: The keys for verification materials inside the DID Document. For creating a DID,
+ // only the key for the authentication verification method is required.
+ // - `submitter`: The account used to submit the transaction to the blockchain. Note: the submitter account must have
+ // enough funds to cover the required storage deposit.
+ // - `fromPublicKey`: The public key that will feature as the DID's initial authentication method and will determine the DID identifier.
+
+ const transactionHandler = Kilt.DidHelpers.createDid({
+ api,
+ signers: [authenticationKeyPair],
+ submitter,
+ fromPublicKey: authenticationKeyPair.publicKeyMultibase,
+ })
- const { didDocument: alice, getSigners: aliceSign } = await createFullDid(
- payerSigner,
- {
- publicKey: new Uint8Array([
- 136, 220, 52, 23, 213, 5, 142, 196, 180, 80, 62, 12, 18, 234, 26, 10,
- 137, 190, 32, 15, 233, 137, 34, 66, 61, 67, 52, 1, 79, 166, 176, 238,
- ]),
- secretKey: new Uint8Array([
- 171, 248, 229, 189, 190, 48, 198, 86, 86, 192, 163, 203, 209, 129, 255,
- 138, 86, 41, 74, 105, 223, 237, 210, 121, 130, 170, 206, 74, 118, 144,
- 145, 21,
- ]),
- }
- )
- console.log('alice setup done')
+ // The `createDid` function returns a transaction handler, which includes two methods:
+ // - `submit`: Submits a transaction for inclusion in a block, resulting in its execution in the blockchain runtime.
+ // - `getSubmittable`: Produces transaction that can be submitted to a blockchain node for inclusion, or signed and submitted by an external service.
- const { didDocument: bob, getSigners: bobSign } = await createFullDid(
- payerSigner,
- {
- publicKey: new Uint8Array([
- 209, 124, 45, 120, 35, 235, 242, 96, 253, 19, 143, 45, 126, 39, 209, 20,
- 192, 20, 93, 150, 139, 95, 245, 0, 97, 37, 242, 65, 79, 173, 174, 105,
- ]),
- secretKey: new Uint8Array([
- 59, 123, 96, 175, 42, 188, 213, 123, 164, 1, 171, 57, 143, 132, 244,
- 202, 84, 189, 107, 33, 64, 210, 80, 63, 188, 243, 40, 101, 53, 254, 63,
- 241,
- ]),
- }
- )
+ // Submit transaction.
+ // Note: `submit()` by default, waits for the block to be finalized. This behaviour can be overwritten
+ // in the function's optional parameters.
+ const didDocumentTransactionResult = await transactionHandler.submit()
- console.log('bob setup done')
+ // Once the transaction is submitted, the result should be checked.
+ // For the sake of this example, we will only check if the transaction went through.
+ if (didDocumentTransactionResult.status !== 'confirmed') {
+ throw new Error('create DID failed')
+ }
- // Light DID Account creation workflow
- const authPublicKey =
- '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
+ // Get the DID Document from the transaction result.
+ let { didDocument, signers } = didDocumentTransactionResult.asConfirmed
+
+ console.log('Did created')
+
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+ // ┃ Create Verification Method ┃
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+ //
+ // - `DidHelpers` include a function to add a verification methods.
+ // Similar to `createDid`, setting a verification method requires some parameters.
+ //
+ // - `didDocument` is the latest state of the DID Document that shall be updated.
+ // - `signers` includes all the keypairs included in the DID documents and necessary for the
+ // specified operation, in this case, the keypair of the authentication key, which is necessary to
+ // allow updates to the DID Document.
+ // - `publicKey` is the key used for the verification method.
+ //
+ // Note: setting a verification method will remove any existing method for the specified relationship.
+
+ // TODO: use mnemonic here.
+ const assertionKeyPair = Kilt.generateKeypair({
+ type: 'sr25519',
+ })
+ const vmTransactionResult = await Kilt.DidHelpers.setVerificationMethod({
+ api,
+ didDocument,
+ signers: [...signers, assertionKeyPair],
+ submitter,
+ publicKey: assertionKeyPair.publicKeyMultibase,
+ relationship: 'assertionMethod',
+ }).submit()
+
+ if (vmTransactionResult.status !== 'confirmed') {
+ throw new Error('add verification method failed')
+ }
+ ;({ didDocument, signers } = vmTransactionResult.asConfirmed)
- // const encPublicKey =
- // '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
+ console.log('assertion method added')
- const address = api.createType('Address', authPublicKey).toString()
- const resolved = await DidResolver.resolve(
- `did:kilt:light:01${address}:z1Ac9CMtYCTRWjetJfJqJoV7FcPDD9nHPHDHry7t3KZmvYe1HQP1tgnBuoG3enuGaowpF8V88sCxytDPDy6ZxhW` as Did,
- {}
- )
- if (
- !resolved.didDocument ||
- resolved.didDocument?.keyAgreement?.length !== 1
- ) {
- throw new Error('DID Test Unsuccessful')
- } else console.info(`light DID successfully resolved`)
-
- // Chain DID workflow -> creation & deletion
- console.log('DID workflow started')
- const {
- didDocument: fullDid,
- getSigners,
- address: didAddress,
- } = await createFullDid(payerSigner, {
- publicKey: new Uint8Array([
- 157, 198, 166, 93, 125, 173, 238, 122, 17, 146, 49, 238, 62, 111, 140, 45,
- 26, 6, 94, 42, 60, 167, 79, 19, 142, 20, 212, 5, 130, 44, 214, 190,
- ]),
- secretKey: new Uint8Array([
- 252, 195, 96, 143, 203, 194, 37, 74, 205, 243, 137, 71, 234, 82, 57, 46,
- 212, 14, 113, 177, 1, 241, 62, 118, 184, 230, 121, 219, 17, 45, 36, 143,
- ]),
- })
+ // ┏━━━━━━━━━━━━━━━━━┓
+ // ┃ Claim web3name ┃
+ // ┗━━━━━━━━━━━━━━━━━┛
+ const claimW3nTransactionResult = await Kilt.DidHelpers.claimWeb3Name({
+ api,
+ didDocument,
+ submitter,
+ signers,
+ name: 'example123',
+ }).submit()
- if (
- fullDid.authentication?.length === 1 &&
- fullDid.assertionMethod?.length === 1 &&
- fullDid.id.endsWith(didAddress)
- ) {
- console.info('DID matches')
- } else {
- throw new Error('DIDs do not match')
+ if (claimW3nTransactionResult.status !== 'confirmed') {
+ throw new Error('claim web3name failed')
}
- const deleteTx = await authorizeTx(
- api,
- api.tx.did.delete(0),
- fullDid.id,
- getSigners(fullDid)[0],
- payerSigner.id
- )
+ // The didDocument now contains an `alsoKnownAs` entry.
+ ;({ didDocument } = claimW3nTransactionResult.asConfirmed)
- await signAndSubmitTx(deleteTx, payerSigner)
+ console.log('w3n claimed')
- const resolvedAgain = await DidResolver.resolve(fullDid.id, {})
- if (resolvedAgain.didDocumentMetadata.deactivated) {
- console.info('DID successfully deleted')
- } else {
- throw new Error('DID was not deleted')
- }
+ // ┏━━━━━━━━━━━━━━━━┓
+ // ┃ Add a service ┃
+ // ┗━━━━━━━━━━━━━━━━┛
+ const addServiceTransactionResult = await Kilt.DidHelpers.addService({
+ api,
+ submitter,
+ signers,
+ didDocument,
+ // TODO: change service endpoint.
+ service: {
+ id: '#my_service',
+ type: ['http://schema.org/EmailService'],
+ serviceEndpoint: ['mailto:info@kilt.io'],
+ },
+ }).submit()
- // CType workflow
- console.log('CType workflow started')
+ if (addServiceTransactionResult.status !== 'confirmed') {
+ throw new Error('add service failed')
+ }
+ ;({ didDocument } = addServiceTransactionResult.asConfirmed)
+
+ console.log('service added')
+
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+ // ┃ Register a CType ┃
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+ //
+ // Register a credential type on chain so we can issue credentials against it.
+ //
+ // Note:
+ // We are registering a CType that has been created previously using functionality from the @kiltprotocol/credentials package.
+ // The @kiltprotocol/sdk-js package and bundle do not currently offer support for this.
+ //
+ // TODO: Decide if CType definitions are expected to be hardcoded in application logic, at least for credential issuance.
+ // Verifying credentials / presentations is already possible even if the CType definition is not known.
+ //
const DriversLicenseDef =
'{"$schema":"ipfs://bafybeiah66wbkhqbqn7idkostj2iqyan2tstc4tpqt65udlhimd7hcxjyq/","additionalProperties":false,"properties":{"age":{"type":"integer"},"name":{"type":"string"}},"title":"Drivers License","type":"object"}'
- const cTypeStoreTx = await authorizeTx(
+ const createCTypeResult = await Kilt.DidHelpers.transact({
api,
- api.tx.ctype.add(DriversLicenseDef),
- alice.id,
- aliceSign(alice)[0],
- payerSigner.id
- )
-
- const result = await signAndSubmitTx(cTypeStoreTx, payerSigner)
+ didDocument,
+ signers,
+ submitter,
+ call: api.tx.ctype.add(DriversLicenseDef),
+ expectedEvents: [{ section: 'CType', method: 'CTypeCreated' }],
+ }).submit()
+
+ if (createCTypeResult.status !== 'confirmed') {
+ throw new Error('CType creation failed')
+ }
- const ctypeHash = result.events
- ?.find((ev) => api.events.ctype.CTypeCreated.is(ev.event))
- ?.event.data[1].toHex()
+ // TODO: We don't have the CType id in the definition, so we need to get it from the events.
+ const ctypeHash = createCTypeResult.asConfirmed.events
+ .find((event) => api.events.ctype.CTypeCreated.is(event))
+ ?.data[1].toHex()
- if (!ctypeHash || !(await api.query.ctype.ctypes(ctypeHash)).isSome) {
- throw new Error('storing ctype failed')
+ if ((await api.query.ctype.ctypes(ctypeHash)).isEmpty) {
+ throw new Error('CType not registered')
}
+ // TODO: Should we at least be able to load an existing CType from the chain?
const DriversLicense = JSON.parse(DriversLicenseDef)
DriversLicense.$id = `kilt:ctype:${ctypeHash}`
- console.info('CType successfully stored on chain')
-
- // Attestation workflow
- console.log('Attestation workflow started')
- const credentialSubject = { id: bob.id, name: 'Bob', age: 21 }
-
- const credential = await Issuer.createCredential({
+ console.log('CType registered')
+
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+ // ┃ Issue a Credential ┃
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+ //
+ // Create and issue a credential using our Did.
+ // The holder is also our Did, so we are issuing to ourselves here.
+ //
+ const unsigned = await Kilt.Issuer.createCredential({
+ issuer: didDocument.id,
+ credentialSubject: {
+ id: didDocument.id,
+ age: 22,
+ name: 'Gustav',
+ },
cType: DriversLicense,
- credentialSubject,
- issuer: alice.id,
})
- console.info('Credential subject conforms to CType')
-
- if (
- credential.credentialSubject.name !== credentialSubject.name ||
- credential.credentialSubject.age !== credentialSubject.age ||
- credential.credentialSubject.id !== bob.id
- ) {
- throw new Error('Claim content inside Credential mismatching')
- }
-
- const issued = await Issuer.issue(credential, {
- didDocument: alice,
- signers: [...aliceSign(alice), payerSigner],
- submitter: payerSigner.id,
- })
- console.info('Credential issued')
-
- const credentialResult = await Verifier.verifyCredential({
- credential: issued,
- config: {
- cTypes: [DriversLicense],
- },
+ const credential = await Kilt.Issuer.issue(unsigned, {
+ didDocument,
+ signers: [...signers, submitter],
+ submitter: submitter.id,
})
- if (credentialResult.verified) {
- console.info('Credential proof verified')
- console.info('Credential status verified')
- } else {
- throw new Error(`Credential failed to verify: ${credentialResult.error}`, {
- cause: credentialResult,
- })
- }
-
- const challenge = crypto.randomUUID()
-
- const derived = await Holder.deriveProof(issued, {
- disclose: { allBut: ['/credentialSubject/name'] },
+ console.log('credential issued')
+
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+ // ┃ Create a Presentation ┃
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+ //
+ // Create a derived credential that only contains selected properties (selective disclosure), then create a credential presentation for it.
+ // The presentation includes a proof of ownership and is scoped to a verified and time frame to prevent unauthorized re-use.
+ //
+ const derived = await Kilt.Holder.deriveProof(credential, {
+ disclose: { only: ['/credentialSubject/age'] },
})
- const presentation = await Holder.createPresentation(
+ const presentation = await Kilt.Holder.createPresentation(
[derived],
{
- didDocument: bob,
- signers: bobSign(bob),
+ didDocument,
+ signers,
},
- {},
- {
- challenge,
- }
+ { verifier: didDocument.id, validUntil: new Date(Date.now() + 100_000) }
)
- console.info('Presentation created')
- const presentationResult = await Verifier.verifyPresentation({
+ console.log('presentation created')
+
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+ // ┃ Verify a Presentation ┃
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+ //
+ // Verify a presentation.
+ //
+ // Verification would fail if:
+ // - The presentation is not signed by the holder's Did.
+ // - The current time is outside of the validity time frame of the presentation.
+ // - The verifier in the presentation does not match the one specified.
+ //
+ const { verified, error } = await Kilt.Verifier.verifyPresentation({
presentation,
- verificationCriteria: { challenge },
+ verificationCriteria: {
+ verifier: didDocument.id,
+ proofPurpose: 'authentication',
+ },
})
- if (presentationResult.verified) {
- console.info('Presentation verified')
- } else {
- throw new Error(
- [
- 'Presentation failed to verify',
- ...(presentationResult.error ?? []),
- ].join('\n '),
- { cause: presentationResult }
- )
+
+ if (verified !== true) {
+ throw new Error(`failed to verify credential: ${JSON.stringify(error)}`)
+ }
+
+ console.log('presentation verified')
+
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+ // ┃ Remove a Verification Method ┃
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+ //
+ // Removing a verification method can be done by specifying its id.
+ //
+ // Note:
+ // - The provided `didDocument` must include the specified verification method.
+ // - The authentication verification method can not be removed.
+ const removeVmTransactionResult =
+ await Kilt.DidHelpers.removeVerificationMethod({
+ api,
+ didDocument,
+ signers,
+ submitter,
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ verificationMethodId: didDocument.assertionMethod![0],
+ relationship: 'assertionMethod',
+ }).submit()
+
+ if (removeVmTransactionResult.status !== 'confirmed') {
+ throw new Error('remove verification method failed')
}
+ ;({ didDocument } = removeVmTransactionResult.asConfirmed)
+
+ console.log('assertion method removed')
+
+ // ┏━━━━━━━━━━━━━━━━━━┓
+ // ┃ Release web3name ┃
+ // ┗━━━━━━━━━━━━━━━━━━┛
+ //
+ // A web3name can be released from a DID and potentially claimed by another DID.
+ const releaseW3nTransactionResult = await Kilt.DidHelpers.releaseWeb3Name({
+ api,
+ didDocument,
+ submitter,
+ signers,
+ }).submit()
+
+ if (releaseW3nTransactionResult.status !== 'confirmed') {
+ throw new Error('release web3name failed')
+ }
+ ;({ didDocument } = releaseW3nTransactionResult.asConfirmed)
+
+ console.log('w3n released')
+
+ // ┏━━━━━━━━━━━━━━━━━━┓
+ // ┃ Remove a service ┃
+ // ┗━━━━━━━━━━━━━━━━━━┛
+ //
+ // Services can be removed by specifying the service `id`
+ const removeServiceTransactionResult = await Kilt.DidHelpers.removeService({
+ api,
+ submitter,
+ signers,
+ didDocument,
+ id: '#my_service',
+ }).submit()
+
+ if (removeServiceTransactionResult.status !== 'confirmed') {
+ throw new Error('remove service failed')
+ }
+ ;({ didDocument } = removeServiceTransactionResult.asConfirmed)
+
+ console.log('service removed')
+
+ // ┏━━━━━━━━━━━━━━━━━━┓
+ // ┃ Deactivate a DID ┃
+ // ┗━━━━━━━━━━━━━━━━━━┛
+ //
+ // _Permanently_ deactivate the DID, removing all verification methods and services from its document.
+ // Deactivating a DID cannot be undone, once a DID has been deactivated, all operations on it (including attempts at re-creation) are permanently disabled.
+ const deactivateDidTransactionResult = await Kilt.DidHelpers.deactivateDid({
+ api,
+ submitter,
+ signers,
+ didDocument,
+ }).submit()
+
+ if (deactivateDidTransactionResult.status !== 'confirmed') {
+ throw new Error('deactivate DID failed')
+ }
+ ;({ didDocument } = deactivateDidTransactionResult.asConfirmed)
+
+ if (Array.isArray(didDocument.verificationMethod)) {
+ throw new Error('Did not deactivated')
+ }
+
+ console.log('Did deactivated')
+
+ // Release the connection to the blockchain.
+ await api.disconnect()
+
+ console.log('disconnected')
}
window.runAll = runAll