Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/testdata/testwallets/validators.pem
Original file line number Diff line number Diff line change
@@ -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-----
1 change: 1 addition & 0 deletions src/wallet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export * from "./userSigner";
export * from "./userVerifier";
export * from "./userWallet";
export * from "./validatorKeys";
export * from "./validatorPem";
export * from "./validatorSigner";
71 changes: 71 additions & 0 deletions src/wallet/validatorPem.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's no test for toText(), but maybe this one for save() is sufficient?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added tests for toText, save is already tested via the other tests


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);
});
});
52 changes: 52 additions & 0 deletions src/wallet/validatorPem.ts
Original file line number Diff line number Diff line change
@@ -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<ValidatorPEM> {
return (await this.fromFileAll(path))[index];
}

static async fromFileAll(path: string): Promise<ValidatorPEM[]> {
const absPath = resolve(path);
const text = readFileSync(absPath, "utf-8");
return await this.fromTextAll(text);
}

static async fromText(text: string, index = 0): Promise<ValidatorPEM> {
return (await ValidatorPEM.fromTextAll(text))[index];
}

static async fromTextAll(text: string): Promise<ValidatorPEM[]> {
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();
}
}
37 changes: 35 additions & 2 deletions src/wallet/validatorSigner.ts
Original file line number Diff line number Diff line change
@@ -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<Uint8Array> {
static async signUsingPem(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny breaking change in theory, but can be in minor version - since, actually, it's a "design correction change" (it's a fix).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as discussed with @andreibancioiu, I think we can mark this as deprecated since we already have the sign method and this does not seem to bring significant benefits.

pemText: string,
pemIndex: number = 0,
signable: Buffer | Uint8Array,
): Promise<Uint8Array> {
await BLS.initIfNecessary();

try {
Expand All @@ -18,4 +30,25 @@ export class ValidatorSigner {
throw new ErrSignerCannotSign(err);
}
}

static async fromPemFile(path: string, index = 0): Promise<ValidatorSigner> {
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);
}
}
Comment on lines +39 to +45
Copy link

Copilot AI Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are now two different sign methods and two different trySign methods with different signatures. The original trySign method (lines 17-23) takes a secretKey parameter, while the new trySign method (lines 41-42) uses the instance's secretKey. Consider renaming one of these methods to avoid confusion and maintain consistency.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment is somehow wrong.

But maybe async signUsingPem should be static?


private trySign(data: Uint8Array): Uint8Array {
Comment on lines +41 to +47
Copy link

Copilot AI Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are now two different sign methods and two different trySign methods with different signatures. The original trySign method (lines 17-23) takes a secretKey parameter, while the new trySign method (lines 41-42) uses the instance's secretKey. Consider renaming one of these methods to avoid confusion and maintain consistency.

Suggested change
return this.trySign(data);
} catch (err) {
throw new ErrSignerCannotSign(err as Error);
}
}
private trySign(data: Uint8Array): Uint8Array {
return this.signWithSecretKey(data);
} catch (err) {
throw new ErrSignerCannotSign(err as Error);
}
}
private signWithSecretKey(data: Uint8Array): Uint8Array {

Copilot uses AI. Check for mistakes.
return this.secretKey.sign(data);
}

getPubkey(): ValidatorPublicKey {
return this.secretKey.generatePublicKey();
}
}
Loading