diff --git a/src/common/upload.ts b/src/common/upload.ts index e28002ad..8c65675d 100644 --- a/src/common/upload.ts +++ b/src/common/upload.ts @@ -84,11 +84,13 @@ export class TurboAuthenticatedUploadService signal, }: TurboFileFactory & TurboAbortSignal): Promise { - const { signedDataItem, signedDataItemSize } = + const { dataItemStreamFactory, dataItemSizeFactory } = await this.signer.signDataItem({ fileStreamFactory, fileSizeFactory, }); + const signedDataItem = dataItemStreamFactory(); + const fileSize = dataItemSizeFactory(); // TODO: add p-limit constraint or replace with separate upload class return this.httpService.post({ endpoint: `/tx`, @@ -96,7 +98,7 @@ export class TurboAuthenticatedUploadService data: signedDataItem, headers: { 'content-type': 'application/octet-stream', - 'content-length': `${signedDataItemSize}`, + 'content-length': `${fileSize}`, }, }); } diff --git a/src/node/signer.ts b/src/node/signer.ts index 3bff6ab4..8c1184a3 100644 --- a/src/node/signer.ts +++ b/src/node/signer.ts @@ -20,12 +20,9 @@ import { randomBytes } from 'node:crypto'; import { Readable } from 'node:stream'; import { JWKInterface } from '../common/jwk.js'; -import { TurboWalletSigner } from '../types.js'; +import { StreamSizeFactory, TurboWalletSigner } from '../types.js'; import { toB64Url } from '../utils/base64.js'; -// TODO: is this deterministic for arweave wallets? -export const SIGNED_DATA_ITEM_HEADERS_SIZE = 1044; - export class TurboNodeArweaveSigner implements TurboWalletSigner { protected privateKey: JWKInterface; protected signer: ArweaveSigner; // TODO: replace with internal signer class @@ -41,17 +38,20 @@ export class TurboNodeArweaveSigner implements TurboWalletSigner { fileSizeFactory, }: { fileStreamFactory: () => Readable; - fileSizeFactory: () => number; + fileSizeFactory: StreamSizeFactory; }): Promise<{ - signedDataItem: Readable; - signedDataItemSize: number; + dataItemStreamFactory: () => Readable; + dataItemSizeFactory: StreamSizeFactory; }> { // TODO: replace with our own signer implementation const [stream1, stream2] = [fileStreamFactory(), fileStreamFactory()]; const signedDataItem = await streamSigner(stream1, stream2, this.signer); + const signedDataItemSize = this.calculateSignedDataHeadersSize({ + dataSize: fileSizeFactory(), + }); return { - signedDataItem: signedDataItem, - signedDataItemSize: fileSizeFactory() + SIGNED_DATA_ITEM_HEADERS_SIZE, + dataItemStreamFactory: () => signedDataItem, + dataItemSizeFactory: () => signedDataItemSize, }; } @@ -67,4 +67,28 @@ export class TurboNodeArweaveSigner implements TurboWalletSigner { 'x-signature': toB64Url(Buffer.from(signature)), }; } + + // TODO: make dynamic that accepts anchor and target and tags to return the size of the headers + data + // reference https://github.com/ArweaveTeam/arweave-standards/blob/master/ans/ANS-104.md#13-dataitem-format + private calculateSignedDataHeadersSize({ dataSize }: { dataSize: number }) { + const anchor = 1; // + whatever they provide (max of 33) + const target = 1; // + whatever they provide (max of 33) + const tags = 0; + const signatureLength = 512; + const ownerLength = 512; + const signatureTypeLength = 2; + const numOfTagsLength = 8; // https://github.com/Bundlr-Network/arbundles/blob/master/src/tags.ts#L191-L198 + const numOfTagsBytesLength = 8; + return [ + anchor, + target, + tags, + signatureLength, + ownerLength, + signatureTypeLength, + numOfTagsLength, + numOfTagsBytesLength, + dataSize, + ].reduce((totalSize, currentSize) => (totalSize += currentSize)); + } } diff --git a/src/types.ts b/src/types.ts index 44b124fe..11692acd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -159,10 +159,7 @@ export interface TurboWalletSigner { signDataItem({ fileStreamFactory, fileSizeFactory, - }: TurboFileFactory): Promise<{ - signedDataItem: Readable | Buffer; - signedDataItemSize: number; - }>; + }: TurboFileFactory): Promise; generateSignedRequestHeaders(): Promise; } diff --git a/src/web/signer.ts b/src/web/signer.ts index c5b98c0a..6d96774a 100644 --- a/src/web/signer.ts +++ b/src/web/signer.ts @@ -40,8 +40,9 @@ export class TurboWebArweaveSigner implements TurboWalletSigner { fileStreamFactory: () => ReadableStream; fileSizeFactory: StreamSizeFactory; }): Promise<{ - signedDataItem: Buffer; - signedDataItemSize: number; + // axios only supports Readables, Buffers, or Blobs - so we need to convert the stream to a buffer + dataItemStreamFactory: () => Buffer; + dataItemSizeFactory: StreamSizeFactory; }> { // TODO: converts the readable stream to a buffer bc incrementally signing ReadableStreams is not trivial const buffer = await readableStreamToBuffer({ @@ -51,8 +52,8 @@ export class TurboWebArweaveSigner implements TurboWalletSigner { const signedDataItem = createData(buffer, this.signer); await signedDataItem.sign(this.signer); return { - signedDataItem: signedDataItem.getRaw(), - signedDataItemSize: signedDataItem.getRaw().length, + dataItemStreamFactory: () => signedDataItem.getRaw(), + dataItemSizeFactory: () => signedDataItem.getRaw().length, }; }