Skip to content

Commit

Permalink
fix(Pluto): encoding and decoding privateKeys missed Secp256k1
Browse files Browse the repository at this point in the history
  • Loading branch information
curtis-h committed Aug 21, 2023
1 parent 366494b commit 29df198
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 76 deletions.
12 changes: 12 additions & 0 deletions src/apollo/utils/Ed25519PrivateKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class Ed25519PrivateKey extends PrivateKey implements SignableKey {
public raw: Uint8Array;
public keySpecification: Map<string, string> = new Map();

// TODO - nativeValue wants a Buffer, otherwise getInstance breaks
constructor(nativeValue: Uint8Array) {
super();
this.raw = nativeValue;
Expand Down Expand Up @@ -44,4 +45,15 @@ export class Ed25519PrivateKey extends PrivateKey implements SignableKey {
const signature = this.getInstance().sign(message);
return signature.toBytes();
}

public readonly to = {
Buffer: () => this.getEncoded(),
Hex: () => this.to.Buffer().toString("hex")
};

static from = {
Buffer: (value: Buffer) => new Ed25519PrivateKey(value),
Hex: (value: string) => Ed25519PrivateKey.from.Buffer(Buffer.from(value, "hex")),
String: (value: string) => Ed25519PrivateKey.from.Buffer(Buffer.from(value)),
};
}
21 changes: 16 additions & 5 deletions src/apollo/utils/Secp256k1PrivateKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ export class Secp256k1PrivateKey extends PrivateKey implements SignableKey {
return new Uint8Array([...padding, ...byteList]);
}

sign(message: Buffer) {
const keyPair = Secp256k1PrivateKey.ec.keyFromPrivate(this.getEncoded());
const sig = keyPair.sign(message);
return Buffer.from(sig.toDER());
}

static secp256k1FromBigInteger(bigInteger: BN): Secp256k1PrivateKey {
return new Secp256k1PrivateKey(Uint8Array.from(bigInteger.toArray()));
}
Expand All @@ -61,9 +67,14 @@ export class Secp256k1PrivateKey extends PrivateKey implements SignableKey {
return new Secp256k1PrivateKey(Uint8Array.from(bnprv.toArray()));
}

sign(message: Buffer) {
const keyPair = Secp256k1PrivateKey.ec.keyFromPrivate(this.getEncoded());
const sig = keyPair.sign(message);
return Buffer.from(sig.toDER());
}
public readonly to = {
Buffer: () => Buffer.from(this.getEncoded()),
Hex: () => this.to.Buffer().toString("hex")
};

static from = {
Buffer: (value: Buffer) => Secp256k1PrivateKey.secp256k1FromBytes(new Uint8Array(value)),
Hex: (value: string) => Secp256k1PrivateKey.from.Buffer(Buffer.from(value, "hex")),
String: (value: string) => Secp256k1PrivateKey.from.Buffer(Buffer.from(value))
};
}
12 changes: 12 additions & 0 deletions src/apollo/utils/X25519PrivateKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,16 @@ export class X25519PrivateKey extends PrivateKey {
const pub = base64url.baseEncode(x25519PrivateKey.publicKey);
return new X25519PublicKey(Buffer.from(pub));
}

public readonly to = {
Buffer: () => this.getEncoded(),
Hex: () => this.to.Buffer().toString("hex")
};

static from = {
Buffer: (value: Buffer) => new X25519PrivateKey(new Uint8Array(value)),
Hex: (value: string) => X25519PrivateKey.from.Buffer(Buffer.from(value, "hex")),
String: (value: string) => X25519PrivateKey.from.Buffer(Buffer.from(value))
};

}
42 changes: 22 additions & 20 deletions src/apollo/utils/jwt/JWT.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Services,
VerificationMethods,
DID,
PrivateKey,
} from "../../../domain";

