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