1
0
mirror of https://github.com/bigchaindb/js-bigchaindb-driver.git synced 2024-11-22 01:36:56 +01:00

restructure driver and make cjs compatible

update examples and tutorials
This commit is contained in:
diminator 2017-04-03 17:54:48 +02:00
parent 63814c47bc
commit a338a5c570
37 changed files with 1292 additions and 2143 deletions

View File

@ -1,18 +1,26 @@
{
'presets': [['latest', { es2015: { modules: false } }]],
'presets': ['es2015-no-commonjs'],
'plugins': [
'transform-export-extensions',
'transform-object-assign',
'transform-object-rest-spread',
[ 'transform-runtime', {
'polyfill': false,
'regenerator': false
} ]
'transform-object-rest-spread'
],
'sourceMaps': true,
'env': {
'bundle': {
'plugins': [
['transform-runtime', {
'polyfill': true,
'regenerator': false
}]
]
},
'cjs': {
'presets': ['latest']
'plugins': [
'add-module-exports',
'transform-es2015-modules-commonjs'
]
}
}
}
}

1862
dist/bundle/bundle.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

401
dist/node/index.js vendored
View File

@ -1,403 +1,26 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
value: true
});
exports.BDB_SERVER_URL = undefined;
exports.Connection = exports.Transaction = exports.Ed25519KeyPair = undefined;
var _extends2 = require('babel-runtime/helpers/extends');
var _Ed25519Keypair = require('./Ed25519Keypair');
var _extends3 = _interopRequireDefault(_extends2);
var _Ed25519Keypair2 = _interopRequireDefault(_Ed25519Keypair);
exports.Ed25519Keypair = Ed25519Keypair;
exports.makeEd25519Condition = makeEd25519Condition;
exports.makeOutput = makeOutput;
exports.makeCreateTransaction = makeCreateTransaction;
exports.makeTransferTransaction = makeTransferTransaction;
exports.signTransaction = signTransaction;
exports.testMe = testMe;
var _transaction = require('./transaction');
var _buffer = require('buffer');
var _Transaction = _interopRequireWildcard(_transaction);
var _bs = require('bs58');
var _connection = require('./connection');
var _bs2 = _interopRequireDefault(_bs);
var _Connection = _interopRequireWildcard(_connection);
var _clone = require('clone');
var _clone2 = _interopRequireDefault(_clone);
var _fiveBellsCondition = require('five-bells-condition');
var _fiveBellsCondition2 = _interopRequireDefault(_fiveBellsCondition);
var _tweetnacl = require('tweetnacl');
var _tweetnacl2 = _interopRequireDefault(_tweetnacl);
var _jsSha = require('js-sha3');
var _jsSha2 = _interopRequireDefault(_jsSha);
var _jsonStableStringify = require('json-stable-stringify');
var _jsonStableStringify2 = _interopRequireDefault(_jsonStableStringify);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* @class Keypair Ed25519 keypair in base58 (as BigchainDB expects base58 keys)
* @type {Object}
* @property {string} publicKey
* @property {string} privateKey
*/
function Ed25519Keypair() {
var keyPair = _tweetnacl2.default.sign.keyPair();
this.publicKey = _bs2.default.encode(keyPair.publicKey);
// tweetnacl's generated secret key is the secret key + public key (resulting in a 64-byte buffer)
this.privateKey = _bs2.default.encode(keyPair.secretKey.slice(0, 32));
}
/**
* Create an Ed25519 Cryptocondition from an Ed25519 public key to put into an Output of a Transaction
* @param {string} publicKey base58 encoded Ed25519 public key for the recipient of the Transaction
* @returns {object} Ed25519 Condition (that will need to wrapped in an Output)
*/
function makeEd25519Condition(publicKey) {
var publicKeyBuffer = new _buffer.Buffer(_bs2.default.decode(publicKey));
var ed25519Fulfillment = new _fiveBellsCondition2.default.Ed25519();
ed25519Fulfillment.setPublicKey(publicKeyBuffer);
var conditionUri = ed25519Fulfillment.getConditionUri();
return {
'details': {
'signature': null,
'type_id': 4,
'type': 'fulfillment',
'bitmask': 32,
'public_key': publicKey
},
'uri': conditionUri
};
}
/**
* Create an Output from a Condition.
* Note: Assumes the given Condition was generated from a single public key (e.g. a Ed25519 Condition)
* @param {object} condition Condition (e.g. a Ed25519 Condition from `makeEd25519Condition()`)
* @param {number} amount Amount of the output
* @returns {object} An Output usable in a Transaction
*/
function makeOutput(condition) {
var amount = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
return {
amount: amount,
condition: condition,
'public_keys': [condition.details.public_key]
};
}
/**
* Generate a `CREATE` transaction holding the `asset`, `metadata`, and `outputs`, to be signed by
* the `issuers`.
* @param {object} asset Created asset's data
* @param {object} metadata Metadata for the Transaction
* @param {object[]} outputs Array of Output objects to add to the Transaction.
* Think of these as the recipients of the asset after the transaction.
* For `CREATE` Transactions, this should usually just be a list of
* Outputs wrapping Ed25519 Conditions generated from the issuers' public
* keys (so that the issuers are the recipients of the created asset).
* @param {...string[]} issuers Public key of one or more issuers to the asset being created by this
* Transaction.
* Note: Each of the private keys corresponding to the given public
* keys MUST be used later (and in the same order) when signing the
* Transaction (`signTransaction()`).
* @returns {object} Unsigned transaction -- make sure to call signTransaction() on it before
* sending it off!
*/
function makeCreateTransaction(asset, metadata, outputs) {
var assetDefinition = {
'data': asset || null
};
for (var _len = arguments.length, issuers = Array(_len > 3 ? _len - 3 : 0), _key = 3; _key < _len; _key++) {
issuers[_key - 3] = arguments[_key];
}
var inputs = issuers.map(function (issuer) {
return makeInputTemplate([issuer]);
});
return makeTransaction('CREATE', assetDefinition, metadata, outputs, inputs);
}
/**
* Generate a `TRANSFER` transaction holding the `asset`, `metadata`, and `outputs`, that fulfills
* the `fulfilledOutputs` of `unspentTransaction`.
* @param {object} unspentTransaction Previous Transaction you have control over (i.e. can fulfill
* its Output Condition)
* @param {object} metadata Metadata for the Transaction
* @param {object[]} outputs Array of Output objects to add to the Transaction.
* Think of these as the recipients of the asset after the transaction.
* For `TRANSFER` Transactions, this should usually just be a list of
* Outputs wrapping Ed25519 Conditions generated from the public keys of
* the recipients.
* @param {...number} fulfilledOutputs Indices of the Outputs in `unspentTransaction` that this
* Transaction fulfills.
* Note that the public keys listed in the fulfilled Outputs
* must be used (and in the same order) to sign the Transaction
* (`signTransaction()`).
* @returns {object} Unsigned transaction -- make sure to call signTransaction() on it before
* sending it off!
*/
function makeTransferTransaction(unspentTransaction, metadata, outputs) {
for (var _len2 = arguments.length, fulfilledOutputs = Array(_len2 > 3 ? _len2 - 3 : 0), _key2 = 3; _key2 < _len2; _key2++) {
fulfilledOutputs[_key2 - 3] = arguments[_key2];
}
var inputs = fulfilledOutputs.map(function (outputIndex) {
var fulfilledOutput = unspentTransaction.outputs[outputIndex];
var transactionLink = {
'output': outputIndex,
'txid': unspentTransaction.id
};
return makeInputTemplate(fulfilledOutput.public_keys, transactionLink);
});
var assetLink = {
'id': unspentTransaction.operation === 'CREATE' ? unspentTransaction.id : unspentTransaction.asset.id
};
return makeTransaction('TRANSFER', assetLink, metadata, outputs, inputs);
}
/**
* Sign the given `transaction` with the given `privateKey`s, returning a new copy of `transaction`
* that's been signed.
* Note: Only generates Ed25519 Fulfillments. Thresholds and other types of Fulfillments are left as
* an exercise for the user.
* @param {object} transaction Transaction to sign. `transaction` is not modified.
* @param {...string} privateKeys Private keys associated with the issuers of the `transaction`.
* Looped through to iteratively sign any Input Fulfillments found in
* the `transaction`.
* @returns {object} The signed version of `transaction`.
*/
function signTransaction(transaction) {
for (var _len3 = arguments.length, privateKeys = Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
privateKeys[_key3 - 1] = arguments[_key3];
}
var signedTx = (0, _clone2.default)(transaction);
signedTx.inputs.forEach(function (input, index) {
var privateKey = privateKeys[index];
var privateKeyBuffer = new _buffer.Buffer(_bs2.default.decode(privateKey));
var serializedTransaction = serializeTransactionIntoCanonicalString(transaction, input);
var ed25519Fulfillment = new _fiveBellsCondition2.default.Ed25519();
ed25519Fulfillment.sign(new _buffer.Buffer(serializedTransaction), privateKeyBuffer);
var fulfillmentUri = ed25519Fulfillment.serializeUri();
input.fulfillment = fulfillmentUri;
});
return signedTx;
}
/*********************
* Transaction utils *
*********************/
function makeTransactionTemplate() {
return {
'id': null,
'operation': null,
'outputs': [],
'inputs': [],
'metadata': null,
'asset': null,
'version': '0.9'
};
}
function makeInputTemplate() {
var publicKeys = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var fulfills = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var fulfillment = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
return {
fulfillment: fulfillment,
fulfills: fulfills,
'owners_before': publicKeys
};
}
function makeTransaction(operation, asset) {
var metadata = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
var outputs = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
var inputs = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : [];
var tx = makeTransactionTemplate();
tx.operation = operation;
tx.asset = asset;
tx.metadata = metadata;
tx.inputs = inputs;
tx.outputs = outputs;
// Hashing must be done after, as the hash is of the Transaction (up to now)
tx.id = hashTransaction(tx);
return tx;
}
/****************
* Crypto utils *
****************/
function hashTransaction(transaction) {
// Safely remove any tx id from the given transaction for hashing
var tx = (0, _extends3.default)({}, transaction);
delete tx.id;
return sha256Hash(serializeTransactionIntoCanonicalString(tx));
}
function sha256Hash(data) {
return _jsSha2.default.sha3_256.create().update(data).hex();
}
function serializeTransactionIntoCanonicalString(transaction, input) {
// BigchainDB signs fulfillments by serializing transactions into a "canonical" format where
// each fulfillment URI is removed before sorting the remaining keys
var tx = (0, _clone2.default)(transaction);
var match = void 0;
tx.inputs.forEach(function (_input) {
if (!(_input && input && _input['fulfills'] && input['fulfills'] && !(_input['fulfills']['txid'] === input['fulfills']['txid'] && _input['fulfills']['output'] === input['fulfills']['output']))) {
match = tx.inputs.indexOf(_input);
}
_input.fulfillment = null;
});
if (input && match >= 0 && tx.inputs) {
tx.inputs = [tx.inputs[match]];
}
// Sort the keys
return (0, _jsonStableStringify2.default)(tx, function (a, b) {
return a.key > b.key ? 1 : -1;
});
}
var BDB_SERVER_URL = exports.BDB_SERVER_URL = process.env.BDB_SERVER_URL;
function testMe() {
return 'test';
}
// import { request as baseRequest, sanitize } from 'js-utility-belt/es6';
//
// const FLASK_BASE_URL = process.env.FLASK_BASE_URL;
// export const BDB_SERVER_URL = process.env.BDB_SERVER_URL;
// const API_PATH = `${BDB_SERVER_URL}/api/v1/`;
//
//
// const DEFAULT_REQUEST_CONFIG = {
// credentials: 'include',
// headers: {
// 'Accept': 'application/json'
// }
// };
//
// /**
// * Small wrapper around js-utility-belt's request that provides url resolving, default settings, and
// * response handling.
// */
// function request(url, config = {}) {
// // Load default fetch configuration and remove any falsy query parameters
// const requestConfig = Object.assign({}, DEFAULT_REQUEST_CONFIG, config, {
// query: config.query && sanitize(config.query)
// });
// let apiUrl = url;
//
// if (requestConfig.jsonBody) {
// requestConfig.headers = Object.assign({}, requestConfig.headers, {
// 'Content-Type': 'application/json'
// });
// }
// if (!url) {
// return Promise.reject(new Error('Request was not given a url.'));
// } else if (!url.match(/^http/)) {
// apiUrl = ApiUrls[url];
// if (!apiUrl) {
// return Promise.reject(new Error(`Request could not find a url mapping for "${url}"`));
// }
// }
//
// return baseRequest(apiUrl, requestConfig)
// .then((res) => res.json())
// .catch((err) => {
// console.error(err);
// throw err;
// });
// }
//
//
//
// export function requestTransaction(txId) {
// return request(ApiUrls['transactions_detail'], {
// urlTemplateSpec: {
// txId
// }
// });
// }
//
// export function postTransaction(transaction) {
// return request(ApiUrls['transactions'], {
// method: 'POST',
// jsonBody: transaction
// })
// }
//
// export function listTransactions({ asset_id, operation }) {
// return request(ApiUrls['transactions'], {
// query: {
// asset_id,
// operation
// }
// })
// }
//
// export function pollStatusAndFetchTransaction(transaction) {
// return new Promise((resolve, reject) => {
// const timer = setInterval(() => {
// requestStatus(transaction.id)
// .then((res) => {
// console.log('Fetched transaction status:', res);
// if (res.status === 'valid') {
// clearInterval(timer);
// requestTransaction(transaction.id)
// .then((res) => {
// console.log('Fetched transaction:', res);
// resolve();
// });
// }
// });
// }, 500)
// })
// }
//
// export function listOutputs({ public_key, unspent }) {
// return request(ApiUrls['outputs'], {
// query: {
// public_key,
// unspent
// }
// })
// }
//
// export function requestStatus(tx_id) {
// return request(ApiUrls['statuses'], {
// query: {
// tx_id
// }
// });
// }
exports.Ed25519KeyPair = _Ed25519Keypair2.default;
exports.Transaction = _Transaction;
exports.Connection = _Connection;

