mirror of
https://github.com/bigchaindb/js-bigchaindb-driver.git
synced 2025-02-14 21:10:32 +01:00
Merge pull request #135 from bigchaindb/transaction-one-file
Transaction folder to one file
This commit is contained in:
commit
2738f6bdef
@ -37,7 +37,9 @@ const fetch = fetchPonyfill(Promise)
|
|||||||
* @return {Promise} Promise that will resolve with the response if its status was 2xx;
|
* @return {Promise} Promise that will resolve with the response if its status was 2xx;
|
||||||
* otherwise rejects with the response
|
* 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
|
let expandedUrl = url
|
||||||
|
|
||||||
if (urlTemplateSpec != null) {
|
if (urlTemplateSpec != null) {
|
||||||
|
@ -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
|
// If there's anything left to interpolate by the end then we've failed to interpolate
|
||||||
// the entire replacement string.
|
// the entire replacement string.
|
||||||
if (interpolationLeft.length) {
|
if (interpolationLeft.length) {
|
||||||
throw new SyntaxError(
|
throw new SyntaxError(`[formatText] failed to parse named argument key: ${replacement}`)
|
||||||
`[formatText] failed to parse named argument key: ${replacement}`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
|
|
||||||
export Ed25519Keypair from './Ed25519Keypair'
|
export Ed25519Keypair from './Ed25519Keypair'
|
||||||
|
|
||||||
export * as Transaction from './transaction'
|
|
||||||
export Connection from './connection'
|
export Connection from './connection'
|
||||||
|
export Transaction from './transaction'
|
||||||
|
export ccJsonLoad from './utils/ccJsonLoad'
|
||||||
|
export ccJsonify from './utils/ccJsonify'
|
||||||
|
260
src/transaction.js
Normal file
260
src/transaction.js
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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))
|
|
||||||
}
|
|
@ -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'
|
|
@ -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)
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
import { Buffer } from 'buffer'
|
|
||||||
|
|
||||||
import base58 from 'bs58'
|
|
||||||
import cc from 'crypto-conditions'
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
export default function makeInputTemplate(publicKeys = [], fulfills = null, fulfillment = null) {
|
|
||||||
return {
|
|
||||||
fulfillment,
|
|
||||||
fulfills,
|
|
||||||
'owners_before': publicKeys,
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
import { Buffer } from 'buffer'
|
|
||||||
|
|
||||||
import cc from 'crypto-conditions'
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
import cc from 'crypto-conditions'
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -1,46 +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 each `unspentTransaction`.
|
|
||||||
* @param {object[]} unspentOutputs Array of unspent Transactions' Outputs.
|
|
||||||
* Each item contains Transaction itself
|
|
||||||
* and index of unspent Output for that 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 {object} metadata Metadata for the Transaction - optional
|
|
||||||
* @returns {object} Unsigned transaction -- make sure to call signTransaction() on it before
|
|
||||||
* sending it off!
|
|
||||||
*/
|
|
||||||
export default function makeTransferTransaction(
|
|
||||||
unspentOutputs,
|
|
||||||
outputs,
|
|
||||||
metadata
|
|
||||||
) {
|
|
||||||
const inputs = unspentOutputs.map((unspentOutput) => {
|
|
||||||
const tx = unspentOutput.tx
|
|
||||||
const outputIndex = unspentOutput.output_index
|
|
||||||
const fulfilledOutput = tx.outputs[outputIndex]
|
|
||||||
const transactionLink = {
|
|
||||||
'output_index': outputIndex,
|
|
||||||
'transaction_id': tx.id,
|
|
||||||
}
|
|
||||||
|
|
||||||
return makeInputTemplate(fulfilledOutput.public_keys, transactionLink)
|
|
||||||
})
|
|
||||||
|
|
||||||
const assetLink = {
|
|
||||||
'id': unspentOutputs[0].tx.operation === 'CREATE' ? unspentOutputs[0].tx.id
|
|
||||||
: unspentOutputs[0].tx.asset.id
|
|
||||||
}
|
|
||||||
|
|
||||||
const meta = metadata || null
|
|
||||||
|
|
||||||
return makeTransaction('TRANSFER', assetLink, meta, outputs, inputs)
|
|
||||||
}
|
|
@ -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))
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
import { Buffer } from 'buffer'
|
|
||||||
import base58 from 'bs58'
|
|
||||||
import cc from 'crypto-conditions'
|
|
||||||
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
|
|
||||||
}
|
|
@ -13,7 +13,7 @@ export default function ccJsonLoad(conditionJson) {
|
|||||||
const condition = new cc.Condition()
|
const condition = new cc.Condition()
|
||||||
condition.type = conditionJson.type_id
|
condition.type = conditionJson.type_id
|
||||||
condition.bitmask = conditionJson.bitmask
|
condition.bitmask = conditionJson.bitmask
|
||||||
condition.hash = new Buffer(base58.decode(conditionJson.hash))
|
condition.hash = Buffer.from(base58.decode(conditionJson.hash))
|
||||||
condition.maxFulfillmentLength = parseInt(conditionJson.max_fulfillment_length, 10)
|
condition.maxFulfillmentLength = parseInt(conditionJson.max_fulfillment_length, 10)
|
||||||
return condition
|
return condition
|
||||||
} else {
|
} else {
|
||||||
@ -34,7 +34,7 @@ export default function ccJsonLoad(conditionJson) {
|
|||||||
|
|
||||||
if (conditionJson.type === 'ed25519-sha-256') {
|
if (conditionJson.type === 'ed25519-sha-256') {
|
||||||
fulfillment = new cc.Ed25519Sha256()
|
fulfillment = new cc.Ed25519Sha256()
|
||||||
fulfillment.publicKey = new Buffer(base58.decode(conditionJson.public_key))
|
fulfillment.publicKey = Buffer.from(base58.decode(conditionJson.public_key))
|
||||||
}
|
}
|
||||||
return fulfillment
|
return fulfillment
|
||||||
}
|
}
|
@ -367,9 +367,7 @@ test('Search blocks containing a transaction', t => {
|
|||||||
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
|
.then(({ id }) => conn.pollStatusAndFetchTransaction(id))
|
||||||
.then(({ id }) => conn.listBlocks(id, 'VALID'))
|
.then(({ id }) => conn.listBlocks(id, 'VALID'))
|
||||||
.then(blocks => conn.getBlock(blocks.pop()))
|
.then(blocks => conn.getBlock(blocks.pop()))
|
||||||
.then(({ block: { transactions } }) => transactions.filter(
|
.then(({ block: { transactions } }) => transactions.filter(({ id }) => id === createTxSigned.id))
|
||||||
({ id }) => id === createTxSigned.id
|
|
||||||
))
|
|
||||||
.then(transactions => t.truthy(transactions.length === 1))
|
.then(transactions => t.truthy(transactions.length === 1))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import test from 'ava'
|
import test from 'ava'
|
||||||
import cc from 'crypto-conditions'
|
import cc from 'crypto-conditions'
|
||||||
import { Ed25519Keypair, Transaction } from '../../src'
|
import { Ed25519Keypair, Transaction, ccJsonLoad } from '../../src'
|
||||||
|
|
||||||
|
|
||||||
test('Ed25519 condition encoding', t => {
|
test('Ed25519 condition encoding', t => {
|
||||||
const publicKey = '4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS'
|
const publicKey = '4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS'
|
||||||
@ -19,8 +18,7 @@ test('Ed25519 condition encoding', t => {
|
|||||||
test('Threshold condition encoding', t => {
|
test('Threshold condition encoding', t => {
|
||||||
const publicKey = '4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS'
|
const publicKey = '4zvwRjXUKGfvwnParsHAS3HuSVzV5cA4McphgmoCtajS'
|
||||||
const ed25519 = Transaction.makeEd25519Condition(publicKey, false)
|
const ed25519 = Transaction.makeEd25519Condition(publicKey, false)
|
||||||
const condition = Transaction.makeThresholdCondition(
|
const condition = Transaction.makeThresholdCondition(1, [ed25519, ed25519])
|
||||||
1, [ed25519, ed25519])
|
|
||||||
const output = Transaction.makeOutput(condition)
|
const output = Transaction.makeOutput(condition)
|
||||||
const target = {
|
const target = {
|
||||||
condition: {
|
condition: {
|
||||||
@ -62,14 +60,15 @@ test('Fulfillment correctly formed', t => {
|
|||||||
)
|
)
|
||||||
const msg = Transaction.serializeTransactionIntoCanonicalString(txTransfer)
|
const msg = Transaction.serializeTransactionIntoCanonicalString(txTransfer)
|
||||||
const txSigned = Transaction.signTransaction(txTransfer, alice.privateKey)
|
const txSigned = Transaction.signTransaction(txTransfer, alice.privateKey)
|
||||||
t.truthy(cc.validateFulfillment(txSigned.inputs[0].fulfillment,
|
t.truthy(cc.validateFulfillment(
|
||||||
txCreate.outputs[0].condition.uri,
|
txSigned.inputs[0].fulfillment, txCreate.outputs[0].condition.uri,
|
||||||
new Buffer(msg)))
|
Buffer.from(msg)
|
||||||
|
))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
test('CryptoConditions JSON load', t => {
|
test('CryptoConditions JSON load', t => {
|
||||||
const cond = Transaction.ccJsonLoad({
|
const cond = ccJsonLoad({
|
||||||
type: 'threshold-sha-256',
|
type: 'threshold-sha-256',
|
||||||
threshold: 1,
|
threshold: 1,
|
||||||
subconditions: [{
|
subconditions: [{
|
||||||
@ -85,7 +84,7 @@ test('CryptoConditions JSON load', t => {
|
|||||||
|
|
||||||
|
|
||||||
test('CryptoConditions JSON load', t => {
|
test('CryptoConditions JSON load', t => {
|
||||||
const cond = Transaction.ccJsonLoad({
|
const cond = ccJsonLoad({
|
||||||
type: 'threshold-sha-256',
|
type: 'threshold-sha-256',
|
||||||
threshold: 1,
|
threshold: 1,
|
||||||
subconditions: [{
|
subconditions: [{
|
||||||
|
@ -2,8 +2,6 @@ import test from 'ava'
|
|||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
|
|
||||||
import { Transaction } from '../../src'
|
import { Transaction } from '../../src'
|
||||||
import * as makeTransaction from '../../src/transaction/makeTransaction' // eslint-disable-line
|
|
||||||
import makeInputTemplate from '../../src/transaction/makeInputTemplate'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
alice,
|
alice,
|
||||||
@ -70,8 +68,7 @@ test('makeOutput throws TypeError with incorrect amount type', t => {
|
|||||||
|
|
||||||
|
|
||||||
test('Create TRANSFER transaction based on CREATE transaction', t => {
|
test('Create TRANSFER transaction based on CREATE transaction', t => {
|
||||||
sinon.spy(makeTransaction, 'default')
|
sinon.spy(Transaction, 'makeTransaction')
|
||||||
|
|
||||||
Transaction.makeTransferTransaction(
|
Transaction.makeTransferTransaction(
|
||||||
[{ tx: createTx, output_index: 0 }],
|
[{ tx: createTx, output_index: 0 }],
|
||||||
[aliceOutput],
|
[aliceOutput],
|
||||||
@ -82,22 +79,21 @@ test('Create TRANSFER transaction based on CREATE transaction', t => {
|
|||||||
{ id: createTx.id },
|
{ id: createTx.id },
|
||||||
metaData,
|
metaData,
|
||||||
[aliceOutput],
|
[aliceOutput],
|
||||||
[makeInputTemplate(
|
[Transaction.makeInputTemplate(
|
||||||
[alice.publicKey],
|
[alice.publicKey],
|
||||||
{ output_index: 0, transaction_id: createTx.id }
|
{ output_index: 0, transaction_id: createTx.id }
|
||||||
)]
|
)]
|
||||||
]
|
]
|
||||||
|
|
||||||
// NOTE: `src/transaction/makeTransaction` is `export default`, hence we
|
// NOTE: `src/transaction/makeTransaction` is `export default`, hence we
|
||||||
// can only mock `makeTransaction.default` with a hack:
|
// can only mock `makeTransaction.default` with a hack:
|
||||||
// See: https://stackoverflow.com/a/33676328/1263876
|
// See: https://stackoverflow.com/a/33676328/1263876
|
||||||
t.truthy(makeTransaction.default.calledWith(...expected))
|
t.truthy(Transaction.makeTransaction.calledWith(...expected))
|
||||||
makeTransaction.default.restore()
|
Transaction.makeTransaction.restore()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
test('Create TRANSFER transaction based on TRANSFER transaction', t => {
|
test('Create TRANSFER transaction based on TRANSFER transaction', t => {
|
||||||
sinon.spy(makeTransaction, 'default')
|
sinon.spy(Transaction, 'makeTransaction')
|
||||||
|
|
||||||
Transaction.makeTransferTransaction(
|
Transaction.makeTransferTransaction(
|
||||||
[{ tx: transferTx, output_index: 0 }],
|
[{ tx: transferTx, output_index: 0 }],
|
||||||
@ -109,12 +105,12 @@ test('Create TRANSFER transaction based on TRANSFER transaction', t => {
|
|||||||
{ id: transferTx.asset.id },
|
{ id: transferTx.asset.id },
|
||||||
metaData,
|
metaData,
|
||||||
[aliceOutput],
|
[aliceOutput],
|
||||||
[makeInputTemplate(
|
[Transaction.makeInputTemplate(
|
||||||
[alice.publicKey],
|
[alice.publicKey],
|
||||||
{ output_index: 0, transaction_id: transferTx.id }
|
{ output_index: 0, transaction_id: transferTx.id }
|
||||||
)]
|
)]
|
||||||
]
|
]
|
||||||
|
|
||||||
t.truthy(makeTransaction.default.calledWith(...expected))
|
t.truthy(Transaction.makeTransaction.calledWith(...expected))
|
||||||
makeTransaction.default.restore()
|
Transaction.makeTransaction.restore()
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user