@enbox/dwn-server 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +353 -0
- package/dist/cjs/index.js +6811 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/src/config.d.ts +55 -0
- package/dist/esm/src/config.d.ts.map +1 -0
- package/dist/esm/src/config.js +60 -0
- package/dist/esm/src/config.js.map +1 -0
- package/dist/esm/src/connection/connection-manager.d.ts +25 -0
- package/dist/esm/src/connection/connection-manager.d.ts.map +1 -0
- package/dist/esm/src/connection/connection-manager.js +26 -0
- package/dist/esm/src/connection/connection-manager.js.map +1 -0
- package/dist/esm/src/connection/socket-connection.d.ts +65 -0
- package/dist/esm/src/connection/socket-connection.d.ts.map +1 -0
- package/dist/esm/src/connection/socket-connection.js +180 -0
- package/dist/esm/src/connection/socket-connection.js.map +1 -0
- package/dist/esm/src/dwn-error.d.ts +29 -0
- package/dist/esm/src/dwn-error.d.ts.map +1 -0
- package/dist/esm/src/dwn-error.js +36 -0
- package/dist/esm/src/dwn-error.js.map +1 -0
- package/dist/esm/src/dwn-server.d.ts +60 -0
- package/dist/esm/src/dwn-server.d.ts.map +1 -0
- package/dist/esm/src/dwn-server.js +130 -0
- package/dist/esm/src/dwn-server.js.map +1 -0
- package/dist/esm/src/http-api.d.ts +26 -0
- package/dist/esm/src/http-api.d.ts.map +1 -0
- package/dist/esm/src/http-api.js +474 -0
- package/dist/esm/src/http-api.js.map +1 -0
- package/dist/esm/src/index.d.ts +7 -0
- package/dist/esm/src/index.d.ts.map +1 -0
- package/dist/esm/src/index.js +6 -0
- package/dist/esm/src/index.js.map +1 -0
- package/dist/esm/src/json-rpc-api.d.ts +3 -0
- package/dist/esm/src/json-rpc-api.d.ts.map +1 -0
- package/dist/esm/src/json-rpc-api.js +8 -0
- package/dist/esm/src/json-rpc-api.js.map +1 -0
- package/dist/esm/src/json-rpc-handlers/dwn/index.d.ts +2 -0
- package/dist/esm/src/json-rpc-handlers/dwn/index.d.ts.map +1 -0
- package/dist/esm/src/json-rpc-handlers/dwn/index.js +2 -0
- package/dist/esm/src/json-rpc-handlers/dwn/index.js.map +1 -0
- package/dist/esm/src/json-rpc-handlers/dwn/process-message.d.ts +3 -0
- package/dist/esm/src/json-rpc-handlers/dwn/process-message.d.ts.map +1 -0
- package/dist/esm/src/json-rpc-handlers/dwn/process-message.js +73 -0
- package/dist/esm/src/json-rpc-handlers/dwn/process-message.js.map +1 -0
- package/dist/esm/src/json-rpc-handlers/subscription/close.d.ts +10 -0
- package/dist/esm/src/json-rpc-handlers/subscription/close.d.ts.map +1 -0
- package/dist/esm/src/json-rpc-handlers/subscription/close.js +39 -0
- package/dist/esm/src/json-rpc-handlers/subscription/close.js.map +1 -0
- package/dist/esm/src/json-rpc-handlers/subscription/index.d.ts +2 -0
- package/dist/esm/src/json-rpc-handlers/subscription/index.d.ts.map +1 -0
- package/dist/esm/src/json-rpc-handlers/subscription/index.js +2 -0
- package/dist/esm/src/json-rpc-handlers/subscription/index.js.map +1 -0
- package/dist/esm/src/json-rpc-socket.d.ts +39 -0
- package/dist/esm/src/json-rpc-socket.d.ts.map +1 -0
- package/dist/esm/src/json-rpc-socket.js +125 -0
- package/dist/esm/src/json-rpc-socket.js.map +1 -0
- package/dist/esm/src/lib/http-server-shutdown-handler.d.ts +10 -0
- package/dist/esm/src/lib/http-server-shutdown-handler.d.ts.map +1 -0
- package/dist/esm/src/lib/http-server-shutdown-handler.js +65 -0
- package/dist/esm/src/lib/http-server-shutdown-handler.js.map +1 -0
- package/dist/esm/src/lib/json-rpc-router.d.ts +30 -0
- package/dist/esm/src/lib/json-rpc-router.d.ts.map +1 -0
- package/dist/esm/src/lib/json-rpc-router.js +14 -0
- package/dist/esm/src/lib/json-rpc-router.js.map +1 -0
- package/dist/esm/src/lib/json-rpc.d.ts +54 -0
- package/dist/esm/src/lib/json-rpc.d.ts.map +1 -0
- package/dist/esm/src/lib/json-rpc.js +60 -0
- package/dist/esm/src/lib/json-rpc.js.map +1 -0
- package/dist/esm/src/main.d.ts +3 -0
- package/dist/esm/src/main.d.ts.map +1 -0
- package/dist/esm/src/main.js +11 -0
- package/dist/esm/src/main.js.map +1 -0
- package/dist/esm/src/metrics.d.ts +4 -0
- package/dist/esm/src/metrics.d.ts.map +1 -0
- package/dist/esm/src/metrics.js +13 -0
- package/dist/esm/src/metrics.js.map +1 -0
- package/dist/esm/src/plugin-loader.d.ts +10 -0
- package/dist/esm/src/plugin-loader.d.ts.map +1 -0
- package/dist/esm/src/plugin-loader.js +19 -0
- package/dist/esm/src/plugin-loader.js.map +1 -0
- package/dist/esm/src/process-handlers.d.ts +11 -0
- package/dist/esm/src/process-handlers.d.ts.map +1 -0
- package/dist/esm/src/process-handlers.js +40 -0
- package/dist/esm/src/process-handlers.js.map +1 -0
- package/dist/esm/src/registration/proof-of-work-manager.d.ts +93 -0
- package/dist/esm/src/registration/proof-of-work-manager.d.ts.map +1 -0
- package/dist/esm/src/registration/proof-of-work-manager.js +243 -0
- package/dist/esm/src/registration/proof-of-work-manager.js.map +1 -0
- package/dist/esm/src/registration/proof-of-work-types.d.ts +8 -0
- package/dist/esm/src/registration/proof-of-work-types.d.ts.map +1 -0
- package/dist/esm/src/registration/proof-of-work-types.js +2 -0
- package/dist/esm/src/registration/proof-of-work-types.js.map +1 -0
- package/dist/esm/src/registration/proof-of-work.d.ts +41 -0
- package/dist/esm/src/registration/proof-of-work.d.ts.map +1 -0
- package/dist/esm/src/registration/proof-of-work.js +69 -0
- package/dist/esm/src/registration/proof-of-work.js.map +1 -0
- package/dist/esm/src/registration/registration-manager.d.ts +55 -0
- package/dist/esm/src/registration/registration-manager.d.ts.map +1 -0
- package/dist/esm/src/registration/registration-manager.js +121 -0
- package/dist/esm/src/registration/registration-manager.js.map +1 -0
- package/dist/esm/src/registration/registration-store.d.ts +24 -0
- package/dist/esm/src/registration/registration-store.d.ts.map +1 -0
- package/dist/esm/src/registration/registration-store.js +56 -0
- package/dist/esm/src/registration/registration-store.js.map +1 -0
- package/dist/esm/src/registration/registration-types.d.ts +18 -0
- package/dist/esm/src/registration/registration-types.d.ts.map +1 -0
- package/dist/esm/src/registration/registration-types.js +2 -0
- package/dist/esm/src/registration/registration-types.js.map +1 -0
- package/dist/esm/src/storage.d.ts +24 -0
- package/dist/esm/src/storage.d.ts.map +1 -0
- package/dist/esm/src/storage.js +146 -0
- package/dist/esm/src/storage.js.map +1 -0
- package/dist/esm/src/web5-connect/sql-ttl-cache.d.ts +39 -0
- package/dist/esm/src/web5-connect/sql-ttl-cache.d.ts.map +1 -0
- package/dist/esm/src/web5-connect/sql-ttl-cache.js +106 -0
- package/dist/esm/src/web5-connect/sql-ttl-cache.js.map +1 -0
- package/dist/esm/src/web5-connect/web5-connect-server.d.ts +58 -0
- package/dist/esm/src/web5-connect/web5-connect-server.d.ts.map +1 -0
- package/dist/esm/src/web5-connect/web5-connect-server.js +77 -0
- package/dist/esm/src/web5-connect/web5-connect-server.js.map +1 -0
- package/dist/esm/src/ws-api.d.ts +13 -0
- package/dist/esm/src/ws-api.d.ts.map +1 -0
- package/dist/esm/src/ws-api.js +31 -0
- package/dist/esm/src/ws-api.js.map +1 -0
- package/dist/esm/tests/common-scenario-validator.d.ts +11 -0
- package/dist/esm/tests/common-scenario-validator.d.ts.map +1 -0
- package/dist/esm/tests/common-scenario-validator.js +114 -0
- package/dist/esm/tests/common-scenario-validator.js.map +1 -0
- package/dist/esm/tests/connection/connection-manager.spec.d.ts +2 -0
- package/dist/esm/tests/connection/connection-manager.spec.d.ts.map +1 -0
- package/dist/esm/tests/connection/connection-manager.spec.js +47 -0
- package/dist/esm/tests/connection/connection-manager.spec.js.map +1 -0
- package/dist/esm/tests/connection/socket-connection.spec.d.ts +2 -0
- package/dist/esm/tests/connection/socket-connection.spec.d.ts.map +1 -0
- package/dist/esm/tests/connection/socket-connection.spec.js +125 -0
- package/dist/esm/tests/connection/socket-connection.spec.js.map +1 -0
- package/dist/esm/tests/cors/http-api.browser.d.ts +2 -0
- package/dist/esm/tests/cors/http-api.browser.d.ts.map +1 -0
- package/dist/esm/tests/cors/http-api.browser.js +60 -0
- package/dist/esm/tests/cors/http-api.browser.js.map +1 -0
- package/dist/esm/tests/cors/ping.browser.d.ts +2 -0
- package/dist/esm/tests/cors/ping.browser.d.ts.map +1 -0
- package/dist/esm/tests/cors/ping.browser.js +7 -0
- package/dist/esm/tests/cors/ping.browser.js.map +1 -0
- package/dist/esm/tests/dwn-process-message.spec.d.ts +2 -0
- package/dist/esm/tests/dwn-process-message.spec.d.ts.map +1 -0
- package/dist/esm/tests/dwn-process-message.spec.js +172 -0
- package/dist/esm/tests/dwn-process-message.spec.js.map +1 -0
- package/dist/esm/tests/dwn-server.spec.d.ts +2 -0
- package/dist/esm/tests/dwn-server.spec.d.ts.map +1 -0
- package/dist/esm/tests/dwn-server.spec.js +49 -0
- package/dist/esm/tests/dwn-server.spec.js.map +1 -0
- package/dist/esm/tests/http-api.spec.d.ts +2 -0
- package/dist/esm/tests/http-api.spec.d.ts.map +1 -0
- package/dist/esm/tests/http-api.spec.js +775 -0
- package/dist/esm/tests/http-api.spec.js.map +1 -0
- package/dist/esm/tests/json-rpc-socket.spec.d.ts +2 -0
- package/dist/esm/tests/json-rpc-socket.spec.d.ts.map +1 -0
- package/dist/esm/tests/json-rpc-socket.spec.js +225 -0
- package/dist/esm/tests/json-rpc-socket.spec.js.map +1 -0
- package/dist/esm/tests/plugins/data-store-sqlite.d.ts +17 -0
- package/dist/esm/tests/plugins/data-store-sqlite.d.ts.map +1 -0
- package/dist/esm/tests/plugins/data-store-sqlite.js +23 -0
- package/dist/esm/tests/plugins/data-store-sqlite.js.map +1 -0
- package/dist/esm/tests/plugins/event-log-sqlite.d.ts +17 -0
- package/dist/esm/tests/plugins/event-log-sqlite.d.ts.map +1 -0
- package/dist/esm/tests/plugins/event-log-sqlite.js +23 -0
- package/dist/esm/tests/plugins/event-log-sqlite.js.map +1 -0
- package/dist/esm/tests/plugins/event-stream-in-memory.d.ts +17 -0
- package/dist/esm/tests/plugins/event-stream-in-memory.d.ts.map +1 -0
- package/dist/esm/tests/plugins/event-stream-in-memory.js +21 -0
- package/dist/esm/tests/plugins/event-stream-in-memory.js.map +1 -0
- package/dist/esm/tests/plugins/message-store-sqlite.d.ts +17 -0
- package/dist/esm/tests/plugins/message-store-sqlite.d.ts.map +1 -0
- package/dist/esm/tests/plugins/message-store-sqlite.js +23 -0
- package/dist/esm/tests/plugins/message-store-sqlite.js.map +1 -0
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts +17 -0
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts.map +1 -0
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.js +23 -0
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.js.map +1 -0
- package/dist/esm/tests/process-handler.spec.d.ts +2 -0
- package/dist/esm/tests/process-handler.spec.d.ts.map +1 -0
- package/dist/esm/tests/process-handler.spec.js +60 -0
- package/dist/esm/tests/process-handler.spec.js.map +1 -0
- package/dist/esm/tests/registration/proof-of-work-manager.spec.d.ts +2 -0
- package/dist/esm/tests/registration/proof-of-work-manager.spec.d.ts.map +1 -0
- package/dist/esm/tests/registration/proof-of-work-manager.spec.js +157 -0
- package/dist/esm/tests/registration/proof-of-work-manager.spec.js.map +1 -0
- package/dist/esm/tests/rpc-subscribe-close.spec.d.ts +2 -0
- package/dist/esm/tests/rpc-subscribe-close.spec.d.ts.map +1 -0
- package/dist/esm/tests/rpc-subscribe-close.spec.js +81 -0
- package/dist/esm/tests/rpc-subscribe-close.spec.js.map +1 -0
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.d.ts +2 -0
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.d.ts.map +1 -0
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js +73 -0
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js.map +1 -0
- package/dist/esm/tests/scenarios/registration.spec.d.ts +2 -0
- package/dist/esm/tests/scenarios/registration.spec.d.ts.map +1 -0
- package/dist/esm/tests/scenarios/registration.spec.js +507 -0
- package/dist/esm/tests/scenarios/registration.spec.js.map +1 -0
- package/dist/esm/tests/scenarios/web5-connect.spec.d.ts +2 -0
- package/dist/esm/tests/scenarios/web5-connect.spec.d.ts.map +1 -0
- package/dist/esm/tests/scenarios/web5-connect.spec.js +137 -0
- package/dist/esm/tests/scenarios/web5-connect.spec.js.map +1 -0
- package/dist/esm/tests/test-dwn.d.ts +7 -0
- package/dist/esm/tests/test-dwn.d.ts.map +1 -0
- package/dist/esm/tests/test-dwn.js +34 -0
- package/dist/esm/tests/test-dwn.js.map +1 -0
- package/dist/esm/tests/utils.d.ts +46 -0
- package/dist/esm/tests/utils.d.ts.map +1 -0
- package/dist/esm/tests/utils.js +116 -0
- package/dist/esm/tests/utils.js.map +1 -0
- package/dist/esm/tests/ws-api.spec.d.ts +2 -0
- package/dist/esm/tests/ws-api.spec.d.ts.map +1 -0
- package/dist/esm/tests/ws-api.spec.js +327 -0
- package/dist/esm/tests/ws-api.spec.js.map +1 -0
- package/package.json +119 -0
- package/src/config.ts +71 -0
- package/src/connection/connection-manager.ts +39 -0
- package/src/connection/socket-connection.ts +221 -0
- package/src/dwn-error.ts +38 -0
- package/src/dwn-server.ts +178 -0
- package/src/http-api.ts +541 -0
- package/src/index.ts +6 -0
- package/src/json-rpc-api.ts +11 -0
- package/src/json-rpc-handlers/dwn/index.ts +1 -0
- package/src/json-rpc-handlers/dwn/process-message.ts +123 -0
- package/src/json-rpc-handlers/subscription/close.ts +59 -0
- package/src/json-rpc-handlers/subscription/index.ts +1 -0
- package/src/json-rpc-socket.ts +155 -0
- package/src/lib/http-server-shutdown-handler.ts +79 -0
- package/src/lib/json-rpc-router.ts +52 -0
- package/src/lib/json-rpc.ts +126 -0
- package/src/main.ts +14 -0
- package/src/metrics.ts +14 -0
- package/src/plugin-loader.ts +17 -0
- package/src/process-handlers.ts +65 -0
- package/src/registration/proof-of-work-manager.ts +317 -0
- package/src/registration/proof-of-work-types.ts +7 -0
- package/src/registration/proof-of-work.ts +100 -0
- package/src/registration/registration-manager.ts +153 -0
- package/src/registration/registration-store.ts +79 -0
- package/src/registration/registration-types.ts +18 -0
- package/src/storage.ts +213 -0
- package/src/web5-connect/sql-ttl-cache.ts +137 -0
- package/src/web5-connect/web5-connect-server.ts +122 -0
- package/src/ws-api.ts +45 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { DwnServerError, DwnServerErrorCode } from "../dwn-error.js";
|
|
2
|
+
import type { ProofOfWorkChallengeModel } from "./proof-of-work-types.js";
|
|
3
|
+
import { ProofOfWork } from "./proof-of-work.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Manages proof-of-work challenge difficulty and lifecycle based on solve rate.
|
|
7
|
+
* Can have multiple instances each having their own desired solve rate and difficulty.
|
|
8
|
+
*/
|
|
9
|
+
export class ProofOfWorkManager {
|
|
10
|
+
// Takes from seconds to ~1 minute to solve on an M1 MacBook.
|
|
11
|
+
public static readonly defaultMaximumAllowedHashValue = '000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF';
|
|
12
|
+
|
|
13
|
+
// Challenge nonces that can be used for proof-of-work.
|
|
14
|
+
private challengeNonces: {
|
|
15
|
+
previousChallengeNonce?: string,
|
|
16
|
+
currentChallengeNonce: string,
|
|
17
|
+
nextChallengeNonce?: string
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// There is opportunity to improve implementation here.
|
|
21
|
+
// TODO: https://github.com/TBD54566975/dwn-server/issues/101
|
|
22
|
+
private proofOfWorkOfLastMinute: Map<string, number> = new Map(); // proofOfWorkId -> timestamp of proof-of-work
|
|
23
|
+
|
|
24
|
+
// Seed to generate the challenge nonce from, this allows all DWN instances in a cluster to generate the same challenge.
|
|
25
|
+
private challengeSeed?: string;
|
|
26
|
+
private difficultyIncreaseMultiplier: number;
|
|
27
|
+
private currentMaximumAllowedHashValueAsBigInt: bigint;
|
|
28
|
+
private initialMaximumAllowedHashValueAsBigInt: bigint;
|
|
29
|
+
private desiredSolveCountPerMinute: number;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* How often the challenge nonce is refreshed.
|
|
33
|
+
*/
|
|
34
|
+
public challengeRefreshFrequencyInSeconds: number;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* How often the difficulty is reevaluated.
|
|
38
|
+
*/
|
|
39
|
+
public difficultyReevaluationFrequencyInSeconds: number;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The current maximum allowed hash value.
|
|
43
|
+
*/
|
|
44
|
+
public get currentMaximumAllowedHashValue(): bigint {
|
|
45
|
+
return this.currentMaximumAllowedHashValueAsBigInt;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The current proof-of-work solve rate.
|
|
50
|
+
*/
|
|
51
|
+
public get currentSolveCountPerMinute(): number {
|
|
52
|
+
return this.proofOfWorkOfLastMinute.size;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private constructor (input: {
|
|
56
|
+
desiredSolveCountPerMinute: number,
|
|
57
|
+
initialMaximumAllowedHashValue: string,
|
|
58
|
+
difficultyIncreaseMultiplier: number,
|
|
59
|
+
challengeSeed?: string,
|
|
60
|
+
challengeRefreshFrequencyInSeconds: number,
|
|
61
|
+
difficultyReevaluationFrequencyInSeconds: number
|
|
62
|
+
}) {
|
|
63
|
+
const { desiredSolveCountPerMinute, initialMaximumAllowedHashValue } = input;
|
|
64
|
+
|
|
65
|
+
this.challengeSeed = input.challengeSeed;
|
|
66
|
+
this.challengeNonces = { currentChallengeNonce: ProofOfWork.generateNonce() };
|
|
67
|
+
this.currentMaximumAllowedHashValueAsBigInt = BigInt(`0x${initialMaximumAllowedHashValue}`);
|
|
68
|
+
this.initialMaximumAllowedHashValueAsBigInt = BigInt(`0x${initialMaximumAllowedHashValue}`);
|
|
69
|
+
this.desiredSolveCountPerMinute = desiredSolveCountPerMinute;
|
|
70
|
+
this.difficultyIncreaseMultiplier = input.difficultyIncreaseMultiplier;
|
|
71
|
+
this.challengeRefreshFrequencyInSeconds = input.challengeRefreshFrequencyInSeconds;
|
|
72
|
+
this.difficultyReevaluationFrequencyInSeconds = input.difficultyReevaluationFrequencyInSeconds;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Creates a new ProofOfWorkManager instance.
|
|
77
|
+
* @param input.difficultyIncreaseMultiplier How fast to increase difficulty when solve rate is higher than desired. Must be >= 1.
|
|
78
|
+
* Defaults to 1 which means if the solve rate is 2x the desired solve rate, the difficulty will increase by 2x.
|
|
79
|
+
* If set to 2, it means if the solve rate is 2x the desired solve rate, the difficulty will increase by 4x.
|
|
80
|
+
* @param input.challengeRefreshFrequencyInSeconds How often the challenge nonce is refreshed. Defaults to 10 minutes.
|
|
81
|
+
* @param input.difficultyReevaluationFrequencyInSeconds How often the difficulty is reevaluated. Defaults to 10 seconds.
|
|
82
|
+
*/
|
|
83
|
+
public static async create(input: {
|
|
84
|
+
desiredSolveCountPerMinute: number,
|
|
85
|
+
autoStart: boolean,
|
|
86
|
+
initialMaximumAllowedHashValue?: string,
|
|
87
|
+
difficultyIncreaseMultiplier?: number,
|
|
88
|
+
challengeSeed?: string,
|
|
89
|
+
challengeRefreshFrequencyInSeconds?: number,
|
|
90
|
+
difficultyReevaluationFrequencyInSeconds?: number
|
|
91
|
+
}): Promise<ProofOfWorkManager> {
|
|
92
|
+
const { desiredSolveCountPerMinute } = input;
|
|
93
|
+
|
|
94
|
+
const initialMaximumAllowedHashValue = input.initialMaximumAllowedHashValue ?? ProofOfWorkManager.defaultMaximumAllowedHashValue;
|
|
95
|
+
const difficultyIncreaseMultiplier = input.difficultyIncreaseMultiplier ?? 1; // 1x default
|
|
96
|
+
const challengeRefreshFrequencyInSeconds = input.challengeRefreshFrequencyInSeconds ?? 10 * 60; // 10 minutes default
|
|
97
|
+
const difficultyReevaluationFrequencyInSeconds = input.difficultyReevaluationFrequencyInSeconds ?? 10; // 10 seconds default
|
|
98
|
+
|
|
99
|
+
const proofOfWorkManager = new ProofOfWorkManager({
|
|
100
|
+
desiredSolveCountPerMinute,
|
|
101
|
+
initialMaximumAllowedHashValue,
|
|
102
|
+
difficultyIncreaseMultiplier,
|
|
103
|
+
challengeSeed: input.challengeSeed,
|
|
104
|
+
challengeRefreshFrequencyInSeconds,
|
|
105
|
+
difficultyReevaluationFrequencyInSeconds
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (input.autoStart) {
|
|
109
|
+
proofOfWorkManager.start();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return proofOfWorkManager;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Starts the proof-of-work manager by starting the challenge nonce and difficulty refresh timers.
|
|
117
|
+
*/
|
|
118
|
+
public start(): void {
|
|
119
|
+
this.periodicallyRefreshChallengeNonce();
|
|
120
|
+
this.periodicallyRefreshProofOfWorkDifficulty();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
public getProofOfWorkChallenge(): ProofOfWorkChallengeModel {
|
|
124
|
+
return {
|
|
125
|
+
challengeNonce: this.challengeNonces.currentChallengeNonce,
|
|
126
|
+
maximumAllowedHashValue: ProofOfWorkManager.bigIntToHexString(this.currentMaximumAllowedHashValue),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Verifies the proof-of-work meets the difficulty requirement.
|
|
132
|
+
*/
|
|
133
|
+
public async verifyProofOfWork(proofOfWork: {
|
|
134
|
+
challengeNonce: string;
|
|
135
|
+
responseNonce: string;
|
|
136
|
+
requestData: string;
|
|
137
|
+
}): Promise<void> {
|
|
138
|
+
const { challengeNonce, responseNonce, requestData } = proofOfWork;
|
|
139
|
+
|
|
140
|
+
if (this.proofOfWorkOfLastMinute.has(responseNonce)) {
|
|
141
|
+
throw new DwnServerError(
|
|
142
|
+
DwnServerErrorCode.ProofOfWorkManagerResponseNonceReused,
|
|
143
|
+
`Not allowed to reused response nonce: ${responseNonce}.`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Verify response nonce is a HEX string that represents a 256 bit value.
|
|
148
|
+
if (!ProofOfWorkManager.isHexString(responseNonce) || responseNonce.length !== 64) {
|
|
149
|
+
throw new DwnServerError(
|
|
150
|
+
DwnServerErrorCode.ProofOfWorkManagerInvalidResponseNonceFormat,
|
|
151
|
+
`Response nonce not a HEX string representing a 256 bit value: ${responseNonce}.`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Verify challenge nonce is valid.
|
|
156
|
+
const { previousChallengeNonce, currentChallengeNonce, nextChallengeNonce } = this.challengeNonces;
|
|
157
|
+
const acceptableChallengeNonces = [previousChallengeNonce, currentChallengeNonce, nextChallengeNonce].filter(nonce => nonce !== undefined && nonce !== '');
|
|
158
|
+
if (!acceptableChallengeNonces.includes(challengeNonce)) {
|
|
159
|
+
throw new DwnServerError(
|
|
160
|
+
DwnServerErrorCode.ProofOfWorkManagerInvalidChallengeNonce,
|
|
161
|
+
`Unknown or expired challenge nonce: ${challengeNonce}.`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const maximumAllowedHashValue = this.currentMaximumAllowedHashValue;
|
|
166
|
+
ProofOfWork.verifyResponseNonce({ challengeNonce, responseNonce, requestData, maximumAllowedHashValue });
|
|
167
|
+
|
|
168
|
+
this.recordProofOfWork(responseNonce);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Records a successful proof-of-work.
|
|
173
|
+
* Exposed for testing purposes.
|
|
174
|
+
*/
|
|
175
|
+
public async recordProofOfWork(proofOfWorkId: string): Promise<void> {
|
|
176
|
+
this.proofOfWorkOfLastMinute.set(proofOfWorkId, Date.now());
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private periodicallyRefreshChallengeNonce (): void {
|
|
180
|
+
try {
|
|
181
|
+
this.refreshChallengeNonce();
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error(`Encountered error while refreshing challenge nonce: ${error}`);
|
|
184
|
+
} finally {
|
|
185
|
+
setTimeout(async () => this.periodicallyRefreshChallengeNonce(), this.challengeRefreshFrequencyInSeconds * 1000);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private periodicallyRefreshProofOfWorkDifficulty (): void {
|
|
190
|
+
try {
|
|
191
|
+
this.refreshMaximumAllowedHashValue();
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error(`Encountered error while updating proof of work difficulty: ${error}`);
|
|
194
|
+
} finally {
|
|
195
|
+
setTimeout(async () => this.periodicallyRefreshProofOfWorkDifficulty(), this.difficultyReevaluationFrequencyInSeconds * 1000);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private removeProofOfWorkOlderThanOneMinute (): void {
|
|
200
|
+
const oneMinuteAgo = Date.now() - 60 * 1000;
|
|
201
|
+
for (const proofOfWorkId of this.proofOfWorkOfLastMinute.keys()) {
|
|
202
|
+
if (this.proofOfWorkOfLastMinute.get(proofOfWorkId) < oneMinuteAgo) {
|
|
203
|
+
this.proofOfWorkOfLastMinute.delete(proofOfWorkId);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private refreshChallengeNonce(): void {
|
|
209
|
+
// If challenge seed is supplied, use it to deterministically generate the challenge nonces.
|
|
210
|
+
if (this.challengeSeed !== undefined) {
|
|
211
|
+
const currentRefreshIntervalId = Math.floor(Date.now() / (this.challengeRefreshFrequencyInSeconds * 1000));
|
|
212
|
+
const previousRefreshIntervalId = currentRefreshIntervalId - 1;
|
|
213
|
+
const nextRefreshIntervalId = currentRefreshIntervalId + 1;
|
|
214
|
+
|
|
215
|
+
const previousChallengeNonce = ProofOfWork.hashAsHexString([this.challengeSeed, previousRefreshIntervalId.toString(), this.challengeSeed]);
|
|
216
|
+
const currentChallengeNonce = ProofOfWork.hashAsHexString([this.challengeSeed, currentRefreshIntervalId.toString(), this.challengeSeed]);
|
|
217
|
+
const nextChallengeNonce = ProofOfWork.hashAsHexString([this.challengeSeed, nextRefreshIntervalId.toString(), this.challengeSeed]);
|
|
218
|
+
|
|
219
|
+
this.challengeNonces = { previousChallengeNonce, currentChallengeNonce, nextChallengeNonce };
|
|
220
|
+
} else {
|
|
221
|
+
const newChallengeNonce = ProofOfWork.generateNonce();
|
|
222
|
+
|
|
223
|
+
this.challengeNonces.previousChallengeNonce = this.challengeNonces.currentChallengeNonce;
|
|
224
|
+
this.challengeNonces.currentChallengeNonce = newChallengeNonce;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Refreshes the difficulty by changing the max hash value.
|
|
230
|
+
* The higher the number, the easier. Scale 1 (hardest) to 2^256 (easiest), represented in HEX.
|
|
231
|
+
*
|
|
232
|
+
* If solve rate rate is higher than expected, the difficulty will increase rapidly.
|
|
233
|
+
* If solve rate is lower than expected, the difficulty will decrease gradually.
|
|
234
|
+
* The difficulty will never be lower than the initial difficulty.
|
|
235
|
+
*/
|
|
236
|
+
private async refreshMaximumAllowedHashValue (): Promise<void> {
|
|
237
|
+
// Cleanup proof-of-work cache and update solve rate.
|
|
238
|
+
this.removeProofOfWorkOlderThanOneMinute();
|
|
239
|
+
|
|
240
|
+
const latestSolveCountPerMinute = this.proofOfWorkOfLastMinute.size;
|
|
241
|
+
|
|
242
|
+
// NOTE: bigint arithmetic does NOT work with decimals, so we work with "full numbers" by multiplying by a scale factor.
|
|
243
|
+
const scaleFactor = 1_000_000;
|
|
244
|
+
const difficultyEvaluationsPerMinute = 60000 / (this.difficultyReevaluationFrequencyInSeconds * 1000); // assumed to be >= 1;
|
|
245
|
+
|
|
246
|
+
// NOTE: easier difficulty is represented by a larger max allowed hash value
|
|
247
|
+
// and harder difficulty is represented by a smaller max allowed hash value.
|
|
248
|
+
if (latestSolveCountPerMinute > this.desiredSolveCountPerMinute) {
|
|
249
|
+
// if solve rate is higher than desired, make difficulty harder by making the max allowed hash value smaller
|
|
250
|
+
|
|
251
|
+
const currentSolveRateInFractionOfDesiredSolveRate = latestSolveCountPerMinute / this.desiredSolveCountPerMinute;
|
|
252
|
+
const newMaximumAllowedHashValueAsBigIntPriorToMultiplierAdjustment
|
|
253
|
+
= (this.currentMaximumAllowedHashValueAsBigInt * BigInt(scaleFactor)) /
|
|
254
|
+
(BigInt(Math.floor(currentSolveRateInFractionOfDesiredSolveRate * this.difficultyIncreaseMultiplier * scaleFactor)));
|
|
255
|
+
|
|
256
|
+
const hashValueDecreaseAmountPriorToEvaluationFrequencyAdjustment
|
|
257
|
+
= (this.currentMaximumAllowedHashValueAsBigInt - newMaximumAllowedHashValueAsBigIntPriorToMultiplierAdjustment) *
|
|
258
|
+
(BigInt(Math.floor(this.difficultyIncreaseMultiplier * scaleFactor)) / BigInt(scaleFactor));
|
|
259
|
+
|
|
260
|
+
// Adjustment based on the reevaluation frequency to provide more-or-less consistent behavior regardless of the reevaluation frequency.
|
|
261
|
+
const hashValueDecreaseAmount = hashValueDecreaseAmountPriorToEvaluationFrequencyAdjustment / BigInt(difficultyEvaluationsPerMinute);
|
|
262
|
+
|
|
263
|
+
this.currentMaximumAllowedHashValueAsBigInt -= hashValueDecreaseAmount;
|
|
264
|
+
|
|
265
|
+
// Resetting to allow hash increment to be recalculated when difficulty needs to be reduced (in `else` block below)
|
|
266
|
+
this.hashValueIncrementPerEvaluation = undefined;
|
|
267
|
+
} else {
|
|
268
|
+
// if solve rate is lower than desired, make difficulty easier by making the max allowed hash value larger
|
|
269
|
+
|
|
270
|
+
if (this.currentMaximumAllowedHashValueAsBigInt === this.initialMaximumAllowedHashValueAsBigInt) {
|
|
271
|
+
// if current difficulty is already at initial difficulty, nothing to do
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (this.hashValueIncrementPerEvaluation === undefined) {
|
|
276
|
+
const backToInitialDifficultyInMinutes = 10;
|
|
277
|
+
const differenceBetweenInitialAndCurrentDifficulty
|
|
278
|
+
= this.initialMaximumAllowedHashValueAsBigInt - this.currentMaximumAllowedHashValueAsBigInt;
|
|
279
|
+
this.hashValueIncrementPerEvaluation
|
|
280
|
+
= differenceBetweenInitialAndCurrentDifficulty / BigInt(backToInitialDifficultyInMinutes * difficultyEvaluationsPerMinute);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// if newly calculated difficulty is lower than initial difficulty, just use the initial difficulty
|
|
284
|
+
const newMaximumAllowedHashValueAsBigInt = this.currentMaximumAllowedHashValueAsBigInt + this.hashValueIncrementPerEvaluation;
|
|
285
|
+
if (newMaximumAllowedHashValueAsBigInt >= this.initialMaximumAllowedHashValueAsBigInt) {
|
|
286
|
+
this.currentMaximumAllowedHashValueAsBigInt = this.initialMaximumAllowedHashValueAsBigInt;
|
|
287
|
+
} else {
|
|
288
|
+
this.currentMaximumAllowedHashValueAsBigInt = newMaximumAllowedHashValueAsBigInt;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Only used by refreshMaximumAllowedHashValue() to reduce the challenge difficulty gradually.
|
|
295
|
+
*/
|
|
296
|
+
private hashValueIncrementPerEvaluation = BigInt(1);
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Verifies that the supplied string is a HEX string.
|
|
300
|
+
*/
|
|
301
|
+
public static isHexString(str: string): boolean {
|
|
302
|
+
const regexp = /^[0-9a-fA-F]+$/;
|
|
303
|
+
return regexp.test(str);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Converts a BigInt to a 256 bit HEX string with padded preceding zeros (64 characters).
|
|
308
|
+
*/
|
|
309
|
+
private static bigIntToHexString (int: BigInt): string {
|
|
310
|
+
let hex = int.toString(16).toUpperCase();
|
|
311
|
+
const stringLength = hex.length;
|
|
312
|
+
for (let pad = stringLength; pad < 64; pad++) {
|
|
313
|
+
hex = '0' + hex;
|
|
314
|
+
}
|
|
315
|
+
return hex;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'crypto';
|
|
2
|
+
|
|
3
|
+
import { DwnServerError, DwnServerErrorCode } from '../dwn-error.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Utility methods related to proof-of-work.
|
|
7
|
+
*/
|
|
8
|
+
export class ProofOfWork {
|
|
9
|
+
/**
|
|
10
|
+
* Computes the resulting hash of the given proof-of-work input.
|
|
11
|
+
*/
|
|
12
|
+
public static computeHash(input: {
|
|
13
|
+
challengeNonce: string;
|
|
14
|
+
responseNonce: string;
|
|
15
|
+
requestData: string;
|
|
16
|
+
}): string {
|
|
17
|
+
const hashInput = [input.challengeNonce, input.responseNonce, input.requestData];
|
|
18
|
+
return this.hashAsHexString(hashInput);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Computes the hash of the given array of strings.
|
|
23
|
+
*/
|
|
24
|
+
public static hashAsHexString(input: string[]): string {
|
|
25
|
+
const hash = createHash('sha256');
|
|
26
|
+
for (const item of input) {
|
|
27
|
+
hash.update(item);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return hash.digest('hex');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Verifies that the response nonce meets the proof-of-work difficulty requirement.
|
|
35
|
+
*/
|
|
36
|
+
public static verifyResponseNonce(input: {
|
|
37
|
+
maximumAllowedHashValue: bigint;
|
|
38
|
+
challengeNonce: string;
|
|
39
|
+
responseNonce: string;
|
|
40
|
+
requestData: string;
|
|
41
|
+
}): void {
|
|
42
|
+
const computedHash = this.computeHash(input);
|
|
43
|
+
const computedHashAsBigInt = BigInt(`0x${computedHash}`);
|
|
44
|
+
|
|
45
|
+
if (computedHashAsBigInt > input.maximumAllowedHashValue) {
|
|
46
|
+
throw new DwnServerError(
|
|
47
|
+
DwnServerErrorCode.ProofOfWorkInsufficientSolutionNonce,
|
|
48
|
+
`Insufficient computed hash ${computedHashAsBigInt}, needs to be <= ${input.maximumAllowedHashValue}.`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Finds a response nonce that qualifies the difficulty requirement for the given proof-of-work challenge and request data.
|
|
55
|
+
* NOTE: mainly for demonstrating the procedure to find a qualified response nonce.
|
|
56
|
+
* Will need to artificially introduce asynchrony to allow other tasks to run if this method is to be used in a real-world client.
|
|
57
|
+
*/
|
|
58
|
+
public static findQualifiedResponseNonce(input: {
|
|
59
|
+
maximumAllowedHashValue: string;
|
|
60
|
+
challengeNonce: string;
|
|
61
|
+
requestData: string;
|
|
62
|
+
}): string {
|
|
63
|
+
const startTime = Date.now();
|
|
64
|
+
|
|
65
|
+
const { maximumAllowedHashValue, challengeNonce, requestData } = input;
|
|
66
|
+
const maximumAllowedHashValueAsBigInt = BigInt(`0x${maximumAllowedHashValue}`);
|
|
67
|
+
|
|
68
|
+
let iterations = 1;
|
|
69
|
+
let randomNonce;
|
|
70
|
+
let qualifiedSolutionNonceFound = false;
|
|
71
|
+
do {
|
|
72
|
+
randomNonce = this.generateNonce();
|
|
73
|
+
const computedHash = this.computeHash({
|
|
74
|
+
challengeNonce,
|
|
75
|
+
responseNonce: randomNonce,
|
|
76
|
+
requestData,
|
|
77
|
+
});
|
|
78
|
+
const computedHashAsBigInt = BigInt(`0x${computedHash}`);
|
|
79
|
+
|
|
80
|
+
qualifiedSolutionNonceFound = computedHashAsBigInt <= maximumAllowedHashValueAsBigInt;
|
|
81
|
+
|
|
82
|
+
iterations++;
|
|
83
|
+
} while (!qualifiedSolutionNonceFound);
|
|
84
|
+
|
|
85
|
+
// Log final/successful iteration.
|
|
86
|
+
console.log(
|
|
87
|
+
`iterations: ${iterations}, time lapsed: ${Date.now() - startTime} ms`,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return randomNonce;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Generates 32 random bytes expressed as a HEX string.
|
|
95
|
+
*/
|
|
96
|
+
public static generateNonce(): string {
|
|
97
|
+
const hexString = randomBytes(32).toString('hex').toUpperCase();
|
|
98
|
+
return hexString;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { ProofOfWorkManager } from "./proof-of-work-manager.js";
|
|
2
|
+
import { ProofOfWork } from "./proof-of-work.js";
|
|
3
|
+
import { RegistrationStore } from "./registration-store.js";
|
|
4
|
+
import type { RegistrationData, RegistrationRequest } from "./registration-types.js";
|
|
5
|
+
import type { ProofOfWorkChallengeModel } from "./proof-of-work-types.js";
|
|
6
|
+
import { DwnServerError, DwnServerErrorCode } from "../dwn-error.js";
|
|
7
|
+
import type { ActiveTenantCheckResult, TenantGate } from "@enbox/dwn-sdk-js";
|
|
8
|
+
import { getDialectFromUrl } from "../storage.js";
|
|
9
|
+
import { readFileSync } from "fs";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The RegistrationManager is responsible for managing the registration of tenants.
|
|
13
|
+
* It handles tenant registration requests and provides the corresponding `TenantGate` implementation.
|
|
14
|
+
*/
|
|
15
|
+
export class RegistrationManager implements TenantGate {
|
|
16
|
+
private proofOfWorkManager: ProofOfWorkManager;
|
|
17
|
+
private registrationStore: RegistrationStore;
|
|
18
|
+
|
|
19
|
+
private termsOfServiceHash?: string;
|
|
20
|
+
private termsOfService?: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The terms-of-service.
|
|
24
|
+
*/
|
|
25
|
+
public getTermsOfService(): string | undefined {
|
|
26
|
+
return this.termsOfService;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The terms-of-service hash.
|
|
31
|
+
*/
|
|
32
|
+
public getTermsOfServiceHash(): string | undefined {
|
|
33
|
+
return this.termsOfServiceHash;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Updates the terms-of-service. Exposed for testing purposes.
|
|
38
|
+
*/
|
|
39
|
+
public updateTermsOfService(termsOfService: string): void {
|
|
40
|
+
this.termsOfServiceHash = ProofOfWork.hashAsHexString([termsOfService]);
|
|
41
|
+
this.termsOfService = termsOfService;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Creates a new RegistrationManager instance.
|
|
46
|
+
* @param input.registrationStoreUrl - The URL of the registration store.
|
|
47
|
+
* Set to `undefined` or empty string if tenant registration is not required (ie. DWN is open for all).
|
|
48
|
+
*
|
|
49
|
+
*/
|
|
50
|
+
public static async create(input: {
|
|
51
|
+
registrationStoreUrl?: string,
|
|
52
|
+
termsOfServiceFilePath?: string
|
|
53
|
+
proofOfWorkChallengeNonceSeed?: string,
|
|
54
|
+
proofOfWorkInitialMaximumAllowedHash?: string,
|
|
55
|
+
}): Promise<RegistrationManager> {
|
|
56
|
+
const { termsOfServiceFilePath, registrationStoreUrl } = input;
|
|
57
|
+
|
|
58
|
+
const registrationManager = new RegistrationManager();
|
|
59
|
+
|
|
60
|
+
// short-circuit if tenant registration is not required.
|
|
61
|
+
if (!registrationStoreUrl) {
|
|
62
|
+
return registrationManager;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Initialize terms-of-service.
|
|
66
|
+
if (termsOfServiceFilePath !== undefined) {
|
|
67
|
+
const termsOfService = readFileSync(termsOfServiceFilePath).toString();
|
|
68
|
+
registrationManager.updateTermsOfService(termsOfService);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Initialize and start ProofOfWorkManager.
|
|
72
|
+
registrationManager.proofOfWorkManager = await ProofOfWorkManager.create({
|
|
73
|
+
autoStart: true,
|
|
74
|
+
desiredSolveCountPerMinute: 10,
|
|
75
|
+
initialMaximumAllowedHashValue: input.proofOfWorkInitialMaximumAllowedHash,
|
|
76
|
+
challengeSeed: input.proofOfWorkChallengeNonceSeed,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Initialize RegistrationStore.
|
|
80
|
+
const sqlDialect = getDialectFromUrl(new URL(registrationStoreUrl));
|
|
81
|
+
const registrationStore = await RegistrationStore.create(sqlDialect);
|
|
82
|
+
registrationManager.registrationStore = registrationStore;
|
|
83
|
+
|
|
84
|
+
return registrationManager;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Gets the proof-of-work challenge.
|
|
89
|
+
*/
|
|
90
|
+
public getProofOfWorkChallenge(): ProofOfWorkChallengeModel {
|
|
91
|
+
const proofOfWorkChallenge = this.proofOfWorkManager.getProofOfWorkChallenge();
|
|
92
|
+
return proofOfWorkChallenge;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Handles a registration request.
|
|
97
|
+
*/
|
|
98
|
+
public async handleRegistrationRequest(registrationRequest: RegistrationRequest): Promise<void> {
|
|
99
|
+
// Ensure the supplied terms of service hash matches the one we require.
|
|
100
|
+
if (registrationRequest.registrationData.termsOfServiceHash !== this.termsOfServiceHash) {
|
|
101
|
+
throw new DwnServerError(DwnServerErrorCode.RegistrationManagerInvalidOrOutdatedTermsOfServiceHash,
|
|
102
|
+
`Expecting terms-of-service hash ${this.termsOfServiceHash}, but got ${registrationRequest.registrationData.termsOfServiceHash}.`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const { challengeNonce, responseNonce } = registrationRequest.proofOfWork;
|
|
107
|
+
|
|
108
|
+
await this.proofOfWorkManager.verifyProofOfWork({
|
|
109
|
+
challengeNonce,
|
|
110
|
+
responseNonce,
|
|
111
|
+
requestData: JSON.stringify(registrationRequest.registrationData),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Store tenant registration data in database.
|
|
115
|
+
await this.recordTenantRegistration(registrationRequest.registrationData);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Records the given registration data in the database.
|
|
120
|
+
* Exposed as a public method for testing purposes.
|
|
121
|
+
*/
|
|
122
|
+
public async recordTenantRegistration(registrationData: RegistrationData): Promise<void> {
|
|
123
|
+
await this.registrationStore.insertOrUpdateTenantRegistration(registrationData);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The TenantGate implementation.
|
|
128
|
+
*/
|
|
129
|
+
public async isActiveTenant(tenant: string): Promise<ActiveTenantCheckResult> {
|
|
130
|
+
// If there is no registration store initialized, then DWN is open for all.
|
|
131
|
+
if (this.registrationStore === undefined) {
|
|
132
|
+
return { isActiveTenant: true };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const tenantRegistration = await this.registrationStore.getTenantRegistration(tenant);
|
|
136
|
+
|
|
137
|
+
if (tenantRegistration === undefined) {
|
|
138
|
+
return {
|
|
139
|
+
isActiveTenant: false,
|
|
140
|
+
detail: 'Not a registered tenant.'
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (tenantRegistration.termsOfServiceHash !== this.termsOfServiceHash) {
|
|
145
|
+
return {
|
|
146
|
+
isActiveTenant: false,
|
|
147
|
+
detail: 'Agreed terms-of-service is outdated.'
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { isActiveTenant: true }
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Kysely } from 'kysely';
|
|
2
|
+
import type { RegistrationData } from './registration-types.js';
|
|
3
|
+
import type { Dialect } from '@enbox/dwn-sql-store';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The RegistrationStore is responsible for storing and retrieving tenant registration information.
|
|
7
|
+
*/
|
|
8
|
+
export class RegistrationStore {
|
|
9
|
+
private static readonly registeredTenantTableName = 'registeredTenants';
|
|
10
|
+
|
|
11
|
+
private db: Kysely<RegistrationDatabase>;
|
|
12
|
+
|
|
13
|
+
private constructor (sqlDialect: Dialect) {
|
|
14
|
+
this.db = new Kysely<RegistrationDatabase>({ dialect: sqlDialect });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a new RegistrationStore instance.
|
|
19
|
+
*/
|
|
20
|
+
public static async create(sqlDialect: Dialect): Promise<RegistrationStore> {
|
|
21
|
+
const proofOfWorkManager = new RegistrationStore(sqlDialect);
|
|
22
|
+
|
|
23
|
+
await proofOfWorkManager.initialize();
|
|
24
|
+
|
|
25
|
+
return proofOfWorkManager;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private async initialize(): Promise<void> {
|
|
29
|
+
await this.db.schema
|
|
30
|
+
.createTable(RegistrationStore.registeredTenantTableName)
|
|
31
|
+
.ifNotExists()
|
|
32
|
+
.addColumn('did', 'text', (column) => column.primaryKey())
|
|
33
|
+
.addColumn('termsOfServiceHash', 'text')
|
|
34
|
+
.execute();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Inserts or updates the tenant registration information.
|
|
39
|
+
*/
|
|
40
|
+
public async insertOrUpdateTenantRegistration(registrationData: RegistrationData): Promise<void> {
|
|
41
|
+
await this.db
|
|
42
|
+
.insertInto(RegistrationStore.registeredTenantTableName)
|
|
43
|
+
.values(registrationData)
|
|
44
|
+
.onConflict((oc) =>
|
|
45
|
+
oc.column('did').doUpdateSet((eb) => ({
|
|
46
|
+
termsOfServiceHash: eb.ref('excluded.termsOfServiceHash'),
|
|
47
|
+
})),
|
|
48
|
+
)
|
|
49
|
+
// Executes the query. No error is thrown if the query doesn’t affect any rows (ie. if the insert or update didn’t change anything).
|
|
50
|
+
.executeTakeFirst();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Retrieves the tenant registration information.
|
|
55
|
+
*/
|
|
56
|
+
public async getTenantRegistration(tenantDid: string): Promise<RegistrationData | undefined> {
|
|
57
|
+
const result = await this.db
|
|
58
|
+
.selectFrom(RegistrationStore.registeredTenantTableName)
|
|
59
|
+
.select('did')
|
|
60
|
+
.select('termsOfServiceHash')
|
|
61
|
+
.where('did', '=', tenantDid)
|
|
62
|
+
.execute();
|
|
63
|
+
|
|
64
|
+
if (result.length === 0) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return result[0];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface RegisteredTenants {
|
|
73
|
+
did: string;
|
|
74
|
+
termsOfServiceHash: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface RegistrationDatabase {
|
|
78
|
+
registeredTenants: RegisteredTenants;
|
|
79
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registration data model to be included as a parameter in the /registration POST request.
|
|
3
|
+
*/
|
|
4
|
+
export type RegistrationData = {
|
|
5
|
+
did: string;
|
|
6
|
+
termsOfServiceHash: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Registration request model of the /registration POST API.
|
|
11
|
+
*/
|
|
12
|
+
export type RegistrationRequest = {
|
|
13
|
+
proofOfWork: {
|
|
14
|
+
challengeNonce: string;
|
|
15
|
+
responseNonce: string;
|
|
16
|
+
},
|
|
17
|
+
registrationData: RegistrationData
|
|
18
|
+
};
|