Add PoW module
This commit is contained in:
@@ -16,7 +16,7 @@ describe('AppController', () => {
|
|||||||
|
|
||||||
describe('root', () => {
|
describe('root', () => {
|
||||||
it('should return "Hello World!"', () => {
|
it('should return "Hello World!"', () => {
|
||||||
expect(appController.getHello()).toBe('Hello World!');
|
expect(appController.getHello()).toBe("Hello World! This is Chip's generalized API for fetching information and things. You can contact me on mastodon @chip@talking.dev.");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -22,6 +22,7 @@ import { redisStore } from 'cache-manager-redis-yet';
|
|||||||
import { RedirectsModule } from './redirects/redirects.module';
|
import { RedirectsModule } from './redirects/redirects.module';
|
||||||
import { FileModule } from './file/file.module';
|
import { FileModule } from './file/file.module';
|
||||||
import { FocoLiveModule } from './foco-live/foco-live.module';
|
import { FocoLiveModule } from './foco-live/foco-live.module';
|
||||||
|
import { PowModule } from './pow/pow.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -78,6 +79,7 @@ import { FocoLiveModule } from './foco-live/foco-live.module';
|
|||||||
RedirectsModule,
|
RedirectsModule,
|
||||||
FileModule,
|
FileModule,
|
||||||
FocoLiveModule,
|
FocoLiveModule,
|
||||||
|
PowModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
|
24
src/pow/pow.controller.ts
Normal file
24
src/pow/pow.controller.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
|
||||||
|
import { PowService } from './pow.service';
|
||||||
|
import { ApiBody, ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
@ApiTags('pow')
|
||||||
|
@Controller('pow')
|
||||||
|
export class PowController {
|
||||||
|
constructor(
|
||||||
|
private readonly powService: PowService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
|
||||||
|
@Get('challenge')
|
||||||
|
async generateChallenge() {
|
||||||
|
return this.powService.generateChallenge();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('challenge')
|
||||||
|
@ApiBody({ schema: { properties: { challenge: { type: 'string' }, proof: { type: 'string' } } } })
|
||||||
|
async verifyChallenge(@Body() body: { challenge: string, proof: string }) {
|
||||||
|
return this.powService.verifyChallenge(body.challenge, body.proof);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
16
src/pow/pow.module.ts
Normal file
16
src/pow/pow.module.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { PowService } from './pow.service';
|
||||||
|
import { CacheModule } from '@nestjs/cache-manager';
|
||||||
|
import { PowController } from './pow.controller';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
CacheModule.register({
|
||||||
|
ttl: 5,
|
||||||
|
max: 10,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
providers: [PowService],
|
||||||
|
controllers: [PowController]
|
||||||
|
})
|
||||||
|
export class PowModule { }
|
88
src/pow/pow.service.ts
Normal file
88
src/pow/pow.service.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||||
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { Cache } from 'cache-manager';
|
||||||
|
import { randomBytes, createHash } from 'crypto';
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PowService {
|
||||||
|
private readonly logger = new Logger(PowService.name);
|
||||||
|
private difficulty = 5;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(CACHE_MANAGER) private cacheManager: Cache,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a proof of work challenge, stored to redis for verification within
|
||||||
|
* the next 10 seconds.
|
||||||
|
*/
|
||||||
|
async generateChallenge() {
|
||||||
|
const challenge = this.generateRandom256BitString();
|
||||||
|
console.log(challenge)
|
||||||
|
await this.cacheManager.set(challenge, true, 60 * 1000);
|
||||||
|
return challenge;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateRandom256BitString() {
|
||||||
|
const randomString = randomBytes(32).toString('hex');
|
||||||
|
return randomString;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
hashAndCheck(string: string) {
|
||||||
|
return this.hashPassesDifficulty(this.hashString(string), this.difficulty);
|
||||||
|
}
|
||||||
|
|
||||||
|
hashPassesDifficulty(hash: string, difficulty: number) {
|
||||||
|
return hash.startsWith('0'.repeat(difficulty));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that the proof of work submitted has a leading number of
|
||||||
|
* zeroes equal to the challenge length and the challenge exists.
|
||||||
|
*/
|
||||||
|
async verifyChallenge(challenge: string, proof: string): Promise<boolean> {
|
||||||
|
const expected = await this.cacheManager.get<boolean>(challenge);
|
||||||
|
return expected ? this.hashAndCheck(proof + challenge) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async markChallengeAsComplete(challenge: string) {
|
||||||
|
await this.cacheManager.del(challenge);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a proof of work challenge to find a proof that hashes to a value
|
||||||
|
*/
|
||||||
|
async performChallenge(challenge: string) {
|
||||||
|
let proof = this.generateRandom256BitString();
|
||||||
|
let hash = this.hashString(proof + challenge);
|
||||||
|
while (!this.hashPassesDifficulty(hash, this.difficulty)) {
|
||||||
|
proof = this.generateRandom256BitString();
|
||||||
|
hash = this.hashString(proof + challenge);
|
||||||
|
}
|
||||||
|
return { proof, hash };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sha512 the provided string and return the result.
|
||||||
|
*/
|
||||||
|
hashString(input: string) {
|
||||||
|
return createHash('sha512').update(input).digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the difficulty of the proof of work challenge.
|
||||||
|
*/
|
||||||
|
setDifficulty(difficulty: number) {
|
||||||
|
this.difficulty = difficulty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current difficulty of the proof of work challenge.
|
||||||
|
*/
|
||||||
|
getDifficulty() {
|
||||||
|
return this.difficulty;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user