Skip to content

Transaction folder to one file #135

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jan 18, 2018
4 changes: 3 additions & 1 deletion src/baseRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ const fetch = fetchPonyfill(Promise)
* @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 } = {}) {
export default function baseRequest(url, {
jsonBody, query, urlTemplateSpec, ...fetchConfig
} = {}) {
let expandedUrl = url

if (urlTemplateSpec != null) {
Expand Down
4 changes: 1 addition & 3 deletions src/format_text.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,7 @@ export default function formatText(s, ...argv) {
// 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}`
)
throw new SyntaxError(`[formatText] failed to parse named argument key: ${replacement}`)
}

return value
Expand Down
5 changes: 3 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

export Ed25519Keypair from './Ed25519Keypair'

export * as Transaction from './transaction'
export Connection from './connection'
export Transaction from './transaction'
export ccJsonLoad from './utils/ccJsonLoad'
export ccJsonify from './utils/ccJsonify'
260 changes: 260 additions & 0 deletions src/transaction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
import { Buffer } from 'buffer'
import stableStringify from 'json-stable-stringify'
import clone from 'clone'
import base58 from 'bs58'
import cc from 'crypto-conditions'
import ccJsonify from './utils/ccJsonify'
import sha256Hash from './sha256Hash'

export default class Transaction {
/**
* @public
* Canonically serializes a transaction into a string by sorting the keys
* @param {object} (transaction)
* @return {string} a canonically serialized Transaction
*/
static 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))
}

static makeInputTemplate(publicKeys = [], fulfills = null, fulfillment = null) {
return {
fulfillment,
fulfills,
'owners_before': publicKeys,
}
}

static hashTransaction(transaction) {
// Safely remove any tx id from the given transaction for hashing
const tx = { ...transaction }
delete tx.id

return sha256Hash(Transaction.serializeTransactionIntoCanonicalString(tx))
}

static makeTransactionTemplate() {
const txTemplate = {
'id': null,
'operation': null,
'outputs': [],
'inputs': [],
'metadata': null,
'asset': null,
'version': '1.0',
}
return txTemplate
}

static makeTransaction(operation, asset, metadata = null, outputs = [], inputs = []) {
const tx = Transaction.makeTransactionTemplate()
tx.operation = operation
tx.asset = asset
tx.metadata = metadata
tx.inputs = inputs
tx.outputs = outputs

tx.id = Transaction.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!
*/
static makeCreateTransaction(asset, metadata, outputs, ...issuers) {
const assetDefinition = {
'data': asset || null,
}
const inputs = issuers.map((issuer) => Transaction.makeInputTemplate([issuer]))

return Transaction.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)
*/
static makeEd25519Condition(publicKey, json = true) {
const publicKeyBuffer = Buffer.from(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
*/
static 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)
*/
static makeSha256Condition(preimage, json = true) {
const sha256Fulfillment = new cc.PreimageSha256()
sha256Fulfillment.preimage = Buffer.from(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)
*/
static 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
static makeTransferTransaction(
unspentOutputs,
outputs,
metadata
) {
const inputs = unspentOutputs.map((unspentOutput) => {
const { tx, outputIndex } = { tx: unspentOutput.tx, outputIndex: unspentOutput.output_index }
const fulfilledOutput = tx.outputs[outputIndex]
const transactionLink = {
'output_index': outputIndex,
'transaction_id': tx.id,
}

return Transaction.makeInputTemplate(fulfilledOutput.public_keys, transactionLink)
})

const assetLink = {
'id': unspentOutputs[0].tx.operation === 'CREATE' ? unspentOutputs[0].tx.id
: unspentOutputs[0].tx.asset.id
}
return Transaction.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`.
*/
static signTransaction(transaction, ...privateKeys) {
const signedTx = clone(transaction)
signedTx.inputs.forEach((input, index) => {
const privateKey = privateKeys[index]
const privateKeyBuffer = Buffer.from(base58.decode(privateKey))
const serializedTransaction = Transaction
.serializeTransactionIntoCanonicalString(transaction)
const ed25519Fulfillment = new cc.Ed25519Sha256()
ed25519Fulfillment.sign(Buffer.from(serializedTransaction), privateKeyBuffer)
const fulfillmentUri = ed25519Fulfillment.serializeUri()

input.fulfillment = fulfillmentUri
})

return signedTx
}
}
10 changes: 0 additions & 10 deletions src/transaction/hashTransaction.js

This file was deleted.

11 changes: 0 additions & 11 deletions src/transaction/index.js

This file was deleted.

31 changes: 0 additions & 31 deletions src/transaction/makeCreateTransaction.js

This file was deleted.

27 changes: 0 additions & 27 deletions src/transaction/makeEd25519Condition.js

This file was deleted.

7 changes: 0 additions & 7 deletions src/transaction/makeInputTemplate.js

This file was deleted.

Loading