diff --git a/packages/hdwallet-native/package.json b/packages/hdwallet-native/package.json index 7fe975723..e372f1fb6 100644 --- a/packages/hdwallet-native/package.json +++ b/packages/hdwallet-native/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "@bitcoinerlab/secp256k1": "^1.1.1", - "@noble/ed25519": "^1.7.3", + "@noble/curves": "^1.7.3", "@shapeshiftoss/bitcoinjs-lib": "7.0.0-shapeshift.0", "@shapeshiftoss/fiosdk": "1.2.1-shapeshift.6", "@shapeshiftoss/hdwallet-core": "1.57.1", diff --git a/packages/hdwallet-native/src/crypto/isolation/adapters/bip32ed25519.ts b/packages/hdwallet-native/src/crypto/isolation/adapters/bip32ed25519.ts index 61ea41008..05f5bb01f 100644 --- a/packages/hdwallet-native/src/crypto/isolation/adapters/bip32ed25519.ts +++ b/packages/hdwallet-native/src/crypto/isolation/adapters/bip32ed25519.ts @@ -1,138 +1,40 @@ -import * as core from "@shapeshiftoss/hdwallet-core"; - -import { BIP32, IsolationError } from "../core"; -import { Path } from "../core/bip32/types"; import { Ed25519Node } from "../core/ed25519"; import { ByteArray } from "../types"; export class BIP32Ed25519Adapter { readonly node: Ed25519Node; - readonly _chainCode: BIP32.ChainCode; - readonly _publicKey: ByteArray; - readonly index: number; - readonly _parent?: BIP32Ed25519Adapter; - readonly _children = new Map(); - readonly _explicitPath?: string; - protected constructor( - node: Ed25519Node, - chainCode: BIP32.ChainCode, - publicKey: ByteArray, - parent?: BIP32Ed25519Adapter, - index?: number - ) { + private constructor(node: Ed25519Node) { this.node = node; - this._chainCode = chainCode; - this._publicKey = publicKey; - this.index = index ?? 0; - this._parent = parent; - if (node.explicitPath) { - Path.assert(node.explicitPath); - this._explicitPath = node.explicitPath; - } } static async fromNode(node: Ed25519Node): Promise { - const ed25519Node = await Ed25519Node.fromIsolatedNode(node); - return await BIP32Ed25519Adapter.create(ed25519Node); - } - - static async create( - isolatedNode: Ed25519Node, - parent?: BIP32Ed25519Adapter, - index?: number - ): Promise { - return new BIP32Ed25519Adapter( - isolatedNode, - await isolatedNode.getChainCode(), - await isolatedNode.getPublicKey(), - parent, - index - ); - } - - get depth(): number { - return this.path ? core.bip32ToAddressNList(this.path).length : 0; - } - - get chainCode() { - return Buffer.from(this._chainCode) as Buffer & BIP32.ChainCode; - } - - getChainCode() { - return this.chainCode; - } - - get path(): string { - if (this._explicitPath) return this._explicitPath; - if (!this._parent) return ""; - let parentPath = this._parent.path ?? ""; - if (parentPath === "") parentPath = "m"; - // Ed25519 only supports hardened derivation - const index = this.index - 0x80000000; - return `${parentPath}/${index}'`; + return new BIP32Ed25519Adapter(node); } - get publicKey() { - return Buffer.from(this._publicKey); - } - - getPublicKey() { - return this.publicKey; - } - - isNeutered() { - return false; - } - - async derive(index: number): Promise { - let out = this._children.get(index); - if (!out) { - // Ed25519 requires hardened derivation - if (index < 0x80000000) { - index += 0x80000000; - } - const childNode = await this.node.derive(index); - out = (await BIP32Ed25519Adapter.create(childNode, this, index)) as this; - this._children.set(index, out); - } - return out; - } - - async deriveHardened(index: number): Promise { - return this.derive(index + 0x80000000); + async getPublicKey(): Promise { + const publicKey = await this.node.getPublicKey(); + return Buffer.from(publicKey); } async derivePath(path: string): Promise { - if (this._explicitPath) { - if (!(path.startsWith(this._explicitPath) && path.length >= this._explicitPath.length)) { - throw new Error("path is not a child of this node"); - } + let currentNode = this.node; + + if (path === "m" || path === "M" || path === "m'" || path === "M'") { + return this; } - const ownPath = this.path; - if (path.startsWith(ownPath)) path = path.slice(ownPath.length); - if (path.startsWith("/")) path = path.slice(1); - if (/^m/.test(path) && this._parent) throw new Error("expected master, got child"); const segments = path - .replace("m/", "") // Remove the 'm/' prefix from bip44, we're only interested in akschual parts not root m/ path + .toLowerCase() .split("/") - .filter(Boolean) - .map((segment) => { - const hardened = segment.endsWith("'"); - const index = parseInt(hardened ? segment.slice(0, -1) : segment); - // Ed25519 requires hardened derivation, so all indices should be hardened - return index + 0x80000000; - }); + .filter((segment) => segment !== "m"); - return segments.reduce( - async (promise: Promise, index) => (await promise).derive(index), - Promise.resolve(this) - ); - } + for (const segment of segments) { + const index = parseInt(segment.replace("'", "")); + currentNode = await currentNode.derive(index); + } - toBase58(): never { - throw new IsolationError("xprv"); + return new BIP32Ed25519Adapter(currentNode); } } diff --git a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts index e4d5a320c..85ccdb086 100644 --- a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts +++ b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts @@ -12,17 +12,20 @@ export class SolanaDirectAdapter { async getAddress(addressNList: core.BIP32Path): Promise { const nodeAdapter = await this.nodeAdapter.derivePath(core.addressNListToBIP32(addressNList)); - const publicKeyBuffer = nodeAdapter.getPublicKey(); + const publicKeyBuffer = await nodeAdapter.getPublicKey(); - // PublicKey constructor in Solana expects the key in big-endian format - return new PublicKey(publicKeyBuffer).toBase58(); + const bufferForHex = Buffer.from(publicKeyBuffer); + + const pubKey = new PublicKey(bufferForHex); + + return pubKey.toBase58(); } + async signDirect(transaction: VersionedTransaction, addressNList: core.BIP32Path): Promise { const nodeAdapter = await this.nodeAdapter.derivePath(core.addressNListToBIP32(addressNList)); const pubkey = await this.getAddress(addressNList); const messageToSign = transaction.message.serialize(); - const signature = await nodeAdapter.node.sign(messageToSign); transaction.addSignature(new PublicKey(pubkey), signature); diff --git a/packages/hdwallet-native/src/crypto/isolation/core/ed25519/index.ts b/packages/hdwallet-native/src/crypto/isolation/core/ed25519/index.ts index 0d84d3d14..ade9dfe7e 100644 --- a/packages/hdwallet-native/src/crypto/isolation/core/ed25519/index.ts +++ b/packages/hdwallet-native/src/crypto/isolation/core/ed25519/index.ts @@ -1,14 +1,9 @@ -import { - ExtendedPoint, - getPublicKey as nobleGetPublicKey, - sign as nobleSign, - verify as nobleVerify, -} from "@noble/ed25519"; -import { createHmac } from "crypto"; +import { ExtPointConstructor } from "@noble/curves/abstract/edwards"; +import { ed25519 } from "@noble/curves/ed25519"; +import * as bip32crypto from "bip32/src/crypto"; import { Revocable, revocable } from "../../engines/default/revocable"; import { ByteArray } from "../../types"; -import { ChainCode } from "../bip32/types"; export type Ed25519Key = { getPublicKey(): Promise; @@ -16,60 +11,35 @@ export type Ed25519Key = { verify(message: Uint8Array, signature: Uint8Array): Promise; }; -export class Ed25519Node extends Revocable(class {}) implements Ed25519Key { - readonly #privateKey: Buffer; - readonly #chainCode: Buffer; +export class Ed25519Node extends Revocable(class {}) { + readonly #privateKey: ByteArray; // Changed to privateKey + readonly #chainCode: ByteArray; readonly explicitPath?: string; - protected constructor(privateKey: Uint8Array, chainCode: Uint8Array, explicitPath?: string) { + protected constructor(privateKey: ByteArray, chainCode: ByteArray, explicitPath?: string) { super(); - // We avoid handing the private key to any non-platform code - if (privateKey.length !== 32) throw new Error("bad private key length"); - if (chainCode.length !== 32) throw new Error("bad chain code length"); - - this.#privateKey = Buffer.from(privateKey); - this.#chainCode = Buffer.from(chainCode); + this.#privateKey = privateKey; + this.#chainCode = chainCode; this.explicitPath = explicitPath; - - this.addRevoker(() => { - this.#privateKey.fill(0); - this.#chainCode.fill(0); - }); - } - static async fromIsolatedNode(node: any): Promise { - return new Proxy(node, { - get(target, prop) { - if (prop === "sign") { - return async (message: Uint8Array) => { - // Use the isolated node's signing capabilities - return target.sign(message); - }; - } - return target[prop]; - }, - }); } - static async create(privateKey: Uint8Array, chainCode: Uint8Array, explicitPath?: string): Promise { + static async create(privateKey: ByteArray, chainCode: ByteArray, explicitPath?: string): Promise { const obj = new Ed25519Node(privateKey, chainCode, explicitPath); return revocable(obj, (x) => obj.addRevoker(x)); } async getPublicKey(): Promise { - return nobleGetPublicKey(this.#privateKey); - } - - async sign(message: Uint8Array): Promise { - return nobleSign(message, this.#privateKey); + // Generate public key using noble-curves ed25519 + return Buffer.from(ed25519.getPublicKey(this.#privateKey)); } - async verify(message: Uint8Array, signature: Uint8Array): Promise { - const publicKey = await this.getPublicKey(); - return nobleVerify(signature, message, publicKey); + async getChainCode(): Promise { + return this.#chainCode; } - async getChainCode(): Promise { - return this.#chainCode as Buffer & ChainCode; + async sign(message: Uint8Array): Promise { + // Sign using noble-curves ed25519 + return Buffer.from(ed25519.sign(message, this.#privateKey)); } async derive(index: number): Promise { @@ -81,16 +51,14 @@ export class Ed25519Node extends Revocable(class {}) implements Ed25519Key { const indexBuffer = Buffer.alloc(4); indexBuffer.writeUInt32BE(index, 0); - const data = Buffer.concat([Buffer.from([0x00]), this.#privateKey, indexBuffer]); - - const hmac = createHmac("sha512", this.#chainCode); - hmac.update(data); - const I = hmac.digest(); + // SLIP-0010 Ed25519 derivation + const data = Buffer.concat([Buffer.from([0x00]), Buffer.from(this.#privateKey), indexBuffer]); + const I = bip32crypto.hmacSHA512(Buffer.from(this.#chainCode), data); const IL = I.slice(0, 32); const IR = I.slice(32); - // Apply clamping as per RFC 8032 + // Ed25519 clamping IL[0] &= 0xf8; IL[31] &= 0x7f; IL[31] |= 0x40; @@ -103,8 +71,8 @@ export class Ed25519Node extends Revocable(class {}) implements Ed25519Key { } } -export type Point = ExtendedPoint; +export type Point = ExtPointConstructor; export const Ed25519Point = { - BASE_POINT: nobleGetPublicKey(new Uint8Array(32)), + BASE_POINT: ed25519.getPublicKey(new Uint8Array(32)), }; -export type { ExtendedPoint }; +export type { Point as ExtendedPoint };