-
Notifications
You must be signed in to change notification settings - Fork 92
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
Add Threshold fulfillments #119
base: master
Are you sure you want to change the base?
Changes from all commits
860ce11
5e83844
d0b0598
35be96e
e48636d
5d217a4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
export default function makeFulfillment(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 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
import clone from 'clone' | ||
import hashTransaction from './hashTransaction' | ||
|
||
|
||
|
@@ -16,13 +17,19 @@ function makeTransactionTemplate() { | |
|
||
export default function makeTransaction(operation, asset, metadata = null, outputs = [], inputs = []) { | ||
const tx = makeTransactionTemplate() | ||
|
||
const realInputs = clone(inputs) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggest naming this to something else than |
||
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) | ||
tx.inputs.forEach((input) => { | ||
input.fulfillment = null | ||
}) | ||
tx.id = hashTransaction(tx) | ||
tx.inputs = inputs | ||
return tx | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(fulfilledOutput.public_keys)) | ||
}) | ||
|
||
const assetLink = { | ||
'id': unspentTransaction.operation === 'CREATE' ? unspentTransaction.id | ||
: unspentTransaction.asset.id | ||
} | ||
const makeT = makeTransaction('TRANSFER', assetLink, metadata, outputs, inputs) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why this change? I suspect you don't need |
||
|
||
return makeTransaction('TRANSFER', assetLink, metadata, outputs, inputs) | ||
return makeT | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,15 +20,43 @@ import serializeTransactionIntoCanonicalString from './serializeTransactionIntoC | |
*/ | ||
export default function signTransaction(transaction, ...privateKeys) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What I'd like to see (maybe as tests): What happens
See also my comment on having a friend sign with you |
||
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 | ||
transaction.inputs.forEach((input) => { | ||
input.fulfillment = null // OJOOO | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does OJOOO mean? |
||
}) | ||
const serializedTransaction = serializeTransactionIntoCanonicalString(transaction) | ||
signedTx.inputs.forEach((input) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In both the
I wonder if that could be it's own routine. DRY. |
||
if (input.fulfillment.type === 'ed25519-sha-256') { | ||
const privateKey = privateKeys[0] | ||
privateKeys.splice(0, 1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
const privateKeyBuffer = new Buffer(base58.decode(privateKey)) | ||
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') { | ||
// Not valid for more than one input with m-of-n signatures where m < n. | ||
const thresholdFulfillment = new cc.ThresholdSha256() | ||
// m represents the number of signatures needed for this input | ||
let m = 0 | ||
input.fulfillment.subconditions.forEach((subcdt) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
const ed25519subFulfillment = new cc.Ed25519Sha256() | ||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Codecov says, this condition is not covered by tests. Please add :) |
||
// 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(m) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of determining One suggestion would be: add threshold to parameters of sign method? |
||
const fulfillmentUri = thresholdFulfillment.serializeUri() | ||
input.fulfillment = fulfillmentUri | ||
} | ||
}) | ||
|
||
return signedTx | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,6 +42,24 @@ test('Valid CREATE transaction', t => { | |
}) | ||
|
||
|
||
test('Valid CREATE transaction with Threshold input', t => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test may not be that useful. The CREATE tx fulfillment isn't reliant on any output. I'd instead test the following:
and
I'd also test:
For last point, think about a real-life scenario. You and a friend want to spend a transaction together, so you start signing with your key. They you send the half-signed tx to your friend. They sign as well and send it off to the network. |
||
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 | ||
) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
subcdts
is not an appreviation we commonly use in the code base.I suggest to rename to a word we also use in other implementations.