altcha-lib 0.1.5 → 0.2.0

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/README.md CHANGED
@@ -70,6 +70,17 @@ Parameters:
70
70
 
71
71
  Returns: `{ controller: AbortController, promise: Promise<Solution | null> }`
72
72
 
73
+ ### `verifyServerSignature(payload, hmacKey)`
74
+
75
+ Verifies the server signature returned by the API. The payload can be a Base64-encoded JSON payload or an object.
76
+
77
+ Parameters:
78
+
79
+ - `payload: string | ServerSignaturePayload`
80
+ - `hmacKey: string`
81
+
82
+ Returns: `Promise<{ verificationData: ServerSignatureVerificationData | null, verified: boolean }>`
83
+
73
84
  ### `solveChallengeWorkers(workerScript, concurrency, challenge, salt, algorithm?, max?, start?)`
74
85
 
75
86
  Finds a solution to the given challenge with [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker) running concurrently.
@@ -2,7 +2,9 @@ import './crypto.js';
2
2
  import type { Algorithm } from './types.js';
3
3
  export declare const encoder: TextEncoder;
4
4
  export declare function ab2hex(ab: ArrayBuffer | Uint8Array): string;
5
- export declare function hash(algorithm: Algorithm, str: string): Promise<string>;
6
- export declare function hmac(algorithm: Algorithm, str: string, secret: string): Promise<string>;
5
+ export declare function hash(algorithm: Algorithm, data: ArrayBuffer | string): Promise<ArrayBuffer>;
6
+ export declare function hashHex(algorithm: Algorithm, data: ArrayBuffer | string): Promise<string>;
7
+ export declare function hmac(algorithm: Algorithm, data: ArrayBuffer | string, secret: string): Promise<ArrayBuffer>;
8
+ export declare function hmacHex(algorithm: Algorithm, data: ArrayBuffer | string, secret: string): Promise<string>;
7
9
  export declare function randomBytes(length: number): Uint8Array;
8
10
  export declare function randomInt(max: number): number;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.randomInt = exports.randomBytes = exports.hmac = exports.hash = exports.ab2hex = exports.encoder = void 0;
3
+ exports.randomInt = exports.randomBytes = exports.hmacHex = exports.hmac = exports.hashHex = exports.hash = exports.ab2hex = exports.encoder = void 0;
4
4
  // @denoify-line-ignore
5
5
  require("./crypto.js");
6
6
  exports.encoder = new TextEncoder();
@@ -10,18 +10,26 @@ function ab2hex(ab) {
10
10
  .join('');
11
11
  }
12
12
  exports.ab2hex = ab2hex;
