Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: hex string length support #213

Merged
merged 7 commits into from
Mar 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/bee-debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,10 @@ export class BeeDebug {
*/

/**
* Get the address of the chequebook contract used
* Get the address of the chequebook contract used.
*
* **Warning:** The address is returned with 0x prefix unlike all other calls.
* https://github.com/ethersphere/bee/issues/1443
*/
getChequebookAddress(): Promise<ChequebookAddressResponse> {
return chequebook.getChequebookAddress(this.url)
Expand Down
9 changes: 4 additions & 5 deletions src/bee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@ import { Signer } from './chunk/signer'
import { downloadSingleOwnerChunk, uploadSingleOwnerChunkData, SOCReader, SOCWriter } from './chunk/soc'
import { Topic, makeTopic, makeTopicFromString } from './feed/topic'
import { createFeedManifest } from './modules/feed'
import { bytesToHex } from './utils/hex'
import { assertBeeUrl, stripLastSlash } from './utils/url'
import { EthAddress, makeEthAddress } from './utils/eth'
import { EthAddress, makeEthAddress, makeHexEthAddress } from './utils/eth'

/**
* The Bee class provides a way of interacting with the Bee APIs based on the provided url
Expand Down Expand Up @@ -382,9 +381,9 @@ export class Bee {
assertIsFeedType(type)

const canonicalTopic = makeTopic(topic)
const canonicalOwner = makeEthAddress(owner)
const canonicalOwner = makeHexEthAddress(owner)

return createFeedManifest(this.url, bytesToHex(canonicalOwner), bytesToHex(canonicalTopic), { type })
return createFeedManifest(this.url, canonicalOwner, canonicalTopic, { type })
}

/**
Expand All @@ -402,7 +401,7 @@ export class Bee {
assertIsFeedType(type)

const canonicalTopic = makeTopic(topic)
const canonicalOwner = makeEthAddress(owner)
const canonicalOwner = makeHexEthAddress(owner)

return makeFeedReader(this.url, type, canonicalTopic, canonicalOwner)
}
Expand Down
9 changes: 4 additions & 5 deletions src/chunk/signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ec, curve } from 'elliptic'
import { BeeError } from '../utils/error'
import { Bytes, verifyBytes } from '../utils/bytes'
import { keccak256Hash } from './hash'
import { hexToBytes, verifyHex } from '../utils/hex'
import { hexToBytes, makeHexString } from '../utils/hex'
import { EthAddress } from '../utils/eth'

/**
Expand Down Expand Up @@ -128,11 +128,10 @@ export function isSigner(signer: unknown): signer is Signer {

export function makeSigner(signer: Signer | Uint8Array | string | unknown): Signer {
if (typeof signer === 'string') {
const hexKey = verifyHex(signer)
const keyBytes = hexToBytes(hexKey)
const verifiedPrivateKey = verifyBytes(32, keyBytes)
const hexKey = makeHexString(signer, 64)
const keyBytes = hexToBytes<32>(hexKey) // HexString is verified for 64 length => 32 is guaranteed

return makeDefaultSigner(verifiedPrivateKey)
return makeDefaultSigner(keyBytes)
} else if (signer instanceof Uint8Array) {
const verifiedPrivateKey = verifyBytes(32, signer)

Expand Down
71 changes: 44 additions & 27 deletions src/feed/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
import { keccak256Hash } from '../chunk/hash'
import { serializeBytes } from '../chunk/serialize'
import { Signer } from '../chunk/signer'
import { Identifier, uploadSingleOwnerChunkData, verifySingleOwnerChunk } from '../chunk/soc'
import { FeedUpdateOptions, fetchFeedUpdate, FetchFeedUpdateResponse } from '../modules/feed'
import { Reference, ReferenceResponse, UploadOptions } from '../types'
import { Bytes, makeBytes, verifyBytes, verifyBytesAtOffset } from '../utils/bytes'
import {
REFERENCE_HEX_LENGTH,
Reference,
ReferenceResponse,
UploadOptions,
ENCRYPTED_REFERENCE_HEX_LENGTH,
ENCRYPTED_REFERENCE_BYTES_LENGTH,
REFERENCE_BYTES_LENGTH,
} from '../types'
import { Bytes, makeBytes, verifyBytesAtOffset } from '../utils/bytes'
import { BeeResponseError } from '../utils/error'
import { bytesToHex, HexString, hexToBytes, verifyHex } from '../utils/hex'
import { bytesToHex, HexString, hexToBytes, makeHexString } from '../utils/hex'
import { readUint64BigEndian, writeUint64BigEndian } from '../utils/uint64'
import * as chunkAPI from '../modules/chunk'
import { Topic } from './topic'
import { FeedType } from './type'
import { EthAddress } from '../utils/eth'
import { EthAddress, HexEthAddress, makeHexEthAddress } from '../utils/eth'

import type { Signer } from '../chunk/signer'
import type { Topic } from './topic'
import type { FeedType } from './type'

const TIMESTAMP_PAYLOAD_OFFSET = 0
const TIMESTAMP_PAYLOAD_SIZE = 8
const REFERENCE_PAYLOAD_OFFSET = TIMESTAMP_PAYLOAD_SIZE
const REFERENCE_PAYLOAD_MIN_SIZE = 32
const REFERENCE_PAYLOAD_MAX_SIZE = 64
const INDEX_HEX_LENGTH = 16

export interface Epoch {
time: number
Expand All @@ -42,7 +52,7 @@ export interface FeedUpdate {
*/
export interface FeedReader {
readonly type: FeedType
readonly owner: EthAddress
readonly owner: HexEthAddress
readonly topic: Topic
/**
* Download the latest feed update
Expand Down Expand Up @@ -70,7 +80,7 @@ export function isEpoch(epoch: unknown): epoch is Epoch {
}

function hashFeedIdentifier(topic: Topic, index: IndexBytes): Identifier {
return keccak256Hash(topic, index)
return keccak256Hash(hexToBytes(topic), index)
}

export function makeSequentialFeedIdentifier(topic: Topic, index: number): Identifier {
Expand All @@ -80,10 +90,9 @@ export function makeSequentialFeedIdentifier(topic: Topic, index: number): Ident
}

export function makeFeedIndexBytes(s: string): IndexBytes {
const hex = verifyHex(s)
const bytes = hexToBytes(hex)
const hex = makeHexString(s, INDEX_HEX_LENGTH)

return verifyBytes(8, bytes)
return hexToBytes(hex)
}

export function makeFeedIdentifier(topic: Topic, index: Index): Identifier {
Expand Down Expand Up @@ -118,14 +127,14 @@ export function uploadFeedUpdate(

export async function findNextIndex(
url: string,
owner: HexString,
topic: HexString,
owner: HexEthAddress,
topic: Topic,
options?: FeedUpdateOptions,
): Promise<string> {
): Promise<HexString<typeof INDEX_HEX_LENGTH>> {
try {
const feedUpdate = await fetchFeedUpdate(url, owner, topic, options)

return feedUpdate.feedIndexNext
return makeHexString(feedUpdate.feedIndexNext, INDEX_HEX_LENGTH)
} catch (e) {
if (e instanceof BeeResponseError && e.status === 404) {
return bytesToHex(makeBytes(8))
Expand All @@ -141,9 +150,8 @@ export async function updateFeed(
reference: ChunkReference,
options?: FeedUploadOptions,
): Promise<ReferenceResponse> {
const ownerHex = bytesToHex(signer.address)
const topicHex = bytesToHex(topic)
const nextIndex = await findNextIndex(url, ownerHex, topicHex, options)
const ownerHex = makeHexEthAddress(signer.address)
const nextIndex = await findNextIndex(url, ownerHex, topic, options)

return uploadFeedUpdate(url, signer, topic, nextIndex, reference, options)
}
Expand Down Expand Up @@ -182,10 +190,8 @@ export async function downloadFeedUpdate(
}
}

export function makeFeedReader(url: string, type: FeedType, topic: Topic, owner: EthAddress): FeedReader {
const ownerHex = bytesToHex(owner)
const topicHex = bytesToHex(topic)
const download = (options?: FeedUpdateOptions) => fetchFeedUpdate(url, ownerHex, topicHex, { ...options, type })
export function makeFeedReader(url: string, type: FeedType, topic: Topic, owner: HexEthAddress): FeedReader {
const download = (options?: FeedUpdateOptions) => fetchFeedUpdate(url, owner, topic, { ...options, type })

return {
type,
Expand All @@ -197,10 +203,21 @@ export function makeFeedReader(url: string, type: FeedType, topic: Topic, owner:

function makeChunkReference(reference: ChunkReference | Reference): ChunkReference {
if (typeof reference === 'string') {
const hexReference = verifyHex(reference)
const referenceBytes = hexToBytes(hexReference)
try {
// Non-encrypted chunk hex string reference
const hexReference = makeHexString(reference, REFERENCE_HEX_LENGTH)

return hexToBytes<typeof REFERENCE_BYTES_LENGTH>(hexReference)
} catch (e) {
if (!(e instanceof TypeError)) {
throw e
}

return verifyChunkReference(referenceBytes)
// Encrypted chunk hex string reference
const hexReference = makeHexString(reference, ENCRYPTED_REFERENCE_HEX_LENGTH)

return hexToBytes<typeof ENCRYPTED_REFERENCE_BYTES_LENGTH>(hexReference)
}
} else if (reference instanceof Uint8Array) {
return verifyChunkReference(reference)
}
Expand All @@ -215,7 +232,7 @@ export function makeFeedWriter(url: string, type: FeedType, topic: Topic, signer
}

return {
...makeFeedReader(url, type, topic, signer.address),
...makeFeedReader(url, type, topic, makeHexEthAddress(signer.address)),
upload,
}
}
21 changes: 10 additions & 11 deletions src/feed/topic.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
import { keccak256Hash } from '../chunk/hash'
import { Bytes, verifyBytes } from '../utils/bytes'
import { hexToBytes, verifyHex } from '../utils/hex'
import { verifyBytes } from '../utils/bytes'
import { HexString, makeHexString, bytesToHex } from '../utils/hex'

export const TOPIC_LENGTH_BYTES = 32
export const TOPIC_LENGTH_HEX = 2 * TOPIC_LENGTH_BYTES
export const TOPIC_BYTES_LENGTH = 32
export const TOPIC_HEX_LENGTH = 64

export type Topic = Bytes<32>
export type Topic = HexString<typeof TOPIC_HEX_LENGTH>

export function makeTopic(topic: Uint8Array | string): Topic {
if (typeof topic === 'string') {
const topicHex = verifyHex(topic)
const topicBytes = hexToBytes(topicHex)

return verifyBytes(TOPIC_LENGTH_BYTES, topicBytes)
return makeHexString(topic, TOPIC_HEX_LENGTH)
} else if (topic instanceof Uint8Array) {
return verifyBytes(TOPIC_LENGTH_BYTES, topic)
verifyBytes(TOPIC_BYTES_LENGTH, topic)

return bytesToHex(topic, TOPIC_HEX_LENGTH)
}
throw new TypeError('invalid topic')
}

export function makeTopicFromString(s: string): Topic {
return keccak256Hash(s)
return bytesToHex(keccak256Hash(s), TOPIC_HEX_LENGTH)
}
10 changes: 5 additions & 5 deletions src/modules/bytes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { AxiosRequestConfig } from 'axios'
import type { Readable } from 'stream'
import { UploadOptions } from '../types'
import { Reference, UploadOptions } from '../types'
import { prepareData } from '../utils/data'
import { extractUploadHeaders } from '../utils/headers'
import { safeAxios } from '../utils/safeAxios'
Expand All @@ -14,8 +14,8 @@ const endpoint = '/bytes'
* @param data Data to be uploaded
* @param options Aditional options like tag, encryption, pinning
*/
export async function upload(url: string, data: string | Uint8Array, options?: UploadOptions): Promise<string> {
const response = await safeAxios<{ reference: string }>({
export async function upload(url: string, data: string | Uint8Array, options?: UploadOptions): Promise<Reference> {
const response = await safeAxios<{ reference: Reference }>({
...options?.axiosOptions,
method: 'post',
url: url + endpoint,
Expand All @@ -36,7 +36,7 @@ export async function upload(url: string, data: string | Uint8Array, options?: U
* @param url Bee URL
* @param hash Bee content reference
*/
export async function download(url: string, hash: string): Promise<Uint8Array> {
export async function download(url: string, hash: Reference): Promise<Uint8Array> {
const response = await safeAxios<ArrayBuffer>({
responseType: 'arraybuffer',
url: `${url}${endpoint}/${hash}`,
Expand All @@ -54,7 +54,7 @@ export async function download(url: string, hash: string): Promise<Uint8Array> {
*/
export async function downloadReadable(
url: string,
hash: string,
hash: Reference,
axiosOptions?: AxiosRequestConfig,
): Promise<Readable> {
const response = await safeAxios<Readable>({
Expand Down
5 changes: 3 additions & 2 deletions src/modules/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { safeAxios } from '../utils/safeAxios'
import { extractUploadHeaders, readFileHeaders } from '../utils/headers'
import { BeeArgumentError } from '../utils/error'
import { fileArrayBuffer } from '../utils/file'
import { Reference } from '../types'

const dirsEndpoint = '/dirs'
const bzzEndpoint = '/bzz'
Expand Down Expand Up @@ -124,7 +125,7 @@ export async function upload(
url: string,
data: Collection<Uint8Array>,
options?: CollectionUploadOptions,
): Promise<string> {
): Promise<Reference> {
if (!url || url === '') {
throw new BeeArgumentError('url parameter is required and cannot be empty', url)
}
Expand All @@ -135,7 +136,7 @@ export async function upload(

const tarData = makeTar(data)

const response = await safeAxios<{ reference: string }>({
const response = await safeAxios<{ reference: Reference }>({
...options?.axiosOptions,
method: 'post',
url: `${url}${dirsEndpoint}`,
Expand Down
10 changes: 6 additions & 4 deletions src/modules/feed.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Dictionary, Reference, ReferenceResponse } from '../types'
import { safeAxios } from '../utils/safeAxios'
import { FeedType } from '../feed/type'
import { HexEthAddress } from '../utils/eth'
import { Topic } from '../feed/topic'

const feedEndpoint = '/feeds'

Expand Down Expand Up @@ -35,8 +37,8 @@ export interface FetchFeedUpdateResponse extends ReferenceResponse, FeedUpdateHe
*/
export async function createFeedManifest(
url: string,
owner: string,
topic: string,
owner: HexEthAddress,
topic: Topic,
options?: CreateFeedOptions,
): Promise<Reference> {
const response = await safeAxios<ReferenceResponse>({
Expand Down Expand Up @@ -70,8 +72,8 @@ function readFeedUpdateHeaders(headers: Dictionary<string>): FeedUpdateHeaders {
*/
export async function fetchFeedUpdate(
url: string,
owner: string,
topic: string,
owner: HexEthAddress,
topic: Topic,
options?: FeedUpdateOptions,
): Promise<FetchFeedUpdateResponse> {
const response = await safeAxios<ReferenceResponse>({
Expand Down
6 changes: 3 additions & 3 deletions src/modules/file.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { AxiosRequestConfig } from 'axios'
import type { Readable } from 'stream'
import { FileData, FileUploadOptions, UploadHeaders } from '../types'
import { FileData, FileUploadOptions, Reference, UploadHeaders } from '../types'
import { prepareData } from '../utils/data'
import { extractUploadHeaders, readFileHeaders } from '../utils/headers'
import { safeAxios } from '../utils/safeAxios'
Expand Down Expand Up @@ -35,8 +35,8 @@ export async function upload(
data: string | Uint8Array | Readable | ArrayBuffer,
name?: string,
options?: FileUploadOptions,
): Promise<string> {
const response = await safeAxios<{ reference: string }>({
): Promise<Reference> {
const response = await safeAxios<{ reference: Reference }>({
...options?.axiosOptions,
method: 'post',
url: url + endpoint,
Expand Down
Loading