@stacks/storage 4.3.6-pr.de51016.0 → 4.3.6-pr.f9a3196.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/package.json CHANGED
@@ -1,15 +1,14 @@
1
1
  {
2
2
  "name": "@stacks/storage",
3
- "version": "4.3.6-pr.de51016.0",
3
+ "version": "4.3.6-pr.f9a3196.0",
4
4
  "description": "Stacks storage library",
5
5
  "license": "MIT",
6
6
  "author": "Hiro Systems PBC (https://hiro.so)",
7
- "homepage": "https://www.hiro.so/stacks-js",
7
+ "homepage": "https://hiro.so/stacks-js",
8
8
  "scripts": {
9
- "build": "npm run clean && npm run build:cjs && npm run build:esm && npm run build:umd && npm run build:polyfill",
9
+ "build": "npm run clean && npm run build:cjs && npm run build:esm && npm run build:umd",
10
10
  "build:cjs": "tsc -b tsconfig.build.json",
11
11
  "build:esm": "tsc -p tsconfig.build.json --module ES6 --outDir ./dist/esm",
12
- "build:polyfill": "NODE_OPTIONS=--max-old-space-size=8192 rollup -c ../../configs/rollup.config.js && rimraf dist/polyfill/dist",
13
12
  "build:umd": "NODE_OPTIONS=--max-old-space-size=8192 webpack --config webpack.config.js",
14
13
  "clean": "rimraf dist && tsc -b tsconfig.build.json --clean",
15
14
  "pack": "npm pack",
@@ -21,11 +20,12 @@
21
20
  "typecheck:watch": "npm run typecheck -- --watch"
22
21
  },
23
22
  "dependencies": {
24
- "@stacks/auth": "^4.3.6-pr.de51016.0",
25
- "@stacks/common": "^4.3.6-pr.de51016.0",
26
- "@stacks/encryption": "^4.3.6-pr.de51016.0",
27
- "@stacks/network": "^4.3.6-pr.de51016.0",
28
- "jsontokens": "^3.1.1"
23
+ "@stacks/auth": "^4.3.6-pr.f9a3196.0",
24
+ "@stacks/common": "^4.3.6-pr.f9a3196.0",
25
+ "@stacks/encryption": "^4.3.6-pr.f9a3196.0",
26
+ "@stacks/network": "^4.3.6-pr.f9a3196.0",
27
+ "base64-js": "^1.5.1",
28
+ "jsontokens": "^4.0.1"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@stacks/network": "^4.1.0",
@@ -38,12 +38,7 @@
38
38
  "process": "^0.11.10",
39
39
  "rimraf": "^3.0.2",
40
40
  "stream-browserify": "^3.0.0",
41
- "ts-jest": "^26.5.5",
42
- "ts-loader": "^9.1.1",
43
- "typescript": "^4.2.4",
44
- "webpack": "^5.36.1",
45
- "webpack-bundle-analyzer": "^4.5.0",
46
- "webpack-cli": "^4.6.0"
41
+ "ts-jest": "^26.5.5"
47
42
  },
48
43
  "sideEffects": false,
49
44
  "typings": "dist/index.d.ts",
@@ -65,5 +60,5 @@
65
60
  "bugs": {
66
61
  "url": "https://github.com/blockstack/blockstack.js/issues"
67
62
  },
68
- "gitHead": "de51016a0406798f8e51c2624f9955d8c05431db"
63
+ "gitHead": "f9a3196694795fdd3327149ac98357d27f65e82f"
69
64
  }
@@ -1,4 +1,5 @@
1
- import { Buffer } from '@stacks/common';
1
+ import { utf8ToBytes } from '@stacks/common';
2
+
2
3
  /**
3
4
  * Retrieves the specified file from the app's data store.
4
5
  * @param {String} path - the path to the file to read
@@ -6,11 +7,11 @@ import { Buffer } from '@stacks/common';
6
7
  * or rejects with an error
7
8
  */
8
9
  export /** @ignore */
9
- type PutFileContent = string | Buffer | ArrayBufferView | ArrayBufferLike | Blob;
10
+ type PutFileContent = string | Uint8Array | ArrayBufferView | ArrayBufferLike | Blob;
10
11
 
11
12
  /** @ignore */
