UNPKG

@ledgerhq/hw-app-starknet

Version:

Ledger Hardware Wallet Starknet Application API

375 lines (374 loc) 14.2 kB
import { serializePath as S, toBN as t } from "./helper.js"; import { INS as o, CLA as X, LedgerError as i, errorCodeToString as M, HASH_MAX_LENGTH as v } from "./common.js"; import { CallData as L, hash as D, typedData as V, shortString as m } from "starknet"; import { EDataAvailabilityMode as G } from "./node_modules/@starknet-io/types-js/dist/esm/api/constants.js"; function Y(T) { let e = T.replace(/^0x0*/, ""); if (e.length > v) throw "invalid hash length"; return e = "0".repeat(1 + v - e.length).concat(e), e; } function H(T) { const e = []; for (let r = 0; r < T.length; r += 2) e.push(parseInt(T.substring(r, r + 2), 16)); return Uint8Array.from(e); } class j { transport; constructor(e) { if (this.transport = e, !e) throw new Error("Transport has not been defined"); } async sendApdu(e = o.GET_VERSION, r = 0, a = 0, s = new Uint8Array(0)) { return this.transport.send(X, e, r, a, Buffer.from(s), [ i.NoError, i.BadCla, i.BadIns, i.InvalidP1P2, i.UserRejected ]).then((n) => { const E = n.subarray(-2), p = E[0] * 256 + E[1]; let _ = M(p); return { data: n.subarray(0, -2), returnCode: p, errorMessage: _ }; }); } /** * get version of Nano Starknet application * @return an object with a major, minor, patch */ async getAppVersion() { const e = await this.sendApdu(o.GET_VERSION); return { returnCode: e.returnCode, errorMessage: e.errorMessage, major: e.data[0], minor: e.data[1], patch: e.data[2] }; } /** * get full starknet public key derived from provided derivation path * @param path a path in EIP-2645 format (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2645.md) * @return an object with publicKey * @example * stark.getPubKey("m/2645'/579218131'/0'/0'").then(o => o.publicKey) */ async getPubKey(e, r = !0) { const a = S(e); try { const s = await this.sendApdu(o.GET_PUB_KEY, r ? 1 : 0, 0, a); return { publicKey: s.data.slice(1, 65), returnCode: s.returnCode, errorMessage: s.errorMessage }; } catch { return console.warn("Caught error"), { publicKey: new Uint8Array(0), returnCode: i.ExecutionError, errorMessage: M(i.ExecutionError) }; } } /** * get stark key derived from provided derivation path * @param path a path in EIP-2645 format (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2645.md) * @return an object with publicKey * @example * stark.getPubKey("m/2645'/579218131'/0'/0'").then(o => o.publicKey) */ async getStarkKey(e, r = !0) { const { publicKey: a, errorMessage: s, returnCode: n } = await this.getPubKey(e, r); return n !== i.NoError ? { starkKey: a, returnCode: n, errorMessage: s } : { starkKey: a.slice(0, 32), returnCode: n, errorMessage: s }; } /** * sign the given hash over the staRknet elliptic curve * @param path Derivation path in EIP-2645 format * @param hash Pedersen hash to be signed * @return an object with (r, s, v) signature */ async signHash(e, r) { const a = S(e), s = H(Y(r)); try { let n = await this.sendApdu(o.SIGN_HASH, 0, 0, a); return n = await this.sendApdu(o.SIGN_HASH, 1, 0, s), n.returnCode !== i.NoError ? { returnCode: n.returnCode, errorMessage: M(n.returnCode), r: new Uint8Array(0), s: new Uint8Array(0), v: 0 } : { returnCode: n.returnCode, errorMessage: n.errorMessage, r: n.data.subarray(1, 33), s: n.data.subarray(33, 65), v: n.data[65] }; } catch { return { returnCode: i.ExecutionError, errorMessage: M(i.ExecutionError), r: new Uint8Array(0), s: new Uint8Array(0), v: 0 }; } } /** * sign a Starknet Tx v3 Invoke transaction * @param path Derivation path in EIP-2645 format * @param calls List of calls [(to, selector, calldata), (), ...] * @param tx Tx fields (account address, tip, l1_gas_bounds, l2_gas_bounds, chainID, nonce, data_availability_mode) * @return an object with Tx hash + (r, s, v) signature */ async signTx(e, r, a) { await this.sendApdu(o.SIGN_TX, 0, 0, S(e)); const s = t(a.accountAddress), n = t(a.chainId), E = t(a.nonce), p = t( this.encodeDataAvailabilityMode(a.nonceDataAvailabilityMode, a.feeDataAvailabilityMode) ); let _ = new Uint8Array([ ...s.toArray("be", 32), ...n.toArray("be", 32), ...E.toArray("be", 32), ...p.toArray("be", 32) ]); await this.sendApdu(o.SIGN_TX, 1, 0, _); const g = t(a.tip), { l1_gas: u, l2_gas: A, l1_data_gas: C } = this.encodeResourceBounds(a.resourceBounds); _ = new Uint8Array([ ...g.toArray("be", 32), ...u.toArray("be", 32), ...A.toArray("be", 32), ...C.toArray("be", 32) ]), await this.sendApdu(o.SIGN_TX, 2, 0, _), await this.sendApdu(o.SIGN_TX, 3, 0, new Uint8Array(0)), await this.sendApdu(o.SIGN_TX, 4, 0, new Uint8Array(0)); let b = t(r.length); _ = new Uint8Array([...b.toArray("be", 32)]), await this.sendApdu(o.SIGN_TX, 5, 0, _); let d, l = { returnCode: i.ExecutionError, errorMessage: "Execution Error", h: new Uint8Array(0), r: new Uint8Array(0), s: new Uint8Array(0), v: 0 }; for (const f of r) { const N = L.toCalldata(f.calldata); let y = new Uint8Array((3 + N.length) * 32); t(f.contractAddress).toArray("be", 32).forEach((h, I) => y[I] = h), t(D.getSelectorFromName(f.entrypoint)).toArray("be", 32).forEach((h, I) => y[32 + I] = h), t(N.length).toArray("be", 32).forEach((h, I) => y[64 + I] = h), N.forEach((h, I) => { t(h).toArray("be", 32).forEach((O, P) => y[96 + 32 * I + P] = O); }); let U = []; for (let h = 0; h < y.length; h += 224) U.push(y.slice(h, h + 224)); if (U.length > 1) { if (d = await this.sendApdu(o.SIGN_TX, 6, 0, U[0]), d.returnCode !== i.NoError) return l; for (const h of U.slice(1)) if (d = await this.sendApdu(o.SIGN_TX, 6, 1, h), d.returnCode !== i.NoError) return l; } else if (d = await this.sendApdu(o.SIGN_TX, 6, 0, U[0]), d.returnCode !== i.NoError) return l; } return d?.returnCode === i.NoError ? { returnCode: d.returnCode, errorMessage: d.errorMessage, h: d.data.subarray(0, 32), r: d.data.subarray(33, 65), s: d.data.subarray(65, 97), v: d.data[97] } : l; } /** * sign a Starknet Tx v1 Invoke transaction * @param path Derivation path in EIP-2645 format * @param calls List of calls [(to, selector, calldata), (), ...] * @param tx Tx fields (account address, max_fee, chainID, nonce) * @return an object with Tx hash + (r, s, v) signature */ async signTxV1(e, r, a) { await this.sendApdu(o.SIGN_TX_V1, 0, 0, S(e)); const s = t(a.accountAddress), n = t(a.max_fee), E = t(a.chainId), p = t(a.nonce); let _ = new Uint8Array([ ...s.toArray("be", 32), ...n.toArray("be", 32), ...E.toArray("be", 32), ...p.toArray("be", 32) ]); await this.sendApdu(o.SIGN_TX_V1, 1, 0, _); let g = t(r.length); _ = new Uint8Array([...g.toArray("be", 32)]), await this.sendApdu(o.SIGN_TX_V1, 2, 0, _); let u, A = { returnCode: i.ExecutionError, errorMessage: "Execution Error", h: new Uint8Array(0), r: new Uint8Array(0), s: new Uint8Array(0), v: 0 }; for (const C of r) { const b = L.toCalldata(C.calldata); let d = new Uint8Array((3 + b.length) * 32); t(C.contractAddress).toArray("be", 32).forEach((c, w) => d[w] = c), t(D.getSelectorFromName(C.entrypoint)).toArray("be", 32).forEach((c, w) => d[32 + w] = c), t(b.length).toArray("be", 32).forEach((c, w) => d[64 + w] = c), b.forEach((c, w) => { t(c).toArray("be", 32).forEach((U, h) => d[96 + 32 * w + h] = U); }); let y = []; for (let c = 0; c < d.length; c += 224) y.push(d.slice(c, c + 224)); if (y.length > 1) { if (u = await this.sendApdu(o.SIGN_TX_V1, 3, 0, y[0]), u.returnCode !== i.NoError) return A; for (const c of y.slice(1)) if (u = await this.sendApdu(o.SIGN_TX_V1, 3, 1, c), u.returnCode !== i.NoError) return A; } else if (u = await this.sendApdu(o.SIGN_TX_V1, 3, 0, y[0]), u.returnCode !== i.NoError) return A; } return u?.returnCode === i.NoError ? { returnCode: u.returnCode, errorMessage: u.errorMessage, h: u.data.subarray(0, 32), r: u.data.subarray(33, 65), s: u.data.subarray(65, 97), v: u.data[97] } : A; } /** * sign a Starknet Tx v3 DeployAccount transaction * @param path Derivation path in EIP-2645 format * @param tx Tx fields (contract_address, tip, resourceBounds, paymaster_data, chain_id, nonce, nonceDataAvailabilityMode, feeDataAvailabilityMode, constructor_calldata, class_hash, contract_address_salt) * @return an object with Tx hash + (r, s, v) signature */ async signDeployAccount(e, r) { await this.sendApdu(o.DEPLOY_ACCOUNT, 0, 0, S(e)); const a = t(r.contractAddress), s = t(r.chainId), n = t(r.nonce), E = t( this.encodeDataAvailabilityMode(r.nonceDataAvailabilityMode, r.feeDataAvailabilityMode) ), p = t(r.class_hash), _ = t(r.contract_address_salt); let g = new Uint8Array([ ...a.toArray("be", 32), ...s.toArray("be", 32), ...n.toArray("be", 32), ...E.toArray("be", 32), ...p.toArray("be", 32), ..._.toArray("be", 32) ]); await this.sendApdu(o.DEPLOY_ACCOUNT, 1, 0, g); const u = t(r.tip), { l1_gas: A, l2_gas: C, l1_data_gas: b } = this.encodeResourceBounds(r.resourceBounds); g = new Uint8Array([ ...u.toArray("be", 32), ...A.toArray("be", 32), ...C.toArray("be", 32), ...b.toArray("be", 32) ]), await this.sendApdu(o.DEPLOY_ACCOUNT, 2, 0, g), await this.sendApdu(o.DEPLOY_ACCOUNT, 3, 0, new Uint8Array(0)); let d = t(r.constructor_calldata.length); g = new Uint8Array([...d.toArray("be", 32)]), await this.sendApdu(o.DEPLOY_ACCOUNT, 4, 0, g); let l, f = { returnCode: i.ExecutionError, errorMessage: "Execution Error", h: new Uint8Array(0), r: new Uint8Array(0), s: new Uint8Array(0), v: 0 }, N = new Uint8Array(r.constructor_calldata.length * 32); r.constructor_calldata.forEach((c, w) => { t(c).toArray("be", 32).forEach((U, h) => N[32 * w + h] = U); }); let y = []; for (let c = 0; c < N.length; c += 224) y.push(N.slice(c, c + 224)); for (const c of y) if (l = await this.sendApdu(o.DEPLOY_ACCOUNT, 5, 0, c), l.returnCode !== i.NoError) return f; return l?.returnCode === i.NoError ? { returnCode: l.returnCode, errorMessage: l.errorMessage, h: l.data.subarray(0, 32), r: l.data.subarray(33, 65), s: l.data.subarray(65, 97), v: l.data[97] } : f; } /** * sign a Starknet Tx v1 DeployAccount transaction * @param path Derivation path in EIP-2645 format * @param tx Tx fields (contract_address, class_hash, contract_address_salt, constructor_calldata, max_fee, chainID, nonce) * @return an object with Tx hash + (r, s, v) signature */ async signDeployAccountV1(e, r) { await this.sendApdu(o.DEPLOY_ACCOUNT_V1, 0, 0, S(e)); const a = t(r.contractAddress), s = t(r.class_hash), n = t(r.contract_address_salt), E = t(r.max_fee), p = t(r.chainId), _ = t(r.nonce); let g = new Uint8Array([ ...a.toArray("be", 32), ...s.toArray("be", 32), ...n.toArray("be", 32), ...E.toArray("be", 32), ...p.toArray("be", 32), ..._.toArray("be", 32) ]); await this.sendApdu(o.DEPLOY_ACCOUNT_V1, 1, 0, g); let u = t(r.constructor_calldata.length); g = new Uint8Array([...u.toArray("be", 32)]), await this.sendApdu(o.DEPLOY_ACCOUNT_V1, 2, 0, g); let A, C = { returnCode: i.ExecutionError, errorMessage: "Execution Error", h: new Uint8Array(0), r: new Uint8Array(0), s: new Uint8Array(0), v: 0 }, b = new Uint8Array(r.constructor_calldata.length * 32); r.constructor_calldata.forEach((l, f) => { t(l).toArray("be", 32).forEach((y, c) => b[32 * f + c] = y); }); let d = []; for (let l = 0; l < b.length; l += 224) d.push(b.slice(l, l + 224)); for (const l of d) if (A = await this.sendApdu(o.DEPLOY_ACCOUNT_V1, 3, 0, l), A.returnCode !== i.NoError) return C; return A?.returnCode === i.NoError ? { returnCode: A.returnCode, errorMessage: A.errorMessage, h: A.data.subarray(0, 32), r: A.data.subarray(33, 65), s: A.data.subarray(65, 97), v: A.data[97] } : C; } /** * sign a SNIP-12 encoded message * @param path Derivation path in EIP-2645 format * @param message message to be signed * @param account accound address to sign the message * @return an object with (r, s, v) signature */ async signMessage(e, r, a) { const s = V.getMessageHash(r, a); return this.signHash(e, s); } encodeResourceBounds(e) { const r = BigInt(64), a = BigInt(128), s = r + a, n = BigInt(m.encodeShortString("L1_GAS")), E = BigInt(m.encodeShortString("L2_GAS")), p = BigInt(m.encodeShortString("L1_DATA")), _ = (n << s) + (BigInt(e.l1_gas.max_amount) << a) + BigInt(e.l1_gas.max_price_per_unit), g = (E << s) + (BigInt(e.l2_gas.max_amount) << a) + BigInt(e.l2_gas.max_price_per_unit), u = (p << s) + (BigInt(e.l1_data_gas.max_amount) << a) + BigInt(e.l1_data_gas.max_price_per_unit); return { l1_gas: t(_), l2_gas: t(g), l1_data_gas: t(u) }; } encodeDataAvailabilityMode(e, r) { const a = BigInt(32), s = e === G.L1 ? 0 : 1, n = r === G.L1 ? 0 : 1; return (BigInt(s) << a) + BigInt(n); } } export { i as LedgerError, j as StarknetClient };