Skip to content

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

Open
wants to merge 56 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
4405c08
feat: implement pow worker and service layer (scope 4)
DecentAgeCoder Apr 16, 2025
443feb8
feat: wire frontend to backend pow API (scope 6)
DecentAgeCoder Apr 16, 2025
cd5da75
🤖 Apply formatting changes
github-actions[bot] Apr 16, 2025
503c082
🤖 Apply formatting changes
github-actions[bot] Apr 16, 2025
def3055
Merge remote-tracking branch 'origin/main' into feat/frontend/pow/pro…
DecentAgeCoder Apr 17, 2025
0f19642
Adapted AllowSigningParams interface so it matches the expectation of…
DecentAgeCoder Apr 22, 2025
0f1b97f
🤖 Apply formatting changes
github-actions[bot] Apr 22, 2025
319a318
Merge branch 'main' into feat/frontend/pow/protect-allow-signing-6
DecentAgeCoder Apr 22, 2025
ddd1684
Revert "Adapted AllowSigningParams interface so it matches the expect…
DecentAgeCoder Apr 22, 2025
6a89abd
Merge branch 'refs/heads/main' into feat/frontend/pow/protect-allow-s…
DecentAgeCoder Apr 22, 2025
6330049
Adapted AllowSigningParams interface so it matches the expectation of…
DecentAgeCoder Apr 22, 2025
c9fa579
Merge branch 'main' into feat/frontend/pow/protect-allow-signing-6
DecentAgeCoder Apr 22, 2025
9e6a759
🤖 Apply formatting changes
github-actions[bot] Apr 22, 2025
65a86f1
Merge branch 'main' into feat/frontend/pow/protect-allow-signing-6
DecentAgeCoder Apr 22, 2025
62deee5
Merge branch 'main' into feat/frontend/pow/protect-allow-signing-6
DecentAgeCoder Apr 22, 2025
c690937
Merge branch 'refs/heads/main' into feat/frontend/pow/protect-allow-s…
DecentAgeCoder Apr 22, 2025
7d99423
Merge branch 'refs/heads/feat/frontend/pow/protect-allow-signing-6' i…
DecentAgeCoder Apr 22, 2025
92815c9
Added interface for Pow Protector Worker
DecentAgeCoder Apr 22, 2025
1231c15
Using Pow Protector Worker interface
DecentAgeCoder Apr 22, 2025
2bc4fd1
🤖 Apply formatting changes
github-actions[bot] Apr 22, 2025
bf7990b
Registered post message events
DecentAgeCoder Apr 22, 2025
4dcde38
Merge remote-tracking branch 'origin/feat/frontend/pow/protect-allow-…
DecentAgeCoder Apr 22, 2025
ee221f6
🤖 Apply formatting changes
github-actions[bot] Apr 22, 2025
25da25d
Merge remote-tracking branch 'origin/main' into feat/frontend/pow/pro…
DecentAgeCoder Apr 22, 2025
f279b8c
Merge remote-tracking branch 'origin/feat/frontend/pow/protect-allow-…
DecentAgeCoder Apr 22, 2025
dfd4282
Merged existing changes
DecentAgeCoder Apr 22, 2025
1e86f6f
🤖 Apply formatting changes
github-actions[bot] Apr 22, 2025
cfbe97b
Changing return type for allowSigning from result to response type an…
DecentAgeCoder Apr 23, 2025
f5aa18a
Merge remote-tracking branch 'origin/main' into feat/frontend/pow/pro…
DecentAgeCoder Apr 23, 2025
1614d99
Changing return type for allowSigning from result to response type an…
DecentAgeCoder Apr 23, 2025
827b99a
Merge remote-tracking branch 'origin/feat/frontend/pow/protect-allow-…
DecentAgeCoder Apr 23, 2025
1040770
Updated allowSigning to match changed signature
DecentAgeCoder Apr 23, 2025
643468a
Merge remote-tracking branch 'origin/main' into feat/frontend/pow/pro…
DecentAgeCoder Apr 23, 2025
2b3c0e2
Merged master changes
DecentAgeCoder Apr 23, 2025
2c9ec2f
🤖 Apply formatting changes
github-actions[bot] Apr 23, 2025
b1cd580
Introduced variable POW_CHALLENGE_INTERVAL_MILLIS which defines the s…
DecentAgeCoder Apr 23, 2025
b82a2b3
Introduced variable POW_CHALLENGE_INTERVAL_MILLIS which defines the s…
DecentAgeCoder Apr 23, 2025
4819ab9
🤖 Apply formatting changes
github-actions[bot] Apr 23, 2025
576b5b8
Merge branch 'main' into feat/frontend/pow/protect-allow-signing-4
DecentAgeCoder Apr 23, 2025
7633c44
Merge branch 'main' into feat/frontend/pow/protect-allow-signing-4
DecentAgeCoder Apr 23, 2025
400460f
Merge branch 'main' into feat/frontend/pow/protect-allow-signing-4
DecentAgeCoder Apr 23, 2025
9781c5f
Merge branch 'main' into feat/frontend/pow/protect-allow-signing-4
DecentAgeCoder Apr 24, 2025
b2864d5
Merge remote-tracking branch 'origin/main' into feat/frontend/pow/pro…
DecentAgeCoder Apr 24, 2025
84154cd
Removed btcAddress
DecentAgeCoder Apr 24, 2025
6d5b695
Merge remote-tracking branch 'origin/feat/frontend/pow/protect-allow-…
DecentAgeCoder Apr 24, 2025
52a33b6
🤖 Apply formatting changes
github-actions[bot] Apr 24, 2025
a142422
Update src/frontend/src/env/pow.env.ts
DecentAgeCoder Apr 24, 2025
8ae18de
Update src/frontend/src/icp/schedulers/pow-protection.scheduler.ts
DecentAgeCoder Apr 24, 2025
4b2279b
🤖 Apply formatting changes
github-actions[bot] Apr 24, 2025
d1f1541
Merge branch 'main' into feat/frontend/pow/protect-allow-signing-4
DecentAgeCoder Apr 24, 2025
fef0347
Merge remote-tracking branch 'origin/main' into feat/frontend/pow/pro…
DecentAgeCoder Apr 24, 2025
21b10e2
Added JSDoc to functions, change dcomments and destructed CreateChall…
DecentAgeCoder Apr 24, 2025
ba25cd4
Merge remote-tracking branch 'origin/feat/frontend/pow/protect-allow-…
DecentAgeCoder Apr 24, 2025
4f38603
🤖 Apply formatting changes
github-actions[bot] Apr 24, 2025
f39f1b5
Merge branch 'main' into feat/frontend/pow/protect-allow-signing-4
DecentAgeCoder Apr 24, 2025
c377763
Merge remote-tracking branch 'origin/main' into feat/frontend/pow/pro…
DecentAgeCoder Apr 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/frontend/src/env/pow.env.ts
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;
107 changes: 107 additions & 0 deletions src/frontend/src/icp/schedulers/pow-protection.scheduler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
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';