385
index.js
View File

@ -1,385 +0,0 @@
import { Buffer } from 'buffer';
import base58 from 'bs58';
import clone from 'clone';
import cc from 'five-bells-condition';
import nacl from 'tweetnacl';
import sha3 from 'js-sha3';
import stableStringify from 'json-stable-stringify';
/**
* @class Keypair Ed25519 keypair in base58 (as BigchainDB expects base58 keys)
* @type {Object}
* @property {string} publicKey
* @property {string} privateKey
*/
export function Ed25519Keypair() {
const keyPair = nacl.sign.keyPair();
this.publicKey = base58.encode(keyPair.publicKey);
// tweetnacl's generated secret key is the secret key + public key (resulting in a 64-byte buffer)
this.privateKey = base58.encode(keyPair.secretKey.slice(0, 32));
}
/**
* Create an Ed25519 Cryptocondition from an Ed25519 public key to put into an Output of a Transaction
* @param {string} publicKey base58 encoded Ed25519 public key for the recipient of the Transaction
* @returns {object} Ed25519 Condition (that will need to wrapped in an Output)
*/
export function makeEd25519Condition(publicKey) {
const publicKeyBuffer = new Buffer(base58.decode(publicKey));
const ed25519Fulfillment = new cc.Ed25519();
ed25519Fulfillment.setPublicKey(publicKeyBuffer);
const conditionUri = ed25519Fulfillment.getConditionUri();
return {
'details': {
'signature': null,
'type_id': 4,
'type': 'fulfillment',
'bitmask': 32,
'public_key': publicKey,
},
'uri': conditionUri,
};
}
/**
* Create an Output from a Condition.
* Note: Assumes the given Condition was generated from a single public key (e.g. a Ed25519 Condition)
* @param {object} condition Condition (e.g. a Ed25519 Condition from `makeEd25519Condition()`)
* @param {number} amount Amount of the output
* @returns {object} An Output usable in a Transaction
*/
export function makeOutput(condition, amount = 1) {
return {
amount,
condition,
'public_keys': [condition.details.public_key],
};
}
/**
* Generate a `CREATE` transaction holding the `asset`, `metadata`, and `outputs`, to be signed by
* the `issuers`.
* @param {object} asset Created asset's data
* @param {object} metadata Metadata for the Transaction
* @param {object[]} outputs Array of Output objects to add to the Transaction.
* Think of these as the recipients of the asset after the transaction.
* For `CREATE` Transactions, this should usually just be a list of
* Outputs wrapping Ed25519 Conditions generated from the issuers' public
* keys (so that the issuers are the recipients of the created asset).
* @param {...string[]} issuers Public key of one or more issuers to the asset being created by this
* Transaction.
* Note: Each of the private keys corresponding to the given public
* keys MUST be used later (and in the same order) when signing the
* Transaction (`signTransaction()`).
* @returns {object} Unsigned transaction -- make sure to call signTransaction() on it before
* sending it off!
*/
export function makeCreateTransaction(asset, metadata, outputs, ...issuers) {
const assetDefinition = {
'data': asset || null,
};
const inputs = issuers.map((issuer) => makeInputTemplate([issuer]));
return makeTransaction('CREATE', assetDefinition, metadata, outputs, inputs);
}
/**
* Generate a `TRANSFER` transaction holding the `asset`, `metadata`, and `outputs`, that fulfills
* the `fulfilledOutputs` of `unspentTransaction`.
* @param {object} unspentTransaction Previous Transaction you have control over (i.e. can fulfill
* its Output Condition)
* @param {object} metadata Metadata for the Transaction
* @param {object[]} outputs Array of Output objects to add to the Transaction.
* Think of these as the recipients of the asset after the transaction.
* For `TRANSFER` Transactions, this should usually just be a list of
* Outputs wrapping Ed25519 Conditions generated from the public keys of
* the recipients.
* @param {...number} fulfilledOutputs Indices of the Outputs in `unspentTransaction` that this
* Transaction fulfills.
* Note that the public keys listed in the fulfilled Outputs
* must be used (and in the same order) to sign the Transaction
* (`signTransaction()`).
* @returns {object} Unsigned transaction -- make sure to call signTransaction() on it before
* sending it off!
*/
export function makeTransferTransaction(unspentTransaction, metadata, outputs, ...fulfilledOutputs) {
const inputs = fulfilledOutputs.map((outputIndex) => {
const fulfilledOutput = unspentTransaction.outputs[outputIndex];
const transactionLink = {
'output': outputIndex,
'txid': unspentTransaction.id,
};
return makeInputTemplate(fulfilledOutput.public_keys, transactionLink);
});
const assetLink = {
'id': unspentTransaction.operation === 'CREATE' ? unspentTransaction.id
: unspentTransaction.asset.id
};
return makeTransaction('TRANSFER', assetLink, metadata, outputs, inputs);
}
/**
* Sign the given `transaction` with the given `privateKey`s, returning a new copy of `transaction`
* that's been signed.
* Note: Only generates Ed25519 Fulfillments. Thresholds and other types of Fulfillments are left as
* an exercise for the user.
* @param {object} transaction Transaction to sign. `transaction` is not modified.
* @param {...string} privateKeys Private keys associated with the issuers of the `transaction`.
* Looped through to iteratively sign any Input Fulfillments found in
* the `transaction`.
* @returns {object} The signed version of `transaction`.
*/
export function signTransaction(transaction, ...privateKeys) {
const signedTx = clone(transaction);
signedTx.inputs.forEach((input, index) => {
const privateKey = privateKeys[index];
const privateKeyBuffer = new Buffer(base58.decode(privateKey));
const serializedTransaction = serializeTransactionIntoCanonicalString(transaction, input);
const ed25519Fulfillment = new cc.Ed25519();
ed25519Fulfillment.sign(new Buffer(serializedTransaction), privateKeyBuffer);
const fulfillmentUri = ed25519Fulfillment.serializeUri();
input.fulfillment = fulfillmentUri;
});
return signedTx;
}
/*********************
* Transaction utils *
*********************/
function makeTransactionTemplate() {
return {
'id': null,
'operation': null,
'outputs': [],
'inputs': [],
'metadata': null,
'asset': null,
'version': '0.9',
};
}
function makeInputTemplate(publicKeys = [], fulfills = null, fulfillment = null) {
return {
fulfillment,
fulfills,
'owners_before': publicKeys,
};
}
function makeTransaction(operation, asset, metadata = null, outputs = [], inputs = []) {
const tx = makeTransactionTemplate();
tx.operation = operation;
tx.asset = asset;
tx.metadata = metadata;
tx.inputs = inputs;
tx.outputs = outputs;
// Hashing must be done after, as the hash is of the Transaction (up to now)
tx.id = hashTransaction(tx);
return tx;
}
/****************
* Crypto utils *
****************/
function hashTransaction(transaction) {
// Safely remove any tx id from the given transaction for hashing
const tx = { ...transaction };
delete tx.id;
return sha256Hash(serializeTransactionIntoCanonicalString(tx));
}
function sha256Hash(data) {
return sha3.sha3_256
.create()
.update(data)
.hex();
}
function serializeTransactionIntoCanonicalString(transaction, input) {
// BigchainDB signs fulfillments by serializing transactions into a "canonical" format where
// each fulfillment URI is removed before sorting the remaining keys
const tx = clone(transaction);
let match;
tx.inputs.forEach((_input) => {
if (!(_input && input && _input['fulfills'] && input['fulfills']
&& !(_input['fulfills']['txid'] === input['fulfills']['txid']
&& _input['fulfills']['output'] === input['fulfills']['output']))) {
match = tx.inputs.indexOf(_input);
}
_input.fulfillment = null;
});
if (input && match >= 0 && tx.inputs) {
tx.inputs = [tx.inputs[match]];
}
// Sort the keys
return stableStringify(tx, (a, b) => (a.key > b.key ? 1 : -1));
}
import { request as baseRequest, sanitize } from 'js-utility-belt/es6';
// import { request } from 'utils/request';
//
// const FLASK_BASE_URL = process.env.FLASK_BASE_URL;
// export const BDB_SERVER_URL = process.env.BDB_SERVER_URL;
// const API_PATH = `${BDB_SERVER_URL}/api/v1/`;
//
//
const DEFAULT_REQUEST_CONFIG = {
credentials: 'include',
headers: {
'Accept': 'application/json'
}
};
/**
* Small wrapper around js-utility-belt's request that provides url resolving, default settings, and
* response handling.
*/
function request(url, config = {}, onlyJsonResponse=true) {
// Load default fetch configuration and remove any falsy query parameters
const requestConfig = Object.assign({}, DEFAULT_REQUEST_CONFIG, config, {
query: config.query && sanitize(config.query)
});
let apiUrl = url;
if (requestConfig.jsonBody) {
requestConfig.headers = Object.assign({}, requestConfig.headers, {
'Content-Type': 'application/json'
});
}
if (!url) {
return Promise.reject(new Error('Request was not given a url.'));
}
return baseRequest(apiUrl, requestConfig)
.then((res) => {
return onlyJsonResponse ? res.json() :
{
json: res.json(),
url: res.url
};
})
.catch((err) => {
console.error(err);
throw err;
});
}
export function getApiUrls(API_PATH) {
return {
'blocks': API_PATH + 'blocks',
'blocks_detail': API_PATH + 'blocks/%(blockId)s',
'outputs': API_PATH + 'outputs',
'statuses': API_PATH + 'statuses',
'transactions': API_PATH + 'transactions',
'transactions_detail': API_PATH + 'transactions/%(txId)s',
'votes': API_PATH + 'votes'
};
}
export function getTransaction(txId, API_PATH) {
return request(getApiUrls(API_PATH)['transactions_detail'], {
urlTemplateSpec: {
txId
}
});
}
export function postTransaction(transaction, API_PATH) {
return request(getApiUrls(API_PATH)['transactions'], {
method: 'POST',
jsonBody: transaction
})
}
export function listTransactions({ asset_id, operation }, API_PATH) {
return request(getApiUrls(API_PATH)['transactions'], {
query: {
asset_id,
operation
}
})
}
export function pollStatusAndFetchTransaction(tx_id, API_PATH) {
return new Promise((resolve, reject) => {
const timer = setInterval(() => {
getStatus(tx_id, API_PATH)
.then((res) => {
console.log('Fetched transaction status:', res);
if (res.status === 'valid') {
clearInterval(timer);
getTransaction(tx_id, API_PATH)
.then((res) => {
console.log('Fetched transaction:', res);
resolve(res);
});
}
})
.catch((err) => {
clearInterval(timer);
reject(err);
});
}, 500)
})
}
export function listOutputs({ public_key, unspent }, API_PATH, onlyJsonResponse=true) {
return request(getApiUrls(API_PATH)['outputs'], {
query: {
public_key,
unspent
}
}, onlyJsonResponse)
}
export function getStatus(tx_id, API_PATH) {
return request(getApiUrls(API_PATH)['statuses'], {
query: {
tx_id
}
});
}
export function getBlock(blockId, API_PATH) {
return request(getApiUrls(API_PATH)['blocks_detail'], {
urlTemplateSpec: {
blockId
}
});
}
export function listBlocks({tx_id, status}, API_PATH) {
return request(getApiUrls(API_PATH)['blocks'], {
query: {
tx_id,
status
}
});
}
export function listVotes(block_id, API_PATH) {
return request(getApiUrls(API_PATH)['votes'], {
query: {
block_id
}
});
}

