284 lines
8.5 KiB
JavaScript
284 lines
8.5 KiB
JavaScript
/* prettier-ignore */
|
|
|
|
/********************************************************************************
|
|
* Ledger Communication toolkit
|
|
* (c) 2016 Ledger
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
********************************************************************************/
|
|
|
|
'use strict';
|
|
|
|
// MEW - Require u2f instead of expecting it in global scope
|
|
var u2f = require('./u2f-api');
|
|
|
|
var LedgerEth = function(comm) {
|
|
this.comm = comm;
|
|
};
|
|
|
|
//MEW - Add error handling method
|
|
LedgerEth.prototype.getError = function(err) {
|
|
return err.errorCode
|
|
? u2f.getErrorByCode(err.errorCode)
|
|
: err;
|
|
};
|
|
|
|
LedgerEth.splitPath = function(path) {
|
|
var result = [];
|
|
var components = path.split('/');
|
|
components.forEach(function(element, index) {
|
|
var number = parseInt(element, 10);
|
|
if (isNaN(number)) {
|
|
return;
|
|
}
|
|
if (element.length > 1 && element[element.length - 1] == "'") {
|
|
number += 0x80000000;
|
|
}
|
|
result.push(number);
|
|
});
|
|
return result;
|
|
};
|
|
|
|
// callback is function(response, error)
|
|
LedgerEth.prototype.getAddress = function(
|
|
path,
|
|
callback,
|
|
boolDisplay,
|
|
boolChaincode
|
|
) {
|
|
var splitPath = LedgerEth.splitPath(path);
|
|
var buffer = new Buffer(5 + 1 + splitPath.length * 4);
|
|
buffer[0] = 0xe0;
|
|
buffer[1] = 0x02;
|
|
buffer[2] = boolDisplay ? 0x01 : 0x00;
|
|
buffer[3] = boolChaincode ? 0x01 : 0x00;
|
|
buffer[4] = 1 + splitPath.length * 4;
|
|
buffer[5] = splitPath.length;
|
|
splitPath.forEach(function(element, index) {
|
|
buffer.writeUInt32BE(element, 6 + 4 * index);
|
|
});
|
|
var self = this;
|
|
var localCallback = function(response, error) {
|
|
if (typeof error != 'undefined') {
|
|
callback(undefined, error);
|
|
} else {
|
|
var result = {};
|
|
response = new Buffer(response, 'hex');
|
|
var sw = response.readUInt16BE(response.length - 2);
|
|
if (sw != 0x9000) {
|
|
callback(
|
|
undefined,
|
|
'Invalid status ' +
|
|
sw.toString(16) +
|
|
'. Check to make sure the right application is selected ?'
|
|
);
|
|
return;
|
|
}
|
|
var publicKeyLength = response[0];
|
|
var addressLength = response[1 + publicKeyLength];
|
|
result['publicKey'] = response
|
|
.slice(1, 1 + publicKeyLength)
|
|
.toString('hex');
|
|
result['address'] =
|
|
'0x' +
|
|
response
|
|
.slice(
|
|
1 + publicKeyLength + 1,
|
|
1 + publicKeyLength + 1 + addressLength
|
|
)
|
|
.toString('ascii');
|
|
if (boolChaincode) {
|
|
result['chainCode'] = response
|
|
.slice(
|
|
1 + publicKeyLength + 1 + addressLength,
|
|
1 + publicKeyLength + 1 + addressLength + 32
|
|
)
|
|
.toString('hex');
|
|
}
|
|
callback(result);
|
|
}
|
|
};
|
|
this.comm.exchange(buffer.toString('hex'), localCallback);
|
|
};
|
|
|
|
// callback is function(response, error)
|
|
LedgerEth.prototype.signTransaction = function(path, rawTxHex, callback) {
|
|
var splitPath = LedgerEth.splitPath(path);
|
|
var offset = 0;
|
|
var rawTx = new Buffer(rawTxHex, 'hex');
|
|
var apdus = [];
|
|
while (offset != rawTx.length) {
|
|
var maxChunkSize = offset == 0 ? 150 - 1 - splitPath.length * 4 : 150;
|
|
var chunkSize =
|
|
offset + maxChunkSize > rawTx.length
|
|
? rawTx.length - offset
|
|
: maxChunkSize;
|
|
var buffer = new Buffer(
|
|
offset == 0 ? 5 + 1 + splitPath.length * 4 + chunkSize : 5 + chunkSize
|
|
);
|
|
buffer[0] = 0xe0;
|
|
buffer[1] = 0x04;
|
|
buffer[2] = offset == 0 ? 0x00 : 0x80;
|
|
buffer[3] = 0x00;
|
|
buffer[4] = offset == 0 ? 1 + splitPath.length * 4 + chunkSize : chunkSize;
|
|
if (offset == 0) {
|
|
buffer[5] = splitPath.length;
|
|
splitPath.forEach(function(element, index) {
|
|
buffer.writeUInt32BE(element, 6 + 4 * index);
|
|
});
|
|
rawTx.copy(buffer, 6 + 4 * splitPath.length, offset, offset + chunkSize);
|
|
} else {
|
|
rawTx.copy(buffer, 5, offset, offset + chunkSize);
|
|
}
|
|
apdus.push(buffer.toString('hex'));
|
|
offset += chunkSize;
|
|
}
|
|
var self = this;
|
|
var localCallback = function(response, error) {
|
|
if (typeof error != 'undefined') {
|
|
callback(undefined, error);
|
|
} else {
|
|
response = new Buffer(response, 'hex');
|
|
var sw = response.readUInt16BE(response.length - 2);
|
|
if (sw != 0x9000) {
|
|
callback(
|
|
undefined,
|
|
'Invalid status ' +
|
|
sw.toString(16) +
|
|
'. Check to make sure contract data is on ?'
|
|
);
|
|
return;
|
|
}
|
|
if (apdus.length == 0) {
|
|
var result = {};
|
|
result['v'] = response.slice(0, 1).toString('hex');
|
|
result['r'] = response.slice(1, 1 + 32).toString('hex');
|
|
result['s'] = response.slice(1 + 32, 1 + 32 + 32).toString('hex');
|
|
callback(result);
|
|
} else {
|
|
self.comm.exchange(apdus.shift(), localCallback);
|
|
}
|
|
}
|
|
};
|
|
self.comm.exchange(apdus.shift(), localCallback);
|
|
};
|
|
|
|
// callback is function(response, error)
|
|
LedgerEth.prototype.getAppConfiguration = function(callback) {
|
|
var buffer = new Buffer(5);
|
|
buffer[0] = 0xe0;
|
|
buffer[1] = 0x06;
|
|
buffer[2] = 0x00;
|
|
buffer[3] = 0x00;
|
|
buffer[4] = 0x00;
|
|
var localCallback = function(response, error) {
|
|
if (typeof error != 'undefined') {
|
|
callback(undefined, error);
|
|
} else {
|
|
response = new Buffer(response, 'hex');
|
|
var result = {};
|
|
var sw = response.readUInt16BE(response.length - 2);
|
|
if (sw != 0x9000) {
|
|
callback(
|
|
undefined,
|
|
'Invalid status ' +
|
|
sw.toString(16) +
|
|
'. Check to make sure the right application is selected ?'
|
|
);
|
|
return;
|
|
}
|
|
result['arbitraryDataEnabled'] = response[0] & 0x01;
|
|
result['version'] =
|
|
'' + response[1] + '.' + response[2] + '.' + response[3];
|
|
callback(result);
|
|
}
|
|
};
|
|
this.comm.exchange(buffer.toString('hex'), localCallback);
|
|
};
|
|
|
|
LedgerEth.prototype.signPersonalMessage_async = function(
|
|
path,
|
|
messageHex,
|
|
callback
|
|
) {
|
|
var splitPath = LedgerEth.splitPath(path);
|
|
var offset = 0;
|
|
var message = new Buffer(messageHex, 'hex');
|
|
var apdus = [];
|
|
var response = [];
|
|
var self = this;
|
|
while (offset != message.length) {
|
|
var maxChunkSize = offset == 0 ? 150 - 1 - splitPath.length * 4 - 4 : 150;
|
|
var chunkSize =
|
|
offset + maxChunkSize > message.length
|
|
? message.length - offset
|
|
: maxChunkSize;
|
|
var buffer = new Buffer(
|
|
offset == 0 ? 5 + 1 + splitPath.length * 4 + 4 + chunkSize : 5 + chunkSize
|
|
);
|
|
buffer[0] = 0xe0;
|
|
buffer[1] = 0x08;
|
|
buffer[2] = offset == 0 ? 0x00 : 0x80;
|
|
buffer[3] = 0x00;
|
|
buffer[4] =
|
|
offset == 0 ? 1 + splitPath.length * 4 + 4 + chunkSize : chunkSize;
|
|
if (offset == 0) {
|
|
buffer[5] = splitPath.length;
|
|
splitPath.forEach(function(element, index) {
|
|
buffer.writeUInt32BE(element, 6 + 4 * index);
|
|
});
|
|
buffer.writeUInt32BE(message.length, 6 + 4 * splitPath.length);
|
|
message.copy(
|
|
buffer,
|
|
6 + 4 * splitPath.length + 4,
|
|
offset,
|
|
offset + chunkSize
|
|
);
|
|
} else {
|
|
message.copy(buffer, 5, offset, offset + chunkSize);
|
|
}
|
|
apdus.push(buffer.toString('hex'));
|
|
offset += chunkSize;
|
|
}
|
|
var self = this;
|
|
var localCallback = function(response, error) {
|
|
if (typeof error != 'undefined') {
|
|
callback(undefined, error);
|
|
} else {
|
|
response = new Buffer(response, 'hex');
|
|
var sw = response.readUInt16BE(response.length - 2);
|
|
if (sw != 0x9000) {
|
|
callback(
|
|
undefined,
|
|
'Invalid status ' +
|
|
sw.toString(16) +
|
|
'. Check to make sure the right application is selected ?'
|
|
);
|
|
return;
|
|
}
|
|
if (apdus.length == 0) {
|
|
var result = {};
|
|
result['v'] = response.slice(0, 1).toString('hex');
|
|
result['r'] = response.slice(1, 1 + 32).toString('hex');
|
|
result['s'] = response.slice(1 + 32, 1 + 32 + 32).toString('hex');
|
|
callback(result);
|
|
} else {
|
|
self.comm.exchange(apdus.shift(), localCallback);
|
|
}
|
|
}
|
|
};
|
|
self.comm.exchange(apdus.shift(), localCallback);
|
|
};
|
|
|
|
module.exports = LedgerEth;
|