qumra-pos-auth 0.1.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 +113 -0
- package/dist/SignatureVerifier-ueydiL4D.d.cts +25 -0
- package/dist/SignatureVerifier-ueydiL4D.d.ts +25 -0
- package/dist/chunk-SRPTNNYT.js +127 -0
- package/dist/chunk-SRPTNNYT.js.map +1 -0
- package/dist/crypto.cjs +133 -0
- package/dist/crypto.cjs.map +1 -0
- package/dist/crypto.d.cts +46 -0
- package/dist/crypto.d.ts +46 -0
- package/dist/crypto.js +3 -0
- package/dist/crypto.js.map +1 -0
- package/dist/index.cjs +706 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +479 -0
- package/dist/index.d.ts +479 -0
- package/dist/index.js +566 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# qumra-pos-auth
|
|
2
|
+
|
|
3
|
+
Offline-capable identity for POS and local-first apps. Pure-JS, cross-platform
|
|
4
|
+
(**React Native, Web, Desktop, Node**), and **completely independent of any storage
|
|
5
|
+
layer** — you wire it to your data/sync code through one callback.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i qumra-pos-auth
|
|
11
|
+
# or
|
|
12
|
+
yarn add qumra-pos-auth
|
|
13
|
+
# or
|
|
14
|
+
pnpm add qumra-pos-auth
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Runtime deps are only `@noble/hashes` (argon2id) and `@noble/curves` (Ed25519) — both
|
|
18
|
+
pure-JS, no native build on any platform.
|
|
19
|
+
|
|
20
|
+
## What it does
|
|
21
|
+
|
|
22
|
+
- **Three operating modes** — `online` (valid tokens, syncing) · `offline-degraded`
|
|
23
|
+
(was online, token expired / no network, still runs on a local session) ·
|
|
24
|
+
`offline-activated` (an Ed25519-signed activation key, verified locally, server never
|
|
25
|
+
contacted) · `unactivated`.
|
|
26
|
+
- **`active → grace → locked` state machine** — data is **never** erased; the worst case
|
|
27
|
+
is that new sales lock. Grace still sells (with a warning); recovery = any successful
|
|
28
|
+
online contact.
|
|
29
|
+
- **Tokens** — OAuth token vault with **single-flight rotating refresh** (15-min access /
|
|
30
|
+
30-day refresh by default). `getValidToken()` never throws — `null` means *pause*, not
|
|
31
|
+
logout.
|
|
32
|
+
- **Local cashier PIN** — argon2id verified against a **synced** hash (same code online &
|
|
33
|
+
offline), with per-cashier lockout.
|
|
34
|
+
- **Offline activation** — parses `payload.signature`, verifies the Ed25519 signature
|
|
35
|
+
against a pinned public key, then device + expiry.
|
|
36
|
+
|
|
37
|
+
## Quick start — the library takes a token from you
|
|
38
|
+
|
|
39
|
+
This package has **zero endpoint code**. *You* do login/refresh (REST, GraphQL, anything)
|
|
40
|
+
and hand it the tokens through the `AuthApi` port. It never knows your server.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { createAuth, type AuthApi } from "qumra-pos-auth";
|
|
44
|
+
|
|
45
|
+
// YOU own the network. The library only calls these two methods.
|
|
46
|
+
const api: AuthApi = {
|
|
47
|
+
async login({ username, password }) {
|
|
48
|
+
const t = await myBackend.login(username, password); // your REST/GraphQL call
|
|
49
|
+
return { accessToken: t.access, refreshToken: t.refresh, expiresInSec: 900, refreshExpiresInSec: 2592000 };
|
|
50
|
+
},
|
|
51
|
+
async refresh(refreshToken) {
|
|
52
|
+
const t = await myBackend.refresh(refreshToken); // your call
|
|
53
|
+
return { accessToken: t.access, refreshToken: t.refresh, expiresInSec: 900, refreshExpiresInSec: 2592000 };
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const auth = createAuth({ api, vault, directory, activationPublicKey });
|
|
58
|
+
await auth.service.init();
|
|
59
|
+
await auth.service.onlineLogin({ username, password });
|
|
60
|
+
await auth.service.cashierLogin("cashier-1", "1234");
|
|
61
|
+
auth.service.onStatusChange((s) => renderBar(s)); // { mode, access, cashier, ... }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Even simpler** — if you manage tokens 100% outside and don't need the vault/refresh
|
|
65
|
+
logic, skip the auth token layer entirely and just feed your token to the sync engine:
|
|
66
|
+
`new SyncEngine(storage, { getAuthToken: () => myToken })`.
|
|
67
|
+
|
|
68
|
+
## The one seam to your storage/sync
|
|
69
|
+
|
|
70
|
+
`qumra-pos-auth` never imports your storage. Wire them in the app with a single callback.
|
|
71
|
+
Since sync engines usually poll a **synchronous** token getter, use `getAccessTokenSync`
|
|
72
|
+
(an in-memory cache kept warm by the async refresh loop):
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
// e.g. with qumra-pos-storage's SyncEngine:
|
|
76
|
+
new SyncEngine(storage, { getAuthToken: () => auth.getAccessTokenSync() });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- `getAccessTokenSync(): string | null` — synchronous, no I/O. `null` ⇒ sync pauses.
|
|
80
|
+
- `getValidToken(): Promise<string | null>` — async variant (triggers refresh if needed).
|
|
81
|
+
|
|
82
|
+
## Ports you implement (per platform)
|
|
83
|
+
|
|
84
|
+
Everything I/O is an injectable port, so the same core runs everywhere:
|
|
85
|
+
|
|
86
|
+
- **`AuthVault`** — secure persistence. Back it with Electron `safeStorage`, browser
|
|
87
|
+
IndexedDB, or RN Keychain. (`saveTokens/loadTokens`, `…Session`, `…Activation`, `…Meta`.)
|
|
88
|
+
- **`CashierDirectory`** — `listCashiers()`; return the synced cashier rows (id, name,
|
|
89
|
+
`pinHash`, active) from your storage.
|
|
90
|
+
- **`AuthApi`** — you implement `login`/`refresh` with your own fetch/GraphQL. The library
|
|
91
|
+
never knows your endpoint; it just receives the tokens you return.
|
|
92
|
+
- **`PinHasher` / `SignatureVerifier`** — default to the bundled `NoblePinHasher`
|
|
93
|
+
(argon2id, PHC format) and `NobleSignatureVerifier` (Ed25519) from `qumra-pos-auth/crypto`;
|
|
94
|
+
swap for a native impl on RN if you want.
|
|
95
|
+
|
|
96
|
+
## Error semantics for your `AuthApi`
|
|
97
|
+
|
|
98
|
+
Throw the right error so the state machine behaves correctly:
|
|
99
|
+
|
|
100
|
+
- Network / server-down / 5xx → `throw new AuthNetworkError(...)` — retryable, **keeps** the
|
|
101
|
+
tokens, sync just pauses.
|
|
102
|
+
- Rejected (bad credentials / refresh revoked) → `throw new AuthRejectedError(...)` —
|
|
103
|
+
permanent, **clears** the tokens.
|
|
104
|
+
|
|
105
|
+
Both are exported from `qumra-pos-auth`.
|
|
106
|
+
|
|
107
|
+
## Publishing
|
|
108
|
+
|
|
109
|
+
Ships dual **ESM + CJS + `.d.ts`** (built with tsup). Crypto is also available at the
|
|
110
|
+
`qumra-pos-auth/crypto` subpath. To publish under a different name, change `name` in
|
|
111
|
+
`package.json`.
|
|
112
|
+
|
|
113
|
+
MIT.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* منفذ تعمية الـ PIN. الافتراضي argon2id (NoblePinHasher) — pure JS يشتغل على كل
|
|
3
|
+
* منصة من غير native build. قابل للحقن علشان RN يقدر يستخدم impl أصلي أسرع،
|
|
4
|
+
* والاختبارات تستخدم fake سريع.
|
|
5
|
+
*
|
|
6
|
+
* صيغة الـ encoded لازم تطابق اللي السيرفر بينتجه (PHC القياسية) علشان الـ hash
|
|
7
|
+
* المتزامن يتحقق محلياً.
|
|
8
|
+
*/
|
|
9
|
+
interface PinHasher {
|
|
10
|
+
/** يعمّي PIN وبيرجّع string بصيغة PHC (فيه الـ salt والمعاملات). */
|
|
11
|
+
hash(pin: string): Promise<string>;
|
|
12
|
+
/** يتحقق من PIN ضد encoded string — مقارنة بزمن ثابت. */
|
|
13
|
+
verify(pin: string, encoded: string): Promise<boolean>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* منفذ التحقق من التوقيع (Ed25519) لمفاتيح التفعيل الأوفلاين. الافتراضي
|
|
18
|
+
* NobleSignatureVerifier (pure JS). الجهاز عنده المفتاح العام لقمرة مثبّت،
|
|
19
|
+
* وبيتحقق من المفتاح محلياً بدون أي سيرفر.
|
|
20
|
+
*/
|
|
21
|
+
interface SignatureVerifier {
|
|
22
|
+
verify(message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): Promise<boolean>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type { PinHasher as P, SignatureVerifier as S };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* منفذ تعمية الـ PIN. الافتراضي argon2id (NoblePinHasher) — pure JS يشتغل على كل
|
|
3
|
+
* منصة من غير native build. قابل للحقن علشان RN يقدر يستخدم impl أصلي أسرع،
|
|
4
|
+
* والاختبارات تستخدم fake سريع.
|
|
5
|
+
*
|
|
6
|
+
* صيغة الـ encoded لازم تطابق اللي السيرفر بينتجه (PHC القياسية) علشان الـ hash
|
|
7
|
+
* المتزامن يتحقق محلياً.
|
|
8
|
+
*/
|
|
9
|
+
interface PinHasher {
|
|
10
|
+
/** يعمّي PIN وبيرجّع string بصيغة PHC (فيه الـ salt والمعاملات). */
|
|
11
|
+
hash(pin: string): Promise<string>;
|
|
12
|
+
/** يتحقق من PIN ضد encoded string — مقارنة بزمن ثابت. */
|
|
13
|
+
verify(pin: string, encoded: string): Promise<boolean>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* منفذ التحقق من التوقيع (Ed25519) لمفاتيح التفعيل الأوفلاين. الافتراضي
|
|
18
|
+
* NobleSignatureVerifier (pure JS). الجهاز عنده المفتاح العام لقمرة مثبّت،
|
|
19
|
+
* وبيتحقق من المفتاح محلياً بدون أي سيرفر.
|
|
20
|
+
*/
|
|
21
|
+
interface SignatureVerifier {
|
|
22
|
+
verify(message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): Promise<boolean>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type { PinHasher as P, SignatureVerifier as S };
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { argon2idAsync } from '@noble/hashes/argon2';
|
|
2
|
+
import { randomBytes, utf8ToBytes } from '@noble/hashes/utils';
|
|
3
|
+
import { ed25519 } from '@noble/curves/ed25519';
|
|
4
|
+
|
|
5
|
+
// src/crypto/base64.ts
|
|
6
|
+
var STD = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
7
|
+
var URL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
8
|
+
function encode(bytes, alphabet) {
|
|
9
|
+
let out = "";
|
|
10
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
11
|
+
const b0 = bytes[i];
|
|
12
|
+
const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
13
|
+
const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
14
|
+
const triple = b0 << 16 | b1 << 8 | b2;
|
|
15
|
+
out += alphabet[triple >> 18 & 63];
|
|
16
|
+
out += alphabet[triple >> 12 & 63];
|
|
17
|
+
if (i + 1 < bytes.length) out += alphabet[triple >> 6 & 63];
|
|
18
|
+
if (i + 2 < bytes.length) out += alphabet[triple & 63];
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
function decode(str, alphabet) {
|
|
23
|
+
const lookup = new Int16Array(128).fill(-1);
|
|
24
|
+
for (let i = 0; i < alphabet.length; i++) lookup[alphabet.charCodeAt(i)] = i;
|
|
25
|
+
const clean = str.replace(/[=\s]/g, "");
|
|
26
|
+
const out = new Uint8Array(Math.floor(clean.length * 6 / 8));
|
|
27
|
+
let bits = 0;
|
|
28
|
+
let value = 0;
|
|
29
|
+
let o = 0;
|
|
30
|
+
for (let i = 0; i < clean.length; i++) {
|
|
31
|
+
const c = clean.charCodeAt(i);
|
|
32
|
+
const idx = c < 128 ? lookup[c] : -1;
|
|
33
|
+
if (idx < 0) throw new Error("base64: \u062D\u0631\u0641 \u063A\u064A\u0631 \u0635\u0627\u0644\u062D");
|
|
34
|
+
value = value << 6 | idx;
|
|
35
|
+
bits += 6;
|
|
36
|
+
if (bits >= 8) {
|
|
37
|
+
bits -= 8;
|
|
38
|
+
out[o++] = value >> bits & 255;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
var b64 = {
|
|
44
|
+
encode: (b) => encode(b, STD),
|
|
45
|
+
decode: (s) => decode(s, STD)
|
|
46
|
+
};
|
|
47
|
+
var b64url = {
|
|
48
|
+
encode: (b) => encode(b, URL),
|
|
49
|
+
decode: (s) => decode(s, URL)
|
|
50
|
+
};
|
|
51
|
+
var DEFAULT_ARGON2_PARAMS = {
|
|
52
|
+
t: 3,
|
|
53
|
+
m: 64 * 1024,
|
|
54
|
+
p: 1,
|
|
55
|
+
dkLen: 32
|
|
56
|
+
};
|
|
57
|
+
var V = 19;
|
|
58
|
+
function constantTimeEqual(a, b) {
|
|
59
|
+
if (a.length !== b.length) return false;
|
|
60
|
+
let diff = 0;
|
|
61
|
+
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
|
62
|
+
return diff === 0;
|
|
63
|
+
}
|
|
64
|
+
var NoblePinHasher = class {
|
|
65
|
+
params;
|
|
66
|
+
constructor(params = DEFAULT_ARGON2_PARAMS) {
|
|
67
|
+
this.params = params;
|
|
68
|
+
}
|
|
69
|
+
async hash(pin) {
|
|
70
|
+
const salt = randomBytes(16);
|
|
71
|
+
const { t, m, p, dkLen } = this.params;
|
|
72
|
+
const out = await argon2idAsync(utf8ToBytes(pin), salt, { t, m, p, dkLen });
|
|
73
|
+
return `$argon2id$v=${V}$m=${m},t=${t},p=${p}$${b64.encode(salt)}$${b64.encode(out)}`;
|
|
74
|
+
}
|
|
75
|
+
async verify(pin, encoded) {
|
|
76
|
+
const parsed = parsePhc(encoded);
|
|
77
|
+
if (!parsed) return false;
|
|
78
|
+
const out = await argon2idAsync(utf8ToBytes(pin), parsed.salt, {
|
|
79
|
+
t: parsed.t,
|
|
80
|
+
m: parsed.m,
|
|
81
|
+
p: parsed.p,
|
|
82
|
+
dkLen: parsed.hash.length
|
|
83
|
+
});
|
|
84
|
+
return constantTimeEqual(out, parsed.hash);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
function parsePhc(encoded) {
|
|
88
|
+
const parts = encoded.split("$");
|
|
89
|
+
if (parts.length !== 6) return null;
|
|
90
|
+
if (parts[1] !== "argon2id") return null;
|
|
91
|
+
const paramStr = parts[3];
|
|
92
|
+
const saltStr = parts[4];
|
|
93
|
+
const hashStr = parts[5];
|
|
94
|
+
if (paramStr === void 0 || saltStr === void 0 || hashStr === void 0) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const params = /* @__PURE__ */ new Map();
|
|
98
|
+
for (const kv of paramStr.split(",")) {
|
|
99
|
+
const [k, v] = kv.split("=");
|
|
100
|
+
if (k === void 0 || v === void 0) return null;
|
|
101
|
+
const n = Number(v);
|
|
102
|
+
if (!Number.isFinite(n)) return null;
|
|
103
|
+
params.set(k, n);
|
|
104
|
+
}
|
|
105
|
+
const m = params.get("m");
|
|
106
|
+
const t = params.get("t");
|
|
107
|
+
const p = params.get("p");
|
|
108
|
+
if (m === void 0 || t === void 0 || p === void 0) return null;
|
|
109
|
+
try {
|
|
110
|
+
return { m, t, p, salt: b64.decode(saltStr), hash: b64.decode(hashStr) };
|
|
111
|
+
} catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
var NobleSignatureVerifier = class {
|
|
116
|
+
async verify(message, signature, publicKey) {
|
|
117
|
+
try {
|
|
118
|
+
return ed25519.verify(signature, message, publicKey);
|
|
119
|
+
} catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export { DEFAULT_ARGON2_PARAMS, NoblePinHasher, NobleSignatureVerifier, b64, b64url };
|
|
126
|
+
//# sourceMappingURL=chunk-SRPTNNYT.js.map
|
|
127
|
+
//# sourceMappingURL=chunk-SRPTNNYT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/crypto/base64.ts","../src/crypto/NoblePinHasher.ts","../src/crypto/NobleSignatureVerifier.ts"],"names":[],"mappings":"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","file":"chunk-SRPTNNYT.js","sourcesContent":["/**\n * Base64 محمول (standard + url-safe) بدون الاعتماد على Buffer أو btoa — علشان نفس\n * الكود يشتغل في Node و المتصفح و React Native. الترميز بدون padding افتراضياً\n * (صيغة PHC و JWT كده)، وفك الترميز بيتقبّل بوجود أو غياب الـ padding.\n */\nconst STD = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\nconst URL = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\n\nfunction encode(bytes: Uint8Array, alphabet: string): string {\n let out = \"\";\n for (let i = 0; i < bytes.length; i += 3) {\n const b0 = bytes[i]!;\n const b1 = i + 1 < bytes.length ? bytes[i + 1]! : 0;\n const b2 = i + 2 < bytes.length ? bytes[i + 2]! : 0;\n const triple = (b0 << 16) | (b1 << 8) | b2;\n out += alphabet[(triple >> 18) & 63];\n out += alphabet[(triple >> 12) & 63];\n if (i + 1 < bytes.length) out += alphabet[(triple >> 6) & 63];\n if (i + 2 < bytes.length) out += alphabet[triple & 63];\n }\n return out;\n}\n\nfunction decode(str: string, alphabet: string): Uint8Array {\n const lookup = new Int16Array(128).fill(-1);\n for (let i = 0; i < alphabet.length; i++) lookup[alphabet.charCodeAt(i)] = i;\n // شيل الـ padding والمسافات\n const clean = str.replace(/[=\\s]/g, \"\");\n const out = new Uint8Array(Math.floor((clean.length * 6) / 8));\n let bits = 0;\n let value = 0;\n let o = 0;\n for (let i = 0; i < clean.length; i++) {\n const c = clean.charCodeAt(i);\n const idx = c < 128 ? lookup[c]! : -1;\n if (idx < 0) throw new Error(\"base64: حرف غير صالح\");\n value = (value << 6) | idx;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n out[o++] = (value >> bits) & 0xff;\n }\n }\n return out;\n}\n\nexport const b64 = {\n encode: (b: Uint8Array): string => encode(b, STD),\n decode: (s: string): Uint8Array => decode(s, STD),\n};\n\nexport const b64url = {\n encode: (b: Uint8Array): string => encode(b, URL),\n decode: (s: string): Uint8Array => decode(s, URL),\n};\n","import { argon2idAsync } from \"@noble/hashes/argon2\";\nimport { randomBytes, utf8ToBytes } from \"@noble/hashes/utils\";\nimport type { PinHasher } from \"../ports/PinHasher\";\nimport { b64 } from \"./base64\";\n\n/**\n * الافتراضي لتعمية الـ PIN: argon2id بصيغة PHC القياسية، pure JS من @noble/hashes.\n * نفس الصيغة اللي السيرفر بينتجها فالـ hash المتزامن يتحقق محلياً (نفس الكود\n * أونلاين وأوفلاين).\n *\n * صيغة الناتج: $argon2id$v=19$m=<m>,t=<t>,p=<p>$<saltB64>$<hashB64>\n */\nexport interface Argon2Params {\n /** iterations (t). */\n t: number;\n /** memory بالـ KiB (m). */\n m: number;\n /** parallelism (p). */\n p: number;\n /** طول الـ hash بالبايت. */\n dkLen: number;\n}\n\n/** إعداد متزن للأجهزة (64MiB, 3 مرات) — يطابق الافتراضي الشائع للسيرفر. */\nexport const DEFAULT_ARGON2_PARAMS: Argon2Params = {\n t: 3,\n m: 64 * 1024,\n p: 1,\n dkLen: 32,\n};\n\nconst V = 19; // argon2 version 1.3\n\nfunction constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a[i]! ^ b[i]!;\n return diff === 0;\n}\n\nexport class NoblePinHasher implements PinHasher {\n private readonly params: Argon2Params;\n\n constructor(params: Argon2Params = DEFAULT_ARGON2_PARAMS) {\n this.params = params;\n }\n\n async hash(pin: string): Promise<string> {\n const salt = randomBytes(16);\n const { t, m, p, dkLen } = this.params;\n const out = await argon2idAsync(utf8ToBytes(pin), salt, { t, m, p, dkLen });\n return `$argon2id$v=${V}$m=${m},t=${t},p=${p}$${b64.encode(salt)}$${b64.encode(out)}`;\n }\n\n async verify(pin: string, encoded: string): Promise<boolean> {\n const parsed = parsePhc(encoded);\n if (!parsed) return false;\n const out = await argon2idAsync(utf8ToBytes(pin), parsed.salt, {\n t: parsed.t,\n m: parsed.m,\n p: parsed.p,\n dkLen: parsed.hash.length,\n });\n return constantTimeEqual(out, parsed.hash);\n }\n}\n\ninterface Phc {\n m: number;\n t: number;\n p: number;\n salt: Uint8Array;\n hash: Uint8Array;\n}\n\n/** يفكّ صيغة PHC لـ argon2id. بيرجّع null لو مش متعرّف عليها (نتيجتها verify=false). */\nfunction parsePhc(encoded: string): Phc | null {\n // $argon2id$v=19$m=..,t=..,p=..$salt$hash\n const parts = encoded.split(\"$\");\n // [\"\", \"argon2id\", \"v=19\", \"m=..,t=..,p=..\", \"<salt>\", \"<hash>\"]\n if (parts.length !== 6) return null;\n if (parts[1] !== \"argon2id\") return null;\n const paramStr = parts[3];\n const saltStr = parts[4];\n const hashStr = parts[5];\n if (paramStr === undefined || saltStr === undefined || hashStr === undefined) {\n return null;\n }\n const params = new Map<string, number>();\n for (const kv of paramStr.split(\",\")) {\n const [k, v] = kv.split(\"=\");\n if (k === undefined || v === undefined) return null;\n const n = Number(v);\n if (!Number.isFinite(n)) return null;\n params.set(k, n);\n }\n const m = params.get(\"m\");\n const t = params.get(\"t\");\n const p = params.get(\"p\");\n if (m === undefined || t === undefined || p === undefined) return null;\n try {\n return { m, t, p, salt: b64.decode(saltStr), hash: b64.decode(hashStr) };\n } catch {\n return null;\n }\n}\n","import { ed25519 } from \"@noble/curves/ed25519\";\nimport type { SignatureVerifier } from \"../ports/SignatureVerifier\";\n\n/**\n * الافتراضي للتحقق من التوقيع: Ed25519 من @noble/curves (pure JS). بيتحقق محلياً\n * من مفاتيح التفعيل الموقّعة بمفتاح قمرة الخاص — بدون أي نداء سيرفر.\n */\nexport class NobleSignatureVerifier implements SignatureVerifier {\n async verify(\n message: Uint8Array,\n signature: Uint8Array,\n publicKey: Uint8Array,\n ): Promise<boolean> {\n try {\n return ed25519.verify(signature, message, publicKey);\n } catch {\n // توقيع/مفتاح بحجم غلط → اعتبره غير صالح بدل ما نرمي.\n return false;\n }\n }\n}\n"]}
|
package/dist/crypto.cjs
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var argon2 = require('@noble/hashes/argon2');
|
|
4
|
+
var utils = require('@noble/hashes/utils');
|
|
5
|
+
var ed25519 = require('@noble/curves/ed25519');
|
|
6
|
+
|
|
7
|
+
// src/crypto/base64.ts
|
|
8
|
+
var STD = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
9
|
+
var URL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
10
|
+
function encode(bytes, alphabet) {
|
|
11
|
+
let out = "";
|
|
12
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
13
|
+
const b0 = bytes[i];
|
|
14
|
+
const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
15
|
+
const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
16
|
+
const triple = b0 << 16 | b1 << 8 | b2;
|
|
17
|
+
out += alphabet[triple >> 18 & 63];
|
|
18
|
+
out += alphabet[triple >> 12 & 63];
|
|
19
|
+
if (i + 1 < bytes.length) out += alphabet[triple >> 6 & 63];
|
|
20
|
+
if (i + 2 < bytes.length) out += alphabet[triple & 63];
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
function decode(str, alphabet) {
|
|
25
|
+
const lookup = new Int16Array(128).fill(-1);
|
|
26
|
+
for (let i = 0; i < alphabet.length; i++) lookup[alphabet.charCodeAt(i)] = i;
|
|
27
|
+
const clean = str.replace(/[=\s]/g, "");
|
|
28
|
+
const out = new Uint8Array(Math.floor(clean.length * 6 / 8));
|
|
29
|
+
let bits = 0;
|
|
30
|
+
let value = 0;
|
|
31
|
+
let o = 0;
|
|
32
|
+
for (let i = 0; i < clean.length; i++) {
|
|
33
|
+
const c = clean.charCodeAt(i);
|
|
34
|
+
const idx = c < 128 ? lookup[c] : -1;
|
|
35
|
+
if (idx < 0) throw new Error("base64: \u062D\u0631\u0641 \u063A\u064A\u0631 \u0635\u0627\u0644\u062D");
|
|
36
|
+
value = value << 6 | idx;
|
|
37
|
+
bits += 6;
|
|
38
|
+
if (bits >= 8) {
|
|
39
|
+
bits -= 8;
|
|
40
|
+
out[o++] = value >> bits & 255;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
var b64 = {
|
|
46
|
+
encode: (b) => encode(b, STD),
|
|
47
|
+
decode: (s) => decode(s, STD)
|
|
48
|
+
};
|
|
49
|
+
var b64url = {
|
|
50
|
+
encode: (b) => encode(b, URL),
|
|
51
|
+
decode: (s) => decode(s, URL)
|
|
52
|
+
};
|
|
53
|
+
var DEFAULT_ARGON2_PARAMS = {
|
|
54
|
+
t: 3,
|
|
55
|
+
m: 64 * 1024,
|
|
56
|
+
p: 1,
|
|
57
|
+
dkLen: 32
|
|
58
|
+
};
|
|
59
|
+
var V = 19;
|
|
60
|
+
function constantTimeEqual(a, b) {
|
|
61
|
+
if (a.length !== b.length) return false;
|
|
62
|
+
let diff = 0;
|
|
63
|
+
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
|
64
|
+
return diff === 0;
|
|
65
|
+
}
|
|
66
|
+
var NoblePinHasher = class {
|
|
67
|
+
params;
|
|
68
|
+
constructor(params = DEFAULT_ARGON2_PARAMS) {
|
|
69
|
+
this.params = params;
|
|
70
|
+
}
|
|
71
|
+
async hash(pin) {
|
|
72
|
+
const salt = utils.randomBytes(16);
|
|
73
|
+
const { t, m, p, dkLen } = this.params;
|
|
74
|
+
const out = await argon2.argon2idAsync(utils.utf8ToBytes(pin), salt, { t, m, p, dkLen });
|
|
75
|
+
return `$argon2id$v=${V}$m=${m},t=${t},p=${p}$${b64.encode(salt)}$${b64.encode(out)}`;
|
|
76
|
+
}
|
|
77
|
+
async verify(pin, encoded) {
|
|
78
|
+
const parsed = parsePhc(encoded);
|
|
79
|
+
if (!parsed) return false;
|
|
80
|
+
const out = await argon2.argon2idAsync(utils.utf8ToBytes(pin), parsed.salt, {
|
|
81
|
+
t: parsed.t,
|
|
82
|
+
m: parsed.m,
|
|
83
|
+
p: parsed.p,
|
|
84
|
+
dkLen: parsed.hash.length
|
|
85
|
+
});
|
|
86
|
+
return constantTimeEqual(out, parsed.hash);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
function parsePhc(encoded) {
|
|
90
|
+
const parts = encoded.split("$");
|
|
91
|
+
if (parts.length !== 6) return null;
|
|
92
|
+
if (parts[1] !== "argon2id") return null;
|
|
93
|
+
const paramStr = parts[3];
|
|
94
|
+
const saltStr = parts[4];
|
|
95
|
+
const hashStr = parts[5];
|
|
96
|
+
if (paramStr === void 0 || saltStr === void 0 || hashStr === void 0) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const params = /* @__PURE__ */ new Map();
|
|
100
|
+
for (const kv of paramStr.split(",")) {
|
|
101
|
+
const [k, v] = kv.split("=");
|
|
102
|
+
if (k === void 0 || v === void 0) return null;
|
|
103
|
+
const n = Number(v);
|
|
104
|
+
if (!Number.isFinite(n)) return null;
|
|
105
|
+
params.set(k, n);
|
|
106
|
+
}
|
|
107
|
+
const m = params.get("m");
|
|
108
|
+
const t = params.get("t");
|
|
109
|
+
const p = params.get("p");
|
|
110
|
+
if (m === void 0 || t === void 0 || p === void 0) return null;
|
|
111
|
+
try {
|
|
112
|
+
return { m, t, p, salt: b64.decode(saltStr), hash: b64.decode(hashStr) };
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
var NobleSignatureVerifier = class {
|
|
118
|
+
async verify(message, signature, publicKey) {
|
|
119
|
+
try {
|
|
120
|
+
return ed25519.ed25519.verify(signature, message, publicKey);
|
|
121
|
+
} catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
exports.DEFAULT_ARGON2_PARAMS = DEFAULT_ARGON2_PARAMS;
|
|
128
|
+
exports.NoblePinHasher = NoblePinHasher;
|
|
129
|
+
exports.NobleSignatureVerifier = NobleSignatureVerifier;
|
|
130
|
+
exports.b64 = b64;
|
|
131
|
+
exports.b64url = b64url;
|
|
132
|
+
//# sourceMappingURL=crypto.cjs.map
|
|
133
|
+
//# sourceMappingURL=crypto.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/crypto/base64.ts","../src/crypto/NoblePinHasher.ts","../src/crypto/NobleSignatureVerifier.ts"],"names":["randomBytes","argon2idAsync","utf8ToBytes","ed25519"],"mappings":"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","file":"crypto.cjs","sourcesContent":["/**\n * Base64 محمول (standard + url-safe) بدون الاعتماد على Buffer أو btoa — علشان نفس\n * الكود يشتغل في Node و المتصفح و React Native. الترميز بدون padding افتراضياً\n * (صيغة PHC و JWT كده)، وفك الترميز بيتقبّل بوجود أو غياب الـ padding.\n */\nconst STD = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\nconst URL = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\n\nfunction encode(bytes: Uint8Array, alphabet: string): string {\n let out = \"\";\n for (let i = 0; i < bytes.length; i += 3) {\n const b0 = bytes[i]!;\n const b1 = i + 1 < bytes.length ? bytes[i + 1]! : 0;\n const b2 = i + 2 < bytes.length ? bytes[i + 2]! : 0;\n const triple = (b0 << 16) | (b1 << 8) | b2;\n out += alphabet[(triple >> 18) & 63];\n out += alphabet[(triple >> 12) & 63];\n if (i + 1 < bytes.length) out += alphabet[(triple >> 6) & 63];\n if (i + 2 < bytes.length) out += alphabet[triple & 63];\n }\n return out;\n}\n\nfunction decode(str: string, alphabet: string): Uint8Array {\n const lookup = new Int16Array(128).fill(-1);\n for (let i = 0; i < alphabet.length; i++) lookup[alphabet.charCodeAt(i)] = i;\n // شيل الـ padding والمسافات\n const clean = str.replace(/[=\\s]/g, \"\");\n const out = new Uint8Array(Math.floor((clean.length * 6) / 8));\n let bits = 0;\n let value = 0;\n let o = 0;\n for (let i = 0; i < clean.length; i++) {\n const c = clean.charCodeAt(i);\n const idx = c < 128 ? lookup[c]! : -1;\n if (idx < 0) throw new Error(\"base64: حرف غير صالح\");\n value = (value << 6) | idx;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n out[o++] = (value >> bits) & 0xff;\n }\n }\n return out;\n}\n\nexport const b64 = {\n encode: (b: Uint8Array): string => encode(b, STD),\n decode: (s: string): Uint8Array => decode(s, STD),\n};\n\nexport const b64url = {\n encode: (b: Uint8Array): string => encode(b, URL),\n decode: (s: string): Uint8Array => decode(s, URL),\n};\n","import { argon2idAsync } from \"@noble/hashes/argon2\";\nimport { randomBytes, utf8ToBytes } from \"@noble/hashes/utils\";\nimport type { PinHasher } from \"../ports/PinHasher\";\nimport { b64 } from \"./base64\";\n\n/**\n * الافتراضي لتعمية الـ PIN: argon2id بصيغة PHC القياسية، pure JS من @noble/hashes.\n * نفس الصيغة اللي السيرفر بينتجها فالـ hash المتزامن يتحقق محلياً (نفس الكود\n * أونلاين وأوفلاين).\n *\n * صيغة الناتج: $argon2id$v=19$m=<m>,t=<t>,p=<p>$<saltB64>$<hashB64>\n */\nexport interface Argon2Params {\n /** iterations (t). */\n t: number;\n /** memory بالـ KiB (m). */\n m: number;\n /** parallelism (p). */\n p: number;\n /** طول الـ hash بالبايت. */\n dkLen: number;\n}\n\n/** إعداد متزن للأجهزة (64MiB, 3 مرات) — يطابق الافتراضي الشائع للسيرفر. */\nexport const DEFAULT_ARGON2_PARAMS: Argon2Params = {\n t: 3,\n m: 64 * 1024,\n p: 1,\n dkLen: 32,\n};\n\nconst V = 19; // argon2 version 1.3\n\nfunction constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a[i]! ^ b[i]!;\n return diff === 0;\n}\n\nexport class NoblePinHasher implements PinHasher {\n private readonly params: Argon2Params;\n\n constructor(params: Argon2Params = DEFAULT_ARGON2_PARAMS) {\n this.params = params;\n }\n\n async hash(pin: string): Promise<string> {\n const salt = randomBytes(16);\n const { t, m, p, dkLen } = this.params;\n const out = await argon2idAsync(utf8ToBytes(pin), salt, { t, m, p, dkLen });\n return `$argon2id$v=${V}$m=${m},t=${t},p=${p}$${b64.encode(salt)}$${b64.encode(out)}`;\n }\n\n async verify(pin: string, encoded: string): Promise<boolean> {\n const parsed = parsePhc(encoded);\n if (!parsed) return false;\n const out = await argon2idAsync(utf8ToBytes(pin), parsed.salt, {\n t: parsed.t,\n m: parsed.m,\n p: parsed.p,\n dkLen: parsed.hash.length,\n });\n return constantTimeEqual(out, parsed.hash);\n }\n}\n\ninterface Phc {\n m: number;\n t: number;\n p: number;\n salt: Uint8Array;\n hash: Uint8Array;\n}\n\n/** يفكّ صيغة PHC لـ argon2id. بيرجّع null لو مش متعرّف عليها (نتيجتها verify=false). */\nfunction parsePhc(encoded: string): Phc | null {\n // $argon2id$v=19$m=..,t=..,p=..$salt$hash\n const parts = encoded.split(\"$\");\n // [\"\", \"argon2id\", \"v=19\", \"m=..,t=..,p=..\", \"<salt>\", \"<hash>\"]\n if (parts.length !== 6) return null;\n if (parts[1] !== \"argon2id\") return null;\n const paramStr = parts[3];\n const saltStr = parts[4];\n const hashStr = parts[5];\n if (paramStr === undefined || saltStr === undefined || hashStr === undefined) {\n return null;\n }\n const params = new Map<string, number>();\n for (const kv of paramStr.split(\",\")) {\n const [k, v] = kv.split(\"=\");\n if (k === undefined || v === undefined) return null;\n const n = Number(v);\n if (!Number.isFinite(n)) return null;\n params.set(k, n);\n }\n const m = params.get(\"m\");\n const t = params.get(\"t\");\n const p = params.get(\"p\");\n if (m === undefined || t === undefined || p === undefined) return null;\n try {\n return { m, t, p, salt: b64.decode(saltStr), hash: b64.decode(hashStr) };\n } catch {\n return null;\n }\n}\n","import { ed25519 } from \"@noble/curves/ed25519\";\nimport type { SignatureVerifier } from \"../ports/SignatureVerifier\";\n\n/**\n * الافتراضي للتحقق من التوقيع: Ed25519 من @noble/curves (pure JS). بيتحقق محلياً\n * من مفاتيح التفعيل الموقّعة بمفتاح قمرة الخاص — بدون أي نداء سيرفر.\n */\nexport class NobleSignatureVerifier implements SignatureVerifier {\n async verify(\n message: Uint8Array,\n signature: Uint8Array,\n publicKey: Uint8Array,\n ): Promise<boolean> {\n try {\n return ed25519.verify(signature, message, publicKey);\n } catch {\n // توقيع/مفتاح بحجم غلط → اعتبره غير صالح بدل ما نرمي.\n return false;\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { P as PinHasher, S as SignatureVerifier } from './SignatureVerifier-ueydiL4D.cjs';
|
|
2
|
+
|
|
3
|
+
declare const b64: {
|
|
4
|
+
encode: (b: Uint8Array) => string;
|
|
5
|
+
decode: (s: string) => Uint8Array;
|
|
6
|
+
};
|
|
7
|
+
declare const b64url: {
|
|
8
|
+
encode: (b: Uint8Array) => string;
|
|
9
|
+
decode: (s: string) => Uint8Array;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* الافتراضي لتعمية الـ PIN: argon2id بصيغة PHC القياسية، pure JS من @noble/hashes.
|
|
14
|
+
* نفس الصيغة اللي السيرفر بينتجها فالـ hash المتزامن يتحقق محلياً (نفس الكود
|
|
15
|
+
* أونلاين وأوفلاين).
|
|
16
|
+
*
|
|
17
|
+
* صيغة الناتج: $argon2id$v=19$m=<m>,t=<t>,p=<p>$<saltB64>$<hashB64>
|
|
18
|
+
*/
|
|
19
|
+
interface Argon2Params {
|
|
20
|
+
/** iterations (t). */
|
|
21
|
+
t: number;
|
|
22
|
+
/** memory بالـ KiB (m). */
|
|
23
|
+
m: number;
|
|
24
|
+
/** parallelism (p). */
|
|
25
|
+
p: number;
|
|
26
|
+
/** طول الـ hash بالبايت. */
|
|
27
|
+
dkLen: number;
|
|
28
|
+
}
|
|
29
|
+
/** إعداد متزن للأجهزة (64MiB, 3 مرات) — يطابق الافتراضي الشائع للسيرفر. */
|
|
30
|
+
declare const DEFAULT_ARGON2_PARAMS: Argon2Params;
|
|
31
|
+
declare class NoblePinHasher implements PinHasher {
|
|
32
|
+
private readonly params;
|
|
33
|
+
constructor(params?: Argon2Params);
|
|
34
|
+
hash(pin: string): Promise<string>;
|
|
35
|
+
verify(pin: string, encoded: string): Promise<boolean>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* الافتراضي للتحقق من التوقيع: Ed25519 من @noble/curves (pure JS). بيتحقق محلياً
|
|
40
|
+
* من مفاتيح التفعيل الموقّعة بمفتاح قمرة الخاص — بدون أي نداء سيرفر.
|
|
41
|
+
*/
|
|
42
|
+
declare class NobleSignatureVerifier implements SignatureVerifier {
|
|
43
|
+
verify(message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): Promise<boolean>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { type Argon2Params, DEFAULT_ARGON2_PARAMS, NoblePinHasher, NobleSignatureVerifier, b64, b64url };
|
package/dist/crypto.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { P as PinHasher, S as SignatureVerifier } from './SignatureVerifier-ueydiL4D.js';
|
|
2
|
+
|
|
3
|
+
declare const b64: {
|
|
4
|
+
encode: (b: Uint8Array) => string;
|
|
5
|
+
decode: (s: string) => Uint8Array;
|
|
6
|
+
};
|
|
7
|
+
declare const b64url: {
|
|
8
|
+
encode: (b: Uint8Array) => string;
|
|
9
|
+
decode: (s: string) => Uint8Array;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* الافتراضي لتعمية الـ PIN: argon2id بصيغة PHC القياسية، pure JS من @noble/hashes.
|
|
14
|
+
* نفس الصيغة اللي السيرفر بينتجها فالـ hash المتزامن يتحقق محلياً (نفس الكود
|
|
15
|
+
* أونلاين وأوفلاين).
|
|
16
|
+
*
|
|
17
|
+
* صيغة الناتج: $argon2id$v=19$m=<m>,t=<t>,p=<p>$<saltB64>$<hashB64>
|
|
18
|
+
*/
|
|
19
|
+
interface Argon2Params {
|
|
20
|
+
/** iterations (t). */
|
|
21
|
+
t: number;
|
|
22
|
+
/** memory بالـ KiB (m). */
|
|
23
|
+
m: number;
|
|
24
|
+
/** parallelism (p). */
|
|
25
|
+
p: number;
|
|
26
|
+
/** طول الـ hash بالبايت. */
|
|
27
|
+
dkLen: number;
|
|
28
|
+
}
|
|
29
|
+
/** إعداد متزن للأجهزة (64MiB, 3 مرات) — يطابق الافتراضي الشائع للسيرفر. */
|
|
30
|
+
declare const DEFAULT_ARGON2_PARAMS: Argon2Params;
|
|
31
|
+
declare class NoblePinHasher implements PinHasher {
|
|
32
|
+
private readonly params;
|
|
33
|
+
constructor(params?: Argon2Params);
|
|
34
|
+
hash(pin: string): Promise<string>;
|
|
35
|
+
verify(pin: string, encoded: string): Promise<boolean>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* الافتراضي للتحقق من التوقيع: Ed25519 من @noble/curves (pure JS). بيتحقق محلياً
|
|
40
|
+
* من مفاتيح التفعيل الموقّعة بمفتاح قمرة الخاص — بدون أي نداء سيرفر.
|
|
41
|
+
*/
|
|
42
|
+
declare class NobleSignatureVerifier implements SignatureVerifier {
|
|
43
|
+
verify(message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): Promise<boolean>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { type Argon2Params, DEFAULT_ARGON2_PARAMS, NoblePinHasher, NobleSignatureVerifier, b64, b64url };
|
package/dist/crypto.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"crypto.js"}
|