View File

@ -8,12 +8,12 @@
},
"license": "¯\\_(ツ)_/¯",
"author": "BigchainDB",
"main": "index.js",
"main": "./src/index.js",
"scripts": {
"lint": "eslint ./",
"build": "npm run clean && npm run build:bundle && npm run build:cjs && npm run build:dist",
"build:bundle": "webpack",
"build:cjs": "cross-env BABEL_ENV=cjs babel index.js -d dist/node",
"build:cjs": "cross-env BABEL_ENV=cjs babel ./src -d dist/node",
"build:dist": "cross-env NODE_ENV=production webpack -p",
"clean": "rimraf dist/bundle dist/node",
"test": "echo \"Error: no test specified AWWWW YEAHHH\" && exit 1"
@ -22,9 +22,13 @@
"babel-cli": "^6.22.2",
"babel-eslint": "^7.1.1",
"babel-loader": "^6.2.10",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-transform-es2015-modules-commonjs": "^6.23.0",
"babel-plugin-transform-export-extensions": "^6.22.0",
"babel-plugin-transform-object-assign": "^6.22.0",
"babel-plugin-transform-object-rest-spread": "^6.22.0",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-plugin-transform-object-rest-spread": "^6.23.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-es2015-no-commonjs": "0.0.2",
"babel-preset-latest": "^6.22.0",
"babel-runtime": "^6.22.0",
"cross-env": "^3.1.4",
@ -39,12 +43,15 @@
"buffer": "^5.0.2",
"clone": "^2.1.0",
"core-js": "^2.4.1",
"decamelize": "^1.2.0",
"es6-promise": "^4.0.5",
"fetch-ponyfill": "^4.0.0",
"five-bells-condition": "=3.3.1",
"isomorphic-fetch": "^2.2.1",
"js-sha3": "^0.5.7",
"js-utility-belt": "^1.5.0",
"json-stable-stringify": "^1.0.1",
"sprintf-js": "^1.0.3",
"tweetnacl": "^0.14.5"
},
"keywords": [

15
src/Ed25519Keypair.js Normal file
View File

@ -0,0 +1,15 @@
import base58 from 'bs58';
import nacl from 'tweetnacl';
/**
* @class Keypair Ed25519 keypair in base58 (as BigchainDB expects base58 keys)
* @type {Object}
* @property {string} publicKey
* @property {string} privateKey
*/
export default function Ed25519Keypair() {
const keyPair = nacl.sign.keyPair();
this.publicKey = base58.encode(keyPair.publicKey);
// tweetnacl's generated secret key is the secret key + public key (resulting in a 64-byte buffer)
this.privateKey = base58.encode(keyPair.secretKey.slice(0, 32));
}

81
src/baseRequest.js Normal file
View File

@ -0,0 +1,81 @@
import { Promise } from 'es6-promise';
import fetchPonyfill from 'fetch-ponyfill';
import { vsprintf } from 'sprintf-js';
import formatText from './format_text';
import stringifyAsQueryParam from './stringify_as_query_param';
const fetch = fetchPonyfill(Promise);
/**
* imported from https://github.com/bigchaindb/js-utility-belt/
*
* Global fetch wrapper that adds some basic error handling and ease of use enhancements.
* Considers any non-2xx response as an error.
*
* For more information on fetch, see https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch/fetch.
*
* Expects fetch to already be available (either in a ES6 environment, bundled through webpack, or
* injected through a polyfill).
*
* @param {string} url Url to request. Can be specified as a sprintf format string (see
* https://github.com/alexei/sprintf.js) that will be resolved using
* `config.urlTemplateSpec`.
* @param {object} config Additional configuration, mostly passed to fetch as its 'init' config
* (see https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch/fetch#Parameters).
* @param {*} config.jsonBody Json payload to the request. Will automatically be
* JSON.stringify()-ed and override `config.body`.
* @param {string|object} config.query Query parameter to append to the end of the url.
* If specified as an object, keys will be
* decamelized into snake case first.
* @param {*[]|object} config.urlTemplateSpec Format spec to use to expand the url (see sprintf).
* @param {*} config.* All other options are passed through to fetch.
*
* @return {Promise} Promise that will resolve with the response if its status was 2xx;
* otherwise rejects with the response
*/
export default function baseRequest(url, { jsonBody, query, urlTemplateSpec, ...fetchConfig } = {}) {
let expandedUrl = url;
if (urlTemplateSpec != null) {
if (Array.isArray(urlTemplateSpec) && urlTemplateSpec.length) {
// Use vsprintf for the array call signature
expandedUrl = vsprintf(url, urlTemplateSpec);
} else if (urlTemplateSpec &&
typeof urlTemplateSpec === 'object' &&
Object.keys(urlTemplateSpec).length) {
expandedUrl = formatText(url, urlTemplateSpec);
} else if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line no-console
console.warn('Supplied urlTemplateSpec was not an array or object. Ignoring...');
}
}
if (query != null) {
if (typeof query === 'string') {
expandedUrl += query;
} else if (query && typeof query === 'object') {
expandedUrl += stringifyAsQueryParam(query);
} else if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line no-console
console.warn('Supplied query was not a string or object. Ignoring...');
}
}
if (jsonBody != null) {
fetchConfig.body = JSON.stringify(jsonBody);
}
return fetch.fetch(expandedUrl, fetchConfig)
.then((res) => {
// If status is not a 2xx (based on Response.ok), assume it's an error
// See https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch/fetch
if (!(res && res.ok)) {
throw res;
}
return res;
});
}

