Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sign block locally #12

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
109 changes: 100 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,15 @@ For more details about websocket, see [Websocket](#websocket).
- [Weight](#weight)
- [Websocket](#websocket)
- [Confirmation](#confirmation)
- [Work](#work)
- [Generate](#generate)
- [Blocks](#blocks)
- [Count](#count)
- [Create block](#create-block)
- [Process block](#process-block)
- [Block info](#block-info)
- [Hash](#hash)
- [Sign](#sign)
- [Key](#key)
- [Create](#create)
- [Expand](#expand)
Expand Down Expand Up @@ -698,6 +702,41 @@ const subscription = await nona.ws.confirmation({
});
```

## Work

Handle work generation.

You can access to the work object with the following code:

```typescript
const work = nona.work;
```

### Generate

```typescript
generate(hash: string): Promise<WorkGenerate>
```

Generates a proof of work for a given hash.
`hash` is the frontier of the account or in the case of an open block, the public key representation of the account

```typescript
const { frontier } = await nona.wallet(privateKey).info();
const pow = await nona.work.generate(frontier);
console.log(pow);
// {
// work: 'a9dcf795347ffcae',
// difficulty: 'fffffffb527099d1',
// multiplier: '1.710143740970892',
// hash: '25B23815022AC42B7456A78FAEAE95D1ED27C66EB668FD5D197B24BDC3CA96AD'
// }
```

> [!WARNING]
> The work is generated by the node, the options to provide or generate the work locally are not yet implemented.
> Work generation can be CPU/GPU intensive, and almost all public nodes do not allow work to be generated.

## Blocks

You can access to the blocks object with the following code:
Expand All @@ -716,7 +755,7 @@ Reports the number of blocks in the ledger and unchecked synchronizing blocks.
This count represent the node ledger and not the network status.

```typescript
const count = await blocks.count();
const count = await nona.blocks.count();
console.log(count);
// { count: '198574599', unchecked: '14', cemented: '198574599' }
```
Expand All @@ -731,22 +770,23 @@ console.log(count);
> This method is for advanced usage, use it if you know what you are doing.

```typescript
create(params: CreateBlockParams): Promise<string>
create(params: CreateBlockParams): SignedBlock
```

Creates a block object based on input data & signed with private key or account in wallet.
Returns a formated block with the hash and the signature of the block.

Create a send block.
Let's say you want to send 1 nano to `nano_1send...`.
Let's say you want to get a formated block to send 1 nano to `nano_1send...`.
You have currently 3 nano in your account.

```typescript
const wallet = nona.wallet(privateKey);
const info = await wallet.info();
const recipient = 'nano_1send...';
const recipientPublicKey = KeyService.getPublicKey(recipient);
const { work } = await nona.work.generate(info.frontier);

const sendBlock = await this.blocks.create({
const sendBlock = await nona.blocks.create({
// Current account
account: wallet.address,
// Final balance for account after block creation in raw unit (here: current balance - send amount).
Expand All @@ -760,10 +800,15 @@ const sendBlock = await this.blocks.create({
// If the block has no balance change but is updating representative only, set link to 0.
link: recipientPublicKey,
// Private key of the account
key: privateKey,
privateKey: privateKey,
// Proof of work for the block
work: work,
});
```

> [!NOTE]
> The block is signed locally using the [nanocurrency-js](https://github.com/marvinroger/nanocurrency-js/tree/master/packages/nanocurrency) package.

### Process block

> [!WARNING]
Expand All @@ -773,13 +818,13 @@ const sendBlock = await this.blocks.create({
process(block: Block, subtype: string): Promise<string>
```

Publish it to the network.
Publish a block to the network.
Returns the hash of the published block.

If we want to process the block created in the previous example:

```typescript
await this.blocks.process(sendBlock, 'send');
await nona.blocks.process(sendBlock, 'send');
```

### Block info
Expand All @@ -792,9 +837,55 @@ Retrieves information about a specific block.

```typescript
const hash = 'D83124BB...';
const info = await blocks.info(hash);
const info = await nona.blocks.info(hash);
```

### Hash

```typescript
hash(block: HashBlockParams): string
```

Returning block hash for given block.

Get the hash of a send block:

```typescript
const wallet = nona.wallet(privateKey);
const info = await wallet.info();
const { work } = await nona.work.generate(info.frontier);
const recipient = 'nano_1send...';

const hash = nona.blocks.hash({
account: wallet.address,
balance: '2000000000000000000000000000000',
previous: info.frontier,
representative: info.representative,
link: KeyService.getPublicKey(recipient),
work: work,
});
```

> [!NOTE]
> The block is hashed locally using the [nanocurrency-js](https://github.com/marvinroger/nanocurrency-js/tree/master/packages/nanocurrency) package.


### Sign

```typescript
sign(params: { hash: string, privateKey: string }): string
```

Signs a block using the provided hash and private key.

```typescript
const hash = nona.blocks.hash(block);
const signedBlock = nona.blocks.sign({ hash, privateKey });
```

> [!NOTE]
> The block is signed locally using the [nanocurrency-js](https://github.com/marvinroger/nanocurrency-js/tree/master/packages/nanocurrency) package.

## Key

You can access to the key object with the following code:
Expand Down
22 changes: 21 additions & 1 deletion lib/modules/blocks/blocks-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,33 @@ export interface CreateBlockParams {
/** Final balance for account after block creation. In raw unit */
balance: string;
/** Private key of the account */
key: string;
privateKey: string;
/** The block hash of the previous block on this account's block chain ("0" for first block). */
previous: string;
/** The representative account for the account. */
representative: NanoAddress;
/** If the block is sending funds, set link to the public key of the destination account. If it is receiving funds, set link to the hash of the block to receive. If the block has no balance change but is updating representative only, set link to 0. */
link: string;
/** Proof of work for the block */
work: string;
}

export interface SignedBlock {
type: string;
account: string;
previous: string;
representative: string;
balance: string;
link: string;
signature: string;
work: string;
}

export type BlockProcessSubtype = 'open' | 'receive' | 'change' | 'send' | 'state';

export type HashBlockParams = Omit<CreateBlockParams, 'privateKey'>;

export interface BlockSignParams {
hash: string;
privateKey: string;
}
71 changes: 59 additions & 12 deletions lib/modules/blocks/blocks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { hashBlock, signBlock } from 'nanocurrency';

import { RpcConsummer } from '../rpc-consumer/rpc-consumer';
import { BlockProcessSubtype, CreateBlockParams } from './blocks-interface';
import { Block, BlockCount, BlockInfo, BlockProcess } from './blocks-schema';
import {
SignedBlock,
BlockProcessSubtype,
BlockSignParams,
CreateBlockParams,
HashBlockParams,
} from './blocks-interface';
import { BlockCount, BlockInfo, BlockProcess } from './blocks-schema';

export class Blocks extends RpcConsummer {
/**
Expand All @@ -15,19 +23,27 @@ export class Blocks extends RpcConsummer {
}

/**
* Creates a new block based on input data & signed with private key or account in wallet.
* Returns a formated block with the hash and the signature of the block.
*
* @param params - The options for creating the block.
* @returns A promise that resolves to the created block.
*/
public async create(params: CreateBlockParams): Promise<Block['block']> {
const res = await this.rpc.call('block_create', {
json_block: 'true',
type: 'state',
...params,
public create(params: CreateBlockParams): SignedBlock {
const { privateKey, ...unsignedBlock } = params;

const hash = this.hash(unsignedBlock);
const signature = this.sign({
hash,
privateKey,
});

return this.parseHandler(res, Block).block;
const block = {
...unsignedBlock,
type: 'state',
signature,
};

return block;
}

/**
Expand All @@ -37,7 +53,7 @@ export class Blocks extends RpcConsummer {
* @param subtype - The subtype of the block
* @returns A promise that resolves to the hash of the published block
*/
public async process(block: Block['block'], subtype: BlockProcessSubtype): Promise<string> {
public async process(block: SignedBlock, subtype: BlockProcessSubtype): Promise<string> {
const res = await this.rpc.call('process', {
json_block: 'true',
subtype,
Expand Down Expand Up @@ -68,9 +84,40 @@ export class Blocks extends RpcConsummer {
* @param block - The block to be received.
* @returns A promise that resolves to the hash of the published block
*/
public async receiveBlock(block: CreateBlockParams): Promise<string> {
const createdBlock = await this.create(block);
public receiveBlock(block: CreateBlockParams): Promise<string> {
const createdBlock = this.create(block);

return this.process(createdBlock, 'receive');
}

/**
* Get the hash of a block
*
* @param block
* @returns
*/
public hash(block: HashBlockParams): string {
let previous = block.previous;
if (previous === '0') {
previous = '0000000000000000000000000000000000000000000000000000000000000000';
}

return hashBlock({
...block,
previous,
});
}

/**
* Signs a block using the provided hash and private key.
*
* @param params - The parameters required for signing the block.
* @returns The signed block.
*/
public sign({ hash, privateKey }: BlockSignParams): string {
return signBlock({
hash,
secretKey: privateKey,
});
}
}
Loading