From aba3c7d4bcf80dc73269fe516a17adf25ba1166d Mon Sep 17 00:00:00 2001 From: michielmulders Date: Thu, 2 Nov 2017 20:51:24 +0100 Subject: [PATCH] Everything in one file --- src/transaction.js | 256 ++++++++++++++++++ src/transaction/hashTransaction.js | 10 - src/transaction/index.js | 11 - src/transaction/makeCreateTransaction.js | 31 --- src/transaction/makeEd25519Condition.js | 27 -- src/transaction/makeInputTemplate.js | 7 - src/transaction/makeOutput.js | 29 -- src/transaction/makeSha256Condition.js | 23 -- src/transaction/makeThresholdCondition.js | 28 -- src/transaction/makeTransaction.js | 28 -- src/transaction/makeTransferTransaction.js | 49 ---- ...serializeTransactionIntoCanonicalString.js | 18 -- src/transaction/signTransaction.js | 35 --- src/{transaction => }/utils/ccJsonLoad.js | 0 src/{transaction => }/utils/ccJsonify.js | 0 15 files changed, 256 insertions(+), 296 deletions(-) create mode 100644 src/transaction.js delete mode 100644 src/transaction/hashTransaction.js delete mode 100644 src/transaction/index.js delete mode 100644 src/transaction/makeCreateTransaction.js delete mode 100644 src/transaction/makeEd25519Condition.js delete mode 100644 src/transaction/makeInputTemplate.js delete mode 100644 src/transaction/makeOutput.js delete mode 100644 src/transaction/makeSha256Condition.js delete mode 100644 src/transaction/makeThresholdCondition.js delete mode 100644 src/transaction/makeTransaction.js delete mode 100644 src/transaction/makeTransferTransaction.js delete mode 100644 src/transaction/serializeTransactionIntoCanonicalString.js delete mode 100644 src/transaction/signTransaction.js rename src/{transaction => }/utils/ccJsonLoad.js (100%) rename src/{transaction => }/utils/ccJsonify.js (100%) diff --git a/src/transaction.js b/src/transaction.js new file mode 100644 index 0000000..7af085b --- /dev/null +++ b/src/transaction.js @@ -0,0 +1,256 @@ +import { Buffer } from 'buffer' +import stableStringify from 'json-stable-stringify' +import clone from 'clone' +import base58 from 'bs58' +import cc from 'five-bells-condition' +import ccJsonify from './utils/ccJsonify' +import sha256Hash from './sha256Hash' + +/** + * @public + * Canonically serializes a transaction into a string by sorting the keys + * @param {object} (transaction) + * @return {string} a canonically serialized Transaction + */ +export default function serializeTransactionIntoCanonicalString(transaction) { + // BigchainDB signs fulfillments by serializing transactions into a + // "canonical" format where + const tx = clone(transaction) + // TODO: set fulfillments to null + // Sort the keys + return stableStringify(tx, (a, b) => (a.key > b.key ? 1 : -1)) +} + +export default function makeInputTemplate(publicKeys = [], fulfills = null, fulfillment = null) { + return { + fulfillment, + fulfills, + 'owners_before': publicKeys, + } +} + +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)) +} + +function makeTransactionTemplate() { + return { + 'id': null, + 'operation': null, + 'outputs': [], + 'inputs': [], + 'metadata': null, + 'asset': null, + 'version': '1.0', + } +} + +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 +} + +/** + * @public + * 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) +} + +/** + * @public + * 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 + * @param {boolean} [json=true] If true returns a json object otherwise a crypto-condition type + * @returns {object} Ed25519 Condition (that will need to wrapped in an Output) + */ +export default function makeEd25519Condition(publicKey, json = true) { + const publicKeyBuffer = new Buffer(base58.decode(publicKey)) + + const ed25519Fulfillment = new cc.Ed25519Sha256() + ed25519Fulfillment.setPublicKey(publicKeyBuffer) + + if (json) { + return ccJsonify(ed25519Fulfillment) + } + + return ed25519Fulfillment +} + +/** + * @public + * 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 {string} amount Amount of the output + * @returns {object} An Output usable in a Transaction + */ +export default function makeOutput(condition, amount = '1') { + if (typeof amount !== 'string') { + throw new TypeError('`amount` must be of type string') + } + const publicKeys = [] + const getPublicKeys = details => { + if (details.type === 'ed25519-sha-256') { + if (!publicKeys.includes(details.public_key)) { + publicKeys.push(details.public_key) + } + } else if (details.type === 'threshold-sha-256') { + details.subconditions.map(getPublicKeys) + } + } + getPublicKeys(condition.details) + return { + condition, + 'amount': amount, + 'public_keys': publicKeys, + } +} + +/** + * @public + * Create a Preimage-Sha256 Cryptocondition from a secret to put into an Output of a Transaction + * @param {string} preimage Preimage to be hashed and wrapped in a crypto-condition + * @param {boolean} [json=true] If true returns a json object otherwise a crypto-condition type + * @returns {object} Preimage-Sha256 Condition (that will need to wrapped in an Output) + */ +export default function makeSha256Condition(preimage, json = true) { + const sha256Fulfillment = new cc.PreimageSha256() + sha256Fulfillment.preimage = new Buffer(preimage) + + if (json) { + return ccJsonify(sha256Fulfillment) + } + return sha256Fulfillment +} + +/** + * @public + * Create an Sha256 Threshold Cryptocondition from threshold to put into an Output of a Transaction + * @param {number} threshold + * @param {Array} [subconditions=[]] + * @param {boolean} [json=true] If true returns a json object otherwise a crypto-condition type + * @returns {object} Sha256 Threshold Condition (that will need to wrapped in an Output) + */ +export default function makeThresholdCondition(threshold, subconditions = [], json = true) { + const thresholdCondition = new cc.ThresholdSha256() + thresholdCondition.threshold = threshold + + subconditions.forEach((subcondition) => { + // TODO: add support for Condition and URIs + thresholdCondition.addSubfulfillment(subcondition) + }) + + if (json) { + return ccJsonify(thresholdCondition) + } + + return thresholdCondition +} + +/** + * @public + * 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} OutputIndices Indices of the Outputs in `unspentTransaction` that this + * Transaction fulfills. + * Note that listed public keys listed 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! + */ +// TODO: +// - Make `metadata` optional argument +export default function makeTransferTransaction( + unspentTransaction, + metadata, + outputs, + ...outputIndices +) { + const inputs = outputIndices.map((outputIndex) => { + const fulfilledOutput = unspentTransaction.outputs[outputIndex] + const transactionLink = { + 'output_index': outputIndex, + 'transaction_id': 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) +} + +/** + * @public + * 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) + const ed25519Fulfillment = new cc.Ed25519Sha256() + ed25519Fulfillment.sign(new Buffer(serializedTransaction), privateKeyBuffer) + const fulfillmentUri = ed25519Fulfillment.serializeUri() + + input.fulfillment = fulfillmentUri + }) + + return signedTx +} diff --git a/src/transaction/hashTransaction.js b/src/transaction/hashTransaction.js deleted file mode 100644 index 13d1c51..0000000 --- a/src/transaction/hashTransaction.js +++ /dev/null @@ -1,10 +0,0 @@ -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)) -} diff --git a/src/transaction/index.js b/src/transaction/index.js deleted file mode 100644 index 01d4a1f..0000000 --- a/src/transaction/index.js +++ /dev/null @@ -1,11 +0,0 @@ -export makeEd25519Condition from './makeEd25519Condition' -export makeSha256Condition from './makeSha256Condition' -export makeThresholdCondition from './makeThresholdCondition' -export makeCreateTransaction from './makeCreateTransaction' -export makeOutput from './makeOutput' -export makeTransaction from './makeTransaction' -export makeTransferTransaction from './makeTransferTransaction' -export serializeTransactionIntoCanonicalString from './serializeTransactionIntoCanonicalString' -export signTransaction from './signTransaction' -export ccJsonLoad from './utils/ccJsonLoad' -export ccJsonify from './utils/ccJsonify' diff --git a/src/transaction/makeCreateTransaction.js b/src/transaction/makeCreateTransaction.js deleted file mode 100644 index 8b259de..0000000 --- a/src/transaction/makeCreateTransaction.js +++ /dev/null @@ -1,31 +0,0 @@ -import makeInputTemplate from './makeInputTemplate' -import makeTransaction from './makeTransaction' - - -/** - * @public - * 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) -} diff --git a/src/transaction/makeEd25519Condition.js b/src/transaction/makeEd25519Condition.js deleted file mode 100644 index 3f0ff57..0000000 --- a/src/transaction/makeEd25519Condition.js +++ /dev/null @@ -1,27 +0,0 @@ -import { Buffer } from 'buffer' - -import base58 from 'bs58' -import cc from 'five-bells-condition' - -import ccJsonify from './utils/ccJsonify' - - -/** - * @public - * 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 - * @param {boolean} [json=true] If true returns a json object otherwise a crypto-condition type - * @returns {object} Ed25519 Condition (that will need to wrapped in an Output) - */ -export default function makeEd25519Condition(publicKey, json = true) { - const publicKeyBuffer = new Buffer(base58.decode(publicKey)) - - const ed25519Fulfillment = new cc.Ed25519Sha256() - ed25519Fulfillment.setPublicKey(publicKeyBuffer) - - if (json) { - return ccJsonify(ed25519Fulfillment) - } - - return ed25519Fulfillment -} diff --git a/src/transaction/makeInputTemplate.js b/src/transaction/makeInputTemplate.js deleted file mode 100644 index f080b06..0000000 --- a/src/transaction/makeInputTemplate.js +++ /dev/null @@ -1,7 +0,0 @@ -export default function makeInputTemplate(publicKeys = [], fulfills = null, fulfillment = null) { - return { - fulfillment, - fulfills, - 'owners_before': publicKeys, - } -} diff --git a/src/transaction/makeOutput.js b/src/transaction/makeOutput.js deleted file mode 100644 index d937570..0000000 --- a/src/transaction/makeOutput.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @public - * 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 {string} amount Amount of the output - * @returns {object} An Output usable in a Transaction - */ -export default function makeOutput(condition, amount = '1') { - if (typeof amount !== 'string') { - throw new TypeError('`amount` must be of type string') - } - const publicKeys = [] - const getPublicKeys = details => { - if (details.type === 'ed25519-sha-256') { - if (!publicKeys.includes(details.public_key)) { - publicKeys.push(details.public_key) - } - } else if (details.type === 'threshold-sha-256') { - details.subconditions.map(getPublicKeys) - } - } - getPublicKeys(condition.details) - return { - condition, - 'amount': amount, - 'public_keys': publicKeys, - } -} diff --git a/src/transaction/makeSha256Condition.js b/src/transaction/makeSha256Condition.js deleted file mode 100644 index 1ecded9..0000000 --- a/src/transaction/makeSha256Condition.js +++ /dev/null @@ -1,23 +0,0 @@ -import { Buffer } from 'buffer' - -import cc from 'five-bells-condition' - -import ccJsonify from './utils/ccJsonify' - - -/** - * @public - * Create a Preimage-Sha256 Cryptocondition from a secret to put into an Output of a Transaction - * @param {string} preimage Preimage to be hashed and wrapped in a crypto-condition - * @param {boolean} [json=true] If true returns a json object otherwise a crypto-condition type - * @returns {object} Preimage-Sha256 Condition (that will need to wrapped in an Output) - */ -export default function makeSha256Condition(preimage, json = true) { - const sha256Fulfillment = new cc.PreimageSha256() - sha256Fulfillment.preimage = new Buffer(preimage) - - if (json) { - return ccJsonify(sha256Fulfillment) - } - return sha256Fulfillment -} diff --git a/src/transaction/makeThresholdCondition.js b/src/transaction/makeThresholdCondition.js deleted file mode 100644 index 3a8ec11..0000000 --- a/src/transaction/makeThresholdCondition.js +++ /dev/null @@ -1,28 +0,0 @@ -import cc from 'five-bells-condition' - -import ccJsonify from './utils/ccJsonify' - - -/** - * @public - * Create an Sha256 Threshold Cryptocondition from threshold to put into an Output of a Transaction - * @param {number} threshold - * @param {Array} [subconditions=[]] - * @param {boolean} [json=true] If true returns a json object otherwise a crypto-condition type - * @returns {object} Sha256 Threshold Condition (that will need to wrapped in an Output) - */ -export default function makeThresholdCondition(threshold, subconditions = [], json = true) { - const thresholdCondition = new cc.ThresholdSha256() - thresholdCondition.threshold = threshold - - subconditions.forEach((subcondition) => { - // TODO: add support for Condition and URIs - thresholdCondition.addSubfulfillment(subcondition) - }) - - if (json) { - return ccJsonify(thresholdCondition) - } - - return thresholdCondition -} diff --git a/src/transaction/makeTransaction.js b/src/transaction/makeTransaction.js deleted file mode 100644 index 00c8ce2..0000000 --- a/src/transaction/makeTransaction.js +++ /dev/null @@ -1,28 +0,0 @@ -import hashTransaction from './hashTransaction' - - -function makeTransactionTemplate() { - return { - 'id': null, - 'operation': null, - 'outputs': [], - 'inputs': [], - 'metadata': null, - 'asset': null, - 'version': '1.0', - } -} - - -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 -} diff --git a/src/transaction/makeTransferTransaction.js b/src/transaction/makeTransferTransaction.js deleted file mode 100644 index 3d62bd2..0000000 --- a/src/transaction/makeTransferTransaction.js +++ /dev/null @@ -1,49 +0,0 @@ -import makeInputTemplate from './makeInputTemplate' -import makeTransaction from './makeTransaction' - - -/** - * @public - * 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} OutputIndices Indices of the Outputs in `unspentTransaction` that this - * Transaction fulfills. - * Note that listed public keys listed 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! - */ -// TODO: -// - Make `metadata` optional argument -export default function makeTransferTransaction( - unspentTransaction, - metadata, - outputs, - ...outputIndices -) { - const inputs = outputIndices.map((outputIndex) => { - const fulfilledOutput = unspentTransaction.outputs[outputIndex] - const transactionLink = { - 'output_index': outputIndex, - 'transaction_id': 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) -} diff --git a/src/transaction/serializeTransactionIntoCanonicalString.js b/src/transaction/serializeTransactionIntoCanonicalString.js deleted file mode 100644 index 382ed32..0000000 --- a/src/transaction/serializeTransactionIntoCanonicalString.js +++ /dev/null @@ -1,18 +0,0 @@ -import stableStringify from 'json-stable-stringify' -import clone from 'clone' - - -/** - * @public - * Canonically serializes a transaction into a string by sorting the keys - * @param {object} (transaction) - * @return {string} a canonically serialized Transaction - */ -export default function serializeTransactionIntoCanonicalString(transaction) { - // BigchainDB signs fulfillments by serializing transactions into a - // "canonical" format where - const tx = clone(transaction) - // TODO: set fulfillments to null - // Sort the keys - return stableStringify(tx, (a, b) => (a.key > b.key ? 1 : -1)) -} diff --git a/src/transaction/signTransaction.js b/src/transaction/signTransaction.js deleted file mode 100644 index 32422a7..0000000 --- a/src/transaction/signTransaction.js +++ /dev/null @@ -1,35 +0,0 @@ -import { Buffer } from 'buffer' -import base58 from 'bs58' -import cc from 'five-bells-condition' -import clone from 'clone' - -import serializeTransactionIntoCanonicalString from './serializeTransactionIntoCanonicalString' - - -/** - * @public - * 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) - const ed25519Fulfillment = new cc.Ed25519Sha256() - ed25519Fulfillment.sign(new Buffer(serializedTransaction), privateKeyBuffer) - const fulfillmentUri = ed25519Fulfillment.serializeUri() - - input.fulfillment = fulfillmentUri - }) - - return signedTx -} diff --git a/src/transaction/utils/ccJsonLoad.js b/src/utils/ccJsonLoad.js similarity index 100% rename from src/transaction/utils/ccJsonLoad.js rename to src/utils/ccJsonLoad.js diff --git a/src/transaction/utils/ccJsonify.js b/src/utils/ccJsonify.js similarity index 100% rename from src/transaction/utils/ccJsonify.js rename to src/utils/ccJsonify.js