@trezor/connect
Version:
High-level javascript interface for Trezor hardware wallet.
348 lines • 11.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.signTx = exports.validate = void 0;
const tslib_1 = require("tslib");
const bs58_1 = tslib_1.__importDefault(require("bs58"));
const constants_1 = require("../../constants");
const binaryToDecimal = (bignum, minDigits = 1) => {
const result = Array(minDigits).fill('0'.charCodeAt(0));
for (let i = bignum.length - 1; i >= 0; --i) {
let carry = bignum[i];
for (let j = 0; j < result.length; ++j) {
const x = ((result[j] - '0'.charCodeAt(0)) << 8) + carry;
result[j] = '0'.charCodeAt(0) + (x % 10);
carry = (x / 10) | 0;
}
while (carry) {
result.push('0'.charCodeAt(0) + (carry % 10));
carry = (carry / 10) | 0;
}
}
result.reverse();
return String.fromCharCode(...result);
};
const serialize = (s) => {
if (typeof s !== 'string') {
throw constants_1.ERRORS.TypedError('Method_InvalidParameter', `Eos serialization error, "${typeof s}" should be a string`);
}
const charToSymbol = (c) => {
if (c >= 'a'.charCodeAt(0) && c <= 'z'.charCodeAt(0)) {
return c - 'a'.charCodeAt(0) + 6;
}
if (c >= '1'.charCodeAt(0) && c <= '5'.charCodeAt(0)) {
return c - '1'.charCodeAt(0) + 1;
}
return 0;
};
const a = new Uint8Array(8);
let bit = 63;
for (let i = 0; i < s.length; ++i) {
let c = charToSymbol(s.charCodeAt(i));
if (bit < 5) {
c <<= 1;
}
for (let j = 4; j >= 0; --j) {
if (bit >= 0) {
a[Math.floor(bit / 8)] |= ((c >> j) & 1) << bit % 8;
--bit;
}
}
}
return binaryToDecimal(a);
};
const parseQuantity = (s) => {
if (typeof s !== 'string') {
throw constants_1.ERRORS.TypedError('Method_InvalidParameter', `Eos serialization error. Expected string containing asset, got: ${typeof s}`);
}
s = s.trim();
let pos = 0;
let amount = '';
let precision = 0;
if (s[pos] === '-') {
amount += '-';
++pos;
}
let foundDigit = false;
while (pos < s.length &&
s.charCodeAt(pos) >= '0'.charCodeAt(0) &&
s.charCodeAt(pos) <= '9'.charCodeAt(0)) {
foundDigit = true;
amount += s[pos];
++pos;
}
if (!foundDigit) {
throw constants_1.ERRORS.TypedError('Method_InvalidParameter', 'Eos serialization error. Asset must begin with a number');
}
if (s[pos] === '.') {
++pos;
while (pos < s.length &&
s.charCodeAt(pos) >= '0'.charCodeAt(0) &&
s.charCodeAt(pos) <= '9'.charCodeAt(0)) {
amount += s[pos];
++precision;
++pos;
}
}
const symbol = s.substring(pos).trim();
const a = [precision & 0xff];
for (let i = 0; i < symbol.length; ++i) {
a.push(symbol.charCodeAt(i));
}
while (a.length < 8) {
a.push(0);
}
return {
amount,
symbol: binaryToDecimal(a.slice(0, 8)),
};
};
const parseAuth = (a) => {
const keyToBuffer = (pk) => {
const len = pk.indexOf('EOS') === 0 ? 3 : 7;
const key = Buffer.from(bs58_1.default.decode(pk.substring(len)));
return {
type: 0,
key: key.subarray(0, key.length - 4).toString('hex'),
};
};
return {
threshold: a.threshold,
keys: a.keys.map(k => ({
weight: k.weight,
...keyToBuffer(k.key),
})),
accounts: a.accounts.map(acc => ({
weight: acc.weight,
account: {
actor: serialize(acc.permission.actor),
permission: serialize(acc.permission.permission),
},
})),
waits: a.waits,
};
};
const parseDate = (d) => {
if (typeof d !== 'string') {
throw constants_1.ERRORS.TypedError('Method_InvalidParameter', 'Eos serialization error. Header.expiration should be string or number');
}
if (d.substring(d.length - 1, d.length) !== 'Z') {
d += 'Z';
}
return Date.parse(d) / 1000;
};
const parseAck = (action) => {
switch (action.name) {
case 'transfer':
return {
transfer: {
sender: serialize(action.data.from),
receiver: serialize(action.data.to),
quantity: parseQuantity(action.data.quantity),
memo: action.data.memo,
},
};
case 'delegatebw':
return {
delegate: {
sender: serialize(action.data.from),
receiver: serialize(action.data.receiver),
net_quantity: parseQuantity(action.data.stake_net_quantity),
cpu_quantity: parseQuantity(action.data.stake_cpu_quantity),
transfer: action.data.transfer,
},
};
case 'undelegatebw':
return {
undelegate: {
sender: serialize(action.data.from),
receiver: serialize(action.data.receiver),
net_quantity: parseQuantity(action.data.unstake_net_quantity),
cpu_quantity: parseQuantity(action.data.unstake_cpu_quantity),
},
};
case 'buyram':
return {
buy_ram: {
payer: serialize(action.data.payer),
receiver: serialize(action.data.receiver),
quantity: parseQuantity(action.data.quant),
},
};
case 'buyrambytes':
return {
buy_ram_bytes: {
payer: serialize(action.data.payer),
receiver: serialize(action.data.receiver),
bytes: action.data.bytes,
},
};
case 'sellram':
return {
sell_ram: {
account: serialize(action.data.account),
bytes: action.data.bytes,
},
};
case 'voteproducer':
return {
vote_producer: {
voter: serialize(action.data.voter),
proxy: serialize(action.data.proxy),
producers: action.data.producers.map(p => serialize(p)),
},
};
case 'refund':
return {
refund: {
owner: serialize(action.data.owner),
},
};
case 'updateauth':
return {
update_auth: {
account: serialize(action.data.account),
permission: serialize(action.data.permission),
parent: serialize(action.data.parent),
auth: parseAuth(action.data.auth),
},
};
case 'deleteauth':
return {
delete_auth: {
account: serialize(action.data.account),
permission: serialize(action.data.permission),
},
};
case 'linkauth':
return {
link_auth: {
account: serialize(action.data.account),
code: serialize(action.data.code),
type: serialize(action.data.type),
requirement: serialize(action.data.requirement),
},
};
case 'unlinkauth':
return {
unlink_auth: {
account: serialize(action.data.account),
code: serialize(action.data.code),
type: serialize(action.data.type),
},
};
case 'newaccount':
return {
new_account: {
creator: serialize(action.data.creator),
name: serialize(action.data.name),
owner: parseAuth(action.data.owner),
active: parseAuth(action.data.active),
},
};
default:
return null;
}
};
const parseUnknown = (action) => {
if (typeof action.data !== 'string')
return null;
return {
unknown: {
data_size: action.data.length / 2,
data_chunk: action.data,
},
};
};
const parseCommon = (action) => ({
account: serialize(action.account),
name: serialize(action.name),
authorization: action.authorization.map(a => ({
actor: serialize(a.actor),
permission: serialize(a.permission),
})),
});
const parseAction = (action) => {
const ack = parseAck(action) || parseUnknown(action);
return {
common: parseCommon(action),
...ack,
};
};
const validate = (tx) => {
const header = {
expiration: typeof tx.header.expiration === 'number'
? tx.header.expiration
: parseDate(tx.header.expiration),
ref_block_num: tx.header.refBlockNum,
ref_block_prefix: tx.header.refBlockPrefix,
max_net_usage_words: tx.header.maxNetUsageWords,
max_cpu_usage_ms: tx.header.maxCpuUsageMs,
delay_sec: tx.header.delaySec,
};
const ack = [];
tx.actions.forEach(action => {
ack.push(parseAction(action));
});
return {
chain_id: tx.chainId,
header,
ack,
};
};
exports.validate = validate;
const CHUNK_SIZE = 2048;
const getDataChunk = (data, offset) => {
if (!data || offset < 0 || data.length < offset)
return '';
const o = offset > 0 ? data.length - offset * 2 : 0;
return data.substring(o, o + CHUNK_SIZE * 2);
};
const processTxRequest = async (typedCall, message, actions, index) => {
const action = actions[index];
const lastOp = index + 1 >= actions.length;
let ack;
const requestedDataSize = message.data_size;
if (action.unknown) {
const { unknown } = action;
const offset = typeof requestedDataSize === 'number' ? requestedDataSize : 0;
const data_chunk = getDataChunk(unknown.data_chunk, offset);
const params = {
common: action.common,
unknown: {
data_size: unknown.data_size,
data_chunk,
},
};
const sent = offset > 0 ? unknown.data_size - offset + CHUNK_SIZE : CHUNK_SIZE;
const lastChunk = sent >= unknown.data_size;
if (lastOp && lastChunk) {
const response = await typedCall('EosTxActionAck', 'EosSignedTx', params);
return response.message;
}
ack = await typedCall('EosTxActionAck', 'EosTxActionRequest', params);
if (lastChunk) {
index++;
}
}
else {
if (lastOp) {
const response = await typedCall('EosTxActionAck', 'EosSignedTx', action);
return response.message;
}
ack = await typedCall('EosTxActionAck', 'EosTxActionRequest', action);
index++;
}
return processTxRequest(typedCall, ack.message, actions, index);
};
const signTx = async (typedCall, address_n, chain_id, header, actions, chunkify) => {
const response = await typedCall('EosSignTx', 'EosTxActionRequest', {
address_n,
chain_id,
header,
num_actions: actions.length,
chunkify,
});
return processTxRequest(typedCall, response.message, actions, 0);
};
exports.signTx = signTx;
//# sourceMappingURL=eosSignTx.js.map