Skip to content
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
6a2302a
chore: version packages to 1.62.32-starknet.0
NeOMakinG Dec 22, 2025
dca2f76
fix: cleanup shit
gomesalexandre Dec 24, 2025
1917f29
fix: restore starknet dependencies to hdwallet-native
gomesalexandre Dec 24, 2025
6c6b329
chore: version packages to 1.62.33-starknet.0
gomesalexandre Dec 24, 2025
45b0493
fix: ci
gomesalexandre Dec 24, 2025
deb8f8e
[skip ci] wip: skip ci
gomesalexandre Dec 24, 2025
87270a8
fix: ci
gomesalexandre Dec 24, 2025
a2ce8d8
chore: version packages to 1.62.33-starknet.1
gomesalexandre Dec 24, 2025
9522d96
fix: sig length
gomesalexandre Dec 24, 2025
d2979c0
feat: don't pollute bip32 node
gomesalexandre Dec 24, 2025
43949d3
chore: version packages to 1.62.33-starknet.2
gomesalexandre Dec 24, 2025
601c369
feat: add comprehensive logging for starknet debugging
gomesalexandre Dec 24, 2025
95aff1c
chore: version packages to 1.62.33-starknet.3
gomesalexandre Dec 24, 2025
754eee5
fix: make starkMasterKey optional in initialize
gomesalexandre Dec 24, 2025
9b8ee2b
chore: version packages to 1.62.33-starknet.4
gomesalexandre Dec 24, 2025
41c6e7a
feat: add initialization logging
gomesalexandre Dec 24, 2025
1a84c9b
chore: version packages to 1.62.33-starknet.5
gomesalexandre Dec 24, 2025
e71eaa3
fix: stark master key initialization in constructor
gomesalexandre Dec 24, 2025
63b360d
chore: version packages to 1.62.33-starknet.6
gomesalexandre Dec 24, 2025
cd03b76
chore: version packages to 1.62.33-starknet.6
gomesalexandre Dec 24, 2025
2d43148
fix: add stark master key to loadDevice
gomesalexandre Dec 24, 2025
81966c7
chore: version packages to 1.62.33-starknet.8
gomesalexandre Dec 24, 2025
1a2fc8c
fix: support non-hardened derivation in stark engine
gomesalexandre Dec 24, 2025
6f8d24e
chore: version packages to 1.62.33-starknet.9
gomesalexandre Dec 24, 2025
ae0cfa5
fix: compute actual contract address instead of returning public key
gomesalexandre Dec 24, 2025
e3d0a98
chore: version packages to 1.62.33-starknet.10
gomesalexandre Dec 24, 2025
dce57a5
docs: add STARK curve and Starknet spec references
gomesalexandre Dec 24, 2025
746cf06
docs: add comprehensive Stark curve documentation and references
gomesalexandre Dec 24, 2025
48b924f
docs: add comments explaining Stark master key in bip32, dummy, and a…
gomesalexandre Dec 24, 2025
bac3fa7
refactor: clean up debugging logs and move stark init back to Promise…
gomesalexandre Dec 24, 2025
fb79a67
fix: pad Starknet public keys to 64 hex chars
gomesalexandre Dec 24, 2025
ef327d7
chore: version packages to 1.62.33-starknet.11
gomesalexandre Dec 24, 2025
6b84173
fix: pad contract address to 64 hex chars
gomesalexandre Dec 24, 2025
b44b240
chore: version packages to 1.62.33-starknet.12
gomesalexandre Dec 24, 2025
4dc33a9
chore: remove debug logging
gomesalexandre Dec 24, 2025
11f947d
chore: version packages to 1.62.33-starknet.13
gomesalexandre Dec 24, 2025
7be85cb
chore: version packages to 1.62.33-starknet.14
gomesalexandre Dec 24, 2025
8feaab6
chore: revert versions to 1.62.32 and use npmjs registry
gomesalexandre Dec 24, 2025
bf77265
fix: remove carets from 1.62.32 versions (should be exact)
gomesalexandre Dec 24, 2025
2a80641
chore: pin all dependency versions across all packages
gomesalexandre Dec 24, 2025
cfb2b6a
feat: clean shit up
gomesalexandre Dec 24, 2025
ffafa43
fix: uh oh
gomesalexandre Dec 24, 2025
c1b54f1
fix: add starkMasterKey to native wallet tests
gomesalexandre Dec 24, 2025
2b1c409
style: format loadDevice calls in native tests
gomesalexandre Dec 24, 2025
2c1b669
chore(release): publish 1.62.33
gomesalexandre Dec 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/hdwallet-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export * from "./secret";
export * from "./terra";
export * from "./thorchain";
export * from "./solana";
export * from "./starknet";
export * from "./sui";
export * from "./transport";
export * from "./tron";
Expand Down
73 changes: 73 additions & 0 deletions packages/hdwallet-core/src/starknet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { addressNListToBIP32, slip44ByCoin } from "./utils";
import { BIP32Path, HDWallet, HDWalletInfo, PathDescription } from "./wallet";

