@lightsparkdev/core 0.3.11 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @lightsparkdev/core
2
2
 
3
+ ## 1.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 808c77a: Consolidate some imports to lightspark-sdk and core
8
+
9
+ ## 1.0.0
10
+
11
+ ### Major Changes
12
+
13
+ - 1f00a50: Change SigningKey hashing method depending on environment
14
+ BREAKING: NodeKeyCaches loadKey now requires signingKeyType as a parameter
15
+
3
16
  ## 0.3.11
4
17
 
5
18
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -36,14 +36,25 @@ __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
+ getErrorMsg: () => getErrorMsg,
53
+ hexToBytes: () => hexToBytes,
46
54
  isBrowser: () => isBrowser,
55
+ isError: () => isError,
56
+ isErrorMsg: () => isErrorMsg,
57
+ isErrorWithMessage: () => isErrorWithMessage,
47
58
  isNode: () => isNode,
48
59
  isType: () => isType,
49
60
  urlsafe_b64decode: () => urlsafe_b64decode
@@ -348,6 +359,50 @@ var KeyOrAlias = {
348
359
 
349
360
  // src/crypto/NodeKeyCache.ts
350
361
  var import_auto_bind = __toESM(require("auto-bind"), 1);
362
+
363
+ // src/crypto/SigningKey.ts
364
+ var import_secp256k1 = __toESM(require("secp256k1"), 1);
365
+ function isAlias(key) {
366
+ return "alias" in key;
367
+ }
368
+ var SigningKey = class {
369
+ type;
370
+ constructor(type) {
371
+ this.type = type;
372
+ }
373
+ };
374
+ var RSASigningKey = class extends SigningKey {
375
+ constructor(privateKey, cryptoImpl) {
376
+ super("RSASigningKey" /* RSASigningKey */);
377
+ this.privateKey = privateKey;
378
+ this.cryptoImpl = cryptoImpl;
379
+ }
380
+ async sign(data) {
381
+ const key = isAlias(this.privateKey) ? this.privateKey.alias : this.privateKey;
382
+ return this.cryptoImpl.sign(key, data);
383
+ }
384
+ };
385
+ var Secp256k1SigningKey = class extends SigningKey {
386
+ constructor(privateKey) {
387
+ super("Secp256k1SigningKey" /* Secp256k1SigningKey */);
388
+ this.privateKey = privateKey;
389
+ }
390
+ async sign(data) {
391
+ const keyBytes = new Uint8Array(hexToBytes(this.privateKey));
392
+ const hash = await createSha256Hash(data);
393
+ const signResult = import_secp256k1.default.ecdsaSign(hash, keyBytes);
394
+ return signResult.signature;
395
+ }
396
+ };
397
+
398
+ // src/crypto/types.ts
399
+ var SigningKeyType = /* @__PURE__ */ ((SigningKeyType2) => {
400
+ SigningKeyType2["RSASigningKey"] = "RSASigningKey";
401
+ SigningKeyType2["Secp256k1SigningKey"] = "Secp256k1SigningKey";
402
+ return SigningKeyType2;
403
+ })(SigningKeyType || {});
404
+
405
+ // src/crypto/NodeKeyCache.ts
351
406
  var NodeKeyCache = class {
352
407
  constructor(cryptoImpl = DefaultCrypto) {
353
408
  this.cryptoImpl = cryptoImpl;
@@ -355,16 +410,35 @@ var NodeKeyCache = class {
355
410
  (0, import_auto_bind.default)(this);
356
411
  }
357
412
  idToKey;
358
- async loadKey(id, keyOrAlias) {
413
+ async loadKey(id, keyOrAlias, signingKeyType) {
414
+ let signingKey;
359
415
  if (keyOrAlias.alias !== void 0) {
360
- this.idToKey.set(id, keyOrAlias.alias);
361
- return keyOrAlias.alias;
416
+ switch (signingKeyType) {
417
+ case "RSASigningKey" /* RSASigningKey */:
418
+ signingKey = new RSASigningKey(
419
+ { alias: keyOrAlias.alias },
420
+ this.cryptoImpl
421
+ );
422
+ break;
423
+ default:
424
+ throw new LightsparkSigningException_default(
425
+ `Aliases are not supported for signing key type ${signingKeyType}`
426
+ );
427
+ }
428
+ this.idToKey.set(id, signingKey);
429
+ return signingKey;
362
430
  }
363
- const decoded = b64decode(this.stripPemTags(keyOrAlias.key));
364
431
  try {
365
- const key = await this.cryptoImpl.importPrivateSigningKey(decoded);
366
- this.idToKey.set(id, key);
367
- return key;
432
+ if (signingKeyType === "Secp256k1SigningKey" /* Secp256k1SigningKey */) {
433
+ signingKey = new Secp256k1SigningKey(keyOrAlias.key);
434
+ } else {
435
+ const decoded = b64decode(this.stripPemTags(keyOrAlias.key));
436
+ const cryptoKeyOrAlias = await this.cryptoImpl.importPrivateSigningKey(decoded);
437
+ const key = typeof cryptoKeyOrAlias === "string" ? { alias: cryptoKeyOrAlias } : cryptoKeyOrAlias;
438
+ signingKey = new RSASigningKey(key, this.cryptoImpl);
439
+ }
440
+ this.idToKey.set(id, signingKey);
441
+ return signingKey;
368
442
  } catch (e) {
369
443
  console.log("Error importing key: ", e);
370
444
  }
@@ -564,7 +638,7 @@ var Requester = class {
564
638
  const encodedPayload = new TextEncoderImpl().encode(
565
639
  JSON.stringify(payload)
566
640
  );
567
- const signedPayload = await this.cryptoImpl.sign(key, encodedPayload);
641
+ const signedPayload = await key.sign(encodedPayload);
568
642
  const encodedSignedPayload = b64encode(signedPayload);
569
643
  headers["X-Lightspark-Signing"] = JSON.stringify({
570
644
  v: "1",
@@ -591,6 +665,16 @@ var apiDomainForEnvironment = (environment) => {
591
665
  };
592
666
  var ServerEnvironment_default = ServerEnvironment;
593
667
 
668
+ // src/utils/createHash.ts
669
+ var createSha256Hash = async (data) => {
670
+ if (isBrowser) {
671
+ return new Uint8Array(await window.crypto.subtle.digest("SHA-256", data));
672
+ } else {
673
+ const { createHash } = await import("crypto");
674
+ return createHash("sha256").update(data).digest();
675
+ }
676
+ };
677
+
594
678
  // src/utils/currency.ts
595
679
  var CONVERSION_MAP = {
596
680
  ["BITCOIN" /* BITCOIN */]: {
@@ -661,6 +745,41 @@ var convertCurrencyAmount = (from, toUnit) => {
661
745
  };
662
746
  };
663
747
 
748
+ // src/utils/errors.ts
749
+ var isError = (e) => {
750
+ return Boolean(
751
+ typeof e === "object" && e !== null && "name" in e && typeof e.name === "string" && "message" in e && typeof e.message === "string" && "stack" in e && (!e.stack || typeof e.stack === "string")
752
+ );
753
+ };
754
+ var isErrorWithMessage = (e) => {
755
+ return Boolean(
756
+ typeof e === "object" && e !== null && "message" in e && typeof e.message === "string"
757
+ );
758
+ };
759
+ var getErrorMsg = (e) => {
760
+ return isErrorWithMessage(e) ? e.message : "Unknown error";
761
+ };
762
+ var isErrorMsg = (e, msg) => {
763
+ if (isError(e)) {
764
+ return e.message === msg;
765
+ }
766
+ return false;
767
+ };
768
+
769
+ // src/utils/hex.ts
770
+ var bytesToHex = (bytes) => {
771
+ return bytes.reduce((acc, byte) => {
772
+ return acc += ("0" + byte.toString(16)).slice(-2);
773
+ }, "");
774
+ };
775
+ var hexToBytes = (hex) => {
776
+ const bytes = [];
777
+ for (let c = 0; c < hex.length; c += 2) {
778
+ bytes.push(parseInt(hex.substr(c, 2), 16));
779
+ }
780
+ return Uint8Array.from(bytes);
781
+ };
782
+
664
783
  // src/utils/types.ts
665
784
  var isType = (typename) => (node) => {
666
785
  return node?.__typename === typename;
@@ -673,14 +792,25 @@ var isType = (typename) => (node) => {
673
792
  LightsparkException,
674
793
  LightsparkSigningException,
675
794
  NodeKeyCache,
795
+ RSASigningKey,
676
796
  Requester,
797
+ Secp256k1SigningKey,
677
798
  ServerEnvironment,
799
+ SigningKey,
800
+ SigningKeyType,
678
801
  StubAuthProvider,
679
802
  apiDomainForEnvironment,
680
803
  b64decode,
681
804
  b64encode,
805
+ bytesToHex,
682
806
  convertCurrencyAmount,
807
+ createSha256Hash,
808
+ getErrorMsg,
809
+ hexToBytes,
683
810
  isBrowser,
811
+ isError,
812
+ isErrorMsg,
813
+ isErrorWithMessage,
684
814
  isNode,
685
815
  isType,
686
816
  urlsafe_b64decode
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,17 @@ declare const convertCurrencyAmount: (from: CurrencyAmount, toUnit: CurrencyUnit
169
196
  declare const isBrowser: boolean;
170
197
  declare const isNode: boolean;
171
198
 
199
+ declare const isError: (e: unknown) => e is Error;
200
+ type ErrorWithMessage = {
201
+ message: string;
202
+ };
203
+ declare const isErrorWithMessage: (e: unknown) => e is ErrorWithMessage;
204
+ declare const getErrorMsg: (e: unknown) => string;
205
+ declare const isErrorMsg: (e: unknown, msg: string) => boolean;
206
+
207
+ declare const bytesToHex: (bytes: Uint8Array) => string;
208
+ declare const hexToBytes: (hex: string) => Uint8Array;
209
+
172
210
  type Maybe<T> = T | null | undefined;
173
211
  type ExpandRecursively<T> = T extends object ? T extends infer O ? {
174
212
  [K in keyof O]: ExpandRecursively<O[K]>;
@@ -183,4 +221,4 @@ declare const isType: <T extends string>(typename: T) => <N extends {
183
221
  __typename: T;
184
222
  }>;
185
223
 
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 };
224
+ 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, getErrorMsg, hexToBytes, isBrowser, isError, isErrorMsg, isErrorWithMessage, 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,41 @@ var convertCurrencyAmount = (from, toUnit) => {
609
682
  };
610
683
  };
611
684
 
685
+ // src/utils/errors.ts
686
+ var isError = (e) => {
687
+ return Boolean(
688
+ typeof e === "object" && e !== null && "name" in e && typeof e.name === "string" && "message" in e && typeof e.message === "string" && "stack" in e && (!e.stack || typeof e.stack === "string")
689
+ );
690
+ };
691
+ var isErrorWithMessage = (e) => {
692
+ return Boolean(
693
+ typeof e === "object" && e !== null && "message" in e && typeof e.message === "string"
694
+ );
695
+ };
696
+ var getErrorMsg = (e) => {
697
+ return isErrorWithMessage(e) ? e.message : "Unknown error";
698
+ };
699
+ var isErrorMsg = (e, msg) => {
700
+ if (isError(e)) {
701
+ return e.message === msg;
702
+ }
703
+ return false;
704
+ };
705
+
706
+ // src/utils/hex.ts
707
+ var bytesToHex = (bytes) => {
708
+ return bytes.reduce((acc, byte) => {
709
+ return acc += ("0" + byte.toString(16)).slice(-2);
710
+ }, "");
711
+ };
712
+ var hexToBytes = (hex) => {
713
+ const bytes = [];
714
+ for (let c = 0; c < hex.length; c += 2) {
715
+ bytes.push(parseInt(hex.substr(c, 2), 16));
716
+ }
717
+ return Uint8Array.from(bytes);
718
+ };
719
+
612
720
  // src/utils/types.ts
613
721
  var isType = (typename) => (node) => {
614
722
  return node?.__typename === typename;
@@ -620,14 +728,25 @@ export {
620
728
  LightsparkException_default as LightsparkException,
621
729
  LightsparkSigningException_default as LightsparkSigningException,
622
730
  NodeKeyCache_default as NodeKeyCache,
731
+ RSASigningKey,
623
732
  Requester_default as Requester,
733
+ Secp256k1SigningKey,
624
734
  ServerEnvironment_default as ServerEnvironment,
735
+ SigningKey,
736
+ SigningKeyType,
625
737
  StubAuthProvider,
626
738
  apiDomainForEnvironment,
627
739
  b64decode,
628
740
  b64encode,
741
+ bytesToHex,
629
742
  convertCurrencyAmount,
743
+ createSha256Hash,
744
+ getErrorMsg,
745
+ hexToBytes,
630
746
  isBrowser,
747
+ isError,
748
+ isErrorMsg,
749
+ isErrorWithMessage,
631
750
  isNode,
632
751
  isType,
633
752
  urlsafe_b64decode
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightsparkdev/core",
3
- "version": "0.3.11",
3
+ "version": "1.0.1",
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",
84
+ "jest": "^29.6.2",
83
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"
@@ -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);
@@ -17,23 +24,51 @@ class NodeKeyCache {
17
24
  public async loadKey(
18
25
  id: string,
19
26
  keyOrAlias: KeyOrAliasType,
20
- ): Promise<CryptoKey | string | null> {
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
+ }
@@ -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
+ }
@@ -228,9 +228,10 @@ class Requester {
228
228
  const encodedPayload = new TextEncoderImpl().encode(
229
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,
@@ -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
+ };
@@ -0,0 +1,36 @@
1
+ export const isError = (e: unknown): e is Error => {
2
+ return Boolean(
3
+ typeof e === "object" &&
4
+ e !== null &&
5
+ "name" in e &&
6
+ typeof e.name === "string" &&
7
+ "message" in e &&
8
+ typeof e.message === "string" &&
9
+ "stack" in e &&
10
+ (!e.stack || typeof e.stack === "string"),
11
+ );
12
+ };
13
+
14
+ type ErrorWithMessage = {
15
+ message: string;
16
+ };
17
+
18
+ export const isErrorWithMessage = (e: unknown): e is ErrorWithMessage => {
19
+ return Boolean(
20
+ typeof e === "object" &&
21
+ e !== null &&
22
+ "message" in e &&
23
+ typeof e.message === "string",
24
+ );
25
+ };
26
+
27
+ export const getErrorMsg = (e: unknown): string => {
28
+ return isErrorWithMessage(e) ? e.message : "Unknown error";
29
+ };
30
+
31
+ export const isErrorMsg = (e: unknown, msg: string) => {
32
+ if (isError(e)) {
33
+ return e.message === msg;
34
+ }
35
+ return false;
36
+ };
@@ -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,9 @@
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 "./errors.js";
8
+ export * from "./hex.js";
6
9
  export * from "./types.js";