View File

@ -0,0 +1,11 @@
export default function getApiUrls(API_PATH) {
return {
'blocks': API_PATH + 'blocks',
'blocks_detail': API_PATH + 'blocks/%(blockId)s',
'outputs': API_PATH + 'outputs',
'statuses': API_PATH + 'statuses',
'transactions': API_PATH + 'transactions',
'transactions_detail': API_PATH + 'transactions/%(txId)s',
'votes': API_PATH + 'votes'
};
}

View File

@ -0,0 +1,13 @@
import getApiUrls from './getApiUrls';
import request from '../request';
export default function getBlock(blockId, API_PATH) {
return request(getApiUrls(API_PATH)['blocks_detail'], {
urlTemplateSpec: {
blockId
}
});
}

View File

@ -0,0 +1,11 @@
import getApiUrls from './getApiUrls';
import request from '../request';
export default function getStatus(tx_id, API_PATH) {
return request(getApiUrls(API_PATH)['statuses'], {
query: {
tx_id
}
});
}

View File

@ -0,0 +1,11 @@
import getApiUrls from './getApiUrls';
import request from '../request';
export default function getTransaction(txId, API_PATH) {
return request(getApiUrls(API_PATH)['transactions_detail'], {
urlTemplateSpec: {
txId
}
});
}

