@lightsparkdev/core 0.3.10 → 1.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @lightsparkdev/core
2
2
 
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 1f00a50: Change SigningKey hashing method depending on environment
8
+ BREAKING: NodeKeyCaches loadKey now requires signingKeyType as a parameter
9
+
10
+ ## 0.3.11
11
+
12
+ ### Patch Changes
13
+
14
+ - 4ffd9a1: Upgrade prettier, fix lint configs, move ls-react-native-crypto-app to examples
15
+
3
16
  ## 0.3.10
4
17
 
5
18
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -36,13 +36,20 @@ __export(src_exports, {
36
36
  LightsparkException: () => LightsparkException_default,
37
37
  LightsparkSigningException: () => LightsparkSigningException_default,
38
38
  NodeKeyCache: () => NodeKeyCache_default,
39
+ RSASigningKey: () => RSASigningKey,
39
40
  Requester: () => Requester_default,
41
+ Secp256k1SigningKey: () => Secp256k1SigningKey,
40
42
  ServerEnvironment: () => ServerEnvironment_default,
43
+ SigningKey: () => SigningKey,
44
+ SigningKeyType: () => SigningKeyType,
41
45
  StubAuthProvider: () => StubAuthProvider,
42
46
  apiDomainForEnvironment: () => apiDomainForEnvironment,
43
47
  b64decode: () => b64decode,
44
48
  b64encode: () => b64encode,
49
+ bytesToHex: () => bytesToHex,
45
50
  convertCurrencyAmount: () => convertCurrencyAmount,
51
+ createSha256Hash: () => createSha256Hash,
52
+ hexToBytes: () => hexToBytes,
46
53
  isBrowser: () => isBrowser,
47
54
  isNode: () => isNode,
48
55
  isType: () => isType,
@@ -348,6 +355,50 @@ var KeyOrAlias = {
348
355
 
349
356
  // src/crypto/NodeKeyCache.ts
350
357
  var import_auto_bind = __toESM(require("auto-bind"), 1);
358
+
359
+ // src/crypto/SigningKey.ts
360
+ var import_secp256k1 = __toESM(require("secp256k1"), 1);
361
+ function isAlias(key) {
362
+ return "alias" in key;
363
+ }
364
+ var SigningKey = class {
365
+ type;
366
+ constructor(type) {
367
+ this.type = type;
368
+ }
369
+ };
370
+ var RSASigningKey = class extends SigningKey {
371
+ constructor(privateKey, cryptoImpl) {
372
+ super("RSASigningKey" /* RSASigningKey */);
373
+ this.privateKey = privateKey;
374
+ this.cryptoImpl = cryptoImpl;
375
+ }
376
+ async sign(data) {
377
+ const key = isAlias(this.privateKey) ? this.privateKey.alias : this.privateKey;
378
+ return this.cryptoImpl.sign(key, data);
379
+ }
380
+ };
381
+ var Secp256k1SigningKey = class extends SigningKey {
382
+ constructor(privateKey) {
383
+ super("Secp256k1SigningKey" /* Secp256k1SigningKey */);
384
+ this.privateKey = privateKey;
385
+ }
386
+ async sign(data) {
387
+ const keyBytes = new Uint8Array(hexToBytes(this.privateKey));
388
+ const hash = await createSha256Hash(data);
389
+ const signResult = import_secp256k1.default.ecdsaSign(hash, keyBytes);
390
+ return signResult.signature;
391
+ }
392
+ };
393
+
394
+ // src/crypto/types.ts
395
+ var SigningKeyType = /* @__PURE__ */ ((SigningKeyType2) => {
396
+ SigningKeyType2["RSASigningKey"] = "RSASigningKey";
397
+ SigningKeyType2["Secp256k1SigningKey"] = "Secp256k1SigningKey";
398
+ return SigningKeyType2;
399
+ })(SigningKeyType || {});
400
+
401
+ // src/crypto/NodeKeyCache.ts
351
402
  var NodeKeyCache = class {
352
403
  constructor(cryptoImpl = DefaultCrypto) {
353
404
  this.cryptoImpl = cryptoImpl;
@@ -355,16 +406,35 @@ var NodeKeyCache = class {
355
406
  (0, import_auto_bind.default)(this);
356
407
  }
357
408
  idToKey;
358
- async loadKey(id, keyOrAlias) {
409
+ async loadKey(id, keyOrAlias, signingKeyType) {
410
+ let signingKey;
359
411
  if (keyOrAlias.alias !== void 0) {
360
- this.idToKey.set(id, keyOrAlias.alias);
361
- return keyOrAlias.alias;
412
+ switch (signingKeyType) {
413
+ case "RSASigningKey" /* RSASigningKey */:
414
+ signingKey = new RSASigningKey(
415
+ { alias: keyOrAlias.alias },
416
+ this.cryptoImpl
417
+ );
418
+ break;
419
+ default:
420
+ throw new LightsparkSigningException_default(
421
+ `Aliases are not supported for signing key type ${signingKeyType}`
422
+ );
423
+ }
424
+ this.idToKey.set(id, signingKey);
425
+ return signingKey;
362
426
  }
363
- const decoded = b64decode(this.stripPemTags(keyOrAlias.key));
364
427
  try {
365
- const key = await this.cryptoImpl.importPrivateSigningKey(decoded);
366
- this.idToKey.set(id, key);
367
- return key;
428
+ if (signingKeyType === "Secp256k1SigningKey" /* Secp256k1SigningKey */) {
429
+ signingKey = new Secp256k1SigningKey(keyOrAlias.key);
430
+ } else {
431
+ const decoded = b64decode(this.stripPemTags(keyOrAlias.key));
432
+ const cryptoKeyOrAlias = await this.cryptoImpl.importPrivateSigningKey(decoded);
433
+ const key = typeof cryptoKeyOrAlias === "string" ? { alias: cryptoKeyOrAlias } : cryptoKeyOrAlias;
434
+ signingKey = new RSASigningKey(key, this.cryptoImpl);
435
+ }
436
+ this.idToKey.set(id, signingKey);
437
+ return signingKey;
368
438
  } catch (e) {
369
439
  console.log("Error importing key: ", e);
370
440
  }
@@ -564,7 +634,7 @@ var Requester = class {
564
634
  const encodedPayload = new TextEncoderImpl().encode(
565
635
  JSON.stringify(payload)
566
636
  );
567
- const signedPayload = await this.cryptoImpl.sign(key, encodedPayload);
637
+ const signedPayload = await key.sign(encodedPayload);
568
638
  const encodedSignedPayload = b64encode(signedPayload);
569
639
  headers["X-Lightspark-Signing"] = JSON.stringify({
570
640
  v: "1",
@@ -591,6 +661,16 @@ var apiDomainForEnvironment = (environment) => {
591
661
  };
592
662
  var ServerEnvironment_default = ServerEnvironment;
593
663
 
664
+ // src/utils/createHash.ts
665
+ var createSha256Hash = async (data) => {
666
+ if (isBrowser) {
667
+ return new Uint8Array(await window.crypto.subtle.digest("SHA-256", data));
668
+ } else {
669
+ const { createHash } = await import("crypto");
670
+ return createHash("sha256").update(data).digest();
671
+ }
672
+ };
673
+
594
674
  // src/utils/currency.ts
595
675
  var CONVERSION_MAP = {
596
676
  ["BITCOIN" /* BITCOIN */]: {
@@ -661,6 +741,20 @@ var convertCurrencyAmount = (from, toUnit) => {
661
741
  };
662
742
  };
663
743
 
744
+ // src/utils/hex.ts
745
+ var bytesToHex = (bytes) => {
746
+ return bytes.reduce((acc, byte) => {
747
+ return acc += ("0" + byte.toString(16)).slice(-2);
748
+ }, "");
749
+ };
750
+ var hexToBytes = (hex) => {
751
+ const bytes = [];
752
+ for (let c = 0; c < hex.length; c += 2) {
753
+ bytes.push(parseInt(hex.substr(c, 2), 16));
754
+ }
755
+ return Uint8Array.from(bytes);
756
+ };
757
+
664
758
  // src/utils/types.ts
665
759
  var isType = (typename) => (node) => {
666
760
  return node?.__typename === typename;
@@ -673,13 +767,20 @@ var isType = (typename) => (node) => {
673
767
  LightsparkException,
674
768
  LightsparkSigningException,
675
769
  NodeKeyCache,
770
+ RSASigningKey,
676
771
  Requester,
772
+ Secp256k1SigningKey,
677
773
  ServerEnvironment,
774
+ SigningKey,
775
+ SigningKeyType,
678
776
  StubAuthProvider,
679
777
  apiDomainForEnvironment,
680
778
  b64decode,
681
779
  b64encode,
780
+ bytesToHex,
682
781
  convertCurrencyAmount,
782
+ createSha256Hash,
783
+ hexToBytes,
683
784
  isBrowser,
684
785
  isNode,
685
786
  isType,
package/dist/index.d.ts CHANGED
@@ -66,12 +66,37 @@ declare const KeyOrAlias: {
66
66
  alias: (alias: string) => OnlyAlias;
67
67
  };
68
68
 
69
+ interface Alias {
70
+ alias: string;
71
+ }
72
+ declare abstract class SigningKey {
73
+ readonly type: SigningKeyType;
74
+ constructor(type: SigningKeyType);
75
+ abstract sign(data: Uint8Array): Promise<ArrayBuffer>;
76
+ }
77
+ declare class RSASigningKey extends SigningKey {
78
+ private readonly privateKey;
79
+ private readonly cryptoImpl;
80
+ constructor(privateKey: CryptoKey | Alias, cryptoImpl: CryptoInterface);
81
+ sign(data: Uint8Array): Promise<ArrayBuffer>;
82
+ }
83
+ declare class Secp256k1SigningKey extends SigningKey {
84
+ private readonly privateKey;
85
+ constructor(privateKey: string);
86
+ sign(data: Uint8Array): Promise<Uint8Array>;
87
+ }
88
+
89
+ declare enum SigningKeyType {
90
+ RSASigningKey = "RSASigningKey",
91
+ Secp256k1SigningKey = "Secp256k1SigningKey"
92
+ }
93
+
69
94
  declare class NodeKeyCache {
70
95
  private readonly cryptoImpl;
71
96
  private idToKey;
72
97
  constructor(cryptoImpl?: CryptoInterface);
73
- loadKey(id: string, keyOrAlias: KeyOrAliasType): Promise<CryptoKey | string | null>;
74
- getKey(id: string): CryptoKey | string | undefined;
98
+ loadKey(id: string, keyOrAlias: KeyOrAliasType, signingKeyType: SigningKeyType): Promise<SigningKey | null>;
99
+ getKey(id: string): SigningKey | undefined;
75
100
  hasKey(id: string): boolean;
76
101
  private stripPemTags;
77
102
  }
@@ -124,6 +149,8 @@ declare const b64decode: (encoded: string) => Uint8Array;
124
149
  declare const urlsafe_b64decode: (encoded: string) => Uint8Array;
125
150
  declare const b64encode: (data: ArrayBuffer) => string;
126
151
 
152
+ declare const createSha256Hash: (data: Uint8Array) => Promise<Uint8Array>;
153
+
127
154
  /** Represents the value and unit for an amount of currency. **/
128
155
  type CurrencyAmount = {
129
156
  /** The original numeric value for this CurrencyAmount. **/
@@ -169,6 +196,9 @@ declare const convertCurrencyAmount: (from: CurrencyAmount, toUnit: CurrencyUnit
169
196
  declare const isBrowser: boolean;
170
197
  declare const isNode: boolean;
171
198
 
199
+ declare const bytesToHex: (bytes: Uint8Array) => string;
200
+ declare const hexToBytes: (hex: string) => Uint8Array;
201
+
172
202
  type Maybe<T> = T | null | undefined;
173
203
  type ExpandRecursively<T> = T extends object ? T extends infer O ? {
174
204
  [K in keyof O]: ExpandRecursively<O[K]>;
@@ -183,4 +213,4 @@ declare const isType: <T extends string>(typename: T) => <N extends {
183
213
  __typename: T;
184
214
  }>;
185
215
 
186
- export { AuthProvider, ById, CryptoInterface, DefaultCrypto, ExpandRecursively, GeneratedKeyPair, KeyOrAlias, KeyOrAliasType, LightsparkAuthException, LightsparkException, LightsparkSigningException, Maybe, NodeKeyCache, OmitTypename, Query, Requester, ServerEnvironment, StubAuthProvider, apiDomainForEnvironment, b64decode, b64encode, convertCurrencyAmount, isBrowser, isNode, isType, urlsafe_b64decode };
216
+ export { AuthProvider, ById, CryptoInterface, DefaultCrypto, ExpandRecursively, GeneratedKeyPair, KeyOrAlias, KeyOrAliasType, LightsparkAuthException, LightsparkException, LightsparkSigningException, Maybe, NodeKeyCache, OmitTypename, Query, RSASigningKey, Requester, Secp256k1SigningKey, ServerEnvironment, SigningKey, SigningKeyType, StubAuthProvider, apiDomainForEnvironment, b64decode, b64encode, bytesToHex, convertCurrencyAmount, createSha256Hash, hexToBytes, isBrowser, isNode, isType, urlsafe_b64decode };
package/dist/index.js CHANGED
@@ -296,6 +296,50 @@ var KeyOrAlias = {
296
296
 
297
297
  // src/crypto/NodeKeyCache.ts
298
298
  import autoBind from "auto-bind";
299
+
300
+ // src/crypto/SigningKey.ts
301
+ import secp256k1 from "secp256k1";
302
+ function isAlias(key) {
303
+ return "alias" in key;
304
+ }
305
+ var SigningKey = class {
306
+ type;
307
+ constructor(type) {
308
+ this.type = type;
309
+ }
310
+ };
311
+ var RSASigningKey = class extends SigningKey {
312
+ constructor(privateKey, cryptoImpl) {
313
+ super("RSASigningKey" /* RSASigningKey */);
314
+ this.privateKey = privateKey;
315
+ this.cryptoImpl = cryptoImpl;
316
+ }
317
+ async sign(data) {
318
+ const key = isAlias(this.privateKey) ? this.privateKey.alias : this.privateKey;
319
+ return this.cryptoImpl.sign(key, data);
320
+ }
321
+ };
322
+ var Secp256k1SigningKey = class extends SigningKey {
323
+ constructor(privateKey) {
324
+ super("Secp256k1SigningKey" /* Secp256k1SigningKey */);
325
+ this.privateKey = privateKey;
326
+ }
327
+ async sign(data) {
328
+ const keyBytes = new Uint8Array(hexToBytes(this.privateKey));
329
+ const hash = await createSha256Hash(data);
330
+ const signResult = secp256k1.ecdsaSign(hash, keyBytes);
331
+ return signResult.signature;
332
+ }
333
+ };
334
+
335
+ // src/crypto/types.ts
336
+ var SigningKeyType = /* @__PURE__ */ ((SigningKeyType2) => {
337
+ SigningKeyType2["RSASigningKey"] = "RSASigningKey";
338
+ SigningKeyType2["Secp256k1SigningKey"] = "Secp256k1SigningKey";
339
+ return SigningKeyType2;
340
+ })(SigningKeyType || {});
341
+
342
+ // src/crypto/NodeKeyCache.ts
299
343
  var NodeKeyCache = class {
300
344
  constructor(cryptoImpl = DefaultCrypto) {
301
345
  this.cryptoImpl = cryptoImpl;
@@ -303,16 +347,35 @@ var NodeKeyCache = class {
303
347
  autoBind(this);
304
348
  }
305
349
  idToKey;
306
- async loadKey(id, keyOrAlias) {
350
+ async loadKey(id, keyOrAlias, signingKeyType) {
351
+ let signingKey;
307
352
  if (keyOrAlias.alias !== void 0) {
308
- this.idToKey.set(id, keyOrAlias.alias);
309
- return keyOrAlias.alias;
353
+ switch (signingKeyType) {
354
+ case "RSASigningKey" /* RSASigningKey */:
355
+ signingKey = new RSASigningKey(
356
+ { alias: keyOrAlias.alias },
357
+ this.cryptoImpl
358
+ );
359
+ break;
360
+ default:
361
+ throw new LightsparkSigningException_default(
362
+ `Aliases are not supported for signing key type ${signingKeyType}`
363
+ );
364
+ }
365
+ this.idToKey.set(id, signingKey);
366
+ return signingKey;
310
367
  }
311
- const decoded = b64decode(this.stripPemTags(keyOrAlias.key));
312
368
  try {
313
- const key = await this.cryptoImpl.importPrivateSigningKey(decoded);
314
- this.idToKey.set(id, key);
315
- return key;
369
+ if (signingKeyType === "Secp256k1SigningKey" /* Secp256k1SigningKey */) {
370
+ signingKey = new Secp256k1SigningKey(keyOrAlias.key);
371
+ } else {
372
+ const decoded = b64decode(this.stripPemTags(keyOrAlias.key));
373
+ const cryptoKeyOrAlias = await this.cryptoImpl.importPrivateSigningKey(decoded);
374
+ const key = typeof cryptoKeyOrAlias === "string" ? { alias: cryptoKeyOrAlias } : cryptoKeyOrAlias;
375
+ signingKey = new RSASigningKey(key, this.cryptoImpl);
376
+ }
377
+ this.idToKey.set(id, signingKey);
378
+ return signingKey;
316
379
  } catch (e) {
317
380
  console.log("Error importing key: ", e);
318
381
  }
@@ -512,7 +575,7 @@ var Requester = class {
512
575
  const encodedPayload = new TextEncoderImpl().encode(
513
576
  JSON.stringify(payload)
514
577
  );
515
- const signedPayload = await this.cryptoImpl.sign(key, encodedPayload);
578
+ const signedPayload = await key.sign(encodedPayload);
516
579
  const encodedSignedPayload = b64encode(signedPayload);
517
580
  headers["X-Lightspark-Signing"] = JSON.stringify({
518
581
  v: "1",
@@ -539,6 +602,16 @@ var apiDomainForEnvironment = (environment) => {
539
602
  };
540
603
  var ServerEnvironment_default = ServerEnvironment;
541
604
 
605
+ // src/utils/createHash.ts
606
+ var createSha256Hash = async (data) => {
607
+ if (isBrowser) {
608
+ return new Uint8Array(await window.crypto.subtle.digest("SHA-256", data));
609
+ } else {
610
+ const { createHash } = await import("crypto");
611
+ return createHash("sha256").update(data).digest();
612
+ }
613
+ };
614
+
542
615
  // src/utils/currency.ts
543
616
  var CONVERSION_MAP = {
544
617
  ["BITCOIN" /* BITCOIN */]: {
@@ -609,6 +682,20 @@ var convertCurrencyAmount = (from, toUnit) => {
609
682
  };
610
683
  };
611
684
 
685
+ // src/utils/hex.ts
686
+ var bytesToHex = (bytes) => {
687
+ return bytes.reduce((acc, byte) => {
688
+ return acc += ("0" + byte.toString(16)).slice(-2);
689
+ }, "");
690
+ };
691
+ var hexToBytes = (hex) => {
692
+ const bytes = [];
693
+ for (let c = 0; c < hex.length; c += 2) {
694
+ bytes.push(parseInt(hex.substr(c, 2), 16));
695
+ }
696
+ return Uint8Array.from(bytes);
697
+ };
698
+
612
699
  // src/utils/types.ts
613
700
  var isType = (typename) => (node) => {
614
701
  return node?.__typename === typename;
@@ -620,13 +707,20 @@ export {
620
707
  LightsparkException_default as LightsparkException,
621
708
  LightsparkSigningException_default as LightsparkSigningException,
622
709
  NodeKeyCache_default as NodeKeyCache,
710
+ RSASigningKey,
623
711
  Requester_default as Requester,
712
+ Secp256k1SigningKey,
624
713
  ServerEnvironment_default as ServerEnvironment,
714
+ SigningKey,
715
+ SigningKeyType,
625
716
  StubAuthProvider,
626
717
  apiDomainForEnvironment,
627
718
  b64decode,
628
719
  b64encode,
720
+ bytesToHex,
629
721
  convertCurrencyAmount,
722
+ createSha256Hash,
723
+ hexToBytes,
630
724
  isBrowser,
631
725
  isNode,
632
726
  isType,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightsparkdev/core",
3
- "version": "0.3.10",
3
+ "version": "1.0.0",
4
4
  "description": "Lightspark JS SDK",
5
5
  "author": "Lightspark Inc.",
6
6
  "keywords": [
@@ -36,7 +36,7 @@
36
36
  }
37
37
  },
38
38
  "engines": {
39
- "node": ">=14.16"
39
+ "node": ">=18.17.0"
40
40
  },
41
41
  "browser": {
42
42
  "crypto": false
@@ -68,6 +68,7 @@
68
68
  "dayjs": "^1.11.7",
69
69
  "graphql": "^16.6.0",
70
70
  "graphql-ws": "^5.11.3",
71
+ "secp256k1": "^5.0.0",
71
72
  "text-encoding": "^0.7.0",
72
73
  "ws": "^8.12.1",
73
74
  "zen-observable-ts": "^1.1.0"
@@ -76,13 +77,14 @@
76
77
  "@lightsparkdev/eslint-config": "*",
77
78
  "@lightsparkdev/tsconfig": "0.0.0",
78
79
  "@types/crypto-js": "^4.1.1",
80
+ "@types/secp256k1": "^4.0.3",
79
81
  "@types/ws": "^8.5.4",
80
82
  "eslint": "^8.3.0",
81
83
  "eslint-watch": "^8.0.0",
82
- "jest": "^29.4.1",
83
- "prettier": "2.8.7",
84
+ "jest": "^29.6.2",
85
+ "prettier": "3.0.2",
84
86
  "prettier-plugin-organize-imports": "^3.2.2",
85
- "ts-jest": "^29.0.5",
87
+ "ts-jest": "^29.1.1",
86
88
  "tsc-absolute": "^1.0.1",
87
89
  "tsup": "^6.7.0",
88
90
  "typescript": "^4.9.5"
@@ -8,7 +8,7 @@ class LightsparkException extends Error {
8
8
  constructor(
9
9
  code: string,
10
10
  message: string,
11
- extraInfo?: Record<string, unknown>
11
+ extraInfo?: Record<string, unknown>,
12
12
  ) {
13
13
  super(message);
14
14
  this.code = code;
@@ -4,7 +4,7 @@ enum ServerEnvironment {
4
4
  }
5
5
 
6
6
  export const apiDomainForEnvironment = (
7
- environment: ServerEnvironment
7
+ environment: ServerEnvironment,
8
8
  ): string => {
9
9
  switch (environment) {
10
10
  case ServerEnvironment.DEV:
@@ -7,6 +7,6 @@ export default interface AuthProvider {
7
7
  addAuthHeaders(headers: Headers): Promise<Headers>;
8
8
  isAuthorized(): Promise<boolean>;
9
9
  addWsConnectionParams(
10
- params: WsConnectionParams
10
+ params: WsConnectionParams,
11
11
  ): Promise<WsConnectionParams>;
12
12
  }
@@ -4,11 +4,18 @@ import autoBind from "auto-bind";
4
4
 
5
5
  import { b64decode } from "../utils/base64.js";
6
6
  import type { CryptoInterface } from "./crypto.js";
7
- import { DefaultCrypto } from "./crypto.js";
7
+ import { DefaultCrypto, LightsparkSigningException } from "./crypto.js";
8
8
  import type { KeyOrAliasType } from "./KeyOrAlias.js";
9
+ import {
10
+ RSASigningKey,
11
+ Secp256k1SigningKey,
12
+ type SigningKey,
13
+ } from "./SigningKey.js";
14
+ import { SigningKeyType } from "./types.js";
9
15
 
10
16
  class NodeKeyCache {
11
- private idToKey: Map<string, CryptoKey | string>;
17
+ private idToKey: Map<string, SigningKey>;
18
+
12
19
  constructor(private readonly cryptoImpl: CryptoInterface = DefaultCrypto) {
13
20
  this.idToKey = new Map();
14
21
  autoBind(this);
@@ -16,24 +23,52 @@ class NodeKeyCache {
16
23
 
17
24
  public async loadKey(
18
25
  id: string,
19
- keyOrAlias: KeyOrAliasType
20
- ): Promise<CryptoKey | string | null> {
26
+ keyOrAlias: KeyOrAliasType,
27
+ signingKeyType: SigningKeyType,
28
+ ): Promise<SigningKey | null> {
29
+ let signingKey: SigningKey;
30
+
21
31
  if (keyOrAlias.alias !== undefined) {
22
- this.idToKey.set(id, keyOrAlias.alias);
23
- return keyOrAlias.alias;
32
+ switch (signingKeyType) {
33
+ case SigningKeyType.RSASigningKey:
34
+ signingKey = new RSASigningKey(
35
+ { alias: keyOrAlias.alias },
36
+ this.cryptoImpl,
37
+ );
38
+ break;
39
+ default:
40
+ throw new LightsparkSigningException(
41
+ `Aliases are not supported for signing key type ${signingKeyType}`,
42
+ );
43
+ }
44
+
45
+ this.idToKey.set(id, signingKey);
46
+ return signingKey;
24
47
  }
25
- const decoded = b64decode(this.stripPemTags(keyOrAlias.key));
48
+
26
49
  try {
27
- const key = await this.cryptoImpl.importPrivateSigningKey(decoded);
28
- this.idToKey.set(id, key);
29
- return key;
50
+ if (signingKeyType === SigningKeyType.Secp256k1SigningKey) {
51
+ signingKey = new Secp256k1SigningKey(keyOrAlias.key);
52
+ } else {
53
+ const decoded = b64decode(this.stripPemTags(keyOrAlias.key));
54
+ const cryptoKeyOrAlias =
55
+ await this.cryptoImpl.importPrivateSigningKey(decoded);
56
+ const key =
57
+ typeof cryptoKeyOrAlias === "string"
58
+ ? { alias: cryptoKeyOrAlias }
59
+ : cryptoKeyOrAlias;
60
+ signingKey = new RSASigningKey(key, this.cryptoImpl);
61
+ }
62
+
63
+ this.idToKey.set(id, signingKey);
64
+ return signingKey;
30
65
  } catch (e) {
31
66
  console.log("Error importing key: ", e);
32
67
  }
33
68
  return null;
34
69
  }
35
70
 
36
- public getKey(id: string): CryptoKey | string | undefined {
71
+ public getKey(id: string): SigningKey | undefined {
37
72
  return this.idToKey.get(id);
38
73
  }
39
74
 
@@ -0,0 +1,54 @@
1
+ import secp256k1 from "secp256k1";
2
+ import {
3
+ createSha256Hash,
4
+ hexToBytes,
5
+ SigningKeyType,
6
+ type CryptoInterface,
7
+ } from "../index.js";
8
+
9
+ interface Alias {
10
+ alias: string;
11
+ }
12
+
13
+ function isAlias(key: CryptoKey | Alias): key is Alias {
14
+ return "alias" in key;
15
+ }
16
+
17
+ export abstract class SigningKey {
18
+ readonly type: SigningKeyType;
19
+
20
+ constructor(type: SigningKeyType) {
21
+ this.type = type;
22
+ }
23
+
24
+ abstract sign(data: Uint8Array): Promise<ArrayBuffer>;
25
+ }
26
+
27
+ export class RSASigningKey extends SigningKey {
28
+ constructor(
29
+ private readonly privateKey: CryptoKey | Alias,
30
+ private readonly cryptoImpl: CryptoInterface,
31
+ ) {
32
+ super(SigningKeyType.RSASigningKey);
33
+ }
34
+
35
+ async sign(data: Uint8Array) {
36
+ const key = isAlias(this.privateKey)
37
+ ? this.privateKey.alias
38
+ : this.privateKey;
39
+ return this.cryptoImpl.sign(key, data);
40
+ }
41
+ }
42
+
43
+ export class Secp256k1SigningKey extends SigningKey {
44
+ constructor(private readonly privateKey: string) {
45
+ super(SigningKeyType.Secp256k1SigningKey);
46
+ }
47
+
48
+ async sign(data: Uint8Array) {
49
+ const keyBytes = new Uint8Array(hexToBytes(this.privateKey));
50
+ const hash = await createSha256Hash(data);
51
+ const signResult = secp256k1.ecdsaSign(hash, keyBytes);
52
+ return signResult.signature;
53
+ }
54
+ }
@@ -14,21 +14,21 @@ export type CryptoInterface = {
14
14
  decryptSecretWithNodePassword: (
15
15
  cipher: string,
16
16
  encryptedSecret: string,
17
- nodePassword: string
17
+ nodePassword: string,
18
18
  ) => Promise<ArrayBuffer | null>;
19
19
 
20
20
  generateSigningKeyPair: () => Promise<GeneratedKeyPair>;
21
21
 
22
22
  serializeSigningKey: (
23
23
  key: CryptoKey | string,
24
- format: "pkcs8" | "spki"
24
+ format: "pkcs8" | "spki",
25
25
  ) => Promise<ArrayBuffer>;
26
26
 
27
27
  getNonce: () => Promise<number>;
28
28
 
29
29
  sign: (
30
30
  keyOrAlias: CryptoKey | string,
31
- data: Uint8Array
31
+ data: Uint8Array,
32
32
  ) => Promise<ArrayBuffer>;
33
33
 
34
34
  importPrivateSigningKey: (keyData: Uint8Array) => Promise<CryptoKey | string>;
@@ -80,7 +80,7 @@ const deriveKey = async (
80
80
  salt: ArrayBuffer,
81
81
  iterations: number,
82
82
  algorithm: string,
83
- bit_len: number
83
+ bit_len: number,
84
84
  ): Promise<[CryptoKey, ArrayBuffer]> => {
85
85
  const enc = new TextEncoder();
86
86
  const cryptoImpl = await getCrypto();
@@ -89,7 +89,7 @@ const deriveKey = async (
89
89
  enc.encode(password),
90
90
  "PBKDF2",
91
91
  false,
92
- ["deriveBits", "deriveKey"]
92
+ ["deriveBits", "deriveKey"],
93
93
  );
94
94
 
95
95
  const derived = await cryptoImpl.subtle.deriveBits(
@@ -100,7 +100,7 @@ const deriveKey = async (
100
100
  hash: "SHA-256",
101
101
  },
102
102
  password_key,
103
- bit_len
103
+ bit_len,
104
104
  );
105
105
 
106
106
  // Split the derived bytes into a 32 byte AES key and a 16 byte IV
@@ -109,7 +109,7 @@ const deriveKey = async (
109
109
  derived.slice(0, 32),
110
110
  { name: algorithm, length: 256 },
111
111
  false,
112
- ["encrypt", "decrypt"]
112
+ ["encrypt", "decrypt"],
113
113
  );
114
114
 
115
115
  const iv = derived.slice(32);
@@ -120,7 +120,7 @@ const deriveKey = async (
120
120
  const decrypt = async (
121
121
  header_json: string,
122
122
  ciphertext: string,
123
- password: string
123
+ password: string,
124
124
  ): Promise<ArrayBuffer> => {
125
125
  let decoded = b64decode(ciphertext);
126
126
 
@@ -139,7 +139,7 @@ const decrypt = async (
139
139
  if (header.v < 0 || header.v > 4) {
140
140
  throw new LightsparkException(
141
141
  "DecryptionError",
142
- "Unknown version ".concat(header.v)
142
+ "Unknown version ".concat(header.v),
143
143
  );
144
144
  }
145
145
 
@@ -158,12 +158,12 @@ const decrypt = async (
158
158
  salt,
159
159
  header.i,
160
160
  algorithm,
161
- 256
161
+ 256,
162
162
  );
163
163
  return await cryptoImpl.subtle.decrypt(
164
164
  { name: algorithm, iv: nonce.buffer },
165
165
  key,
166
- cipherText
166
+ cipherText,
167
167
  );
168
168
  } else {
169
169
  const salt = decoded.slice(0, salt_len);
@@ -173,12 +173,12 @@ const decrypt = async (
173
173
  salt,
174
174
  header.i,
175
175
  algorithm,
176
- bit_len
176
+ bit_len,
177
177
  );
178
178
  return await cryptoImpl.subtle.decrypt(
179
179
  { name: algorithm, iv },
180
180
  key,
181
- encrypted
181
+ encrypted,
182
182
  );
183
183
  }
184
184
  };
@@ -186,7 +186,7 @@ const decrypt = async (
186
186
  async function decryptSecretWithNodePassword(
187
187
  cipher: string,
188
188
  encryptedSecret: string,
189
- nodePassword: string
189
+ nodePassword: string,
190
190
  ): Promise<ArrayBuffer | null> {
191
191
  let decryptedValue: ArrayBuffer | null = null;
192
192
  try {
@@ -209,18 +209,18 @@ const generateSigningKeyPair = async (): Promise<GeneratedKeyPair> => {
209
209
  hash: "SHA-256",
210
210
  },
211
211
  /*extractable*/ true,
212
- /*keyUsages*/ ["sign", "verify"]
212
+ /*keyUsages*/ ["sign", "verify"],
213
213
  );
214
214
  };
215
215
 
216
216
  const serializeSigningKey = async (
217
217
  key: CryptoKey | string,
218
- format: "pkcs8" | "spki"
218
+ format: "pkcs8" | "spki",
219
219
  ): Promise<ArrayBuffer> => {
220
220
  const cryptoImpl = await getCrypto();
221
221
  return await cryptoImpl.subtle.exportKey(
222
222
  /*format*/ format,
223
- /*key*/ key as CryptoKey
223
+ /*key*/ key as CryptoKey,
224
224
  );
225
225
  };
226
226
 
@@ -231,11 +231,11 @@ const getNonce = async () => {
231
231
 
232
232
  const sign = async (
233
233
  keyOrAlias: CryptoKey | string,
234
- data: Uint8Array
234
+ data: Uint8Array,
235
235
  ): Promise<ArrayBuffer> => {
236
236
  if (typeof keyOrAlias === "string") {
237
237
  throw new LightsparkSigningException(
238
- "Key alias not supported for default crypto."
238
+ "Key alias not supported for default crypto.",
239
239
  );
240
240
  }
241
241
  const cryptoImpl = await getCrypto();
@@ -245,12 +245,12 @@ const sign = async (
245
245
  saltLength: 32,
246
246
  },
247
247
  keyOrAlias as CryptoKey,
248
- data
248
+ data,
249
249
  );
250
250
  };
251
251
 
252
252
  const importPrivateSigningKey = async (
253
- keyData: Uint8Array
253
+ keyData: Uint8Array,
254
254
  ): Promise<CryptoKey | string> => {
255
255
  const cryptoImpl = await getCrypto();
256
256
  return await cryptoImpl.subtle.importKey(
@@ -261,7 +261,7 @@ const importPrivateSigningKey = async (
261
261
  hash: "SHA-256",
262
262
  },
263
263
  /*extractable*/ true,
264
- /*keyUsages*/ ["sign"]
264
+ /*keyUsages*/ ["sign"],
265
265
  );
266
266
  };
267
267
 
@@ -4,3 +4,5 @@ export * from "./crypto.js";
4
4
  export * from "./KeyOrAlias.js";
5
5
  export { default as LightsparkSigningException } from "./LightsparkSigningException.js";
6
6
  export { default as NodeKeyCache } from "./NodeKeyCache.js";
7
+ export * from "./SigningKey.js";
8
+ export * from "./types.js";
@@ -0,0 +1,4 @@
1
+ export enum SigningKeyType {
2
+ RSASigningKey = "RSASigningKey",
3
+ Secp256k1SigningKey = "Secp256k1SigningKey",
4
+ }
@@ -33,7 +33,7 @@ class Requester {
33
33
  private readonly sdkUserAgent: string,
34
34
  private readonly authProvider: AuthProvider = new StubAuthProvider(),
35
35
  private readonly baseUrl: string = DEFAULT_BASE_URL,
36
- private readonly cryptoImpl: CryptoInterface = DefaultCrypto
36
+ private readonly cryptoImpl: CryptoInterface = DefaultCrypto,
37
37
  ) {
38
38
  let websocketImpl;
39
39
  if (typeof WebSocket === "undefined" && typeof window === "undefined") {
@@ -58,14 +58,14 @@ class Requester {
58
58
  query.queryPayload,
59
59
  query.variables || {},
60
60
  query.signingNodeId,
61
- !!query.skipAuth
61
+ !!query.skipAuth,
62
62
  );
63
63
  return query.constructObject(data);
64
64
  }
65
65
 
66
66
  public subscribe<T>(
67
67
  queryPayload: string,
68
- variables: { [key: string]: unknown } = {}
68
+ variables: { [key: string]: unknown } = {},
69
69
  ) {
70
70
  const operationNameRegex = /^\s*(query|mutation|subscription)\s+(\w+)/i;
71
71
  const operationMatch = queryPayload.match(operationNameRegex);
@@ -76,7 +76,7 @@ class Requester {
76
76
  if (operationType == "mutation") {
77
77
  throw new LightsparkException(
78
78
  "InvalidQuery",
79
- "Mutation queries should call makeRawRequest instead"
79
+ "Mutation queries should call makeRawRequest instead",
80
80
  );
81
81
  }
82
82
  // Undefined variables need to be null instead.
@@ -97,7 +97,7 @@ class Requester {
97
97
  next: (data) => observer.next(data as { data: T }),
98
98
  error: (err) => observer.error(err),
99
99
  complete: () => observer.complete(),
100
- })
100
+ }),
101
101
  );
102
102
  }
103
103
 
@@ -105,7 +105,7 @@ class Requester {
105
105
  queryPayload: string,
106
106
  variables: { [key: string]: unknown } = {},
107
107
  signingNodeId: string | undefined = undefined,
108
- skipAuth: boolean = false
108
+ skipAuth: boolean = false,
109
109
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any -- LIG-3400 */
110
110
  ): Promise<any | null> {
111
111
  const operationNameRegex = /^\s*(query|mutation|subscription)\s+(\w+)/i;
@@ -117,7 +117,7 @@ class Requester {
117
117
  if (operationType == "subscription") {
118
118
  throw new LightsparkException(
119
119
  "InvalidQuery",
120
- "Subscription queries should call subscribe instead"
120
+ "Subscription queries should call subscribe instead",
121
121
  );
122
122
  }
123
123
  // Undefined variables need to be null instead.
@@ -147,7 +147,7 @@ class Requester {
147
147
  bodyData = await this.addSigningDataIfNeeded(
148
148
  bodyData,
149
149
  headers,
150
- signingNodeId
150
+ signingNodeId,
151
151
  );
152
152
 
153
153
  let urlWithProtocol = this.baseUrl;
@@ -165,7 +165,7 @@ class Requester {
165
165
  if (!response.ok) {
166
166
  throw new LightsparkException(
167
167
  "RequestFailed",
168
- `Request ${operation} failed. ${response.statusText}`
168
+ `Request ${operation} failed. ${response.statusText}`,
169
169
  );
170
170
  }
171
171
  const responseJson = await response.json();
@@ -173,7 +173,7 @@ class Requester {
173
173
  if (!data) {
174
174
  throw new LightsparkException(
175
175
  "RequestFailed",
176
- `Request ${operation} failed. ${JSON.stringify(responseJson.errors)}`
176
+ `Request ${operation} failed. ${JSON.stringify(responseJson.errors)}`,
177
177
  );
178
178
  }
179
179
  return data;
@@ -192,7 +192,7 @@ class Requester {
192
192
  private async addSigningDataIfNeeded(
193
193
  queryPayload: { query: string; variables: unknown; operationName: string },
194
194
  headers: { [key: string]: string },
195
- signingNodeId: string | undefined
195
+ signingNodeId: string | undefined,
196
196
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any -- LIG-3400 */
197
197
  ): Promise<any> {
198
198
  if (!signingNodeId) {
@@ -217,7 +217,7 @@ class Requester {
217
217
  const key = await this.nodeKeyCache.getKey(signingNodeId);
218
218
  if (!key) {
219
219
  throw new LightsparkSigningException(
220
- "Missing node of encrypted_signing_private_key"
220
+ "Missing node of encrypted_signing_private_key",
221
221
  );
222
222
  }
223
223
 
@@ -226,11 +226,12 @@ class Requester {
226
226
  TextEncoderImpl = (await import("text-encoding")).TextEncoder;
227
227
  }
228
228
  const encodedPayload = new TextEncoderImpl().encode(
229
- JSON.stringify(payload)
229
+ JSON.stringify(payload),
230
230
  );
231
- const signedPayload = await this.cryptoImpl.sign(key, encodedPayload);
232
- const encodedSignedPayload = b64encode(signedPayload);
233
231
 
232
+ const signedPayload = await key.sign(encodedPayload);
233
+
234
+ const encodedSignedPayload = b64encode(signedPayload);
234
235
  headers["X-Lightspark-Signing"] = JSON.stringify({
235
236
  v: "1",
236
237
  signature: encodedSignedPayload,
@@ -16,7 +16,7 @@ const Base64 = {
16
16
 
17
17
  if (charCode > 0xff) {
18
18
  throw new Error(
19
- "'btoa' failed: The string to be encoded contains characters outside of the Latin1 range."
19
+ "'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.",
20
20
  );
21
21
  }
22
22
 
@@ -32,7 +32,7 @@ const Base64 = {
32
32
 
33
33
  if (str.length % 4 == 1) {
34
34
  throw new Error(
35
- "'atob' failed: The string to be decoded is not correctly encoded."
35
+ "'atob' failed: The string to be decoded is not correctly encoded.",
36
36
  );
37
37
  }
38
38
  for (
@@ -59,6 +59,6 @@ export const urlsafe_b64decode = (encoded: string): Uint8Array => {
59
59
 
60
60
  export const b64encode = (data: ArrayBuffer): string => {
61
61
  return Base64.btoa(
62
- String.fromCharCode.apply(null, Array.from(new Uint8Array(data)))
62
+ String.fromCharCode.apply(null, Array.from(new Uint8Array(data))),
63
63
  );
64
64
  };
@@ -0,0 +1,12 @@
1
+ import { isBrowser } from "./environment.js";
2
+
3
+ export const createSha256Hash = async (
4
+ data: Uint8Array,
5
+ ): Promise<Uint8Array> => {
6
+ if (isBrowser) {
7
+ return new Uint8Array(await window.crypto.subtle.digest("SHA-256", data));
8
+ } else {
9
+ const { createHash } = await import("crypto");
10
+ return createHash("sha256").update(data).digest();
11
+ }
12
+ };
@@ -101,7 +101,7 @@ const CONVERSION_MAP = {
101
101
 
102
102
  export const convertCurrencyAmount = (
103
103
  from: CurrencyAmount,
104
- toUnit: CurrencyUnit
104
+ toUnit: CurrencyUnit,
105
105
  ): CurrencyAmount => {
106
106
  if (
107
107
  from.originalUnit === CurrencyUnit.FUTURE_VALUE ||
@@ -116,7 +116,7 @@ export const convertCurrencyAmount = (
116
116
  if (!conversionFn) {
117
117
  throw new LightsparkException(
118
118
  "CurrencyError",
119
- `Cannot convert from ${from.originalUnit} to ${toUnit}`
119
+ `Cannot convert from ${from.originalUnit} to ${toUnit}`,
120
120
  );
121
121
  }
122
122
  return {
@@ -0,0 +1,15 @@
1
+ export const bytesToHex = (bytes: Uint8Array): string => {
2
+ return bytes.reduce((acc: string, byte: number) => {
3
+ return (acc += ("0" + byte.toString(16)).slice(-2));
4
+ }, "");
5
+ };
6
+
7
+ export const hexToBytes = (hex: string): Uint8Array => {
8
+ const bytes: number[] = [];
9
+
10
+ for (let c = 0; c < hex.length; c += 2) {
11
+ bytes.push(parseInt(hex.substr(c, 2), 16));
12
+ }
13
+
14
+ return Uint8Array.from(bytes);
15
+ };
@@ -1,6 +1,8 @@
1
1
  // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved
2
2
 
3
3
  export * from "./base64.js";
4
+ export * from "./createHash.js";
4
5
  export * from "./currency.js";
5
6
  export * from "./environment.js";
7
+ export * from "./hex.js";
6
8
  export * from "./types.js";
@@ -17,7 +17,7 @@ export type OmitTypename<T> = Omit<T, "__typename">;
17
17
  export const isType =
18
18
  <T extends string>(typename: T) =>
19
19
  <N extends { __typename: string }>(
20
- node: N | undefined | null
20
+ node: N | undefined | null,
21
21
  ): node is Extract<N, { __typename: T }> => {
22
22
  return node?.__typename === typename;
23
23
  };