From 860ce117e199e73fc5007140cd7069b060878ed4 Mon Sep 17 00:00:00 2001 From: manolodewiner Date: Fri, 3 Nov 2017 12:17:50 +0100 Subject: [PATCH 1/6] fulfillment with values --- src/transaction/makeCreateTransaction.js | 4 +++- src/transaction/makeTransaction.js | 5 +++++ src/transaction/makeTransferTransaction.js | 9 +++++---- src/transaction/signTransaction.js | 20 +++++++++++++------- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/transaction/makeCreateTransaction.js b/src/transaction/makeCreateTransaction.js index 8b259dea..42989e13 100644 --- a/src/transaction/makeCreateTransaction.js +++ b/src/transaction/makeCreateTransaction.js @@ -1,3 +1,4 @@ +import makeFulfillment from './makeFulfillment' import makeInputTemplate from './makeInputTemplate' import makeTransaction from './makeTransaction' @@ -25,7 +26,8 @@ export default function makeCreateTransaction(asset, metadata, outputs, ...issue const assetDefinition = { 'data': asset || null, } - const inputs = issuers.map((issuer) => makeInputTemplate([issuer])) + const inputs = issuers.map((issuer) => makeInputTemplate([issuer], null, + makeFulfillment([issuer]))) return makeTransaction('CREATE', assetDefinition, metadata, outputs, inputs) } diff --git a/src/transaction/makeTransaction.js b/src/transaction/makeTransaction.js index 00c8ce26..71dcea42 100644 --- a/src/transaction/makeTransaction.js +++ b/src/transaction/makeTransaction.js @@ -1,3 +1,4 @@ +import clone from 'clone' import hashTransaction from './hashTransaction' @@ -16,6 +17,8 @@ function makeTransactionTemplate() { export default function makeTransaction(operation, asset, metadata = null, outputs = [], inputs = []) { const tx = makeTransactionTemplate() + + const realInputs = clone(inputs) tx.operation = operation tx.asset = asset tx.metadata = metadata @@ -23,6 +26,8 @@ export default function makeTransaction(operation, asset, metadata = null, outpu tx.outputs = outputs // Hashing must be done after, as the hash is of the Transaction (up to now) + tx.inputs[0].fulfillment = null tx.id = hashTransaction(tx) + tx.inputs = realInputs return tx } diff --git a/src/transaction/makeTransferTransaction.js b/src/transaction/makeTransferTransaction.js index 3d62bd23..f49fb873 100644 --- a/src/transaction/makeTransferTransaction.js +++ b/src/transaction/makeTransferTransaction.js @@ -1,3 +1,4 @@ +import makeFulfillment from './makeFulfillment' import makeInputTemplate from './makeInputTemplate' import makeTransaction from './makeTransaction' @@ -36,14 +37,14 @@ export default function makeTransferTransaction( 'output_index': outputIndex, 'transaction_id': unspentTransaction.id, } - - return makeInputTemplate(fulfilledOutput.public_keys, transactionLink) + return makeInputTemplate(fulfilledOutput.public_keys, transactionLink, + makeFulfillment(outputs[outputIndex].public_keys)) }) - const assetLink = { 'id': unspentTransaction.operation === 'CREATE' ? unspentTransaction.id : unspentTransaction.asset.id } + const makeT = makeTransaction('TRANSFER', assetLink, metadata, outputs, inputs) - return makeTransaction('TRANSFER', assetLink, metadata, outputs, inputs) + return makeT } diff --git a/src/transaction/signTransaction.js b/src/transaction/signTransaction.js index 32422a7f..e5511a01 100644 --- a/src/transaction/signTransaction.js +++ b/src/transaction/signTransaction.js @@ -21,14 +21,20 @@ import serializeTransactionIntoCanonicalString from './serializeTransactionIntoC 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() + console.log('inpuuuuut', input) - input.fulfillment = fulfillmentUri + if (input.fulfillment.type === 'ed25519-sha-256') { + transaction.inputs[0].fulfillment = null + 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 + } else if (input.fulfillment.type === 'threshold-sha-256') { + input.fulfillment = 'fulfillmentUri' + } }) return signedTx From 5e838447dba0bf4ad25d4502eb1e28fdd680dca0 Mon Sep 17 00:00:00 2001 From: manolodewiner Date: Fri, 3 Nov 2017 13:07:34 +0100 Subject: [PATCH 2/6] create makeFulfillment --- src/transaction/makeFulfillment.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/transaction/makeFulfillment.js diff --git a/src/transaction/makeFulfillment.js b/src/transaction/makeFulfillment.js new file mode 100644 index 00000000..d4f7e93c --- /dev/null +++ b/src/transaction/makeFulfillment.js @@ -0,0 +1,21 @@ +export default function createFulfillment(issuers = []) { + if (issuers.length === 1) { + const fulfillment = { + type: 'ed25519-sha-256', + public_keys: issuers + } + return fulfillment + } else { + const subcdts = [] + issuers.map((issuer) => ( + subcdts.push({ type: 'ed25519-sha-256', + public_key: issuer + }) + )) + const fulfillment = { + type: 'threshold-sha-256', + subconditions: subcdts + } + return fulfillment + } +} From d0b059829f975267306332a84c997a9e6dca91a2 Mon Sep 17 00:00:00 2001 From: manolodewiner Date: Mon, 6 Nov 2017 00:06:18 +0100 Subject: [PATCH 3/6] sign therhold condition --- src/transaction/makeCreateTransaction.js | 6 +++--- src/transaction/signTransaction.js | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/transaction/makeCreateTransaction.js b/src/transaction/makeCreateTransaction.js index 42989e13..552080af 100644 --- a/src/transaction/makeCreateTransaction.js +++ b/src/transaction/makeCreateTransaction.js @@ -26,8 +26,8 @@ export default function makeCreateTransaction(asset, metadata, outputs, ...issue const assetDefinition = { 'data': asset || null, } - const inputs = issuers.map((issuer) => makeInputTemplate([issuer], null, - makeFulfillment([issuer]))) + // Create transaction has 1 and just 1 input + const inputs = makeInputTemplate(issuers, null, makeFulfillment(issuers)) - return makeTransaction('CREATE', assetDefinition, metadata, outputs, inputs) + return makeTransaction('CREATE', assetDefinition, metadata, outputs, [inputs]) } diff --git a/src/transaction/signTransaction.js b/src/transaction/signTransaction.js index e5511a01..00ea0077 100644 --- a/src/transaction/signTransaction.js +++ b/src/transaction/signTransaction.js @@ -22,18 +22,28 @@ export default function signTransaction(transaction, ...privateKeys) { const signedTx = clone(transaction) signedTx.inputs.forEach((input, index) => { console.log('inpuuuuut', input) + transaction.inputs[index].fulfillment = null // OJOOO + const serializedTransaction = serializeTransactionIntoCanonicalString(transaction) if (input.fulfillment.type === 'ed25519-sha-256') { - transaction.inputs[0].fulfillment = null - const privateKey = privateKeys[index] + const privateKey = privateKeys[index] // TODO index is not correct here. just work for some cases 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 } else if (input.fulfillment.type === 'threshold-sha-256') { - input.fulfillment = 'fulfillmentUri' + const thresholdFulfillment = new cc.ThresholdSha256() + input.fulfillment.subconditions.forEach((subcondition, indexSubcondition) => { + const privateKey = privateKeys[index + indexSubcondition] // TODO index is not correct here. just work for some cases + const privateKeyBuffer = new Buffer(base58.decode(privateKey)) + const ed25519subFulfillment = new cc.Ed25519Sha256() + ed25519subFulfillment.sign(new Buffer(serializedTransaction), privateKeyBuffer) + thresholdFulfillment.addSubfulfillmentUri(ed25519subFulfillment) + }) + thresholdFulfillment.setThreshold(1) // defaults to subconditions.length + const fulfillmentUri = thresholdFulfillment.serializeUri() + input.fulfillment = fulfillmentUri } }) From 35be96e31d2b22020be9404adf34ae26c187a328 Mon Sep 17 00:00:00 2001 From: manolodewiner Date: Mon, 6 Nov 2017 13:24:42 +0100 Subject: [PATCH 4/6] fulfillment null for test_cryptocondition --- src/transaction/makeTransaction.js | 4 +++- src/transaction/makeTransferTransaction.js | 2 +- src/transaction/signTransaction.js | 18 ++++++++++-------- test/transaction/test_cryptoconditions.js | 5 ++++- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/transaction/makeTransaction.js b/src/transaction/makeTransaction.js index 71dcea42..367d9086 100644 --- a/src/transaction/makeTransaction.js +++ b/src/transaction/makeTransaction.js @@ -26,7 +26,9 @@ export default function makeTransaction(operation, asset, metadata = null, outpu tx.outputs = outputs // Hashing must be done after, as the hash is of the Transaction (up to now) - tx.inputs[0].fulfillment = null + tx.inputs.forEach((input) => { + input.fulfillment = null + }) tx.id = hashTransaction(tx) tx.inputs = realInputs return tx diff --git a/src/transaction/makeTransferTransaction.js b/src/transaction/makeTransferTransaction.js index f49fb873..18532d0f 100644 --- a/src/transaction/makeTransferTransaction.js +++ b/src/transaction/makeTransferTransaction.js @@ -38,7 +38,7 @@ export default function makeTransferTransaction( 'transaction_id': unspentTransaction.id, } return makeInputTemplate(fulfilledOutput.public_keys, transactionLink, - makeFulfillment(outputs[outputIndex].public_keys)) + makeFulfillment(fulfilledOutput.public_keys)) }) const assetLink = { 'id': unspentTransaction.operation === 'CREATE' ? unspentTransaction.id diff --git a/src/transaction/signTransaction.js b/src/transaction/signTransaction.js index 00ea0077..963186c9 100644 --- a/src/transaction/signTransaction.js +++ b/src/transaction/signTransaction.js @@ -20,13 +20,14 @@ import serializeTransactionIntoCanonicalString from './serializeTransactionIntoC */ export default function signTransaction(transaction, ...privateKeys) { const signedTx = clone(transaction) - signedTx.inputs.forEach((input, index) => { - console.log('inpuuuuut', input) - transaction.inputs[index].fulfillment = null // OJOOO - const serializedTransaction = serializeTransactionIntoCanonicalString(transaction) - + transaction.inputs.forEach((input) => { + input.fulfillment = null // OJOOO + }) + const serializedTransaction = serializeTransactionIntoCanonicalString(transaction) + signedTx.inputs.forEach((input) => { if (input.fulfillment.type === 'ed25519-sha-256') { - const privateKey = privateKeys[index] // TODO index is not correct here. just work for some cases + const privateKey = privateKeys[0] + privateKeys.splice(0, 1) const privateKeyBuffer = new Buffer(base58.decode(privateKey)) const ed25519Fulfillment = new cc.Ed25519Sha256() ed25519Fulfillment.sign(new Buffer(serializedTransaction), privateKeyBuffer) @@ -34,8 +35,9 @@ export default function signTransaction(transaction, ...privateKeys) { input.fulfillment = fulfillmentUri } else if (input.fulfillment.type === 'threshold-sha-256') { const thresholdFulfillment = new cc.ThresholdSha256() - input.fulfillment.subconditions.forEach((subcondition, indexSubcondition) => { - const privateKey = privateKeys[index + indexSubcondition] // TODO index is not correct here. just work for some cases + input.fulfillment.subconditions.forEach(() => { + const privateKey = privateKeys[0] + privateKeys.splice(0, 1) const privateKeyBuffer = new Buffer(base58.decode(privateKey)) const ed25519subFulfillment = new cc.Ed25519Sha256() ed25519subFulfillment.sign(new Buffer(serializedTransaction), privateKeyBuffer) diff --git a/test/transaction/test_cryptoconditions.js b/test/transaction/test_cryptoconditions.js index c6bab619..0b24c080 100644 --- a/test/transaction/test_cryptoconditions.js +++ b/test/transaction/test_cryptoconditions.js @@ -61,8 +61,11 @@ test('Fulfillment correctly formed', t => { [Transaction.makeOutput(Transaction.makeEd25519Condition(alice.publicKey))], [0] ) - const msg = Transaction.serializeTransactionIntoCanonicalString(txTransfer) const txSigned = Transaction.signTransaction(txTransfer, alice.privateKey) + txTransfer.inputs.forEach((input) => { + input.fulfillment = null // OJOOO + }) + const msg = Transaction.serializeTransactionIntoCanonicalString(txTransfer) t.truthy(cc.validateFulfillment(txSigned.inputs[0].fulfillment, txCreate.outputs[0].condition.uri, new Buffer(msg))) From e48636d075d3199039c79ceb80d5aa6ecef7383d Mon Sep 17 00:00:00 2001 From: manolodewiner Date: Wed, 8 Nov 2017 15:06:16 +0100 Subject: [PATCH 5/6] Add test makeFulfillment --- src/transaction/makeFulfillment.js | 2 +- src/transaction/makeTransaction.js | 4 +- test/integration/test_integration.js | 52 ++++++++++++++++++++++- test/transaction/test_cryptoconditions.js | 4 +- test/transaction/test_transaction.js | 7 ++- 5 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/transaction/makeFulfillment.js b/src/transaction/makeFulfillment.js index d4f7e93c..7faea107 100644 --- a/src/transaction/makeFulfillment.js +++ b/src/transaction/makeFulfillment.js @@ -1,4 +1,4 @@ -export default function createFulfillment(issuers = []) { +export default function makeFulfillment(issuers = []) { if (issuers.length === 1) { const fulfillment = { type: 'ed25519-sha-256', diff --git a/src/transaction/makeTransaction.js b/src/transaction/makeTransaction.js index 367d9086..5af659c7 100644 --- a/src/transaction/makeTransaction.js +++ b/src/transaction/makeTransaction.js @@ -22,7 +22,7 @@ export default function makeTransaction(operation, asset, metadata = null, outpu tx.operation = operation tx.asset = asset tx.metadata = metadata - tx.inputs = inputs + tx.inputs = realInputs tx.outputs = outputs // Hashing must be done after, as the hash is of the Transaction (up to now) @@ -30,6 +30,6 @@ export default function makeTransaction(operation, asset, metadata = null, outpu input.fulfillment = null }) tx.id = hashTransaction(tx) - tx.inputs = realInputs + tx.inputs = inputs return tx } diff --git a/test/integration/test_integration.js b/test/integration/test_integration.js index b5e53fee..992efd70 100644 --- a/test/integration/test_integration.js +++ b/test/integration/test_integration.js @@ -42,6 +42,24 @@ test('Valid CREATE transaction', t => { }) +test('Valid CREATE transaction with Threshold input', t => { + const conn = new Connection(API_PATH) + + const tx = Transaction.makeCreateTransaction( + asset(), + metaData, + [aliceOutput], + alice.publicKey, + bob.publicKey + ) + const txSigned = Transaction.signTransaction(tx, alice.privateKey, bob.privateKey) + + return conn.postTransaction(txSigned) + .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) + .then(resTx => t.truthy(resTx)) +}) + + test('Valid TRANSFER transaction with single Ed25519 input', t => { const conn = new Connection(API_PATH) const createTx = Transaction.makeCreateTransaction( @@ -109,6 +127,39 @@ test('Valid TRANSFER transaction with multiple Ed25519 inputs', t => { }) }) +test('Valid TRANSFER transaction with Threshold input', t => { + const conn = new Connection(API_PATH) + const createTx = Transaction.makeCreateTransaction( + asset(), + metaData, + [aliceOutput], + alice.publicKey, + bob.publicKey + ) + const createTxSigned = Transaction.signTransaction( + createTx, + alice.privateKey, + bob.privateKey + ) + + return conn.postTransaction(createTxSigned) + .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) + .then(() => { + const transferTx = Transaction.makeTransferTransaction( + createTxSigned, + metaData, + [aliceOutput], + 0 + ) + const transferTxSigned = Transaction.signTransaction( + transferTx, + alice.privateKey + ) + return conn.postTransaction(transferTxSigned) + .then(({ id }) => conn.pollStatusAndFetchTransaction(id)) + .then(resTx => t.truthy(resTx)) + }) +}) test('Search for spent and unspent outputs of a given public key', t => { const conn = new Connection(API_PATH) @@ -214,7 +265,6 @@ test('Search for spent outputs for a given public key', t => { ) const createTxSigned = Transaction.signTransaction( createTx, - carol.privateKey, carol.privateKey ) diff --git a/test/transaction/test_cryptoconditions.js b/test/transaction/test_cryptoconditions.js index 0b24c080..0c741afc 100644 --- a/test/transaction/test_cryptoconditions.js +++ b/test/transaction/test_cryptoconditions.js @@ -62,9 +62,7 @@ test('Fulfillment correctly formed', t => { [0] ) const txSigned = Transaction.signTransaction(txTransfer, alice.privateKey) - txTransfer.inputs.forEach((input) => { - input.fulfillment = null // OJOOO - }) + const msg = Transaction.serializeTransactionIntoCanonicalString(txTransfer) t.truthy(cc.validateFulfillment(txSigned.inputs[0].fulfillment, txCreate.outputs[0].condition.uri, diff --git a/test/transaction/test_transaction.js b/test/transaction/test_transaction.js index d5a28ed3..79f02ef0 100644 --- a/test/transaction/test_transaction.js +++ b/test/transaction/test_transaction.js @@ -4,6 +4,7 @@ import sinon from 'sinon' import { Transaction } from '../../src' import * as makeTransaction from '../../src/transaction/makeTransaction' // eslint-disable-line import makeInputTemplate from '../../src/transaction/makeInputTemplate' +import makeFulfillment from '../../src/transaction/makeFulfillment' import { alice, @@ -85,7 +86,8 @@ test('Create TRANSFER transaction based on CREATE transaction', t => { [aliceOutput], [makeInputTemplate( [alice.publicKey], - { output_index: 0, transaction_id: createTx.id } + { output_index: 0, transaction_id: createTx.id }, + makeFulfillment([alice.publicKey]) )] ] @@ -113,7 +115,8 @@ test('Create TRANSFER transaction based on TRANSFER transaction', t => { [aliceOutput], [makeInputTemplate( [alice.publicKey], - { output_index: 0, transaction_id: transferTx.id } + { output_index: 0, transaction_id: transferTx.id }, + makeFulfillment([alice.publicKey]) )] ] From 5d217a4a1d99d70bc22ba64f676db0307bd72428 Mon Sep 17 00:00:00 2001 From: manolodewiner Date: Tue, 21 Nov 2017 10:44:21 +0100 Subject: [PATCH 6/6] Added generic setThreshold for thresholdFulfillment --- src/transaction/signTransaction.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/transaction/signTransaction.js b/src/transaction/signTransaction.js index 963186c9..8b1a05c1 100644 --- a/src/transaction/signTransaction.js +++ b/src/transaction/signTransaction.js @@ -34,16 +34,26 @@ export default function signTransaction(transaction, ...privateKeys) { const fulfillmentUri = ed25519Fulfillment.serializeUri() input.fulfillment = fulfillmentUri } else if (input.fulfillment.type === 'threshold-sha-256') { + // Not valid for more than one input with m-of-n signatures where m < n. const thresholdFulfillment = new cc.ThresholdSha256() - input.fulfillment.subconditions.forEach(() => { - const privateKey = privateKeys[0] - privateKeys.splice(0, 1) - const privateKeyBuffer = new Buffer(base58.decode(privateKey)) + // m represents the number of signatures needed for this input + let m = 0 + input.fulfillment.subconditions.forEach((subcdt) => { const ed25519subFulfillment = new cc.Ed25519Sha256() - ed25519subFulfillment.sign(new Buffer(serializedTransaction), privateKeyBuffer) + if (privateKeys.length > 0) { + m++ + const privateKey = privateKeys[0] + privateKeys.splice(0, 1) + const privateKeyBuffer = new Buffer(base58.decode(privateKey)) + ed25519subFulfillment.sign(new Buffer(serializedTransaction), privateKeyBuffer) + } else { + // All conditions are needed as the "circuit definition" is needed. + const publicKeyBuffer = new Buffer(base58.decode(subcdt.public_key)) + ed25519subFulfillment.setPublicKey(publicKeyBuffer) + } thresholdFulfillment.addSubfulfillmentUri(ed25519subFulfillment) }) - thresholdFulfillment.setThreshold(1) // defaults to subconditions.length + thresholdFulfillment.setThreshold(m) const fulfillmentUri = thresholdFulfillment.serializeUri() input.fulfillment = fulfillmentUri }