diff --git a/package.json b/package.json index 7aa2b68..e40b201 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ ], "main": "./dist/node/index.js", "browser": "./dist/browser/bigchaindb-driver.cjs2.min.js", + "types": "./types/index.d.ts", "sideEffects": false, "scripts": { "lint": "eslint .", diff --git a/src/index.js b/src/index.js index 5723214..ce37222 100644 --- a/src/index.js +++ b/src/index.js @@ -2,9 +2,12 @@ // SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) // Code is Apache-2.0 and docs are CC-BY-4.0 -export Ed25519Keypair from './Ed25519Keypair' +import Ed25519Keypair from './Ed25519Keypair' +import Connection from './connection' +import Transaction from './transaction' +import ccJsonLoad from './utils/ccJsonLoad' +import ccJsonify from './utils/ccJsonify' -export Connection from './connection' -export Transaction from './transaction' -export ccJsonLoad from './utils/ccJsonLoad' -export ccJsonify from './utils/ccJsonify' +export { + ccJsonLoad, ccJsonify, Connection, Ed25519Keypair, Transaction +} diff --git a/src/utils/ccJsonLoad.js b/src/utils/ccJsonLoad.js index 7dc4ec4..a169394 100644 --- a/src/utils/ccJsonLoad.js +++ b/src/utils/ccJsonLoad.js @@ -2,9 +2,8 @@ // SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) // Code is Apache-2.0 and docs are CC-BY-4.0 -import { Buffer } from 'buffer' import base58 from 'bs58' -import cc from 'crypto-conditions' +import { Condition, Ed25519Sha256, ThresholdSha256 } from 'crypto-conditions' /** * Loads a crypto-condition class (Fulfillment or Condition) from a BigchainDB JSON object @@ -13,17 +12,18 @@ import cc from 'crypto-conditions' */ export default function ccJsonLoad(conditionJson) { if ('hash' in conditionJson) { - const condition = new cc.Condition() - condition.type = conditionJson.type_id - condition.bitmask = conditionJson.bitmask - condition.hash = Buffer.from(base58.decode(conditionJson.hash)) + const condition = new Condition() + condition.setTypeId(conditionJson.type_id) + condition.setSubtypes(conditionJson.bitmask) + condition.setHash(base58.decode(conditionJson.hash)) + // TODO: fix this, maxFulfillmentLength cannot be set in CryptoCondition lib condition.maxFulfillmentLength = parseInt(conditionJson.max_fulfillment_length, 10) return condition } else { let fulfillment if (conditionJson.type === 'threshold-sha-256') { - fulfillment = new cc.ThresholdSha256() + fulfillment = new ThresholdSha256() fulfillment.threshold = conditionJson.threshold conditionJson.subconditions.forEach((subconditionJson) => { const subcondition = ccJsonLoad(subconditionJson) @@ -36,8 +36,8 @@ export default function ccJsonLoad(conditionJson) { } if (conditionJson.type === 'ed25519-sha-256') { - fulfillment = new cc.Ed25519Sha256() - fulfillment.publicKey = Buffer.from(base58.decode(conditionJson.public_key)) + fulfillment = new Ed25519Sha256() + fulfillment.setPublicKey(base58.decode(conditionJson.public_key)) } return fulfillment } diff --git a/src/utils/ccJsonify.js b/src/utils/ccJsonify.js index 52f3ddb..9abb47b 100644 --- a/src/utils/ccJsonify.js +++ b/src/utils/ccJsonify.js @@ -19,8 +19,8 @@ export default function ccJsonify(fulfillment) { } const jsonBody = { - 'details': {}, - 'uri': conditionUri, + details: {}, + uri: conditionUri, } if (fulfillment.getTypeId() === 0) { @@ -35,15 +35,15 @@ export default function ccJsonify(fulfillment) { if (fulfillment.getTypeId() === 2) { return { - 'details': { - 'type': 'threshold-sha-256', - 'threshold': fulfillment.threshold, - 'subconditions': fulfillment.subconditions.map((subcondition) => { + details: { + type: 'threshold-sha-256', + threshold: fulfillment.threshold, + subconditions: fulfillment.subconditions.map((subcondition) => { const subconditionJson = ccJsonify(subcondition.body) return subconditionJson.details }) }, - 'uri': conditionUri, + uri: conditionUri, } } diff --git a/test/transaction/test_cryptoconditions.js b/test/transaction/test_cryptoconditions.js index d9a102e..4fedc29 100644 --- a/test/transaction/test_cryptoconditions.js +++ b/test/transaction/test_cryptoconditions.js @@ -2,8 +2,10 @@ // SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) // Code is Apache-2.0 and docs are CC-BY-4.0 +import { createHash } from 'crypto' +import { validateFulfillment } from 'crypto-conditions' import test from 'ava' -import cc from 'crypto-conditions' +import base58 from 'bs58' import { Ed25519Keypair, Transaction, ccJsonLoad } from '../../src' import { delegatedSignTransaction } from '../constants' import sha256Hash from '../../src/sha256Hash' @@ -89,7 +91,7 @@ test('Fulfillment correctly formed', t => { .concat(txTransfer.inputs[0].fulfills.output_index) : msg const msgHash = sha256Hash(msgUniqueFulfillment) - t.truthy(cc.validateFulfillment( + t.truthy(validateFulfillment( txSigned.inputs[0].fulfillment, txCreate.outputs[0].condition.uri, Buffer.from(msgHash, 'hex') )) @@ -114,15 +116,16 @@ test('Delegated signature is correct', t => { }) test('CryptoConditions JSON load', t => { + const publicKey = '4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS' const cond = ccJsonLoad({ type: 'threshold-sha-256', threshold: 1, subconditions: [{ type: 'ed25519-sha-256', - public_key: 'a' + public_key: publicKey }, { - hash: 'a' + hash: base58.encode(createHash('sha256').update('a').digest()) }], }) t.truthy(cond.subconditions.length === 2) diff --git a/types/Ed25519Keypair.d.ts b/types/Ed25519Keypair.d.ts new file mode 100644 index 0000000..71fcdbc --- /dev/null +++ b/types/Ed25519Keypair.d.ts @@ -0,0 +1,10 @@ +// Copyright BigchainDB GmbH and BigchainDB contributors +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + +export default class Ed25519Keypair { + publicKey: string; + privateKey: string; + + constructor(seed?: Buffer); +} diff --git a/types/baseRequest.d.ts b/types/baseRequest.d.ts new file mode 100644 index 0000000..177506d --- /dev/null +++ b/types/baseRequest.d.ts @@ -0,0 +1,31 @@ +// Copyright BigchainDB GmbH and BigchainDB contributors +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + +export interface RequestConfig { + headers?: Record; + jsonBody?: Record; + query?: Record; + method?: 'GET' | ' POST' | 'PUT'; + urlTemplateSpec?: any[] | Record; + [key: string]: any; +} + +export function ResponseError( + message: string, + status?: number, + requestURI?: string +): void; + +declare function timeout( + ms: number, + promise: Promise +): Promise; + +declare function handleResponse(res: Response): Response; + +export default function baseRequest( + url: string, + config: RequestConfig = {}, + requestTimeout?: number +): Promise; diff --git a/types/connection.d.ts b/types/connection.d.ts new file mode 100644 index 0000000..336f8f4 --- /dev/null +++ b/types/connection.d.ts @@ -0,0 +1,162 @@ +// Copyright BigchainDB GmbH and BigchainDB contributors +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + +import type { RequestConfig } from './baseRequest'; +import type { Node } from './request'; +import type Transport from './transport'; +import type { + CreateTransaction, + TransactionOperations, + TransferTransaction, + TransactionCommon, +} from './transaction'; + +declare const HEADER_BLACKLIST = ['content-type']; +declare const DEFAULT_NODE = 'http://localhost:9984/api/v1/'; +declare const DEFAULT_TIMEOUT = 20000; // The default value is 20 seconds + +export interface InputNode { + endpoint: string; +} + +export enum Endpoints { + blocks = 'blocks', + blocksDetail = 'blocksDetail', + outputs = 'outputs', + transactions = 'transactions', + transactionsSync = 'transactionsSync', + transactionsAsync = 'transactionsAsync', + transactionsCommit = 'transactionsCommit', + transactionsDetail = 'transactionsDetail', + assets = 'assets', + metadata = 'metadata', +} + +export interface EndpointsUrl { + [Endpoints.blocks]: 'blocks'; + [Endpoints.blocksDetail]: 'blocks/%(blockHeight)s'; + [Endpoints.outputs]: 'outputs'; + [Endpoints.transactions]: 'transactions'; + [Endpoints.transactionsSync]: 'transactions?mode=sync'; + [Endpoints.transactionsAsync]: 'transactions?mode=async'; + [Endpoints.transactionsCommit]: 'transactions?mode=commit'; + [Endpoints.transactionsDetail]: 'transactions/%(transactionId)s'; + [Endpoints.assets]: 'assets'; + [Endpoints.metadata]: 'metadata'; +} + +export interface EndpointsResponse< + O = TransactionOperations.CREATE, + A = Record, + M = Record +> { + [Endpoints.blocks]: number[]; + [Endpoints.blocksDetail]: { + height: number; + transactions: (CreateTransaction | TransferTransaction)[]; + }; + [Endpoints.outputs]: { + transaction_id: string; + output_index: number; + }[]; + [Endpoints.transactions]: O extends TransactionOperations.CREATE + ? CreateTransaction[] + : O extends TransactionOperations.TRANSFER + ? TransferTransaction[] + : (CreateTransaction | TransferTransaction)[]; + [Endpoints.transactionsSync]: O extends TransactionOperations.CREATE + ? CreateTransaction + : TransferTransaction; + [Endpoints.transactionsAsync]: O extends TransactionOperations.CREATE + ? CreateTransaction + : TransferTransaction; + [Endpoints.transactionsCommit]: O extends TransactionOperations.CREATE + ? CreateTransaction + : TransferTransaction; + [Endpoints.transactionsDetail]: O extends TransactionOperations.CREATE + ? CreateTransaction + : TransferTransaction; + [Endpoints.assets]: { id: string; data: Record }[]; + [Endpoints.metadata]: { id: string; metadata: Record }[]; +} + +export default class Connection { + private transport: Transport; + private normalizedNodes: Node[]; + private headers: Record; + + constructor( + nodes: string | InputNode | (string | InputNode)[], + headers: Record = {}, + timeout?: number + ); + + static normalizeNode( + node: string | InputNode, + headers: Record + ): Node; + + static getApiUrls(endpoint: E): EndpointsUrl[E]; + + private _req>( + path: EndpointsUrl[E], + options: RequestConfig = {} + ): Promise; + + getBlock( + blockHeight: number | string + ): Promise; + + getTransaction( + transactionId: string + ): Promise[Endpoints.transactionsDetail]>; + + listBlocks(transactionId: string): Promise; + + listOutputs( + publicKey: string, + spent?: boolean + ): Promise; + + listTransactions( + assetId: string, + operation?: TransactionOperations + ): Promise[Endpoints.transactions]>; + + postTransaction< + O = TransactionOperations.CREATE, + A = Record, + M = Record + >( + transaction: TransactionCommon + ): Promise[Endpoints.transactionsCommit]>; + + postTransactionSync< + O = TransactionOperations.CREATE, + A = Record, + M = Record + >( + transaction: TransactionCommon + ): Promise[Endpoints.transactionsSync]>; + + postTransactionAsync< + O = TransactionOperations.CREATE, + A = Record, + M = Record + >( + transaction: TransactionCommon + ): Promise[Endpoints.transactionsAsync]>; + + postTransactionCommit< + O = TransactionOperations.CREATE, + A = Record, + M = Record + >( + transaction: TransactionCommon + ): Promise[Endpoints.transactionsCommit]>; + + searchAssets(search: string): Promise; + + searchMetadata(search: string): Promise; +} diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 0000000..a10316a --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,11 @@ +// Copyright BigchainDB GmbH and BigchainDB contributors +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + +import Ed25519Keypair from './Ed25519Keypair' +import Connection from './connection' +import Transaction from './transaction' +import ccJsonLoad from './utils/ccJsonLoad' +import ccJsonify from './utils/ccJsonify' + +export { ccJsonLoad, ccJsonify, Connection, Ed25519Keypair, Transaction } diff --git a/types/request.d.ts b/types/request.d.ts new file mode 100644 index 0000000..9f4a649 --- /dev/null +++ b/types/request.d.ts @@ -0,0 +1,32 @@ +// Copyright BigchainDB GmbH and BigchainDB contributors +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + +import type { RequestConfig } from './baseRequest'; + +export interface Node { + endpoint: string; + headers: Record; +} + +export default class Request { + private node: Node; + private backoffTime: number; + private retries: number; + private connectionError?: Error; + + constructor(node: Node); + + async request>( + urlPath: string, + config: RequestConfig = {}, + timeout?: number, + maxBackoffTime?: number + ): Promise; + + updateBackoffTime(maxBackoffTime: number): void; + + getBackoffTimedelta(): number; + + static sleep(ms: number): void; +} diff --git a/types/sanitize.d.ts b/types/sanitize.d.ts new file mode 100644 index 0000000..48ab058 --- /dev/null +++ b/types/sanitize.d.ts @@ -0,0 +1,25 @@ +// Copyright BigchainDB GmbH and BigchainDB contributors +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + +declare type FilterFn = (val: any, key?: string) => void; + +declare function filterFromObject>( + obj: I, + filter: Array | FilterFn, + conf: { isInclusion?: boolean } = {} +): Partial; + +declare function applyFilterOnObject>( + obj: I, + filterFn?: FilterFn +): Partial; + +declare function selectFromObject>( + obj: I, + filter: Array | FilterFn +): Partial; + +export default function sanitize>( + obj: I +): Partial | I; diff --git a/types/transaction.d.ts b/types/transaction.d.ts new file mode 100644 index 0000000..ff0011e --- /dev/null +++ b/types/transaction.d.ts @@ -0,0 +1,214 @@ +import type { + Ed25519Sha256, + Fulfillment, + PreimageSha256, + ThresholdSha256, +} from 'crypto-conditions'; +import { + Ed25519Sha256JSONCondition, + PreimageSha256JSONCondition, + ThresholdSha256JSONCondition, +} from './utils/ccJsonify'; + +export interface TransactionInput { + fulfillment: string; + fulfills: { + output_index: number; + transaction_id: string; + } | null; + owners_before: string[]; +} +export interface TransactionOutput { + amount: string; + condition: + | PreimageSha256JSONCondition + | ThresholdSha256JSONCondition + | Ed25519Sha256JSONCondition; + public_keys: string[]; +} + +export enum TransactionOperations { + CREATE = 'CREATE', + TRANSFER = 'TRANSFER', +} + +export interface TransactionCommon< + O = TransactionOperations, + A = Record, + M = Record +> { + id?: string; + inputs: TransactionInput[]; + outputs: TransactionOutput[]; + version: string; + metadata: M; + operation: O; + asset: TransactionAssetMap; +} + +export interface TransactionCommonSigned< + O = TransactionOperations, + A = Record, + M = Record +> extends Omit, 'id'> { + id: string; +} + +export type TransactionAssetMap< + Operation, + A = Record +> = Operation extends TransactionOperations.CREATE + ? { + data: A; + } + : { + id: string; + }; + +export interface CreateTransaction< + A = Record, + M = Record +> extends TransactionCommon { + id: string; + asset: TransactionAssetMap; + operation: TransactionOperations.CREATE; +} + +export interface TransferTransaction> + extends TransactionCommon { + id: string; + asset: TransactionAssetMap; + operation: TransactionOperations.TRANSFER; +} + +export interface TransactionUnspentOutput { + tx: TransactionCommon; + output_index: number; +} + +interface TxTemplate { + id: null; + operation: null; + outputs: []; + inputs: []; + metadata: null; + asset: null; + version: '2.0'; +} + +declare function DelegateSignFunction( + serializedTransaction: string, + input: TransactionInput, + index?: number +): string; + +export default class Transaction { + static serializeTransactionIntoCanonicalString( + transaction: CreateTransaction | TransferTransaction + ): string; + + static makeEd25519Condition(publicKey: string): Ed25519Sha256JSONCondition; + + static makeEd25519Condition( + publicKey: string, + json: true + ): Ed25519Sha256JSONCondition; + + static makeEd25519Condition(publicKey: string, json: false): Ed25519Sha256; + + static makeEd25519Condition( + publicKey: string, + json: boolean = true + ): Ed25519Sha256 | Ed25519Sha256JSONCondition; + + static makeSha256Condition(preimage: string): PreimageSha256JSONCondition; + + static makeSha256Condition( + preimage: string, + json: true + ): PreimageSha256JSONCondition; + + static makeSha256Condition(preimage: string, json: false): PreimageSha256; + + static makeSha256Condition( + preimage: string, + json: boolean = true + ): PreimageSha256 | PreimageSha256JSONCondition; + + static makeThresholdCondition( + threshold: number, + subconditions: (string | Fulfillment)[] + ): ThresholdSha256JSONCondition; + + static makeThresholdCondition( + threshold: number, + subconditions: (string | Fulfillment)[], + json: true + ): ThresholdSha256JSONCondition; + + static makeThresholdCondition( + threshold: number, + subconditions: (string | Fulfillment)[], + json: false + ): ThresholdSha256; + + static makeThresholdCondition( + threshold: number, + subconditions: (string | Fulfillment)[], + json: boolean = true + ): ThresholdSha256 | ThresholdSha256JSONCondition; + + static makeInputTemplate( + publicKeys: string[], + fulfills?: TransactionInput['fulfills'], + fulfillment?: TransactionInput['fulfillment'] + ): TransactionInput; + + static makeOutput( + condition: + | PreimageSha256JSONCondition + | ThresholdSha256JSONCondition + | Ed25519Sha256JSONCondition, + amount: string = '1' + ): TransactionOutput; + + static makeTransactionTemplate(): TxTemplate; + + static makeTransaction< + O extends keyof TransactionOperations, + A = Record, + M = Record + >( + operation: O, + asset: A, + metadata: M, + outputs: TransactionOutput[], + inputs: TransactionInput[] + ): TransactionCommon; + + static makeCreateTransaction< + A = Record, + M = Record + >( + asset: A, + metadata: M, + outputs: TransactionOutput[], + ...issuers: string[] + ): CreateTransaction; + + static makeTransferTransaction>( + unspentOutputs: TransactionUnspentOutput[], + outputs: TransactionOutput[], + metadata: M + ): TransferTransaction; + + static signTransaction( + transaction: TransactionCommon, + ...privateKeys: string[] + ): TransactionCommonSigned; + + static delegateSignTransaction( + transaction: TransactionCommon, + signFn: DelegateSignFunction + ): TransactionCommonSigned; +} diff --git a/types/transport.d.ts b/types/transport.d.ts new file mode 100644 index 0000000..3119057 --- /dev/null +++ b/types/transport.d.ts @@ -0,0 +1,21 @@ +// Copyright BigchainDB GmbH and BigchainDB contributors +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + +import Request, { Node } from './request'; +import type { RequestConfig } from './baseRequest'; + +export default class Transport { + private connectionPool: Request[]; + private timeout: number; + private maxBackoffTime: number; + + constructor(nodes: Node[], timeout: number); + + pickConnection(): Request; + + async forwardRequest>( + path: string, + config: RequestConfig + ): Promise; +} diff --git a/types/utils/ccJsonLoad.d.ts b/types/utils/ccJsonLoad.d.ts new file mode 100644 index 0000000..16053b2 --- /dev/null +++ b/types/utils/ccJsonLoad.d.ts @@ -0,0 +1,32 @@ +// Copyright BigchainDB GmbH and BigchainDB contributors +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + +import type { + Condition, + Ed25519Sha256, + PreimageSha256, + ThresholdSha256, +} from 'crypto-conditions'; +import type { + Ed25519Sha256JSONCondition, + JSONCondition, + PreimageSha256JSONCondition, + ThresholdSha256JSONCondition, +} from './ccJsonify'; + +declare function ccJsonLoad( + conditionJson: PreimageSha256JSONCondition +): PreimageSha256; + +declare function ccJsonLoad( + conditionJson: ThresholdSha256JSONCondition +): ThresholdSha256; + +declare function ccJsonLoad( + conditionJson: Ed25519Sha256JSONCondition +): Ed25519Sha256; + +declare function ccJsonLoad(conditionJson: JSONCondition): Condition; + +export default ccJsonLoad; diff --git a/types/utils/ccJsonify.d.ts b/types/utils/ccJsonify.d.ts new file mode 100644 index 0000000..2e6cbe0 --- /dev/null +++ b/types/utils/ccJsonify.d.ts @@ -0,0 +1,70 @@ +// Copyright BigchainDB GmbH and BigchainDB contributors +// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +// Code is Apache-2.0 and docs are CC-BY-4.0 + +import type { + Condition, + Ed25519Sha256, + PreimageSha256, + ThresholdSha256, +} from 'crypto-conditions'; +import type { TypeId } from 'crypto-conditions/types/types'; + +interface BaseJSONCondition { + details: { + [key: string]: any; + }; + uri: string; +} + +export interface JSONCondition extends BaseJSONCondition { + details: { + type_id: TypeId; + bitmask: number; + type: 'condition'; + hash: string; + max_fulfillment_length: number; + }; +} + +export interface PreimageSha256JSONCondition extends BaseJSONCondition { + details: { + type_id: TypeId.PreimageSha256; + bitmask: 3; + preimage?: string; + type?: 'fulfillement'; + }; +} + +export interface ThresholdSha256JSONCondition extends BaseJSONCondition { + details: { + type: TypeName.ThresholdSha256; + subConditions: (Ed25519Sha256JSONCondition | PreimageSha256JSONCondition)[]; + }; +} + +export interface Ed25519Sha256JSONCondition extends BaseJSONCondition { + details: { type: TypeName.Ed25519Sha256; publicKey?: string }; +} + +export type JSONConditionUnion = + | JSONCondition + | PreimageSha256JSONCondition + | ThresholdSha256JSONCondition + | Ed25519Sha256JSONCondition; + +declare function ccJsonify( + fulfillment: PreimageSha256 +): PreimageSha256JSONCondition; + +declare function ccJsonify( + fulfillment: ThresholdSha256 +): ThresholdSha256JSONCondition; + +declare function ccJsonify( + fulfillment: Ed25519Sha256 +): Ed25519Sha256JSONCondition; + +declare function ccJsonify(fulfillment: Condition): JSONCondition; + +export default ccJsonify;