diff --git a/src/testdata/testwallets/validators.pem b/src/testdata/testwallets/validators.pem new file mode 100644 index 000000000..14bc776d8 --- /dev/null +++ b/src/testdata/testwallets/validators.pem @@ -0,0 +1,8 @@ +-----BEGIN PRIVATE KEY for f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d----- +N2MxOWJmM2EwYzU3Y2RkMWZiMDhlNDYwN2NlYmFhMzY0N2Q2YjkyNjFiNDY5M2Y2 +MWU5NmU1NGIyMThkNDQyYQ== +-----END PRIVATE KEY for f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d----- +-----BEGIN PRIVATE KEY for 1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d----- +MzAzNGIxZDU4NjI4YTg0Mjk4NGRhMGM3MGRhMGI1YTI1MWViYjJhZWJmNTFhZmM1 +YjU4NmUyODM5YjVlNTI2Mw== +-----END PRIVATE KEY for 1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d----- diff --git a/src/wallet/index.ts b/src/wallet/index.ts index a0181b16d..da2cc8a5f 100644 --- a/src/wallet/index.ts +++ b/src/wallet/index.ts @@ -8,4 +8,5 @@ export * from "./userSigner"; export * from "./userVerifier"; export * from "./userWallet"; export * from "./validatorKeys"; +export * from "./validatorPem"; export * from "./validatorSigner"; diff --git a/src/wallet/validatorPem.spec.ts b/src/wallet/validatorPem.spec.ts new file mode 100644 index 000000000..746ef9635 --- /dev/null +++ b/src/wallet/validatorPem.spec.ts @@ -0,0 +1,71 @@ +import { assert } from "chai"; +import * as fs from "fs"; +import path from "path"; +import { getTestWalletsPath } from "../testutils"; +import { ValidatorPEM } from "./validatorPem"; + +describe("test ValidatorPEMs", () => { + const walletsPath = path.join("src", "testdata", "testwallets"); + const pemPath = `${getTestWalletsPath()}/validatorKey00.pem`; + const savedPath = pemPath.replace(".pem", "-saved.pem"); + + afterEach(() => { + if (fs.existsSync(savedPath)) { + fs.unlinkSync(savedPath); // cleanup + } + }); + + it("should save pem from file", async function () { + const contentExpected = fs.readFileSync(pemPath, "utf-8").trim(); + + // fromFile is async → await + const pem = await ValidatorPEM.fromFile(pemPath); + pem.save(savedPath); + + const contentActual = fs.readFileSync(savedPath, "utf-8").trim(); + assert.deepEqual(contentActual, contentExpected); + }); + + it("should create from text all", async function () { + let text = fs.readFileSync(path.join(walletsPath, "validatorKey00.pem"), "utf-8"); + + let entries = await ValidatorPEM.fromTextAll(text); + let entry = entries[0]; + + assert.lengthOf(entries, 1); + assert.equal( + entry.label, + "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208", + ); + assert.equal(entry.secretKey.hex(), "7cff99bd671502db7d15bc8abc0c9a804fb925406fbdd50f1e4c17a4cd774247"); + + text = fs.readFileSync(path.join(walletsPath, "validators.pem"), "utf-8"); + entries = await ValidatorPEM.fromTextAll(text); + entry = entries[0]; + + assert.lengthOf(entries, 2); + assert.equal( + entry.label, + "f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d", + ); + assert.equal(entry.secretKey.hex(), "7c19bf3a0c57cdd1fb08e4607cebaa3647d6b9261b4693f61e96e54b218d442a"); + + entry = entries[1]; + assert.equal( + entry.label, + "1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d", + ); + assert.equal(entry.secretKey.hex(), "3034b1d58628a842984da0c70da0b5a251ebb2aebf51afc5b586e2839b5e5263"); + }); + + it("should convert to text", async function () { + let text = fs.readFileSync(path.join(walletsPath, "validatorKey00.pem"), "utf-8").trim(); + const entry = await ValidatorPEM.fromTextAll(text); + assert.deepEqual(entry[0].toText(), text); + + text = fs.readFileSync(path.join(walletsPath, "multipleValidatorKeys.pem"), "utf-8").trim(); + const entries = await ValidatorPEM.fromTextAll(text); + const actualText = entries.map((entry) => entry.toText()).join("\n"); + assert.deepEqual(actualText, text); + }); +}); diff --git a/src/wallet/validatorPem.ts b/src/wallet/validatorPem.ts new file mode 100644 index 000000000..b7e07f56d --- /dev/null +++ b/src/wallet/validatorPem.ts @@ -0,0 +1,52 @@ +import { readFileSync, writeFileSync } from "fs"; +import { resolve } from "path"; +import { PemEntry } from "../wallet/pemEntry"; +import { BLS, ValidatorSecretKey } from "../wallet/validatorKeys"; + +export class ValidatorPEM { + readonly label: string; + readonly secretKey: ValidatorSecretKey; + + constructor(label: string, secretKey: ValidatorSecretKey) { + this.label = label; + this.secretKey = secretKey; + } + + static async fromFile(path: string, index = 0): Promise { + return (await this.fromFileAll(path))[index]; + } + + static async fromFileAll(path: string): Promise { + const absPath = resolve(path); + const text = readFileSync(absPath, "utf-8"); + return await this.fromTextAll(text); + } + + static async fromText(text: string, index = 0): Promise { + return (await ValidatorPEM.fromTextAll(text))[index]; + } + + static async fromTextAll(text: string): Promise { + const entries = PemEntry.fromTextAll(text); + const resultItems: ValidatorPEM[] = []; + + await BLS.initIfNecessary(); + for (const entry of entries) { + const secretKey = new ValidatorSecretKey(entry.message); + const item = new ValidatorPEM(entry.label, secretKey); + resultItems.push(item); + } + + return resultItems; + } + + save(path: string): void { + const absPath = resolve(path); + writeFileSync(absPath, this.toText(), "utf-8"); + } + + toText(): string { + const message = this.secretKey.valueOf(); + return new PemEntry(this.label, message).toText(); + } +} diff --git a/src/wallet/validatorSigner.ts b/src/wallet/validatorSigner.ts index 41122980f..ff684796b 100644 --- a/src/wallet/validatorSigner.ts +++ b/src/wallet/validatorSigner.ts @@ -1,14 +1,26 @@ import { ErrSignerCannotSign } from "../core/errors"; -import { BLS, ValidatorSecretKey } from "./validatorKeys"; +import { BLS, ValidatorPublicKey, ValidatorSecretKey } from "./validatorKeys"; +import { ValidatorPEM } from "./validatorPem"; /** * Validator signer (BLS signer) */ export class ValidatorSigner { + private readonly secretKey: ValidatorSecretKey; + + constructor(secretKey: ValidatorSecretKey) { + this.secretKey = secretKey; + } + /** + * * @deprecated This method will be deprecated! Use the sign method directly. * Signs a message. */ - async signUsingPem(pemText: string, pemIndex: number = 0, signable: Buffer | Uint8Array): Promise { + static async signUsingPem( + pemText: string, + pemIndex: number = 0, + signable: Buffer | Uint8Array, + ): Promise { await BLS.initIfNecessary(); try { @@ -18,4 +30,25 @@ export class ValidatorSigner { throw new ErrSignerCannotSign(err); } } + + static async fromPemFile(path: string, index = 0): Promise { + const secretKey = (await ValidatorPEM.fromFile(path, index)).secretKey; + return new ValidatorSigner(secretKey); + } + + sign(data: Uint8Array): Uint8Array { + try { + return this.trySign(data); + } catch (err) { + throw new ErrSignerCannotSign(err as Error); + } + } + + private trySign(data: Uint8Array): Uint8Array { + return this.secretKey.sign(data); + } + + getPubkey(): ValidatorPublicKey { + return this.secretKey.generatePublicKey(); + } }