13
- async function hash(algorithm, str) {
14
- return ab2hex(await crypto.subtle.digest(algorithm.toUpperCase(), exports.encoder.encode(str)));
13
+ async function hash(algorithm, data) {
14
+ return crypto.subtle.digest(algorithm.toUpperCase(), typeof data === 'string' ? exports.encoder.encode(data) : new Uint8Array(data));
15
15
  }
16
16
  exports.hash = hash;
17
- async function hmac(algorithm, str, secret) {
17
+ async function hashHex(algorithm, data) {
18
+ return ab2hex(await hash(algorithm, data));
19
+ }
20
+ exports.hashHex = hashHex;
21
+ async function hmac(algorithm, data, secret) {
18
22
  const key = await crypto.subtle.importKey('raw', exports.encoder.encode(secret), {
19
23
  name: 'HMAC',
20
24
  hash: algorithm,
21
25
  }, false, ['sign', 'verify']);
22
- return ab2hex(await crypto.subtle.sign('HMAC', key, exports.encoder.encode(str)));
26
+ return crypto.subtle.sign('HMAC', key, typeof data === 'string' ? exports.encoder.encode(data) : new Uint8Array(data));
23
27
  }
24
28
  exports.hmac = hmac;
29
+ async function hmacHex(algorithm, data, secret) {
30
+ return ab2hex(await hmac(algorithm, data, secret));
31
+ }
32
+ exports.hmacHex = hmacHex;
25
33
  function randomBytes(length) {
26
34
  const ab = new Uint8Array(length);
27
35
  crypto.getRandomValues(ab);
@@ -1,6 +1,10 @@
1
- import type { Challenge, ChallengeOptions, Payload, Solution } from './types.js';
1
+ import type { Challenge, ChallengeOptions, Payload, ServerSignaturePayload, ServerSignatureVerificationData, Solution } from './types.js';
2
2
  export declare function createChallenge(options: ChallengeOptions): Promise<Challenge>;
3
3
  export declare function verifySolution(payload: string | Payload, hmacKey: string): Promise<boolean>;
4
+ export declare function verifyServerSignature(payload: string | ServerSignaturePayload, hmacKey: string): Promise<{
5
+ verificationData: ServerSignatureVerificationData | null;
6
+ verified: boolean | null;
7
+ }>;
4
8
  export declare function solveChallenge(challenge: string, salt: string, algorithm?: string, max?: number, start?: number): {
5
9
  promise: Promise<Solution | null>;
6
10
  controller: AbortController;
@@ -8,8 +12,9 @@ export declare function solveChallenge(challenge: string, salt: string, algorith
8
12
  export declare function solveChallengeWorkers(workerScript: string | URL | (() => Worker), concurrency: number, challenge: string, salt: string, algorithm?: string, max?: number, startNumber?: number): Promise<Solution | null>;
9
13
  declare const _default: {
10
14
  createChallenge: typeof createChallenge;
11
- verifySolution: typeof verifySolution;
12
15
  solveChallenge: typeof solveChallenge;
13
16
  solveChallengeWorkers: typeof solveChallengeWorkers;
17
+ verifyServerSignature: typeof verifyServerSignature;
18
+ verifySolution: typeof verifySolution;
14
19
  };
15
20
  export default _default;
package/cjs/dist/index.js CHANGED
@@ -1,23 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.solveChallengeWorkers = exports.solveChallenge = exports.verifySolution = exports.createChallenge = void 0;
3
+ exports.solveChallengeWorkers = exports.solveChallenge = exports.verifyServerSignature = exports.verifySolution = exports.createChallenge = void 0;
4
4
  const helpers_js_1 = require("./helpers.js");
5
5
  const DEFAULT_MAX_NUMBER = 1e6;
6
6
  const DEFAULT_SALT_LEN = 12;
7
7
  const DEFAULT_ALG = 'SHA-256';
8
8
  async function createChallenge(options) {
9
9
  const algorithm = options.algorithm || DEFAULT_ALG;
10
- const max = options.maxNumber || DEFAULT_MAX_NUMBER;
10
+ const maxnumber = options.maxnumber || options.maxNumber || DEFAULT_MAX_NUMBER;
11
11
  const saltLength = options.saltLength || DEFAULT_SALT_LEN;
12
12
  const salt = options.salt || (0, helpers_js_1.ab2hex)((0, helpers_js_1.randomBytes)(saltLength));
13
- const number = options.number === void 0 ? (0, helpers_js_1.randomInt)(max) : options.number;
14
- const challenge = await (0, helpers_js_1.hash)(algorithm, salt + number);
13
+ const number = options.number === void 0 ? (0, helpers_js_1.randomInt)(maxnumber) : options.number;
14
+ const challenge = await (0, helpers_js_1.hashHex)(algorithm, salt + number);
15
15
  return {
16
16
  algorithm,
17
17
  challenge,
18
- max,
18
+ maxnumber,
19
19
  salt,
20
- signature: await (0, helpers_js_1.hmac)(algorithm, challenge, options.hmacKey),
20
+ signature: await (0, helpers_js_1.hmacHex)(algorithm, challenge, options.hmacKey),
21
21
  };
22
22
  }
23
23
  exports.createChallenge = createChallenge;
@@ -35,6 +35,39 @@ async function verifySolution(payload, hmacKey) {
35
35
  check.signature === payload.signature);
36
36
  }
37
37
  exports.verifySolution = verifySolution;
38
+ async function verifyServerSignature(payload, hmacKey) {
39
+ if (typeof payload === 'string') {
40
+ payload = JSON.parse(atob(payload));
41
+ }
42
+ const signature = await (0, helpers_js_1.hmacHex)(payload.algorithm, await (0, helpers_js_1.hash)(payload.algorithm, payload.verificationData), hmacKey);
43
+ let verificationData = null;
44
+ try {
45
+ const params = new URLSearchParams(payload.verificationData);
46
+ verificationData = {
47
+ ...Object.fromEntries(params),
48
+ expire: parseInt(params.get('expire') || '0', 10),
49
+ fields: params.get('fields')?.split(','),
50
+ reasons: params.get('reasons')?.split(','),
51
+ score: params.get('score')
52
+ ? parseFloat(params.get('score') || '0')
53
+ : void 0,
54
+ time: parseInt(params.get('time') || '0', 10),
55
+ verified: params.get('verified') === 'true',
56
+ };
57
+ }
58
+ catch {
59
+ // noop
60
+ }
61
+ return {
62
+ verificationData,
63
+ verified: payload.verified === true &&
64
+ verificationData &&
65
+ verificationData.verified === true &&
66
+ verificationData.expire > Math.floor(Date.now() / 1000) &&
67
+ payload.signature === signature,
68
+ };
69
+ }
70
+ exports.verifyServerSignature = verifyServerSignature;
38
71
  function solveChallenge(challenge, salt, algorithm = 'SHA-256', max = 1e6, start = 0) {
39
72
  const controller = new AbortController();
40
73
  const promise = new Promise((resolve, reject) => {
@@ -44,7 +77,7 @@ function solveChallenge(challenge, salt, algorithm = 'SHA-256', max = 1e6, start
44
77
  resolve(null);
45
78
  }
46
79
  else {
47
- hashChallenge(salt, n, algorithm)
80
+ (0, helpers_js_1.hashHex)(algorithm, salt + n)
48
81
  .then((t) => {
49
82
  if (t === challenge) {
50
83
  resolve({
@@ -117,12 +150,10 @@ async function solveChallengeWorkers(workerScript, concurrency, challenge, salt,
117
150
  return solutions.find((solution) => !!solution) || null;
118
151
  }
119
152
  exports.solveChallengeWorkers = solveChallengeWorkers;
120
- async function hashChallenge(salt, num, algorithm) {
121
- return (0, helpers_js_1.ab2hex)(await crypto.subtle.digest(algorithm.toUpperCase(), helpers_js_1.encoder.encode(salt + num)));
122
- }
123
153
  exports.default = {
124
154
  createChallenge,
125
- verifySolution,
126
155
  solveChallenge,
127
156
  solveChallengeWorkers,
157
+ verifyServerSignature,
158
+ verifySolution,
128
159
  };
@@ -2,13 +2,14 @@ export type Algorithm = 'SHA-1' | 'SHA-256' | 'SHA-512';
2
2
  export interface Challenge {
3
3
  algorithm: Algorithm;
4
4
  challenge: string;
5
- max?: number;
5
+ maxnumber?: number;
6
6
  salt: string;
7
7
  signature: string;
8
8
  }
9
9
  export interface ChallengeOptions {
10
10
  algorithm?: Algorithm;
11
11
  hmacKey: string;
12
+ maxnumber?: number;
12
13
  maxNumber?: number;
13
14
  number?: number;
14
15
  salt?: string;
@@ -21,6 +22,23 @@ export interface Payload {
21
22
  salt: string;
22
23
  signature: string;
23
24
  }
25
+ export interface ServerSignaturePayload {
26
+ algorithm: Algorithm;
27
+ signature: string;
28
+ verificationData: string;
29
+ verified: boolean;
30
+ }
31
+ export interface ServerSignatureVerificationData {
32
+ classification?: string;
33
+ email?: string;
34
+ expire: number;
35
+ fields?: string[];
36
+ fieldsHash?: string;
37
+ reasons?: string[];
38
+ score?: number;
39
+ time: number;
40
+ verified: boolean;
41
+ }
24
42
  export interface Solution {
25
43
  number: number;
26
44
  took: number;
package/dist/helpers.d.ts CHANGED
@@ -2,7 +2,9 @@ import './crypto.js';
2
2
  import type { Algorithm } from './types.js';
3
3
  export declare const encoder: TextEncoder;
4
4
  export declare function ab2hex(ab: ArrayBuffer | Uint8Array): string;
5
- export declare function hash(algorithm: Algorithm, str: string): Promise<string>;
6
- export declare function hmac(algorithm: Algorithm, str: string, secret: string): Promise<string>;
5
+ export declare function hash(algorithm: Algorithm, data: ArrayBuffer | string): Promise<ArrayBuffer>;
6
+ export declare function hashHex(algorithm: Algorithm, data: ArrayBuffer | string): Promise<string>;
7
+ export declare function hmac(algorithm: Algorithm, data: ArrayBuffer | string, secret: string): Promise<ArrayBuffer>;
8
+ export declare function hmacHex(algorithm: Algorithm, data: ArrayBuffer | string, secret: string): Promise<string>;
7
9
  export declare function randomBytes(length: number): Uint8Array;
8
10
  export declare function randomInt(max: number): number;
package/dist/helpers.js CHANGED
@@ -6,15 +6,21 @@ export function ab2hex(ab) {
6
6
  .map((x) => x.toString(16).padStart(2, '0'))
7
7
  .join('');
8
8
  }
9
- export async function hash(algorithm, str) {
10
- return ab2hex(await crypto.subtle.digest(algorithm.toUpperCase(), encoder.encode(str)));
9
+ export async function hash(algorithm, data) {
10
+ return crypto.subtle.digest(algorithm.toUpperCase(), typeof data === 'string' ? encoder.encode(data) : new Uint8Array(data));
11
11
  }
12
- export async function hmac(algorithm, str, secret) {
12
+ export async function hashHex(algorithm, data) {
13
+ return ab2hex(await hash(algorithm, data));
14
+ }
15
+ export async function hmac(algorithm, data, secret) {
13
16
  const key = await crypto.subtle.importKey('raw', encoder.encode(secret), {
14
17
  name: 'HMAC',
15
18
  hash: algorithm,
16
19
  }, false, ['sign', 'verify']);
17
- return ab2hex(await crypto.subtle.sign('HMAC', key, encoder.encode(str)));
20
+ return crypto.subtle.sign('HMAC', key, typeof data === 'string' ? encoder.encode(data) : new Uint8Array(data));
21
+ }
22
+ export async function hmacHex(algorithm, data, secret) {
23
+ return ab2hex(await hmac(algorithm, data, secret));
18
24
  }
19
25
  export function randomBytes(length) {
20
26
  const ab = new Uint8Array(length);
package/dist/index.d.ts CHANGED
@@ -1,6 +1,10 @@
1
- import type { Challenge, ChallengeOptions, Payload, Solution } from './types.js';
1
+ import type { Challenge, ChallengeOptions, Payload, ServerSignaturePayload, ServerSignatureVerificationData, Solution } from './types.js';
2
2
  export declare function createChallenge(options: ChallengeOptions): Promise<Challenge>;
3
3
  export declare function verifySolution(payload: string | Payload, hmacKey: string): Promise<boolean>;
4
+ export declare function verifyServerSignature(payload: string | ServerSignaturePayload, hmacKey: string): Promise<{
5
+ verificationData: ServerSignatureVerificationData | null;
6
+ verified: boolean | null;
7
+ }>;
4
8
  export declare function solveChallenge(challenge: string, salt: string, algorithm?: string, max?: number, start?: number): {
5
9
  promise: Promise<Solution | null>;
6
10
  controller: AbortController;
@@ -8,8 +12,9 @@ export declare function solveChallenge(challenge: string, salt: string, algorith
8
12
  export declare function solveChallengeWorkers(workerScript: string | URL | (() => Worker), concurrency: number, challenge: string, salt: string, algorithm?: string, max?: number, startNumber?: number): Promise<Solution | null>;
9
13
  declare const _default: {
10
14
  createChallenge: typeof createChallenge;
11
- verifySolution: typeof verifySolution;
12
15
  solveChallenge: typeof solveChallenge;
13
16
  solveChallengeWorkers: typeof solveChallengeWorkers;
17
+ verifyServerSignature: typeof verifyServerSignature;
18
+ verifySolution: typeof verifySolution;
14
19
  };
15
20
  export default _default;
package/dist/index.js CHANGED
@@ -1,20 +1,20 @@
1
- import { ab2hex, encoder, hash, hmac, randomBytes, randomInt, } from './helpers.js';
1
+ import { ab2hex, hash, hashHex, hmacHex, randomBytes, randomInt, } from './helpers.js';
2
2
  const DEFAULT_MAX_NUMBER = 1e6;
3
3
  const DEFAULT_SALT_LEN = 12;
4
4
  const DEFAULT_ALG = 'SHA-256';
5
5
  export async function createChallenge(options) {
6
6
  const algorithm = options.algorithm || DEFAULT_ALG;
7
- const max = options.maxNumber || DEFAULT_MAX_NUMBER;
7
+ const maxnumber = options.maxnumber || options.maxNumber || DEFAULT_MAX_NUMBER;
8
8
  const saltLength = options.saltLength || DEFAULT_SALT_LEN;
9
9
  const salt = options.salt || ab2hex(randomBytes(saltLength));
10
- const number = options.number === void 0 ? randomInt(max) : options.number;
11
- const challenge = await hash(algorithm, salt + number);
10
+ const number = options.number === void 0 ? randomInt(maxnumber) : options.number;
11
+ const challenge = await hashHex(algorithm, salt + number);
12
12
  return {
13
13
  algorithm,
14
14
  challenge,
15
- max,
15
+ maxnumber,
16
16
  salt,
17
- signature: await hmac(algorithm, challenge, options.hmacKey),
17
+ signature: await hmacHex(algorithm, challenge, options.hmacKey),
18
18
  };
19
19
  }
20
20
  export async function verifySolution(payload, hmacKey) {
@@ -30,6 +30,38 @@ export async function verifySolution(payload, hmacKey) {
30
30
  return (check.challenge === payload.challenge &&
31
31
  check.signature === payload.signature);
32
32
  }
33
+ export async function verifyServerSignature(payload, hmacKey) {
34
+ if (typeof payload === 'string') {
35
+ payload = JSON.parse(atob(payload));
36
+ }
37
+ const signature = await hmacHex(payload.algorithm, await hash(payload.algorithm, payload.verificationData), hmacKey);
38
+ let verificationData = null;
39
+ try {
40
+ const params = new URLSearchParams(payload.verificationData);
41
+ verificationData = {
42
+ ...Object.fromEntries(params),
43
+ expire: parseInt(params.get('expire') || '0', 10),
44
+ fields: params.get('fields')?.split(','),
45
+ reasons: params.get('reasons')?.split(','),
46
+ score: params.get('score')
47
+ ? parseFloat(params.get('score') || '0')
48
+ : void 0,
49
+ time: parseInt(params.get('time') || '0', 10),
50
+ verified: params.get('verified') === 'true',
51
+ };
52
+ }
53
+ catch {
54
+ // noop
55
+ }
56
+ return {
57
+ verificationData,
58
+ verified: payload.verified === true &&
59
+ verificationData &&
60
+ verificationData.verified === true &&
61
+ verificationData.expire > Math.floor(Date.now() / 1000) &&
62
+ payload.signature === signature,
63
+ };
64
+ }
33
65
  export function solveChallenge(challenge, salt, algorithm = 'SHA-256', max = 1e6, start = 0) {
34
66
  const controller = new AbortController();
35
67
  const promise = new Promise((resolve, reject) => {
@@ -39,7 +71,7 @@ export function solveChallenge(challenge, salt, algorithm = 'SHA-256', max = 1e6
39
71
  resolve(null);
40
72
  }
41
73
  else {
42
- hashChallenge(salt, n, algorithm)
74
+ hashHex(algorithm, salt + n)
43
75
  .then((t) => {
44
76
  if (t === challenge) {
45
77
  resolve({
@@ -110,12 +142,10 @@ export async function solveChallengeWorkers(workerScript, concurrency, challenge
110
142
  }
111
143
  return solutions.find((solution) => !!solution) || null;
112
144
  }
113
- async function hashChallenge(salt, num, algorithm) {
114
- return ab2hex(await crypto.subtle.digest(algorithm.toUpperCase(), encoder.encode(salt + num)));
115
- }
116
145
  export default {
117
146
  createChallenge,
118
- verifySolution,
119
147
  solveChallenge,
120
148
  solveChallengeWorkers,
149
+ verifyServerSignature,
150
+ verifySolution,
121
151
  };
package/dist/types.d.ts CHANGED
@@ -2,13 +2,14 @@ export type Algorithm = 'SHA-1' | 'SHA-256' | 'SHA-512';
2
2
  export interface Challenge {
3
3
  algorithm: Algorithm;
4
4
  challenge: string;
5
- max?: number;
5
+ maxnumber?: number;
6
6
  salt: string;
7
7
  signature: string;
8
8
  }
9
9
  export interface ChallengeOptions {
10
10
  algorithm?: Algorithm;
11
11
  hmacKey: string;
12
+ maxnumber?: number;
12
13
  maxNumber?: number;
13
14
  number?: number;
14
15
  salt?: string;
@@ -21,6 +22,23 @@ export interface Payload {
21
22
  salt: string;
22
23
  signature: string;
23
24
  }
25
+ export interface ServerSignaturePayload {
26
+ algorithm: Algorithm;
27
+ signature: string;
28
+ verificationData: string;
29
+ verified: boolean;
30
+ }
31
+ export interface ServerSignatureVerificationData {
32
+ classification?: string;
33
+ email?: string;
34
+ expire: number;
35
+ fields?: string[];
36
+ fieldsHash?: string;
37
+ reasons?: string[];
38
+ score?: number;
39
+ time: number;
40
+ verified: boolean;
41
+ }
24
42
  export interface Solution {
25
43
  number: number;
26
44
  took: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "altcha-lib",
3
- "version": "0.1.5",
3
+ "version": "0.2.0",
4
4
  "description": "A library for creating and verifying ALTCHA challenges for Node.js, Bun and Deno.",
5
5
  "author": "Daniel Regeci",
6
6
  "license": "MIT",