@ledgerhq/hw-app-starknet
Version:
Ledger Hardware Wallet Starknet Application API
378 lines (377 loc) • 14.5 kB
JavaScript
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
};