export class PowProtectionScheduler implements Scheduler<PostMessageDataRequest> {
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; // The unique timestamp for the challenge
difficulty: number; // The difficulty level
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use the docstring like the other functions, so that you define the params and the results directly in it

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I will add a complete docstring here.

Copy link
Collaborator

@AntonioVentilii AntonioVentilii Apr 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove them from here?

}): 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
while (true) {
// 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++;
}
Copy link
Collaborator

@AntonioVentilii AntonioVentilii Apr 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe make it functional? maybe with self-called functions?

const nonce = ...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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.

Copy link
Collaborator

@AntonioVentilii AntonioVentilii Apr 24, 2025

Choose a reason for hiding this comment

The 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 while (prefix > target) and refactor the logic?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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.

Copy link
Collaborator

@AntonioVentilii AntonioVentilii Apr 24, 2025

Choose a reason for hiding this comment

The 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 while loop for readability. May you please change it to have a condition instead of while(true)

Copy link
Collaborator

@AntonioVentilii AntonioVentilii Apr 24, 2025

Choose a reason for hiding this comment

The 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 }
});
};
}
31 changes: 31 additions & 0 deletions src/frontend/src/icp/services/worker.pow-protection.services.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type {
PowProtectorWorker,
PowProtectorWorkerInitResult
} from '$icp/types/pow-protector-listener';
import type { PostMessage, PostMessageDataResponse } from '$lib/types/post-message';

export const initPowProtectorWorker: PowProtectorWorker =
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'
});
}
};
};
7 changes: 7 additions & 0 deletions src/frontend/src/icp/types/pow-protector-listener.ts
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>;
22 changes: 22 additions & 0 deletions src/frontend/src/icp/workers/pow-protection.worker.ts
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;
}
};
7 changes: 6 additions & 1 deletion src/frontend/src/lib/schema/post-message.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export const PostMessageRequestSchema = z.enum([
'stopCodeTimer',
'startExchangeTimer',
'stopExchangeTimer',
'startPowProtectionTimer',
'triggerPowProtectionTimer',
'stopPowProtectionTimer',
'stopIcpWalletTimer',
'startIcpWalletTimer',
'triggerIcpWalletTimer',
Expand Down Expand Up @@ -104,7 +107,8 @@ export const PostMessageResponseStatusSchema = z.enum([
'syncSolWalletStatus',
'syncBtcStatusesStatus',
'syncCkMinterInfoStatus',
'syncCkBTCUpdateBalanceStatus'
'syncCkBTCUpdateBalanceStatus',
'syncPowProtectionStatus'
]);

export const PostMessageResponseSchema = z.enum([
Expand All @@ -129,6 +133,7 @@ export const PostMessageResponseSchema = z.enum([
'syncBtcPendingUtxos',
'syncCkBTCUpdateOk',
'syncBtcAddress',
'syncPowProtection',
...PostMessageResponseStatusSchema.options
]);

Expand Down
4 changes: 3 additions & 1 deletion src/frontend/src/lib/workers/workers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { onCkBtcUpdateBalanceMessage } from '$icp/workers/ckbtc-update-balance.w
import { onCkEthMinterInfoMessage } from '$icp/workers/cketh-minter-info.worker';
import { onIcpWalletMessage } from '$icp/workers/icp-wallet.worker';
import { onIcrcWalletMessage } from '$icp/workers/icrc-wallet.worker';
import { onPowProtectionMessage } from '$icp/workers/pow-protection.worker';
import type { PostMessage, PostMessageDataRequest } from '$lib/types/post-message';
import { onAuthMessage } from '$lib/workers/auth.worker';
import { onExchangeMessage } from '$lib/workers/exchange.worker';
Expand All @@ -21,6 +22,7 @@ onmessage = async (msg: MessageEvent<PostMessage<PostMessageDataRequest>>) => {
onCkEthMinterInfoMessage(msg),
onIcpWalletMessage(msg),
onIcrcWalletMessage(msg),
onSolWalletMessage(msg)
onSolWalletMessage(msg),
onPowProtectionMessage(msg)
]);
};
Loading