gridplus-sdk
Advanced tools
Comparing version 0.7.8 to 0.7.9
{ | ||
"name": "gridplus-sdk", | ||
"version": "0.7.8", | ||
"version": "0.7.9", | ||
"description": "SDK to interact with GridPlus Lattice1 device", | ||
@@ -15,2 +15,3 @@ "scripts": { | ||
"test-eth": "mocha --timeout 180000 -R spec test/testEth.js --recursive --exit", | ||
"test-eth-msg": "mocha --timeout 180000 -R spec test/testEthMsg.js --recursive --exit", | ||
"test-btc": "mocha --timeout 180000 -R spec test/testBtc.js --recursive --exit", | ||
@@ -31,4 +32,7 @@ "test-wallet-jobs": "mocha --timeout 300000 -R spec test/testWalletJobs.js --recursive --exit" | ||
"buffer": "^5.6.0", | ||
"cbor": "^7.0.3", | ||
"crc-32": "^1.2.0", | ||
"elliptic": "6.5.4", | ||
"ethers": "^5.0.31", | ||
"ethers-eip712": "^0.2.0", | ||
"js-sha3": "^0.8.0", | ||
@@ -53,3 +57,2 @@ "rlp-browser": "^1.0.1", | ||
"ethereumjs-tx": "^2.1.2", | ||
"ethers": "^5.0.31", | ||
"it-each": "^0.4.0", | ||
@@ -56,0 +59,0 @@ "lodash": "^4.17.20", |
@@ -163,7 +163,8 @@ const bitwise = require('bitwise'); | ||
const { startPath, n, skipCache=true } = opts; | ||
if (startPath === undefined || n === undefined || startPath.length !== 5) { | ||
if (startPath === undefined || n === undefined) | ||
return cb('Please provide `startPath` and `n` options'); | ||
} else if (n > MAX_ADDR) { | ||
if (startPath.length < 2 || startPath.length > 5) | ||
return cb('Path must include between 2 and 5 indices'); | ||
if (n > MAX_ADDR) | ||
return cb(`You may only request ${MAX_ADDR} addresses at once.`); | ||
} | ||
@@ -174,3 +175,10 @@ if ((skipCache === false && false === isValidAssetPath(startPath)) || | ||
const payload = Buffer.alloc(1 + 32 + startPath.length * 4); | ||
const fwConstants = getFwVersionConst(this.fwVersion); | ||
let sz = 32 + 20 + 1; // walletUID + 5 u32 indices + count/flag | ||
if (fwConstants.varAddrPathSzAllowed) { | ||
sz += 1; // pathDepth | ||
} else if (startPath.length !== 5) { | ||
return cb('Your Lattice firmware only supports derivation paths with 5 indices. Please upgrade.') | ||
} | ||
const payload = Buffer.alloc(sz); | ||
let off = 0; | ||
@@ -183,4 +191,9 @@ | ||
// Build the start path (5x u32 indices) | ||
for (let i = 0; i < startPath.length; i++) { | ||
payload.writeUInt32BE(startPath[i], off); | ||
if (fwConstants.varAddrPathSzAllowed) { | ||
payload.writeUInt8(startPath.length, off); | ||
off += 1; | ||
} | ||
for (let i = 0; i < 5; i++) { | ||
if (i <= startPath.length) | ||
payload.writeUInt32BE(startPath[i], off); | ||
off += 4; | ||
@@ -192,3 +205,2 @@ } | ||
let val; | ||
const fwConstants = getFwVersionConst(this.fwVersion); | ||
if (true === fwConstants.addrFlagsAllowed) { | ||
@@ -501,2 +513,3 @@ const flag = skipCache === true ? bitwise.nibble.read(SKIP_CACHE_FLAG) : bitwise.nibble.read(0); | ||
.catch((err) => { | ||
console.log('request err', err) | ||
const isTimeout = err.code === 'ECONNABORTED' && err.errno === 'ETIME'; | ||
@@ -503,0 +516,0 @@ if (isTimeout) |
@@ -117,3 +117,59 @@ // Consistent with Lattice's IV | ||
const ethMsgProtocol = { | ||
SIGN_PERSONAL: 0, | ||
SIGN_PERSONAL: { | ||
str: 'signPersonal', | ||
enumIdx: 0, // Enum index of this protocol in Lattice firmware | ||
}, | ||
TYPED_DATA: { | ||
str: 'typedData', | ||
enumIdx: 1, | ||
rawDataMaxLen: 1629, // Max size of raw data payload in bytes | ||
typeCodes: { // Enum indices of data types in Lattice firmware | ||
'bytes1': 1, | ||
'bytes2': 2, | ||
'bytes3': 3, | ||
'bytes4': 4, | ||
'bytes5': 5, | ||
'bytes6': 6, | ||
'bytes7': 7, | ||
'bytes8': 8, | ||
'bytes9': 9, | ||
'bytes10': 10, | ||
'bytes11': 11, | ||
'bytes12': 12, | ||
'bytes13': 13, | ||
'bytes14': 14, | ||
'bytes15': 15, | ||
'bytes16': 16, | ||
'bytes17': 17, | ||
'bytes18': 18, | ||
'bytes19': 19, | ||
'bytes20': 20, | ||
'bytes21': 21, | ||
'bytes22': 22, | ||
'bytes23': 23, | ||
'bytes24': 24, | ||
'bytes25': 25, | ||
'bytes26': 27, | ||
'bytes27': 28, | ||
'bytes28': 29, | ||
'bytes29': 30, | ||
'bytes30': 31, | ||
'bytes31': 32, | ||
'bytes32': 33, | ||
'uint8': 34, | ||
'uint16': 35, | ||
'uint32': 36, | ||
'uint64': 37, | ||
'uint256': 38, | ||
// 'int8': 39, // We do not support signed typed right now | ||
// 'int16': 40, | ||
// 'int32': 41, | ||
// 'int64': 42, | ||
// 'int256': 43, | ||
'bool': 44, | ||
'address': 45, | ||
'bytes': 47, | ||
'string': 48, | ||
} | ||
}, | ||
} | ||
@@ -204,3 +260,2 @@ | ||
const c = { | ||
reqMaxDataSz: 1152, | ||
extraDataFrameSz: 0, | ||
@@ -216,11 +271,19 @@ extraDataMaxFrames: 0, | ||
} | ||
if (v.length === 0 || !gte(v, [0, 10, 0])) { | ||
// Legacy versions (<0.10.0) | ||
c.ethMaxDataSz = c.reqMaxDataSz - 128; | ||
c.ethMaxMsgSz = c.ethMaxDataSz; | ||
c.ethMaxGasPrice = 500000000000; // 500 gwei | ||
c.addrFlagsAllowed = false; | ||
return c; | ||
} | ||
if (gte(v, [0, 10, 4])) { | ||
// Very old legacy versions do not give a version number | ||
const legacy = (v.length === 0); | ||
// V0.10.5 added the ability to use flexible address path sizes, which | ||
// changes the `getAddress` API. It also added support for EIP712 | ||
if (!legacy && gte(v, [0, 10, 5])) { | ||
c.varAddrPathSzAllowed = true; | ||
c.eip712Supported = true; | ||
} | ||
// V0.10.4 introduced the ability to send signing requests over multiple | ||
// data frames (i.e. in multiple requests) | ||
if (!legacy && gte(v, [0, 10, 4])) { | ||
c.extraDataFrameSz = 1500; // 1500 bytes per frame of extraData allowed | ||
c.extraDataMaxFrames = 1; // 1 frame of extraData allowed | ||
} | ||
// Various size constants have changed on the firmware side over time and | ||
// are captured here | ||
if (!legacy && gte(v, [0, 10, 4])) { | ||
// >=0.10.3 | ||
@@ -232,5 +295,3 @@ c.reqMaxDataSz = 1678; | ||
c.addrFlagsAllowed = true; | ||
c.extraDataFrameSz = 1500; // 1500 bytes per frame of extraData allowed | ||
c.extraDataMaxFrames = 1; // 1 frame of extraData allowed | ||
} else { | ||
} else if (!legacy && gte(v, [0, 10, 0])) { | ||
// >=0.10.0 | ||
@@ -242,2 +303,9 @@ c.reqMaxDataSz = 1678; | ||
c.addrFlagsAllowed = true; | ||
} else { | ||
// Legacy or <0.10.0 | ||
c.reqMaxDataSz = 1152; | ||
c.ethMaxDataSz = c.reqMaxDataSz - 128; | ||
c.ethMaxMsgSz = c.ethMaxDataSz; | ||
c.ethMaxGasPrice = 500000000000; // 500 gwei | ||
c.addrFlagsAllowed = false; | ||
} | ||
@@ -244,0 +312,0 @@ return c; |
// Utils for Ethereum transactions. This is effecitvely a shim of ethereumjs-util, which | ||
// does not have browser (or, by proxy, React-Native) support. | ||
const BN = require('bignumber.js'); | ||
const Buffer = require('buffer/').Buffer | ||
const Buffer = require('buffer/').Buffer; | ||
const cbor = require('cbor'); | ||
const constants = require('./constants'); | ||
const ethers = require('ethers'); | ||
const eip712 = require('ethers-eip712'); | ||
const keccak256 = require('js-sha3').keccak256; | ||
@@ -13,2 +16,4 @@ const rlp = require('rlp-browser'); | ||
throw new Error('You must provide `payload`, `signerPath`, and `protocol` arguments in the messsage request'); | ||
if (input.signerPath.length > 5 || input.signerPath.length < 2) | ||
throw new Error('Please provide a signer path with 2-5 indices'); | ||
const req = { | ||
@@ -20,67 +25,15 @@ schema: constants.signingSchema.ETH_MSG, | ||
} | ||
const { ethMaxMsgSz, extraDataFrameSz, extraDataMaxFrames } = input.fwConstants; | ||
const MAX_BASE_MSG_SZ = ethMaxMsgSz; | ||
const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0; | ||
if (input.protocol === 'signPersonal') { | ||
const L = ((input.signerPath.length + 1) * 4) + MAX_BASE_MSG_SZ + 4; | ||
let off = 0; | ||
req.payload = Buffer.alloc(L); | ||
req.payload.writeUInt8(constants.ethMsgProtocol.SIGN_PERSONAL, 0); off += 1; | ||
req.payload.writeUInt32LE(input.signerPath.length, off); off += 4; | ||
for (let i = 0; i < input.signerPath.length; i++) { | ||
req.payload.writeUInt32LE(input.signerPath[i], off); off += 4; | ||
try { | ||
switch (input.protocol) { | ||
case 'signPersonal': | ||
return buildPersonalSignRequest(req, input) | ||
case 'eip712': | ||
if (!input.fwConstants.eip712Supported) | ||
throw new Error('EIP712 is not supported by your Lattice firmware version. Please upgrade.') | ||
return buildEIP712Request(req, input) | ||
default: | ||
throw new Error('Unsupported protocol'); | ||
} | ||
// Write the payload buffer. The payload can come in either as a buffer or as a string | ||
let payload = input.payload; | ||
// Determine if this is a hex string | ||
let displayHex = false; | ||
if (typeof input.payload === 'string') { | ||
if (input.payload.slice(0, 2) === '0x') { | ||
payload = ensureHexBuffer(input.payload) | ||
displayHex = true === isHexStr(input.payload.slice(2)); | ||
} else { | ||
if (false === latticeCanDisplayStr(input.payload)) | ||
throw new Error('Currently, the Lattice can only display ASCII strings.'); | ||
payload = Buffer.from(input.payload) | ||
} | ||
} else if (typeof input.displayHex === 'boolean') { | ||
// If this is a buffer and the user has specified whether or not this | ||
// is a hex buffer with the optional argument, write that | ||
displayHex = input.displayHex | ||
} else { | ||
// Otherwise, determine if this buffer is an ASCII string. If it is, set `displayHex` accordingly. | ||
// NOTE: THIS MEANS THAT NON-ASCII STRINGS WILL DISPLAY AS HEX SINCE WE CANNOT KNOW IF THE REQUESTER | ||
// EXPECTED NON-ASCII CHARACTERS TO DISPLAY IN A STRING | ||
// TODO: Develop a more elegant solution for this | ||
if (!input.payload.toString) | ||
throw new Error('Unsupported input data type'); | ||
displayHex = false === isASCIIStr(input.payload.toString()) | ||
} | ||
// Flow data into extraData requests, which will follow-up transaction requests, if supported/applicable | ||
const extraDataPayloads = []; | ||
if (payload.length > MAX_BASE_MSG_SZ) { | ||
// Determine sizes and run through sanity checks | ||
const maxSzAllowed = MAX_BASE_MSG_SZ + (extraDataMaxFrames * extraDataFrameSz); | ||
if (!EXTRA_DATA_ALLOWED) | ||
throw new Error(`Your message is ${payload.length} bytes, but can only be a maximum of ${MAX_BASE_MSG_SZ}`); | ||
else if (EXTRA_DATA_ALLOWED && payload.length > maxSzAllowed) | ||
throw new Error(`Your message is ${payload.length} bytes, but can only be a maximum of ${maxSzAllowed}`); | ||
// Split overflow data into extraData frames | ||
const frames = splitFrames(payload.slice(MAX_BASE_MSG_SZ), extraDataFrameSz); | ||
frames.forEach((frame) => { | ||
const szLE = Buffer.alloc(4); | ||
szLE.writeUInt32LE(frame.length); | ||
extraDataPayloads.push(Buffer.concat([szLE, frame])); | ||
}) | ||
} | ||
// Write the payload and metadata into our buffer | ||
req.extraDataPayloads = extraDataPayloads | ||
req.msg = payload; | ||
req.payload.writeUInt8(displayHex, off); off += 1; | ||
req.payload.writeUInt16LE(payload.length, off); off += 2; | ||
payload.copy(req.payload, off); | ||
return req; | ||
} else { | ||
throw new Error('Unsupported protocol'); | ||
} catch (err) { | ||
return { err: err.toString() } | ||
} | ||
@@ -99,3 +52,7 @@ } | ||
// may be configurable in future versions | ||
return addRecoveryParam(Buffer.concat([prefix, msg]), sig, signer, 1, false) | ||
const hash = Buffer.from(keccak256(Buffer.concat([prefix, msg])), 'hex') | ||
return addRecoveryParam(hash, sig, signer, 1, false) | ||
} else if (input.protocol === 'eip712') { | ||
const digest = eip712.TypedDataUtils.encodeDigest(req.input.payload) | ||
return addRecoveryParam(digest, sig, signer) | ||
} else { | ||
@@ -110,5 +67,7 @@ throw new Error('Unsupported protocol'); | ||
const { signerPath, eip155=null, fwConstants } = data; | ||
const { ethMaxDataSz, extraDataFrameSz, extraDataMaxFrames } = fwConstants; | ||
const { extraDataFrameSz, extraDataMaxFrames } = fwConstants; | ||
const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0; | ||
const MAX_BASE_DATA_SZ = ethMaxDataSz; | ||
const MAX_BASE_DATA_SZ = fwConstants.ethMaxDataSz; | ||
const VAR_PATH_SZ = fwConstants.varAddrPathSzAllowed; | ||
// Sanity checks: | ||
@@ -123,4 +82,4 @@ // There are a handful of named chains we allow the user to reference (`chainIds`) | ||
// Sanity check on signePath | ||
if (!signerPath || signerPath.length !== 5) | ||
throw new Error('Please provider full signer path (`signerPath`)') | ||
if (!signerPath) | ||
throw new Error('`signerPath` not provided'); | ||
@@ -193,10 +152,7 @@ // Determine if we should use EIP155 given the chainID. | ||
// 2. BIP44 Path | ||
// 2. Signer Path | ||
//------------------ | ||
// First write the number of indices in this path (will probably always be 5, but | ||
// we want to keep this extensible) | ||
txReqPayload.writeUInt32LE(signerPath.length, off); off += 4; | ||
for (let i = 0; i < signerPath.length; i++) { | ||
txReqPayload.writeUInt32LE(signerPath[i], off); off += 4; | ||
} | ||
const signerPathBuf = buildSignerPathBuf(signerPath, VAR_PATH_SZ); | ||
signerPathBuf.copy(txReqPayload, off); | ||
off += signerPathBuf.length; | ||
@@ -258,3 +214,3 @@ // 3. ETH TX request data | ||
rawTx, | ||
payload: txReqPayload, | ||
payload: txReqPayload.slice(0, off), | ||
extraDataPayloads, | ||
@@ -286,3 +242,4 @@ schema: constants.signingSchema.ETH_TRANSFER, // We will use eth transfer for all ETH txs for v1 | ||
const rlpEncoded = rlp.encode(tx.rawTx); | ||
const newSig = addRecoveryParam(rlpEncoded, sig, address, tx.chainId, useEIP155); | ||
const hash = Buffer.from(keccak256(rlpEncoded), 'hex') | ||
const newSig = addRecoveryParam(hash, sig, address, tx.chainId, useEIP155); | ||
// Use the signature to generate a new raw transaction payload | ||
@@ -299,6 +256,6 @@ const newRawTx = tx.rawTx.slice(0, 6); | ||
// Attach a recovery parameter to a signature by brute-forcing ECRecover | ||
function addRecoveryParam(payload, sig, address, chainId, useEIP155) { | ||
function addRecoveryParam(hashBuf, sig, address, chainId, useEIP155) { | ||
try { | ||
// Rebuild the keccak256 hash here so we can `ecrecover` | ||
const hash = new Uint8Array(Buffer.from(keccak256(payload), 'hex')); | ||
const hash = new Uint8Array(hashBuf); | ||
let v = 0; | ||
@@ -330,3 +287,2 @@ // Fix signature componenet lengths to 32 bytes each | ||
} | ||
exports.addRecoveryParam = addRecoveryParam; | ||
@@ -457,11 +413,165 @@ // Convert an RLP-serialized transaction (plus signature) into a transaction hash | ||
function ensureHexBuffer(x) { | ||
if (x === null || x === 0) return Buffer.alloc(0); | ||
else if (Buffer.isBuffer(x)) return x; | ||
if (typeof x === 'number') x = `${new BN(x).toString(16)}`; | ||
else if (typeof x === 'string' && x.slice(0, 2) === '0x') x = x.slice(2); | ||
if (x.length % 2 > 0) x = `0${x}`; | ||
return Buffer.from(x, 'hex'); | ||
try { | ||
// For null values, return a 0-sized buffer | ||
if (x === null || x === 0) return Buffer.alloc(0); | ||
// Otherwise try to get this converted to a hex string | ||
if (typeof x === 'number') { | ||
x = `${new BN(x).toString(16)}`; | ||
} else if (typeof x === 'string' && x.slice(0, 2) === '0x') { | ||
x = x.slice(2); | ||
} else { | ||
x = x.toString('hex') | ||
} | ||
if (x.length % 2 > 0) x = `0${x}`; | ||
if (x === '00') | ||
return Buffer.alloc(0); | ||
return Buffer.from(x, 'hex'); | ||
} catch (err) { | ||
throw new Error(`Cannot convert ${x.toString()} to hex buffer (${err.toString()})`); | ||
} | ||
} | ||
exports.ensureHexBuffer = ensureHexBuffer; | ||
function buildPersonalSignRequest(req, input) { | ||
const MAX_BASE_MSG_SZ = input.fwConstants.ethMaxMsgSz; | ||
const VAR_PATH_SZ = input.fwConstants.varAddrPathSzAllowed; | ||
const L = (24) + MAX_BASE_MSG_SZ + 4; | ||
let off = 0; | ||
req.payload = Buffer.alloc(L); | ||
req.payload.writeUInt8(constants.ethMsgProtocol.SIGN_PERSONAL, 0); off += 1; | ||
// Write the signer path into the buffer | ||
const signerPathBuf = buildSignerPathBuf(input.signerPath, VAR_PATH_SZ); | ||
signerPathBuf.copy(req.payload, off); | ||
off += signerPathBuf.length; | ||
// Write the payload buffer. The payload can come in either as a buffer or as a string | ||
let payload = input.payload; | ||
// Determine if this is a hex string | ||
let displayHex = false; | ||
if (typeof input.payload === 'string') { | ||
if (input.payload.slice(0, 2) === '0x') { | ||
payload = ensureHexBuffer(input.payload) | ||
displayHex = true === isHexStr(input.payload.slice(2)); | ||
} else { | ||
if (false === latticeCanDisplayStr(input.payload)) | ||
throw new Error('Currently, the Lattice can only display ASCII strings.'); | ||
payload = Buffer.from(input.payload) | ||
} | ||
} else if (typeof input.displayHex === 'boolean') { | ||
// If this is a buffer and the user has specified whether or not this | ||
// is a hex buffer with the optional argument, write that | ||
displayHex = input.displayHex | ||
} else { | ||
// Otherwise, determine if this buffer is an ASCII string. If it is, set `displayHex` accordingly. | ||
// NOTE: THIS MEANS THAT NON-ASCII STRINGS WILL DISPLAY AS HEX SINCE WE CANNOT KNOW IF THE REQUESTER | ||
// EXPECTED NON-ASCII CHARACTERS TO DISPLAY IN A STRING | ||
// TODO: Develop a more elegant solution for this | ||
if (!input.payload.toString) | ||
throw new Error('Unsupported input data type'); | ||
displayHex = false === isASCIIStr(input.payload.toString()) | ||
} | ||
// Flow data into extraData requests, which will follow-up transaction requests, if supported/applicable | ||
const extraDataPayloads = getExtraData(payload, input); | ||
// Write the payload and metadata into our buffer | ||
req.extraDataPayloads = extraDataPayloads | ||
req.msg = payload; | ||
req.payload.writeUInt8(displayHex, off); off += 1; | ||
req.payload.writeUInt16LE(payload.length, off); off += 2; | ||
payload.copy(req.payload, off); | ||
return req; | ||
} | ||
function buildEIP712Request(req, input) { | ||
try { | ||
const MAX_BASE_MSG_SZ = input.fwConstants.ethMaxMsgSz; | ||
const VAR_PATH_SZ = input.fwConstants.varAddrPathSzAllowed; | ||
const TYPED_DATA = constants.ethMsgProtocol.TYPED_DATA; | ||
const L = (24) + MAX_BASE_MSG_SZ + 4; | ||
let off = 0; | ||
req.payload = Buffer.alloc(L); | ||
req.payload.writeUInt8(TYPED_DATA.enumIdx, 0); off += 1; | ||
// Write the signer path | ||
const signerPathBuf = buildSignerPathBuf(input.signerPath, VAR_PATH_SZ); | ||
signerPathBuf.copy(req.payload, off); | ||
off += signerPathBuf.length; | ||
// Parse/clean the EIP712 payload, serialize with CBOR, and write to the payload | ||
const data = JSON.parse(JSON.stringify(input.payload)); | ||
if (!data.primaryType || !data.types[data.primaryType]) | ||
throw new Error('primaryType must be specified and the type must be included.') | ||
if (!data.message || !data.domain) | ||
throw new Error('message and domain must be specified.') | ||
if (0 > Object.keys(data.types).indexOf('EIP712Domain')) | ||
throw new Error('EIP712Domain type must be defined.') | ||
// Parse the payload to ensure we have valid EIP712 data types and that | ||
// they are encoded such that Lattice firmware can parse them. | ||
// We need two different encodings: | ||
// 1. Use `ethers` BigNumber when building the request to be validated by ethers-eip712. | ||
// Make sure we use a copy of the data to avoid mutation problems | ||
input.payload.message = parseEIP712Msg( JSON.parse(JSON.stringify(data.message)), | ||
JSON.parse(JSON.stringify(data.primaryType)), | ||
JSON.parse(JSON.stringify(data.types)), | ||
true); | ||
input.payload.domain = parseEIP712Msg( JSON.parse(JSON.stringify(data.domain)), | ||
'EIP712Domain', | ||
JSON.parse(JSON.stringify(data.types)), | ||
true); | ||
// 2. Use `bignumber.js` for the request going to the Lattice, since it's the required | ||
// BigNumber lib for `cbor`, which we use to encode the request data to the Lattice. | ||
data.domain = parseEIP712Msg(data.domain, 'EIP712Domain', data.types, false); | ||
data.message = parseEIP712Msg(data.message, data.primaryType, data.types, false); | ||
// Now build the message to be sent to the Lattice | ||
const payload = Buffer.from(cbor.encode(data)); | ||
const extraDataPayloads = getExtraData(payload, input); | ||
req.extraDataPayloads = extraDataPayloads; | ||
req.payload.writeUInt16LE(payload.length, off); off += 2; | ||
payload.copy(req.payload, off); off += payload.length; | ||
// Slice out the part of the buffer that we didn't use. | ||
req.payload = req.payload.slice(0, off); | ||
return req; | ||
} catch (err) { | ||
return { err: `Failed to build EIP712 request: ${err.message}` }; | ||
} | ||
} | ||
function buildSignerPathBuf(signerPath, varAddrPathSzAllowed) { | ||
const buf = Buffer.alloc(24); | ||
let off = 0; | ||
if (varAddrPathSzAllowed && signerPath.length > 5) | ||
throw new Error('Signer path must be <=5 indices.'); | ||
if (!varAddrPathSzAllowed && signerPath.length !== 5) | ||
throw new Error('Your Lattice firmware only supports 5-index derivation paths. Please upgrade.'); | ||
buf.writeUInt32LE(signerPath.length, off); off += 4; | ||
for (let i = 0; i < 5; i++) { | ||
if (i < signerPath.length) | ||
buf.writeUInt32LE(signerPath[i], off); | ||
else | ||
buf.writeUInt32LE(0, off); | ||
off += 4; | ||
} | ||
return buf; | ||
} | ||
function getExtraData(payload, input) { | ||
const { ethMaxMsgSz, extraDataFrameSz, extraDataMaxFrames } = input.fwConstants; | ||
const MAX_BASE_MSG_SZ = ethMaxMsgSz; | ||
const EXTRA_DATA_ALLOWED = extraDataFrameSz > 0 && extraDataMaxFrames > 0; | ||
const extraDataPayloads = []; | ||
if (payload.length > MAX_BASE_MSG_SZ) { | ||
// Determine sizes and run through sanity checks | ||
const maxSzAllowed = MAX_BASE_MSG_SZ + (extraDataMaxFrames * extraDataFrameSz); | ||
if (!EXTRA_DATA_ALLOWED) | ||
throw new Error(`Your message is ${payload.length} bytes, but can only be a maximum of ${MAX_BASE_MSG_SZ}`); | ||
else if (EXTRA_DATA_ALLOWED && payload.length > maxSzAllowed) | ||
throw new Error(`Your message is ${payload.length} bytes, but can only be a maximum of ${maxSzAllowed}`); | ||
// Split overflow data into extraData frames | ||
const frames = splitFrames(payload.slice(MAX_BASE_MSG_SZ), extraDataFrameSz); | ||
frames.forEach((frame) => { | ||
const szLE = Buffer.alloc(4); | ||
szLE.writeUInt32LE(frame.length); | ||
extraDataPayloads.push(Buffer.concat([szLE, frame])); | ||
}) | ||
} | ||
return extraDataPayloads; | ||
} | ||
function splitFrames(data, frameSz) { | ||
@@ -476,2 +586,60 @@ const frames = [] | ||
return frames; | ||
} | ||
function parseEIP712Msg(msg, typeName, types, isEthers=false) { | ||
try { | ||
const type = types[typeName]; | ||
type.forEach((item) => { | ||
const isCustomType = Object.keys(types).indexOf(item.type) > -1; | ||
if (true === isCustomType) { | ||
msg[item.name] = parseEIP712Msg(msg[item.name], item.type, types, isEthers) | ||
} else { | ||
msg[item.name] = parseEIP712Item(msg[item.name], item.type, isEthers) | ||
} | ||
}) | ||
} catch (err) { | ||
throw new Error(err.message); | ||
} | ||
return msg; | ||
} | ||
function parseEIP712Item(data, type, isEthers=false) { | ||
if (type === 'bytes') { | ||
// Variable sized bytes need to be buffer type | ||
data = ensureHexBuffer(data); | ||
if (data.length === 0) | ||
throw new Error('"bytes" type must contain at least one byte in value'); | ||
} else if (type.slice(0, 5) === 'bytes') { | ||
// Fixed sizes bytes need to be buffer type. We also add some sanity checks. | ||
const nBytes = parseInt(type.slice(5)); | ||
data = ensureHexBuffer(data); | ||
if (data.length !== nBytes) | ||
throw new Error(`Expected ${type} type, but got ${data.length} bytes`); | ||
} else if (type === 'address') { | ||
// Address must be a 20 byte buffer | ||
data = ensureHexBuffer(data); | ||
if (data.length !== 20) | ||
throw new Error(`Address type must be 20 bytes, but got ${data.length} bytes`); | ||
// Ethers wants addresses as hex strings | ||
if (isEthers === true) { | ||
data = `0x${data.toString('hex')}` | ||
} | ||
} else if (type === 'uint8' || type === 'uint16' || type === 'uint32' || type === 'uint64') { | ||
data = parseInt(ensureHexBuffer(data).toString('hex'), 16) | ||
} else if (type === 'uint256') { | ||
// Uint256s should be encoded as bignums. | ||
if (isEthers === true) { | ||
// `ethers` uses their own BigNumber lib | ||
data = ethers.BigNumber.from(`0x${ensureHexBuffer(data).toString('hex')}`) | ||
} else { | ||
// `bignumber.js` is needed for `cbor` encoding, which gets sent to the Lattice and plays | ||
// nicely with its firmware cbor lib. | ||
data = new BN(ensureHexBuffer(data).toString('hex'), 16) | ||
} | ||
} else if (type === 'bool') { | ||
// Booleans need to be cast to a u8 | ||
data = data === true ? 1 : 0; | ||
} | ||
// Other types don't need to be modified | ||
return data; | ||
} |
100541
20
2278
15
+ Addedcbor@^7.0.3
+ Addedethers@^5.0.31
+ Addedethers-eip712@^0.2.0
+ Added@cto.af/textdecoder@0.0.0(transitive)
+ Added@ethersproject/abi@5.7.0(transitive)
+ Added@ethersproject/abstract-provider@5.7.0(transitive)
+ Added@ethersproject/abstract-signer@5.7.0(transitive)
+ Added@ethersproject/address@5.7.0(transitive)
+ Added@ethersproject/base64@5.7.0(transitive)
+ Added@ethersproject/basex@5.7.0(transitive)
+ Added@ethersproject/bignumber@5.7.0(transitive)
+ Added@ethersproject/bytes@5.7.0(transitive)
+ Added@ethersproject/constants@5.7.0(transitive)
+ Added@ethersproject/contracts@5.7.0(transitive)
+ Added@ethersproject/hash@5.7.0(transitive)
+ Added@ethersproject/hdnode@5.7.0(transitive)
+ Added@ethersproject/json-wallets@5.7.0(transitive)
+ Added@ethersproject/keccak256@5.7.0(transitive)
+ Added@ethersproject/logger@5.7.0(transitive)
+ Added@ethersproject/networks@5.7.1(transitive)
+ Added@ethersproject/pbkdf2@5.7.0(transitive)
+ Added@ethersproject/properties@5.7.0(transitive)
+ Added@ethersproject/providers@5.7.2(transitive)
+ Added@ethersproject/random@5.7.0(transitive)
+ Added@ethersproject/rlp@5.7.0(transitive)
+ Added@ethersproject/sha2@5.7.0(transitive)
+ Added@ethersproject/signing-key@5.7.0(transitive)
+ Added@ethersproject/solidity@5.7.0(transitive)
+ Added@ethersproject/strings@5.7.0(transitive)
+ Added@ethersproject/transactions@5.7.0(transitive)
+ Added@ethersproject/units@5.7.0(transitive)
+ Added@ethersproject/wallet@5.7.0(transitive)
+ Added@ethersproject/web@5.7.1(transitive)
+ Added@ethersproject/wordlists@5.7.0(transitive)
+ Addedaes-js@3.0.0(transitive)
+ Addedbech32@1.1.4(transitive)
+ Addedbn.js@5.2.1(transitive)
+ Addedcbor@7.0.6(transitive)
+ Addedethers@5.7.2(transitive)
+ Addedethers-eip712@0.2.0(transitive)
+ Addednofilter@2.0.3(transitive)
+ Addedscrypt-js@3.0.1(transitive)
+ Addedws@7.4.6(transitive)