generate longform
This commit is contained in:
Родитель
7b541c3fe5
Коммит
520f22bc5b
|
@ -1,151 +1,76 @@
|
|||
import { Crypto, KeyReference, JsonWebKey } from './index';
|
||||
import base64url from 'base64url';
|
||||
import OperationType from '@decentralized-identity/sidetree/dist/lib/core/enums/OperationType';
|
||||
import CreateOperation from '@decentralized-identity/sidetree/dist/lib/core/versions/latest/CreateOperation';
|
||||
import Multihash from '@decentralized-identity/sidetree/dist/lib/core/versions/latest/Multihash';
|
||||
import { KeyStoreOptions } from 'verifiablecredentials-crypto-sdk-typescript-keystore';
|
||||
const canonicalize = require('canonicalize');
|
||||
import { IonDid } from '@decentralized-identity/ion-sdk';
|
||||
const clone = require('clone');
|
||||
|
||||
/**
|
||||
* Helper class to work with long form DID's
|
||||
*/
|
||||
export default class LongFormDid {
|
||||
constructor(private crypto: Crypto) { }
|
||||
constructor(private crypto: Crypto, private services: any = []) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create key and return longform
|
||||
* @param recoveryKeyReference Reference to the recovery key
|
||||
* Create longform DID based on keys in crypto object
|
||||
*/
|
||||
public async serialize(): Promise<string> {
|
||||
// See https://github.com/diafygi/webcrypto-examples for examples how to use the W3C web Crypto stamdard
|
||||
|
||||
if (!this.crypto.builder.signingKeyReference) {
|
||||
throw new Error(`No signing key reference. Use CryptoBuilder.useSigningKeyReference.`)
|
||||
if (this.crypto.builder.signingAlgorithm !== 'ES256K') {
|
||||
return Promise.reject(new Error(`Longform DIDs only support ES256K. Signing algorithm: ${this.crypto.builder.signingAlgorithm}`));
|
||||
}
|
||||
|
||||
if (!this.crypto.builder.recoveryKeyReference) {
|
||||
throw new Error(`No recovery key reference. Use CryptoBuilder.useRecoveryKeyReference.`)
|
||||
if (this.crypto.builder.recoveryAlgorithm !== 'ES256K') {
|
||||
return Promise.reject(new Error(`Longform DIDs only support ES256K. Recovery algorithm: ${this.crypto.builder.recoveryAlgorithm}`));
|
||||
}
|
||||
|
||||
let signingPublic = await (await this.crypto.builder.keyStore.get(this.crypto.builder.signingKeyReference, new KeyStoreOptions({publicKeyOnly: true}))).getKey<JsonWebKey>();
|
||||
signingPublic = this.normalizeJwk(signingPublic);
|
||||
let recoveryPublic = await (await this.crypto.builder.keyStore.get(this.crypto.builder.recoveryKeyReference, new KeyStoreOptions({publicKeyOnly: true}))).getKey<JsonWebKey>();
|
||||
recoveryPublic = this.normalizeJwk(recoveryPublic);
|
||||
if (this.crypto.builder.updateAlgorithm !== 'ES256K') {
|
||||
return Promise.reject(new Error(`Longform DIDs only support ES256K. Update algorithm: ${this.crypto.builder.updateAlgorithm}`));
|
||||
}
|
||||
|
||||
let signingPublic: any;
|
||||
let recoveryKey: any;
|
||||
let updateKey: any;
|
||||
|
||||
try {
|
||||
signingPublic = await (await this.crypto.builder.keyStore.get(this.crypto.builder.signingKeyReference, new KeyStoreOptions({ publicKeyOnly: true }))).getKey<JsonWebKey>();
|
||||
signingPublic = this.normalizeJwk(signingPublic);
|
||||
recoveryKey = await (await this.crypto.builder.keyStore.get(this.crypto.builder.recoveryKeyReference, new KeyStoreOptions({ publicKeyOnly: true }))).getKey<JsonWebKey>();
|
||||
recoveryKey = this.normalizeJwk(recoveryKey);
|
||||
updateKey = await (await this.crypto.builder.keyStore.get(this.crypto.builder.updateKeyReference, new KeyStoreOptions({ publicKeyOnly: true }))).getKey<JsonWebKey>();
|
||||
updateKey = this.normalizeJwk(updateKey);
|
||||
} catch (exception) {
|
||||
return Promise.reject(exception);
|
||||
}
|
||||
|
||||
// Create long-form did
|
||||
const createOperationData = await this.generateCreateOperation(recoveryPublic, signingPublic, this.crypto.builder.signingKeyReference);
|
||||
const didMethodName = 'ion';
|
||||
const didUniqueSuffix = createOperationData.createOperation.didUniqueSuffix;
|
||||
const shortFormDid = `did:${didMethodName}:${didUniqueSuffix}`;
|
||||
const encodedSuffixData = createOperationData.createOperation.encodedSuffixData;
|
||||
const encodedDelta = createOperationData.createOperation.encodedDelta;
|
||||
const longFormDid = `${shortFormDid}?-ion-initial-state=${encodedSuffixData}.${encodedDelta}`;
|
||||
const didDocumentKeys: any = {
|
||||
id: this.crypto.builder.recoveryKeyReference.keyReference,
|
||||
type: "EcdsaSecp256k1VerificationKey2019",
|
||||
"publicKeyJwk": signingPublic,
|
||||
"purposes": [
|
||||
"authentication", "keyAgreement"
|
||||
]
|
||||
};
|
||||
|
||||
// const did = await Did.create(longFormDid, didMethodName);
|
||||
const document = {
|
||||
publicKeys: [didDocumentKeys],
|
||||
services: this.services
|
||||
};
|
||||
|
||||
const longFormDid = IonDid.createLongFormDid({ recoveryKey, updateKey, document });
|
||||
return longFormDid;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Generates an create operation.
|
||||
*/
|
||||
public async generateCreateOperation(recoveryPublicKey: any, signingPublicKey: any, keyReference: KeyReference) {
|
||||
|
||||
const operationRequest = this.generateCreateOperationRequest(
|
||||
recoveryPublicKey,
|
||||
signingPublicKey,
|
||||
keyReference
|
||||
);
|
||||
const operationBuffer = Buffer.from(JSON.stringify(operationRequest));
|
||||
const createOperation = await CreateOperation.parse(operationBuffer);
|
||||
|
||||
return {
|
||||
createOperation,
|
||||
operationRequest,
|
||||
recoveryPublicKey,
|
||||
signingPublicKey
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Canonicalize the given content, then multihashes the result using the lastest supported hash algorithm, then encodes the multihash.
|
||||
* Mainly used for testing purposes.
|
||||
*/
|
||||
public static canonicalizeThenHashThenEncode (content: object) {
|
||||
const contentBuffer = LongFormDid.canonicalizeAsBuffer(content);
|
||||
const contentHash = Multihash.hash(contentBuffer, 18);
|
||||
const contentHashEncodedString = base64url.encode(contentHash);
|
||||
return contentHashEncodedString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Canonicalizes the given content as a UTF8 buffer.
|
||||
*/
|
||||
public static canonicalizeAsBuffer (content: object): Buffer {
|
||||
const canonicalizedString: string = canonicalize(content);
|
||||
const contentBuffer = Buffer.from(canonicalizedString);
|
||||
return contentBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a create operation request.
|
||||
* @param nextRecoveryCommitment The encoded commitment hash for the next recovery.
|
||||
* @param nextUpdateCommitment The encoded commitment hash for the next update.
|
||||
*/
|
||||
public generateCreateOperationRequest(
|
||||
recoveryPublicKey: any,
|
||||
signingPublicKey: any,
|
||||
keyReference: KeyReference) {
|
||||
|
||||
const publicKey = {
|
||||
id: keyReference.keyReference,
|
||||
type: "EcdsaSecp256k1VerificationKey2019",
|
||||
jwk: signingPublicKey,
|
||||
purpose: [
|
||||
"auth",
|
||||
"general"
|
||||
]
|
||||
}
|
||||
const document = {
|
||||
public_keys : [publicKey]
|
||||
};
|
||||
|
||||
const patches = [{
|
||||
action: 'replace',
|
||||
document
|
||||
}];
|
||||
|
||||
const updateCommitment = LongFormDid.canonicalizeThenHashThenEncode(signingPublicKey);
|
||||
const delta = {
|
||||
update_commitment: updateCommitment,
|
||||
patches
|
||||
};
|
||||
|
||||
const deltaBuffer = Buffer.from(JSON.stringify(delta));
|
||||
const deltaHash = base64url.encode(Multihash.hash(deltaBuffer));
|
||||
|
||||
const recoveryCommitment = LongFormDid.canonicalizeThenHashThenEncode(recoveryPublicKey);
|
||||
const suffixData = {
|
||||
delta_hash: deltaHash,
|
||||
recovery_commitment: recoveryCommitment
|
||||
};
|
||||
|
||||
const suffixDataEncodedString = base64url.encode(JSON.stringify(suffixData));
|
||||
const deltaEncodedString = base64url.encode(deltaBuffer);
|
||||
const operation = {
|
||||
type: OperationType.Create,
|
||||
suffix_data: suffixDataEncodedString,
|
||||
delta: deltaEncodedString
|
||||
};
|
||||
|
||||
return operation;
|
||||
}
|
||||
|
||||
private normalizeJwk(key: any) {
|
||||
delete key.key_ops;
|
||||
delete key.ext;
|
||||
key.crv = 'secp256k1';
|
||||
return key;
|
||||
private normalizeJwk(key: any): any {
|
||||
const jwk = clone(key);
|
||||
delete jwk.key_ops;
|
||||
delete jwk.ext;
|
||||
delete jwk.kid;
|
||||
delete jwk.use;
|
||||
delete jwk.alg;
|
||||
jwk.crv = 'secp256k1';
|
||||
return jwk;
|
||||
}
|
||||
}
|
|
@ -45,7 +45,6 @@
|
|||
"@azure/keyvault-keys": "4.0.2",
|
||||
"@azure/keyvault-secrets": "4.0.2",
|
||||
"@decentralized-identity/ion-sdk": "0.4.1",
|
||||
"@decentralized-identity/sidetree": "0.11.0",
|
||||
"base64url": "3.0.1",
|
||||
"bs58": "4.0.1",
|
||||
"canonicalize": "1.0.1",
|
||||
|
|
|
@ -14,7 +14,7 @@ describe('SuiteJcsEd25519Signature2020', () => {
|
|||
.build();
|
||||
crypto = await crypto.generateKey(KeyUse.Signature);
|
||||
crypto = await crypto.generateKey(KeyUse.Signature, 'recovery');
|
||||
const did = await new LongFormDid(crypto).serialize();
|
||||
const did = 'did:test:12345';
|
||||
crypto = crypto.builder.useDid(did).build();
|
||||
|
||||
let jsonLdProofs = new JoseBuilder(crypto)
|
||||
|
|
|
@ -23,7 +23,8 @@ describe('JSONLD proofs', () => {
|
|||
.build();
|
||||
crypto = await crypto.generateKey(KeyUse.Signature);
|
||||
crypto = await crypto.generateKey(KeyUse.Signature, 'recovery');
|
||||
crypto.builder.useDid(await new LongFormDid(crypto).serialize());
|
||||
const did = 'did:test:12345';
|
||||
crypto.builder.useDid(did);
|
||||
let jsonLdProofBuilder = new JoseBuilder(crypto)
|
||||
.useJsonLdProofsProtocol('JcsEd25519Signature2020')
|
||||
let jsonLdProof: IPayloadProtectionSigning = jsonLdProofBuilder.build();
|
||||
|
|
|
@ -6,10 +6,11 @@
|
|||
import { CryptoBuilder, CryptoFactory, KeyStoreFactory, Subtle, KeyUse, KeyStoreOptions, KeyReference, LongFormDid } from '../lib/index';
|
||||
|
||||
describe('LongFormDid', () => {
|
||||
it('should generate a longform DID', async () =>{
|
||||
it('should generate a longform DID', async () => {
|
||||
let crypto = new CryptoBuilder()
|
||||
.useSigningKeyReference(new KeyReference('mars'))
|
||||
.useRecoveryKeyReference(new KeyReference('recovery'))
|
||||
.useUpdateKeyReference(new KeyReference('update'))
|
||||
.build();
|
||||
crypto = await crypto.generateKey(KeyUse.Signature);
|
||||
crypto = await crypto.generateKey(KeyUse.Signature, 'recovery');
|
||||
|
@ -21,5 +22,88 @@ describe('LongFormDid', () => {
|
|||
let did = await new LongFormDid(crypto).serialize();
|
||||
expect(did.startsWith('did:ion')).toBeTruthy();
|
||||
console.log(did);
|
||||
});
|
||||
|
||||
// negative cases
|
||||
|
||||
// missing keys
|
||||
crypto = new CryptoBuilder()
|
||||
.useSigningKeyReference(new KeyReference('mars'))
|
||||
.useRecoveryKeyReference(new KeyReference('recovery'))
|
||||
.useUpdateKeyReference(new KeyReference('update'))
|
||||
.build();
|
||||
try {
|
||||
await new LongFormDid(crypto).serialize();
|
||||
fail('missing signing key should fail');
|
||||
} catch (exception) {
|
||||
expect(exception.message).toEqual('mars not found');
|
||||
}
|
||||
crypto = await crypto.generateKey(KeyUse.Signature);
|
||||
try {
|
||||
await new LongFormDid(crypto).serialize();
|
||||
fail('missing recovery key should fail');
|
||||
} catch (exception) {
|
||||
expect(exception.message).toEqual('recovery not found');
|
||||
}
|
||||
crypto = await crypto.generateKey(KeyUse.Signature, 'recovery');
|
||||
try {
|
||||
await new LongFormDid(crypto).serialize();
|
||||
fail('missing update key should fail');
|
||||
} catch (exception) {
|
||||
expect(exception.message).toEqual('update not found');
|
||||
}
|
||||
|
||||
// wrong key algos
|
||||
crypto = new CryptoBuilder()
|
||||
.useUpdateAlgorithm('EdDSA')
|
||||
.build();
|
||||
let longform = new LongFormDid(crypto);
|
||||
try {
|
||||
await longform.serialize();
|
||||
fail('wrong update key algorithm should fail');
|
||||
} catch (exception) {
|
||||
expect(exception.message).toEqual('Longform DIDs only support ES256K. Update algorithm: EdDSA');
|
||||
}
|
||||
crypto = new CryptoBuilder()
|
||||
.useRecoveryAlgorithm('EdDSA')
|
||||
.build();
|
||||
longform = new LongFormDid(crypto);
|
||||
try {
|
||||
await longform.serialize();
|
||||
fail('wrong recovery key algorithm should fail');
|
||||
} catch (exception) {
|
||||
expect(exception.message).toEqual('Longform DIDs only support ES256K. Recovery algorithm: EdDSA');
|
||||
}
|
||||
crypto = new CryptoBuilder()
|
||||
.useSigningAlgorithm('EdDSA')
|
||||
.build();
|
||||
longform = new LongFormDid(crypto);
|
||||
try {
|
||||
await longform.serialize();
|
||||
fail('wrong signing key algorithm should fail');
|
||||
} catch (exception) {
|
||||
expect(exception.message).toEqual('Longform DIDs only support ES256K. Signing algorithm: EdDSA');
|
||||
}
|
||||
|
||||
});
|
||||
it('should add services to the longform DID', async () => {
|
||||
let crypto = new CryptoBuilder()
|
||||
.useSigningKeyReference(new KeyReference('mars'))
|
||||
.useRecoveryKeyReference(new KeyReference('recovery'))
|
||||
.useUpdateKeyReference(new KeyReference('update'))
|
||||
.build();
|
||||
crypto = await crypto.generateKey(KeyUse.Signature);
|
||||
crypto = await crypto.generateKey(KeyUse.Signature, 'recovery');
|
||||
crypto = await crypto.generateKey(KeyUse.Signature, 'update');
|
||||
|
||||
let did1 = await new LongFormDid(crypto).serialize();
|
||||
let did2 = await new LongFormDid(crypto).serialize();
|
||||
expect(did1).toEqual(did2);
|
||||
const services = {
|
||||
id: "service1Id",
|
||||
type: "service1Type",
|
||||
serviceEndpoint: "http://www.service1.com"
|
||||
}
|
||||
did2 = await new LongFormDid(crypto, [services]).serialize();
|
||||
expect(did1).not.toEqual(did2);
|
||||
});
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче