Skip to content

Commit

Permalink
feat(Castor): extending createPrismDID to accept a KeyPair or PublicKey
Browse files Browse the repository at this point in the history
  • Loading branch information
curtis-h committed Aug 16, 2023
1 parent a9d12e7 commit f26f8da
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 87 deletions.
39 changes: 22 additions & 17 deletions src/castor/Castor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
PrismDIDMethodId,
DIDDocumentCoreProperty,
DIDResolver,
KeyPair,
} from "../domain/models";
import {
getUsageId,
Expand Down Expand Up @@ -96,7 +97,9 @@ export default class Castor implements CastorInterface {
* given master public key and list of services.
*
* @example
* This function creates a new `prism` DID, using a given master public key and a list of services. It may throw an error if the master public key or services are invalid.
* This function creates a new `prism` DID, using a given master Public Key and a list of Services.
* The Public Key may be an individual Key or a KeyPair
* It may throw an error if the master Public Key or Services are invalid.
*
* ```ts
* const exampleServiceEndpoint = new Domain.Service("didcomm", ["DIDCommMessaging"], {
Expand All @@ -111,14 +114,16 @@ export default class Castor implements CastorInterface {
* ```
*
* @async
* @param {PublicKey} masterPublicKey
* @param {PublicKey | KeyPair} masterPublicKey
* @param {?(Service[] | undefined)} [services]
* @returns {Promise<DID>}
*/
async createPrismDID(
masterPublicKey: PublicKey,
key: PublicKey | KeyPair,
services?: Service[] | undefined
): Promise<DID> {
const masterPublicKey = "publicKey" in key ? key.publicKey : key;

if (!masterPublicKey.isCurve<Secp256k1PublicKey>(Curve.SECP256K1)) {
throw new CastorError.InvalidKeyError();
}
Expand Down Expand Up @@ -328,31 +333,31 @@ export default class Castor implements CastorInterface {
const material =
method.publicKeyJwk.crv === Curve.X25519
? new VerificationMaterialAgreement(
JSON.stringify(method.publicKeyJwk),
VerificationMethodTypeAgreement.JSON_WEB_KEY_2020,
VerificationMaterialFormatPeerDID.JWK
)
JSON.stringify(method.publicKeyJwk),
VerificationMethodTypeAgreement.JSON_WEB_KEY_2020,
VerificationMaterialFormatPeerDID.JWK
)

Check warning on line 339 in src/castor/Castor.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
: new VerificationMaterialAuthentication(
JSON.stringify(method.publicKeyJwk),
VerificationMethodTypeAuthentication.JSON_WEB_KEY_2020,
VerificationMaterialFormatPeerDID.JWK
);
JSON.stringify(method.publicKeyJwk),
VerificationMethodTypeAuthentication.JSON_WEB_KEY_2020,
VerificationMaterialFormatPeerDID.JWK
);

const decodedKey =
method.publicKeyJwk.crv === Curve.X25519
? JWKHelper.fromJWKAgreement(
material as VerificationMaterialAgreement
)
material as VerificationMaterialAgreement
)

Check warning on line 350 in src/castor/Castor.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
: JWKHelper.fromJWKAuthentication(
material as VerificationMaterialAuthentication
);
material as VerificationMaterialAuthentication
);

publicKey =
method.publicKeyJwk.crv === Curve.X25519
? new X25519PublicKey(Buffer.from(base64url.baseEncode(decodedKey)))
: new Ed25519PublicKey(
Buffer.from(base64url.baseEncode(decodedKey))
);
Buffer.from(base64url.baseEncode(decodedKey))
);