export class JWT {
Expand Down Expand Up @@ -45,24 +46,24 @@ export class JWT {
verificationMethod:
verificationMethods && verificationMethods.values
? verificationMethods.values.map((vm) => {
if (vm.publicKeyMultibase) {
return {
id: vm.id,
type: "EcdsaSecp256k1VerificationKey2019",
controller: vm.controller,
publicKeyMultibase: vm.publicKeyMultibase,
};
}
if (vm.publicKeyJwk) {
return {
id: vm.id,
type: "JsonWebKey2020",
controller: vm.controller,
publicKeyJwk: vm.publicKeyJwk,
};
}
throw new Error("Invalid KeyType");
})
if (vm.publicKeyMultibase) {
return {
id: vm.id,
type: "EcdsaSecp256k1VerificationKey2019",
controller: vm.controller,
publicKeyMultibase: vm.publicKeyMultibase,
};

Check warning on line 55 in src/apollo/utils/jwt/JWT.ts

View workflow job for this annotation

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

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 56 in src/apollo/utils/jwt/JWT.ts

View workflow job for this annotation

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

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 56 in src/apollo/utils/jwt/JWT.ts

View workflow job for this annotation

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

🌿 Branch is not covered

Warning! Not covered branch
if (vm.publicKeyJwk) {
return {
id: vm.id,
type: "JsonWebKey2020",
controller: vm.controller,
publicKeyJwk: vm.publicKeyJwk,
};

Check warning on line 63 in src/apollo/utils/jwt/JWT.ts

View workflow job for this annotation

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

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 64 in src/apollo/utils/jwt/JWT.ts

View workflow job for this annotation

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

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 64 in src/apollo/utils/jwt/JWT.ts

View workflow job for this annotation

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

🌿 Branch is not covered

Warning! Not covered branch
throw new Error("Invalid KeyType");

Check warning on line 65 in src/apollo/utils/jwt/JWT.ts

View workflow job for this annotation

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

🧾 Statement is not covered

Warning! Not covered statement
})

Check warning on line 66 in src/apollo/utils/jwt/JWT.ts

View workflow job for this annotation

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

🌿 Branch is not covered

Warning! Not covered branch
: [],
service:
service?.values?.reduce<didResolver.Service[]>((acc, service) => {
Expand All @@ -82,11 +83,12 @@ export class JWT {

async sign(
issuer: DID,
privateKey: Uint8Array,
privateKey: PrivateKey | Uint8Array,
payload: Partial<didJWT.JWTPayload>
): Promise<string> {
const raw = privateKey instanceof PrivateKey ? privateKey.raw : privateKey;
//TODO: Better check if this method is called with PrismDID and not PeerDID or other
const signer = didJWT.ES256KSigner(privateKey);
const signer = didJWT.ES256KSigner(raw);
const jwt = await didJWT.createJWT(
payload,
{ issuer: issuer.toString(), signer },
Expand Down
5 changes: 5 additions & 0 deletions src/domain/models/keyManagement/PrivateKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,9 @@ export abstract class PrivateKey extends Key {
return this.raw;
}
abstract publicKey(): PublicKey;

abstract to: {
Buffer: () => Buffer;
Hex: () => string;
};
}
101 changes: 53 additions & 48 deletions src/pluto/Pluto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { PrivateKey } from "../domain/models";
import { KeyProperties } from "../domain/models/KeyProperties";
import { Ed25519PrivateKey } from "../apollo/utils/Ed25519PrivateKey";
import { X25519PrivateKey } from "../apollo/utils/X25519PrivateKey";
import { Secp256k1PrivateKey } from "../apollo/utils/Secp256k1PrivateKey";

type IgnoreProps = "entries" | "entityPrefix" | "metadataTableName";
export type PlutoConnectionProps =
Expand Down Expand Up @@ -82,12 +83,12 @@ export default class Pluto implements PlutoInterface {
const presetSqlJSConfig =
connection.type === "sqljs"
? {
location: "pluto",
useLocalForage: typeof window !== "undefined",
sqlJsConfig: {
locateFile: (file: string) => `${this.wasmUrl}/dist/${file}`,
},
}
location: "pluto",
useLocalForage: typeof window !== "undefined",
sqlJsConfig: {
locateFile: (file: string) => `${this.wasmUrl}/dist/${file}`,

Check warning on line 89 in src/pluto/Pluto.ts

View workflow job for this annotation

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

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 89 in src/pluto/Pluto.ts

View workflow job for this annotation

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

🕹️ Function is not covered

Warning! Not covered function
},
}

Check warning on line 91 in src/pluto/Pluto.ts

View workflow job for this annotation

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

🌿 Branch is not covered

Warning! Not covered branch
: {};
this.dataSource = new DataSource({
...presetSqlJSConfig,
Expand Down Expand Up @@ -160,7 +161,9 @@ export default class Pluto implements PlutoInterface {
didEntity.methodId = did.methodId;
didEntity.schema = did.schema;
didEntity.alias = alias ?? "";

await this.dataSource.manager.save(didEntity);

await this.storePrivateKeys(
privateKey,
did,
Expand Down Expand Up @@ -192,7 +195,7 @@ export default class Pluto implements PlutoInterface {
did,
privateKey.getProperty(KeyProperties.index)
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
parseInt(privateKey.getProperty(KeyProperties.index)!)
parseInt(privateKey.getProperty(KeyProperties.index)!)

Check warning on line 198 in src/pluto/Pluto.ts

View workflow job for this annotation

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

🌿 Branch is not covered

Warning! Not covered branch
: 0,
null
)
Expand Down Expand Up @@ -266,11 +269,17 @@ export default class Pluto implements PlutoInterface {
metaId: string | null
) {
const privateKeysEntity = new entities.PrivateKey();
metaId && (privateKeysEntity.id = metaId); // question: Where should I store metaId

if (typeof metaId === "string") {
// question: Where should I store metaId
privateKeysEntity.id = metaId;
}

privateKeysEntity.curve = privateKey.curve;
privateKeysEntity.privateKey = Buffer.from(privateKey.value).toString();
privateKeysEntity.privateKey = privateKey.to.Hex();
privateKeysEntity.keyPathIndex = keyPathIndex ?? 0;
privateKeysEntity.didId = did.toString();

await this.dataSource.manager.save(privateKeysEntity);
}

Expand Down Expand Up @@ -383,11 +392,11 @@ export default class Pluto implements PlutoInterface {
}
return didResponse.map(
(item) =>
({
did: DID.fromString(item.did),
alias: item.alias,
keyPathIndex: item.private_key_keyPathIndex,
} as PrismDIDInfo)
({
did: DID.fromString(item.did),
alias: item.alias,
keyPathIndex: item.private_key_keyPathIndex,
} as PrismDIDInfo)
);
} catch (error) {
throw new Error((error as Error).message);
Expand Down Expand Up @@ -454,11 +463,11 @@ export default class Pluto implements PlutoInterface {
* @async
* @returns {unknown}
*/
async getAllPeerDIDs() {
async getAllPeerDIDs(): Promise<PeerDID[]> {
const didRepository: Repository<entities.DID> =
this.dataSource.manager.getRepository("did");
const privateKeysRepository =
this.dataSource.manager.getRepository("private_key");
this.dataSource.manager.getRepository<entities.PrivateKey>("private_key");
/*
* This method is overcomplicated, dids should have relations.
* */
Expand All @@ -482,13 +491,16 @@ export default class Pluto implements PlutoInterface {
})
);

return didsWithKeys.map((item) => ({
did: DID.fromString(item.did),
privateKeys: item.privateKeys.map((key) => ({
const peerDIDs = didsWithKeys.map(item => {
const privateKeys = item.privateKeys.map((key) => ({
keyCurve: getKeyCurveByNameAndIndex(key.curve, key.keyPathIndex),
value: Buffer.from(key.privateKey),
})),
})) as PeerDID[];
value: Buffer.from(key.privateKey, "hex"),
}));

return new PeerDID(DID.fromString(item.did), privateKeys);
});

return peerDIDs;
} catch (error) {
throw new Error((error as Error).message);
}
Expand All @@ -499,27 +511,26 @@ export default class Pluto implements PlutoInterface {
*
* @async
* @param {DID} did
* @returns {unknown}
* @returns {PrivateKey[]}
*/
async getDIDPrivateKeysByDID(did: DID): Promise<PrivateKey[]> {
const repository = this.dataSource.manager.getRepository("private_key");
try {
const didString = did.toString();
const data = await repository.findBy({
didId: Like(`${didString}%`),
});
return data.map((item) => {
const keyCurve = getKeyCurveByNameAndIndex(item.curve);
const repository = this.dataSource.manager.getRepository<entities.PrivateKey>("private_key");
const data = await repository.findBy({ didId: Like(`${did.toString()}%`) });

if (keyCurve.curve === Curve.ED25519) {
return new Ed25519PrivateKey(Buffer.from(item.privateKey));
}
return data.map(item => this.mapToPrivateKey(item));
}

return new X25519PrivateKey(Buffer.from(item.privateKey));
}) as PrivateKey[];
} catch (error) {
throw new Error((error as Error).message);
private mapToPrivateKey(entity: entities.PrivateKey): PrivateKey {
switch (entity.curve) {
case Curve.SECP256K1:
return Secp256k1PrivateKey.from.Hex(entity.privateKey);
case Curve.ED25519:
return Ed25519PrivateKey.from.Hex(entity.privateKey);
case Curve.X25519:
return X25519PrivateKey.from.Hex(entity.privateKey);
}

throw new Error("Key Curve not recognised");

Check warning on line 533 in src/pluto/Pluto.ts

View workflow job for this annotation

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

🧾 Statement is not covered

Warning! Not covered statement
}

/**
Expand All @@ -530,24 +541,18 @@ export default class Pluto implements PlutoInterface {
* @returns {unknown}
*/
async getDIDPrivateKeyByID(id: string) {
const repository = this.dataSource.manager.getRepository("private_key");
const repository = this.dataSource.manager.getRepository<entities.PrivateKey>("private_key");

try {
const data = await repository.findOne({
where: {
id,
},
where: { id },
});

if (!data) {
return null;
}
const keyCurve = getKeyCurveByNameAndIndex(data.curve);

if (keyCurve.curve === Curve.ED25519) {
return new Ed25519PrivateKey(Buffer.from(data.privateKey));
}

return new X25519PrivateKey(Buffer.from(data.privateKey));
return this.mapToPrivateKey(data);
} catch (error) {
throw new Error((error as Error).message);
}
Expand Down
2 changes: 1 addition & 1 deletion src/prism-agent/Agent.Credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ export class AgentCredentials implements AgentCredentialsClass {

const signedJWT = await jwt.sign(
didInfo.did,
base64url.baseDecode(Buffer.from(prismPrivateKey.value).toString()),
prismPrivateKey,
{
iss: didInfo.did.toString(),
aud: domain,
Expand Down
37 changes: 37 additions & 0 deletions tests/apollo/JWT.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { expect } from "chai";
import { Ed25519PrivateKey } from "../../src/apollo/utils/Ed25519PrivateKey";
import { Secp256k1PrivateKey } from "../../src/apollo/utils/Secp256k1PrivateKey";
import { X25519PrivateKey } from "../../src/apollo/utils/X25519PrivateKey";
import { DID } from "../../src/domain";
import { JWT } from "../../src/apollo/utils/jwt/JWT";


describe("Apollo - JWT", () => {
describe("sign", () => {
[
Ed25519PrivateKey,
X25519PrivateKey,
Secp256k1PrivateKey
].forEach(keyClass => {
test(`${keyClass.name} - can sign with raw key (Uint8Array)`, async function () {
const prismDid = DID.fromString("did:prism:dadsa:1231321dhsauda23847");
const privateKey = keyClass.from.Hex("5ee38f66d07fc5a7f3968b33564bbd3b00eaddd481365838a9d6d3ad2fb82f41");

const sut = new JWT({} as any);

const result = await sut.sign(prismDid, privateKey.raw, { testing: 123 });
expect(result).to.be.a.string;
});

test(`${keyClass.name} - can sign with PrivateKey`, async function () {
const prismDid = DID.fromString("did:prism:dadsa:1231321dhsauda23847");
const privateKey = keyClass.from.Hex("5ee38f66d07fc5a7f3968b33564bbd3b00eaddd481365838a9d6d3ad2fb82f41");

const sut = new JWT({} as any);

const result = await sut.sign(prismDid, privateKey, { testing: 123 });
expect(result).to.be.a.string;
});
});
});
});
Loading

0 comments on commit 29df198

Please sign in to comment.