Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ErgoHDKey utility methods #101

Merged
merged 5 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/angry-bobcats-poke.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"@fleet-sdk/wallet": minor
---

Add `ProveDLog` transaction signing and verifying
Add `ProveDLog` transaction signing and verification
5 changes: 5 additions & 0 deletions .changeset/friendly-monkeys-do.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fleet-sdk/wallet": minor
---

Add hex encoding support to `ErgoHDKey#fromExtendedKey()`
5 changes: 5 additions & 0 deletions .changeset/itchy-games-switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fleet-sdk/wallet": minor
---

Added `ErgoHDKey#isNeutered()`, `ErgoHDKey#isExtended()`, and `ErgoHDKey#hasPrivateKey()` utility methods.
5 changes: 5 additions & 0 deletions .changeset/sour-pillows-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fleet-sdk/wallet": minor
---

Deprecate optioned arguments for `ErgoHDKey#fromExtendedKeys()` and create default constructor
6 changes: 2 additions & 4 deletions packages/common/src/utils/bytes.bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@ const HEX_CHARSET = new Set("0123456789abcdefABCDEF");
function isHexChar(value: string) {
if (!value || value.length % 2) return false;

const valueSet = new Set(Array.from(value));
const valueSet = new Set(value);
for (const c of valueSet) {
if (!HEX_CHARSET.has(c)) {
return false;
}
if (!HEX_CHARSET.has(c)) return false;
}

return true;
Expand Down
39 changes: 30 additions & 9 deletions packages/wallet/src/ergoHDKey.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { first } from "@fleet-sdk/common";
import { base58check, hex } from "@fleet-sdk/crypto";
import { mnemonicToSeedSync } from "@scure/bip39";
import SigmaRust from "ergo-lib-wasm-nodejs";
import { describe, expect, it } from "vitest";
import { describe, expect, it, test } from "vitest";
import { keyAddressesTestVectors } from "./_test-vectors/keyVectors";
import { ERGO_CHANGE_PATH, ErgoHDKey } from "./ergoHDKey";
import { generateMnemonic } from "./mnemonic";
Expand Down Expand Up @@ -53,10 +53,27 @@ describe("Instantiation", () => {
key.wipePrivateData();
expect(key.privateKey).to.be.undefined;
});

test("Utility checks", () => {
const fullKey = ErgoHDKey.fromMnemonicSync(generateMnemonic());
expect(fullKey.hasPrivateKey()).to.be.true;
expect(fullKey.isNeutered()).to.be.false;
expect(fullKey.isExtended()).to.be.true;

const neuteredKey = fullKey.wipePrivateData();
expect(neuteredKey.hasPrivateKey()).to.be.false;
expect(neuteredKey.isNeutered()).to.be.true;
expect(neuteredKey.isExtended()).to.be.true;

const simplePk = new ErgoHDKey({ publicKey: fullKey.publicKey });
expect(simplePk.hasPrivateKey()).to.be.false;
expect(simplePk.isNeutered()).to.be.true;
expect(simplePk.isExtended()).to.be.false;
});
});

