Skip to content

Commit

Permalink
feat: expose external types (#235)
Browse files Browse the repository at this point in the history
  • Loading branch information
AuHau authored Mar 30, 2021
1 parent 7bc26e5 commit 5c1ddac
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 122 deletions.
13 changes: 9 additions & 4 deletions src/bee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,23 @@ import type {
CollectionUploadOptions,
FileUploadOptions,
Data,
Signer,
FeedReader,
FeedWriter,
SOCWriter,
SOCReader,
Topic,
BeeOptions,
} from './types'
import { BeeError } from './utils/error'
import { prepareWebsocketData } from './utils/data'
import { fileArrayBuffer, isFile } from './utils/file'
import { AxiosRequestConfig } from 'axios'
import { FeedReader, FeedWriter, makeFeedReader, makeFeedWriter } from './feed'
import { makeFeedReader, makeFeedWriter } from './feed'
import { makeSigner } from './chunk/signer'
import { assertIsFeedType, FeedType } from './feed/type'
import { Signer } from './chunk/signer'
import { downloadSingleOwnerChunk, uploadSingleOwnerChunkData, SOCReader, SOCWriter } from './chunk/soc'
import { Topic, makeTopic, makeTopicFromString } from './feed/topic'
import { downloadSingleOwnerChunk, uploadSingleOwnerChunkData } from './chunk/soc'
import { makeTopic, makeTopicFromString } from './feed/topic'
import { createFeedManifest } from './modules/feed'
import { assertBeeUrl, stripLastSlash } from './utils/url'
import { EthAddress, makeEthAddress, makeHexEthAddress } from './utils/eth'
Expand Down
50 changes: 4 additions & 46 deletions src/chunk/signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,11 @@ import { ec, curve } from 'elliptic'
import { BeeError } from '../utils/error'
import { Bytes, isBytes, verifyBytes, wrapBytesWithHelpers } from '../utils/bytes'
import { keccak256Hash } from '../utils/hash'
import { HexString, hexToBytes, makeHexString } from '../utils/hex'
import { hexToBytes, makeHexString } from '../utils/hex'
import { EthAddress } from '../utils/eth'
import { Data } from '../types'
import { Data, PrivateKeyBytes, Signature, SIGNATURE_BYTES_LENGTH, SIGNATURE_HEX_LENGTH, Signer } from '../types'

/**
* Ethereum compatible signing and recovery
*/
const SIGNATURE_HEX_LENGTH = 130
const SIGNATURE_BYTES_LENGTH = 65

export type Signature = Bytes<typeof SIGNATURE_BYTES_LENGTH>
export type PrivateKey = Bytes<32>
export type PublicKey = Bytes<32> | Bytes<64>

/**
* Signing function that takes digest in Uint8Array to be signed that has helpers to convert it
* conveniently into other types like hex-string (non prefix).
* Result of the signing can be returned either in Uint8Array or hex string form.
*
* @see Data
*/
type SyncSigner = (digest: Data) => Signature | HexString<typeof SIGNATURE_HEX_LENGTH> | string
type AsyncSigner = (digest: Data) => Promise<Signature | HexString<typeof SIGNATURE_HEX_LENGTH> | string>
type EllipticPublicKey = curve.base.BasePoint

/**
* Interface for implementing Ethereum compatible signing.
*
* In order to be compatible with Ethereum and its signing method `personal_sign`, the data
* that are passed to sign() function should be prefixed with: `\x19Ethereum Signed Message:\n${data.length}`, hashed
* and only then signed.
* If you are wrapping another signer tool/library (like Metamask or some other Ethereum wallet), you might not have
* to do this prefixing manually if you use the `personal_sign` method. Check documentation of the tool!
* If you are writing your own storage for keys, then you have to prefix the data manually otherwise the Bee node
* will reject the chunks signed by you!
*
* For example see the hashWithEthereumPrefix() function.
*
* @property sign The sign function that can be sync or async. This function takes non-prefixed data. See above.
* @property address The ethereum address of the signer in bytes.
* @see hashWithEthereumPrefix
*/
export type Signer = {
sign: SyncSigner | AsyncSigner
address: EthAddress
}

const UNCOMPRESSED_RECOVERY_ID = 27

function hashWithEthereumPrefix(data: Uint8Array): Bytes<32> {
Expand All @@ -65,7 +23,7 @@ function hashWithEthereumPrefix(data: Uint8Array): Bytes<32> {
* @param data The data to be signed
* @param privateKey The private key used for signing the data
*/
export function defaultSign(data: Uint8Array, privateKey: PrivateKey): Signature {
export function defaultSign(data: Uint8Array, privateKey: PrivateKeyBytes): Signature {
const curve = new ec('secp256k1')
const keyPair = curve.keyFromPrivate(privateKey)

Expand Down Expand Up @@ -119,7 +77,7 @@ export function recoverAddress(signature: Signature, digest: Uint8Array): EthAdd
*
* @param privateKey The private key
*/
export function makePrivateKeySigner(privateKey: PrivateKey): Signer {
export function makePrivateKeySigner(privateKey: PrivateKeyBytes): Signer {
const curve = new ec('secp256k1')
const keyPair = curve.keyFromPrivate(privateKey)
const address = publicKeyToAddress(keyPair.getPublic())
Expand Down
30 changes: 2 additions & 28 deletions src/chunk/soc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Bytes, bytesAtOffset, bytesEqual, flexBytesAtOffset, verifyBytesAtOffset } from '../utils/bytes'
import { bmtHash } from './bmt'
import { recoverAddress, sign, Signature, Signer } from './signer'
import { recoverAddress, sign } from './signer'
import { keccak256Hash } from '../utils/hash'
import { SPAN_SIZE } from './span'
import { serializeBytes } from './serialize'
Expand All @@ -13,7 +13,7 @@ import {
MIN_PAYLOAD_SIZE,
assertValidChunkData,
} from './cac'
import { ReferenceResponse, UploadOptions } from '../types'
import { ReferenceResponse, UploadOptions, Signature, Signer } from '../types'
import { bytesToHex } from '../utils/hex'
import * as socAPI from '../modules/soc'
import * as chunkAPI from '../modules/chunk'
Expand Down Expand Up @@ -43,32 +43,6 @@ export interface SingleOwnerChunk extends Chunk {
owner: () => EthAddress
}

/**
* Interface for downloading single owner chunks
*/
export interface SOCReader {
/**
* Downloads a single owner chunk
*
* @param identifier The identifier of the chunk
*/
download: (identifier: Identifier) => Promise<SingleOwnerChunk>
}

/**
* Interface for downloading and uploading single owner chunks
*/
export interface SOCWriter extends SOCReader {
/**
* Uploads a single owner chunk
*
* @param identifier The identifier of the chunk
* @param data The chunk payload data
* @param options Upload options
*/
upload: (identifier: Identifier, data: Uint8Array, options?: UploadOptions) => Promise<ReferenceResponse>
}

function recoverChunkOwner(data: Uint8Array): EthAddress {
const cacData = data.slice(SOC_SPAN_OFFSET)
const chunkAddress = bmtHash(cacData)
Expand Down
36 changes: 5 additions & 31 deletions src/feed/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { keccak256Hash } from '../utils/hash'
import { serializeBytes } from '../chunk/serialize'
import { Identifier, uploadSingleOwnerChunkData, verifySingleOwnerChunk } from '../chunk/soc'
import { FeedUpdateOptions, fetchFeedUpdate, FetchFeedUpdateResponse } from '../modules/feed'
import { FeedUpdateOptions, fetchFeedUpdate } from '../modules/feed'
import {
REFERENCE_HEX_LENGTH,
Reference,
Expand All @@ -10,6 +10,10 @@ import {
ENCRYPTED_REFERENCE_HEX_LENGTH,
ENCRYPTED_REFERENCE_BYTES_LENGTH,
REFERENCE_BYTES_LENGTH,
Signer,
FeedReader,
FeedWriter,
Topic,
} from '../types'
import { Bytes, makeBytes, verifyBytesAtOffset } from '../utils/bytes'
import { BeeResponseError } from '../utils/error'
Expand All @@ -18,8 +22,6 @@ import { readUint64BigEndian, writeUint64BigEndian } from '../utils/uint64'
import * as chunkAPI from '../modules/chunk'
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
Expand Down Expand Up @@ -47,34 +49,6 @@ export interface FeedUpdate {
reference: ChunkReference
}

/**
* FeedReader is an interface for downloading feed updates
*/
export interface FeedReader {
readonly type: FeedType
readonly owner: HexEthAddress
readonly topic: Topic
/**
* Download the latest feed update
*/
download(options?: FeedUpdateOptions): Promise<FetchFeedUpdateResponse>
}

/**
* FeedWriter is an interface for updating feeds
*/
export interface FeedWriter extends FeedReader {
/**
* Upload a new feed update
*
* @param reference The reference to be stored in the new update
* @param options Additional options like `at`
*
* @returns The reference of the new update
*/
upload(reference: ChunkReference | Reference, options?: FeedUploadOptions): Promise<ReferenceResponse>
}

export function isEpoch(epoch: unknown): epoch is Epoch {
return typeof epoch === 'object' && epoch !== null && 'time' in epoch && 'level' in epoch
}
Expand Down
8 changes: 2 additions & 6 deletions src/feed/topic.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { keccak256Hash } from '../utils/hash'
import { verifyBytes } from '../utils/bytes'
import { HexString, makeHexString, bytesToHex } from '../utils/hex'

export const TOPIC_BYTES_LENGTH = 32
export const TOPIC_HEX_LENGTH = 64

export type Topic = HexString<typeof TOPIC_HEX_LENGTH>
import { makeHexString, bytesToHex } from '../utils/hex'
import { Topic, TOPIC_BYTES_LENGTH, TOPIC_HEX_LENGTH } from '../types'

export function makeTopic(topic: Uint8Array | string): Topic {
if (typeof topic === 'string') {
Expand Down
3 changes: 1 addition & 2 deletions src/modules/feed.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Dictionary, Reference, ReferenceResponse } from '../types'
import { Dictionary, Reference, ReferenceResponse, Topic } 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
116 changes: 115 additions & 1 deletion src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { BeeError } from '../utils/error'
import type { AxiosRequestConfig } from 'axios'
import { HexString } from '../utils/hex'
import type { Signer } from '../chunk/signer'
import { Bytes } from '../utils/bytes'
import { EthAddress, HexEthAddress } from '../utils/eth'
import { Identifier, SingleOwnerChunk } from '../chunk/soc'
import { FeedType } from '../feed/type'
import { FeedUpdateOptions, FetchFeedUpdateResponse } from '../modules/feed'
import { ChunkReference, FeedUploadOptions } from '../feed'
export * from './debug'

export interface Dictionary<T> {
Expand Down Expand Up @@ -118,6 +123,115 @@ export interface ReferenceResponse {
reference: Reference
}

/*********************************************************
* Writers and Readers interfaces
*/

export const TOPIC_BYTES_LENGTH = 32
export const TOPIC_HEX_LENGTH = 64

/**
* Hex string of length 64 chars without prefix that specifies topics for feed.
*/
export type Topic = HexString<typeof TOPIC_HEX_LENGTH>

/**
* FeedReader is an interface for downloading feed updates
*/
export interface FeedReader {
readonly type: FeedType
readonly owner: HexEthAddress
readonly topic: Topic
/**
* Download the latest feed update
*/
download(options?: FeedUpdateOptions): Promise<FetchFeedUpdateResponse>
}

/**
* FeedWriter is an interface for updating feeds
*/
export interface FeedWriter extends FeedReader {
/**
* Upload a new feed update
*
* @param reference The reference to be stored in the new update
* @param options Additional options like `at`
*
* @returns The reference of the new update
*/
upload(reference: ChunkReference | Reference, options?: FeedUploadOptions): Promise<ReferenceResponse>
}

/**
* Interface for downloading single owner chunks
*/
export interface SOCReader {
/**
* Downloads a single owner chunk
*
* @param identifier The identifier of the chunk
*/
download: (identifier: Identifier) => Promise<SingleOwnerChunk>
}

/**
* Interface for downloading and uploading single owner chunks
*/
export interface SOCWriter extends SOCReader {
/**
* Uploads a single owner chunk
*
* @param identifier The identifier of the chunk
* @param data The chunk payload data
* @param options Upload options
*/
upload: (identifier: Identifier, data: Uint8Array, options?: UploadOptions) => Promise<ReferenceResponse>
}

/*********************************************************
* Ethereum compatible signing interfaces and definitions
*/

export const SIGNATURE_HEX_LENGTH = 130
export const SIGNATURE_BYTES_LENGTH = 65

export type Signature = Bytes<typeof SIGNATURE_BYTES_LENGTH>
export type PrivateKeyBytes = Bytes<32>
export type PublicKeyBytes = Bytes<32> | Bytes<64>

/**
* Signing function that takes digest in Uint8Array to be signed that has helpers to convert it
* conveniently into other types like hex-string (non prefix).
* Result of the signing can be returned either in Uint8Array or hex string form.
*
* @see Data
*/
type SyncSigner = (digest: Data) => Signature | HexString<typeof SIGNATURE_HEX_LENGTH> | string
type AsyncSigner = (digest: Data) => Promise<Signature | HexString<typeof SIGNATURE_HEX_LENGTH> | string>

/**
* Interface for implementing Ethereum compatible signing.
*
* In order to be compatible with Ethereum and its signing method `personal_sign`, the data
* that are passed to sign() function should be prefixed with: `\x19Ethereum Signed Message:\n${data.length}`, hashed
* and only then signed.
* If you are wrapping another signer tool/library (like Metamask or some other Ethereum wallet), you might not have
* to do this prefixing manually if you use the `personal_sign` method. Check documentation of the tool!
* If you are writing your own storage for keys, then you have to prefix the data manually otherwise the Bee node
* will reject the chunks signed by you!
*
* For example see the hashWithEthereumPrefix() function.
*
* @property sign The sign function that can be sync or async. This function takes non-prefixed data. See above.
* @property address The ethereum address of the signer in bytes.
* @see hashWithEthereumPrefix
*/
export type Signer = {
sign: SyncSigner | AsyncSigner
address: EthAddress
}

/**
* These type are used to create new nominal types
*
Expand Down
3 changes: 1 addition & 2 deletions src/utils/eth.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { keccak256, sha3_256 } from 'js-sha3'
import { BrandedString, Data } from '../types'
import { BrandedString, Data, Signer } from '../types'
import { HexString, hexToBytes, intToHex, makeHexString, assertHexString } from './hex'
import { Bytes, verifyBytes } from './bytes'
import { Signer } from '../chunk/signer'

export type OverlayAddress = BrandedString<'OverlayAddress'>
export type EthAddress = Bytes<20>
Expand Down
4 changes: 2 additions & 2 deletions test/feed/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { HexString, hexToBytes, makeHexString } from '../../src/utils/hex'
import { beeUrl, ERR_TIMEOUT, testIdentity } from '../utils'
import { ChunkReference, downloadFeedUpdate, findNextIndex, Index, uploadFeedUpdate } from '../../src/feed'
import { Bytes, verifyBytes } from '../../src/utils/bytes'
import { makePrivateKeySigner, PrivateKey, Signer } from '../../src/chunk/signer'
import { makePrivateKeySigner, PrivateKeyBytes, Signer } from '../../src/chunk/signer'
import { makeContentAddressedChunk } from '../../src/chunk/cac'
import * as chunkAPI from '../../src/modules/chunk'
import { Topic } from '../../src/feed/topic'
Expand Down Expand Up @@ -38,7 +38,7 @@ async function tryUploadFeedUpdate(url: string, signer: Signer, topic: Topic, in
describe('feed', () => {
const url = beeUrl()
const owner = makeHexString(testIdentity.address, 40)
const signer = makePrivateKeySigner(hexToBytes(testIdentity.privateKey) as PrivateKey)
const signer = makePrivateKeySigner(hexToBytes(testIdentity.privateKey) as PrivateKeyBytes)
const topic = '0000000000000000000000000000000000000000000000000000000000000000' as Topic

test(
Expand Down

0 comments on commit 5c1ddac

Please sign in to comment.