9
src/connection/index.js Normal file
View File

@ -0,0 +1,9 @@
export getBlock from './getBlock';
export getTransaction from './getTransaction';
export getStatus from './getStatus';
export listBlocks from './listBlocks';
export listOutputs from './listOutputs';
export listTransactions from './listTransactions';
export listVotes from './listVotes';
export pollStatusAndFetchTransaction from './pollStatusAndFetchTransaction';
export postTransaction from './postTransaction';

View File

@ -0,0 +1,12 @@
import getApiUrls from './getApiUrls';
import request from '../request';
export default function listBlocks({tx_id, status}, API_PATH) {
return request(getApiUrls(API_PATH)['blocks'], {
query: {
tx_id,
status
}
});
}

View File

@ -0,0 +1,12 @@
import getApiUrls from './getApiUrls';
import request from '../request';
export default function listOutputs({ public_key, unspent }, API_PATH, onlyJsonResponse=true) {
return request(getApiUrls(API_PATH)['outputs'], {
query: {
public_key,
unspent
}
}, onlyJsonResponse)
}

View File

@ -0,0 +1,12 @@
import getApiUrls from './getApiUrls';
import request from '../request';
export default function listTransactions({ asset_id, operation }, API_PATH) {
return request(getApiUrls(API_PATH)['transactions'], {
query: {
asset_id,
operation
}
})
}

