From 9dbcc456b1992bec1dd0094a815da81d8fda629e Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 19 Dec 2024 00:28:19 +0300 Subject: [PATCH 01/24] wip: solana native support --- .../src/crypto/isolation/adapters/index.ts | 1 + .../src/crypto/isolation/adapters/solana.ts | 43 +++++++++++ packages/hdwallet-native/src/native.ts | 28 ++++--- packages/hdwallet-native/src/solana.ts | 76 +++++++++++++++++++ 4 files changed, 139 insertions(+), 9 deletions(-) create mode 100644 packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts create mode 100644 packages/hdwallet-native/src/solana.ts diff --git a/packages/hdwallet-native/src/crypto/isolation/adapters/index.ts b/packages/hdwallet-native/src/crypto/isolation/adapters/index.ts index 58a1b28ce..d6ef2914d 100644 --- a/packages/hdwallet-native/src/crypto/isolation/adapters/index.ts +++ b/packages/hdwallet-native/src/crypto/isolation/adapters/index.ts @@ -5,3 +5,4 @@ export { default as FIO } from "./fio"; export { default as Binance } from "./binance"; export { default as Cosmos } from "./cosmos"; export { default as CosmosDirect } from "./cosmosDirect"; +export { default as SolanaDirect } from "./solana"; diff --git a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts new file mode 100644 index 000000000..55b7c21b2 --- /dev/null +++ b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts @@ -0,0 +1,43 @@ +import { PublicKey, Transaction, VersionedTransaction } from "@solana/web3.js"; + +import { SecP256K1 } from "../core"; + +export class SolanaDirectAdapter { + protected readonly _isolatedKey: SecP256K1.ECDSAKey; + protected readonly _pubkey: Uint8Array; + readonly address: string; + + protected constructor(isolatedKey: SecP256K1.ECDSAKey, pubkey: Uint8Array, address: string) { + this._isolatedKey = isolatedKey; + this._pubkey = pubkey; + this.address = address; + } + + static async create(isolatedKey: SecP256K1.ECDSAKey): Promise { + const pubkey = await isolatedKey.getPublicKey(); + const address = new PublicKey(pubkey).toString(); + return new SolanaDirectAdapter(isolatedKey, pubkey, address); + } + + async signDirect(transaction: VersionedTransaction): Promise { + const pubkey = new PublicKey(this._pubkey); + + // Get the message to sign + const messageToSign = + transaction instanceof Transaction ? transaction.serializeMessage() : transaction.message.serialize(); + + // Sign using the isolated key + const signature = await this._isolatedKey.ecdsaSign("sha256", messageToSign); + + // Add the signature to the transaction + if (transaction instanceof Transaction) { + transaction.addSignature(pubkey, Buffer.from(signature)); + } else { + transaction.addSignature(pubkey, new Uint8Array(signature)); + } + + return transaction; + } +} + +export default SolanaDirectAdapter; diff --git a/packages/hdwallet-native/src/native.ts b/packages/hdwallet-native/src/native.ts index ce127db89..c7cf3bffd 100644 --- a/packages/hdwallet-native/src/native.ts +++ b/packages/hdwallet-native/src/native.ts @@ -15,6 +15,7 @@ import { MixinNativeKavaWallet, MixinNativeKavaWalletInfo } from "./kava"; import { getNetwork } from "./networks"; import { MixinNativeOsmosisWallet, MixinNativeOsmosisWalletInfo } from "./osmosis"; import { MixinNativeSecretWallet, MixinNativeSecretWalletInfo } from "./secret"; +import { MixinNativeSolanaWallet, MixinNativeSolanaWalletInfo } from "./solana"; import { MixinNativeTerraWallet, MixinNativeTerraWalletInfo } from "./terra"; import { MixinNativeThorchainWallet, MixinNativeThorchainWalletInfo } from "./thorchain"; @@ -123,11 +124,13 @@ class NativeHDWalletInfo MixinNativeETHWalletInfo( MixinNativeCosmosWalletInfo( MixinNativeBinanceWalletInfo( - MixinNativeThorchainWalletInfo( - MixinNativeSecretWalletInfo( - MixinNativeTerraWalletInfo( - MixinNativeKavaWalletInfo( - MixinNativeArkeoWalletInfo(MixinNativeOsmosisWalletInfo(NativeHDWalletBase)) + MixinNativeSolanaWalletInfo( + MixinNativeThorchainWalletInfo( + MixinNativeSecretWalletInfo( + MixinNativeTerraWalletInfo( + MixinNativeKavaWalletInfo( + MixinNativeArkeoWalletInfo(MixinNativeOsmosisWalletInfo(NativeHDWalletBase)) + ) ) ) ) @@ -163,6 +166,8 @@ class NativeHDWalletInfo case "rune": case "thorchain": return core.thorchainDescribePath(msg.path); + case "solana": + return core.solanaDescribePath(msg.path); case "secret": case "scrt": case "tscrt": @@ -195,10 +200,12 @@ export class NativeHDWallet MixinNativeETHWallet( MixinNativeCosmosWallet( MixinNativeBinanceWallet( - MixinNativeThorchainWallet( - MixinNativeSecretWallet( - MixinNativeTerraWallet( - MixinNativeKavaWallet(MixinNativeOsmosisWallet(MixinNativeArkeoWallet(NativeHDWalletInfo))) + MixinNativeSolanaWallet( + MixinNativeThorchainWallet( + MixinNativeSecretWallet( + MixinNativeTerraWallet( + MixinNativeKavaWallet(MixinNativeOsmosisWallet(MixinNativeArkeoWallet(NativeHDWalletInfo))) + ) ) ) ) @@ -215,6 +222,7 @@ export class NativeHDWallet core.OsmosisWallet, core.FioWallet, core.ThorchainWallet, + core.SolanaWallet, core.SecretWallet, core.TerraWallet, core.KavaWallet, @@ -236,6 +244,7 @@ export class NativeHDWallet readonly _supportsBinance = true; readonly _supportsFio = true; readonly _supportsThorchain = true; + readonly _supportsSolana = true; readonly _supportsSecret = true; readonly _supportsTerra = true; readonly _supportsKava = true; @@ -370,6 +379,7 @@ export class NativeHDWallet this.#initialized = false; this.#masterKey = undefined; + super.solanaWipe(); super.btcWipe(); super.ethWipe(); super.cosmosWipe(); diff --git a/packages/hdwallet-native/src/solana.ts b/packages/hdwallet-native/src/solana.ts new file mode 100644 index 000000000..fc53dc43d --- /dev/null +++ b/packages/hdwallet-native/src/solana.ts @@ -0,0 +1,76 @@ +import * as core from "@shapeshiftoss/hdwallet-core"; + +import * as Isolation from "./crypto/isolation"; +import SignerAdapter from "./crypto/isolation/adapters/ethereum"; +import { NativeHDWalletBase } from "./native"; +import * as util from "./util"; + +export function MixinNativeSolanaWalletInfo>(Base: TBase) { + // eslint-disable-next-line @typescript-eslint/no-shadow + return class MixinNativeSolanaWalletInfo extends Base implements core.SolanaWalletInfo { + readonly _supportsSolanaInfo = true; + _chainId = 1; + + solanaGetAccountPaths(msg: core.ETHGetAccountPath): Array { + return core.solanaGetAccountPaths(msg); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + solanaNextAccountPath(msg: core.SolanaAccountPath): core.SolanaAccountPath | undefined { + throw new Error("Method not implemented"); + } + }; +} + +export function MixinNativeSolanaWallet>(Base: TBase) { + // eslint-disable-next-line @typescript-eslint/no-shadow + return class MixinNativeSolanaWallet extends Base { + readonly _supportsSolana = true; + + #solanaSigner: SignerAdapter | undefined; + #masterKey: Isolation.Core.BIP32.Node | undefined; + + async solanaInitializeWallet(masterKey: Isolation.Core.BIP32.Node): Promise { + const nodeAdapter = await Isolation.Adapters.BIP32.create(masterKey); + + this.#solanaSigner = new SignerAdapter(nodeAdapter); + this.#masterKey = masterKey; + } + + solanaWipe() { + this.#solanaSigner = undefined; + } + + async solanaGetAddress(msg: core.ETHGetAddress): Promise { + return this.needsMnemonic(!!this.#solanaSigner, () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return this.#solanaSigner!.getAddress(msg.addressNList); + }); + } + + async solanaSignTx(msg: core.SolanaSignTx): Promise { + return this.needsMnemonic(!!this.#masterKey, async () => { + const keyPair = await util.getKeyPair(this.#masterKey!, msg.addressNList, "solana"); + + const adapter = await Isolation.Adapters.SolanaDirect.create(keyPair.node); + + const address = await this.solanaGetAddress({ + addressNList: msg.addressNList, + showDisplay: false, + }); + + if (!address) throw new Error("Failed to get Solana address"); + + const transaction = core.solanaBuildTransaction(msg, address); + const signedTx = await adapter.signDirect(transaction); + + const serializedData = signedTx.serialize(); + + return { + serialized: Buffer.from(serializedData).toString("base64"), + signatures: signedTx.signatures.map((signature) => Buffer.from(signature).toString("base64")), + }; + }); + } + }; +} From b15b11094dedf46336b46486ec7f8be2e266d5b5 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 19 Dec 2024 01:09:58 +0300 Subject: [PATCH 02/24] fix: shit --- packages/hdwallet-native/src/native.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/hdwallet-native/src/native.ts b/packages/hdwallet-native/src/native.ts index c7cf3bffd..db89e54db 100644 --- a/packages/hdwallet-native/src/native.ts +++ b/packages/hdwallet-native/src/native.ts @@ -338,6 +338,7 @@ export class NativeHDWallet super.binanceInitializeWallet(masterKey), super.fioInitializeWallet(masterKey), super.thorchainInitializeWallet(masterKey), + super.solanaInitializeWallet(masterKey), super.secretInitializeWallet(masterKey), super.terraInitializeWallet(masterKey), super.kavaInitializeWallet(masterKey), From 58237872e49ecfe3d363fdcb853ba6d35c5872df Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 19 Dec 2024 01:31:18 +0300 Subject: [PATCH 03/24] fix: shit --- .../src/crypto/isolation/adapters/solana.ts | 20 ++++++++----------- packages/hdwallet-native/src/solana.ts | 12 +++++------ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts index 55b7c21b2..67f235b04 100644 --- a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts +++ b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts @@ -1,4 +1,4 @@ -import { PublicKey, Transaction, VersionedTransaction } from "@solana/web3.js"; +import { PublicKey, VersionedTransaction } from "@solana/web3.js"; import { SecP256K1 } from "../core"; @@ -19,25 +19,21 @@ export class SolanaDirectAdapter { return new SolanaDirectAdapter(isolatedKey, pubkey, address); } + async getAddress(): Promise { + return this.address; + } + async signDirect(transaction: VersionedTransaction): Promise { const pubkey = new PublicKey(this._pubkey); - // Get the message to sign - const messageToSign = - transaction instanceof Transaction ? transaction.serializeMessage() : transaction.message.serialize(); + const messageToSign = transaction.message.serialize(); - // Sign using the isolated key const signature = await this._isolatedKey.ecdsaSign("sha256", messageToSign); - // Add the signature to the transaction - if (transaction instanceof Transaction) { - transaction.addSignature(pubkey, Buffer.from(signature)); - } else { - transaction.addSignature(pubkey, new Uint8Array(signature)); - } + transaction.addSignature(pubkey, new Uint8Array(signature)); return transaction; } } -export default SolanaDirectAdapter; +export { SolanaDirectAdapter as default }; diff --git a/packages/hdwallet-native/src/solana.ts b/packages/hdwallet-native/src/solana.ts index fc53dc43d..566e7a304 100644 --- a/packages/hdwallet-native/src/solana.ts +++ b/packages/hdwallet-native/src/solana.ts @@ -1,7 +1,7 @@ import * as core from "@shapeshiftoss/hdwallet-core"; import * as Isolation from "./crypto/isolation"; -import SignerAdapter from "./crypto/isolation/adapters/ethereum"; +import SignerAdapter from "./crypto/isolation/adapters/solana"; import { NativeHDWalletBase } from "./native"; import * as util from "./util"; @@ -31,20 +31,18 @@ export function MixinNativeSolanaWallet { - const nodeAdapter = await Isolation.Adapters.BIP32.create(masterKey); - - this.#solanaSigner = new SignerAdapter(nodeAdapter); this.#masterKey = masterKey; } - solanaWipe() { this.#solanaSigner = undefined; } + // TODO(gomes): make getAddress in adapter work with addressNList to support multi-account + // eslint-disable-next-line @typescript-eslint/no-unused-vars async solanaGetAddress(msg: core.ETHGetAddress): Promise { return this.needsMnemonic(!!this.#solanaSigner, () => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return this.#solanaSigner!.getAddress(msg.addressNList); + return this.#solanaSigner!.getAddress(); }); } @@ -52,8 +50,10 @@ export function MixinNativeSolanaWallet { const keyPair = await util.getKeyPair(this.#masterKey!, msg.addressNList, "solana"); + // Create the adapter for isolated signing const adapter = await Isolation.Adapters.SolanaDirect.create(keyPair.node); + // Get the address const address = await this.solanaGetAddress({ addressNList: msg.addressNList, showDisplay: false, From 6707876838822e69a7c780230611b593b79ee764 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 19 Dec 2024 01:55:41 +0300 Subject: [PATCH 04/24] fix: toBase58 --- .../hdwallet-native/src/crypto/isolation/adapters/solana.ts | 2 +- packages/hdwallet-native/src/solana.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts index 67f235b04..9055f890b 100644 --- a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts +++ b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts @@ -15,7 +15,7 @@ export class SolanaDirectAdapter { static async create(isolatedKey: SecP256K1.ECDSAKey): Promise { const pubkey = await isolatedKey.getPublicKey(); - const address = new PublicKey(pubkey).toString(); + const address = new PublicKey(pubkey).toBase58(); return new SolanaDirectAdapter(isolatedKey, pubkey, address); } diff --git a/packages/hdwallet-native/src/solana.ts b/packages/hdwallet-native/src/solana.ts index 566e7a304..d62dca30f 100644 --- a/packages/hdwallet-native/src/solana.ts +++ b/packages/hdwallet-native/src/solana.ts @@ -39,7 +39,7 @@ export function MixinNativeSolanaWallet { + async solanaGetAddress(msg: core.SolanaGetAddress): Promise { return this.needsMnemonic(!!this.#solanaSigner, () => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return this.#solanaSigner!.getAddress(); From 2009e8490cdd30a527532c78c9ad3aa679f8b9aa Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 19 Dec 2024 02:24:49 +0300 Subject: [PATCH 05/24] feat: revert back toString() --- .../hdwallet-native/src/crypto/isolation/adapters/solana.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts index 9055f890b..67f235b04 100644 --- a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts +++ b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts @@ -15,7 +15,7 @@ export class SolanaDirectAdapter { static async create(isolatedKey: SecP256K1.ECDSAKey): Promise { const pubkey = await isolatedKey.getPublicKey(); - const address = new PublicKey(pubkey).toBase58(); + const address = new PublicKey(pubkey).toString(); return new SolanaDirectAdapter(isolatedKey, pubkey, address); } From 2c1e92ba9ecdd4f98ec5385aa7098e1ec907a143 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 19 Dec 2024 02:45:07 +0300 Subject: [PATCH 06/24] Revert "feat: revert back toString()" This reverts commit 2009e8490cdd30a527532c78c9ad3aa679f8b9aa. --- .../hdwallet-native/src/crypto/isolation/adapters/solana.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts index 67f235b04..9055f890b 100644 --- a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts +++ b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts @@ -15,7 +15,7 @@ export class SolanaDirectAdapter { static async create(isolatedKey: SecP256K1.ECDSAKey): Promise { const pubkey = await isolatedKey.getPublicKey(); - const address = new PublicKey(pubkey).toString(); + const address = new PublicKey(pubkey).toBase58(); return new SolanaDirectAdapter(isolatedKey, pubkey, address); } From a061c435b5debeb2a8af3078cc3ef2669a9d8e39 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 19 Dec 2024 02:51:09 +0300 Subject: [PATCH 07/24] feat: last try --- .../src/crypto/isolation/adapters/solana.ts | 37 +++++++------------ packages/hdwallet-native/src/solana.ts | 15 ++------ 2 files changed, 18 insertions(+), 34 deletions(-) diff --git a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts index 9055f890b..fef3dcff8 100644 --- a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts +++ b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts @@ -1,39 +1,30 @@ +import * as core from "@shapeshiftoss/hdwallet-core"; import { PublicKey, VersionedTransaction } from "@solana/web3.js"; +import { Isolation } from "../.."; import { SecP256K1 } from "../core"; export class SolanaDirectAdapter { - protected readonly _isolatedKey: SecP256K1.ECDSAKey; - protected readonly _pubkey: Uint8Array; - readonly address: string; + protected readonly nodeAdapter: Isolation.Adapters.BIP32; - protected constructor(isolatedKey: SecP256K1.ECDSAKey, pubkey: Uint8Array, address: string) { - this._isolatedKey = isolatedKey; - this._pubkey = pubkey; - this.address = address; + constructor(nodeAdapter: Isolation.Adapters.BIP32) { + this.nodeAdapter = nodeAdapter; } - static async create(isolatedKey: SecP256K1.ECDSAKey): Promise { - const pubkey = await isolatedKey.getPublicKey(); - const address = new PublicKey(pubkey).toBase58(); - return new SolanaDirectAdapter(isolatedKey, pubkey, address); + async getAddress(addressNList: core.BIP32Path): Promise { + const nodeAdapter = await this.nodeAdapter.derivePath(core.addressNListToBIP32(addressNList)); + return new PublicKey(SecP256K1.UncompressedPoint.from(nodeAdapter.getPublicKey())).toBase58(); } - - async getAddress(): Promise { - return this.address; - } - - async signDirect(transaction: VersionedTransaction): Promise { - const pubkey = new PublicKey(this._pubkey); + async signDirect(transaction: VersionedTransaction, addressNList: core.BIP32Path): Promise { + const nodeAdapter = await this.nodeAdapter.derivePath(core.addressNListToBIP32(addressNList)); + const pubkey = nodeAdapter.getPublicKey(); const messageToSign = transaction.message.serialize(); + const signature = await nodeAdapter.node.ecdsaSign("sha256", messageToSign); - const signature = await this._isolatedKey.ecdsaSign("sha256", messageToSign); - - transaction.addSignature(pubkey, new Uint8Array(signature)); - + transaction.addSignature(new PublicKey(pubkey), new Uint8Array(signature)); return transaction; } } -export { SolanaDirectAdapter as default }; +export default SolanaDirectAdapter; diff --git a/packages/hdwallet-native/src/solana.ts b/packages/hdwallet-native/src/solana.ts index d62dca30f..fe1152b0b 100644 --- a/packages/hdwallet-native/src/solana.ts +++ b/packages/hdwallet-native/src/solana.ts @@ -3,7 +3,6 @@ import * as core from "@shapeshiftoss/hdwallet-core"; import * as Isolation from "./crypto/isolation"; import SignerAdapter from "./crypto/isolation/adapters/solana"; import { NativeHDWalletBase } from "./native"; -import * as util from "./util"; export function MixinNativeSolanaWalletInfo>(Base: TBase) { // eslint-disable-next-line @typescript-eslint/no-shadow @@ -37,23 +36,18 @@ export function MixinNativeSolanaWallet { return this.needsMnemonic(!!this.#solanaSigner, () => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return this.#solanaSigner!.getAddress(); + return this.#solanaSigner!.getAddress(msg.addressNList); }); } async solanaSignTx(msg: core.SolanaSignTx): Promise { return this.needsMnemonic(!!this.#masterKey, async () => { - const keyPair = await util.getKeyPair(this.#masterKey!, msg.addressNList, "solana"); - - // Create the adapter for isolated signing - const adapter = await Isolation.Adapters.SolanaDirect.create(keyPair.node); + const nodeAdapter = await Isolation.Adapters.BIP32.create(this.#masterKey!); + const adapter = new Isolation.Adapters.SolanaDirect(nodeAdapter); - // Get the address const address = await this.solanaGetAddress({ addressNList: msg.addressNList, showDisplay: false, @@ -62,8 +56,7 @@ export function MixinNativeSolanaWallet Date: Thu, 19 Dec 2024 03:02:02 +0300 Subject: [PATCH 08/24] fix: for real last try ffs --- packages/hdwallet-native/src/solana.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/hdwallet-native/src/solana.ts b/packages/hdwallet-native/src/solana.ts index fe1152b0b..f0b7ae3bb 100644 --- a/packages/hdwallet-native/src/solana.ts +++ b/packages/hdwallet-native/src/solana.ts @@ -30,7 +30,10 @@ export function MixinNativeSolanaWallet { + const nodeAdapter = await Isolation.Adapters.BIP32.create(masterKey); + this.#masterKey = masterKey; + this.#solanaSigner = new SignerAdapter(nodeAdapter); } solanaWipe() { this.#solanaSigner = undefined; From d8d6c785ab0c44c2763a40a015ac446b5a8250a2 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 19 Dec 2024 03:16:10 +0300 Subject: [PATCH 09/24] feat: last last try I swear fml --- .../hdwallet-native/src/crypto/isolation/adapters/solana.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts index fef3dcff8..cd6057b13 100644 --- a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts +++ b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts @@ -13,7 +13,8 @@ export class SolanaDirectAdapter { async getAddress(addressNList: core.BIP32Path): Promise { const nodeAdapter = await this.nodeAdapter.derivePath(core.addressNListToBIP32(addressNList)); - return new PublicKey(SecP256K1.UncompressedPoint.from(nodeAdapter.getPublicKey())).toBase58(); + const publicKeyBytes = Buffer.from(SecP256K1.CompressedPoint.from(nodeAdapter.getPublicKey())); + return new PublicKey(publicKeyBytes).toBase58(); } async signDirect(transaction: VersionedTransaction, addressNList: core.BIP32Path): Promise { const nodeAdapter = await this.nodeAdapter.derivePath(core.addressNListToBIP32(addressNList)); From 2aaeea621d8391c7cdf8748ab868a0f103f826c3 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 19 Dec 2024 09:57:39 +0300 Subject: [PATCH 10/24] fix: ok sleeping it off was a good idea, she works --- .../hdwallet-native/src/crypto/isolation/adapters/solana.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts index cd6057b13..21e5e7a62 100644 --- a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts +++ b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts @@ -14,7 +14,9 @@ export class SolanaDirectAdapter { async getAddress(addressNList: core.BIP32Path): Promise { const nodeAdapter = await this.nodeAdapter.derivePath(core.addressNListToBIP32(addressNList)); const publicKeyBytes = Buffer.from(SecP256K1.CompressedPoint.from(nodeAdapter.getPublicKey())); - return new PublicKey(publicKeyBytes).toBase58(); + + // strip the 0x02 compressed point prefix to make it a valid PublicKey input + return new PublicKey(publicKeyBytes.slice(1)).toBase58(); } async signDirect(transaction: VersionedTransaction, addressNList: core.BIP32Path): Promise { const nodeAdapter = await this.nodeAdapter.derivePath(core.addressNListToBIP32(addressNList)); From 96835d3e89a8f3e9e097da88ff0789eddb92818d Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:56:29 +0300 Subject: [PATCH 11/24] fix: sign use 0x02-stripped pubkey --- .../hdwallet-native/src/crypto/isolation/adapters/solana.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts index 21e5e7a62..54f57c072 100644 --- a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts +++ b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts @@ -20,7 +20,7 @@ export class SolanaDirectAdapter { } async signDirect(transaction: VersionedTransaction, addressNList: core.BIP32Path): Promise { const nodeAdapter = await this.nodeAdapter.derivePath(core.addressNListToBIP32(addressNList)); - const pubkey = nodeAdapter.getPublicKey(); + const pubkey = await this.getAddress(addressNList); const messageToSign = transaction.message.serialize(); const signature = await nodeAdapter.node.ecdsaSign("sha256", messageToSign); From a59c5a12b265b6f64c65920cf330358a250227c2 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:32:18 +0300 Subject: [PATCH 12/24] feat: I have no idea what I am doing --- packages/hdwallet-native/package.json | 1 + .../crypto/isolation/adapters/bip32ed25519.ts | 133 ++++++++++++++++++ .../src/crypto/isolation/adapters/solana.ts | 19 ++- .../crypto/isolation/core/ed25519/index.ts | 96 +++++++++++++ yarn.lock | 22 ++- 5 files changed, 259 insertions(+), 12 deletions(-) create mode 100644 packages/hdwallet-native/src/crypto/isolation/adapters/bip32ed25519.ts create mode 100644 packages/hdwallet-native/src/crypto/isolation/core/ed25519/index.ts diff --git a/packages/hdwallet-native/package.json b/packages/hdwallet-native/package.json index 2455d4573..5281442d2 100644 --- a/packages/hdwallet-native/package.json +++ b/packages/hdwallet-native/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@bitcoinerlab/secp256k1": "^1.1.1", + "@noble/ed25519": "^2.1.0", "@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 new file mode 100644 index 000000000..c111fa769 --- /dev/null +++ b/packages/hdwallet-native/src/crypto/isolation/adapters/bip32ed25519.ts @@ -0,0 +1,133 @@ +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 + ) { + 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 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}'`; + } + + 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 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"); + } + } + 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 + .split("/") + .filter(Boolean) + .map((segment) => { + const hardened = segment.endsWith("'"); + const index = parseInt(hardened ? segment.slice(0, -1) : segment); + // Ed25519 requires hardened derivation + return hardened ? index + 0x80000000 : index + 0x80000000; + }); + + return segments.reduce( + async (promise: Promise, index) => (await promise).derive(index), + Promise.resolve(this) + ); + } + + toBase58(): never { + throw new IsolationError("xprv"); + } +} + +export default BIP32Ed25519Adapter; diff --git a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts index 54f57c072..c3118a3c9 100644 --- a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts +++ b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts @@ -1,31 +1,30 @@ import * as core from "@shapeshiftoss/hdwallet-core"; import { PublicKey, VersionedTransaction } from "@solana/web3.js"; -import { Isolation } from "../.."; -import { SecP256K1 } from "../core"; +import BIP32Ed25519Adapter from "./bip32ed25519"; export class SolanaDirectAdapter { - protected readonly nodeAdapter: Isolation.Adapters.BIP32; + protected readonly nodeAdapter: BIP32Ed25519Adapter; - constructor(nodeAdapter: Isolation.Adapters.BIP32) { + constructor(nodeAdapter: BIP32Ed25519Adapter) { this.nodeAdapter = nodeAdapter; } async getAddress(addressNList: core.BIP32Path): Promise { const nodeAdapter = await this.nodeAdapter.derivePath(core.addressNListToBIP32(addressNList)); - const publicKeyBytes = Buffer.from(SecP256K1.CompressedPoint.from(nodeAdapter.getPublicKey())); - - // strip the 0x02 compressed point prefix to make it a valid PublicKey input - return new PublicKey(publicKeyBytes.slice(1)).toBase58(); + const publicKey = nodeAdapter.getPublicKey(); + return new PublicKey(publicKey).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.ecdsaSign("sha256", messageToSign); - transaction.addSignature(new PublicKey(pubkey), new Uint8Array(signature)); + const signature = await nodeAdapter.node.sign(messageToSign); + + transaction.addSignature(new PublicKey(pubkey), signature); return transaction; } } diff --git a/packages/hdwallet-native/src/crypto/isolation/core/ed25519/index.ts b/packages/hdwallet-native/src/crypto/isolation/core/ed25519/index.ts new file mode 100644 index 000000000..e0deda787 --- /dev/null +++ b/packages/hdwallet-native/src/crypto/isolation/core/ed25519/index.ts @@ -0,0 +1,96 @@ +import { + ExtendedPoint, + getPublicKey as nobleGetPublicKey, + sign as nobleSign, + verify as nobleVerify, +} from "@noble/ed25519"; +import { createHmac } from "crypto"; + +import { Revocable, revocable } from "../../engines/default/revocable"; +import { ByteArray } from "../../types"; +import { ChainCode } from "../bip32/types"; + +export type Ed25519Key = { + getPublicKey(): Promise; + sign(message: Uint8Array): Promise; + verify(message: Uint8Array, signature: Uint8Array): Promise; +}; + +export class Ed25519Node extends Revocable(class {}) implements Ed25519Key { + readonly #privateKey: Buffer; + readonly #chainCode: Buffer; + readonly explicitPath?: string; + + protected constructor(privateKey: Uint8Array, chainCode: Uint8Array, 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.explicitPath = explicitPath; + + this.addRevoker(() => { + this.#privateKey.fill(0); + this.#chainCode.fill(0); + }); + } + + static async create(privateKey: Uint8Array, chainCode: Uint8Array, 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); + } + + async verify(message: Uint8Array, signature: Uint8Array): Promise { + const publicKey = await this.getPublicKey(); + return nobleVerify(signature, message, publicKey); + } + + async getChainCode(): Promise { + return this.#chainCode as Buffer & ChainCode; + } + + async derive(index: number): Promise { + const indexBuffer = Buffer.alloc(4); + indexBuffer.writeUInt32BE(index, 0); + + const data = Buffer.concat([ + Buffer.from([0x00]), // Hardened derivation prefix + this.#privateKey, // Private key + indexBuffer, // Index + ]); + + const hmac = createHmac("sha512", this.#chainCode); + hmac.update(data); + const I = hmac.digest(); + + const IL = I.slice(0, 32); // Private key + const IR = I.slice(32); // Chain code + + // ED25519 key clamping, whatever that means + IL[0] &= 0xf8; + IL[31] &= 0x7f; + IL[31] |= 0x40; + + const path = this.explicitPath + ? `${this.explicitPath}/${index >= 0x80000000 ? index - 0x80000000 + "'" : index}` + : undefined; + + return Ed25519Node.create(IL, IR, path); + } +} + +export type Point = ExtendedPoint; +export const Ed25519Point = { + BASE_POINT: nobleGetPublicKey(new Uint8Array(32)), +}; +export type { ExtendedPoint }; diff --git a/yarn.lock b/yarn.lock index ba966b4bf..5946274d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3764,6 +3764,11 @@ resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-1.7.3.tgz#57e1677bf6885354b466c38e2b620c62f45a7123" integrity sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ== +"@noble/ed25519@^2.1.0": + version "2.1.0" + resolved "http://127.0.0.1:4873/@noble/ed25519/-/ed25519-2.1.0.tgz#4bf661de9ee0ad775d41fcacbfc9aeec491f459c" + integrity sha512-KM4qTyXPinyCgMzeYJH/UudpdL+paJXtY3CHtHYZQtBkS8MZoPr4rOikZllIutJe0d06QDQKisyn02gxZ8TcQA== + "@noble/hashes@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.0.0.tgz#d5e38bfbdaba174805a4e649f13be9a9ed3351ae" @@ -5481,7 +5486,7 @@ dependencies: ethereumjs-util "*" -"@types/eventsource@1.1.8", "@types/eventsource@^1.1.2": +"@types/eventsource@^1.1.2": version "1.1.8" resolved "https://registry.yarnpkg.com/@types/eventsource/-/eventsource-1.1.8.tgz#48ae1f3aaf9bb84c713038f354112cc7ceaad519" integrity sha512-fJQNt9LijJCZwYvM6O30uLzdpAK9zs52Uc9iUW9M2Zsg0HQM6DLf6QysjC/wuFX+0798B8AppVMvgdO6IftPKQ== @@ -15577,7 +15582,7 @@ p-pipe@^3.1.0: resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-3.1.0.tgz#48b57c922aa2e1af6a6404cb7c6bf0eb9cc8e60e" integrity sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw== -p-queue@^6.6.2, p-queue@^7.4.1: +p-queue@^6.6.2: version "6.6.2" resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== @@ -15585,6 +15590,14 @@ p-queue@^6.6.2, p-queue@^7.4.1: eventemitter3 "^4.0.4" p-timeout "^3.2.0" +p-queue@^7.4.1: + version "7.4.1" + resolved "http://127.0.0.1:4873/p-queue/-/p-queue-7.4.1.tgz#7f86f853048beca8272abdbb7cec1ed2afc0f265" + integrity sha512-vRpMXmIkYF2/1hLBKisKeVYJZ8S2tZ0zEAmIJgdVKP2nq0nh4qCdf8bgw+ZgKrkh71AOCaqzwbJJk1WtdcF3VA== + dependencies: + eventemitter3 "^5.0.1" + p-timeout "^5.0.2" + p-reduce@^2.0.0, p-reduce@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-2.1.0.tgz#09408da49507c6c274faa31f28df334bc712b64a" @@ -15597,6 +15610,11 @@ p-timeout@^3.2.0: dependencies: p-finally "^1.0.0" +p-timeout@^5.0.2: + version "5.1.0" + resolved "http://127.0.0.1:4873/p-timeout/-/p-timeout-5.1.0.tgz#b3c691cf4415138ce2d9cfe071dba11f0fee085b" + integrity sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew== + p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" From 029ce81f2e8ffe7a67f9d29ac13b53d762483e02 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:44:44 +0300 Subject: [PATCH 13/24] fix: versions --- packages/hdwallet-native/package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/hdwallet-native/package.json b/packages/hdwallet-native/package.json index 5281442d2..7fe975723 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": "^2.1.0", + "@noble/ed25519": "^1.7.3", "@shapeshiftoss/bitcoinjs-lib": "7.0.0-shapeshift.0", "@shapeshiftoss/fiosdk": "1.2.1-shapeshift.6", "@shapeshiftoss/hdwallet-core": "1.57.1", @@ -58,5 +58,6 @@ "bs58": "^4.0.1", "cosmjs-types": "^0.4.1", "msw": "^0.27.1" - } + }, + "gitHead": "a59c5a12b265b6f64c65920cf330358a250227c2" } From e900ec4bff17fb927cb5bc26e3d91011e476ddc1 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:50:42 +0300 Subject: [PATCH 14/24] fix: still no idea what I'm doing btw --- .../crypto/isolation/adapters/bip32ed25519.ts | 6 +++++ .../crypto/isolation/core/ed25519/index.ts | 13 +++++++++++ packages/hdwallet-native/src/solana.ts | 22 +++++++++---------- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/packages/hdwallet-native/src/crypto/isolation/adapters/bip32ed25519.ts b/packages/hdwallet-native/src/crypto/isolation/adapters/bip32ed25519.ts index c111fa769..d1b6aa17f 100644 --- a/packages/hdwallet-native/src/crypto/isolation/adapters/bip32ed25519.ts +++ b/packages/hdwallet-native/src/crypto/isolation/adapters/bip32ed25519.ts @@ -1,5 +1,6 @@ import * as core from "@shapeshiftoss/hdwallet-core"; +import { Isolation } from "../.."; import { BIP32, IsolationError } from "../core"; import { Path } from "../core/bip32/types"; import { Ed25519Node } from "../core/ed25519"; @@ -32,6 +33,11 @@ export class BIP32Ed25519Adapter { } } + static async fromNode(node: Isolation.Core.BIP32.Node): Promise { + const ed25519Node = await Ed25519Node.fromIsolatedNode(node); + return await BIP32Ed25519Adapter.create(ed25519Node); + } + static async create( isolatedNode: Ed25519Node, parent?: BIP32Ed25519Adapter, 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 e0deda787..57411c795 100644 --- a/packages/hdwallet-native/src/crypto/isolation/core/ed25519/index.ts +++ b/packages/hdwallet-native/src/crypto/isolation/core/ed25519/index.ts @@ -36,6 +36,19 @@ export class Ed25519Node extends Revocable(class {}) implements Ed25519Key { 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 { const obj = new Ed25519Node(privateKey, chainCode, explicitPath); diff --git a/packages/hdwallet-native/src/solana.ts b/packages/hdwallet-native/src/solana.ts index f0b7ae3bb..f5f84c544 100644 --- a/packages/hdwallet-native/src/solana.ts +++ b/packages/hdwallet-native/src/solana.ts @@ -1,7 +1,8 @@ import * as core from "@shapeshiftoss/hdwallet-core"; import * as Isolation from "./crypto/isolation"; -import SignerAdapter from "./crypto/isolation/adapters/solana"; +import BIP32Ed25519Adapter from "./crypto/isolation/adapters/bip32ed25519"; +import { SolanaDirectAdapter } from "./crypto/isolation/adapters/solana"; import { NativeHDWalletBase } from "./native"; export function MixinNativeSolanaWalletInfo>(Base: TBase) { @@ -25,16 +26,16 @@ export function MixinNativeSolanaWallet { - const nodeAdapter = await Isolation.Adapters.BIP32.create(masterKey); + // Create an Ed25519 BIP32 adapter directly from our masterKey + const ed25519Adapter = await BIP32Ed25519Adapter.fromNode(masterKey); - this.#masterKey = masterKey; - this.#solanaSigner = new SignerAdapter(nodeAdapter); + // Initialize the Solana adapter with the Ed25519 adapter + this.#solanaSigner = new SolanaDirectAdapter(ed25519Adapter); } + solanaWipe() { this.#solanaSigner = undefined; } @@ -47,10 +48,7 @@ export function MixinNativeSolanaWallet { - return this.needsMnemonic(!!this.#masterKey, async () => { - const nodeAdapter = await Isolation.Adapters.BIP32.create(this.#masterKey!); - const adapter = new Isolation.Adapters.SolanaDirect(nodeAdapter); - + return this.needsMnemonic(!!this.#solanaSigner, async () => { const address = await this.solanaGetAddress({ addressNList: msg.addressNList, showDisplay: false, @@ -59,7 +57,7 @@ export function MixinNativeSolanaWallet Date: Fri, 20 Dec 2024 14:03:32 +0300 Subject: [PATCH 15/24] fix: segments --- .../src/crypto/isolation/adapters/bip32ed25519.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/hdwallet-native/src/crypto/isolation/adapters/bip32ed25519.ts b/packages/hdwallet-native/src/crypto/isolation/adapters/bip32ed25519.ts index d1b6aa17f..ea7c32b20 100644 --- a/packages/hdwallet-native/src/crypto/isolation/adapters/bip32ed25519.ts +++ b/packages/hdwallet-native/src/crypto/isolation/adapters/bip32ed25519.ts @@ -116,13 +116,14 @@ export class BIP32Ed25519Adapter { 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 .split("/") .filter(Boolean) .map((segment) => { const hardened = segment.endsWith("'"); const index = parseInt(hardened ? segment.slice(0, -1) : segment); - // Ed25519 requires hardened derivation - return hardened ? index + 0x80000000 : index + 0x80000000; + // Ed25519 requires hardened derivation, so all indices should be hardened + return index + 0x80000000; }); return segments.reduce( From dc162a9a27ddf6980e5a13bb301e4c902e4ce50b Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Fri, 20 Dec 2024 23:49:44 +0300 Subject: [PATCH 16/24] feat: make it work (highly doubt it but hey) --- .../src/crypto/isolation/adapters/bip32ed25519.ts | 3 +-- .../src/crypto/isolation/core/bip32/interfaces.ts | 2 ++ .../src/crypto/isolation/engines/default/bip32.ts | 13 +++++++++++++ .../src/crypto/isolation/engines/dummy/bip32.ts | 8 ++++++++ packages/hdwallet-native/src/native.ts | 12 +++++++++++- packages/hdwallet-native/src/solana.ts | 4 ++-- 6 files changed, 37 insertions(+), 5 deletions(-) diff --git a/packages/hdwallet-native/src/crypto/isolation/adapters/bip32ed25519.ts b/packages/hdwallet-native/src/crypto/isolation/adapters/bip32ed25519.ts index ea7c32b20..61ea41008 100644 --- a/packages/hdwallet-native/src/crypto/isolation/adapters/bip32ed25519.ts +++ b/packages/hdwallet-native/src/crypto/isolation/adapters/bip32ed25519.ts @@ -1,6 +1,5 @@ import * as core from "@shapeshiftoss/hdwallet-core"; -import { Isolation } from "../.."; import { BIP32, IsolationError } from "../core"; import { Path } from "../core/bip32/types"; import { Ed25519Node } from "../core/ed25519"; @@ -33,7 +32,7 @@ export class BIP32Ed25519Adapter { } } - static async fromNode(node: Isolation.Core.BIP32.Node): Promise { + static async fromNode(node: Ed25519Node): Promise { const ed25519Node = await Ed25519Node.fromIsolatedNode(node); return await BIP32Ed25519Adapter.create(ed25519Node); } diff --git a/packages/hdwallet-native/src/crypto/isolation/core/bip32/interfaces.ts b/packages/hdwallet-native/src/crypto/isolation/core/bip32/interfaces.ts index a5eba403a..5b490aca8 100644 --- a/packages/hdwallet-native/src/crypto/isolation/core/bip32/interfaces.ts +++ b/packages/hdwallet-native/src/crypto/isolation/core/bip32/interfaces.ts @@ -1,11 +1,13 @@ import * as core from "@shapeshiftoss/hdwallet-core"; import { Revocable } from ".."; +import { Ed25519Node } from "../ed25519"; import * as SecP256K1 from "../secp256k1"; import { ChainCode } from "."; export interface Seed extends Partial { toMasterKey(hmacKey?: string | Uint8Array): Promise; + toEd25519MasterKey(): Promise; } export interface Node extends Partial, SecP256K1.ECDSAKey, Partial { diff --git a/packages/hdwallet-native/src/crypto/isolation/engines/default/bip32.ts b/packages/hdwallet-native/src/crypto/isolation/engines/default/bip32.ts index a3e0daeb5..136ba42aa 100644 --- a/packages/hdwallet-native/src/crypto/isolation/engines/default/bip32.ts +++ b/packages/hdwallet-native/src/crypto/isolation/engines/default/bip32.ts @@ -3,6 +3,7 @@ import * as bip32crypto from "bip32/src/crypto"; import { TextEncoder } from "web-encoding"; import { BIP32, Digest, SecP256K1 } from "../../core"; +import { Ed25519Node } from "../../core/ed25519"; import { assertType, ByteArray, checkType, safeBufferFrom, Uint32 } from "../../types"; import { Revocable, revocable } from "./revocable"; @@ -172,4 +173,16 @@ export class Seed extends Revocable(class {}) implements BIP32.Seed { this.addRevoker(() => out.revoke?.()); return out; } + + async toEd25519MasterKey(): Promise { + // Use Ed25519-specific HMAC key + // https://github.com/trezor/trezor-crypto/blob/master/bip32.c#L56 + const hmacKey = safeBufferFrom(new TextEncoder().encode("ed25519 seed")); + const I = safeBufferFrom(bip32crypto.hmacSHA512(hmacKey, this.#seed)); + const IL = I.slice(0, 32); + const IR = I.slice(32, 64); + const out = await Ed25519Node.create(IL, IR); + this.addRevoker(() => out.revoke?.()); + return out; + } } diff --git a/packages/hdwallet-native/src/crypto/isolation/engines/dummy/bip32.ts b/packages/hdwallet-native/src/crypto/isolation/engines/dummy/bip32.ts index 482bd1ed1..be6f175ff 100644 --- a/packages/hdwallet-native/src/crypto/isolation/engines/dummy/bip32.ts +++ b/packages/hdwallet-native/src/crypto/isolation/engines/dummy/bip32.ts @@ -3,6 +3,7 @@ import { toArrayBuffer } from "@shapeshiftoss/hdwallet-core"; import * as bip32crypto from "bip32/src/crypto"; import { BIP32, Digest, SecP256K1 } from "../../core"; +import { Ed25519Node } from "../../core/ed25519"; import { ByteArray, checkType, safeBufferFrom, Uint32 } from "../../types"; import { DummyEngineError, ParsedXpubTree } from "./types"; @@ -115,4 +116,11 @@ export class Seed implements BIP32.Seed { return await Node.create(this.xpubTree); } + + toEd25519MasterKey(): Promise; + async toEd25519MasterKey(hmacKey?: string | Uint8Array): Promise { + if (hmacKey !== undefined) throw new Error("bad hmacKey type"); + + return Ed25519Node.create(this.xpubTree.publicKey, this.xpubTree.chainCode); + } } diff --git a/packages/hdwallet-native/src/native.ts b/packages/hdwallet-native/src/native.ts index db89e54db..2e3450cb4 100644 --- a/packages/hdwallet-native/src/native.ts +++ b/packages/hdwallet-native/src/native.ts @@ -9,6 +9,7 @@ import { MixinNativeBinanceWallet, MixinNativeBinanceWalletInfo } from "./binanc import { MixinNativeBTCWallet, MixinNativeBTCWalletInfo } from "./bitcoin"; import { MixinNativeCosmosWallet, MixinNativeCosmosWalletInfo } from "./cosmos"; import * as Isolation from "./crypto/isolation"; +import { Ed25519Node } from "./crypto/isolation/core/ed25519"; import { MixinNativeETHWallet, MixinNativeETHWalletInfo } from "./ethereum"; import { MixinNativeFioWallet, MixinNativeFioWalletInfo } from "./fio"; import { MixinNativeKavaWallet, MixinNativeKavaWalletInfo } from "./kava"; @@ -254,6 +255,7 @@ export class NativeHDWallet #deviceId: string; #initialized = false; #masterKey: Promise | undefined = undefined; + #ed25519MasterKey: Promise | undefined = undefined; constructor({ mnemonic, deviceId, masterKey }: NativeAdapterArgs) { super(); @@ -266,6 +268,12 @@ export class NativeHDWallet const seed = await isolatedMnemonic.toSeed(); return await seed.toMasterKey(); })(); + this.#ed25519MasterKey = (async () => { + const isolatedMnemonic = + typeof mnemonic === "string" ? await Isolation.Engines.Default.BIP39.Mnemonic.create(mnemonic) : mnemonic; + const seed = await isolatedMnemonic.toSeed(); + return await seed.toEd25519MasterKey(); + })(); } this.#deviceId = deviceId; } @@ -329,6 +337,8 @@ export class NativeHDWallet return this.needsMnemonic(!!this.#masterKey, async () => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const masterKey = await this.#masterKey!; + // Assume if we have a seckp256k1 masterKey, we have a ed25519 masterKey too + const ed25519MasterKey = await this.#ed25519MasterKey!; try { await Promise.all([ super.btcInitializeWallet(masterKey), @@ -338,7 +348,7 @@ export class NativeHDWallet super.binanceInitializeWallet(masterKey), super.fioInitializeWallet(masterKey), super.thorchainInitializeWallet(masterKey), - super.solanaInitializeWallet(masterKey), + super.solanaInitializeWallet(ed25519MasterKey), super.secretInitializeWallet(masterKey), super.terraInitializeWallet(masterKey), super.kavaInitializeWallet(masterKey), diff --git a/packages/hdwallet-native/src/solana.ts b/packages/hdwallet-native/src/solana.ts index f5f84c544..87da7a8f4 100644 --- a/packages/hdwallet-native/src/solana.ts +++ b/packages/hdwallet-native/src/solana.ts @@ -1,8 +1,8 @@ import * as core from "@shapeshiftoss/hdwallet-core"; -import * as Isolation from "./crypto/isolation"; import BIP32Ed25519Adapter from "./crypto/isolation/adapters/bip32ed25519"; import { SolanaDirectAdapter } from "./crypto/isolation/adapters/solana"; +import { Ed25519Node } from "./crypto/isolation/core/ed25519"; import { NativeHDWalletBase } from "./native"; export function MixinNativeSolanaWalletInfo>(Base: TBase) { @@ -28,7 +28,7 @@ export function MixinNativeSolanaWallet { + async solanaInitializeWallet(masterKey: Ed25519Node): Promise { // Create an Ed25519 BIP32 adapter directly from our masterKey const ed25519Adapter = await BIP32Ed25519Adapter.fromNode(masterKey); From 62434a39ce9108b735406e5254ad226bd03a941c Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Sat, 21 Dec 2024 00:29:39 +0300 Subject: [PATCH 17/24] fix: try and fix initialize --- packages/hdwallet-native/src/native.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hdwallet-native/src/native.ts b/packages/hdwallet-native/src/native.ts index 2e3450cb4..14dba0034 100644 --- a/packages/hdwallet-native/src/native.ts +++ b/packages/hdwallet-native/src/native.ts @@ -334,7 +334,7 @@ export class NativeHDWallet async clearSession(): Promise {} async initialize(): Promise { - return this.needsMnemonic(!!this.#masterKey, async () => { + return this.needsMnemonic(!!this.#masterKey || !!this.#ed25519MasterKey, async () => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const masterKey = await this.#masterKey!; // Assume if we have a seckp256k1 masterKey, we have a ed25519 masterKey too From 2996b2cf3888bef08b564b5adb2e8de2b9b35d74 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Sat, 21 Dec 2024 00:46:17 +0300 Subject: [PATCH 18/24] fix: fml --- packages/hdwallet-native/src/native.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/hdwallet-native/src/native.ts b/packages/hdwallet-native/src/native.ts index 14dba0034..9b9da4915 100644 --- a/packages/hdwallet-native/src/native.ts +++ b/packages/hdwallet-native/src/native.ts @@ -435,6 +435,30 @@ export class NativeHDWallet })(msg?.mnemonic, msg?.masterKey) ); + this.#ed25519MasterKey = Promise.resolve( + await (async (mnemonic, masterKey) => { + if (masterKey !== undefined) { + // If a master key is provided, we'll need a way to derive the Ed25519 key from it + // This might require additional logic depending on your key derivation method + throw new Error("TODO?"); + } else if (mnemonic !== undefined) { + const isolatedMnemonic = await (async () => { + if (isMnemonicInterface(mnemonic)) return mnemonic; + if (typeof mnemonic === "string" && bip39.validateMnemonic(mnemonic)) { + return await Isolation.Engines.Default.BIP39.Mnemonic.create(mnemonic); + } + throw new Error("Required property [mnemonic] is invalid"); + })(); + const seed = await isolatedMnemonic.toSeed(); + seed.addRevoker?.(() => isolatedMnemonic.revoke?.()); + const out = await seed.toEd25519MasterKey(); + out.addRevoker?.(() => seed.revoke?.()); + return out; + } + throw new Error("Either [mnemonic] or [masterKey] is required"); + })(msg?.mnemonic, msg?.masterKey) + ); + if (typeof msg?.deviceId === "string") this.#deviceId = msg?.deviceId; this.#initialized = false; From d60beb268e9b4e04cfc4db69ccd99ae717fe21af Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Mon, 23 Dec 2024 19:45:34 +0300 Subject: [PATCH 19/24] fix: maybe? --- .../src/crypto/isolation/engines/dummy/bip32.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/hdwallet-native/src/crypto/isolation/engines/dummy/bip32.ts b/packages/hdwallet-native/src/crypto/isolation/engines/dummy/bip32.ts index be6f175ff..7a1285f4d 100644 --- a/packages/hdwallet-native/src/crypto/isolation/engines/dummy/bip32.ts +++ b/packages/hdwallet-native/src/crypto/isolation/engines/dummy/bip32.ts @@ -118,9 +118,8 @@ export class Seed implements BIP32.Seed { } toEd25519MasterKey(): Promise; - async toEd25519MasterKey(hmacKey?: string | Uint8Array): Promise { - if (hmacKey !== undefined) throw new Error("bad hmacKey type"); - - return Ed25519Node.create(this.xpubTree.publicKey, this.xpubTree.chainCode); + async toEd25519MasterKey(): Promise { + const edKey = await Ed25519Node.create(this.xpubTree.publicKey, this.xpubTree.chainCode); + return edKey; } } From acfc891d9e24f2923d7b3d91157697fec0dc232e Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Mon, 23 Dec 2024 19:58:20 +0300 Subject: [PATCH 20/24] fix: maybe?? --- .../src/crypto/isolation/engines/default/bip39.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/hdwallet-native/src/crypto/isolation/engines/default/bip39.ts b/packages/hdwallet-native/src/crypto/isolation/engines/default/bip39.ts index f563e66a7..4d783b480 100644 --- a/packages/hdwallet-native/src/crypto/isolation/engines/default/bip39.ts +++ b/packages/hdwallet-native/src/crypto/isolation/engines/default/bip39.ts @@ -5,6 +5,7 @@ import { TextEncoder } from "web-encoding"; import type { Seed as SeedType } from "../../core/bip32"; import type { Mnemonic as Bip39Mnemonic } from "../../core/bip39"; +import { Ed25519Node } from "../../core/ed25519"; import { Seed } from "./bip32"; import { Revocable, revocable } from "./revocable"; @@ -42,4 +43,8 @@ export class Mnemonic extends Revocable(class {}) implements Bip39Mnemonic { this.addRevoker(() => out.revoke?.()); return out; } + async toEd25519MasterKey(passphrase?: string): Promise { + const seed = await this.toSeed(passphrase); + return seed.toEd25519MasterKey(); + } } From c3f3a3bc6d25b145f3414a15df885bea475b26dc Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Mon, 23 Dec 2024 20:18:29 +0300 Subject: [PATCH 21/24] fix: maybe??? --- .../src/crypto/isolation/adapters/solana.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts index c3118a3c9..4d520a790 100644 --- a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts +++ b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts @@ -12,8 +12,11 @@ export class SolanaDirectAdapter { async getAddress(addressNList: core.BIP32Path): Promise { const nodeAdapter = await this.nodeAdapter.derivePath(core.addressNListToBIP32(addressNList)); - const publicKey = nodeAdapter.getPublicKey(); - return new PublicKey(publicKey).toBase58(); + const publicKeyBuffer = nodeAdapter.getPublicKey(); + // Convert the public key to Solana format by reversing the bytes, something something Little vs. Big Endian + const solanaFormat = Buffer.from(publicKeyBuffer).reverse(); + + return new PublicKey(solanaFormat).toBase58(); } async signDirect(transaction: VersionedTransaction, addressNList: core.BIP32Path): Promise { From b07f3bd76c621775a3335696c0f3d974fc9d94d7 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Mon, 23 Dec 2024 21:11:43 +0300 Subject: [PATCH 22/24] fix: maybe???? --- .../src/crypto/isolation/adapters/solana.ts | 6 ++---- .../src/crypto/isolation/core/ed25519/index.ts | 17 +++++++++-------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts index 4d520a790..e4d5a320c 100644 --- a/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts +++ b/packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts @@ -13,12 +13,10 @@ export class SolanaDirectAdapter { async getAddress(addressNList: core.BIP32Path): Promise { const nodeAdapter = await this.nodeAdapter.derivePath(core.addressNListToBIP32(addressNList)); const publicKeyBuffer = nodeAdapter.getPublicKey(); - // Convert the public key to Solana format by reversing the bytes, something something Little vs. Big Endian - const solanaFormat = Buffer.from(publicKeyBuffer).reverse(); - return new PublicKey(solanaFormat).toBase58(); + // PublicKey constructor in Solana expects the key in big-endian format + return new PublicKey(publicKeyBuffer).toBase58(); } - async signDirect(transaction: VersionedTransaction, addressNList: core.BIP32Path): Promise { const nodeAdapter = await this.nodeAdapter.derivePath(core.addressNListToBIP32(addressNList)); const pubkey = await this.getAddress(addressNList); 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 57411c795..0d84d3d14 100644 --- a/packages/hdwallet-native/src/crypto/isolation/core/ed25519/index.ts +++ b/packages/hdwallet-native/src/crypto/isolation/core/ed25519/index.ts @@ -73,23 +73,24 @@ export class Ed25519Node extends Revocable(class {}) implements Ed25519Key { } async derive(index: number): Promise { + // Ensure hardened derivation + if (index < 0x80000000) { + index += 0x80000000; + } + const indexBuffer = Buffer.alloc(4); indexBuffer.writeUInt32BE(index, 0); - const data = Buffer.concat([ - Buffer.from([0x00]), // Hardened derivation prefix - this.#privateKey, // Private key - indexBuffer, // Index - ]); + const data = Buffer.concat([Buffer.from([0x00]), this.#privateKey, indexBuffer]); const hmac = createHmac("sha512", this.#chainCode); hmac.update(data); const I = hmac.digest(); - const IL = I.slice(0, 32); // Private key - const IR = I.slice(32); // Chain code + const IL = I.slice(0, 32); + const IR = I.slice(32); - // ED25519 key clamping, whatever that means + // Apply clamping as per RFC 8032 IL[0] &= 0xf8; IL[31] &= 0x7f; IL[31] |= 0x40; From a3f0af459f898de25a7fb478ab9596375fe94787 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Mon, 23 Dec 2024 22:46:02 +0300 Subject: [PATCH 23/24] fix: maybe????? --- packages/hdwallet-native/package.json | 2 +- .../crypto/isolation/adapters/bip32ed25519.ts | 130 +++--------------- .../src/crypto/isolation/adapters/solana.ts | 11 +- .../crypto/isolation/core/ed25519/index.ts | 80 ++++------- 4 files changed, 48 insertions(+), 175 deletions(-) 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 }; From 5f7eb93f2de4b408e5b8d62ffc68e3f2fabbdab1 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Mon, 23 Dec 2024 22:53:13 +0300 Subject: [PATCH 24/24] feat: rage quit --- packages/hdwallet-native/package.json | 2 +- .../src/crypto/isolation/core/ed25519/index.ts | 7 +++---- packages/hdwallet-native/src/native.ts | 2 -- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/hdwallet-native/package.json b/packages/hdwallet-native/package.json index e372f1fb6..0a43b6b9a 100644 --- a/packages/hdwallet-native/package.json +++ b/packages/hdwallet-native/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "@bitcoinerlab/secp256k1": "^1.1.1", - "@noble/curves": "^1.7.3", + "@noble/curves": "^1.4.0", "@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/core/ed25519/index.ts b/packages/hdwallet-native/src/crypto/isolation/core/ed25519/index.ts index ade9dfe7e..bc2016ee3 100644 --- a/packages/hdwallet-native/src/crypto/isolation/core/ed25519/index.ts +++ b/packages/hdwallet-native/src/crypto/isolation/core/ed25519/index.ts @@ -12,7 +12,7 @@ export type Ed25519Key = { }; export class Ed25519Node extends Revocable(class {}) { - readonly #privateKey: ByteArray; // Changed to privateKey + readonly #privateKey: ByteArray; readonly #chainCode: ByteArray; readonly explicitPath?: string; @@ -29,7 +29,7 @@ export class Ed25519Node extends Revocable(class {}) { } async getPublicKey(): Promise { - // Generate public key using noble-curves ed25519 + // Generate public key from private key return Buffer.from(ed25519.getPublicKey(this.#privateKey)); } @@ -38,7 +38,6 @@ export class Ed25519Node extends Revocable(class {}) { } async sign(message: Uint8Array): Promise { - // Sign using noble-curves ed25519 return Buffer.from(ed25519.sign(message, this.#privateKey)); } @@ -51,7 +50,7 @@ export class Ed25519Node extends Revocable(class {}) { const indexBuffer = Buffer.alloc(4); indexBuffer.writeUInt32BE(index, 0); - // SLIP-0010 Ed25519 derivation + // SLIP-0010 for Ed25519 const data = Buffer.concat([Buffer.from([0x00]), Buffer.from(this.#privateKey), indexBuffer]); const I = bip32crypto.hmacSHA512(Buffer.from(this.#chainCode), data); diff --git a/packages/hdwallet-native/src/native.ts b/packages/hdwallet-native/src/native.ts index 9b9da4915..bb7c32b2e 100644 --- a/packages/hdwallet-native/src/native.ts +++ b/packages/hdwallet-native/src/native.ts @@ -438,8 +438,6 @@ export class NativeHDWallet this.#ed25519MasterKey = Promise.resolve( await (async (mnemonic, masterKey) => { if (masterKey !== undefined) { - // If a master key is provided, we'll need a way to derive the Ed25519 key from it - // This might require additional logic depending on your key derivation method throw new Error("TODO?"); } else if (mnemonic !== undefined) { const isolatedMnemonic = await (async () => {