describe("Extended keys", () => {
it("Should create and restore from public extended key", async () => {
it("Should create and restore from public encoded extended key", async () => {
const mnemonic = generateMnemonic();
const key = await ErgoHDKey.fromMnemonic(mnemonic);

Expand All @@ -71,7 +88,15 @@ describe("Extended keys", () => {
expect(recreatedKeyFromPk.chainCode).to.be.deep.equal(key.chainCode);
});

it("Should create and restore from private extended key", async () => {
it("Should create from extended public key encoded as hex string", () => {
const xpk =
"0488b21e000000000000000000412bc02c85a3499d85f985ee6068c23f2345eb1c8b3fc34309df13b6a5ac56e803cf03e43683a9b7965603a5cc222bd31fa269d12527cda358965716c794c82253";

const key = ErgoHDKey.fromExtendedKey(xpk).deriveChild(0);
expect(key.address.encode()).to.be.equal("9gfJXuRPGwocBsVvKc6GPp294Y3i24v3rd5GP6UXQiV1bjChoDB");
});

it("Should create and restore from private encoded extended key", async () => {
const mnemonic = generateMnemonic();
const key = await ErgoHDKey.fromMnemonic(mnemonic);

Expand Down Expand Up @@ -106,18 +131,14 @@ describe("Extended keys", () => {
expect(fullyRecreatedKey.publicKey).to.be.deep.equal(key.publicKey);
expect(fullyRecreatedKey.chainCode).to.be.deep.equal(key.chainCode);

const recreatedFromPrivateKey = ErgoHDKey.fromExtendedKey({
privateKey: key.privateKey!
});
const recreatedFromPrivateKey = new ErgoHDKey({ privateKey: key.privateKey! });
expect(recreatedFromPrivateKey.depth).to.be.equal(0);
expect(recreatedFromPrivateKey.index).to.be.equal(0);
expect(recreatedFromPrivateKey.privateKey).to.deep.equal(key.privateKey);
expect(recreatedFromPrivateKey.publicKey).to.be.deep.equal(key.publicKey);
expect(recreatedFromPrivateKey.chainCode).to.be.undefined;

const recreatedFromPublicKey = ErgoHDKey.fromExtendedKey({
publicKey: key.publicKey
});
const recreatedFromPublicKey = new ErgoHDKey({ publicKey: key.publicKey });
expect(recreatedFromPublicKey.depth).to.be.equal(0);
expect(recreatedFromPublicKey.index).to.be.equal(0);
expect(recreatedFromPublicKey.privateKey).to.be.undefined;
Expand Down
52 changes: 35 additions & 17 deletions packages/wallet/src/ergoHDKey.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { assert } from "@fleet-sdk/common";
import { isHex } from "@fleet-sdk/common";
import { ErgoAddress } from "@fleet-sdk/core";
import { base58check, hex } from "@fleet-sdk/crypto";
import { HDKey } from "@scure/bip32";
import { mnemonicToSeed, mnemonicToSeedSync } from "@scure/bip39";

Expand Down Expand Up @@ -28,21 +29,20 @@ export type PublicKeyOptions = HDKeyOptions & {
publicKey: Uint8Array;
};

type ExtendedErgoKey = ErgoHDKey & { chainCode: Uint8Array };
type FullErgoKey = ExtendedErgoKey & { chainCode: Uint8Array; privateKey: Uint8Array };
type NeuteredErgoKey = Omit<ErgoHDKey, "privateKey" | "extendedPrivateKey">;

export class ErgoHDKey {
readonly #root: HDKey;
readonly #publicKey: Uint8Array;

#address?: ErgoAddress;

private constructor(key: HDKey) {
assert(!!key.publicKey, "Public key is not present");

this.#root = key;
this.#publicKey = key.publicKey;
constructor(keyOrOpt: HDKey | PrivateKeyOptions | PublicKeyOptions) {
this.#root = keyOrOpt instanceof HDKey ? keyOrOpt : new HDKey(keyOrOpt);
}

get publicKey(): Uint8Array {
return this.#publicKey;
return this.#root.publicKey!;
}

get privateKey(): Uint8Array | undefined {
Expand Down Expand Up @@ -92,16 +92,23 @@ export class ErgoHDKey {
return new ErgoHDKey(key);
}

/**
* Create an ErgoHDKey from an extended key
* @param encodedKey
*/
static fromExtendedKey(encodedKey: string): ErgoHDKey;
/** @deprecated use the default constructor instead */
static fromExtendedKey(options: PrivateKeyOptions): ErgoHDKey;
/** @deprecated use the default constructor instead */
static fromExtendedKey(options: PublicKeyOptions): ErgoHDKey;
static fromExtendedKey(encodedKey: string): ErgoHDKey;
/** @deprecated use the default constructor instead */
static fromExtendedKey(keyOrOptions: string | PrivateKeyOptions | PublicKeyOptions): ErgoHDKey {
const rootKey =
typeof keyOrOptions === "string"
? HDKey.fromExtendedKey(keyOrOptions)
: new HDKey(keyOrOptions);
if (typeof keyOrOptions !== "string") {
return new ErgoHDKey(keyOrOptions);
}

return new ErgoHDKey(rootKey);
const xKey = isHex(keyOrOptions) ? base58check.encode(hex.decode(keyOrOptions)) : keyOrOptions;
return new ErgoHDKey(HDKey.fromExtendedKey(xKey));
}

deriveChild(index: number): ErgoHDKey {
Expand All @@ -112,9 +119,20 @@ export class ErgoHDKey {
return new ErgoHDKey(this.#root.derive(path));
}

wipePrivateData(): ErgoHDKey {
wipePrivateData(): NeuteredErgoKey {
this.#root.wipePrivateData();

return this;
}

isExtended(): this is ExtendedErgoKey {
return this.chainCode !== undefined;
}

isNeutered(): this is NeuteredErgoKey {
return this.privateKey === undefined;
}

hasPrivateKey(): this is FullErgoKey {
return this.privateKey !== undefined;
}
}
Loading