if (
publicKey.canVerify() &&
Expand Down
187 changes: 117 additions & 70 deletions tests/castor/PrismDID.test.ts
Original file line number Diff line number Diff line change
@@ -1,93 +1,140 @@
import { expect } from "chai";
import chai from "chai";
import chaiAsPromised from "chai-as-promised";
import { base58btc } from "multiformats/bases/base58";
import { VerificationMethods } from "../../src/domain";
import { Curve, KeyTypes, VerificationMethods } from "../../src/domain";
import Apollo from "../../src/apollo/Apollo";
import Castor from "../../src/castor/Castor";
import * as ECConfig from "../../src/config/ECConfig";
import { Secp256k1PublicKey } from "../../src/apollo/utils/Secp256k1PublicKey";
import { Curve, KeyTypes } from "../../src/domain/models";
import * as Fixtures from "../fixtures";

describe("PRISMDID CreateTest", () => {
it("Should correctly create a prismDID from an existing HexKey", async () => {
const apollo = new Apollo();
const castor = new Castor(apollo);
chai.use(chaiAsPromised);
const expect = chai.expect;

const didExample =
"did:prism:733e594871d7700d35e6116011a08fc11e88ff9d366d8b5571ffc1aa18d249ea:Ct8BCtwBEnQKH2F1dGhlbnRpY2F0aW9uYXV0aGVudGljYXRpb25LZXkQBEJPCglzZWNwMjU2azESIDS5zeYUkLCSAJLI6aLXRTPRxstCLPUEI6TgBrAVCHkwGiDk-ffklrHIFW7pKkT8i-YksXi-XXi5h31czUMaVClcpxJkCg9tYXN0ZXJtYXN0ZXJLZXkQAUJPCglzZWNwMjU2azESIDS5zeYUkLCSAJLI6aLXRTPRxstCLPUEI6TgBrAVCHkwGiDk-ffklrHIFW7pKkT8i-YksXi-XXi5h31czUMaVClcpw";
const resolvedDID = await castor.resolveDID(didExample);
describe("PRISMDID", () => {
describe("createPrismDID", () => {
it("Should create a prismDID from a PublicKey (SECP256K1)", async () => {
const castor = new Castor({} as any);

const pubHex =
"0434b9cde61490b0920092c8e9a2d74533d1c6cb422cf50423a4e006b015087930e4f9f7e496b1c8156ee92a44fc8be624b178be5d78b9877d5ccd431a54295ca7";
const result = await castor.createPrismDID(Fixtures.Keys.secp256K1.publicKey, []);

const masterPublicKey = new Secp256k1PublicKey(Buffer.from(pubHex, "hex"));
expect(result).not.to.be.null;
expect(result.toString()).to.equal(Fixtures.Keys.expectedDIDSecp256K1);
});

it("Should create a prismDID from a KeyPair (SECP256K1)", async () => {
const castor = new Castor({} as any);

const createdDID = await castor.createPrismDID(masterPublicKey, []);
const resolveCreated = await castor.resolveDID(createdDID.toString());
const result = await castor.createPrismDID(Fixtures.Keys.secp256K1, []);

const verificationMethod = resolveCreated.coreProperties.find(
(prop): prop is VerificationMethods => prop instanceof VerificationMethods
);
expect(result).not.to.be.null;
expect(result.toString()).to.equal(Fixtures.Keys.expectedDIDSecp256K1);
});

const resolvedPublicKeyMultibase =
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain
verificationMethod?.values.at(0)?.publicKeyMultibase!;
it("does not create a prismDID from a PublicKey (ED25519)", async () => {
const castor = new Castor({} as any);
expect(castor.createPrismDID(Fixtures.Keys.ed25519.publicKey, [])).to.eventually.be.rejected;
});

const resolvedPublicKeyBuffer = Buffer.from(
base58btc.decode(resolvedPublicKeyMultibase)
);
it("does not create a prismDID from a KeyPair (ED25519)", async () => {
const castor = new Castor({} as any);
expect(castor.createPrismDID(Fixtures.Keys.ed25519, [])).to.eventually.be.rejected;
});

it("does not create a prismDID from a PublicKey (X25519)", async () => {
const castor = new Castor({} as any);
expect(castor.createPrismDID(Fixtures.Keys.x25519.publicKey, [])).to.eventually.be.rejected;
});

expect(resolvedPublicKeyBuffer).to.deep.equal(masterPublicKey.raw);
expect(resolveCreated.id.toString()).to.be.equal(resolvedDID.id.toString());
it("does not create a prismDID from a KeyPair (X25519)", async () => {
const castor = new Castor({} as any);
expect(castor.createPrismDID(Fixtures.Keys.x25519, [])).to.eventually.be.rejected;
});
});

it("Create a PrismDID and verify a signature", async () => {
const apollo = new Apollo();
const castor = new Castor(apollo);
const privateKey = apollo.createPrivateKey({
type: KeyTypes.EC,
curve: Curve.SECP256K1,
seed: Buffer.from(apollo.createRandomSeed().seed.value).toString("hex"),
describe("Integration Tests", () => {
it("Should correctly create a prismDID from an existing HexKey", async () => {
const apollo = new Apollo();
const castor = new Castor(apollo);

const didExample =
"did:prism:733e594871d7700d35e6116011a08fc11e88ff9d366d8b5571ffc1aa18d249ea:Ct8BCtwBEnQKH2F1dGhlbnRpY2F0aW9uYXV0aGVudGljYXRpb25LZXkQBEJPCglzZWNwMjU2azESIDS5zeYUkLCSAJLI6aLXRTPRxstCLPUEI6TgBrAVCHkwGiDk-ffklrHIFW7pKkT8i-YksXi-XXi5h31czUMaVClcpxJkCg9tYXN0ZXJtYXN0ZXJLZXkQAUJPCglzZWNwMjU2azESIDS5zeYUkLCSAJLI6aLXRTPRxstCLPUEI6TgBrAVCHkwGiDk-ffklrHIFW7pKkT8i-YksXi-XXi5h31czUMaVClcpw";
const resolvedDID = await castor.resolveDID(didExample);

const pubHex =
"0434b9cde61490b0920092c8e9a2d74533d1c6cb422cf50423a4e006b015087930e4f9f7e496b1c8156ee92a44fc8be624b178be5d78b9877d5ccd431a54295ca7";

const masterPublicKey = new Secp256k1PublicKey(Buffer.from(pubHex, "hex"));

const createdDID = await castor.createPrismDID(masterPublicKey, []);
const resolveCreated = await castor.resolveDID(createdDID.toString());

const verificationMethod = resolveCreated.coreProperties.find(
(prop): prop is VerificationMethods => prop instanceof VerificationMethods
);

const resolvedPublicKeyMultibase =
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain
verificationMethod?.values.at(0)?.publicKeyMultibase!;

const resolvedPublicKeyBuffer = Buffer.from(
base58btc.decode(resolvedPublicKeyMultibase)
);

expect(resolvedPublicKeyBuffer).to.deep.equal(masterPublicKey.raw);
expect(resolveCreated.id.toString()).to.be.equal(resolvedDID.id.toString());
});
const publicKey = privateKey.publicKey();

const did = await castor.createPrismDID(publicKey, []);
const text = "The quick brown fox jumps over the lazy dog";
const signature =
privateKey.isSignable() && privateKey.sign(Buffer.from(text));
it("Create a PrismDID and verify a signature", async () => {
const apollo = new Apollo();
const castor = new Castor(apollo);
const privateKey = apollo.createPrivateKey({
type: KeyTypes.EC,
curve: Curve.SECP256K1,
seed: Buffer.from(apollo.createRandomSeed().seed.value).toString("hex"),
});
const publicKey = privateKey.publicKey();

const did = await castor.createPrismDID(publicKey, []);
const text = "The quick brown fox jumps over the lazy dog";
const signature =
privateKey.isSignable() && privateKey.sign(Buffer.from(text));

expect(signature).to.not.be.equal(false);

if (signature) {
const result = await castor.verifySignature(
did,
Buffer.from(text),
Buffer.from(signature)
);
expect(result).to.be.equal(true);
}
});

expect(signature).to.not.be.equal(false);
it("Should resolve prismDID key correctly", async () => {
const apollo = new Apollo();
const castor = new Castor(apollo);
const did =
"did:prism:2c6e089b137b566e97bf8e1c234755f9f8690194c3bc52c6431ff4bb960394b1:CtADCs0DElsKBmF1dGgtMRAEQk8KCXNlY3AyNTZrMRIgvMs2bdoiICUhwR4BGk2hip8QWzG0YUfKaOa1xDyxMNUaIHm3gJ0eaeiqadY0NFlXOcAidM1SUyupvouHKsaCr0IaEmAKC2Fzc2VydGlvbi0xEAJCTwoJc2VjcDI1NmsxEiCr03dJu2xHHYCOBKNK4JNwh3ypp2JX6-Cr8tXiI17KnBogK9A6g0btjurK8n1R2ZeACOFmZkzPs2wDUy01UtqLH4sSXAoHbWFzdGVyMBABQk8KCXNlY3AyNTZrMRIgA1ltJZ4-5OmDYoiP2ZiKg-MMDR3BfDdw-oHYCvpGZEQaIAh1R73E0DW_wi4Ng5xxkDQ77ocpSz_iiEGE9svSPxtaGjoKE2h0dHBzOi8vZm9vLmJhci5jb20SDUxpbmtlZERvbWFpbnMaFGh0dHBzOi8vZm9vLmJhci5jb20vGjgKEmh0dHBzOi8vdXBkYXRlLmNvbRINTGlua2VkRG9tYWlucxoTaHR0cHM6Ly91cGRhdGUuY29tLxo4ChJodHRwczovL3JlbW92ZS5jb20SDUxpbmtlZERvbWFpbnMaE2h0dHBzOi8vcmVtb3ZlLmNvbS8";
const resolved = await castor.resolveDID(did);

if (signature) {
const result = await castor.verifySignature(
did,
Buffer.from(text),
Buffer.from(signature)
const verificationMethod = resolved.coreProperties.find(
(prop): prop is VerificationMethods => prop instanceof VerificationMethods
);
expect(result).to.be.equal(true);
}
});
it("Should resolve prismDID key correctly", async () => {
const apollo = new Apollo();
const castor = new Castor(apollo);
const did =
"did:prism:2c6e089b137b566e97bf8e1c234755f9f8690194c3bc52c6431ff4bb960394b1:CtADCs0DElsKBmF1dGgtMRAEQk8KCXNlY3AyNTZrMRIgvMs2bdoiICUhwR4BGk2hip8QWzG0YUfKaOa1xDyxMNUaIHm3gJ0eaeiqadY0NFlXOcAidM1SUyupvouHKsaCr0IaEmAKC2Fzc2VydGlvbi0xEAJCTwoJc2VjcDI1NmsxEiCr03dJu2xHHYCOBKNK4JNwh3ypp2JX6-Cr8tXiI17KnBogK9A6g0btjurK8n1R2ZeACOFmZkzPs2wDUy01UtqLH4sSXAoHbWFzdGVyMBABQk8KCXNlY3AyNTZrMRIgA1ltJZ4-5OmDYoiP2ZiKg-MMDR3BfDdw-oHYCvpGZEQaIAh1R73E0DW_wi4Ng5xxkDQ77ocpSz_iiEGE9svSPxtaGjoKE2h0dHBzOi8vZm9vLmJhci5jb20SDUxpbmtlZERvbWFpbnMaFGh0dHBzOi8vZm9vLmJhci5jb20vGjgKEmh0dHBzOi8vdXBkYXRlLmNvbRINTGlua2VkRG9tYWlucxoTaHR0cHM6Ly91cGRhdGUuY29tLxo4ChJodHRwczovL3JlbW92ZS5jb20SDUxpbmtlZERvbWFpbnMaE2h0dHBzOi8vcmVtb3ZlLmNvbS8";
const resolved = await castor.resolveDID(did);

const verificationMethod = resolved.coreProperties.find(
(prop): prop is VerificationMethods => prop instanceof VerificationMethods
);

const resolvedPublicKeyBase64 =
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain
verificationMethod?.values.at(0)?.publicKeyMultibase!;

const resolvedPublicKeyBuffer = Buffer.from(
base58btc.decode(resolvedPublicKeyBase64)
);

resolvedPublicKeyBuffer.length;
expect(resolvedPublicKeyBuffer.length).to.be.equal(
ECConfig.PUBLIC_KEY_BYTE_SIZE
);

const resolvedPublicKeyBase64 =
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain
verificationMethod?.values.at(0)?.publicKeyMultibase!;

const resolvedPublicKeyBuffer = Buffer.from(
base58btc.decode(resolvedPublicKeyBase64)
);

resolvedPublicKeyBuffer.length;
expect(resolvedPublicKeyBuffer.length).to.be.equal(
ECConfig.PUBLIC_KEY_BYTE_SIZE
);
});
});
});
1 change: 1 addition & 0 deletions tests/fixtures/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as Keys from "./keys";
17 changes: 17 additions & 0 deletions tests/fixtures/keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Ed25519KeyPair } from "../../src/apollo/utils/Ed25519KeyPair";
import { Ed25519PrivateKey } from "../../src/apollo/utils/Ed25519PrivateKey";
import { Secp256k1KeyPair } from "../../src/apollo/utils/Secp256k1KeyPair";
import { Secp256k1PrivateKey } from "../../src/apollo/utils/Secp256k1PrivateKey";
import { X25519KeyPair } from "../../src/apollo/utils/X25519KeyPair";
import { X25519PrivateKey } from "../../src/apollo/utils/X25519PrivateKey";

export const expectedDIDSecp256K1 = "did:prism:da61cf65fbf04b6b9fe06fa3b577fca3e05895a13902decaad419845a20d2d78:Ct8BCtwBEnQKH2F1dGhlbnRpY2F0aW9uYXV0aGVudGljYXRpb25LZXkQBEJPCglzZWNwMjU2azESIP0gMhTAVOk7SgWRluzmeJIjtm2-YMc6AbrD3ePKJQj-GiDZlsa5pQuXGzKvgK10D8SzuDvh79u5oMB7-ZeJNAh-ixJkCg9tYXN0ZXJtYXN0ZXJLZXkQAUJPCglzZWNwMjU2azESIP0gMhTAVOk7SgWRluzmeJIjtm2-YMc6AbrD3ePKJQj-GiDZlsa5pQuXGzKvgK10D8SzuDvh79u5oMB7-ZeJNAh-iw";

const secpPrivateKey = new Secp256k1PrivateKey(new Uint8Array([45, 182, 188, 189, 107, 229, 136, 180, 199, 177, 110, 84, 98, 140, 121, 84, 107, 105, 179, 139, 14, 174, 177, 63, 173, 141, 7, 118, 161, 192, 192, 221]));
export const secp256K1 = new Secp256k1KeyPair(secpPrivateKey, secpPrivateKey.publicKey());

const ed25519PrivateKey = new Ed25519PrivateKey(Buffer.from("JLIJQ5jlkyqtGmtOth6yggJLLC0zuRhUPiBhd1-rGPs"));
export const ed25519 = new Ed25519KeyPair(ed25519PrivateKey, ed25519PrivateKey.publicKey());

const x25519PrivateKey = new X25519PrivateKey(Buffer.from("eHbEtI71XIBIsuQK7XdjZ_ZPnLZb3y4paWoqSoS7BnI"));
export const x25519 = new X25519KeyPair(x25519PrivateKey, x25519PrivateKey.publicKey());

0 comments on commit f26f8da

Please sign in to comment.