-
Notifications
You must be signed in to change notification settings - Fork 33
feat(frontend): implement pow worker and service layer #5887
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
Changes from all commits
4405c08
443feb8
cd5da75
503c082
def3055
0f19642
0f1b97f
319a318
ddd1684
6a89abd
6330049
c9fa579
9e6a759
65a86f1
62deee5
c690937
7d99423
92815c9
1231c15
2bc4fd1
bf7990b
4dcde38
ee221f6
25da25d
f279b8c
dfd4282
1e86f6f
cfbe97b
f5aa18a
1614d99
827b99a
1040770
643468a
2b3c0e2
2c9ec2f
b1cd580
b82a2b3
4819ab9
576b5b8
7633c44
400460f
9781c5f
b2864d5
84154cd
6d5b695
52a33b6
a142422
8ae18de
4b2279b
d1f1541
fef0347
21b10e2
ba25cd4
4f38603
f39f1b5
c377763
d77c1f0
32f7d4f
c3f3bde
62b8311
75012b2
f1ee00c
fedc352
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
import { parseBoolEnvVar } from '$lib/utils/env.utils'; | ||
|
||
export const POW_FEATURE_ENABLED = parseBoolEnvVar(import.meta.env.VITE_POW_FEATURE_ENABLED); | ||
|
||
export const POW_CHALLENGE_INTERVAL_MILLIS = 120000; | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import type { CreateChallengeResponse } from '$declarations/backend/backend.did'; | ||
import { POW_CHALLENGE_INTERVAL_MILLIS } from '$env/pow.env'; | ||
import { allowSigning, createPowChallenge } from '$lib/api/backend.api'; | ||
import { SchedulerTimer, type Scheduler, type SchedulerJobData } from '$lib/schedulers/scheduler'; | ||
import type { PostMessageDataRequest } from '$lib/types/post-message'; | ||
import { hashText } from '@dfinity/utils'; | ||
|
||
// TODO: add tests for POW worker/scheduler | ||
export class PowProtectionScheduler implements Scheduler<PostMessageDataRequest> { | ||
AntonioVentilii marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private timer = new SchedulerTimer('syncPowProtectionStatus'); | ||
|
||
stop() { | ||
this.timer.stop(); | ||
} | ||
|
||
async start(data: PostMessageDataRequest | undefined) { | ||
await this.timer.start<PostMessageDataRequest>({ | ||
interval: POW_CHALLENGE_INTERVAL_MILLIS, | ||
job: this.requestSignerCycles, | ||
data | ||
}); | ||
} | ||
|
||
async trigger(data: PostMessageDataRequest | undefined) { | ||
await this.timer.trigger<PostMessageDataRequest>({ | ||
job: this.requestSignerCycles, | ||
data | ||
}); | ||
} | ||
|
||
/** | ||
* Solves a Proof-of-Work (PoW) challenge by finding a `nonce` that satisfies the given difficulty level | ||
* | ||
* @param timestamp - A unique `bigint` value for the challenge. | ||
* @param difficulty - A positive number influencing the challenge's complexity. | ||
* @returns The `nonce` that solves the challenge as a `bigint`. | ||
* @throws An error if `difficulty` is not greater than zero. | ||
*/ | ||
private solvePowChallenge = async ({ | ||
timestamp, | ||
difficulty | ||
}: { | ||
timestamp: bigint; | ||
difficulty: number; | ||
}): Promise<bigint> => { | ||
if (difficulty <= 0) { | ||
throw new Error('Difficulty must be greater than zero'); | ||
} | ||
|
||
// This is the value we need to find to solve the challenge (changed to bigint) | ||
let nonce = 0n; | ||
|
||
// Target is proportional to 1/difficulty (converted target to bigint) | ||
const target = BigInt(Math.floor(0xffffffff / difficulty)); | ||
|
||
// Continuously try different nonce values until the challenge is solved | ||
// TODO: Check if it is worth introducing a max number of iterations | ||
while (true) { | ||
AntonioVentilii marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Concatenate the timestamp and nonce as the challenge string | ||
const challengeStr = `${timestamp}.${nonce}`; | ||
|
||
// Hash the string into a hex representation | ||
const hashHex = await hashText(challengeStr); | ||
|
||
// Extract the first 4 bytes of the hash as a number (prefix converted to bigint) | ||
const prefix = BigInt(parseInt(hashHex.slice(0, 8), 16)); | ||
|
||
// If the prefix satisfies the difficulty target, stop the loop | ||
if (prefix <= target) { | ||
break; | ||
} | ||
|
||
// Otherwise, increment the nonce (bigint increment) and try again | ||
nonce++; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe make it functional? maybe with self-called functions?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can do that but It could reduce readability. Since this is a critical code a more 'procedural' presentation could therefore be desired here. I will give it a try. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thank you! in any case, if you prefer this implementation, maybe preferable to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A do-while-loop and a conditional-loop will be less readable since they duplicate the condition. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, my idea was something like const target = BigInt(Math.floor(0xffffffff / difficulty));
const solvePowChallengeRecursive = async (
timestamp: bigint,
nonce: bigint = 0n
): Promise<bigint> => {
const challengeStr = `${timestamp}.${nonce}`;
const hashHex = await hashText(challengeStr);
const prefix = BigInt(parseInt(hashHex.slice(0, 8), 16));
return prefix <= target
? nonce
: solveChallengeRecursive(timestamp, nonce + 1n);
}
const nonce = await solvePowChallengeRecursive(...) But anyway, let's proceed with the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah! and for safety, let's put an hard coded limit of N iterations that raise an error P.S. and tests |
||
|
||
// Return the nonce that solves the challenge (bigint type) | ||
return nonce; | ||
}; | ||
|
||
/** | ||
* Initiates the Proof-of-Work (PoW) and signing request cycles. | ||
* | ||
* This method: | ||
* 1. Creates a PoW challenge using the given identity. | ||
* 2. Solves the challenge to find a valid `nonce`. | ||
* 3. Uses the solved `nonce` to request signing permission. | ||
* | ||
* @param identity - The user's identity for the operation. | ||
* @throws Errors if any step in the sequence fails. | ||
*/ | ||
private requestSignerCycles = async ({ identity }: SchedulerJobData<PostMessageDataRequest>) => { | ||
// Step 1: Request creation of the Proof-of-Work (PoW) challenge (throws when unsuccessful). | ||
const { start_timestamp_ms: timestamp, difficulty }: CreateChallengeResponse = | ||
await createPowChallenge({ identity }); | ||
|
||
// Step 2: Solve the PoW challenge. | ||
const nonce = await this.solvePowChallenge({ | ||
timestamp, | ||
difficulty | ||
}); | ||
|
||
// Step 3: Request allowance for signing operations with solved nonce. | ||
await allowSigning({ | ||
identity, | ||
request: { nonce } | ||
}); | ||
AntonioVentilii marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import type { | ||
PowProtectorWorker, | ||
PowProtectorWorkerInitResult | ||
} from '$icp/types/pow-protector-listener'; | ||
import type { PostMessage, PostMessageDataResponse } from '$lib/types/post-message'; | ||
|
||
// TODO: add tests for POW worker/scheduler | ||
export const initPowProtectorWorker: PowProtectorWorker = | ||
AntonioVentilii marked this conversation as resolved.
Show resolved
Hide resolved
|
||
async (): Promise<PowProtectorWorkerInitResult> => { | ||
const PowWorker = await import('$lib/workers/workers?worker'); | ||
const worker: Worker = new PowWorker.default(); | ||
|
||
worker.onmessage = ({ data: _data }: MessageEvent<PostMessage<PostMessageDataResponse>>) => {}; | ||
|
||
return { | ||
start: () => { | ||
worker.postMessage({ | ||
msg: 'startPowProtectionTimer' | ||
}); | ||
}, | ||
stop: () => { | ||
worker.postMessage({ | ||
msg: 'stopPowProtectionTimer' | ||
}); | ||
}, | ||
trigger: () => { | ||
worker.postMessage({ | ||
msg: 'triggerPowProtectionTimer' | ||
}); | ||
} | ||
}; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export interface PowProtectorWorkerInitResult { | ||
start: () => void; | ||
stop: () => void; | ||
trigger: () => void; | ||
} | ||
|
||
export type PowProtectorWorker = () => Promise<PowProtectorWorkerInitResult>; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { PowProtectionScheduler } from '$icp/schedulers/pow-protection.scheduler'; | ||
import type { PostMessage, PostMessageDataRequest } from '$lib/types/post-message'; | ||
|
||
const scheduler = new PowProtectionScheduler(); | ||
|
||
export const onPowProtectionMessage = async ({ | ||
data: dataMsg | ||
}: MessageEvent<PostMessage<PostMessageDataRequest>>) => { | ||
const { msg, data } = dataMsg; | ||
|
||
switch (msg) { | ||
case 'stopPowProtectionTimer': | ||
scheduler.stop(); | ||
return; | ||
case 'startPowProtectionTimer': | ||
await scheduler.start(data); | ||
return; | ||
case 'triggerPowProtectionTimer': | ||
await scheduler.trigger(data); | ||
return; | ||
} | ||
}; |
Uh oh!
There was an error while loading. Please reload this page.