Skip to content

Commit 2e8df95

Browse files
committed
use mnemonic or seed instead of privatekey
1 parent 84ee6df commit 2e8df95

25 files changed

+174
-93
lines changed

packages/registrar_client/README.md

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,12 @@ This package provides a client for interacting with the TFGrid v4 Node Registrar
3535

3636
## Getting Started
3737

38-
To initialize the Registrar Client, you need to provide the base url of registrar and Base64-encoded, 64-byte raw Ed25519 private key (nacl format).
38+
To initialize the Registrar Client, you need to provide the base url of registrar and your mnemonics or 64 character hex seed
3939

40-
To generate a 64-byte ed25519 private key, you can use tweetnacl library to generate key:
40+
To generate 64 character hex seed:
4141

42-
```typescript
43-
import nacl from "tweetnacl";
44-
import base64 from "base64-js";
45-
46-
const keyPair = nacl.sign.keyPair();
47-
const privateKey = base64.fromByteArray(keyPair.secretKey);
48-
49-
console.log("Your 64-byte ed25519 private key:", privateKey);
42+
```bash
43+
openssl rand -hex 32
5044
```
5145

5246
Here is an example:

packages/registrar_client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"dependencies": {
5757
"@stellar/stellar-base": "^12.1.1",
5858
"@types/jest": "^29.5.14",
59+
"bip39": "^3.1.0",
5960
"dotenv": "^16.4.7",
6061
"jest": "^29.7.0",
6162
"ts-jest": "^29.2.5",

packages/registrar_client/scripts/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ yarn install
1616

1717
## Getting Started
1818

19-
- Set base URL, your private key, and Stellar address (if needed) in `scripts/config.json`.
19+
- Set base URL, your mnemonic or seed, and Stellar address (if needed) in `scripts/config.json`.
2020

2121
## Usage
2222

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"baseUrl": "http://localhost:8080/v1", "privateKey": "", "stellarAddress": ""}
1+
{"baseUrl": "https://registrar.dev4.grid.tf/v1", "mnemonicOrSeed": "", "stellarAddress": ""}

packages/registrar_client/scripts/create_account.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ async function createAccount(client: RegistrarClient) {
1010
}
1111

1212
async function main() {
13-
const client = new RegistrarClient({ baseURL: config.baseUrl, privateKey: config.privateKey });
13+
const client = new RegistrarClient({ baseURL: config.baseUrl, mnemonicOrSeed: config.mnemonicOrSeed });
1414
await createAccount(client);
1515
}
1616

packages/registrar_client/scripts/create_farm.ts

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

77
async function getAccount(client: RegistrarClient): Promise<Account> {
8-
const publicKey = tweetnacl.sign.keyPair.fromSecretKey(base64.toByteArray(config.privateKey)).publicKey;
8+
const publicKey = await derivePublicKey(config.mnemonicOrSeed);
99
const account = await client.accounts.getAccountByPublicKey(base64.fromByteArray(publicKey));
1010
log("================= Getting Account =================");
1111
log(account);
@@ -29,7 +29,7 @@ async function getFarm(client: RegistrarClient, farmID: number) {
2929
}
3030

3131
async function main() {
32-
const client = new RegistrarClient({ baseURL: config.baseUrl, privateKey: config.privateKey });
32+
const client = new RegistrarClient({ baseURL: config.baseUrl, mnemonicOrSeed: config.mnemonicOrSeed });
3333
const account = await getAccount(client);
3434
const twinID = account.twin_id;
3535
const farmID = await createFarm(client, twinID, config.stellarAddress);

packages/registrar_client/scripts/create_node.ts

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

77
async function getAccount(client: RegistrarClient): Promise<Account> {
8-
const publicKey = tweetnacl.sign.keyPair.fromSecretKey(base64.toByteArray(config.privateKey)).publicKey;
8+
const publicKey = await derivePublicKey(config.mnemonicOrSeed);
99
const account = await client.accounts.getAccountByPublicKey(base64.fromByteArray(publicKey));
1010
log("================= Getting Account =================");
1111
log(account);
@@ -29,7 +29,7 @@ async function getNode(client: RegistrarClient, nodeID: number) {
2929
}
3030

3131
async function main() {
32-
const client = new RegistrarClient({ baseURL: config.baseUrl, privateKey: config.privateKey });
32+
const client = new RegistrarClient({ baseURL: config.baseUrl, mnemonicOrSeed: config.mnemonicOrSeed });
3333
const account = await getAccount(client);
3434
const twinID = account.twin_id;
3535
const node: NodeRegistrationRequest = {

packages/registrar_client/scripts/list_farms.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ async function listFarms(client: RegistrarClient, filter: NodesFilter) {
99
}
1010

1111
async function main() {
12-
const client = new RegistrarClient({ baseURL: config.baseUrl, privateKey: config.privateKey });
12+
const client = new RegistrarClient({ baseURL: config.baseUrl, mnemonicOrSeed: config.mnemonicOrSeed });
1313
await listFarms(client, {});
1414
}
1515

packages/registrar_client/scripts/list_nodes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ async function listNodes(client: RegistrarClient, filter: NodesFilter) {
99
}
1010

1111
async function main() {
12-
const client = new RegistrarClient({ baseURL: config.baseUrl, privateKey: config.privateKey });
12+
const client = new RegistrarClient({ baseURL: config.baseUrl, mnemonicOrSeed: config.mnemonicOrSeed });
1313
const filter: NodesFilter = {
1414
farm_id: 70,
1515
};

packages/registrar_client/scripts/update_account.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ async function getAccount(client: RegistrarClient, twinID: number) {
1717
}
1818

1919
async function main() {
20-
const client = new RegistrarClient({ baseURL: config.baseUrl, privateKey: config.privateKey });
20+
const client = new RegistrarClient({ baseURL: config.baseUrl, mnemonicOrSeed: config.mnemonicOrSeed });
2121
const update: UpdateAccountRequest = {
2222
relays: ["relay1", "relay2"],
2323
rmb_enc_key: "rmb_enc_key",

packages/registrar_client/scripts/update_farm.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ async function getFarm(client: RegistrarClient, farmID: number) {
1717
}
1818

1919
async function main() {
20-
const client = new RegistrarClient({ baseURL: config.baseUrl, privateKey: config.privateKey });
20+
const client = new RegistrarClient({ baseURL: config.baseUrl, mnemonicOrSeed: config.mnemonicOrSeed });
2121
const twinID = 143;
2222
const farmID = 46;
2323
const farmName = "testfarm";

packages/registrar_client/scripts/update_node.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ async function getNode(client: RegistrarClient, nodeID: number) {
1717
}
1818

1919
async function main() {
20-
const client = new RegistrarClient({ baseURL: config.baseUrl, privateKey: config.privateKey });
20+
const client = new RegistrarClient({ baseURL: config.baseUrl, mnemonicOrSeed: config.mnemonicOrSeed });
2121
const update: UpdateNodeRequest = {
2222
farm_id: 46,
2323
interfaces: [
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { validateMnemonic, mnemonicToSeed } from "bip39";
2+
import { sign } from "tweetnacl";
3+
4+
export async function derivePublicKey(mnemonicOrSeed: string): Promise<Uint8Array> {
5+
let seed: Buffer;
6+
if (validateMnemonic(mnemonicOrSeed)) {
7+
seed = (await mnemonicToSeed(mnemonicOrSeed)).subarray(0, 32);
8+
} else if (mnemonicOrSeed.startsWith("0x")) {
9+
seed = Buffer.from(mnemonicOrSeed.slice(2), "hex");
10+
} else {
11+
throw new Error("Invalid seed or mnemonic");
12+
}
13+
const keyPair = sign.keyPair.fromSeed(seed);
14+
return keyPair.publicKey
15+
}

packages/registrar_client/src/client/client.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Accounts } from "../modules/accounts";
33
import { Farms } from "../modules/farms";
44
import { Nodes } from "../modules/nodes";
55
import { Zos } from "../modules/zos";
6-
6+
import {validateMnemonic} from "bip39";
77
export abstract class BaseClient {
88
private client: AxiosInstance;
99

@@ -35,25 +35,40 @@ export abstract class BaseClient {
3535
}
3636
interface Config {
3737
baseURL: string;
38-
privateKey: string;
38+
mnemonicOrSeed: string;
3939
}
4040

4141
export class RegistrarClient extends BaseClient {
42-
public readonly privateKey: string;
42+
public readonly mnemonicOrSeed: string;
4343
accounts: Accounts;
4444
farms: Farms;
4545
nodes: Nodes;
4646
zos: Zos;
4747

48-
constructor({ baseURL, privateKey }: Config) {
48+
_validateSeed(seed: string): string {
49+
if (!seed.startsWith("0x")) {
50+
seed = `0x${seed}`;
51+
}
52+
if (!seed.match(/^0x[a-fA-F0-9]{64}$/)) {
53+
return "";
54+
}
55+
return seed;
56+
}
57+
58+
constructor({ baseURL, mnemonicOrSeed }: Config) {
4959
if (!baseURL) {
5060
throw new Error("Base URL is required");
5161
}
52-
if (!privateKey) {
53-
throw new Error("Private key is required");
54-
}
5562
super(baseURL);
56-
this.privateKey = privateKey;
63+
64+
if (!validateMnemonic(mnemonicOrSeed)) {
65+
mnemonicOrSeed = this._validateSeed(mnemonicOrSeed);
66+
}
67+
if (!mnemonicOrSeed) {
68+
throw new Error("Invalid mnemonic or seed");
69+
}
70+
71+
this.mnemonicOrSeed = mnemonicOrSeed;
5772
this.accounts = new Accounts(this);
5873
this.farms = new Farms(this);
5974
this.nodes = new Nodes(this);

packages/registrar_client/src/modules/accounts.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { RegistrarClient } from "../client/client";
22
import { Account, CreateAccountRequest, UpdateAccountRequest } from "../types/account";
3-
import * as tweetnacl from "tweetnacl";
4-
import * as base64 from "base64-js";
53
import { createSignatureWithPublicKey, createAuthHeader } from "../utils";
64

75
export class Accounts {
@@ -14,11 +12,7 @@ export class Accounts {
1412
}
1513

1614
async createAccount(request: Partial<CreateAccountRequest>): Promise<Account | null> {
17-
const privateKey = this.client.privateKey;
18-
const keyPair = tweetnacl.sign.keyPair.fromSecretKey(base64.toByteArray(privateKey));
19-
20-
const publicKey = base64.fromByteArray(keyPair.publicKey);
21-
const { signature, timestamp } = createSignatureWithPublicKey(publicKey, privateKey);
15+
const { signature, publicKey, timestamp } = await createSignatureWithPublicKey(this.client.mnemonicOrSeed);
2216

2317
request.public_key = publicKey;
2418
request.signature = signature;
@@ -59,7 +53,7 @@ export class Accounts {
5953

6054
async updateAccount(twinID: number, body: UpdateAccountRequest): Promise<any> {
6155
try {
62-
const headers = createAuthHeader(twinID, this.client.privateKey);
56+
const headers = await createAuthHeader(twinID, this.client.mnemonicOrSeed);
6357

6458
const data = await this.client.patch<any>(`${this.accountUri}/${twinID}`, body, { headers });
6559
return data;

packages/registrar_client/src/modules/farms.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export class Farms {
5252
}
5353

5454
const farm = { farm_name: farmName, dedicated, twin_id: twinID, stellar_address: stellarAddress };
55-
const headers = createAuthHeader(twinID, this.client.privateKey);
55+
const headers = await createAuthHeader(twinID, this.client.mnemonicOrSeed);
5656
try {
5757
const data = await this.client.post<FarmCreationResponse>(`${this.farmUri}/`, farm, { headers });
5858
return data;
@@ -94,7 +94,7 @@ export class Farms {
9494
throw new Error("Invalid stellar address");
9595
}
9696

97-
const headers = createAuthHeader(twinID, this.client.privateKey);
97+
const headers = await createAuthHeader(twinID, this.client.mnemonicOrSeed);
9898
const farm : FarmUpdateRequest = { farm_name: name, stellar_address: stellarAddress };
9999
try {
100100
const data = await this.client.patch<any>(`${this.farmUri}/${farmID}`,farm , { headers });

packages/registrar_client/src/modules/nodes.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export class Nodes {
1919

2020
async registerNode(node: NodeRegistrationRequest): Promise<NodeRegistrationResponse> {
2121
this._validateNodeData(node);
22-
const headers = createAuthHeader(node.twin_id, this.client.privateKey);
22+
const headers = await createAuthHeader(node.twin_id, this.client.mnemonicOrSeed);
2323
try {
2424
const data = await this.client.post<NodeRegistrationResponse>(`${this.nodeUri}/`, node, { headers });
2525
return data;
@@ -50,7 +50,7 @@ export class Nodes {
5050

5151
async updateNode(nodeID: number, twinID: number, node: UpdateNodeRequest): Promise<any> {
5252
this._validateNodeData(node);
53-
const headers = createAuthHeader(twinID, this.client.privateKey);
53+
const headers = await createAuthHeader(twinID, this.client.mnemonicOrSeed);
5454
try {
5555
const data = await this.client.patch<Node>(`${this.nodeUri}/${nodeID}`, node, { headers });
5656
return data;
@@ -60,7 +60,7 @@ export class Nodes {
6060
}
6161

6262
async reportNodeUptime(nodeID: number, twinID: number, uptime: UptimeReportRequest): Promise<any> {
63-
const headers = createAuthHeader(twinID, this.client.privateKey);
63+
const headers = await createAuthHeader(twinID, this.client.mnemonicOrSeed);
6464
try {
6565
const data = await this.client.post<any>(`${this.nodeUri}/${nodeID}/uptime`, uptime, { headers });
6666
return data;

packages/registrar_client/src/utils.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,40 @@ import * as base64 from "base64-js";
22
import * as tweetnacl from "tweetnacl";
33
import { AxiosRequestConfig } from "axios";
44
import { Buffer } from "buffer";
5+
import { mnemonicToSeed, validateMnemonic} from "bip39";
56

67
function createSignatureForChallenge(challenge: string, privateKey: string): string {
78
const signature = tweetnacl.sign.detached(Buffer.from(challenge, "utf-8"), base64.toByteArray(privateKey));
89
return base64.fromByteArray(signature);
910
}
1011

11-
export function createSignatureWithPublicKey(publicKey: string, privateKey: string): { signature: string; timestamp: number } {
12-
if (publicKey === "") {
13-
throw new Error("Public key is required");
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);
16+
17+
} else if(mnemonicOrSeed.startsWith("0x")) {
18+
seed = Buffer.from(mnemonicOrSeed.slice(2), "hex");
19+
} else {
20+
throw new Error("Invalid seed or mnemonic");
1421
}
22+
const keyPair = tweetnacl.sign.keyPair.fromSeed(seed);
23+
return {
24+
publicKey: base64.fromByteArray(keyPair.publicKey),
25+
privateKey: base64.fromByteArray(keyPair.secretKey),
26+
};
27+
}
28+
29+
export async function createSignatureWithPublicKey(mnemonicOrSeed: string): Promise<{ signature: string; publicKey: string; timestamp: number; }> {
30+
const { publicKey, privateKey } = await deriveKeyPair(mnemonicOrSeed);
1531
const timestamp = Math.floor(Date.now() / 1000);
1632
const challenge = `${timestamp}:${publicKey}`;
1733
const signature = createSignatureForChallenge(challenge, privateKey);
18-
return { signature, timestamp };
34+
return { signature, publicKey, timestamp };
1935
}
2036

21-
export function createAuthHeader(twinID: number, privateKey: string): AxiosRequestConfig["headers"] {
37+
export async function createAuthHeader(twinID: number, mnemonicOrSeed: string): Promise<AxiosRequestConfig["headers"]> {
38+
const { privateKey } = await deriveKeyPair(mnemonicOrSeed);
2239
const timestamp = Math.floor(Date.now() / 1000);
2340
const challenge = `${timestamp}:${twinID}`;
2441
const signature = createSignatureForChallenge(challenge, privateKey);
@@ -27,3 +44,4 @@ export function createAuthHeader(twinID: number, privateKey: string): AxiosReque
2744
};
2845
return header;
2946
}
47+

packages/registrar_client/tests/integration_tests/account.spec.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,38 @@
11
import { describe, test, expect } from "@jest/globals";
22
import { RegistrarClient } from "../../src/client/client";
33
import { UpdateAccountRequest } from "../../src/types/account";
4-
import { generateKeypair } from "../utils";
4+
import {generateMnemonic} from "bip39";
5+
56
import config from "../config.json";
7+
import { derivePublicKey, generateRandomSeed } from "../utils";
68
describe("test account module", () => {
7-
const { publicKey, privateKey } = generateKeypair();
9+
const mnemonic = generateMnemonic();
810

9-
const client = new RegistrarClient({ baseURL: config.baseUrl, privateKey: privateKey });
11+
const client = new RegistrarClient({ baseURL: config.baseUrl, mnemonicOrSeed: mnemonic });
1012

1113
let twinID = 1;
12-
test("create account", async () => {
14+
test("create account with mnemonic", async () => {
1315
const account = await client.accounts.createAccount({});
1416
expect(account).not.toBeNull();
1517
if (account) {
1618
twinID = account.twin_id;
1719
}
1820
});
1921

22+
test("create account with seed", async () => {
23+
const seed = generateRandomSeed();
24+
const client = new RegistrarClient({ baseURL: config.baseUrl, mnemonicOrSeed: seed });
25+
const account = await client.accounts.createAccount({});
26+
expect(account).not.toBeNull();
27+
});
28+
29+
2030
test.skip("create account with same private key", async () => {
2131
await expect(client.accounts.createAccount({})).rejects.toThrowError("Failed to create account: 409 Conflict");
2232
});
2333

2434
test("get account by public key", async () => {
35+
const publicKey = await derivePublicKey(mnemonic);
2536
const account = await client.accounts.getAccountByPublicKey(publicKey);
2637
expect(account).not.toBeNull();
2738
});

0 commit comments

Comments
 (0)