Skip to content

Commit

Permalink
feat(apollo): Implementing PeerDID ED25519 and X25519 algorithms for …
Browse files Browse the repository at this point in the history
…KeyAgreement and Authentication. (#13)
  • Loading branch information
elribonazo authored Mar 6, 2023
1 parent 0b6ec28 commit f6eabef
Show file tree
Hide file tree
Showing 39 changed files with 1,653 additions and 195 deletions.
133 changes: 85 additions & 48 deletions apollo/Apollo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,39 +19,67 @@ import {
MnemonicLengthException,
MnemonicWordException,
} from "../domain/models/errors/Mnemonic";

import { DerivationPath } from "./utils/derivation/DerivationPath";
import { KeyDerivation } from "./utils/derivation/KeyDerivation";
import { Secp256k1PublicKey } from "./utils/Secp256k1PublicKey";
import { Secp256k1PrivateKey } from "./utils/Secp256k1PrivateKey";
import { Ed25519PrivateKey } from "./utils/Ed25519PrivateKey";
import { Ed25519PublicKey } from "./utils/Ed25519PublicKey";
import { X25519PrivateKey } from "./utils/X25519PrivateKey";
import { Ed25519KeyPair } from "./utils/Ed25519KeyPair";
import { X25519KeyPair } from "./utils/X25519KeyPair";
import { base64url } from "multiformats/bases/base64";
const EC = elliptic.ec;
const EDDSA = elliptic.eddsa;

const ec = new EC("secp256k1");
const eddsa = new EDDSA("ed25519");

export default class Apollo implements ApolloInterface {
getPrivateJWKJson(id: string, keyPair: KeyPair): string {
throw new Error("Method not implemented.");
}
getPublicJWKJson(id: string, keyPair: KeyPair): string {
throw new Error("Method not implemented.");
}
private getKeyPairForCurve(seed: Seed, curve: KeyCurve): KeyPair {
if (curve.curve == Curve.SECP256K1 || curve.curve == Curve.X25519) {
const keyPair = ec.genKeyPair({ entropy: Buffer.from(seed.value) });
const derivationPath = DerivationPath.fromPath(
`m/${curve.index || 0}'/0'/0'`
);
if (curve.curve == Curve.SECP256K1) {
const extendedKey = KeyDerivation.deriveKey(seed.value, derivationPath);
const keyPair = extendedKey.keyPair();
return {
keyCurve: curve,
privateKey: {
keyCurve: curve,
value: keyPair.getPrivate("hex"),
value: keyPair.privateKey.getEncoded(),
},
publicKey: {
keyCurve: curve,
value: keyPair.getPublic().encode("hex", false),
value: keyPair.publicKey.getEncoded(),
},
};
} else if (curve.curve == Curve.ED25519) {
const prv = eddsa.keyFromSecret(Buffer.from(seed.value));
const keyPair = new Ed25519KeyPair();
return {
keyCurve: curve,
privateKey: {
keyCurve: curve,
value: keyPair.getPrivate(),
},
publicKey: {
keyCurve: curve,
value: keyPair.getPublic(),
},
};
} else if (curve.curve == Curve.X25519) {
const keyPair = new X25519KeyPair();
return {
keyCurve: curve,
privateKey: {
keyCurve: curve,
value: prv.getSecret("hex"),
value: keyPair.getPrivate(),
},
publicKey: {
keyCurve: curve,
value: prv.getPublic("hex"),
value: keyPair.getPublic(),
},
};
} else {
Expand Down Expand Up @@ -94,94 +122,103 @@ export default class Apollo implements ApolloInterface {
return this.getKeyPairForCurve(seed, privateKey.keyCurve);
}
compressedPublicKeyFromPublicKey(publicKey: PublicKey): CompressedPublicKey {
const keyPair = ec.keyFromPublic(Buffer.from(publicKey.value, "hex"));
const secp256k1PublicKey = Secp256k1PublicKey.secp256k1FromBytes(
Buffer.from(publicKey.value)
);
return {
uncompressed: {
keyCurve: {
curve: Curve.SECP256K1,
},
value: keyPair.getPublic().encode("hex", true),
value: secp256k1PublicKey.getEncoded(),
},
value: keyPair.getPublic().encode("hex", true),
value: secp256k1PublicKey.getEncodedCompressed(),
};
}
compressedPublicKeyFromCompresedData(
compressedData: Uint8Array | string
compressedData: Uint8Array
): CompressedPublicKey {
const point = ec.curve.decodePoint(compressedData).encode("hex");
const keyPair = ec.keyFromPublic(Buffer.from(point, "hex"));
const secp256k1PublicKey =
Secp256k1PublicKey.secp256k1FromCompressed(compressedData);
return {
uncompressed: {
keyCurve: {
curve: Curve.SECP256K1,
},
value: keyPair.getPublic().encode("hex", true),
value: secp256k1PublicKey.getEncoded(),
},
value: keyPair.getPublic().encode("hex", true),
value: secp256k1PublicKey.getEncodedCompressed(),
};
}
publicKeyFromPoints(
curve: KeyCurve,
x: Uint8Array,
y: Uint8Array
): PublicKey {
const publicKey = ec.keyFromPublic({
x: Buffer.from(x).toString("hex"),
y: Buffer.from(y).toString("hex"),
});
const publicKey = Secp256k1PublicKey.secp256k1FromByteCoordinates(x, y);
return {
keyCurve: curve,
value: publicKey.getPublic().encode("hex", true),
value: publicKey.getEncoded(),
};
}
publicKeyFromPoint(curve: KeyCurve, x: Uint8Array): PublicKey {
const publicKey = ec.keyFromPublic(Buffer.from(x));
const publicKey = Secp256k1PublicKey.secp256k1FromBytes(x);
return {
keyCurve: curve,
value: publicKey.getPublic().encode("hex", true),
value: publicKey.getEncoded(),
};
}
signByteArrayMessage(privateKey: PrivateKey, message: Uint8Array): Signature {
const messageBuffer = Buffer.from(message);
if (privateKey.keyCurve.curve == Curve.ED25519) {
const keyPair = eddsa.keyFromSecret(Buffer.from(privateKey.value));
const ed25519PrivateKey = new Ed25519PrivateKey(
Buffer.from(privateKey.value)
);
return {
value: Buffer.from(ed25519PrivateKey.sign(messageBuffer)),
};
} else if (privateKey.keyCurve.curve == Curve.X25519) {
const x25519PrivateKeyPair = new X25519PrivateKey(
Buffer.from(privateKey.value)
);
return {
value: keyPair.sign(messageBuffer).toBytes(),
value: Buffer.from(x25519PrivateKeyPair.sign(messageBuffer)),
};
} else if (
privateKey.keyCurve.curve == Curve.SECP256K1 ||
privateKey.keyCurve.curve == Curve.X25519
) {
const keyPair = ec.keyFromPrivate(privateKey.value, "hex");
} else if (privateKey.keyCurve.curve == Curve.SECP256K1) {
const secp256k1PrivateKey = Secp256k1PrivateKey.secp256k1FromBytes(
privateKey.value
);
return {
value: Buffer.from(keyPair.sign(messageBuffer).toDER()),
value: Buffer.from(secp256k1PrivateKey.sign(messageBuffer)),
};
}
throw new Error("Method not implemented.");
}
signStringMessage(privateKey: PrivateKey, message: string): Signature {
return this.signByteArrayMessage(privateKey, Buffer.from(message));
}
getECInstanceByCurve(curve: Curve): elliptic.ec {
return new EC(curve === Curve.SECP256K1 ? "secp256k1" : "curve25519");
}
verifySignature(
publicKey: PublicKey,
challenge: Uint8Array,
signature: Signature
signature: Uint8Array
): boolean {
const challengeBuffer = Buffer.from(challenge);
const signatureBuffer = Buffer.from(signature.value);

const signatureBuffer = Buffer.from(signature);
if (publicKey.keyCurve.curve == Curve.ED25519) {
return eddsa.verify(challengeBuffer, signatureBuffer, publicKey.value);
} else if (
publicKey.keyCurve.curve == Curve.SECP256K1 ||
publicKey.keyCurve.curve == Curve.X25519
) {
return ec.verify(
challengeBuffer,
signatureBuffer,
Buffer.from(publicKey.value, "hex")
const ed25519PublicKey = new Ed25519PublicKey(publicKey.value);
return ed25519PublicKey.verify(challengeBuffer, signatureBuffer);
} else if (publicKey.keyCurve.curve == Curve.X25519) {
throw new Error("Method not implemented.");
} else if (publicKey.keyCurve.curve == Curve.SECP256K1) {
const compressed = this.compressedPublicKeyFromPublicKey(publicKey);
const secp256k1PublicKey = Secp256k1PublicKey.secp256k1FromCompressed(
compressed.value
);
return secp256k1PublicKey.verify(challengeBuffer, signatureBuffer);
}
throw new Error("Method not implemented.");
return false;
}
}
7 changes: 7 additions & 0 deletions apollo/utils/Ed25519KeyCommon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as elliptic from "elliptic";

const eddsa = new elliptic.eddsa("ed25519");
export abstract class Ed25519KeyCommon {
public static eddsa = eddsa;
public eddsa = eddsa;
}
44 changes: 44 additions & 0 deletions apollo/utils/Ed25519KeyPair.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import elliptic from "elliptic";
import { base64url } from "multiformats/bases/base64";

import { Ed25519KeyCommon } from "./Ed25519KeyCommon";
import { Ed25519PrivateKey } from "./Ed25519PrivateKey";
import { Ed25519PublicKey } from "./Ed25519PublicKey";

export class Ed25519KeyPair extends Ed25519KeyCommon {
private privateKey: Ed25519PrivateKey;
private publicKey: Ed25519PublicKey;

constructor() {
super();

const secret = Buffer.from(elliptic.rand(32));
const keyPair = this.eddsa.keyFromSecret(secret);

const pub = Buffer.from(keyPair.getPublic());

this.privateKey = new Ed25519PrivateKey(
Buffer.from(base64url.baseEncode(secret))
);

this.publicKey = new Ed25519PublicKey(
Buffer.from(base64url.baseEncode(pub))
);
}

public getPrivate(): Buffer {
return this.privateKey.getEncoded();
}

public getPublic(): Buffer {
return this.publicKey.getEncoded();
}

public sign(message: Buffer) {
return this.privateKey.sign(message);
}

public verify(message: Buffer, sig: Buffer) {
return this.publicKey.verify(message, sig);
}
}
24 changes: 24 additions & 0 deletions apollo/utils/Ed25519PrivateKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as elliptic from "elliptic";
import { base64url } from "multiformats/bases/base64";

import { Ed25519KeyCommon } from "./Ed25519KeyCommon";

export class Ed25519PrivateKey extends Ed25519KeyCommon {
private keyPair: elliptic.eddsa.KeyPair;

constructor(nativeValue: Buffer) {
super();
this.keyPair = this.eddsa.keyFromSecret(
Buffer.from(base64url.baseDecode(nativeValue.toString()))
);
}

getEncoded(): Buffer {
return Buffer.from(base64url.baseEncode(this.keyPair.getSecret()));
}

sign(message: Buffer) {
const sig = this.keyPair.sign(message);
return sig.toBytes();
}
}
29 changes: 29 additions & 0 deletions apollo/utils/Ed25519PublicKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as elliptic from "elliptic";
import { base64url } from "multiformats/bases/base64";

import { Ed25519KeyCommon } from "./Ed25519KeyCommon";

export class Ed25519PublicKey extends Ed25519KeyCommon {
private keyPair: elliptic.eddsa.KeyPair;

constructor(nativeValue: Uint8Array) {
super();

this.keyPair = this.eddsa.keyFromPublic(
Array.from(
base64url.baseDecode(nativeValue.toString())
) as unknown as Buffer
);
}

getEncoded(): Buffer {
return Buffer.from(base64url.baseEncode(this.keyPair.getPublic()));
}

verify(message: Buffer, sig: Buffer) {
//TODO: Report a bug in elliptic, this method is not expecting a Buffer (bytes)
//Internally it expects to find an array, if not Buffer.slice.concat fails when Array.slice.concat doesn't
//Must keep this...
return this.keyPair.verify(message, Array.from(sig) as unknown as Buffer);
}
}
7 changes: 7 additions & 0 deletions apollo/utils/Secp256k1KeyCommon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as elliptic from "elliptic";

const ec = new elliptic.ec("secp256k1");
export abstract class Secp256k1KeyCommon {
public static ec = ec;
public ec = ec;
}
32 changes: 32 additions & 0 deletions apollo/utils/Secp256k1KeyPair.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import BN from "bn.js";
import * as elliptic from "elliptic";
import { Secp256k1KeyCommon } from "./Secp256k1KeyCommon";

import { Secp256k1PrivateKey } from "./Secp256k1PrivateKey";
import { Secp256k1PublicKey } from "./Secp256k1PublicKey";

export class Secp256k1KeyPair extends Secp256k1KeyCommon {
constructor(
public privateKey: Secp256k1PrivateKey,
public publicKey: Secp256k1PublicKey
) {
super();
}

static generateSecp256k1KeyPair(): Secp256k1KeyPair {
const keyPair = this.ec.genKeyPair();
const bigNumber = keyPair.getPrivate();
const basePoint = keyPair.getPublic();
return Secp256k1KeyPair.fromNativeValues(bigNumber, basePoint);
}

static fromNativeValues(
privateNative: BN,
publicNative: elliptic.curve.base.BasePoint
): Secp256k1KeyPair {
return new Secp256k1KeyPair(
new Secp256k1PrivateKey(privateNative),
new Secp256k1PublicKey(publicNative)
);
}
}
Loading

0 comments on commit f6eabef

Please sign in to comment.