diff --git a/src/transaction/index.js b/src/transaction/index.js index 667a1cd..49a9614 100644 --- a/src/transaction/index.js +++ b/src/transaction/index.js @@ -1,6 +1,10 @@ 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 signTransaction from './signTransaction'; \ No newline at end of file +export serializeTransactionIntoCanonicalString from './serializeTransactionIntoCanonicalString'; +export signTransaction from './signTransaction'; +export ccJsonLoad from './utils/ccJsonLoad'; diff --git a/src/transaction/makeEd25519Condition.js b/src/transaction/makeEd25519Condition.js index 2dac434..1bc4e26 100644 --- a/src/transaction/makeEd25519Condition.js +++ b/src/transaction/makeEd25519Condition.js @@ -3,26 +3,24 @@ import { Buffer } from 'buffer'; import base58 from 'bs58'; import cc from 'five-bells-condition'; +import ccJsonify from './utils/ccJsonify'; + + /** * 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 {bool} json 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) { +export default function makeEd25519Condition(publicKey, json=true) { 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, - }; + if (json) { + return ccJsonify(ed25519Fulfillment) + } + + return ed25519Fulfillment; } diff --git a/src/transaction/makeOutput.js b/src/transaction/makeOutput.js index 92af00f..55eeed6 100644 --- a/src/transaction/makeOutput.js +++ b/src/transaction/makeOutput.js @@ -7,8 +7,9 @@ */ export default function makeOutput(condition, amount = 1) { return { - amount, + amount: JSON.stringify(amount), condition, - 'public_keys': [condition.details.public_key], + 'public_keys': condition.details.hasOwnProperty('public_key') ? + [condition.details.public_key] : [], }; } \ No newline at end of file diff --git a/src/transaction/makeSha256Condition.js b/src/transaction/makeSha256Condition.js new file mode 100644 index 0000000..234cd3e --- /dev/null +++ b/src/transaction/makeSha256Condition.js @@ -0,0 +1,21 @@ +import { Buffer } from 'buffer'; + +import cc from 'five-bells-condition'; + +import ccJsonify from './utils/ccJsonify'; + +/** + * 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 {bool} json 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 new file mode 100644 index 0000000..dafcd03 --- /dev/null +++ b/src/transaction/makeThresholdCondition.js @@ -0,0 +1,28 @@ +import { Buffer } from 'buffer'; + +import base58 from 'bs58'; +import cc from 'five-bells-condition'; + +import ccJsonify from './utils/ccJsonify'; +/** + * Create an Sha256 Threshold Cryptocondition from threshold to put into an Output of a Transaction + * @param {number} threshold + * @param {array} subconditions + * @param {bool} json 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/serializeTransactionIntoCanonicalString.js b/src/transaction/serializeTransactionIntoCanonicalString.js index d2cd54b..65d2fe8 100644 --- a/src/transaction/serializeTransactionIntoCanonicalString.js +++ b/src/transaction/serializeTransactionIntoCanonicalString.js @@ -2,7 +2,7 @@ import stableStringify from 'json-stable-stringify'; import clone from 'clone'; -export default function serializeTransactionIntoCanonicalString(transaction, input) { +export default function serializeTransactionIntoCanonicalString(transaction) { // BigchainDB signs fulfillments by serializing transactions into a "canonical" format where const tx = clone(transaction); // Sort the keys diff --git a/src/transaction/signTransaction.js b/src/transaction/signTransaction.js index fa99f24..dc1e154 100644 --- a/src/transaction/signTransaction.js +++ b/src/transaction/signTransaction.js @@ -22,7 +22,7 @@ export default function signTransaction(transaction, ...privateKeys) { signedTx.inputs.forEach((input, index) => { const privateKey = privateKeys[index]; const privateKeyBuffer = new Buffer(base58.decode(privateKey)); - const serializedTransaction = serializeTransactionIntoCanonicalString(transaction, input); + const serializedTransaction = serializeTransactionIntoCanonicalString(transaction); const ed25519Fulfillment = new cc.Ed25519(); ed25519Fulfillment.sign(new Buffer(serializedTransaction), privateKeyBuffer); const fulfillmentUri = ed25519Fulfillment.serializeUri(); diff --git a/src/transaction/utils/ccJsonLoad.js b/src/transaction/utils/ccJsonLoad.js new file mode 100644 index 0000000..e756553 --- /dev/null +++ b/src/transaction/utils/ccJsonLoad.js @@ -0,0 +1,48 @@ +import base58 from 'bs58'; +import cc from 'five-bells-condition'; +import { Buffer } from 'buffer'; + +/** + * + * @param {object} conditionJson + * @returns {cc.Condition} Ed25519 Condition (that will need to wrapped in an Output) + */ +export default function ccJsonLoad(conditionJson) { + + if ('hash' in conditionJson) { + let condition = new cc.Condition(); + condition.type = conditionJson.type_id; + condition.bitmask = conditionJson.bitmask; + condition.hash = new Buffer(base58.decode(conditionJson.hash)); + condition.maxFulfillmentLength = parseInt(conditionJson.max_fulfillment_length, 10); + return condition + } else { + let fulfillment; + + if (conditionJson.type_id === 2) { + fulfillment = new cc.ThresholdSha256(); + fulfillment.threshold = conditionJson.threshold; + conditionJson.subfulfillments.forEach((subfulfillment) => { + subfulfillment = ccJsonLoad(subfulfillment); + if ('getConditionUri' in subfulfillment) + fulfillment.addSubfulfillment(subfulfillment); + else if ('serializeUri' in subfulfillment) + fulfillment.addSubcondition(subfulfillment) + }) + } + + if (conditionJson.type_id === 0) { + fulfillment = new cc.PreimageSha256(); + fulfillment.preimage = new Buffer(conditionJson.preimage); + + } + + if (conditionJson.type_id === 4) { + fulfillment = new cc.Ed25519(); + fulfillment.publicKey = new Buffer(base58.decode(conditionJson.public_key)); + if (conditionJson.signature) + fulfillment.signature = new Buffer(base58.decode(conditionJson.signature)); + } + return fulfillment; + } +} diff --git a/src/transaction/utils/ccJsonify.js b/src/transaction/utils/ccJsonify.js new file mode 100644 index 0000000..3380e27 --- /dev/null +++ b/src/transaction/utils/ccJsonify.js @@ -0,0 +1,66 @@ +import base58 from 'bs58'; + +/** + * Create an Ed25519 Cryptocondition from an Ed25519 public key to put into an Output of a Transaction + * @param {cc.Fulfillment} fulfillment 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 ccJsonify(fulfillment) { + + let conditionUri; + + if ('getConditionUri' in fulfillment) + conditionUri = fulfillment.getConditionUri(); + else if ('serializeUri' in fulfillment) + conditionUri = fulfillment.serializeUri(); + + let jsonBody = { + 'details': {}, + 'uri': conditionUri, + }; + + if (fulfillment.getTypeId() === 0) { + jsonBody.details.type_id = 0; + jsonBody.details.bitmask = 3; + + if ('preimage' in fulfillment) { + jsonBody.details.preimage = fulfillment.preimage.toString(); + jsonBody.details.type = 'fulfillment'; + } + } + + if (fulfillment.getTypeId() === 2) + return { + 'details': { + 'type_id': 2, + 'type': 'fulfillment', + 'bitmask': fulfillment.getBitmask(), + 'threshold': fulfillment.threshold, + 'subfulfillments': fulfillment.subconditions.map((subcondition) => { + const subconditionJson = ccJsonify(subcondition.body); + subconditionJson.details.weight = 1; + return subconditionJson.details; + }) + }, + 'uri': conditionUri, + }; + + if (fulfillment.getTypeId() === 4) { + jsonBody.details.type_id = 4; + jsonBody.details.bitmask = 32; + + if ('publicKey' in fulfillment) { + jsonBody.details.signature = null; + jsonBody.details.public_key = base58.encode(fulfillment.publicKey); + jsonBody.details.type = 'fulfillment'; + } + } + + if ('hash' in fulfillment) { + jsonBody.details.hash = base58.encode(fulfillment.hash); + jsonBody.details.max_fulfillment_length = fulfillment.maxFulfillmentLength; + jsonBody.details.type = 'condition'; + } + + return jsonBody; +}