View File

@ -0,0 +1,12 @@
import getApiUrls from './getApiUrls';
import request from '../request';
export default function listVotes(block_id, API_PATH) {
return request(getApiUrls(API_PATH)['votes'], {
query: {
block_id
}
});
}

View File

@ -0,0 +1,26 @@
import getTransaction from './getTransaction';
import getStatus from './getStatus';
export default function (tx_id, API_PATH) {
return new Promise((resolve, reject) => {
const timer = setInterval(() => {
getStatus(tx_id, API_PATH)
.then((res) => {
console.log('Fetched transaction status:', res);
if (res.status === 'valid') {
clearInterval(timer);
getTransaction(tx_id, API_PATH)
.then((res) => {
console.log('Fetched transaction:', res);
resolve(res);
});
}
})
.catch((err) => {
clearInterval(timer);
reject(err);
});
}, 500)
})
}

View File

@ -0,0 +1,11 @@
import getApiUrls from './getApiUrls';
import request from '../request';
export default function postTransaction(transaction, API_PATH) {
return request(getApiUrls(API_PATH)['transactions'], {
method: 'POST',
jsonBody: transaction
})
}

97
src/format_text.js Normal file
View File

@ -0,0 +1,97 @@
import { sprintf } from 'sprintf-js';
// Regexes taken from or inspired by sprintf-js
const Regex = {
TEMPLATE_LITERAL: /\${([^\)]+?)}/g,
KEY: /^([a-z_][a-z_\d]*)/i,
KEY_ACCESS: /^\.([a-z_][a-z_\d]*)/i,
INDEX_ACCESS: /^\[(\d+)\]/
};
/**
* imported from https://github.com/bigchaindb/js-utility-belt/
*
* Formats strings similarly to C's sprintf, with the addition of '${...}' formats.
*
* Makes a first pass replacing '${...}' formats before passing the expanded string and other
* arguments to sprintf-js. For more information on what sprintf can do, see
* https://github.com/alexei/sprintf.js.
*
* Examples:
* formatText('Hi there ${dimi}!', { dimi: 'Dimi' })
* => 'Hi there Dimi!'
*
* formatText('${database} is %(status)s', { database: 'BigchainDB', status: 'big' })
* => 'BigchainDB is big'
*
* Like sprintf-js, string interpolation for keywords and indexes is supported too:
* formatText('Berlin is best known for its ${berlin.topKnownFor[0].name}', {
* berlin: {
* topKnownFor: [{
* name: 'Currywurst'
* }, ...
* ]
* }
* })
* => 'Berlin is best known for its Currywurst'
*/
export default function formatText(s, ...argv) {
let expandedFormatStr = s;
// Try to replace formats of the form '${...}' if named replacement fields are used
if (s && argv.length === 1 && typeof argv[0] === 'object') {
const templateSpecObj = argv[0];
expandedFormatStr = s.replace(Regex.TEMPLATE_LITERAL, (match, replacement) => {
let interpolationLeft = replacement;
/**
* Interpolation algorithm inspired by sprintf-js.
*
* Goes through the replacement string getting the left-most key or index to interpolate
* on each pass. `value` at each step holds the last interpolation result, `curMatch` is
* the current property match, and `interpolationLeft` is the portion of the replacement
* string still to be interpolated.
*
* It's useful to note that RegExp.exec() returns with an array holding:
* [0]: Full string matched
* [1+]: Matching groups
*
* And that in the regexes defined, the first matching group always corresponds to the
* property matched.
*/
let value;
let curMatch = Regex.KEY.exec(interpolationLeft);
if (curMatch !== null) {
value = templateSpecObj[curMatch[1]];
// Assigning in the conditionals here makes the code less bloated
/* eslint-disable no-cond-assign */
while ((interpolationLeft = interpolationLeft.substring(curMatch[0].length)) &&
value != null) {
if ((curMatch = Regex.KEY_ACCESS.exec(interpolationLeft))) {
value = value[curMatch[1]];
} else if ((curMatch = Regex.INDEX_ACCESS.exec(interpolationLeft))) {
value = value[curMatch[1]];
} else {
break;
}
}
/* eslint-enable no-cond-assign */
}
// If there's anything left to interpolate by the end then we've failed to interpolate
// the entire replacement string.
if (interpolationLeft.length) {
throw new SyntaxError(
`[formatText] failed to parse named argument key: ${replacement}`
);
}
return value;
});
}
return sprintf(expandedFormatStr, ...argv);
}

5
src/index.js Normal file
View File

@ -0,0 +1,5 @@
export Ed25519Keypair from './Ed25519Keypair';
export * as Transaction from './transaction';
export * as Connection from './connection';

45
src/request.js Normal file
View File

@ -0,0 +1,45 @@
import baseRequest from './baseRequest';
import sanitize from './sanitize';
const DEFAULT_REQUEST_CONFIG = {
credentials: 'include',
headers: {
'Accept': 'application/json'
}
};
/**
* Small wrapper around js-utility-belt's request that provides url resolving, default settings, and
* response handling.
*/
export default function request(url, config = {}, onlyJsonResponse=true) {
// Load default fetch configuration and remove any falsy query parameters
const requestConfig = Object.assign({}, DEFAULT_REQUEST_CONFIG, config, {
query: config.query && sanitize(config.query)
});
let apiUrl = url;
if (requestConfig.jsonBody) {
requestConfig.headers = Object.assign({}, requestConfig.headers, {
'Content-Type': 'application/json'
});
}
if (!url) {
return Promise.reject(new Error('Request was not given a url.'));
}
return baseRequest(apiUrl, requestConfig)
.then((res) => {
return onlyJsonResponse ? res.json() :
{
json: res.json(),
url: res.url
};
})
.catch((err) => {
console.error(err);
throw err;
});
}

