Skip to content

Commit fc80106

Browse files
suyukuoacnpetermetz
authored andcommitted
feat(common): KeyConverter class to and from PEM/hex/buffe
Signed-off-by: suyukuoacn <[email protected]>
1 parent ad01dee commit fc80106

File tree

10 files changed

+611
-13
lines changed

10 files changed

+611
-13
lines changed

karma.conf.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@ module.exports = (config) => {
1212
// 'Electron',
1313
// "ElectronWithGui",
1414
// 'Chrome',
15-
// 'ChromeHeadless',
16-
"ChromeHeadlessDebug",
15+
"ChromeHeadless",
16+
// "ChromeHeadlessDebug",
1717
],
1818

1919
files: [
20-
{
21-
pattern: "packages/cactus-common/src/test/typescript/unit/**/*.ts",
22-
},
20+
// FIXME: For whatever reason only the first test gets executed not all of them
21+
"./packages/cactus-common/src/test/typescript/unit/**/*",
2322
],
2423

2524
plugins: [

packages/cactus-common/package-lock.json

+44-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cactus-common/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
"dependencies": {
6565
"joi": "14.3.1",
6666
"json-stable-stringify": "1.0.1",
67+
"key-encoder": "2.0.3",
6768
"loglevel": "1.6.7",
6869
"loglevel-plugin-prefix": "0.8.4",
6970
"secp256k1": "4.0.2",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import KeyEncoder from "key-encoder";
2+
3+
export enum KeyFormat {
4+
Raw = "raw",
5+
Hex = "hex",
6+
PEM = "pem",
7+
}
8+
9+
export class KeyConverter {
10+
private keyEncoder: KeyEncoder;
11+
12+
public readonly supportedKeyFormats: string[];
13+
14+
constructor() {
15+
this.supportedKeyFormats = Object.values(KeyFormat);
16+
this.keyEncoder = new KeyEncoder("secp256k1");
17+
}
18+
19+
/**
20+
* Convert public key from one format to another format
21+
* @param key
22+
* @param fromFormat
23+
* @param toFormat
24+
*/
25+
public publicKeyAs(
26+
key: string | Uint8Array,
27+
fromFormat: KeyFormat,
28+
toFormat: KeyFormat
29+
): string | Uint8Array {
30+
this.validateKeyFormatValue(fromFormat);
31+
this.validateKeyFormatValue(toFormat);
32+
33+
let keyValue: string = "";
34+
let convertToRaw = false;
35+
36+
if (key instanceof Uint8Array) {
37+
keyValue = Buffer.from(key).toString("hex");
38+
} else {
39+
keyValue = key;
40+
}
41+
42+
if (fromFormat === KeyFormat.Hex) {
43+
fromFormat = KeyFormat.Raw;
44+
}
45+
46+
if (toFormat === KeyFormat.Raw) {
47+
convertToRaw = true;
48+
} else if (toFormat === KeyFormat.Hex) {
49+
toFormat = KeyFormat.Raw;
50+
}
51+
52+
let resultKey: string | Uint8Array = this.keyEncoder.encodePublic(
53+
keyValue,
54+
fromFormat,
55+
toFormat
56+
);
57+
58+
if (convertToRaw) {
59+
resultKey = Uint8Array.from(Buffer.from(resultKey, "hex"));
60+
}
61+
62+
return resultKey;
63+
}
64+
65+
/**
66+
* Convert private key from one format to another format
67+
* @param key
68+
* @param fromFormat
69+
* @param toFormat
70+
*/
71+
public privateKeyAs(
72+
key: string | Buffer,
73+
fromFormat: KeyFormat,
74+
toFormat: KeyFormat
75+
): string | Buffer {
76+
this.validateKeyFormatValue(fromFormat);
77+
this.validateKeyFormatValue(toFormat);
78+
79+
let keyValue = key;
80+
let convertToRaw = false;
81+
82+
if (fromFormat === KeyFormat.Raw) {
83+
if (key instanceof Buffer) {
84+
keyValue = key.toString("hex");
85+
}
86+
} else if (fromFormat === KeyFormat.Hex) {
87+
fromFormat = KeyFormat.Raw;
88+
}
89+
90+
if (toFormat === KeyFormat.Raw) {
91+
convertToRaw = true;
92+
} else if (toFormat === KeyFormat.Hex) {
93+
toFormat = KeyFormat.Raw;
94+
}
95+
96+
let resultKey: string | Buffer = this.keyEncoder.encodePrivate(
97+
keyValue,
98+
fromFormat,
99+
toFormat
100+
);
101+
102+
if (convertToRaw) {
103+
resultKey = Buffer.from(resultKey, "hex");
104+
}
105+
106+
return resultKey;
107+
}
108+
109+
/**
110+
* This method will validate if the input key format match to one of the enum value.
111+
* @param keyFormat
112+
*/
113+
private validateKeyFormatValue(keyFormat: KeyFormat): void {
114+
const fnTag = "KeyConverter#publicKeyAs()";
115+
116+
if (!this.supportedKeyFormats.some((val) => val === keyFormat)) {
117+
const csv =
118+
this.supportedKeyFormats.join(", ") +
119+
` => (` +
120+
Object.keys(KeyFormat)
121+
.map((f) => `KeyFormat.${f}`)
122+
.join(", ") +
123+
`)`;
124+
const msg = `${fnTag} Invalid KeyFormat ${keyFormat} Supported: (${csv})`;
125+
throw new Error(msg);
126+
}
127+
}
128+
}

packages/cactus-common/src/main/typescript/public-api.ts

+1
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ export {
1717

1818
export { ISignerKeyPair } from "./signer-key-pair";
1919
export { Secp256k1Keys } from "./secp256k1-keys";
20+
export { KeyFormat, KeyConverter } from "./key-converter";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import crypto from "crypto";
2+
import secp256k1 from "secp256k1";
3+
4+
export interface ISignerKeyPairs {
5+
privateKey: any;
6+
publicKey: any;
7+
}
8+
9+
export class Secp256k1Keys {
10+
/**
11+
* Generate random private and public secp256k1 key in Buffer format
12+
* @return Generated key pair
13+
*/
14+
static generateKeyPairsBuffer(): ISignerKeyPairs {
15+
let privKey: any;
16+
// generate secp256K1 private key
17+
do {
18+
privKey = crypto.randomBytes(32);
19+
} while (!secp256k1.privateKeyVerify(privKey));
20+
21+
// generate secp256K1 public key
22+
const pubKey = secp256k1.publicKeyCreate(privKey);
23+
24+
return { privateKey: privKey, publicKey: pubKey };
25+
}
26+
}

packages/cactus-common/src/test/typescript/unit/api-surface.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
// tslint:disable-next-line: no-var-requires
2-
import test from "tape";
1+
import test, { Test } from "tape";
32

43
import { Logger, LoggerProvider } from "../../../main/typescript/public-api";
54

6-
test("Library can be loaded", (assert: any) => {
7-
assert.plan(2);
5+
test("Library can be loaded", (assert: Test) => {
86
assert.ok(Logger);
97
assert.ok(LoggerProvider);
108
assert.end();

packages/cactus-common/src/test/typescript/unit/js-object-signer.test.ts

+9
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ test("Simple JSON Test", async (assert: Test) => {
5252
const sign2 = jsObjectSigner.sign(payload2);
5353

5454
assert.equals(sign1.toString, sign2.toString);
55+
assert.end();
5556
});
5657

5758
test("Simple Nested JSON Test", async (assert: Test) => {
@@ -70,6 +71,7 @@ test("Simple Nested JSON Test", async (assert: Test) => {
7071
const sign2 = jsObjectSigner.sign(outer2);
7172

7273
assert.equals(sign1.toString, sign2.toString);
74+
assert.end();
7375
});
7476

7577
test("Simple Date JSON Test", async (assert: Test) => {
@@ -106,6 +108,7 @@ test("Simple Date JSON Test", async (assert: Test) => {
106108
const sign2 = jsObjectSigner.sign(outer2);
107109

108110
assert.equals(sign1.toString, sign2.toString);
111+
assert.end();
109112
});
110113

111114
test("Circular JSON Test", async (assert: Test) => {
@@ -121,6 +124,7 @@ test("Circular JSON Test", async (assert: Test) => {
121124
obj.b = obj;
122125

123126
assert.throws(() => jsObjectSigner.sign(obj));
127+
assert.end();
124128
});
125129

126130
test("Very Signature Test", async (assert: Test) => {
@@ -136,6 +140,7 @@ test("Very Signature Test", async (assert: Test) => {
136140
const verify = jsObjectSigner.verify(payload1, sign1, keyPairs.publicKey);
137141

138142
assert.equals(true, verify);
143+
assert.end();
139144
});
140145

141146
test("Test optional sign function", async (assert: Test) => {
@@ -156,6 +161,7 @@ test("Test optional sign function", async (assert: Test) => {
156161
const sign2 = jsObjectSigner.sign(outer2);
157162

158163
assert.equals(sign1.toString, sign2.toString);
164+
assert.end();
159165
});
160166

161167
test("Test optional verify sign function", async (assert: Test) => {
@@ -175,6 +181,7 @@ test("Test optional verify sign function", async (assert: Test) => {
175181
const verify = jsObjectSigner.verify(outer1, sign1, keyPairs.publicKey);
176182

177183
assert.equals(true, verify);
184+
assert.end();
178185
});
179186

180187
test("Test optional hash function", async (assert: Test) => {
@@ -195,6 +202,7 @@ test("Test optional hash function", async (assert: Test) => {
195202
const sign2 = jsObjectSigner.sign(outer2);
196203

197204
assert.equals(sign1.toString, sign2.toString);
205+
assert.end();
198206
});
199207

200208
test("Test missing required constructor field", async (assert: Test) => {
@@ -207,4 +215,5 @@ test("Test missing required constructor field", async (assert: Test) => {
207215
} catch (e) {
208216
assert.equal(e.message, "JsObjectSigner#ctor options.privateKey falsy.");
209217
}
218+
assert.end();
210219
});

0 commit comments

Comments
 (0)