12
13
  export class FileContentLoader {
13
- readonly content: Buffer | Blob;
14
+ readonly content: Uint8Array | Blob;
14
15
 
15
16
  readonly wasString: boolean;
16
17
 
@@ -18,11 +19,11 @@ export class FileContentLoader {
18
19
 
19
20
  readonly contentByteLength: number;
20
21
 
21
- private loadedData?: Promise<Buffer>;
22
+ private loadedData?: Promise<Uint8Array>;
22
23
 
23
24
  static readonly supportedTypesMsg =
24
25
  'Supported types are: `string` (to be UTF8 encoded), ' +
25
- '`Buffer`, `Blob`, `File`, `ArrayBuffer`, `UInt8Array` or any other typed array buffer. ';
26
+ '`Blob`, `File`, `ArrayBuffer`, `UInt8Array` or any other typed array buffer. ';
26
27
 
27
28
  constructor(content: PutFileContent, contentType: string) {
28
29
  this.wasString = typeof content === 'string';
@@ -34,7 +35,7 @@ export class FileContentLoader {
34
35
  private static normalizeContentDataType(
35
36
  content: PutFileContent,
36
37
  contentType: string
37
- ): Buffer | Blob {
38
+ ): Uint8Array | Blob {
38
39
  try {
39
40
  if (typeof content === 'string') {
40
41
  // If a charset is specified it must be either utf8 or ascii, otherwise the encoded content
@@ -49,17 +50,19 @@ export class FileContentLoader {
49
50
  }
50
51
  if (typeof TextEncoder !== 'undefined') {
51
52
  const encodedString = new TextEncoder().encode(content);
52
- return Buffer.from(encodedString.buffer);
53
+ return new Uint8Array(encodedString.buffer);
53
54
  }
54
- return Buffer.from(content);
55
- } else if (Buffer.isBuffer(content)) {
55
+ return utf8ToBytes(content);
56
+ } else if (content instanceof Uint8Array) {
56
57
  return content;
57
58
  } else if (ArrayBuffer.isView(content)) {
58
- return Buffer.from(content.buffer, content.byteOffset, content.byteLength);
59
+ return new Uint8Array(
60
+ content.buffer.slice(content.byteOffset, content.byteOffset + content.byteLength)
61
+ );
59
62
  } else if (typeof Blob !== 'undefined' && content instanceof Blob) {
60
63
  return content;
61
64
  } else if (typeof ArrayBuffer !== 'undefined' && content instanceof ArrayBuffer) {
62
- return Buffer.from(content);
65
+ return new Uint8Array(content);
63
66
  } else if (Array.isArray(content)) {
64
67
  // Provided with a regular number `Array` -- this is either an (old) method
65
68
  // of representing an octet array, or a dev error. Perform basic check for octet array.
@@ -71,7 +74,7 @@ export class FileContentLoader {
71
74
  `Unexpected array values provided as file data: value "${content[0]}" at index 0 is not an octet number. ${this.supportedTypesMsg}`
72
75
  );
73
76
  }
74
- return Buffer.from(content);
77
+ return new Uint8Array(content);
75
78
  } else {
76
79
  const typeName = Object.prototype.toString.call(content);
77
80
  throw new Error(
@@ -95,7 +98,7 @@ export class FileContentLoader {
95
98
  }
96
99
 
97
100
  private detectContentLength(): number {
98
- if (ArrayBuffer.isView(this.content) || Buffer.isBuffer(this.content)) {
101
+ if (ArrayBuffer.isView(this.content) || this.content instanceof Uint8Array) {
99
102
  return this.content.byteLength;
100
103
  } else if (typeof Blob !== 'undefined' && this.content instanceof Blob) {
101
104
  return this.content.size;
@@ -106,26 +109,29 @@ export class FileContentLoader {
106
109
  throw error;
107
110
  }
108
111
 
109
- private async loadContent(): Promise<Buffer> {
112
+ private async loadContent(): Promise<Uint8Array> {
110
113
  try {
111
- if (Buffer.isBuffer(this.content)) {
114
+ if (this.content instanceof Uint8Array) {
112
115
  return this.content;
113
116
  } else if (ArrayBuffer.isView(this.content)) {
114
- return Buffer.from(this.content.buffer, this.content.byteOffset, this.content.byteLength);
117
+ return new Uint8Array(
118
+ this.content.buffer,
119
+ this.content.byteOffset,
120
+ this.content.byteLength
121
+ );
115
122
  } else if (typeof Blob !== 'undefined' && this.content instanceof Blob) {
116
123
  const reader = new FileReader();
117
- const readPromise = new Promise<Buffer>((resolve, reject) => {
124
+ const readPromise = new Promise<Uint8Array>((resolve, reject) => {
118
125
  reader.onerror = err => {
119
126
  reject(err);
120
127
  };
121
128
  reader.onload = () => {
122
129
  const arrayBuffer = reader.result as ArrayBuffer;
123
- resolve(Buffer.from(arrayBuffer));
130
+ resolve(new Uint8Array(arrayBuffer));
124
131
  };
125
132
  reader.readAsArrayBuffer(this.content as Blob);
126
133
  });
127
- const result = await readPromise;
128
- return result;
134
+ return await readPromise;
129
135
  } else {
130
136
  const typeName = Object.prototype.toString.call(this.content);
131
137
  throw new Error(`Unexpected type ${typeName}`);
@@ -138,7 +144,7 @@ export class FileContentLoader {
138
144
  }
139
145
  }
140
146
 
141
- load(): Promise<Buffer | string> {
147
+ load(): Promise<Uint8Array | string> {
142
148
  if (this.loadedData === undefined) {
143
149
  this.loadedData = this.loadContent();
144
150
  }
package/src/hub.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BadPathError,
3
- Buffer,
3
+ bytesToHex,
4
4
  ConflictError,
5
5
  DoesNotExist,
6
6
  GaiaHubErrorResponse,
@@ -9,6 +9,7 @@ import {
9
9
  NotEnoughProofError,
10
10
  PayloadTooLargeError,
11
11
  PreconditionFailedError,
12
+ utf8ToBytes,
12
13
  ValidationError,
13
14
  } from '@stacks/common';
14
15
  import {
@@ -21,6 +22,7 @@ import {
21
22
  Signature,
22
23
  } from '@stacks/encryption';
23
24
  import { createFetchFn, FetchFn } from '@stacks/network';
25
+ import { fromByteArray } from 'base64-js';
24
26
  import { TokenSigner } from 'jsontokens';
25
27
 
26
28
  /**
@@ -57,7 +59,7 @@ interface UploadResponse {
57
59
  */
58
60
  export async function uploadToGaiaHub(
59
61
  filename: string,
60
- contents: Blob | Buffer | ArrayBufferView | string,
62
+ contents: Blob | Uint8Array | ArrayBufferView | string,
61
63
  hubConfig: GaiaHubConfig,
62
64
  contentType = 'application/octet-stream',
63
65
  newFile = true,
@@ -148,19 +150,16 @@ function makeLegacyAuthToken(challengeText: string, signerKeyHex: string): strin
148
150
  throw new Error('Failed in parsing legacy challenge text from the gaia hub.');
149
151
  }
150
152
  if (parsedChallenge[0] === 'gaiahub' && parsedChallenge[3] === 'blockstack_storage_please_sign') {
151
- const digest = hashSha256Sync(Buffer.from(challengeText));
152
- const signatureBuffer = ecSign(digest, compressPrivateKey(signerKeyHex));
153
+ const digest = hashSha256Sync(utf8ToBytes(challengeText));
154
+ const signatureBytes = ecSign(digest, compressPrivateKey(signerKeyHex));
153
155
  // We only want the DER encoding so use toDERHex provided by @noble/secp256k1
154
- const signature = Signature.fromCompact(signatureBuffer.toString('hex')).toDERHex();
156
+ const signature = Signature.fromCompact(bytesToHex(signatureBytes)).toDERHex();
155
157
 
156
158
  const publickey = getPublicKeyFromPrivate(signerKeyHex);
157
- const token = Buffer.from(JSON.stringify({ publickey, signature })).toString('base64');
159
+ const token = fromByteArray(utf8ToBytes(JSON.stringify({ publickey, signature })));
158
160
  return token;
159
- } else {
160
- throw new Error(
161
- 'Failed to connect to legacy gaia hub. If you operate this hub, please update.'
162
- );
163
161
  }
162
+ throw new Error('Failed to connect to legacy gaia hub. If you operate this hub, please update.');
164
163
  }
165
164
 
166
165
  /**
@@ -187,7 +186,7 @@ function makeV1GaiaAuthToken(
187
186
  return makeLegacyAuthToken(challengeText, signerKeyHex);
188
187
  }
189
188
 
190
- const salt = randomBytes(16).toString('hex');
189
+ const salt = bytesToHex(randomBytes(16));
191
190
  const payload = {
192
191
  gaiaChallenge: challengeText,
193
192
  hubUrl,
package/src/storage.ts CHANGED
@@ -8,12 +8,14 @@ import {
8
8
  megabytesToBytes,
9
9
  PayloadTooLargeError,
10
10
  SignatureVerificationError,
11
+ utf8ToBytes,
11
12
  } from '@stacks/common';
12
13
  import {
13
14
  eciesGetJsonStringLength,
14
15
  EncryptionOptions,
15
16
  getPublicKeyFromPrivate,
16
17
  publicKeyToAddress,
18
+ publicKeyToBtcAddress,
17
19
  signECDSA,
18
20
  verifyECDSA,
19
21
  } from '@stacks/encryption';
@@ -321,20 +323,19 @@ export class Storage {
321
323
  this.getGaiaAddress(opt.app!, opt.username, opt.zoneFileLookupURL),
322
324
  ]);
323
325
 
324
- if (!fileContents) {
325
- return fileContents;
326
- }
327
- if (!gaiaAddress) {
326
+ if (!fileContents) return fileContents;
327
+
328
+ if (!gaiaAddress)
328
329
  throw new SignatureVerificationError(
329
330
  `Failed to get gaia address for verification of: ${path}`
330
331
  );
331
- }
332
- if (!signatureContents || typeof signatureContents !== 'string') {
332
+
333
+ if (!signatureContents || typeof signatureContents !== 'string')
333
334
  throw new SignatureVerificationError(
334
335
  'Failed to obtain signature for file: ' +
335
336
  `${path} -- looked in ${path}${SIGNATURE_FILE_SUFFIX}`
336
337
  );
337
- }
338
+
338
339
  let signature;
339
340
  let publicKey;
340
341
  try {
@@ -342,40 +343,39 @@ export class Storage {
342
343
  signature = sigObject.signature;
343
344
  publicKey = sigObject.publicKey;
344
345
  } catch (err) {
345
- if (err instanceof SyntaxError) {
346
+ if (err instanceof SyntaxError)
346
347
  throw new Error(
347
- 'Failed to parse signature content JSON ' +
348
- `(path: ${path}${SIGNATURE_FILE_SUFFIX})` +
349
- ' The content may be corrupted.'
348
+ `Failed to parse signature content JSON (path: ${path}${SIGNATURE_FILE_SUFFIX}) The content may be corrupted.`
350
349
  );
351
- } else {
352
- throw err;
353
- }
350
+ throw err;
354
351
  }
355
352
  const signerAddress = publicKeyToAddress(publicKey);
356
- if (gaiaAddress !== signerAddress) {
353
+ if (gaiaAddress !== signerAddress)
357
354
  throw new SignatureVerificationError(
358
- `Signer pubkey address (${signerAddress}) doesn't` +
359
- ` match gaia address (${gaiaAddress})`
355
+ `Signer pubkey address (${signerAddress}) doesn't match gaia address (${gaiaAddress})`
360
356
  );
361
- } else if (!verifyECDSA(fileContents, publicKey, signature)) {
357
+
358
+ if (
359
+ !verifyECDSA(
360
+ typeof fileContents === 'string'
361
+ ? utf8ToBytes(fileContents)
362
+ : new Uint8Array(fileContents),
363
+ publicKey,
364
+ signature
365
+ )
366
+ ) {
362
367
  throw new SignatureVerificationError(
363
- 'Contents do not match ECDSA signature: ' +
364
- `path: ${path}, signature: ${path}${SIGNATURE_FILE_SUFFIX}`
368
+ `Contents do not match ECDSA signature: path: ${path}, signature: ${path}${SIGNATURE_FILE_SUFFIX}`
365
369
  );
366
- } else {
367
- return fileContents;
368
370
  }
371
+ return fileContents;
369
372
  } catch (err) {
370
373
  // For missing .sig files, throw `SignatureVerificationError` instead of `DoesNotExist` error.
371
- if (err instanceof DoesNotExist && err.message.indexOf(sigPath) >= 0) {
374
+ if (err instanceof DoesNotExist && err.message.indexOf(sigPath) >= 0)
372
375
  throw new SignatureVerificationError(
373
- 'Failed to obtain signature for file: ' +
374
- `${path} -- looked in ${path}${SIGNATURE_FILE_SUFFIX}`
376
+ `Failed to obtain signature for file: ${path} -- looked in ${path}${SIGNATURE_FILE_SUFFIX}`
375
377
  );
376
- } else {
377
- throw err;
378
- }
378
+ throw err;
379
379
  }
380
380
  }
381
381
 
@@ -393,18 +393,14 @@ export class Storage {
393
393
  privateKey?: string,
394
394
  username?: string,
395
395
  zoneFileLookupURL?: string
396
- // eslint-disable-next-line node/prefer-global/buffer
397
- ): Promise<string | Buffer> {
396
+ ): Promise<string | Uint8Array> {
398
397
  const appPrivateKey = privateKey || this.userSession.loadUserData().appPrivateKey;
399
398
 
400
399
  const appPublicKey = getPublicKeyFromPrivate(appPrivateKey);
401
400
 
402
- let address: string;
403
- if (username) {
404
- address = await this.getGaiaAddress(app, username, zoneFileLookupURL);
405
- } else {
406
- address = publicKeyToAddress(appPublicKey);
407
- }
401
+ const address: string = username
402
+ ? await this.getGaiaAddress(app, username, zoneFileLookupURL)
403
+ : publicKeyToBtcAddress(appPublicKey);
408
404
  if (!address) {
409
405
  throw new SignatureVerificationError(
410
406
  `Failed to get gaia address for verification of: ${path}`
@@ -416,9 +412,7 @@ export class Storage {
416
412
  } catch (err) {
417
413
  if (err instanceof SyntaxError) {
418
414
  throw new Error(
419
- 'Failed to parse encrypted, signed content JSON. The content may not ' +
420
- 'be encrypted. If using getFile, try passing' +
421
- ' { verify: false, decrypt: false }.'
415
+ 'Failed to parse encrypted, signed content JSON. The content may not be encrypted. If using getFile, try passing { verify: false, decrypt: false }.'
422
416
  );
423
417
  } else {
424
418
  throw err;
@@ -431,15 +425,15 @@ export class Storage {
431
425
 
432
426
  if (!signerPublicKey || !cipherText || !signature) {
433
427
  throw new SignatureVerificationError(
434
- 'Failed to get signature verification data from file:' + ` ${path}`
428
+ `Failed to get signature verification data from file: ${path}`
435
429
  );
436
430
  } else if (signerAddress !== address) {
437
431
  throw new SignatureVerificationError(
438
- `Signer pubkey address (${signerAddress}) doesn't` + ` match gaia address (${address})`
432
+ `Signer pubkey address (${signerAddress}) doesn't match gaia address (${address})`
439
433
  );
440
434
  } else if (!verifyECDSA(cipherText, signerPublicKey, signature)) {
441
435
  throw new SignatureVerificationError(
442
- 'Contents do not match ECDSA signature in file:' + ` ${path}`
436
+ `Contents do not match ECDSA signature in file: ${path}`
443
437
  );
444
438
  } else if (typeof privateKey === 'string') {
445
439
  const decryptOpt = { privateKey };
@@ -452,7 +446,7 @@ export class Storage {
452
446
  /**
453
447
  * Stores the data provided in the app's data store to to the file specified.
454
448
  * @param {String} path - the path to store the data in
455
- * @param {String|Buffer} content - the data to store in the file
449
+ * @param {String|Uint8Array} content - the data to store in the file
456
450
  * @param options a [[PutFileOptions]] object
457
451
  *
458
452
  * @returns {Promise} that resolves if the operation succeed and rejects
@@ -460,8 +454,7 @@ export class Storage {
460
454
  */
461
455
  async putFile(
462
456
  path: string,
463
- // eslint-disable-next-line node/prefer-global/buffer
464
- content: string | Buffer | ArrayBufferView | Blob,
457
+ content: string | Uint8Array | ArrayBufferView | Blob,
465
458
  options?: PutFileOptions
466
459
  ): Promise<string> {
467
460
  const defaults: PutFileOptions = {
@@ -557,8 +550,7 @@ export class Storage {
557
550
  };
558
551
  } else {
559
552
  // In all other cases, we only need one upload.
560
- // eslint-disable-next-line node/prefer-global/buffer
561
- let contentForUpload: string | Buffer | Blob;
553
+ let contentForUpload: string | Uint8Array | Blob;
562
554
  if (!opt.encrypt && !opt.sign) {
563
555
  // If content does not need encrypted or signed, it can be passed directly
564
556
  // to the fetch request without loading into memory.