@session.js/blinded-session-id 1.0.3 → 1.0.5

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
@@ -1,39 +1,179 @@
1
1
  # @session.js/blinded-session-id
2
2
 
3
- Utility JavaScript library with methods to work with Session's blinded Session ID.
3
+ Utility JavaScript library with methods to work with Session's blinded Session ID. Uses [noble v2](https://www.npmjs.com/package/@noble/ciphers) under the hood.
4
4
 
5
- Example of normal Session ID:
5
+ Example of unblinded Session ID (05-prefixed):
6
6
  `057aeb66e45660c3bdfb7c62706f6440226af43ec13f3b6f899c1dd4db1b8fce5b`
7
7
 
8
- Example of blinded Session ID:
8
+ Example of blinded Session ID (15-prefixed):
9
9
  `15d9fd3a6c3c5ddf7500b862174f205ab27164232d9b10fe31f145e61629b676e3`
10
10
 
11
+ Example of blinded Session ID (25-prefixed):
12
+ `253b991dcbba44cfdb45d5b38880d95cff723309e3ece6fd01415ad5fa1dccc7ac`
13
+
11
14
  Blinded IDs are used on Session SOGS to conceal identity of room's users.
12
15
 
16
+ - [@session.js/blinded-session-id](#sessionjsblinded-session-id)
17
+ - [Usage](#usage)
18
+ - [Blinding](#blinding)
19
+ - [Blinding with Session ID or x25519 public key](#blinding-with-session-id-or-x25519-public-key)
20
+ - [Blinding with ed25519 public key](#blinding-with-ed25519-public-key)
21
+ - [Unblinding](#unblinding)
22
+ - [Unblinding 15-prefixed Session IDs](#unblinding-15-prefixed-session-ids)
23
+ - [Unblinding 25-prefixed Session IDs](#unblinding-25-prefixed-session-ids)
24
+ - [Advanced usage](#advanced-usage)
25
+ - [getBlindingK](#getblindingk)
26
+ - [Acknowledgements](#acknowledgements)
27
+ - [Made for Session.js](#made-for-sessionjs)
28
+ - [Donate](#donate)
29
+ - [License](#license)
30
+
13
31
  ## Usage
14
32
 
33
+ ### Blinding
34
+
35
+ #### Blinding with Session ID or x25519 public key
36
+
37
+ To "blind" Session ID, we need the Sesion ID itself and the SOGS's public key (the part after `?public_key=` in the SOGS url).
38
+
39
+ You have multiple choices on input format. All blindSessionId signatures require `type` property defining type of the output Session ID (15- or 25-prefixed). The simpliest format is passing both sessionId and sogsPublicKey as hex-encoded strings.
40
+
41
+ You'll get two variants of the blinded Session ID. **Both outputs are valid and possible and there is absolutely no way to determine which one is correct** (unless you have user's ed25519 key, more on that below).
42
+
43
+ ```ts
44
+ import { blindSessionId } from "@session.js/blinded-session-id";
45
+
46
+ blindSessionId({
47
+ type: "15",
48
+ sessionId: "057aeb66e45660c3bdfb7c62706f6440226af43ec13f3b6f899c1dd4db1b8fce5b",
49
+ sogsPublicKey: "cb4fd6199b84dc3664f0373354341a01007ecaa99a388496fe8775b9b76a253b",
50
+ });
51
+ // Either of those will be the blinded ID
52
+ // => [
53
+ // "15383d0a3ba605abe3b5b7343102be3fc0026056b9812e06f6daee3be62a6a5663",
54
+ // "15383d0a3ba605abe3b5b7343102be3fc0026056b9812e06f6daee3be62a6a56e3"
55
+ // ]
56
+ ```
57
+
58
+ You can also pass sogsPublicKey as Uint8Array, in case you already have it decoded in your app. You can also pass Session ID as x25519 public key as Uint8Array in case you already have it decoded in your app, but keep in mind that Session ID is 33 bytes long and x25519 public key is 32 bytes long, because Session ID has a prefix byte (0x05, 0x15 or 0x25).
59
+
60
+ ```ts
61
+ import { blindSessionId } from "@session.js/blinded-session-id";
62
+
63
+ blindSessionId({
64
+ type: "15",
65
+ x25519PublicKey: new Uint8Array([
66
+ 122, 235, 102, 228, 86, 96, 195, 189, 251, 124, 98, 112, 111, 100, 64, 34, 106, 244, 62, 193,
67
+ 63, 59, 111, 137, 156, 29, 212, 219, 27, 143, 206, 91,
68
+ ]),
69
+ sogsPublicKey: new Uint8Array([
70
+ 203, 79, 214, 25, 155, 132, 220, 54, 100, 240, 55, 51, 84, 52, 26, 1, 0, 126, 202, 169, 154, 56,
71
+ 132, 150, 254, 135, 117, 185, 183, 106, 37, 59,
72
+ ]),
73
+ });
74
+ // Either of those will be the blinded ID
75
+ // => [
76
+ // "15383d0a3ba605abe3b5b7343102be3fc0026056b9812e06f6daee3be62a6a5663",
77
+ // "15383d0a3ba605abe3b5b7343102be3fc0026056b9812e06f6daee3be62a6a56e3"
78
+ // ]
79
+ ```
80
+
81
+ When converting Session's x25519 (Session ID) to the ed25519 key (needed for blinding), the sign (+/-) information is lost, producing two equally valid possibilities. So unless you have user's ed25519 public key, you cannot have a guaranteed determenistic correct single output for blinding, you will always have to account for two outputs.
82
+
83
+ <details>
84
+ <summary>But why two blinded IDs?</summary>
85
+
86
+ Hang on tight, we're diving into some real nerdy cryptography here!
87
+
88
+ You know how Session actually has two keypairs — x25519 (public, secret) and ed25519 (public, secret), all of which are derived from the main secret seed (which can be encoded into words with mnemonic/secret phrase) and Session ID is the public x25519 key?
89
+
90
+ In order to "blind" Session ID we first calculate a "blinding factor" `k` using modulo of SOGS public key and ed25519's ORDER constant. Then we calculate `kA` by multiplying user's ed25519 public key and `k`. Finally, we convert `kA` to hex and prepend it with `15` to mark it as a "blinded" Session ID.
91
+
92
+ But wait a second, where did "user's ed25519 public key" come from? Remember, Session ID is a x25519 public key, not ed25519 public key. These are two different keypairs, two different elliptic curves and they behave in very different ways!
93
+
94
+ That's where the major flaw of Session comes in. Technically, we could just settle on some way of converting value from one curve to another and vice verse. In fact, that's precisely what libsodium (and official Session clients) do using crypto_sign_curve25519_pk_to_ed25519. In the end, both curves are just mathematical curves and translating one to another should be determenistic.
95
+
96
+ There is just one problem though: x25519 does not have negative values, while ed25519 does have them. So ed25519's `123` converted to x25519 would be `456` and vice-verse, but what happens when we convert ed25519's `-123` to x25519 curve is... `456` too! See the problem? Two different values producing same output.
97
+
98
+ And this wouldn't be bad if we only wanted x25519 for blinding, but as you saw earlier, we need ed25519 keys. Now you want to convert x25519 back to ed25519 and only have this x25519 value: `456`. Was it `123` or `-123` in ed25519 that produced that number?
99
+
100
+ So what happens when we try to convert x25519 public key (Session ID) -> ed25519 public key (for blinding) is that we end up with two keys since we don't know whether ed25519 should be positive or negative.
101
+
102
+ If only we had user's secret seed (mnemonic), we could easily calculate ed25519 and avoid x25519 -> ed25519 conversion issue at all... But alas, Session IDs are using x25519 and converting x25519 to ed25519 produces two results because of the lost signing bit.
103
+
104
+ More on blinding: <a href="https://blog.li0ard.rest/blindedid">“Blinded ID в Session и что с ними не так” by li0ard</a>
105
+ </details>
106
+
107
+ #### Blinding with ed25519 public key
108
+
109
+ If you do have user's ed25519 public key (e.g. you control the secret seed, running from the user's client, etc), you can pass it directly instead of Session ID or x25519PublicKey and get a single, correct blinded ID.
110
+
111
+ ```ts
112
+ import { blindSessionId } from "@session.js/blinded-session-id"
113
+
114
+ blindSessionId({
115
+ type: "15",
116
+ ed25519PublicKey: new Uint8Array([
117
+ 240, 55, 163, 9, 91, 114, 247, 212, 93, 217, 12, 245, 213, 114, 251, 182, 195, 95, 46, 4, 211,
118
+ 85, 177, 217, 201, 218, 3, 95, 202, 132, 158, 151,
119
+ ]),
120
+ sogsPublicKey: "cb4fd6199b84dc3664f0373354341a01007ecaa99a388496fe8775b9b76a253b",
121
+ });
122
+ // => 15383d0a3ba605abe3b5b7343102be3fc0026056b9812e06f6daee3be62a6a56e3
123
+ ```
124
+
125
+ There is no way to convert x25519 public key to ed25519 public key without losing the sign bit and thus getting two possible ed25519 keys and two possible blinded Session IDs.
126
+
127
+ ### Unblinding
128
+
129
+ Unblinding is a security vulnerability [discovered by li0ard](https://blog.li0ard.rest/blindedid) possible because 15-prefixed IDs use SOGS's public key as blake2b hash input, which is publicly known, making it possible to reverse 15-prefixed blinding by using the same sogsPublicKey.
130
+
131
+ In 25-prefixed IDs this issue was fixed by using both x25519 public key and SOGS's public key as blake2b hash input, so unless you know user's 05-prefixed Session ID it's impossible to unblind 25-prefixed Session ID, and if you do, there is no sense to unblind it anyway.
132
+
133
+ #### Unblinding 15-prefixed Session IDs
134
+
135
+ To unblind a Session ID, pass it as `sessionId` and SOGS's public key in either hex-encoded string or as Uint8Array.
136
+
15
137
  ```ts
16
- import { blindSessionId } from '@session.js/blinded-session-id'
138
+ import { unblindSessionId } from "@session.js/blinded-session-id"
17
139
 
18
- await blindSessionId({
19
- sessionId: '057aeb66e45660c3bdfb7c62706f6440226af43ec13f3b6f899c1dd4db1b8fce5b',
20
- serverPk: 'cb4fd6199b84dc3664f0373354341a01007ecaa99a388496fe8775b9b76a253b'
21
- }) // => 15383d0a3ba605abe3b5b7343102be3fc0026056b9812e06f6daee3be62a6a56e3
140
+ unblindSessionId({
141
+ sessionId: "15264c132e2e72a9c50b7a981eac11a48b3e51ae5a0ea45ea47deb519a3fa76612",
142
+ sogsPublicKey: "ac9c872e525a58970df6971655abb944a30b38853442a793b29843d20795e840",
143
+ });
144
+ // => 057aeb66e45660c3bdfb7c62706f6440226af43ec13f3b6f899c1dd4db1b8fce5b
22
145
  ```
23
146
 
24
- ## Advanced use
147
+ #### Unblinding 25-prefixed Session IDs
148
+
149
+ Unblinding 25-prefixed Session IDs is impossible.
150
+
151
+ ### Advanced usage
152
+
153
+ #### getBlindingK
154
+
155
+ generates a hash for blinding/unblinding using blake2b
156
+
157
+ ```ts
158
+ import { getBlindingK } from "@session.js/blinded-session-id/utils.js";
159
+
160
+ const sogsPublicKey = new Uint8Array([
161
+ 203, 79, 214, 25, 155, 132, 220, 54, 100, 240, 55, 51, 84, 52, 26, 1, 0, 126, 202, 169, 154, 56,
162
+ 132, 150, 254, 135, 117, 185, 183, 106, 37, 59,
163
+ ]);
164
+ getBlindingK(sogsPublicKey);
165
+ // => Uint8Array(32) [ 27, 203, 111, 10, 221, 88, 187, 146, 221, 11, 206, 55, 7, 86, 218, 223, 21, 123, 29, 214, 198, 182, 3, 40, 188, 123, 190, 73, 35, 122, 140, 13 ]
166
+ ```
25
167
 
26
- - generateBlindedIds — returns legacy and modern blinded id
27
- - generateKAs — returns legacy and modern KAs as Uint8Array
28
- - convertToX25519Key, convertToEd25519Key — self explanatory
168
+ ## Acknowledgements
29
169
 
30
- ## Credit
170
+ Credit to li0ard for [https://github.com/theinfinityway/session_id/](https://github.com/theinfinityway/session_id/) (MIT license)
31
171
 
32
- Credit to li0ard, this code was mostly taken from [https://github.com/theinfinityway/session_id/](https://github.com/theinfinityway/session_id/)
172
+ Credit to [xHD-Wallet-API-ts](https://github.com/algorandfoundation/xHD-Wallet-API-ts) for scalar multiplication functions implementations (Apache-2.0 license)
33
173
 
34
- ## Made for session.js
174
+ ## Made for Session.js
35
175
 
36
- Use Session messenger programmatically with [Session.js](https://github.com/sessionjs/client): Session bots, custom Session clients, and more.
176
+ Use Session messenger programmatically with [Session.js](https://git.hloth.dev/session.js/client): Session bots, custom Session clients, and more.
37
177
 
38
178
  ## Donate
39
179
 
@@ -0,0 +1,23 @@
1
+ export declare function blindKey15({ ed25519PublicKey, serverPublicKey, }: {
2
+ ed25519PublicKey: Uint8Array;
3
+ serverPublicKey: Uint8Array;
4
+ }): Uint8Array;
5
+ export declare function blindKey25({ x25519PublicKey, ed25519PublicKey, serverPublicKey, }: {
6
+ x25519PublicKey: Uint8Array;
7
+ ed25519PublicKey: Uint8Array;
8
+ serverPublicKey: Uint8Array;
9
+ }): Uint8Array;
10
+ export declare function blindSessionId(options: {
11
+ ed25519PublicKey: Uint8Array;
12
+ } & {
13
+ sogsPublicKey: string | Uint8Array;
14
+ type: "15" | "25";
15
+ }): string;
16
+ export declare function blindSessionId(options: ({
17
+ sessionId: string;
18
+ } | {
19
+ x25519PublicKey: Uint8Array;
20
+ }) & {
21
+ sogsPublicKey: string | Uint8Array;
22
+ type: "15" | "25";
23
+ }): [string, string];
@@ -0,0 +1,107 @@
1
+ import { hexToBytes } from "@noble/curves/utils.js";
2
+ import { SessionValidationError, SessionValidationErrorCode } from "@session.js/errors";
3
+ import { crypto_scalarmult_ed25519_noclamp, crypto_sign_curve25519_pk_to_ed25519, } from "./scalar-math";
4
+ import { getBlindingK, hexRegex, keyToSessionId } from "./utils";
5
+ export function blindKey15({ ed25519PublicKey, serverPublicKey, }) {
6
+ const blindingKInput = serverPublicKey;
7
+ const k = getBlindingK(blindingKInput);
8
+ const kA = crypto_scalarmult_ed25519_noclamp(k, ed25519PublicKey);
9
+ return kA;
10
+ }
11
+ export function blindKey25({ x25519PublicKey, ed25519PublicKey, serverPublicKey, }) {
12
+ const blindingKInput = new Uint8Array([0x05, ...x25519PublicKey, ...serverPublicKey]);
13
+ const k = getBlindingK(blindingKInput);
14
+ const kA = crypto_scalarmult_ed25519_noclamp(k, ed25519PublicKey);
15
+ return kA;
16
+ }
17
+ export function blindSessionId(options) {
18
+ let x25519PublicKey;
19
+ let serverPublicKey;
20
+ let ed25519PublicKey;
21
+ if ("ed25519PublicKey" in options) {
22
+ if (options.ed25519PublicKey.length !== 32) {
23
+ throw new SessionValidationError({
24
+ code: SessionValidationErrorCode.InvalidOptions,
25
+ message: "ed25519PublicKey must be 32 bytes long",
26
+ });
27
+ }
28
+ ed25519PublicKey = options.ed25519PublicKey;
29
+ x25519PublicKey = crypto_sign_curve25519_pk_to_ed25519(ed25519PublicKey);
30
+ }
31
+ else {
32
+ if ("sessionId" in options) {
33
+ if (!hexRegex.test(options.sessionId) || options.sessionId.length % 2 !== 0) {
34
+ throw new SessionValidationError({
35
+ code: SessionValidationErrorCode.InvalidSessionID,
36
+ message: "Session ID must be a hex string",
37
+ });
38
+ }
39
+ const sessionIdBytes = hexToBytes(options.sessionId);
40
+ if (sessionIdBytes.length !== 33) {
41
+ throw new SessionValidationError({
42
+ code: SessionValidationErrorCode.InvalidSessionID,
43
+ message: "Session ID must be 33 bytes long",
44
+ });
45
+ }
46
+ if (sessionIdBytes[0] !== 0x05) {
47
+ throw new SessionValidationError({
48
+ code: SessionValidationErrorCode.InvalidSessionID,
49
+ message: "Session ID must start with 05 to blind it",
50
+ });
51
+ }
52
+ x25519PublicKey = sessionIdBytes.subarray(1);
53
+ }
54
+ else {
55
+ if (options.x25519PublicKey.length !== 32) {
56
+ throw new SessionValidationError({
57
+ code: SessionValidationErrorCode.InvalidOptions,
58
+ message: "x25519PublicKey must be 32 bytes long",
59
+ });
60
+ }
61
+ x25519PublicKey = options.x25519PublicKey;
62
+ }
63
+ ed25519PublicKey = crypto_sign_curve25519_pk_to_ed25519(x25519PublicKey);
64
+ }
65
+ if (typeof options.sogsPublicKey === "string") {
66
+ if (!hexRegex.test(options.sogsPublicKey) || options.sogsPublicKey.length % 2 !== 0) {
67
+ throw new SessionValidationError({
68
+ code: SessionValidationErrorCode.InvalidOptions,
69
+ message: "sogsPublicKey must be a 64-character hex string",
70
+ });
71
+ }
72
+ serverPublicKey = hexToBytes(options.sogsPublicKey);
73
+ }
74
+ else {
75
+ serverPublicKey = options.sogsPublicKey;
76
+ }
77
+ if (serverPublicKey.length !== 32) {
78
+ throw new SessionValidationError({
79
+ code: SessionValidationErrorCode.InvalidOptions,
80
+ message: "sogsPublicKey must be 32 bytes long",
81
+ });
82
+ }
83
+ let kA;
84
+ if (options.type === "15") {
85
+ kA = blindKey15({ ed25519PublicKey, serverPublicKey });
86
+ }
87
+ else if (options.type === "25") {
88
+ kA = blindKey25({ x25519PublicKey, ed25519PublicKey, serverPublicKey });
89
+ }
90
+ else {
91
+ throw new SessionValidationError({
92
+ code: SessionValidationErrorCode.InvalidOptions,
93
+ message: "type must be either '15' or '25' for blindSessionId",
94
+ });
95
+ }
96
+ const prefix = options.type === "15" ? 0x15 : 0x25;
97
+ const ftSessionId = (kA) => keyToSessionId(prefix, kA);
98
+ if ("ed25519PublicKey" in options) {
99
+ return ftSessionId(kA);
100
+ }
101
+ else {
102
+ const kA2 = new Uint8Array(32);
103
+ kA2.set(kA);
104
+ kA2[31] = kA[31] ^ 0b1000_0000;
105
+ return [ftSessionId(kA), ftSessionId(kA2)];
106
+ }
107
+ }
package/dist/index.d.ts CHANGED
@@ -1,10 +1,2 @@
1
- export declare function crypto_sign_curve25519_pk_to_ed25519(pk: Uint8Array): Uint8Array;
2
- export declare const convertToEd25519Key: (key: string) => string;
3
- export declare const convertToX25519Key: (key: string) => string;
4
- export declare function crypto_core_ed25519_scalar_reduce(scalar: Uint8Array): Uint8Array;
5
- export declare const generateKA: (sessionId: string, serverPk: string) => Uint8Array;
6
- export declare const generateBlindedKeys: (sessionId: string, serverPk: string) => Uint8Array[];
7
- export declare const blindSessionId: ({ sessionId, serverPk }: {
8
- sessionId: string;
9
- serverPk: string;
10
- }) => string;
1
+ export { blindSessionId } from "./blinding";
2
+ export { unblindSessionId } from "./unblinding";
package/dist/index.js CHANGED
@@ -1,589 +1,2 @@
1
- // Huge thanks to li0ard for this code
2
- // CREDIT: https://github.com/theinfinityway/session_id/
3
- /* eslint-disable prefer-const */
4
- import { ed25519 } from "@noble/curves/ed25519.js";
5
- import { bytesToHex, hexToBytes } from "@noble/curves/utils.js";
6
- import { blake2b } from "@noble/hashes/blake2.js";
7
- import { mod } from "@noble/curves/abstract/modular.js";
8
- function bytesToNumberLE(bytes) {
9
- let value = 0n;
10
- for (let i = bytes.length - 1; i >= 0; i--) {
11
- value = (value << 8n) + BigInt(bytes[i]);
12
- }
13
- return value;
14
- }
15
- function numberToBytesLE(num, length) {
16
- const out = new Uint8Array(length);
17
- let n = num;
18
- for (let i = 0; i < length; i++) {
19
- out[i] = Number(n & 0xffn);
20
- n >>= 8n;
21
- }
22
- return out;
23
- }
24
- function ed25519ScalarmultNoClamp(scalar32, point32) {
25
- if (scalar32.length !== 32) {
26
- throw new Error(`ed25519ScalarmultNoClamp: expected 32-byte scalar, got ${scalar32.length}`);
27
- }
28
- if (point32.length !== 32) {
29
- throw new Error(`ed25519ScalarmultNoClamp: expected 32-byte point, got ${point32.length}`);
30
- }
31
- const L = ed25519.Point.Fn.ORDER;
32
- const s = bytesToNumberLE(scalar32) % L;
33
- const P = ed25519.Point.fromBytes(point32);
34
- if (P.isSmallOrder()) {
35
- throw new Error("ed25519ScalarmultNoClamp: invalid point (small order)");
36
- }
37
- return P.multiply(s).toBytes();
38
- }
39
- function gf(init) {
40
- let i, r = new Float64Array(16);
41
- if (init)
42
- for (i = 0; i < init.length; i++)
43
- r[i] = init[i];
44
- return r;
45
- }
46
- function unpack25519(o, n) {
47
- let i;
48
- for (i = 0; i < 16; i++)
49
- o[i] = n[2 * i] + (n[2 * i + 1] << 8);
50
- o[15] &= 0x7fff;
51
- }
52
- function A(o, a, b) {
53
- for (let i = 0; i < 16; i++)
54
- o[i] = a[i] + b[i];
55
- }
56
- function Z(o, a, b) {
57
- for (let i = 0; i < 16; i++)
58
- o[i] = a[i] - b[i];
59
- }
60
- function S(o, a) {
61
- M(o, a, a);
62
- }
63
- function inv25519(o, i) {
64
- const c = gf();
65
- let a;
66
- for (a = 0; a < 16; a++)
67
- c[a] = i[a];
68
- for (a = 253; a >= 0; a--) {
69
- S(c, c);
70
- if (a !== 2 && a !== 4)
71
- M(c, c, i);
72
- }
73
- for (a = 0; a < 16; a++)
74
- o[a] = c[a];
75
- }
76
- function M(o, a, b) {
77
- let v, c, t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0, t6 = 0, t7 = 0, t8 = 0, t9 = 0, t10 = 0, t11 = 0, t12 = 0, t13 = 0, t14 = 0, t15 = 0, t16 = 0, t17 = 0, t18 = 0, t19 = 0, t20 = 0, t21 = 0, t22 = 0, t23 = 0, t24 = 0, t25 = 0, t26 = 0, t27 = 0, t28 = 0, t29 = 0, t30 = 0, b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3], b4 = b[4], b5 = b[5], b6 = b[6], b7 = b[7], b8 = b[8], b9 = b[9], b10 = b[10], b11 = b[11], b12 = b[12], b13 = b[13], b14 = b[14], b15 = b[15];
78
- v = a[0];
79
- t0 += v * b0;
80
- t1 += v * b1;
81
- t2 += v * b2;
82
- t3 += v * b3;
83
- t4 += v * b4;
84
- t5 += v * b5;
85
- t6 += v * b6;
86
- t7 += v * b7;
87
- t8 += v * b8;
88
- t9 += v * b9;
89
- t10 += v * b10;
90
- t11 += v * b11;
91
- t12 += v * b12;
92
- t13 += v * b13;
93
- t14 += v * b14;
94
- t15 += v * b15;
95
- v = a[1];
96
- t1 += v * b0;
97
- t2 += v * b1;
98
- t3 += v * b2;
99
- t4 += v * b3;
100
- t5 += v * b4;
101
- t6 += v * b5;
102
- t7 += v * b6;
103
- t8 += v * b7;
104
- t9 += v * b8;
105
- t10 += v * b9;
106
- t11 += v * b10;
107
- t12 += v * b11;
108
- t13 += v * b12;
109
- t14 += v * b13;
110
- t15 += v * b14;
111
- t16 += v * b15;
112
- v = a[2];
113
- t2 += v * b0;
114
- t3 += v * b1;
115
- t4 += v * b2;
116
- t5 += v * b3;
117
- t6 += v * b4;
118
- t7 += v * b5;
119
- t8 += v * b6;
120
- t9 += v * b7;
121
- t10 += v * b8;
122
- t11 += v * b9;
123
- t12 += v * b10;
124
- t13 += v * b11;
125
- t14 += v * b12;
126
- t15 += v * b13;
127
- t16 += v * b14;
128
- t17 += v * b15;
129
- v = a[3];
130
- t3 += v * b0;
131
- t4 += v * b1;
132
- t5 += v * b2;
133
- t6 += v * b3;
134
- t7 += v * b4;
135
- t8 += v * b5;
136
- t9 += v * b6;
137
- t10 += v * b7;
138
- t11 += v * b8;
139
- t12 += v * b9;
140
- t13 += v * b10;
141
- t14 += v * b11;
142
- t15 += v * b12;
143
- t16 += v * b13;
144
- t17 += v * b14;
145
- t18 += v * b15;
146
- v = a[4];
147
- t4 += v * b0;
148
- t5 += v * b1;
149
- t6 += v * b2;
150
- t7 += v * b3;
151
- t8 += v * b4;
152
- t9 += v * b5;
153
- t10 += v * b6;
154
- t11 += v * b7;
155
- t12 += v * b8;
156
- t13 += v * b9;
157
- t14 += v * b10;
158
- t15 += v * b11;
159
- t16 += v * b12;
160
- t17 += v * b13;
161
- t18 += v * b14;
162
- t19 += v * b15;
163
- v = a[5];
164
- t5 += v * b0;
165
- t6 += v * b1;
166
- t7 += v * b2;
167
- t8 += v * b3;
168
- t9 += v * b4;
169
- t10 += v * b5;
170
- t11 += v * b6;
171
- t12 += v * b7;
172
- t13 += v * b8;
173
- t14 += v * b9;
174
- t15 += v * b10;
175
- t16 += v * b11;
176
- t17 += v * b12;
177
- t18 += v * b13;
178
- t19 += v * b14;
179
- t20 += v * b15;
180
- v = a[6];
181
- t6 += v * b0;
182
- t7 += v * b1;
183
- t8 += v * b2;
184
- t9 += v * b3;
185
- t10 += v * b4;
186
- t11 += v * b5;
187
- t12 += v * b6;
188
- t13 += v * b7;
189
- t14 += v * b8;
190
- t15 += v * b9;
191
- t16 += v * b10;
192
- t17 += v * b11;
193
- t18 += v * b12;
194
- t19 += v * b13;
195
- t20 += v * b14;
196
- t21 += v * b15;
197
- v = a[7];
198
- t7 += v * b0;
199
- t8 += v * b1;
200
- t9 += v * b2;
201
- t10 += v * b3;
202
- t11 += v * b4;
203
- t12 += v * b5;
204
- t13 += v * b6;
205
- t14 += v * b7;
206
- t15 += v * b8;
207
- t16 += v * b9;
208
- t17 += v * b10;
209
- t18 += v * b11;
210
- t19 += v * b12;
211
- t20 += v * b13;
212
- t21 += v * b14;
213
- t22 += v * b15;
214
- v = a[8];
215
- t8 += v * b0;
216
- t9 += v * b1;
217
- t10 += v * b2;
218
- t11 += v * b3;
219
- t12 += v * b4;
220
- t13 += v * b5;
221
- t14 += v * b6;
222
- t15 += v * b7;
223
- t16 += v * b8;
224
- t17 += v * b9;
225
- t18 += v * b10;
226
- t19 += v * b11;
227
- t20 += v * b12;
228
- t21 += v * b13;
229
- t22 += v * b14;
230
- t23 += v * b15;
231
- v = a[9];
232
- t9 += v * b0;
233
- t10 += v * b1;
234
- t11 += v * b2;
235
- t12 += v * b3;
236
- t13 += v * b4;
237
- t14 += v * b5;
238
- t15 += v * b6;
239
- t16 += v * b7;
240
- t17 += v * b8;
241
- t18 += v * b9;
242
- t19 += v * b10;
243
- t20 += v * b11;
244
- t21 += v * b12;
245
- t22 += v * b13;
246
- t23 += v * b14;
247
- t24 += v * b15;
248
- v = a[10];
249
- t10 += v * b0;
250
- t11 += v * b1;
251
- t12 += v * b2;
252
- t13 += v * b3;
253
- t14 += v * b4;
254
- t15 += v * b5;
255
- t16 += v * b6;
256
- t17 += v * b7;
257
- t18 += v * b8;
258
- t19 += v * b9;
259
- t20 += v * b10;
260
- t21 += v * b11;
261
- t22 += v * b12;
262
- t23 += v * b13;
263
- t24 += v * b14;
264
- t25 += v * b15;
265
- v = a[11];
266
- t11 += v * b0;
267
- t12 += v * b1;
268
- t13 += v * b2;
269
- t14 += v * b3;
270
- t15 += v * b4;
271
- t16 += v * b5;
272
- t17 += v * b6;
273
- t18 += v * b7;
274
- t19 += v * b8;
275
- t20 += v * b9;
276
- t21 += v * b10;
277
- t22 += v * b11;
278
- t23 += v * b12;
279
- t24 += v * b13;
280
- t25 += v * b14;
281
- t26 += v * b15;
282
- v = a[12];
283
- t12 += v * b0;
284
- t13 += v * b1;
285
- t14 += v * b2;
286
- t15 += v * b3;
287
- t16 += v * b4;
288
- t17 += v * b5;
289
- t18 += v * b6;
290
- t19 += v * b7;
291
- t20 += v * b8;
292
- t21 += v * b9;
293
- t22 += v * b10;
294
- t23 += v * b11;
295
- t24 += v * b12;
296
- t25 += v * b13;
297
- t26 += v * b14;
298
- t27 += v * b15;
299
- v = a[13];
300
- t13 += v * b0;
301
- t14 += v * b1;
302
- t15 += v * b2;
303
- t16 += v * b3;
304
- t17 += v * b4;
305
- t18 += v * b5;
306
- t19 += v * b6;
307
- t20 += v * b7;
308
- t21 += v * b8;
309
- t22 += v * b9;
310
- t23 += v * b10;
311
- t24 += v * b11;
312
- t25 += v * b12;
313
- t26 += v * b13;
314
- t27 += v * b14;
315
- t28 += v * b15;
316
- v = a[14];
317
- t14 += v * b0;
318
- t15 += v * b1;
319
- t16 += v * b2;
320
- t17 += v * b3;
321
- t18 += v * b4;
322
- t19 += v * b5;
323
- t20 += v * b6;
324
- t21 += v * b7;
325
- t22 += v * b8;
326
- t23 += v * b9;
327
- t24 += v * b10;
328
- t25 += v * b11;
329
- t26 += v * b12;
330
- t27 += v * b13;
331
- t28 += v * b14;
332
- t29 += v * b15;
333
- v = a[15];
334
- t15 += v * b0;
335
- t16 += v * b1;
336
- t17 += v * b2;
337
- t18 += v * b3;
338
- t19 += v * b4;
339
- t20 += v * b5;
340
- t21 += v * b6;
341
- t22 += v * b7;
342
- t23 += v * b8;
343
- t24 += v * b9;
344
- t25 += v * b10;
345
- t26 += v * b11;
346
- t27 += v * b12;
347
- t28 += v * b13;
348
- t29 += v * b14;
349
- t30 += v * b15;
350
- t0 += 38 * t16;
351
- t1 += 38 * t17;
352
- t2 += 38 * t18;
353
- t3 += 38 * t19;
354
- t4 += 38 * t20;
355
- t5 += 38 * t21;
356
- t6 += 38 * t22;
357
- t7 += 38 * t23;
358
- t8 += 38 * t24;
359
- t9 += 38 * t25;
360
- t10 += 38 * t26;
361
- t11 += 38 * t27;
362
- t12 += 38 * t28;
363
- t13 += 38 * t29;
364
- t14 += 38 * t30;
365
- // t15 left as is
366
- // first car
367
- c = 1;
368
- v = t0 + c + 65535;
369
- c = Math.floor(v / 65536);
370
- t0 = v - c * 65536;
371
- v = t1 + c + 65535;
372
- c = Math.floor(v / 65536);
373
- t1 = v - c * 65536;
374
- v = t2 + c + 65535;
375
- c = Math.floor(v / 65536);
376
- t2 = v - c * 65536;
377
- v = t3 + c + 65535;
378
- c = Math.floor(v / 65536);
379
- t3 = v - c * 65536;
380
- v = t4 + c + 65535;
381
- c = Math.floor(v / 65536);
382
- t4 = v - c * 65536;
383
- v = t5 + c + 65535;
384
- c = Math.floor(v / 65536);
385
- t5 = v - c * 65536;
386
- v = t6 + c + 65535;
387
- c = Math.floor(v / 65536);
388
- t6 = v - c * 65536;
389
- v = t7 + c + 65535;
390
- c = Math.floor(v / 65536);
391
- t7 = v - c * 65536;
392
- v = t8 + c + 65535;
393
- c = Math.floor(v / 65536);
394
- t8 = v - c * 65536;
395
- v = t9 + c + 65535;
396
- c = Math.floor(v / 65536);
397
- t9 = v - c * 65536;
398
- v = t10 + c + 65535;
399
- c = Math.floor(v / 65536);
400
- t10 = v - c * 65536;
401
- v = t11 + c + 65535;
402
- c = Math.floor(v / 65536);
403
- t11 = v - c * 65536;
404
- v = t12 + c + 65535;
405
- c = Math.floor(v / 65536);
406
- t12 = v - c * 65536;
407
- v = t13 + c + 65535;
408
- c = Math.floor(v / 65536);
409
- t13 = v - c * 65536;
410
- v = t14 + c + 65535;
411
- c = Math.floor(v / 65536);
412
- t14 = v - c * 65536;
413
- v = t15 + c + 65535;
414
- c = Math.floor(v / 65536);
415
- t15 = v - c * 65536;
416
- t0 += c - 1 + 37 * (c - 1);
417
- // second car
418
- c = 1;
419
- v = t0 + c + 65535;
420
- c = Math.floor(v / 65536);
421
- t0 = v - c * 65536;
422
- v = t1 + c + 65535;
423
- c = Math.floor(v / 65536);
424
- t1 = v - c * 65536;
425
- v = t2 + c + 65535;
426
- c = Math.floor(v / 65536);
427
- t2 = v - c * 65536;
428
- v = t3 + c + 65535;
429
- c = Math.floor(v / 65536);
430
- t3 = v - c * 65536;
431
- v = t4 + c + 65535;
432
- c = Math.floor(v / 65536);
433
- t4 = v - c * 65536;
434
- v = t5 + c + 65535;
435
- c = Math.floor(v / 65536);
436
- t5 = v - c * 65536;
437
- v = t6 + c + 65535;
438
- c = Math.floor(v / 65536);
439
- t6 = v - c * 65536;
440
- v = t7 + c + 65535;
441
- c = Math.floor(v / 65536);
442
- t7 = v - c * 65536;
443
- v = t8 + c + 65535;
444
- c = Math.floor(v / 65536);
445
- t8 = v - c * 65536;
446
- v = t9 + c + 65535;
447
- c = Math.floor(v / 65536);
448
- t9 = v - c * 65536;
449
- v = t10 + c + 65535;
450
- c = Math.floor(v / 65536);
451
- t10 = v - c * 65536;
452
- v = t11 + c + 65535;
453
- c = Math.floor(v / 65536);
454
- t11 = v - c * 65536;
455
- v = t12 + c + 65535;
456
- c = Math.floor(v / 65536);
457
- t12 = v - c * 65536;
458
- v = t13 + c + 65535;
459
- c = Math.floor(v / 65536);
460
- t13 = v - c * 65536;
461
- v = t14 + c + 65535;
462
- c = Math.floor(v / 65536);
463
- t14 = v - c * 65536;
464
- v = t15 + c + 65535;
465
- c = Math.floor(v / 65536);
466
- t15 = v - c * 65536;
467
- t0 += c - 1 + 37 * (c - 1);
468
- o[0] = t0;
469
- o[1] = t1;
470
- o[2] = t2;
471
- o[3] = t3;
472
- o[4] = t4;
473
- o[5] = t5;
474
- o[6] = t6;
475
- o[7] = t7;
476
- o[8] = t8;
477
- o[9] = t9;
478
- o[10] = t10;
479
- o[11] = t11;
480
- o[12] = t12;
481
- o[13] = t13;
482
- o[14] = t14;
483
- o[15] = t15;
484
- }
485
- function car25519(o) {
486
- let i, v, c = 1;
487
- for (i = 0; i < 16; i++) {
488
- v = o[i] + c + 65535;
489
- c = Math.floor(v / 65536);
490
- o[i] = v - c * 65536;
491
- }
492
- o[0] += c - 1 + 37 * (c - 1);
493
- }
494
- function sel25519(p, q, b) {
495
- let t, c = ~(b - 1);
496
- for (let i = 0; i < 16; i++) {
497
- t = c & (p[i] ^ q[i]);
498
- p[i] ^= t;
499
- q[i] ^= t;
500
- }
501
- }
502
- function pack25519(o, n) {
503
- let i, j, b;
504
- const m = gf(), t = gf();
505
- for (i = 0; i < 16; i++)
506
- t[i] = n[i];
507
- car25519(t);
508
- car25519(t);
509
- car25519(t);
510
- for (j = 0; j < 2; j++) {
511
- m[0] = t[0] - 0xffed;
512
- for (i = 1; i < 15; i++) {
513
- m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
514
- m[i - 1] &= 0xffff;
515
- }
516
- m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
517
- b = (m[15] >> 16) & 1;
518
- m[14] &= 0xffff;
519
- sel25519(t, m, 1 - b);
520
- }
521
- for (i = 0; i < 16; i++) {
522
- o[2 * i] = t[i] & 0xff;
523
- o[2 * i + 1] = t[i] >> 8;
524
- }
525
- }
526
- export function crypto_sign_curve25519_pk_to_ed25519(pk) {
527
- return convertPublicKey(pk);
528
- }
529
- // Converts Curve25519 public key back to Ed25519 public key.
530
- // edwardsY = (montgomeryX - 1) / (montgomeryX + 1)
531
- function convertPublicKey(pk) {
532
- const z = new Uint8Array(32), x = gf(), a = gf(), b = gf(), gf1 = gf([1]);
533
- unpack25519(x, pk);
534
- A(a, x, gf1);
535
- Z(b, x, gf1);
536
- inv25519(a, a);
537
- M(a, a, b);
538
- pack25519(z, a);
539
- return z;
540
- }
541
- export const convertToEd25519Key = (key) => {
542
- const inbin = hexToBytes(key);
543
- const xEd25519Key = crypto_sign_curve25519_pk_to_ed25519(inbin);
544
- return bytesToHex(xEd25519Key);
545
- };
546
- export const convertToX25519Key = (key) => {
547
- const inbin = hexToBytes(key);
548
- const xEd25519Key = ed25519.utils.toMontgomery(inbin);
549
- return bytesToHex(xEd25519Key);
550
- };
551
- function combineKeys(lhsKeyBytes, rhsKeyBytes) {
552
- return ed25519ScalarmultNoClamp(lhsKeyBytes, rhsKeyBytes);
553
- }
554
- export function crypto_core_ed25519_scalar_reduce(scalar) {
555
- const scalarNum = bytesToNumberLE(scalar);
556
- const result = mod(scalarNum, ed25519.Point.Fn.ORDER);
557
- return numberToBytesLE(result, 32);
558
- }
559
- const generateBlindingFactor = (serverPk) => {
560
- const hexServerPk = hexToBytes(serverPk);
561
- const serverPkHash = blake2b(hexServerPk, {
562
- dkLen: 64,
563
- });
564
- return crypto_core_ed25519_scalar_reduce(serverPkHash);
565
- };
566
- export const generateKA = (sessionId, serverPk) => {
567
- const sessionIdNoPrefix = sessionId.substring(2);
568
- const kBytes = generateBlindingFactor(serverPk);
569
- const xEd25519Key = hexToBytes(convertToEd25519Key(sessionIdNoPrefix));
570
- const kA = combineKeys(kBytes, xEd25519Key);
571
- return kA;
572
- };
573
- export const generateBlindedKeys = (sessionId, serverPk) => {
574
- const kA = generateKA(sessionId, serverPk);
575
- const key1 = kA;
576
- const modifiedByte = kA[31] & 0x7F;
577
- const key2 = new Uint8Array(32);
578
- key2.set(kA.slice(0, 31), 0);
579
- key2[31] = modifiedByte;
580
- return [key1, key2];
581
- };
582
- export const blindSessionId = ({ sessionId, serverPk }) => {
583
- const [key1, key2] = generateBlindedKeys(sessionId, serverPk);
584
- const isKey2 = key1[31] & 0x80;
585
- if (isKey2) {
586
- return '15' + bytesToHex(key2);
587
- }
588
- return '15' + bytesToHex(key1);
589
- };
1
+ export { blindSessionId } from "./blinding";
2
+ export { unblindSessionId } from "./unblinding";
@@ -0,0 +1,7 @@
1
+ export declare function crypto_scalarmult_ed25519_base_noclamp(scalar: Uint8Array): Uint8Array;
2
+ export declare function crypto_core_ed25519_scalar_add(scalarA: Uint8Array, scalarB: Uint8Array): Uint8Array;
3
+ export declare function crypto_core_ed25519_scalar_mul(scalarA: Uint8Array, scalarB: Uint8Array): Uint8Array;
4
+ export declare function crypto_core_ed25519_scalar_reduce(scalar: Uint8Array): Uint8Array;
5
+ export declare function crypto_sign_ed25519_sk_to_curve25519(edPrivKey: Uint8Array): Uint8Array;
6
+ export declare function crypto_sign_curve25519_pk_to_ed25519(x25519Pk: Uint8Array): Uint8Array;
7
+ export declare function crypto_scalarmult_ed25519_noclamp(scalar32: Uint8Array, point32: Uint8Array): Uint8Array;
@@ -0,0 +1,95 @@
1
+ // Credit: https://github.com/algorandfoundation/xHD-Wallet-API-ts/blob/2c5afbf6a1bed04ed952b65b754a36ed31669872/src/sumo.facade.ts
2
+ // License: Apache 2.0: https://github.com/algorandfoundation/xHD-Wallet-API-ts/blob/main/LICENSE
3
+ import { ed25519 } from "@noble/curves/ed25519.js";
4
+ import { mod } from "@noble/curves/abstract/modular.js";
5
+ import { bytesToNumberLE, numberToBytesLE } from "@noble/curves/utils.js";
6
+ const crypto_scalarmult_ed25519_SCALARBYTES = 32;
7
+ export function crypto_scalarmult_ed25519_base_noclamp(scalar) {
8
+ if (scalar.length !== crypto_scalarmult_ed25519_SCALARBYTES) {
9
+ throw new Error(`scalar must be ${crypto_scalarmult_ed25519_SCALARBYTES} bytes`);
10
+ }
11
+ const scalarBigint = bytesToNumberLE(scalar);
12
+ try {
13
+ const point = ed25519.Point.BASE.multiply(scalarBigint);
14
+ return point.toBytes();
15
+ }
16
+ catch {
17
+ if (scalarBigint === 0n) {
18
+ const identity = new Uint8Array(32);
19
+ identity[0] = 1;
20
+ return identity;
21
+ }
22
+ const reducedScalar = mod(scalarBigint, ed25519.Point.Fn.ORDER);
23
+ if (reducedScalar === 0n) {
24
+ const identity = new Uint8Array(32);
25
+ identity[0] = 1;
26
+ return identity;
27
+ }
28
+ const point = ed25519.Point.BASE.multiply(reducedScalar);
29
+ return point.toBytes();
30
+ }
31
+ }
32
+ export function crypto_core_ed25519_scalar_add(scalarA, scalarB) {
33
+ const a = bytesToNumberLE(scalarA);
34
+ const b = bytesToNumberLE(scalarB);
35
+ const result = mod(a + b, ed25519.Point.Fn.ORDER);
36
+ return numberToBytesLE(result, 32);
37
+ }
38
+ export function crypto_core_ed25519_scalar_mul(scalarA, scalarB) {
39
+ const a = bytesToNumberLE(scalarA);
40
+ const b = bytesToNumberLE(scalarB);
41
+ const result = mod(a * b, ed25519.Point.Fn.ORDER);
42
+ return numberToBytesLE(result, 32);
43
+ }
44
+ export function crypto_core_ed25519_scalar_reduce(scalar) {
45
+ const scalarNum = bytesToNumberLE(scalar);
46
+ const result = mod(scalarNum, ed25519.Point.Fn.ORDER);
47
+ return numberToBytesLE(result, 32);
48
+ }
49
+ export function crypto_sign_ed25519_sk_to_curve25519(edPrivKey) {
50
+ const seed = edPrivKey.slice(0, 32);
51
+ return ed25519.utils.toMontgomerySecret(seed);
52
+ }
53
+ export function crypto_sign_curve25519_pk_to_ed25519(x25519Pk) {
54
+ const P = 2n ** 255n - 19n;
55
+ let u = 0n;
56
+ for (let i = 0; i < x25519Pk.length; i++) {
57
+ u += BigInt(x25519Pk[i]) << (8n * BigInt(i));
58
+ }
59
+ const modPow = (base, exp, mod) => {
60
+ let result = 1n;
61
+ base = base % mod;
62
+ while (exp > 0n) {
63
+ if (exp % 2n === 1n)
64
+ result = (result * base) % mod;
65
+ exp = exp >> 1n;
66
+ base = (base * base) % mod;
67
+ }
68
+ return result;
69
+ };
70
+ const modInv = (a) => modPow(a, P - 2n, P);
71
+ const y = (((u - 1n + P) % P) * modInv((u + 1n) % P)) % P;
72
+ const yBytes = new Uint8Array(32);
73
+ let yTemp = y;
74
+ for (let i = 0; i < 32; i++) {
75
+ yBytes[i] = Number(yTemp & 0xffn);
76
+ yTemp >>= 8n;
77
+ }
78
+ yBytes[31] &= 0x7f;
79
+ return yBytes;
80
+ }
81
+ export function crypto_scalarmult_ed25519_noclamp(scalar32, point32) {
82
+ if (scalar32.length !== 32) {
83
+ throw new Error(`crypto_scalarmult_ed25519_noclamp: expected 32-byte scalar, got ${scalar32.length}`);
84
+ }
85
+ if (point32.length !== 32) {
86
+ throw new Error(`crypto_scalarmult_ed25519_noclamp: expected 32-byte point, got ${point32.length}`);
87
+ }
88
+ const L = ed25519.Point.Fn.ORDER;
89
+ const s = bytesToNumberLE(scalar32) % L;
90
+ const P = ed25519.Point.fromBytes(point32);
91
+ if (P.isSmallOrder()) {
92
+ throw new Error("crypto_scalarmult_ed25519_noclamp: invalid point (small order)");
93
+ }
94
+ return P.multiply(s).toBytes();
95
+ }
@@ -0,0 +1,8 @@
1
+ export declare function unblindKey15({ blindedKey, serverPublicKey, }: {
2
+ blindedKey: Uint8Array;
3
+ serverPublicKey: Uint8Array;
4
+ }): Uint8Array;
5
+ export declare function unblindSessionId({ sessionId, sogsPublicKey, }: {
6
+ sessionId: string | Uint8Array;
7
+ sogsPublicKey: string | Uint8Array;
8
+ }): string | undefined;
@@ -0,0 +1,63 @@
1
+ import { ed25519 } from "@noble/curves/ed25519.js";
2
+ import { bytesToNumberLE, numberToBytesLE, hexToBytes } from "@noble/curves/utils.js";
3
+ import { SessionValidationError, SessionValidationErrorCode } from "@session.js/errors";
4
+ import { crypto_scalarmult_ed25519_noclamp } from "./scalar-math";
5
+ import { getBlindingK, hexRegex, keyToSessionId } from "./utils";
6
+ export function unblindKey15({ blindedKey, serverPublicKey, }) {
7
+ const blindingKInput = serverPublicKey;
8
+ const k = getBlindingK(blindingKInput);
9
+ const kInverted = numberToBytesLE(ed25519.Point.Fn.inv(bytesToNumberLE(k)), 32);
10
+ const kA = crypto_scalarmult_ed25519_noclamp(kInverted, blindedKey);
11
+ const x25519PublicKey = ed25519.utils.toMontgomery(kA);
12
+ return x25519PublicKey;
13
+ }
14
+ export function unblindSessionId({ sessionId, sogsPublicKey, }) {
15
+ let sessionIdBytes;
16
+ if (typeof sessionId === "string") {
17
+ if (!hexRegex.test(sessionId) || sessionId.length % 2 !== 0) {
18
+ throw new SessionValidationError({
19
+ code: SessionValidationErrorCode.InvalidSessionID,
20
+ message: "Session ID must be a hex string",
21
+ });
22
+ }
23
+ sessionIdBytes = hexToBytes(sessionId);
24
+ }
25
+ else {
26
+ sessionIdBytes = sessionId;
27
+ }
28
+ if (sessionIdBytes.length !== 33) {
29
+ throw new SessionValidationError({
30
+ code: SessionValidationErrorCode.InvalidSessionID,
31
+ message: "Session ID must be 33 bytes long",
32
+ });
33
+ }
34
+ const prefix = sessionIdBytes[0];
35
+ if (prefix !== 0x15) {
36
+ throw new SessionValidationError({
37
+ code: SessionValidationErrorCode.InvalidSessionID,
38
+ message: "Only 15-prefixed Session IDs can be unblinded with this function",
39
+ });
40
+ }
41
+ const blindedKey = sessionIdBytes.subarray(1);
42
+ let serverPublicKey;
43
+ if (typeof sogsPublicKey === "string") {
44
+ serverPublicKey = hexToBytes(sogsPublicKey);
45
+ }
46
+ else {
47
+ serverPublicKey = sogsPublicKey;
48
+ }
49
+ if (serverPublicKey.length !== 32) {
50
+ throw new SessionValidationError({
51
+ code: SessionValidationErrorCode.InvalidOptions,
52
+ message: "sogsPublicKey must be 32 bytes long",
53
+ });
54
+ }
55
+ const ftSessionId = (kA) => keyToSessionId(0x05, kA);
56
+ if (prefix === 0x15) {
57
+ const x25519PublicKey = unblindKey15({
58
+ blindedKey,
59
+ serverPublicKey,
60
+ });
61
+ return ftSessionId(x25519PublicKey);
62
+ }
63
+ }
@@ -0,0 +1,3 @@
1
+ export declare const hexRegex: RegExp;
2
+ export declare function getBlindingK(input: Uint8Array): Uint8Array;
3
+ export declare function keyToSessionId(prefix: number, key: Uint8Array): string;
package/dist/utils.js ADDED
@@ -0,0 +1,14 @@
1
+ import { blake2b } from "@noble/hashes/blake2.js";
2
+ import { bytesToHex } from "@noble/curves/utils.js";
3
+ import { crypto_core_ed25519_scalar_reduce } from "./scalar-math";
4
+ export const hexRegex = /^[0-9a-fA-F]+$/i;
5
+ export function getBlindingK(input) {
6
+ const serverPkHash = blake2b(input, {
7
+ dkLen: 64,
8
+ });
9
+ const k = crypto_core_ed25519_scalar_reduce(serverPkHash);
10
+ return k;
11
+ }
12
+ export function keyToSessionId(prefix, key) {
13
+ return bytesToHex(new Uint8Array([prefix, ...key]));
14
+ }
package/package.json CHANGED
@@ -1,37 +1,46 @@
1
1
  {
2
- "name": "@session.js/blinded-session-id",
3
- "type": "module",
4
- "description": "Utility JavaScript library with methods to work with Session's blinded Session ID",
5
- "version": "1.0.3",
6
- "author": "Viktor Shchelochkov <hi@hloth.dev> (https://hloth.dev)",
7
- "main": "dist/index.js",
8
- "types": "dist/index.d.ts",
9
- "repository": {
10
- "type": "git",
11
- "url": "git+https://git.hloth.dev/session.js/blinded-session-id.git"
12
- },
13
- "bugs": {
14
- "url": "https://git.hloth.dev/session.js/blinded-session-id/issues"
15
- },
16
- "homepage": "https://git.hloth.dev/session.js/blinded-session-id#readme",
17
- "license": "MIT",
18
- "funding": "https://hloth.dev/donate",
19
- "scripts": {
20
- "build": "rm -rf dist && tsc --project tsconfig.build.json"
21
- },
22
- "files": [
23
- "dist/index.js",
24
- "dist/index.d.ts"
25
- ],
26
- "dependencies": {
27
- "@noble/curves": "^2.0.1",
28
- "@noble/hashes": "^2.0.1"
29
- },
30
- "peerDependencies": {
31
- "typescript": "^5.0.0"
32
- },
33
- "devDependencies": {
34
- "@types/bun": "^1.1.6",
35
- "@types/lodash": "^4.17.7"
36
- }
2
+ "name": "@session.js/blinded-session-id",
3
+ "version": "1.0.5",
4
+ "description": "Utility JavaScript library with methods to work with Session's blinded Session ID",
5
+ "homepage": "https://git.hloth.dev/session.js/blinded-session-id#readme",
6
+ "bugs": {
7
+ "url": "https://git.hloth.dev/session.js/blinded-session-id/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://git.hloth.dev/session.js/blinded-session-id.git"
12
+ },
13
+ "funding": "https://hloth.dev/donate",
14
+ "license": "MIT",
15
+ "author": "Viktor Shchelochkov <hi@hloth.dev> (https://hloth.dev)",
16
+ "type": "module",
17
+ "main": "dist/index.js",
18
+ "types": "dist/index.d.ts",
19
+ "files": [
20
+ "dist/**/*.js",
21
+ "dist/**/*.d.ts",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "scripts": {
26
+ "build": "rm -rf dist && tsc --project tsconfig.build.json"
27
+ },
28
+ "dependencies": {
29
+ "@noble/curves": "^2.0.1",
30
+ "@noble/hashes": "^2.0.1",
31
+ "@session.js/errors": "^1.0.11"
32
+ },
33
+ "devDependencies": {
34
+ "@eslint/compat": "^2.0.1",
35
+ "@eslint/js": "^9.39.2",
36
+ "@session.js/keypair": "^1.0.8",
37
+ "@session.js/mnemonic": "^1.0.6",
38
+ "@types/bun": "latest",
39
+ "eslint-config-prettier": "^10.1.8",
40
+ "prettier": "^3.8.0",
41
+ "typescript-eslint": "^8.53.1"
42
+ },
43
+ "peerDependencies": {
44
+ "typescript": "^5.0.0"
45
+ }
37
46
  }