Skip to content
Snippets Groups Projects
Verified Commit 0d243b47 authored by Mateusz Tyszczak's avatar Mateusz Tyszczak :scroll:
Browse files

Improve workerbee security by letting user provide their wallet

parent a42a7d0f
No related branches found
No related tags found
1 merge request!3Improve wallet security
Pipeline #82410 canceled
......@@ -99,8 +99,14 @@ observer.subscribe({
```js
import WorkerBee from "@hive-staging/workerbee";
import beekeeperFactory from "@hive-staging/beekeeper";
const bot = new WorkerBee({ postingKey: "5JkFnXrLM2ap9t3AmAxBJvQHF7xSKtnTrCTginQCkhzU5S7ecPT" });
const beekeeper = await beekeeperFactory();
const session = await beekeeper.createSession("my.salt");
const { wallet } = await session.createWallet("w0", "mypassword");
await wallet.importKey("5JkFnXrLM2ap9t3AmAxBJvQHF7xSKtnTrCTginQCkhzU5S7ecPT");
const bot = new WorkerBee();
bot.on("error", console.error);
await bot.start();
......@@ -117,7 +123,7 @@ builder.push({
});
// Broadcast our transaction with custom internal expiration time
const observer = await bot.signAndBroadcast(builder.build());
const observer = await bot.broadcast(builder.build(wallet, "5RqVBAVNp5ufMCetQtvLGLJo7unX9nyCBMMrTXRWQ9i1Zzzizh"));
// Observe if our transaction has been applied
observer.subscribe({
......
......@@ -37,7 +37,7 @@ src/index.ts:8
#### Defined in
src/interfaces.ts:8
src/interfaces.ts:9
___
......@@ -47,7 +47,7 @@ ___
#### Defined in
src/interfaces.ts:7
src/interfaces.ts:8
<a name="interfacesibroadcastoptionsmd"></a>
......@@ -71,7 +71,7 @@ undefined
#### Defined in
src/interfaces.ts:72
src/interfaces.ts:73
<a name="interfacesioperationdatamd"></a>
......@@ -86,7 +86,7 @@ src/interfaces.ts:72
#### Defined in
src/interfaces.ts:18
src/interfaces.ts:19
___
......@@ -96,7 +96,7 @@ ___
#### Defined in
src/interfaces.ts:19
src/interfaces.ts:20
<a name="interfacesiqueenbeemd"></a>
......@@ -126,7 +126,7 @@ subscribable object that will call `next` each time time its manabar is 98 perce
#### Defined in
src/interfaces.ts:61
src/interfaces.ts:62
___
......@@ -150,7 +150,7 @@ subscribable object that will call `next` on every operation related to the give
#### Defined in
src/interfaces.ts:52
src/interfaces.ts:53
___
......@@ -174,7 +174,7 @@ subscribable object that will call `next` only once and completes
#### Defined in
src/interfaces.ts:29
src/interfaces.ts:30
**block**(`blockNumber`): `Subscribable`\<[`IBlockData`](#interfacesiblockdatamd)\>
......@@ -194,7 +194,7 @@ subscribable object that will call `next` only once and completes
#### Defined in
src/interfaces.ts:36
src/interfaces.ts:37
___
......@@ -218,7 +218,7 @@ subscribable object that will call `next` only once and completes
#### Defined in
src/interfaces.ts:44
src/interfaces.ts:45
<a name="interfacesistartconfigurationmd"></a>
......@@ -241,7 +241,7 @@ Beekeeper wallet options
#### Defined in
src/bot.ts:35
src/bot.ts:28
___
......@@ -259,19 +259,7 @@ Wax chain options
#### Defined in
src/bot.ts:27
___
### postingKey
`Optional` **postingKey**: `string`
Posting private key in WIF format
#### Defined in
src/bot.ts:19
src/bot.ts:20
<a name="interfacesitransactiondatamd"></a>
......@@ -286,7 +274,7 @@ src/bot.ts:19
#### Defined in
src/interfaces.ts:14
src/interfaces.ts:15
___
......@@ -296,7 +284,7 @@ ___
#### Defined in
src/interfaces.ts:12
src/interfaces.ts:13
___
......@@ -306,7 +294,7 @@ ___
#### Defined in
src/interfaces.ts:13
src/interfaces.ts:14
<a name="interfacesiworkerbeemd"></a>
......@@ -332,7 +320,7 @@ Remember that chain property will be initialized during [start](#start) call and
#### Defined in
src/interfaces.ts:85
src/interfaces.ts:86
___
......@@ -342,7 +330,7 @@ ___
#### Defined in
src/interfaces.ts:77
src/interfaces.ts:78
___
......@@ -352,7 +340,7 @@ ___
#### Defined in
src/interfaces.ts:102
src/interfaces.ts:105
___
......@@ -362,7 +350,7 @@ ___
#### Defined in
src/interfaces.ts:76
src/interfaces.ts:77
## Methods
......@@ -378,7 +366,7 @@ Allows you to iterate over blocks indefinitely
#### Defined in
src/interfaces.ts:122
src/interfaces.ts:123
___
......@@ -413,6 +401,34 @@ node_modules/.pnpm/@types+node@20.7.1/node_modules/@types/node/events.d.ts:462
___
### broadcast
**broadcast**(`tx`, `options?`): `Promise`\<`Subscribable`\<[`ITransactionData`](#interfacesitransactiondatamd)\>\>
Broadcast given transaction to the remote and returns a subscribable object
that calls error after [throwAfter](#interfacesibroadcastoptionsmd) time (if given)
If [throwAfter](#interfacesibroadcastoptionsmd) has not been specified, it is automatically
set to the transaction expiration time plus one minute
Requires signed transaction
#### Parameters
| Name | Type | Description |
| :------ | :------ | :------ |
| `tx` | `transaction` | Protobuf transactoin to broadcast |
| `options?` | [`IBroadcastOptions`](#interfacesibroadcastoptionsmd) | Options for broadcasting |
#### Returns
`Promise`\<`Subscribable`\<[`ITransactionData`](#interfacesitransactiondatamd)\>\>
#### Defined in
src/interfaces.ts:118
___
### delete
**delete**(): `Promise`\<`void`\>
......@@ -425,7 +441,7 @@ Deletes the current bot instance and underlying wax and beekepeer objects
#### Defined in
src/interfaces.ts:100
src/interfaces.ts:103
___
......@@ -685,7 +701,7 @@ EventEmitter.on
#### Defined in
src/interfaces.ts:130
src/interfaces.ts:131
**on**(`event`, `handler`): [`IWorkerBee`](#interfacesiworkerbeemd)
......@@ -708,7 +724,7 @@ EventEmitter.on
#### Defined in
src/interfaces.ts:137
src/interfaces.ts:138
**on**(`event`, `handler`): [`IWorkerBee`](#interfacesiworkerbeemd)
......@@ -731,7 +747,7 @@ EventEmitter.on
#### Defined in
src/interfaces.ts:144
src/interfaces.ts:145
**on**(`event`, `handler`): [`IWorkerBee`](#interfacesiworkerbeemd)
......@@ -754,7 +770,7 @@ EventEmitter.on
#### Defined in
src/interfaces.ts:151
src/interfaces.ts:152
**on**(`event`, `handler`): [`IWorkerBee`](#interfacesiworkerbeemd)
......@@ -777,7 +793,7 @@ EventEmitter.on
#### Defined in
src/interfaces.ts:158
src/interfaces.ts:159
___
......@@ -1153,41 +1169,17 @@ node_modules/.pnpm/@types+node@20.7.1/node_modules/@types/node/events.d.ts:633
___
### signAndBroadcast
**signAndBroadcast**(`tx`, `options?`): `Promise`\<`Subscribable`\<[`ITransactionData`](#interfacesitransactiondatamd)\>\>
### start
Broadcast given transaction to the remote and returns a subscribable object
that calls error after [throwAfter](#interfacesibroadcastoptionsmd) time (if given)
If [throwAfter](#interfacesibroadcastoptionsmd) has not been specified, it is automatically
set to the transaction expiration time plus one minute
**start**(`wallet?`): `Promise`\<`void`\>
If transaction is not already signed (at least one signature is present)
WorkerBee will try signing the transaction using specified in the configuration
private key
Starts the automation with given configuration
#### Parameters
| Name | Type | Description |
| :------ | :------ | :------ |
| `tx` | `transaction` | Protobuf transactoin to broadcast |
| `options?` | [`IBroadcastOptions`](#interfacesibroadcastoptionsmd) | Options for broadcasting |
#### Returns
`Promise`\<`Subscribable`\<[`ITransactionData`](#interfacesitransactiondatamd)\>\>
#### Defined in
src/interfaces.ts:117
___
### start
**start**(): `Promise`\<`void`\>
Starts the automation with given configuration
| `wallet?` | `IBeekeeperUnlockedWallet` | optional unlocked beekeper wallet for bot operations |
#### Returns
......@@ -1195,7 +1187,7 @@ Starts the automation with given configuration
#### Defined in
src/interfaces.ts:90
src/interfaces.ts:93
___
......@@ -1211,7 +1203,7 @@ Request automation stop
#### Defined in
src/interfaces.ts:95
src/interfaces.ts:98
<a name="interfacesiworkerbeeconstructormd"></a>
......@@ -1238,4 +1230,4 @@ Constructs new WorkerBee bot object
#### Defined in
src/interfaces.ts:167
src/interfaces.ts:168
......@@ -65,6 +65,17 @@ pnpm dlx parcel account-observer/index.html
### Post observer
Run the wallet manager first, create wallet and import key, then run the `index.html` page
**[post-observer/wallet.html](post-observer/wallet.html)**
Wallet manager for post observer creates wallet and imports keys for given account using our libraries
```bash
# Run example
pnpm dlx parcel post-observer/wallet.html
```
**[post-observer/index.html](post-observer/index.html)**
Post observer explains how to observe given account for new posts and automatically vote on them on the blockchain using our libraries
......
......@@ -30,7 +30,9 @@
Observed account:<input id="acc" placeholder="Observe account" value="initminer">
Beneficiary account:<input id="beneficiary" placeholder="Beneficiary account" value="initminer">
Voting account:<input id="voter" placeholder="Voting account" value="voter">
Private Posting key:<input id="key" placeholder="Private Posting key" value="5JqodRzm7Ag1QJgLgsCgEpQpkRKq1NQZGjaxy1Y48WxohCy2u5J">
Wallet name:<input id="wallet" placeholder="Wallet name" value="wallet0">
Wallet password:<input id="password" placeholder="Wallet password" value="password">
Public key:<input id="key" placeholder="Public key for signing" value="8BcysuXFFHMx3fcSzTH2Xpg1rtpPZ2oakaWe2LbKY1bCZH5HQJ">
<button id="start">Start</button>
<hr>
API endpoint:<input id="api-endpoint" placeholder="API endpoint">
......@@ -41,12 +43,15 @@
<script type="module">
import WorkerBee from "../../dist/bundle/index.js";
import { OperationVisitor } from "@hive-staging/wax";
import beekeeperFactory from "@hive-staging/beekeeper";
const startBtn = document.getElementById("start");
const logger = document.getElementById("log");
const chainIdNode = document.getElementById("chain-id");
const apiEndpointInput = document.getElementById("api-endpoint");
let publicKey, wallet;
const log = (what, error = false) => {
console[error ? 'error' : 'info'](what);
......@@ -90,7 +95,7 @@
});
// Broadcast our transaction with custom internal expiration time
const observer = await bot.signAndBroadcast(builder.build());
const observer = await bot.broadcast(builder.build(wallet, publicKey));
voted.add(getKey(op));
......@@ -203,7 +208,6 @@
apiEndpoint = apiEndpointInput.value;
bot = new WorkerBee({
postingKey: document.getElementById('key').value,
chainOptions: {
apiEndpoint,
chainId
......@@ -213,7 +217,13 @@
log(error, true);
});
await bot.start();
const beekeeper = await beekeeperFactory();
const session = await beekeeper.createSession("my.salt");
const lockedWallet = await session.openWallet(document.getElementById('wallet').value);
wallet = lockedWallet.unlock(document.getElementById('password').value);
await bot.start(wallet);
publicKey = document.getElementById('key').value;
const accountToObserve = String(document.getElementById("acc").value);
......
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Post observer wallet manager</title>
<style>
#log {
border: 3px solid black;
height: 50vh;
overflow: auto;
padding: 5px;
font-family: 'Courier New', monospace;
font-size: .8rem;
word-break: break-word;
}
#log .data {
margin: 5px;
padding: 5px;
color: darkslategray;
border-bottom: 2px solid gray;
}
#log .data.error {
color: red;
}
</style>
</head>
<body>
<h1>WorkerBee Post observer wallet manager</h1>
Wallet name:<input id="wallet" placeholder="Wallet name" value="wallet0">
Wallet password:<input id="password" placeholder="Wallet password" value="password">
Private Posting key:<input id="key" placeholder="Private Posting key" value="5JqodRzm7Ag1QJgLgsCgEpQpkRKq1NQZGjaxy1Y48WxohCy2u5J">
<button id="start">Import</button>
<hr>
<h2>Log</h2>
<div id="log"></div>
<script type="module">
import beekeeperFactory from "@hive-staging/beekeeper";
const startBtn = document.getElementById("start");
const logger = document.getElementById("log");
const log = (what, error = false) => {
console[error ? 'error' : 'info'](what);
const node = document.createElement('div');
node.classList.add('data');
if (error)
node.classList.add('error');
node.innerText = typeof what === 'string' ? what : JSON.stringify(what);
logger.prepend(node);
};
startBtn.addEventListener('click', async () => {
try {
const beekeeper = await beekeeperFactory();
const session = await beekeeper.createSession("my.salt");
const walletName = document.getElementById('wallet').value;
log(`Creating wallet: "${walletName}"`);
const { wallet } = await session.createWallet(walletName, document.getElementById('password').value);
log("Wallet created.");
const publicKey = await wallet.importKey(document.getElementById('key').value);
log(`Imported key wallet: "${publicKey}"`);
await wallet.close();
log(`Closed wallet "${walletName}"`);
await beekeeper.delete();
} catch (error) {
log(error, true);
}
});
</script>
</body>
</html>
{
"name": "@hive-staging/workerbee",
"version": "0.3.8",
"version": "0.3.11",
"description": "Example bot to use in your browser app based on the wax and beekeeper library",
"main": "dist/bundle/index.js",
"types": "dist/bundle/index.d.ts",
......@@ -45,8 +45,8 @@
"typescript": "5.2.2"
},
"dependencies": {
"@hive-staging/beekeeper": "^0.1.2",
"@hive-staging/wax": "^0.3.23",
"@hive-staging/beekeeper": "^0.1.3",
"@hive-staging/wax": "^0.3.24",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"events": "^3.3.0",
......
......@@ -6,11 +6,11 @@ settings:
dependencies:
'@hive-staging/beekeeper':
specifier: ^0.1.2
version: 0.1.2
specifier: ^0.1.3
version: 0.1.3
'@hive-staging/wax':
specifier: ^0.3.23
version: 0.3.23
specifier: ^0.3.24
version: 0.3.24
class-transformer:
specifier: ^0.5.1
version: 0.5.1
......@@ -156,16 +156,16 @@ packages:
- supports-color
dev: true
/@hive-staging/beekeeper@0.1.2:
resolution: {integrity: sha512-+9zfaai+4TZzWNNr1jwaTJr4e0bGNbEuB7h1HEadIyk+w77lqKN4/UDPC6N5NSGbtr+SLEGXEjSX6LG8Rjx3Sg==}
/@hive-staging/beekeeper@0.1.3:
resolution: {integrity: sha512-G3KRlZCo0H+RgWCov/7kcWEfo+4gTt6n/Pqr7FhDEjyRSGgvgDeJeVls01HLvsj3lndEPFqOA3hSkZV2qyydeQ==}
engines: {node: '>= 12'}
dev: false
/@hive-staging/wax@0.3.23:
resolution: {integrity: sha512-aKQO9UrvPfOspKR0WkiePckib03F7uycgKAShrdta5Hd9aLoug8W/KN5A3smFnAhepyZjCadra7jpRKdviF9IQ==}
/@hive-staging/wax@0.3.24:
resolution: {integrity: sha512-xhi5edmu7awusgA78NCvjJdewZEhbAP3Z6IwCNt8PMiq6DXSHmlB4J4cuVknfUXRmcdZQo1uUWXmtLaUAKhKRA==}
engines: {node: '>= 12'}
dependencies:
'@hive-staging/beekeeper': 0.1.2
'@hive-staging/beekeeper': 0.1.3
class-transformer: 0.5.1
class-validator: 0.14.0
long: 5.2.3
......
import EventEmitter from "events";
import beekeeperFactory, { IBeekeeperInstance, IBeekeeperOptions, IBeekeeperUnlockedWallet } from "@hive-staging/beekeeper";
import type { IBeekeeperOptions, IBeekeeperUnlockedWallet } from "@hive-staging/beekeeper";
import { BroadcastTransactionRequest, calculateExpiration, IWaxOptionsChain, transaction, TWaxExtended } from "@hive-staging/wax";
import type { Subscribable } from "rxjs";
......@@ -11,13 +11,6 @@ import { getWax, WaxExtendTypes } from "./wax/extend";
const ONE_MINUTE = 1000 * 60;
export interface IStartConfiguration {
/**
* Posting private key in WIF format
*
* @type {?string}
*/
postingKey?: string;
/**
* Wax chain options
*
......@@ -48,10 +41,6 @@ export class WorkerBee extends EventEmitter implements IWorkerBee {
public chain?: TWaxExtended<typeof WaxExtendTypes>;
private publicKey!: string;
private beekeeper?: IBeekeeperInstance;
private wallet?: IBeekeeperUnlockedWallet;
private headBlockNumber: number = 0;
......@@ -71,17 +60,9 @@ export class WorkerBee extends EventEmitter implements IWorkerBee {
});
}
private get isAuthorized(): boolean {
return typeof this.configuration.postingKey === "string";
}
public async signAndBroadcast(tx: transaction, options: IBroadcastOptions = {}): Promise<Subscribable<ITransactionData>> {
if(tx.signatures.length === 0) {
if(!this.isAuthorized)
throw new WorkerBeeError("You are trying to broadcast transaction without signing!");
tx = new this.chain!.TransactionBuilder(tx).build(this.wallet!, this.publicKey);
}
public async broadcast(tx: transaction, options: IBroadcastOptions = {}): Promise<Subscribable<ITransactionData>> {
if(tx.signatures.length === 0)
throw new WorkerBeeError("You are trying to broadcast transaction without signing!");
if(typeof options.throwAfter === "undefined") {
const expiration = calculateExpiration(tx.expiration);
......@@ -102,24 +83,17 @@ export class WorkerBee extends EventEmitter implements IWorkerBee {
return this.observe.transaction(apiTx.id, expireDate.getTime());
}
public async start(): Promise<void> {
public async start(wallet?: IBeekeeperUnlockedWallet): Promise<void> {
// Initialize chain and beekepeer if required
if(typeof this.chain === "undefined")
if(typeof this.chain === "undefined") {
this.chain = await getWax(this.configuration.chainOptions);
if(typeof this.beekeeper === "undefined" || typeof this.wallet === "undefined") {
this.beekeeper = await beekeeperFactory(this.configuration.beekeeperOptions);
const random = Math.random().toString(16)
.slice(2);
({ wallet: this.wallet } = await this.beekeeper.createSession(random).createWallet(random));
if(this.isAuthorized)
this.publicKey = await this.wallet.importKey(this.configuration.postingKey as string);
({ head_block_number: this.headBlockNumber } = await this.chain.api.database_api.get_dynamic_global_properties({}));
}
if(typeof this.wallet === "undefined")
this.wallet = wallet;
// Ensure the app is not running
await this.stop();
......@@ -198,10 +172,8 @@ export class WorkerBee extends EventEmitter implements IWorkerBee {
this.chain?.delete();
this.wallet?.close();
await this.beekeeper?.delete();
this.chain = undefined;
this.beekeeper = undefined;
this.wallet = undefined;
}
}
import type EventEmitter from "events";
import type { IBeekeeperUnlockedWallet } from "@hive-staging/beekeeper";
import type { ApiAccount, ApiBlock, ApiTransaction, IHiveChainInterface, operation, transaction } from "@hive-staging/wax";
import type { Subscribable } from "rxjs";
import type { IStartConfiguration } from "./bot";
......@@ -86,8 +87,10 @@ export interface IWorkerBee extends EventEmitter {
/**
* Starts the automation with given configuration
*
* @param {?IBeekeeperUnlockedWallet} wallet optional unlocked beekeper wallet for bot operations
*/
start(): Promise<void>;
start(wallet?: IBeekeeperUnlockedWallet): Promise<void>;
/**
* Request automation stop
......@@ -107,14 +110,12 @@ export interface IWorkerBee extends EventEmitter {
* If {@link IBroadcastOptions throwAfter} has not been specified, it is automatically
* set to the transaction expiration time plus one minute
*
* If transaction is not already signed (at least one signature is present)
* WorkerBee will try signing the transaction using specified in the configuration
* private key
* Requires signed transaction
*
* @param tx Protobuf transactoin to broadcast
* @param options Options for broadcasting
*/
signAndBroadcast(tx: transaction, options?: IBroadcastOptions): Promise<Subscribable<ITransactionData>>;
broadcast(tx: transaction, options?: IBroadcastOptions): Promise<Subscribable<ITransactionData>>;
/**
* Allows you to iterate over blocks indefinitely
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment