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..9bd1aee 100644 --- a/src/transaction/makeEd25519Condition.js +++ b/src/transaction/makeEd25519Condition.js @@ -3,26 +3,21 @@ 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 * @returns {object} Ed25519 Condition (that will need to wrapped in an Output) */ -export default function makeEd25519Condition(publicKey) { +export default function makeEd25519Condition(publicKey, ccFormat=false) { 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 (ccFormat) return ed25519Fulfillment; + + return ccJsonify(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..611ab01 --- /dev/null +++ b/src/transaction/makeSha256Condition.js @@ -0,0 +1,19 @@ +import { Buffer } from 'buffer'; + +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 + * @returns {object} Ed25519 Condition (that will need to wrapped in an Output) + */ +export default function makeSha256Condition(secret, ccFormat=false) { + const sha256Fulfillment = new cc.PreimageSha256(); + sha256Fulfillment.preimage = new Buffer(secret); + + if (ccFormat) return sha256Fulfillment; + + return ccJsonify(sha256Fulfillment) +} diff --git a/src/transaction/makeThresholdCondition.js b/src/transaction/makeThresholdCondition.js new file mode 100644 index 0000000..f683f8e --- /dev/null +++ b/src/transaction/makeThresholdCondition.js @@ -0,0 +1,16 @@ +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 + * @returns {object} Ed25519 Condition (that will need to wrapped in an Output) + */ +export default function makeThresholdCondition(thresholdCondition, ccFormat=false) { + if (ccFormat) return new cc.ThresholdSha256(); + + return ccJsonify(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; +}