export interface StarknetGetAddress {
addressNList: BIP32Path;
showDisplay?: boolean;
}

export interface StarknetGetPublicKey {
addressNList: BIP32Path;
}

export interface StarknetSignTx {
addressNList: BIP32Path;
txHash: string;
}

export interface StarknetSignedTx {
signature: string[];
}

export interface StarknetGetAccountPaths {
accountIdx: number;
}

export interface StarknetAccountPath {
addressNList: BIP32Path;
}

export interface StarknetWalletInfo extends HDWalletInfo {
readonly _supportsStarknetInfo: boolean;

starknetGetAccountPaths(msg: StarknetGetAccountPaths): Array<StarknetAccountPath>;
starknetNextAccountPath(msg: StarknetAccountPath): StarknetAccountPath | undefined;
}

export interface StarknetWallet extends StarknetWalletInfo, HDWallet {
readonly _supportsStarknet: boolean;

starknetGetAddress(msg: StarknetGetAddress): Promise<string | null>;
starknetGetPublicKey(msg: StarknetGetPublicKey): Promise<string | null>;
starknetSignTx(msg: StarknetSignTx): Promise<StarknetSignedTx | null>;
}

export function starknetDescribePath(path: BIP32Path): PathDescription {
const pathStr = addressNListToBIP32(path);
const unknown: PathDescription = {
verbose: pathStr,
coin: "Starknet",
isKnown: false,
};

if (path.length != 5) return unknown;
if (path[0] != 0x80000000 + 44) return unknown;
if (path[1] != 0x80000000 + slip44ByCoin("Starknet")) return unknown;
if ((path[2] & 0x80000000) >>> 0 !== 0x80000000) return unknown;
if (path[3] !== 0) return unknown;
if (path[4] !== 0) return unknown;

const index = path[2] & 0x7fffffff;
return {
verbose: `Starknet Account #${index}`,
accountIdx: index,
wholeAccount: true,
coin: "Starknet",
isKnown: true,
};
}
Comment thread
gomesalexandre marked this conversation as resolved.

