Skip to content

Commit

Permalink
fix: maybe?????
Browse files Browse the repository at this point in the history
  • Loading branch information
gomesalexandre committed Dec 23, 2024
1 parent b07f3bd commit a3f0af4
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 175 deletions.
2 changes: 1 addition & 1 deletion packages/hdwallet-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
130 changes: 16 additions & 114 deletions packages/hdwallet-native/src/crypto/isolation/adapters/bip32ed25519.ts
Original file line number Diff line number Diff line change
@@ -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<number, this>();
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<BIP32Ed25519Adapter> {
const ed25519Node = await Ed25519Node.fromIsolatedNode(node);
return await BIP32Ed25519Adapter.create(ed25519Node);
}

static async create(
isolatedNode: Ed25519Node,
parent?: BIP32Ed25519Adapter,
index?: number
): Promise<BIP32Ed25519Adapter> {
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<this> {
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<BIP32Ed25519Adapter> {
return this.derive(index + 0x80000000);
async getPublicKey(): Promise<ByteArray> {
const publicKey = await this.node.getPublicKey();
return Buffer.from(publicKey);
}

async derivePath(path: string): Promise<BIP32Ed25519Adapter> {
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<BIP32Ed25519Adapter>, 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);
}
}

Expand Down
11 changes: 7 additions & 4 deletions packages/hdwallet-native/src/crypto/isolation/adapters/solana.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,20 @@ export class SolanaDirectAdapter {

async getAddress(addressNList: core.BIP32Path): Promise<string> {
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<VersionedTransaction> {
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);
Expand Down
80 changes: 24 additions & 56 deletions packages/hdwallet-native/src/crypto/isolation/core/ed25519/index.ts
Original file line number Diff line number Diff line change
@@ -1,75 +1,45 @@
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<ByteArray>;
sign(message: Uint8Array): Promise<ByteArray>;
verify(message: Uint8Array, signature: Uint8Array): Promise<boolean>;
};

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<Ed25519Node> {
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<Ed25519Node> {
static async create(privateKey: ByteArray, chainCode: ByteArray, explicitPath?: string): Promise<Ed25519Node> {
const obj = new Ed25519Node(privateKey, chainCode, explicitPath);
return revocable(obj, (x) => obj.addRevoker(x));
}

async getPublicKey(): Promise<ByteArray> {
return nobleGetPublicKey(this.#privateKey);
}

async sign(message: Uint8Array): Promise<ByteArray> {
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<boolean> {
const publicKey = await this.getPublicKey();
return nobleVerify(signature, message, publicKey);
async getChainCode(): Promise<ByteArray> {
return this.#chainCode;
}

async getChainCode(): Promise<ChainCode> {
return this.#chainCode as Buffer & ChainCode;
async sign(message: Uint8Array): Promise<ByteArray> {
// Sign using noble-curves ed25519
return Buffer.from(ed25519.sign(message, this.#privateKey));
}

async derive(index: number): Promise<Ed25519Node> {
Expand All @@ -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;
Expand All @@ -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 };

0 comments on commit a3f0af4

Please sign in to comment.