UNPKG

@ledgerhq/hw-app-starknet

Version:

Ledger Hardware Wallet Starknet Application API

378 lines (377 loc) 14.5 kB
var V = Object.defineProperty; var Y = (p, t, r) => t in p ? V(p, t, { enumerable: !0, configurable: !0, writable: !0, value: r }) : p[t] = r; var v = (p, t, r) => Y(p, typeof t != "symbol" ? t + "" : t, r); import { serializePath as T, toBN as e } from "./helper.js"; import { CLA as H, LedgerError as c, errorCodeToString as M, INS as o, HASH_MAX_LENGTH as L } from "./common.js"; import { CallData as D, hash as G, typedData as K, shortString as m } from "starknet"; import { EDataAvailabilityMode as O } from "./node_modules/.pnpm/@starknet-io_types-js@0.8.1/node_modules/@starknet-io/types-js/dist/esm/api/constants.js"; function R(p) { let t = p.replace(/^0x0*/, ""); if (t.length > L) throw "invalid hash length"; return t = "0".repeat(1 + L - t.length).concat(t), t; } function k(p) { const t = []; for (let r = 0; r < p.length; r += 2) t.push(parseInt(p.substring(r, r + 2), 16)); return Uint8Array.from(t); } class W { constructor(t) { v(this, "transport"); if (this.transport = t, !t) throw new Error("Transport has not been defined"); } async sendApdu(t = o.GET_VERSION, r = 0, a = 0, s = new Uint8Array(0)) { return this.transport.send(H, t, r, a, Buffer.from(s), [ c.NoError, c.BadCla, c.BadIns, c.InvalidP1P2, c.UserRejected ]).then((n) => { const E = n.subarray(-2), b = E[0] * 256 + E[1]; let _ = M(b); return { data: n.subarray(0, -2), returnCode: b, errorMessage: _ }; }); } /** * get version of Nano Starknet application * @return an object with a major, minor, patch */ async getAppVersion() { const t = await this.sendApdu(o.GET_VERSION); return { returnCode: t.returnCode, errorMessage: t.errorMessage, major: t.data[0], minor: t.data[1], patch: t.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(t, r = !0) { const a = T(t); 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: c.ExecutionError, errorMessage: M(c.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(t, r = !0) { const { publicKey: a, errorMessage: s, returnCode: n } = await this.getPubKey(t, r); return n !== c.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(t, r) { const a = T(t), s = k(R(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 !== c.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: c.ExecutionError, errorMessage: M(c.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(t, r, a) { await this.sendApdu(o.SIGN_TX, 0, 0, T(t)); const s = e(a.accountAddress), n = e(a.chainId), E = e(a.nonce), b = e( this.encodeDataAvailabilityMode(a.nonceDataAvailabilityMode, a.feeDataAvailabilityMode) ); let _ = new Uint8Array([ ...s.toArray("be", 32), ...n.toArray("be", 32), ...E.toArray("be", 32), ...b.toArray("be", 32) ]); await this.sendApdu(o.SIGN_TX, 1, 0, _); const g = e(a.tip), { l1_gas: u, l2_gas: A, l1_data_gas: w } = this.encodeResourceBounds(a.resourceBounds); _ = new Uint8Array([ ...g.toArray("be", 32), ...u.toArray("be", 32), ...A.toArray("be", 32), ...w.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 C = e(r.length); _ = new Uint8Array([...C.toArray("be", 32)]), await this.sendApdu(o.SIGN_TX, 5, 0, _); let i, d = { returnCode: c.ExecutionError, errorMessage: "Execution Error", h: new Uint8Array(0), r: new Uint8Array(0), s: new Uint8Array(0), v: 0 }; for (const N of r) { const U = D.toCalldata(N.calldata); let y = new Uint8Array((3 + U.length) * 32); e(N.contractAddress).toArray("be", 32).forEach((h, S) => y[S] = h), e(G.getSelectorFromName(N.entrypoint)).toArray("be", 32).forEach((h, S) => y[32 + S] = h), e(U.length).toArray("be", 32).forEach((h, S) => y[64 + S] = h), U.forEach((h, S) => { e(h).toArray("be", 32).forEach((P, X) => y[96 + 32 * S + X] = P); }); let I = []; for (let h = 0; h < y.length; h += 7 * 32) I.push(y.slice(h, h + 7 * 32)); if (I.length > 1) { if (i = await this.sendApdu(o.SIGN_TX, 6, 0, I[0]), i.returnCode !== c.NoError) return d; for (const h of I.slice(1)) if (i = await this.sendApdu(o.SIGN_TX, 6, 1, h), i.returnCode !== c.NoError) return d; } else if (i = await this.sendApdu(o.SIGN_TX, 6, 0, I[0]), i.returnCode !== c.NoError) return d; } return (i == null ? void 0 : i.returnCode) === c.NoError ? { returnCode: i.returnCode, errorMessage: i.errorMessage, h: i.data.subarray(0, 32), r: i.data.subarray(33, 65), s: i.data.subarray(65, 97), v: i.data[97] } : d; } /** * 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(t, r, a) { await this.sendApdu(o.SIGN_TX_V1, 0, 0, T(t)); const s = e(a.accountAddress), n = e(a.max_fee), E = e(a.chainId), b = e(a.nonce); let _ = new Uint8Array([ ...s.toArray("be", 32), ...n.toArray("be", 32), ...E.toArray("be", 32), ...b.toArray("be", 32) ]); await this.sendApdu(o.SIGN_TX_V1, 1, 0, _); let g = e(r.length); _ = new Uint8Array([...g.toArray("be", 32)]), await this.sendApdu(o.SIGN_TX_V1, 2, 0, _); let u, A = { returnCode: c.ExecutionError, errorMessage: "Execution Error", h: new Uint8Array(0), r: new Uint8Array(0), s: new Uint8Array(0), v: 0 }; for (const w of r) { const C = D.toCalldata(w.calldata); let i = new Uint8Array((3 + C.length) * 32); e(w.contractAddress).toArray("be", 32).forEach((l, f) => i[f] = l), e(G.getSelectorFromName(w.entrypoint)).toArray("be", 32).forEach((l, f) => i[32 + f] = l), e(C.length).toArray("be", 32).forEach((l, f) => i[64 + f] = l), C.forEach((l, f) => { e(l).toArray("be", 32).forEach((I, h) => i[96 + 32 * f + h] = I); }); let y = []; for (let l = 0; l < i.length; l += 7 * 32) y.push(i.slice(l, l + 7 * 32)); if (y.length > 1) { if (u = await this.sendApdu(o.SIGN_TX_V1, 3, 0, y[0]), u.returnCode !== c.NoError) return A; for (const l of y.slice(1)) if (u = await this.sendApdu(o.SIGN_TX_V1, 3, 1, l), u.returnCode !== c.NoError) return A; } else if (u = await this.sendApdu(o.SIGN_TX_V1, 3, 0, y[0]), u.returnCode !== c.NoError) return A; } return (u == null ? void 0 : u.returnCode) === c.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(t, r) { await this.sendApdu(o.DEPLOY_ACCOUNT, 0, 0, T(t)); const a = e(r.contractAddress), s = e(r.chainId), n = e(r.nonce), E = e( this.encodeDataAvailabilityMode(r.nonceDataAvailabilityMode, r.feeDataAvailabilityMode) ), b = e(r.class_hash), _ = e(r.contract_address_salt); let g = new Uint8Array([ ...a.toArray("be", 32), ...s.toArray("be", 32), ...n.toArray("be", 32), ...E.toArray("be", 32), ...b.toArray("be", 32), ..._.toArray("be", 32) ]); await this.sendApdu(o.DEPLOY_ACCOUNT, 1, 0, g); const u = e(r.tip), { l1_gas: A, l2_gas: w, l1_data_gas: C } = this.encodeResourceBounds(r.resourceBounds); g = new Uint8Array([ ...u.toArray("be", 32), ...A.toArray("be", 32), ...w.toArray("be", 32), ...C.toArray("be", 32) ]), await this.sendApdu(o.DEPLOY_ACCOUNT, 2, 0, g), await this.sendApdu(o.DEPLOY_ACCOUNT, 3, 0, new Uint8Array(0)); let i = e(r.constructor_calldata.length); g = new Uint8Array([...i.toArray("be", 32)]), await this.sendApdu(o.DEPLOY_ACCOUNT, 4, 0, g); let d, N = { returnCode: c.ExecutionError, errorMessage: "Execution Error", h: new Uint8Array(0), r: new Uint8Array(0), s: new Uint8Array(0), v: 0 }, U = new Uint8Array(r.constructor_calldata.length * 32); r.constructor_calldata.forEach((l, f) => { e(l).toArray("be", 32).forEach((I, h) => U[32 * f + h] = I); }); let y = []; for (let l = 0; l < U.length; l += 7 * 32) y.push(U.slice(l, l + 7 * 32)); for (const l of y) if (d = await this.sendApdu(o.DEPLOY_ACCOUNT, 5, 0, l), d.returnCode !== c.NoError) return N; return (d == null ? void 0 : d.returnCode) === c.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] } : N; } /** * 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(t, r) { await this.sendApdu(o.DEPLOY_ACCOUNT_V1, 0, 0, T(t)); const a = e(r.contractAddress), s = e(r.class_hash), n = e(r.contract_address_salt), E = e(r.max_fee), b = e(r.chainId), _ = e(r.nonce); let g = new Uint8Array([ ...a.toArray("be", 32), ...s.toArray("be", 32), ...n.toArray("be", 32), ...E.toArray("be", 32), ...b.toArray("be", 32), ..._.toArray("be", 32) ]); await this.sendApdu(o.DEPLOY_ACCOUNT_V1, 1, 0, g); let u = e(r.constructor_calldata.length); g = new Uint8Array([...u.toArray("be", 32)]), await this.sendApdu(o.DEPLOY_ACCOUNT_V1, 2, 0, g); let A, w = { returnCode: c.ExecutionError, errorMessage: "Execution Error", h: new Uint8Array(0), r: new Uint8Array(0), s: new Uint8Array(0), v: 0 }, C = new Uint8Array(r.constructor_calldata.length * 32); r.constructor_calldata.forEach((d, N) => { e(d).toArray("be", 32).forEach((y, l) => C[32 * N + l] = y); }); let i = []; for (let d = 0; d < C.length; d += 7 * 32) i.push(C.slice(d, d + 7 * 32)); for (const d of i) if (A = await this.sendApdu(o.DEPLOY_ACCOUNT_V1, 3, 0, d), A.returnCode !== c.NoError) return w; return (A == null ? void 0 : A.returnCode) === c.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] } : w; } /** * 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(t, r, a) { const s = K.getMessageHash(r, a); return this.signHash(t, s); } encodeResourceBounds(t) { const r = BigInt(64), a = BigInt(128), s = r + a, n = BigInt(m.encodeShortString("L1_GAS")), E = BigInt(m.encodeShortString("L2_GAS")), b = BigInt(m.encodeShortString("L1_DATA")), _ = (n << s) + (BigInt(t.l1_gas.max_amount) << a) + BigInt(t.l1_gas.max_price_per_unit), g = (E << s) + (BigInt(t.l2_gas.max_amount) << a) + BigInt(t.l2_gas.max_price_per_unit), u = (b << s) + (BigInt(t.l1_data_gas.max_amount) << a) + BigInt(t.l1_data_gas.max_price_per_unit); return { l1_gas: e(_), l2_gas: e(g), l1_data_gas: e(u) }; } encodeDataAvailabilityMode(t, r) { const a = BigInt(32), s = t === O.L1 ? 0 : 1, n = r === O.L1 ? 0 : 1; return (BigInt(s) << a) + BigInt(n); } } export { c as LedgerError, W as StarknetClient };