64
src/sanitize.js Normal file
View File

@ -0,0 +1,64 @@
import coreIncludes from 'core-js/library/fn/array/includes';
import coreObjectEntries from 'core-js/library/fn/object/entries';
/**
* Abstraction for selectFromObject and omitFromObject for DRYness.
* Set isInclusion to true if the filter should be for including the filtered items (ie. selecting
* only them vs omitting only them).
*/
function filterFromObject(obj, filter, { isInclusion = true } = {}) {
if (filter && Array.isArray(filter)) {
return applyFilterOnObject(obj, isInclusion ? ((_, key) => coreIncludes(filter, key))
: ((_, key) => !coreIncludes(filter, key)));
} else if (filter && typeof filter === 'function') {
// Flip the filter fn's return if it's for inclusion
return applyFilterOnObject(obj, isInclusion ? filter
: (...args) => !filter(...args));
} else {
throw new Error('The given filter is not an array or function. Exclude aborted');
}
}
/**
* Returns a filtered copy of the given object's own enumerable properties (no inherited
* properties), keeping any keys that pass the given filter function.
*/
function applyFilterOnObject(obj, filterFn) {
if (filterFn == null) {
return Object.assign({}, obj);
}
const filteredObj = {};
coreObjectEntries(obj).forEach(([key, val]) => {
if (filterFn(val, key)) {
filteredObj[key] = val;
}
});
return filteredObj;
}
/**
* Similar to lodash's _.pick(), this returns a copy of the given object's
* own and inherited enumerable properties, selecting only the keys in
* the given array or whose value pass the given filter function.
* @param {object} obj Source object
* @param {array|function} filter Array of key names to select or function to invoke per iteration
* @return {object} The new object
*/
function selectFromObject(obj, filter) {
return filterFromObject(obj, filter);
}
/**
* Glorified selectFromObject. Takes an object and returns a filtered shallow copy that strips out
* any properties that are falsy (including coercions, ie. undefined, null, '', 0, ...).
* Does not modify the passed in object.
*
* @param {object} obj Javascript object
* @return {object} Sanitized Javascript object
*/
export default function sanitize(obj) {
return selectFromObject(obj, (val) => !!val);
}

8
src/sha256Hash.js Normal file
View File

@ -0,0 +1,8 @@
import sha3 from 'js-sha3';
export default function sha256Hash(data) {
return sha3.sha3_256
.create()
.update(data)
.hex();
}

View File

@ -0,0 +1,42 @@
import coreObjectEntries from 'core-js/library/fn/object/entries';
import decamelize from 'decamelize';
import queryString from 'query-string';
/**
* imported from https://github.com/bigchaindb/js-utility-belt/
*
* Takes a key-value dictionary (ie. object) and converts it to a query-parameter string that you
* can directly append into a URL.
*
* Extends queryString.stringify by allowing you to specify a `transform` function that will be
* invoked on each of the dictionary's keys before being stringified into the query-parameter
* string.
*
* By default `transform` is `decamelize`, so a dictionary of the form:
*
* {
* page: 1,
* pageSize: 10
* }
*
* will be converted to a string like:
*
* ?page=1&page_size=10
*
* @param {object} obj Query params dictionary
* @param {function} [transform=decamelize] Transform function for each of the param keys
* @return {string} Query param string
*/
export default function stringifyAsQueryParam(obj, transform = decamelize) {
if (!obj || typeof obj !== 'object' || !Object.keys(obj).length) {
return '';
}
const transformedKeysObj = coreObjectEntries(obj).reduce((paramsObj, [key, value]) => {
paramsObj[transform(key)] = value;
return paramsObj;
}, {});
return `?${queryString.stringify(transformedKeysObj)}`;
}

View File

@ -0,0 +1,10 @@
import serializeTransactionIntoCanonicalString from './serializeTransactionIntoCanonicalString';
import sha256Hash from '../sha256Hash';
export default function hashTransaction(transaction) {
// Safely remove any tx id from the given transaction for hashing
const tx = { ...transaction };
delete tx.id;
return sha256Hash(serializeTransactionIntoCanonicalString(tx));
}

6
src/transaction/index.js Normal file
View File

@ -0,0 +1,6 @@
export makeEd25519Condition from './makeEd25519Condition';
export makeCreateTransaction from './makeCreateTransaction';
export makeOutput from './makeOutput';
export makeTransaction from './makeTransaction';
export makeTransferTransaction from './makeTransferTransaction';
export signTransaction from './signTransaction';

View File

@ -0,0 +1,29 @@
import makeInputTemplate from './makeInputTemplate';
import makeTransaction from './makeTransaction';
/**
* Generate a `CREATE` transaction holding the `asset`, `metadata`, and `outputs`, to be signed by
* the `issuers`.
* @param {object} asset Created asset's data
* @param {object} metadata Metadata for the Transaction
* @param {object[]} outputs Array of Output objects to add to the Transaction.
* Think of these as the recipients of the asset after the transaction.
* For `CREATE` Transactions, this should usually just be a list of
* Outputs wrapping Ed25519 Conditions generated from the issuers' public
* keys (so that the issuers are the recipients of the created asset).
* @param {...string[]} issuers Public key of one or more issuers to the asset being created by this
* Transaction.
* Note: Each of the private keys corresponding to the given public
* keys MUST be used later (and in the same order) when signing the
* Transaction (`signTransaction()`).
* @returns {object} Unsigned transaction -- make sure to call signTransaction() on it before
* sending it off!
*/
export default function makeCreateTransaction(asset, metadata, outputs, ...issuers) {
const assetDefinition = {
'data': asset || null,
};
const inputs = issuers.map((issuer) => makeInputTemplate([issuer]));
return makeTransaction('CREATE', assetDefinition, metadata, outputs, inputs);
}

View File

