Skip to content

Commit 35f428a

Browse files
committed
refactor: use polkadot keyring for deriving keys and signing challenges
1 parent 2e8df95 commit 35f428a

File tree

8 files changed

+239
-69
lines changed

8 files changed

+239
-69
lines changed

packages/registrar_client/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ openssl rand -hex 32
4646
Here is an example:
4747

4848
```typescript
49-
const client = new RegistrarClient({ baseURl: "https://registrar.dev4.grid.tf/v1", privateKey: your_private_key });
49+
const client = new RegistrarClient({ baseURl: "https://registrar.dev4.grid.tf/v1", mnemonicOrSeed: "your_mnemonic_or_seed" });
5050
```
5151

5252
To be able to create a farm you need to have a Stellar wallet and provide your Stellar address. For more details on how to create a Stellar wallet and generate a Stellar address, please refer to the [Stellar Account Viewer](https://www.stellar.org/account-viewer/#!/) or the [Stellar Documentation](https://developers.stellar.org/docs/tutorials/create-account/).
@@ -56,7 +56,7 @@ To be able to create a farm you need to have a Stellar wallet and provide your S
5656
Here is an example of how to use the Registrar Client:
5757

5858
```typescript
59-
const client = new RegistrarClient({ baseUrl: URl, privateKey: your_private_key });
59+
const client = new RegistrarClient({ baseUrl: URl, mnemonicOrSeed: your_mnemonic_or_seed });
6060

6161
// Example: Create an account
6262
const accountRequest: CreateAccountRequest = {

packages/registrar_client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"typescript": "^5.7.3"
5555
},
5656
"dependencies": {
57+
"@polkadot/keyring": "^13.4.4",
5758
"@stellar/stellar-base": "^12.1.1",
5859
"@types/jest": "^29.5.14",
5960
"bip39": "^3.1.0",

packages/registrar_client/scripts/create_farm.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { log } from "console";
22
import { Account, RegistrarClient } from "../src/";
33
import config from "./config.json";
44
import * as base64 from "base64-js";
5-
import { derivePublicKey } from "./utils";
5+
import { deriveKeyPair } from "../src/utils";
66

77
async function getAccount(client: RegistrarClient): Promise<Account> {
8-
const publicKey = await derivePublicKey(config.mnemonicOrSeed);
9-
const account = await client.accounts.getAccountByPublicKey(base64.fromByteArray(publicKey));
8+
const keyPair = await deriveKeyPair(config.mnemonicOrSeed);
9+
const account = await client.accounts.getAccountByPublicKey(base64.fromByteArray(keyPair.publicKey));
1010
log("================= Getting Account =================");
1111
log(account);
1212
log("================= Getting Account =================");

packages/registrar_client/scripts/create_node.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { log } from "console";
22
import { Account, RegistrarClient, NodeRegistrationRequest } from "../src/";
33
import config from "./config.json";
44
import * as base64 from "base64-js";
5-
import { derivePublicKey } from "./utils";
5+
import { deriveKeyPair } from "../src/utils";
66

77
async function getAccount(client: RegistrarClient): Promise<Account> {
8-
const publicKey = await derivePublicKey(config.mnemonicOrSeed);
9-
const account = await client.accounts.getAccountByPublicKey(base64.fromByteArray(publicKey));
8+
const keyPair = await deriveKeyPair(config.mnemonicOrSeed);
9+
const account = await client.accounts.getAccountByPublicKey(base64.fromByteArray(keyPair.publicKey));
1010
log("================= Getting Account =================");
1111
log(account);
1212
log("================= Getting Account =================");

packages/registrar_client/scripts/utils.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.

packages/registrar_client/src/utils.ts

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,56 @@
11
import * as base64 from "base64-js";
2-
import * as tweetnacl from "tweetnacl";
32
import { AxiosRequestConfig } from "axios";
43
import { Buffer } from "buffer";
5-
import { mnemonicToSeed, validateMnemonic} from "bip39";
4+
import { Keyring } from "@polkadot/keyring";
5+
import { KeypairType } from "@polkadot/util-crypto/types";
6+
import { KeyringPair } from "@polkadot/keyring/types";
7+
import { validateMnemonic } from "bip39";
8+
import { cryptoWaitReady } from "@polkadot/util-crypto";
69

7-
function createSignatureForChallenge(challenge: string, privateKey: string): string {
8-
const signature = tweetnacl.sign.detached(Buffer.from(challenge, "utf-8"), base64.toByteArray(privateKey));
10+
const SUPPORTED_KEYPAIR_TYPES: KeypairType[] = ["sr25519", "ed25519"];
11+
12+
13+
14+
function createSignatureForChallenge(challenge: string, keypair: KeyringPair): string {
15+
const signature = keypair.sign(Buffer.from(challenge));
916
return base64.fromByteArray(signature);
1017
}
1118

12-
async function deriveKeyPair(mnemonicOrSeed: string): Promise<{ publicKey: string; privateKey: string; }> {
13-
let seed : Buffer;
14-
if (validateMnemonic(mnemonicOrSeed)) {
15-
seed = (await mnemonicToSeed(mnemonicOrSeed)).subarray(0, 32);
19+
export async function deriveKeyPair(
20+
mnemonicOrSeed: string,
21+
keypairType: KeypairType = SUPPORTED_KEYPAIR_TYPES[0],
22+
): Promise<KeyringPair> {
23+
if (!SUPPORTED_KEYPAIR_TYPES.includes(keypairType)) {
24+
throw new Error(`Unsupported keypair type: ${keypairType}`);
25+
}
1626

17-
} else if(mnemonicOrSeed.startsWith("0x")) {
18-
seed = Buffer.from(mnemonicOrSeed.slice(2), "hex");
19-
} else {
27+
if (!validateMnemonic(mnemonicOrSeed) && !mnemonicOrSeed.startsWith("0x")) {
2028
throw new Error("Invalid seed or mnemonic");
2129
}
22-
const keyPair = tweetnacl.sign.keyPair.fromSeed(seed);
23-
return {
24-
publicKey: base64.fromByteArray(keyPair.publicKey),
25-
privateKey: base64.fromByteArray(keyPair.secretKey),
26-
};
30+
await cryptoWaitReady();
31+
const keyring = new Keyring({ type: keypairType });
32+
const keypair = keyring.addFromUri(mnemonicOrSeed);
33+
return keypair;
2734
}
2835

29-
export async function createSignatureWithPublicKey(mnemonicOrSeed: string): Promise<{ signature: string; publicKey: string; timestamp: number; }> {
30-
const { publicKey, privateKey } = await deriveKeyPair(mnemonicOrSeed);
36+
export async function createSignatureWithPublicKey(
37+
mnemonicOrSeed: string,
38+
): Promise<{ signature: string; publicKey: string; timestamp: number }> {
39+
const keypair = await deriveKeyPair(mnemonicOrSeed);
40+
const publicKey = base64.fromByteArray(keypair.publicKey);
3141
const timestamp = Math.floor(Date.now() / 1000);
3242
const challenge = `${timestamp}:${publicKey}`;
33-
const signature = createSignatureForChallenge(challenge, privateKey);
43+
const signature = createSignatureForChallenge(challenge, keypair);
3444
return { signature, publicKey, timestamp };
3545
}
3646

3747
export async function createAuthHeader(twinID: number, mnemonicOrSeed: string): Promise<AxiosRequestConfig["headers"]> {
38-
const { privateKey } = await deriveKeyPair(mnemonicOrSeed);
48+
const keypair = await deriveKeyPair(mnemonicOrSeed);
3949
const timestamp = Math.floor(Date.now() / 1000);
4050
const challenge = `${timestamp}:${twinID}`;
41-
const signature = createSignatureForChallenge(challenge, privateKey);
51+
const signature = createSignatureForChallenge(challenge, keypair);
4252
const header = {
4353
"X-Auth": `${Buffer.from(challenge).toString("base64")}:${signature}`,
4454
};
4555
return header;
4656
}
47-

packages/registrar_client/tests/unit_tests/utils.spec.ts

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
import * as base64 from "base64-js";
2-
import * as tweetnacl from "tweetnacl";
32
import { describe, test, expect, jest, beforeEach } from "@jest/globals";
43
import { createSignatureWithPublicKey, createAuthHeader } from "../../src/utils";
54
import { generateMnemonic} from "bip39";
65

7-
jest.mock("tweetnacl", () => ({
8-
sign: {
9-
detached: jest.fn(() => new Uint8Array([1, 2, 3, 4])),
10-
keyPair: {
11-
fromSeed: jest.fn(() => ({
12-
publicKey: new Uint8Array([1, 2, 3, 4]),
13-
secretKey: new Uint8Array([1, 2, 3, 4]),
14-
})),
15-
},
16-
},
17-
}));
6+
7+
8+
const mockPublicKey = new Uint8Array([1, 2, 3, 4]);
9+
const mockSignature = jest.fn().mockReturnValue(new Uint8Array([1, 2, 3, 4]));
10+
jest.mock("@polkadot/keyring", () => {
11+
return {
12+
Keyring: jest.fn().mockImplementation(() => ({
13+
addFromUri: jest.fn().mockReturnValue({
14+
publicKey: mockPublicKey,
15+
sign: mockSignature,
16+
}),
17+
})),
18+
};
19+
});
1820

1921
describe("Util Functions", () => {
2022
const mnemonic = generateMnemonic();
@@ -23,39 +25,39 @@ describe("Util Functions", () => {
2325

2426
beforeEach(() => {
2527
jest.clearAllMocks();
28+
29+
jest.spyOn(global.Date, "now").mockImplementation(() => 1700000000000);
2630
});
2731

2832
test("createSignatureWithPublicKey generates a valid signature from mnemonics", async () => {
2933
const { signature, publicKey, timestamp } = await createSignatureWithPublicKey(mnemonic);
30-
expect(tweetnacl.sign.detached).toHaveBeenCalledWith(
34+
expect(timestamp).toBe(1700000000);
35+
expect(publicKey).toBe(base64.fromByteArray(new Uint8Array([1, 2, 3, 4])));
36+
37+
expect(mockSignature).toHaveBeenCalledWith(
3138
Buffer.from(`${timestamp}:${publicKey}`, "utf-8"),
32-
new Uint8Array([1, 2, 3, 4]),
3339
);
34-
3540
expect(signature).toBe(base64.fromByteArray(new Uint8Array([1, 2, 3, 4])));
3641
});
3742

3843
test("createSignatureWithPublicKey generates a valid signature from seed", async () => {
3944
const { signature, publicKey, timestamp } = await createSignatureWithPublicKey(seed);
40-
expect(tweetnacl.sign.detached).toHaveBeenCalledWith(
45+
expect(timestamp).toBe(1700000000);
46+
expect(publicKey).toBe(base64.fromByteArray(new Uint8Array([1, 2, 3, 4])));
47+
expect(mockSignature).toHaveBeenCalledWith(
4148
Buffer.from(`${timestamp}:${publicKey}`, "utf-8"),
42-
new Uint8Array([1, 2, 3, 4]),
4349
);
44-
45-
expect(signature).toBe(base64.fromByteArray(new Uint8Array
46-
([1, 2, 3, 4])));
50+
expect(signature).toBe(base64.fromByteArray(new Uint8Array([1, 2, 3, 4])));
4751
});
4852

4953
test("createAuthHeader generates correct headers", async () => {
50-
jest.spyOn(global.Date, "now").mockImplementation(() => 1700000000000);
51-
const timestamp = 1700000000;
5254

5355
const headers = await createAuthHeader(twinID, mnemonic);
5456

5557
expect(headers).toHaveProperty("X-Auth");
5658
const [encodedChallenge, signature] = headers!["X-Auth"].split(":");
5759

58-
expect(Buffer.from(encodedChallenge, "base64").toString("utf-8")).toBe(`${timestamp}:${twinID}`);
60+
expect(encodedChallenge).toBe(base64.fromByteArray(Buffer.from(`${1700000000}:${twinID}`)));
5961
expect(signature).toBe(base64.fromByteArray(new Uint8Array([1, 2, 3, 4])));
6062
});
6163

0 commit comments

Comments
 (0)