export function starknetGetAccountPaths(msg: StarknetGetAccountPaths): Array<StarknetAccountPath> {
const slip44 = slip44ByCoin("Starknet");
return [{ addressNList: [0x80000000 + 44, 0x80000000 + slip44, 0x80000000 + msg.accountIdx, 0, 0] }];
}
1 change: 1 addition & 0 deletions packages/hdwallet-core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export const slip44Table = Object.freeze({
Terra: 330,
Kava: 459,
Solana: 501,
Starknet: 9004,
Tron: 195,
Sui: 784,
// EVM chains all use the same SLIP44
Expand Down
9 changes: 9 additions & 0 deletions packages/hdwallet-core/src/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { OsmosisWallet, OsmosisWalletInfo } from "./osmosis";
import { RippleWallet, RippleWalletInfo } from "./ripple";
import { SecretWallet, SecretWalletInfo } from "./secret";
import { SolanaWallet, SolanaWalletInfo } from "./solana";
import { StarknetWallet, StarknetWalletInfo } from "./starknet";
import { SuiWallet, SuiWalletInfo } from "./sui";
import { TerraWallet, TerraWalletInfo } from "./terra";
import { ThorchainWallet, ThorchainWalletInfo } from "./thorchain";
Expand Down Expand Up @@ -271,6 +272,14 @@ export function infoSolana(info: HDWalletInfo): info is SolanaWalletInfo {
return isObject(info) && (info as any)._supportsSolanaInfo;
}

export function supportsStarknet(wallet: HDWallet): wallet is StarknetWallet {
return isObject(wallet) && (wallet as any)._supportsStarknet;
}

export function infoStarknet(info: HDWalletInfo): info is StarknetWalletInfo {
return isObject(info) && (info as any)._supportsStarknetInfo;
}

export function supportsTron(wallet: HDWallet): wallet is TronWallet {
return isObject(wallet) && (wallet as any)._supportsTron;
}
Expand Down
2 changes: 2 additions & 0 deletions packages/hdwallet-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"dependencies": {
"@bitcoinerlab/secp256k1": "^1.1.1",
"@noble/curves": "^1.4.0",
"@scure/starknet": "^1.0.0",
Comment thread
gomesalexandre marked this conversation as resolved.
"@shapeshiftoss/bitcoinjs-lib": "7.0.0-shapeshift.2",
"@shapeshiftoss/hdwallet-core": "1.62.32",
"@shapeshiftoss/proto-tx-builder": "0.10.0",
Expand All @@ -38,6 +39,7 @@
"node-fetch": "^2.6.1",
"p-lazy": "^3.1.0",
"scrypt-js": "^3.0.1",
"starknet": "^6.11.0",
"tendermint-tx-builder": "1.0.16"
},
"devDependencies": {
Expand Down
17 changes: 17 additions & 0 deletions packages/hdwallet-native/src/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,35 @@ import * as core from "@shapeshiftoss/hdwallet-core";
import * as Isolation from "./crypto/isolation";
import * as native from "./native";

/**
* NativeAdapter arguments
*
* Supports two initialization modes:
* 1. From mnemonic: Wallet derives all master keys (secp256k1, ed25519, stark)
* 2. From master keys: Externally-provided master keys for each curve type
*
* MASTER KEY TYPES:
* - secp256k1MasterKey: For Bitcoin, Ethereum, Tron, Cosmos chains
* - ed25519MasterKey: For Solana, Sui chains
* - starkMasterKey: For Starknet (uses STARK curve, separate from secp256k1)
*
* Note: Starknet uses secp256k1 BIP32 derivation math but STARK curve for signing,
* requiring a separate master key type. See engines/default/stark.ts for details.
*/
export type NativeAdapterArgs = {
deviceId: string;
} & (
| {
mnemonic?: string | Isolation.Core.BIP39.Mnemonic;
secp256k1MasterKey?: never;
ed25519MasterKey?: never;
starkMasterKey?: never;
}
| {
mnemonic?: never;
secp256k1MasterKey?: Isolation.Core.BIP32.Node;
ed25519MasterKey?: Isolation.Core.Ed25519.Node;
starkMasterKey?: Isolation.Core.Stark.Node;
}
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
export { default as Bitcoin } from "./bitcoin";
export { default as BIP32 } from "./bip32";
export { default as Ed25519 } from "./ed25519";
export { default as Stark } from "./stark";
export { default as Ethereum } from "./ethereum";
export { default as Binance } from "./binance";
export { default as Cosmos } from "./cosmos";
export { default as CosmosDirect } from "./cosmosDirect";
export { default as Solana } from "./solana";
export { default as Tron } from "./tron";
export { default as Starknet } from "./starknet";
export { default as Sui } from "./sui";
export { default as Tron } from "./tron";
43 changes: 43 additions & 0 deletions packages/hdwallet-native/src/crypto/isolation/adapters/stark.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Stark } from "../core";

/**
* Stark curve adapter for Starknet
*
* Simple wrapper around Stark.Node providing derivation utilities.
* The Stark Node handles all Starknet-specific operations (key grinding,
* STARK curve public keys, and ECDSA signing on STARK curve).
*
* STARK Curve Specifications:
* - Curve type: Weierstrass (y² = x³ + ax + b)
* - Prime field (p): 2^251 + 17×2^192 + 1 (251-bit)
* - Curve parameters: a = 1, b = π (3141592...)
* - Curve order (n): 252-bit
*
* References:
* - STARK Curve Spec: https://docs.starkware.co/starkex/crypto/stark-curve.html
* - Key Derivation: https://docs.starkware.co/starkex/crypto/key-derivation.html
* - Account Standard: https://community.starknet.io/t/account-keys-and-addresses-derivation-standard/1230
* - Starknet Cryptography: https://docs.starknet.io/architecture/cryptography/
* - @scure/starknet: https://github.com/paulmillr/scure-starknet
*/
export class StarkAdapter {
readonly node: Stark.Node;

constructor(node: Stark.Node) {
this.node = node;
}

async getPublicKey(): Promise<string> {
return this.node.getPublicKey();
}

async derive(index: number): Promise<this> {
return new StarkAdapter(await this.node.derive(index)) as this;
}

async derivePath(path: string): Promise<StarkAdapter> {
return Stark.derivePath<StarkAdapter>(this, path);
}
}

export default StarkAdapter;
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import * as core from "@shapeshiftoss/hdwallet-core";
import { CallData, hash } from "starknet";

import { Isolation } from "../..";

/**
* Starknet adapter using Stark curve engine
*
* Uses dedicated Stark engine with STARK curve operations instead of
* polluting the generic BIP32 secp256k1 engine with chain-specific code.
*
* ACCOUNT ABSTRACTION:
* All Starknet accounts are smart contracts (no EOAs). The account address is computed from:
* - Account contract class hash (OpenZeppelin, Argent, Braavos implementations)
* - Constructor calldata (including public key)
* - Salt (typically the public key)
* - Deployer address (0 for counterfactual deployment)
*
* DERIVATION PATH:
* Uses BIP-44 format: m/44'/9004'/accountIdx'/0/0
* - SLIP-044 coin type: 9004 (Starknet)
* - Community consensus: BIP-44 preferred over EIP-2645
* - See: https://community.starknet.io/t/account-keys-and-addresses-derivation-standard/1230
*
* REFERENCES:
* - Starknet Account Architecture: https://docs.starknet.io/architecture/accounts/
* - Account Abstraction Part I: https://medium.com/starknet-edu/account-abstraction-on-starknet-part-i-2ff84c6a3c30
* - Account Abstraction Part II: https://medium.com/starknet-edu/account-abstraction-on-starknet-part-ii-24d52874e0bd
* - Account Creation Guide: https://starknetjs.com/docs/guides/create_account/
* - OpenZeppelin Accounts: https://docs.openzeppelin.com/contracts-cairo/0.14.0/accounts
* - SLIP-044 Registry: https://github.com/satoshilabs/slips/blob/master/slip-0044.md
*/
export class StarknetAdapter {
protected readonly nodeAdapter: Isolation.Adapters.Stark;

// OpenZeppelin account v0.15.0-rc.0 class hash (used by most wallets)
// See: https://docs.openzeppelin.com/contracts-cairo/0.14.0/accounts
private readonly OZ_ACCOUNT_CLASS_HASH = "0x05b4b537eaa2399e3aa99c4e2e0208ebd6c71bc1467938cd52c798c601e43564";

constructor(nodeAdapter: Isolation.Adapters.Stark) {
this.nodeAdapter = nodeAdapter;
}

/**
* Get Starknet public key from BIP32 path
*/
async getPublicKey(addressNList: core.BIP32Path): Promise<string> {
const bip32Path = core.addressNListToBIP32(addressNList);
const nodeAdapter = await this.nodeAdapter.derivePath(bip32Path);
const publicKey = await nodeAdapter.getPublicKey();
return publicKey;
}

/**
* Get Starknet address from BIP32 path
* Computes the counterfactual contract address using OpenZeppelin account implementation
*/
async getAddress(addressNList: core.BIP32Path): Promise<string> {
const bip32Path = core.addressNListToBIP32(addressNList);
const nodeAdapter = await this.nodeAdapter.derivePath(bip32Path);
const publicKey = await nodeAdapter.getPublicKey();

// Compute actual contract address using OpenZeppelin account contract
const constructorCalldata = CallData.compile({ publicKey });
const contractAddress = hash.calculateContractAddressFromHash(
publicKey,
this.OZ_ACCOUNT_CLASS_HASH,
constructorCalldata,
0
);

// Ensure contract address is zero-padded to 64 hex chars (Starknet spec)
const paddedAddress = contractAddress.startsWith("0x")
? "0x" + contractAddress.slice(2).padStart(64, "0")
: "0x" + contractAddress.padStart(64, "0");

return paddedAddress;
}

/**
* Sign Starknet transaction
* Starknet uses ECDSA on the STARK curve for transaction signing
*/
async signTransaction(txHash: string, addressNList: core.BIP32Path): Promise<string[]> {
const bip32Path = core.addressNListToBIP32(addressNList);
const nodeAdapter = await this.nodeAdapter.derivePath(bip32Path);
const signature = await nodeAdapter.node.sign(txHash);
return [signature.r, signature.s];
}
}

export default StarknetAdapter;
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as core from "@shapeshiftoss/hdwallet-core";

import { BIP32, Ed25519, Revocable } from "..";
import { BIP32, Ed25519, Revocable, Stark } from "..";
import * as SecP256K1 from "../secp256k1";
import { ChainCode } from ".";

export interface Seed extends Partial<Revocable> {
toSecp256k1MasterKey(): Promise<BIP32.Node>;
toEd25519MasterKey(): Promise<Ed25519.Node>;
toStarkMasterKey(): Promise<Stark.Node>;
}

export interface Node extends Partial<Revocable>, SecP256K1.ECDSAKey, Partial<SecP256K1.ECDHKey> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * as BIP39 from "./bip39";
export * as Digest from "./digest";
export * as SecP256K1 from "./secp256k1";
export * as Ed25519 from "./ed25519";
export * as Stark from "./stark";

export class IsolationError extends Error {
constructor(name: string) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { derivePath } from "../bip32";
export * from "./interfaces";
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Revocable } from "..";
import { ChainCode } from "../bip32";

/**
* Stark curve Node interface for Starknet
*
* OVERVIEW:
* Implements hierarchical deterministic key derivation for Starknet using the STARK curve.
* Uses standard BIP32 secp256k1 derivation math (per SLIP-0010 and industry standard),
* then applies Starknet-specific key grinding for public key generation and signing.
*
* STARK CURVE:
* - Weierstrass curve: y² = x³ + ax + b (mod p)
* - Prime field: p = 2^251 + 17×2^192 + 1 (251-bit, not 256-bit like secp256k1)
* - Parameters: a = 1, b = π
* - Different from secp256k1, requires separate implementation
* - Spec: https://docs.starkware.co/starkex/crypto/stark-curve.html
*
* KEY METHODS:
* - getPublicKey(): Returns Stark public key (after grinding) as hex string
* - sign(): ECDSA signature on STARK curve, returns {r, s} with 64-char hex padding
* - derive(): BIP32 child key derivation using secp256k1 field arithmetic
*
* REFERENCES:
* - STARK Curve: https://docs.starkware.co/starkex/crypto/stark-curve.html
* - Key Grinding: https://docs.starkware.co/starkex/crypto/key-derivation.html
* - Starknet Cryptography: https://docs.starknet.io/architecture/cryptography/
* - SLIP-0010: https://github.com/satoshilabs/slips/blob/master/slip-0010.md
*/
export interface Node extends Partial<Revocable> {
readonly explicitPath?: string;
/** Get Stark public key (applies key grinding, returns hex string) */
getPublicKey(): Promise<string>;
/** Get BIP32 chain code for derivation */
getChainCode(): Promise<ChainCode>;
/** Derive child key using BIP32 secp256k1 math (per industry standard) */
derive(index: number): Promise<this>;
/** Sign transaction hash on STARK curve (returns padded 64-char hex r,s) */
sign(txHash: string): Promise<{ r: string; s: string }>;
}
Loading