@ -0,0 +1,28 @@
import { Buffer } from 'buffer';
import base58 from 'bs58';
import cc from 'five-bells-condition';
/**
* Create an Ed25519 Cryptocondition from an Ed25519 public key to put into an Output of a Transaction
* @param {string} publicKey base58 encoded Ed25519 public key for the recipient of the Transaction
* @returns {object} Ed25519 Condition (that will need to wrapped in an Output)
*/
export default function makeEd25519Condition(publicKey) {
const publicKeyBuffer = new Buffer(base58.decode(publicKey));
const ed25519Fulfillment = new cc.Ed25519();
ed25519Fulfillment.setPublicKey(publicKeyBuffer);
const conditionUri = ed25519Fulfillment.getConditionUri();
return {
'details': {
'signature': null,
'type_id': 4,
'type': 'fulfillment',
'bitmask': 32,
'public_key': publicKey,
},
'uri': conditionUri,
};
}

View File

@ -0,0 +1,7 @@
export default function makeInputTemplate(publicKeys = [], fulfills = null, fulfillment = null) {
return {
fulfillment,
fulfills,
'owners_before': publicKeys,
};
}

View File

@ -0,0 +1,14 @@
/**
* Create an Output from a Condition.
* Note: Assumes the given Condition was generated from a single public key (e.g. a Ed25519 Condition)
* @param {object} condition Condition (e.g. a Ed25519 Condition from `makeEd25519Condition()`)
* @param {number} amount Amount of the output
* @returns {object} An Output usable in a Transaction
*/
export default function makeOutput(condition, amount = 1) {
return {
amount,
condition,
'public_keys': [condition.details.public_key],
};
}

View File

@ -0,0 +1,28 @@
import hashTransaction from './hashTransaction';
function makeTransactionTemplate() {
return {
'id': null,
'operation': null,
'outputs': [],
'inputs': [],
'metadata': null,
'asset': null,
'version': '0.9',
};
}
export default function makeTransaction(operation, asset, metadata = null, outputs = [], inputs = []) {
const tx = makeTransactionTemplate();
tx.operation = operation;
tx.asset = asset;
tx.metadata = metadata;
tx.inputs = inputs;
tx.outputs = outputs;
// Hashing must be done after, as the hash is of the Transaction (up to now)
tx.id = hashTransaction(tx);
return tx;
}

View File

@ -0,0 +1,40 @@
import makeInputTemplate from './makeInputTemplate';
import makeTransaction from './makeTransaction';
/**
* Generate a `TRANSFER` transaction holding the `asset`, `metadata`, and `outputs`, that fulfills
* the `fulfilledOutputs` of `unspentTransaction`.
* @param {object} unspentTransaction Previous Transaction you have control over (i.e. can fulfill
* its Output Condition)
* @param {object} metadata Metadata for the Transaction
* @param {object[]} outputs Array of Output objects to add to the Transaction.
* Think of these as the recipients of the asset after the transaction.
* For `TRANSFER` Transactions, this should usually just be a list of
* Outputs wrapping Ed25519 Conditions generated from the public keys of
* the recipients.
* @param {...number} fulfilledOutputs Indices of the Outputs in `unspentTransaction` that this
* Transaction fulfills.
* Note that the public keys listed in the fulfilled Outputs
* must be used (and in the same order) to sign the Transaction
* (`signTransaction()`).
* @returns {object} Unsigned transaction -- make sure to call signTransaction() on it before
* sending it off!
*/
export default function makeTransferTransaction(unspentTransaction, metadata, outputs, ...fulfilledOutputs) {
const inputs = fulfilledOutputs.map((outputIndex) => {
const fulfilledOutput = unspentTransaction.outputs[outputIndex];
const transactionLink = {
'output': outputIndex,
'txid': unspentTransaction.id,
};
return makeInputTemplate(fulfilledOutput.public_keys, transactionLink);
});
const assetLink = {
'id': unspentTransaction.operation === 'CREATE' ? unspentTransaction.id
: unspentTransaction.asset.id
};
return makeTransaction('TRANSFER', assetLink, metadata, outputs, inputs);
}

View File

@ -0,0 +1,24 @@
import stableStringify from 'json-stable-stringify';
import clone from 'clone';
export default function serializeTransactionIntoCanonicalString(transaction, input) {
// BigchainDB signs fulfillments by serializing transactions into a "canonical" format where
// each fulfillment URI is removed before sorting the remaining keys
const tx = clone(transaction);
let match;
tx.inputs.forEach((_input) => {
if (!(_input && input && _input['fulfills'] && input['fulfills']
&& !(_input['fulfills']['txid'] === input['fulfills']['txid']
&& _input['fulfills']['output'] === input['fulfills']['output']))) {
match = tx.inputs.indexOf(_input);
}
_input.fulfillment = null;
});
if (input && match >= 0 && tx.inputs) {
tx.inputs = [tx.inputs[match]];
}
// Sort the keys
return stableStringify(tx, (a, b) => (a.key > b.key ? 1 : -1));
}

View File

@ -0,0 +1,34 @@
import { Buffer } from 'buffer';
import base58 from 'bs58';
import cc from 'five-bells-condition';
import clone from 'clone';
import serializeTransactionIntoCanonicalString from './serializeTransactionIntoCanonicalString';
/**
* Sign the given `transaction` with the given `privateKey`s, returning a new copy of `transaction`
* that's been signed.
* Note: Only generates Ed25519 Fulfillments. Thresholds and other types of Fulfillments are left as
* an exercise for the user.
* @param {object} transaction Transaction to sign. `transaction` is not modified.
* @param {...string} privateKeys Private keys associated with the issuers of the `transaction`.
* Looped through to iteratively sign any Input Fulfillments found in
* the `transaction`.
* @returns {object} The signed version of `transaction`.
*/
export default function signTransaction(transaction, ...privateKeys) {
const signedTx = clone(transaction);
signedTx.inputs.forEach((input, index) => {
const privateKey = privateKeys[index];
const privateKeyBuffer = new Buffer(base58.decode(privateKey));
const serializedTransaction = serializeTransactionIntoCanonicalString(transaction, input);
const ed25519Fulfillment = new cc.Ed25519();
ed25519Fulfillment.sign(new Buffer(serializedTransaction), privateKeyBuffer);
const fulfillmentUri = ed25519Fulfillment.serializeUri();
input.fulfillment = fulfillmentUri;
});
return signedTx;
}

View File

@ -9,7 +9,7 @@ const webpack = require('webpack');
const PRODUCTION = process.env.NODE_ENV === 'production';
const PATHS = {
ENTRY: path.resolve(__dirname, 'index.js'),
ENTRY: path.resolve(__dirname, './src/index.js'),
BUNDLE: path.resolve(__dirname, 'dist/bundle'),
NODE_MODULES: path.resolve(__dirname, 'node_modules'),
};