diff --git a/__tests__/ledger/ledgerNanoS.test.js b/__tests__/ledger/ledgerNanoS.test.js index 639740a07..bba3a610d 100644 --- a/__tests__/ledger/ledgerNanoS.test.js +++ b/__tests__/ledger/ledgerNanoS.test.js @@ -1,64 +1,32 @@ -// NOTE - begin process of testing the ledger app -import commNode from '../../app/ledger/ledger-comm-node' -import { ledgerNanoSCreateSignatureAsync } from '../../app/ledger/ledgerNanoS' -import { BIP44_PATH } from '../../app/core/constants' - -const promiseMock = (result, error = false) => { - return jest.fn(() => { - return new Promise((resolve, reject) => { - if (error) reject(new Error()) - resolve(result) - }) - }) -} - -commNode.create_async = promiseMock({ - exchange: promiseMock('8457891359059130190984390719035789753903759037590347590475907905'), - device: { - close: () => {} - } -}) - -const unsignedTx = { - type: 128, - version: 0, - scripts: [], - inputs: [ - { - prevHash: 'c6da426a36407e6f9993699e16bd57706daee021701f1d857ae0c218d67b59e0', - prevIndex: 0 - } - ], - attributes: [], - outputs: [ - { - assetId: 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', - value: 1, - scriptHash: '5df31f6f59e6a4fbdd75103786bf73db1000b235' - } - ] -} - -const serializedUnsignedTx = '8000000255842a0f096b977d6e6cf74c2387e95fc426e94d85ae9667f18e746021dbda650100a6651aab1c6de7e3c1e9e3a83cb149782ecf187f580e98840b75e4d60eea616e000002e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6000e1f505000000003775292229eccdf904f16fff8e83e7cffdc0f0cee72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c60fc7ec0010000000035b20010db73bf86371075ddfba4e6596f1ff35d' - +// // NOTE - begin process of testing the ledger app +// import commNode from '../../app/ledger/ledger-comm-node' +// import { ledgerNanoSCreateSignatureAsync } from '../../app/ledger/ledgerNanoS' +// import { BIP44_PATH } from '../../app/core/constants' +// +// const promiseMock = (result, error = false) => { +// return jest.fn(() => { +// return new Promise((resolve, reject) => { +// if (error) reject(new Error()) +// resolve(result) +// }) +// }) +// } +// +// commNode.create_async = promiseMock({ +// exchange: promiseMock('8457891359059130190984390719035789753903759037590347590475907905'), +// device: { +// close: () => {} +// } +// }) +// +// const unsignedTx = {} // TODO create viable unsignedTx and publicKey to pass into the async funciton for testing +// const publicKey = '' // TODO create viable unsignedTx and publicKey to pass into the async funciton for testing +// +// describe('ledgerNano Async Signature tests', () => { test('ledgerNano Async Signature works', async () => { - const sign = await ledgerNanoSCreateSignatureAsync(serializedUnsignedTx) - console.log('sign repsonse', sign) + // const sign = await ledgerNanoSCreateSignatureAsync(unsignedTx, publicKey) + // console.log('sign repsonse', sign) expect(true).toEqual(true) }) }) - -// const signedTx = { type: 128, -// version: 0, -// scripts: -// [ { invocationScript: '400df6c730e705b612b6179a978d1d1c43e572cd683f67fc40a03a825db6d793a29d0b3de252d7e2d74e78b1b59356bede731eb0de88a99a54391983abca6935b3', -// verificationScript: '2102028a99826edc0c97d18e22b6932373d908d323aa7f92656a77ec26e8861699efac' } ], -// inputs: -// [ { prevHash: 'c6da426a36407e6f9993699e16bd57706daee021701f1d857ae0c218d67b59e0', -// prevIndex: 0 } ], -// attributes: [], -// outputs: -// [ { assetId: 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', -// value: 1, -// scriptHash: '5df31f6f59e6a4fbdd75103786bf73db1000b235' } ] } diff --git a/app/containers/LoginLedgerNanoS/LoginLedgerNanoS.jsx b/app/containers/LoginLedgerNanoS/LoginLedgerNanoS.jsx index fa62e6bed..91a0b431b 100644 --- a/app/containers/LoginLedgerNanoS/LoginLedgerNanoS.jsx +++ b/app/containers/LoginLedgerNanoS/LoginLedgerNanoS.jsx @@ -19,9 +19,7 @@ export default class LoginLedgerNanoS extends Component { } _componentDidMount = async (getInfoAsync: Function) => { - // process.stdout.write('started componentDidMount \n') await getInfoAsync() - // process.stdout.write('success componentDidMount \n') } onLedgerNanoSChange = () => { @@ -33,7 +31,7 @@ export default class LoginLedgerNanoS extends Component { } render () { - const { hardwareDeviceInfo, hardwarePublicKeyInfo } = this.props + const { hardwareDeviceInfo, hardwarePublicKeyInfo, publicKey } = this.props return (
@@ -41,7 +39,7 @@ export default class LoginLedgerNanoS extends Component {
- +
{hardwareDeviceInfo}
diff --git a/app/ledger/ledgerNanoS.js b/app/ledger/ledgerNanoS.js index ac32167fb..248ad5421 100644 --- a/app/ledger/ledgerNanoS.js +++ b/app/ledger/ledgerNanoS.js @@ -1,336 +1,110 @@ -import axios from 'axios' import commNode from '../ledger/ledger-comm-node' import { BIP44_PATH } from '../core/constants' import { - getPublicKeyEncoded, - getAccountFromPublicKey, - getScriptHashFromAddress, - getScriptHashFromPublicKey, - getBalance, serializeTransaction, - create, - addContract, - queryRPC, - getAPIEndpoint, - ASSETS + createSignatureScript } from 'neon-js' +import asyncWrap from '../core/asyncHelper' export const CURRENT_VERSION = 0 -export const ledgerNanoSCreateSignatureAsync = async (txData) => { - return new Promise((resolve, reject) => { - let signatureInfo = 'Ledger Signing Text of Length [' + txData.length + "], Please Confirm Using the Device's Buttons. " + txData - const signData = txData + BIP44_PATH - // process.stdout.write(signatureInfo + '\n') - const validStatus = [0x9000] - const messages = [] - - const bufferSize = 255 * 2 - let offset = 0 - while (offset < signData.length) { - let chunk - let p1 - if ((signData.length - offset) > bufferSize) { - chunk = signData.substring(offset, offset + bufferSize) - } else { - chunk = signData.substring(offset) - } - if ((offset + chunk.length) === signData.length) { - p1 = '80' - } else { - p1 = '00' - } - - const chunkLength = chunk.length / 2 - // process.stdout.write('Ledger Signature chunkLength ' + chunkLength + '\n') - - let chunkLengthHex = chunkLength.toString(16) - while (chunkLengthHex.length < 2) { - chunkLengthHex = '0' + chunkLengthHex - } - // process.stdout.write('Ledger Signature chunkLength hex ' + chunkLengthHex + '\n') - // console.log('chunk.length', chunk.length) - // console.log('signData.length', signData.length) - - messages.push('8002' + p1 + '00' + chunkLengthHex + chunk) - offset += chunk.length +export const ledgerNanoSCreateSignatureAsync = async (unsignedTx, publicKeyEncoded) => { + const txData = serializeTransaction(unsignedTx) + const signData = txData + BIP44_PATH + const validStatus = [0x9000] + const messages = [] + + const bufferSize = 255 * 2 + let offset = 0 + while (offset < signData.length) { + let chunk + let p1 + if ((signData.length - offset) > bufferSize) { + chunk = signData.substring(offset, offset + bufferSize) + } else { + chunk = signData.substring(offset) + } + if ((offset + chunk.length) === signData.length) { + p1 = '80' + } else { + p1 = '00' } - // console.log('what is messages', messages) - commNode.create_async(0, false).then((comm) => { - // console.log('what is comm', comm) - for (let ix = 0; ix < messages.length; ix++) { - let message = messages[ix] - // process.stdout.write('Ledger Message (' + ix + '/' + messages.length + ') ' + message + '\n') - - comm.exchange(message, validStatus).then((response) => { - // process.stdout.write('Ledger Signature Response ' + response + '\n') - if (response !== '9000') { - comm.device.close() - - /** - * https://stackoverflow.com/questions/25829939/specification-defining-ecdsa-signature-data
- * the signature is TLV encoded. the first byte is 30, the "signature" type
- * the second byte is the length (always 44)
- * the third byte is 02, the "number: type
- * the fourth byte is the length of R (always 20)
- * the byte after the encoded number is 02, the "number: type
- * the byte after is the length of S (always 20)
- *

- * eg: - * 304402200262675396fbcc768bf505c9dc05728fd98fd977810c547d1a10c7dd58d18802022069c9c4a38ee95b4f394e31a3dd6a63054f8265ff9fd2baf68a9c4c3aa8c5d47e9000 - * is 30LL0220RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR0220SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS - */ - - let rLenHex = response.substring(6, 8) - // process.stdout.write('Ledger Signature rLenHex ' + rLenHex + '\n') - let rLen = parseInt(rLenHex, 16) * 2 - // process.stdout.write('Ledger Signature rLen ' + rLen + '\n') - let rStart = 8 - // process.stdout.write('Ledger Signature rStart ' + rStart + '\n') - let rEnd = rStart + rLen - // process.stdout.write('Ledger Signature rEnd ' + rEnd + '\n') - - while ((response.substring(rStart, rStart + 2) === '00') && ((rEnd - rStart) > 64)) { - rStart += 2 - } - - let r = response.substring(rStart, rEnd) - // process.stdout.write('Ledger Signature R [' + rStart + ',' + rEnd + ']:' + (rEnd - rStart) + ' ' + r + '\n') - let sLenHex = response.substring(rEnd + 2, rEnd + 4) - // process.stdout.write('Ledger Signature sLenHex ' + sLenHex + '\n') - let sLen = parseInt(sLenHex, 16) * 2 - // process.stdout.write('Ledger Signature sLen ' + sLen + '\n') - let sStart = rEnd + 4 - // process.stdout.write('Ledger Signature sStart ' + sStart + '\n') - let sEnd = sStart + sLen - // process.stdout.write('Ledger Signature sEnd ' + sEnd + '\n') - - while ((response.substring(sStart, sStart + 2) === '00') && ((sEnd - sStart) > 64)) { - sStart += 2 - } - - let s = response.substring(sStart, sEnd) - // process.stdout.write('Ledger Signature S [' + sStart + ',' + sEnd + ']:' + (sEnd - sStart) + ' ' + s + '\n') - - // let msgHashStart = sEnd + 4 - // let msgHashEnd = msgHashStart + 64 - // let msgHash = response.substring(msgHashStart, msgHashEnd) - // process.stdout.write('Ledger Signature msgHash [' + msgHashStart + ',' + msgHashEnd + '] ' + msgHash + '\n') - - while (r.length < 64) { - r = '00' + r - } - - while (s.length < 64) { - s = '00' + s - } + const chunkLength = chunk.length / 2 + let chunkLengthHex = chunkLength.toString(16) + while (chunkLengthHex.length < 2) { + chunkLengthHex = '0' + chunkLengthHex + } - let signature = r + s - // let signatureInfo = 'Signature of Length [' + signature.length + '] : ' + signature - // process.stdout.write('r[' + r.length + ']:"' + r + '"+s[' + s.length + ']"' + s + '" =' + signatureInfo + '\n') + messages.push(`8002${p1}00${chunkLengthHex}${chunk}`) + offset += chunk.length + } - resolve(signature) - } - }).catch((reason) => { - comm.device.close() - signatureInfo = 'An error occured[1]: ' + reason - // process.stdout.write('Signature Reponse ' + signatureInfo + '\n') - reject(signatureInfo) - }) + let [err, comm] = await asyncWrap(commNode.create_async(0, false)) + if (err) { + console.log('Signature Reponse An error occured[2]:', err) + return 'An error occured[2]: ' + err + } + for (let ix = 0; ix < messages.length; ix++) { + let message = messages[ix] + + let [error, response] = await asyncWrap(comm.exchange(message, validStatus)) + if (error) { + comm.device.close() + console.log('Signature Reponse An error occured[1]:', error) + return 'An error occured[1]: ' + error + } + if (response !== '9000') { + comm.device.close() + + /** + * https://stackoverflow.com/questions/25829939/specification-defining-ecdsa-signature-data
+ * the signature is TLV encoded. the first byte is 30, the "signature" type
+ * the second byte is the length (always 44)
+ * the third byte is 02, the "number: type
+ * the fourth byte is the length of R (always 20)
+ * the byte after the encoded number is 02, the "number: type
+ * the byte after is the length of S (always 20)
+ *

+ * eg: + * 304402200262675396fbcc768bf505c9dc05728fd98fd977810c547d1a10c7dd58d18802022069c9c4a38ee95b4f394e31a3dd6a63054f8265ff9fd2baf68a9c4c3aa8c5d47e9000 + * is 30LL0220RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR0220SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS + */ + + let rLenHex = response.substring(6, 8) + let rLen = parseInt(rLenHex, 16) * 2 + let rStart = 8 + let rEnd = rStart + rLen + + while ((response.substring(rStart, rStart + 2) === '00') && ((rEnd - rStart) > 64)) { + rStart += 2 } - }).catch((reason) => { - signatureInfo = 'An error occured[2]: ' + reason - // process.stdout.write('Signature Reponse ' + signatureInfo + '\n') - reject(signatureInfo) - }) - }) -} - -export const hardwareDoClaimAllGas = (net, publicKey, signingFunction) => { - return new Promise(function (resolve, reject) { - ledgerNanoSGetdoClaimAllGas(net, publicKey, ledgerNanoSCreateSignatureAsync).then((response) => { - resolve(response) - }).catch(function (reason) { - // process.stdout.write('failure hardwareDoClaimAllGas ' + reason + '\n') - // process.stdout.write('failure hardwareDoClaimAllGas ' + reason.stack + '\n') - reject(reason) - }) - }) -} - -export const hardwareDoSendAsset = (net, sendAddress, publicKey, sendAsset, signingFunction) => { - return new Promise(function (resolve, reject) { - ledgerNanoSGetdoSendAsset(net, sendAddress, sendAsset, ledgerNanoSCreateSignatureAsync, publicKey).then((response) => { - resolve(response) - }).catch(function (reason) { - // process.stdout.write('failure hardwareDoSendAsset ' + reason + '\n') - // process.stdout.write('failure hardwareDoSendAsset ' + reason.stack + '\n') - reject(reason) - }) - }) -} -export const ledgerNanoSGetdoSendAsset = (net, toAddress, assetAmounts, signingFunction, publicKey) => { - return new Promise(function (resolve, reject) { - // process.stdout.write('started ledgerNanoSGetdoSendAsset net "' + JSON.stringify(net) + '"\n') - // process.stdout.write('started ledgerNanoSGetdoSendAsset toAddress "' + JSON.stringify(toAddress) + '"\n') - // process.stdout.write('started ledgerNanoSGetdoSendAsset assetAmounts "' + JSON.stringify(assetAmounts) + '"\n') - // process.stdout.write('started ledgerNanoSGetdoSendAsset signingFunctionFn "' + (signingFunction instanceof Function) + '"\n') - // process.stdout.write('interim ledgerNanoSGetdoSendAsset publicKey "' + JSON.stringify(publicKey) + '" \n') - const publicKeyEncoded = getPublicKeyEncoded(publicKey) - // process.stdout.write('interim ledgerNanoSGetdoSendAsset publicKeyEncoded "' + JSON.stringify(publicKeyEncoded) + '" \n') - const fromAccount = getAccountFromPublicKey(publicKeyEncoded) - // process.stdout.write('interim ledgerNanoSGetdoSendAsset fromAccount "' + JSON.stringify(fromAccount) + '" \n') - // process.stdout.write('interim ledgerNanoSGetdoSendAsset toAddress "' + toAddress + '" \n') - const toScriptHash = getScriptHashFromAddress(toAddress) - // process.stdout.write('interim ledgerNanoSGetdoSendAsset toScriptHash "' + toScriptHash + '" \n') + let r = response.substring(rStart, rEnd) + let sLenHex = response.substring(rEnd + 2, rEnd + 4) + let sLen = parseInt(sLenHex, 16) * 2 + let sStart = rEnd + 4 + let sEnd = sStart + sLen - return getBalance(net, fromAccount.address).then((balances) => { - // process.stdout.write('interim ledgerNanoSGetdoSendAsset getBalance assetAmounts "' + JSON.stringify(assetAmounts) + '" balances "' + JSON.stringify(balances) + '" \n') - /* eslint-disable */ - const intents = _.map(assetAmounts, (v, k) => { - return { assetId: ASSETS[k], value: v, scriptHash: toScriptHash } - }) - /* eslint-enable */ - // process.stdout.write('interim ledgerNanoSGetdoSendAsset transferTransaction \n') - // process.stdout.write('interim ledgerNanoSGetdoSendAsset create.contract publicKeyEncoded "' + JSON.stringify(fromAccount.publicKeyEncoded) + '"\n') - // process.stdout.write('interim ledgerNanoSGetdoSendAsset create.contract balances "' + JSON.stringify(balances) + '"\n') - // process.stdout.write('interim ledgerNanoSGetdoSendAsset create.contract intents "' + JSON.stringify(intents) + '"\n') - // process.stdout.write('interim ledgerNanoSGetdoSendAsset create.contract src "' + create.contract + '" \n') - // process.stdout.write('interim ledgerNanoSGetdoSendAsset create.contract src "' + JSON.stringify(create.contract) + '" \n') - // process.stdout.write('interim ledgerNanoSGetdoSendAsset calculateInputs src "' + calculateInputs + '" \n') - // process.stdout.write('interim ledgerNanoSGetdoSendAsset calculateInputs src "' + JSON.stringify(calculateInputs) + '" \n') - - const unsignedTx = ContractTx(fromAccount.publicKeyEncoded, balances, intents) - // process.stdout.write('interim ledgerNanoSGetdoSendAsset serializeTransaction unsignedTx "' + JSON.stringify(unsignedTx) + '" \n') - const txData = serializeTransaction(unsignedTx) - // process.stdout.write('interim ledgerNanoSGetdoSendAsset txData "' + txData + '" \n') - signingFunction(txData).then((sign) => { - // process.stdout.write('interim ledgerNanoSGetdoSendAsset sign1 "' + JSON.stringify(sign) + '" \n') - // process.stdout.write('interim ledgerNanoSGetdoSendAsset sign2 "' + JSON.stringify(sign) + '" \n') - // process.stdout.write('interim ledgerNanoSGetdoSendAsset sign account "' + JSON.stringify(fromAccount) + '" \n') - // process.stdout.write('interim ledgerNanoSGetdoSendAsset sign account.publicKeyEncoded "' + fromAccount.publicKeyEncoded + '" \n') - // process.stdout.write('interim ledgerNanoSGetdoSendAsset sign Ledger "' + sign + '" \n') - const txRawData = addContract(txData, sign, fromAccount.publicKeyEncoded) - queryRPC(net, 'sendrawtransaction', [txRawData], 4).then((response) => { - // process.stdout.write('interim ledgerNanoSGetdoSendAsset sendrawtransaction "' + JSON.stringify(response) + '" \n') - resolve(response) - }).catch(function (reason) { - // process.stdout.write('failure ledgerNanoSGetdoSendAsset1 ' + reason + '\n') - // process.stdout.write('failure ledgerNanoSGetdoSendAsset1 ' + reason.stack + '\n') - reject(reason) - }) - }).catch(function (reason) { - // process.stdout.write('failure ledgerNanoSGetdoSendAsset2 ' + reason + '\n') - // process.stdout.write('failure ledgerNanoSGetdoSendAsset2 ' + reason.stack + '\n') - reject(reason) - }) - }).catch(function (reason) { - // process.stdout.write('failure ledgerNanoSGetdoSendAsset4 ' + reason + '\n') - // process.stdout.write('failure ledgerNanoSGetdoSendAsset4 ' + reason.stack + '\n') - reject(reason) - }) - }) -} + while ((response.substring(sStart, sStart + 2) === '00') && ((sEnd - sStart) > 64)) { + sStart += 2 + } -export const ledgerNanoSGetdoClaimAllGas = (net, publicKey, signingFunction) => { - return new Promise(function (resolve, reject) { - // process.stdout.write('started ledgerNanoSGetdoClaimAllGas signingFunction "' + JSON.stringify(signingFunction) + '"\n') - const apiEndpoint = getAPIEndpoint(net) - const publicKeyEncoded = getPublicKeyEncoded(publicKey) - // process.stdout.write('interim ledgerNanoSGetdoClaimAllGas publicKeyEncoded "' + JSON.stringify(publicKeyEncoded) + '" \n') - const fromAccount = getAccountFromPublicKey(publicKeyEncoded) - // process.stdout.write('interim ledgerNanoSGetdoClaimAllGas fromAccount "' + JSON.stringify(fromAccount) + '" \n') - // process.stdout.write('interim ledgerNanoSGetdoClaimAllGas sign fromAccount "' + JSON.stringify(fromAccount) + '" \n') - // TODO: when fully working replace this with mainnet/testnet switch - return axios.get(apiEndpoint + '/v2/address/claims/' + fromAccount.address).then((response) => { - // process.stdout.write('interim ledgerNanoSGetdoClaimAllGas sign signingFunction "' + (signingFunction instanceof Function) + '" \n') - const txData = serializeTransaction(create.claim(fromAccount.publicKeyEncoded, response.data)) - // process.stdout.write('interim ledgerNanoSGetdoClaimAllGas txData "' + txData + '" \n') - signingFunction(txData).then((sign) => { - // process.stdout.write('interim ledgerNanoSGetdoClaimAllGas sign fromAccount "' + JSON.stringify(fromAccount) + '" \n') - // process.stdout.write('interim ledgerNanoSGetdoClaimAllGas sign fromAccount.publicKeyEncoded "' + fromAccount.publicKeyEncoded + '" \n') - // process.stdout.write('interim ledgerNanoSGetdoClaimAllGas sign Ledger "' + sign + '" \n') - const txRawData = addContract(txData, sign, fromAccount.publicKeyEncoded) - queryRPC(net, 'sendrawtransaction', [txRawData], 4).then((response) => { - resolve(response) - }).catch(function (reason) { - // process.stdout.write('failure ledgerNanoSGetdoClaimAllGas ' + reason + '\n') - reject(reason) - }) - }).catch(function (reason) { - // process.stdout.write('failure ledgerNanoSGetdoClaimAllGas ' + reason + '\n') - reject(reason) - }) - }).catch(function (reason) { - // process.stdout.write('failure ledgerNanoSGetdoClaimAllGas ' + reason + '\n') - reject(reason) - }) - }) -} + let s = response.substring(sStart, sEnd) -export const ContractTx = (publicKey, balances, intents, override = {}) => { - // process.stdout.write('ContractTx publicKey ' + JSON.stringify(publicKey) + '\n') - const tx = Object.assign({ - type: 128, - version: CURRENT_VERSION, - scripts: [] - }, override) - // process.stdout.write('ContractTx tx ' + JSON.stringify(tx) + '\n') - const attributes = [] - // process.stdout.write('ContractTx attributes ' + JSON.stringify(attributes) + '\n') - let { inputs, change } = calculateInputs(publicKey, balances, intents) - return Object.assign(tx, { inputs, attributes, outputs: intents.concat(change) }, override) -} - -const calculateInputs = (publicKey, balances, intents, gasCost = 0) => { - // process.stdout.write('calculateInputs publicKey ' + JSON.stringify(publicKey) + '\n') - // We will work in integers here to be more accurate. - // As assets are stored as Fixed8, we just multiple everything by 10e8 and round off to get integers. - const requiredAssets = intents.reduce((assets, intent) => { - const fixed8Value = Math.round(intent.value * 100000000) - assets[intent.assetId] ? assets[intent.assetId] += fixed8Value : assets[intent.assetId] = fixed8Value - return assets - }, {}) + while (r.length < 64) { + r = '00' + r + } - // process.stdout.write('calculateInputs requiredAssets ' + JSON.stringify(requiredAssets) + '\n') + while (s.length < 64) { + s = '00' + s + } - // Add GAS cost in - if (gasCost > 0) { - const fixed8GasCost = gasCost * 100000000 - requiredAssets[ASSETS.GAS] ? requiredAssets[ASSETS.GAS] += fixed8GasCost : requiredAssets[ASSETS.GAS] = fixed8GasCost - } - let change = [] - const inputs = Object.keys(requiredAssets).map((assetId) => { - const requiredAmt = requiredAssets[assetId] - const assetBalance = balances[ASSETS[assetId]] - // process.stdout.write('calculateInputs assetId ' + JSON.stringify(assetId) + '\n') - // process.stdout.write('calculateInputs ASSETS[assetId] ' + JSON.stringify(ASSETS[assetId]) + '\n') - // process.stdout.write('calculateInputs balances[ASSETS[assetId]] ' + JSON.stringify(balances[ASSETS[assetId]]) + '\n') + const signature = r + s + const script = createSignatureScript(publicKeyEncoded) - if (assetBalance.balance * 100000000 < requiredAmt) throw new Error(`Insufficient ${ASSETS[assetId]}! Need ${requiredAmt / 100000000} but only found ${assetBalance.balance}`) - // Ascending order sort - assetBalance.unspent.sort((a, b) => a.value - b.value) - let selectedInputs = 0 - let selectedAmt = 0 - // Selected min inputs to satisfy outputs - while (selectedAmt < requiredAmt) { - selectedInputs += 1 - selectedAmt += Math.round(assetBalance.unspent[selectedInputs - 1].value * 100000000) + // txData + '01' (sign num) + '41' (sign struct len) + '40' (sign data len) + signature + '23' (Contract data len) + script + return `${txData}014140${signature}23${script}` } - // Construct change output - if (selectedAmt > requiredAmt) { - change.push({ - assetId, - value: (selectedAmt - requiredAmt) / 100000000, - scriptHash: getScriptHashFromPublicKey(publicKey) - }) - } - // Format inputs - return assetBalance.unspent.slice(0, selectedInputs).map((input) => { - return { prevHash: input.txid, prevIndex: input.index } - }) - }).reduce((prev, curr) => prev.concat(curr), []) - return { inputs, change } + } } diff --git a/app/modules/account.js b/app/modules/account.js index f01346fc2..5b226bf01 100644 --- a/app/modules/account.js +++ b/app/modules/account.js @@ -102,49 +102,48 @@ export const loginWithPrivateKey = (wif: string, history: Object, route?: RouteT // Reducer that manages account state (account now = private key) export const ledgerNanoSGetInfoAsync = () => async (dispatch: DispatchType) => { dispatch(hardwareDeviceInfo('Looking for USB Devices')) - // console.log('started ledgerNanoSGetInfoAsync') let [err, result] = await asyncWrap(commNode.list_async()) - if (err) return dispatch(hardwareDeviceInfo(`Finding USB Error: ${err}`)) + if (err) return dispatch(hardwareDeviceInfo(`Finding USB Error: ${err}. Connect device and try again.`)) if (result.length === 0) { - // console.log('getLedgerDeviceInfo "No device found"') - return dispatch(hardwareDeviceInfo('USB Failure: No device found')) + dispatch(hardwarePublicKeyInfo('')) + return dispatch(hardwareDeviceInfo('USB Failure: No device found. Connect device and try again.')) } else { let [err, comm] = await asyncWrap(commNode.create_async()) - if (err) return dispatch(hardwareDeviceInfo(`Finding USB Error: ${err}`)) + if (err) { + dispatch(hardwarePublicKeyInfo('')) + return dispatch(hardwareDeviceInfo(`Finding USB Error: ${err}. Connect device and try again.`)) + } const deviceInfo = comm.device.getDeviceInfo() - // process.stdout.write('getLedgerDeviceInfo success "' + ledgerNanoSGetDeviceInfo + '"\n') comm.device.close() dispatch(hardwareDeviceInfo(`Found USB ${deviceInfo.manufacturer} ${deviceInfo.product}`)) } - // process.stdout.write('success ledgerNanoSGetInfoAsync \n') [err, result] = await asyncWrap(commNode.list_async()) if (result.length === 0) { - // process.stdout.write('getPublicKeyInfo "No device found"\n') - dispatch(hardwarePublicKeyInfo('App Failure: No device found')) + return dispatch(hardwarePublicKeyInfo('Hardware Device Error. Login to NEO App and try again')) } else { let [err, comm] = await asyncWrap(commNode.create_async()) - if (err) return dispatch(hardwarePublicKeyInfo(`Public Key Comm Init Error: ${err}`)) + if (err) { + console.log(`Public Key Comm Init Error: ${err}`) + return dispatch(hardwarePublicKeyInfo('Hardware Device Error. Login to NEO App and try again')) + } let message = Buffer.from(`8004000000${BIP44_PATH}`, 'hex') const validStatus = [0x9000] let [error, response] = await asyncWrap(comm.exchange(message.toString('hex'), validStatus)) if (error) { comm.device.close() // NOTE: do we need this close here - what about the other errors that do not have it at the moment - // process.stdout.write('getPublicKeyInfo comm.exchange error reason ' + err + '\n') if (error === 'Invalid status 28160') { return dispatch(hardwarePublicKeyInfo('NEO App does not appear to be open, request for private key returned error 28160.')) } else { - return dispatch(hardwarePublicKeyInfo(`Public Key Comm Messaging Error: ${error}`)) + console.log(`Public Key Comm Messaging Error: ${error}`) + return dispatch(hardwarePublicKeyInfo('Hardware Device Error. Login to NEO App and try again')) } } comm.device.close() - // process.stdout.write('getPublicKey success "' + ledgerNanoSGetPublicKey + '"\n') - // process.stdout.write('getPublicKeyInfo success "' + ledgerNanoSGetPublicKeyInfo + '"\n') dispatch(hardwarePublicKey(response.substring(0, 130))) - return dispatch(hardwarePublicKeyInfo('App Found, Public Key Available')) + return dispatch(hardwarePublicKeyInfo('Success. NEO App Found on Hardware Device. Click Button Above to Login')) } - // process.stdout.write('success getPublicKeyInfo \n') } const initialState = { @@ -163,8 +162,6 @@ const initialState = { export default (state: Object = initialState, action: Object) => { switch (action.type) { case LOGIN: - // process.stdout.write('interim action "' + JSON.stringify(action) + '"\n') - // process.stdout.write('interim action.wif "' + JSON.stringify(action.wif) + '" signingFunction "' + JSON.stringify(action.signingFunction) + '"\n') let loadAccount: Object | number try { if (action.signingFunction) { @@ -174,11 +171,9 @@ export default (state: Object = initialState, action: Object) => { loadAccount = getAccountFromWIFKey(action.wif) } } catch (e) { - // process.stdout.write('error loadAccount "' + e + '" "' + e.message + '" \n') console.log(e.stack) loadAccount = -1 } - // process.stdout.write('interim loadAccount "' + JSON.stringify(loadAccount) + '" \n') if (typeof loadAccount !== 'object') { return { ...state, diff --git a/app/modules/claim.js b/app/modules/claim.js index b38e65aca..742e4a0cb 100644 --- a/app/modules/claim.js +++ b/app/modules/claim.js @@ -1,6 +1,5 @@ // @flow -import { doClaimAllGas, doSendAsset, getClaimAmounts } from 'neon-js' -import { hardwareDoSendAsset, hardwareDoClaimAllGas } from '../ledger/ledgerNanoS.js' +import { doClaimAllGas, doSendAsset, getClaimAmounts, hardwareDoSendAsset, hardwareDoClaimAllGas } from 'neon-js' import { sendEvent, clearTransactionEvent } from '../modules/transactions' import { log } from '../util/Logs' import { ASSETS } from '../core/constants' @@ -52,6 +51,7 @@ export const doClaimNotify = () => (dispatch: DispatchType, getState: GetStateTy let claimGasFn if (isHardwareClaim) { + dispatch(sendEvent(true, 'Sign transaction 2 of 2 to claim Gas on your hardware device (claiming Gas)')) claimGasFn = () => hardwareDoClaimAllGas(net, publicKey, signingFunction) } else { claimGasFn = () => doClaimAllGas(net, wif) @@ -90,6 +90,7 @@ export const doGasClaim = () => (dispatch: DispatchType, getState: GetStateType) let sendAssetFn if (isHardwareClaim) { + dispatch(sendEvent(true, 'Sign transaction 1 of 2 to claim Gas on your hardware device (sending Neo to yourself)')) sendAssetFn = () => hardwareDoSendAsset(net, address, publicKey, { [ASSETS.NEO]: neo }, signingFunction) } else { sendAssetFn = () => doSendAsset(net, address, wif, { [ASSETS.NEO]: neo }) diff --git a/app/modules/transactions.js b/app/modules/transactions.js index 2db1c0d51..e5b056869 100644 --- a/app/modules/transactions.js +++ b/app/modules/transactions.js @@ -1,10 +1,9 @@ // @flow import { ASSETS_LABELS, ASSETS } from '../core/constants' import { validateTransactionBeforeSending } from '../core/wallet' -import { getTransactionHistory, doSendAsset } from 'neon-js' +import { getTransactionHistory, doSendAsset, hardwareDoSendAsset } from 'neon-js' import { setTransactionHistory } from '../modules/wallet' import { log } from '../util/Logs' -import { hardwareDoSendAsset } from '../ledger/ledgerNanoS.js' // Constants export const SEND_TRANSACTION = 'SEND_TRANSACTION' @@ -79,6 +78,7 @@ export const sendTransaction = (sendAddress: string, sendAmount: string) => (dis let sendAssetFn if (isHardwareSend) { + dispatch(sendEvent(true, 'Please sign the transaction on your hardware device')) sendAssetFn = () => hardwareDoSendAsset(net, sendAddress, publicKey, sendAsset, signingFunction) } else { sendAssetFn = () => doSendAsset(net, sendAddress, wif, sendAsset) diff --git a/app/styles/main.scss b/app/styles/main.scss index f462ceb86..c351a246a 100644 --- a/app/styles/main.scss +++ b/app/styles/main.scss @@ -302,6 +302,9 @@ $box-margin: 10px; margin-left: -25px; margin-top: 10px; } + #ledger_device_info { + margin: 10px 0; + } } #network{ diff --git a/package.json b/package.json index 9d35f3512..f202cb88e 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "isomorphic-fetch": "^2.2.1", "js-scrypt": "^0.2.0", "lodash": "^4.17.4", - "neon-js": "git+https://github.com/CityOfZion/neon-js.git#fdff0cef8e2e23879f6227126e257a88602fca96", + "neon-js": "git+https://github.com/CityOfZion/neon-js.git#02bdca256b5a112ab6d657401be5951ecf9e54f5", "node-hid": "^0.5.7", "qrcode": "^0.8.2", "react": "^15.0.2", diff --git a/yarn.lock b/yarn.lock index 532421264..b34d0c1b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5968,9 +5968,9 @@ negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" -"neon-js@git+https://github.com/CityOfZion/neon-js.git#fdff0cef8e2e23879f6227126e257a88602fca96": +"neon-js@git+https://github.com/dvdschwrtz/neon-js.git#hardware-changes": version "1.1.0" - resolved "git+https://github.com/CityOfZion/neon-js.git#fdff0cef8e2e23879f6227126e257a88602fca96" + resolved "git+https://github.com/dvdschwrtz/neon-js.git#004b9932e8b5da2f7f04ba162b34858abf6ddc4c" dependencies: axios "^0.16.2" base-x "^3.0.2"