From 18cd0841f68c592ca84a7bb00fda70eaa0a2f7f5 Mon Sep 17 00:00:00 2001 From: Curtis Date: Tue, 26 Mar 2024 09:44:09 +0000 Subject: [PATCH] fix: Agent.createNewPrismDID to use derivationPath (#158) --- .vscode/launch.json | 1 + src/apollo/Apollo.ts | 51 +++-- src/apollo/utils/Secp256k1PrivateKey.ts | 57 +++--- src/apollo/utils/Secp256k1PublicKey.ts | 10 +- src/apollo/utils/derivation/DerivationPath.ts | 57 ++++++ src/domain/models/KeyProperties.ts | 10 + src/domain/models/errors/Apollo.ts | 3 + src/pluto/repositories/KeyRepository.ts | 5 + src/prism-agent/Agent.DIDHigherFunctions.ts | 21 +- .../Agent.PrismKeyPathIndexTask.ts | 2 +- src/prism-agent/Agent.ts | 4 +- tests/agent/Agent.functional.test.ts | 61 ++++++ tests/apollo/Apollo.createPrivateKey.test.ts | 186 ++++++++++++++++++ tests/apollo/Apollo.test.ts | 38 ++-- tests/apollo/keys/Secp256k1.test.ts | 100 +++++++++- tests/fixtures/inmemory/factory.ts | 15 ++ tests/fixtures/keys.ts | 39 ++++ 17 files changed, 558 insertions(+), 102 deletions(-) create mode 100644 tests/agent/Agent.functional.test.ts create mode 100644 tests/apollo/Apollo.createPrivateKey.test.ts create mode 100644 tests/fixtures/inmemory/factory.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index f770ecabc..3559b54a1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -25,6 +25,7 @@ "request": "launch", "program": "${workspaceRoot}/node_modules/jest/bin/jest.js", "args": [ + "${fileBasenameNoExtension}", "--colors", "--workerThreads", "--maxWorkers", diff --git a/src/apollo/Apollo.ts b/src/apollo/Apollo.ts index cbff7921e..7ae2045ad 100644 --- a/src/apollo/Apollo.ts +++ b/src/apollo/Apollo.ts @@ -31,6 +31,7 @@ import { Ed25519PublicKey } from "./utils/Ed25519PublicKey"; import { X25519PublicKey } from "./utils/X25519PublicKey"; import ApolloPKG from "@atala/apollo"; +import { DerivationPath } from "./utils/derivation/DerivationPath"; const ApolloSDK = ApolloPKG.io.iohk.atala.prism.apollo; const Mnemonic = ApolloSDK.derivation.Mnemonic.Companion; @@ -285,43 +286,35 @@ export default class Apollo implements ApolloInterface, KeyRestoration { return new Secp256k1PrivateKey(keyData); } - const derivationPathStr = parameters[KeyProperties.derivationPath] - ? Buffer.from(parameters[KeyProperties.derivationPath]).toString("hex") - : Buffer.from(`m/0'/0'/0'`).toString("hex"); + const seedHex = parameters[KeyProperties.seed]; + if (!seedHex) { + throw new ApolloError.MissingKeyParameters([KeyProperties.seed]); + } - const seedStr = parameters[KeyProperties.seed]; + const seed = Buffer.from(seedHex, "hex"); + const hdKey = HDKey.InitFromSeed(Int8Array.from(seed), 0, BigIntegerWrapper.initFromInt(0)); - if (!derivationPathStr) { - throw new ApolloError.MissingKeyParameters([ - KeyProperties.derivationPath, - ]); + if (hdKey.privateKey == null) { + throw new ApolloError.MissingPrivateKey(); } - if (!seedStr) { - throw new ApolloError.MissingKeyParameters([KeyProperties.seed]); + if (hdKey.chainCode == null) { + throw new ApolloError.MissingChainCode(); } - const seed = Buffer.from(seedStr, "hex"); + const baseKey = new Secp256k1PrivateKey(Buffer.from(hdKey.privateKey)); + baseKey.keySpecification.set(KeyProperties.chainCode, Buffer.from(hdKey.chainCode).toString("hex")); + baseKey.keySpecification.set(KeyProperties.derivationPath, Buffer.from(`m/0'/0'/0'`).toString("hex")); + baseKey.keySpecification.set(KeyProperties.index, "0"); - const newExtendedKey = HDKey.InitFromSeed( - Int8Array.from(seed), - 0, - BigIntegerWrapper.initFromInt(0) - ).derive(Buffer.from(derivationPathStr, "hex").toString()); - - const newExtendedPrivateKey = new Secp256k1PrivateKey( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - Uint8Array.from(newExtendedKey.privateKey!) - ); - - newExtendedPrivateKey.keySpecification.set(KeyProperties.seed, seedStr); - newExtendedPrivateKey.keySpecification.set( - KeyProperties.derivationPath, - derivationPathStr - ); - newExtendedPrivateKey.keySpecification.set(KeyProperties.index, "0"); + const derivationParam = parameters[KeyProperties.derivationPath]; + if (derivationParam) { + const derivationPath = DerivationPath.from(derivationParam); + const privateKey = baseKey.derive(derivationPath); + return privateKey; + } - return newExtendedPrivateKey; + return baseKey; } } diff --git a/src/apollo/utils/Secp256k1PrivateKey.ts b/src/apollo/utils/Secp256k1PrivateKey.ts index cf493a1ad..7d5287535 100644 --- a/src/apollo/utils/Secp256k1PrivateKey.ts +++ b/src/apollo/utils/Secp256k1PrivateKey.ts @@ -4,18 +4,15 @@ import BN from "bn.js"; import * as ECConfig from "../../config/ECConfig"; import { Secp256k1PublicKey } from "./Secp256k1PublicKey"; import { DerivationPath } from "./derivation/DerivationPath"; +import { ApolloError, Curve, KeyTypes, KeyProperties, } from "../../domain"; import { - ApolloError, - Curve, + PrivateKey, DerivableKey, ExportableKey, ImportableKey, - KeyTypes, - KeyProperties, - PrivateKey, SignableKey, - StorableKey -} from "../../domain"; + StorableKey, +} from "../../domain/models/keyManagement"; const Apollo = ApolloPkg.io.iohk.atala.prism.apollo; const HDKey = Apollo.derivation.HDKey; @@ -55,35 +52,40 @@ export class Secp256k1PrivateKey this.size = this.raw.length; } - derive(derivationPath: DerivationPath): PrivateKey { - const seedHex = this.getProperty(KeyProperties.seed); + derive(derivationPath: DerivationPath): Secp256k1PrivateKey { + const chainCodeHex = this.getProperty(KeyProperties.chainCode); - if (!seedHex) { - throw new Error("Seed not found"); + if (!chainCodeHex) { + throw new ApolloError.MissingKeyParameters([KeyProperties.chainCode]); } - const seed = Buffer.from(seedHex, "hex"); + const chaincode = Buffer.from(chainCodeHex, "hex"); const derivationPathStr = derivationPath.toString(); - const newExtendedKey = HDKey.InitFromSeed( - Int8Array.from(seed), + const hdKey = new HDKey( + Int8Array.from(this.raw), + null, + Int8Array.from(chaincode), 0, - BigIntegerWrapper.initFromInt(0) - ).derive(derivationPathStr); - - const newExtendedPrivateKey = new Secp256k1PrivateKey( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - Uint8Array.from(newExtendedKey.privateKey!) + BigIntegerWrapper.initFromInt(this.index ?? 0) ); - newExtendedPrivateKey.keySpecification.set(KeyProperties.seed, seedHex); - newExtendedPrivateKey.keySpecification.set( - KeyProperties.derivationPath, - Buffer.from(derivationPathStr).toString("hex") - ); - newExtendedPrivateKey.keySpecification.set(KeyProperties.index, "0"); + const derivedKey = hdKey.derive(derivationPathStr); + + if (derivedKey.privateKey == null) { + throw new ApolloError.MissingPrivateKey(); + } + + const privateKey = new Secp256k1PrivateKey(Buffer.from(derivedKey.privateKey)); + // TODO(BR) dont keep derivationPath as hex + privateKey.keySpecification.set(KeyProperties.derivationPath, Buffer.from(derivationPathStr).toString("hex")); + privateKey.keySpecification.set(KeyProperties.index, `${derivationPath.index}`); + + if (derivedKey.chainCode) { + privateKey.keySpecification.set(KeyProperties.chainCode, Buffer.from(derivedKey.chainCode).toString("hex")); + } - return newExtendedPrivateKey; + return privateKey; } publicKey() { @@ -105,6 +107,7 @@ export class Secp256k1PrivateKey ); } + // ?? move to `from` property static secp256k1FromBigInteger(bigInteger: BN): Secp256k1PrivateKey { return new Secp256k1PrivateKey(Uint8Array.from(bigInteger.toArray())); } diff --git a/src/apollo/utils/Secp256k1PublicKey.ts b/src/apollo/utils/Secp256k1PublicKey.ts index 91b382e84..f904b3377 100644 --- a/src/apollo/utils/Secp256k1PublicKey.ts +++ b/src/apollo/utils/Secp256k1PublicKey.ts @@ -4,17 +4,14 @@ import BigInteger from "bn.js"; import * as ECConfig from "../../config/ECConfig"; import { ECPoint } from "./ec/ECPoint"; +import { ApolloError, Curve, KeyProperties, KeyTypes, } from "../../domain"; import { - ApolloError, - Curve, + PublicKey, ExportableKey, ImportableKey, - KeyProperties, - KeyTypes, - PublicKey, StorableKey, VerifiableKey -} from "../../domain"; +} from "../../domain/models/keyManagement"; /** * @ignore @@ -22,7 +19,6 @@ import { export class Secp256k1PublicKey extends PublicKey implements StorableKey, ExportableKey, VerifiableKey { public readonly recoveryId = StorableKey.recoveryId("secp256k1", "pub"); - public keySpecification: Map = new Map(); public size: number; public raw: Uint8Array; diff --git a/src/apollo/utils/derivation/DerivationPath.ts b/src/apollo/utils/derivation/DerivationPath.ts index fd12016af..da480533e 100644 --- a/src/apollo/utils/derivation/DerivationPath.ts +++ b/src/apollo/utils/derivation/DerivationPath.ts @@ -7,6 +7,18 @@ export class DerivationPath { this.axes = axes; } + /** + * get the index at depth 0. + * default to 0 if no value found + * + * @returns {number} - the value at depth 0 + */ + get index(): number { + const first = this.axes.at(0); + const index = first?.number ?? 0; + return index; + } + /** * Creates child derivation path for given index, hardened or not */ @@ -22,6 +34,51 @@ export class DerivationPath { return new DerivationPath([]); } + /** + * Attempt to create a DerivationPath from a value + * + * @param value + * @returns {DerivationPath} + * @throws - if the value cannot be converted to a DerivationPath + */ + static from(value: unknown): DerivationPath { + if (value instanceof DerivationPath) { + return value; + } + + if (typeof value === "string") { + if (value.at(0) === "m") { + return DerivationPath.fromPath(value); + } + } + + if (typeof value === "number") { + return DerivationPath.fromIndex(value); + } + + throw new Error(`DerivationPath value not valid [${value}]`); + } + + + /** + * Create a DerivationPath from an index + * + * index is hardened and used at depth 1, with two subsequent hardened 0s + * Example: index: 3 = DerivationPath: `m/3'/0'/0'` + * + * equivalent of Swift DerivableKey.keyPathString + * + * @param index - hardened index for depth 1 + * @returns {DerivationPath} + */ + static fromIndex(index: number): DerivationPath { + return new DerivationPath([ + DerivationAxis.hardened(index), + DerivationAxis.hardened(0), + DerivationAxis.hardened(0), + ]); + } + /** * Parses string representation of derivation path * diff --git a/src/domain/models/KeyProperties.ts b/src/domain/models/KeyProperties.ts index a0b8a0b88..f53b6ee42 100644 --- a/src/domain/models/KeyProperties.ts +++ b/src/domain/models/KeyProperties.ts @@ -14,6 +14,16 @@ export enum KeyProperties { */ seed = "seed", + /** + * The 'chainCode' used for key derivation. + * hex encoded value. + * + * ```ts + * const chainCode = Buffer.from(props[KeyProperties.chainCode], "hex"); + * ``` + */ + chainCode = "chainCode", + /** * The 'rawKey' refers to the raw binary form of the key. */ diff --git a/src/domain/models/errors/Apollo.ts b/src/domain/models/errors/Apollo.ts index 245e3df8b..7a57340d0 100644 --- a/src/domain/models/errors/Apollo.ts +++ b/src/domain/models/errors/Apollo.ts @@ -61,3 +61,6 @@ export class KeyRestoratonFailed extends Error { super(`Key Restoration Failed: [${key?.recoveryId}]`); } } + +export class MissingPrivateKey extends Error {} +export class MissingChainCode extends Error {} diff --git a/src/pluto/repositories/KeyRepository.ts b/src/pluto/repositories/KeyRepository.ts index a1c08977a..110c382dd 100644 --- a/src/pluto/repositories/KeyRepository.ts +++ b/src/pluto/repositories/KeyRepository.ts @@ -16,6 +16,11 @@ export class KeyRepository extends MapperRepository { - const index = keyPathIndex ?? this.getNextKeyPathIndex(); - + const index = keyPathIndex ?? await this.getNextKeyPathIndex(); + const derivationPath = DerivationPath.fromIndex(index); const privateKey = this.apollo.createPrivateKey({ - type: KeyTypes.EC, - curve: Curve.SECP256K1, - seed: Buffer.from(this.seed.value).toString("hex"), + [KeyProperties.type]: KeyTypes.EC, + [KeyProperties.curve]: Curve.SECP256K1, + [KeyProperties.seed]: Buffer.from(this.seed.value).toString("hex"), + [KeyProperties.derivationPath]: derivationPath, }); const publicKey = privateKey.publicKey(); @@ -158,7 +160,12 @@ export class AgentDIDHigherFunctions implements AgentDIDHigherFunctionsClass { return did; } - private async getNextKeyPathIndex() { + /** + * Determine the Index for the subsequent Key + * + * @returns {number} + */ + private async getNextKeyPathIndex(): Promise { const getIndexTask = new PrismKeyPathIndexTask(this.pluto); const index = await getIndexTask.run(); const next = index + 1; diff --git a/src/prism-agent/Agent.PrismKeyPathIndexTask.ts b/src/prism-agent/Agent.PrismKeyPathIndexTask.ts index fef58383f..121309ad1 100644 --- a/src/prism-agent/Agent.PrismKeyPathIndexTask.ts +++ b/src/prism-agent/Agent.PrismKeyPathIndexTask.ts @@ -18,4 +18,4 @@ export class PrismKeyPathIndexTask { return keyPathIndex; } -} \ No newline at end of file +} diff --git a/src/prism-agent/Agent.ts b/src/prism-agent/Agent.ts index bbcae0170..44ac8f8a5 100644 --- a/src/prism-agent/Agent.ts +++ b/src/prism-agent/Agent.ts @@ -160,7 +160,7 @@ export default class Agent const store = new PublicMediatorStore(pluto); const handler = new BasicMediatorHandler(mediatorDID, mercury, store); - const pollux = new Pollux(castor) + const pollux = new Pollux(castor); const seed = params.seed ?? apollo.createRandomSeed().seed; const agentCredentials = new AgentCredentials( @@ -169,7 +169,7 @@ export default class Agent pluto, pollux, seed - ) + ); const manager = new ConnectionsManager(castor, mercury, pluto, agentCredentials, handler); const agent = new Agent( diff --git a/tests/agent/Agent.functional.test.ts b/tests/agent/Agent.functional.test.ts new file mode 100644 index 000000000..cd2038d4a --- /dev/null +++ b/tests/agent/Agent.functional.test.ts @@ -0,0 +1,61 @@ +/** + * @jest-environment node + */ +import chai from "chai"; +import chaiAsPromised from "chai-as-promised"; +import * as sinon from "sinon"; +import SinonChai from "sinon-chai"; +import Agent from "../../src/prism-agent/Agent"; +import { Pluto } from "../../src"; +import { mockPluto } from "../fixtures/inmemory/factory"; +import * as Fixtures from "../fixtures"; + +chai.use(SinonChai); +chai.use(chaiAsPromised); +const expect = chai.expect; + +describe("Agent", () => { + let agent: Agent; + let pluto: Pluto; + let sandbox: sinon.SinonSandbox; + + afterEach(async () => { + jest.useRealTimers(); + + await agent.stop(); + sandbox.restore(); + }); + + beforeEach(async () => { + jest.useFakeTimers(); + + sandbox = sinon.createSandbox(); + pluto = mockPluto(); + agent = Agent.initialize({ mediatorDID: Fixtures.DIDs.peerDID1, pluto }); + // ??? cant start agent - errors for start mediation + // await agent.start(); + await pluto.start(); + }); + + describe("Functional Tests", () => { + describe("createPrismDID", () => { + test("default parameters - should return unique DIDs", async () => { + const first = await agent.createNewPrismDID("a"); + const second = await agent.createNewPrismDID("a"); + + expect(first).to.not.deep.eq(second); + }); + + test("same services and keyPathIndex - should return the same DID", async () => { + const services = []; + const keyPathIndex = 1; + // alias (first parameter) doesn't affect ouput + const first = await agent.createNewPrismDID("first", services, keyPathIndex); + const second = await agent.createNewPrismDID("second", services, keyPathIndex); + + expect(first).to.deep.eq(second); + expect(first.toString()).to.deep.eq(second.toString()); + }); + }); + }); +}); diff --git a/tests/apollo/Apollo.createPrivateKey.test.ts b/tests/apollo/Apollo.createPrivateKey.test.ts new file mode 100644 index 000000000..00e83245f --- /dev/null +++ b/tests/apollo/Apollo.createPrivateKey.test.ts @@ -0,0 +1,186 @@ +import { expect, assert } from "chai"; + +import Apollo from "../../src/apollo/Apollo"; +import { ApolloError, Curve, KeyProperties, KeyTypes } from "../../src/domain/models"; +import { Secp256k1PrivateKey } from "../../src/apollo/utils/Secp256k1PrivateKey"; +import { DerivationPath } from "../../src/apollo/utils/derivation/DerivationPath"; +import * as Fixtures from "../fixtures"; + +describe("Apollo", () => { + let apollo: Apollo; + + beforeEach(() => { + apollo = new Apollo(); + }); + + describe("createPrivateKey", () => { + const seedHex = "947877896c61a5c64f266adbebbc69a2a01f1a2cfbf72c08a11c693d0429ccded34bdc0c28b5be910a5095b97e7bc6e3e209527ce8e75f9964d25cd6f6ad63e0"; + + describe("Secp256k1", () => { + it("default - creates a new key", () => { + const result = apollo.createPrivateKey({ + [KeyProperties.type]: KeyTypes.EC, + [KeyProperties.curve]: Curve.SECP256K1, + [KeyProperties.seed]: seedHex, + }); + + expect(result).to.be.an.instanceOf(Secp256k1PrivateKey); + expect(result.raw).to.eql(Uint8Array.from([232, 19, 52, 112, 248, 184, 7, 231, 180, 5, 168, 209, 33, 77, 26, 108, 130, 201, 137, 168, 15, 197, 29, 152, 88, 235, 87, 76, 73, 255, 159, 229])); + expect(result.type).to.eq(KeyTypes.EC); + expect(result.curve).to.eq(Curve.SECP256K1); + + expect(result.getProperty(KeyProperties.curve)).to.eq(Curve.SECP256K1); + expect(result.getProperty(KeyProperties.chainCode)).to.eq("7e9952eb18d135283fd633180e31b202a5ec87e3e37cc66c6836f18bdf9684b2"); + + // no derivationPath provided, defaults to `m/0'/0'/0'` hexed + expect(result.getProperty(KeyProperties.derivationPath)).to.eq("6d2f30272f30272f3027"); + + // no index provided, defaults to 0 + expect(result.getProperty(KeyProperties.index)).to.eq("0"); + }); + + it("KeyProperties.type - missing - throws", () => { + const sut = () => apollo.createPrivateKey({ + [KeyProperties.curve]: Curve.SECP256K1, + [KeyProperties.seed]: seedHex, + }); + + expect(sut).to.throw(ApolloError.InvalidKeyType); + }); + + it("KeyProperties.curve - missing - throws", () => { + const sut = () => apollo.createPrivateKey({ + [KeyProperties.type]: KeyTypes.EC, + [KeyProperties.seed]: seedHex, + }); + + expect(sut).to.throw(ApolloError.InvalidKeyCurve); + }); + + it("KeyProperties.seed - missing - throws", () => { + const sut = () => apollo.createPrivateKey({ + [KeyProperties.type]: KeyTypes.EC, + [KeyProperties.curve]: Curve.SECP256K1, + }); + + // ?? improve to more specific ApolloError similar to InvalidKeyType and InvalidKeyCurve + expect(sut).to.throw(ApolloError.MissingKeyParameters); + }); + + it("KeyProperties.seed - invalid (non hex) - throws", () => { + const sut = () => apollo.createPrivateKey({ + [KeyProperties.type]: KeyTypes.EC, + [KeyProperties.curve]: Curve.SECP256K1, + [KeyProperties.seed]: "notAHexSeed", + }); + + // ?? improve to specific ApolloError (currently IllegalArgumentException) + expect(sut).to.throw(); + }); + + + it("KeyProperties.derivationPath - invalid - throws", () => { + const sut = () => apollo.createPrivateKey({ + [KeyProperties.type]: KeyTypes.EC, + [KeyProperties.curve]: Curve.SECP256K1, + [KeyProperties.seed]: seedHex, + [KeyProperties.derivationPath]: "invalid" + }); + + // ?? improve code to throw specific ApolloError (currently standard Error with message) + expect(sut).to.throw(); + }); + + Fixtures.Keys.Derivations.forEach(fixture => { + test('key.derive is equal to apollo.createPrivateKey with derivationPath', function () { + const master = apollo.createPrivateKey({ + [KeyProperties.type]: KeyTypes.EC, + [KeyProperties.curve]: Curve.SECP256K1, + [KeyProperties.seed]: fixture.seed, + }); + + const child = master.isDerivable() + ? master.derive(DerivationPath.from(fixture.path)) + : null; + + const derived = apollo.createPrivateKey({ + [KeyProperties.type]: KeyTypes.EC, + [KeyProperties.curve]: Curve.SECP256K1, + [KeyProperties.seed]: fixture.seed, + [KeyProperties.derivationPath]: fixture.path + }); + + assert.equal(master.to.String("hex"), fixture.raw); + assert.equal(child?.to.String("hex"), fixture.derived); + assert.equal(derived.to.String("hex"), fixture.derived); + }); + }); + + it("KeyProperties.derivationPath - `m/0'/0'/0'` - returns key", () => { + const result = apollo.createPrivateKey({ + [KeyProperties.type]: KeyTypes.EC, + [KeyProperties.curve]: Curve.SECP256K1, + [KeyProperties.seed]: seedHex, + [KeyProperties.derivationPath]: `m/0'/0'/0'` + }); + + expect(result).to.be.an.instanceOf(Secp256k1PrivateKey); + expect(result.raw).to.eql(Uint8Array.from([48, 82, 38, 112, 243, 185, 72, 236, 243, 178, 189, 199, 153, 90, 179, 73, 176, 178, 227, 149, 217, 24, 191, 123, 45, 65, 144, 242, 89, 29, 253, 162])); + expect(result.type).to.eq(KeyTypes.EC); + expect(result.curve).to.eq(Curve.SECP256K1); + expect(result.getProperty(KeyProperties.curve)).to.eq(Curve.SECP256K1); + expect(result.getProperty(KeyProperties.chainCode)).to.eq("99f91ece40d2b5ff6896e96fd1e626dd17bc7f21d09538795021318009a1013c"); + expect(result.getProperty(KeyProperties.derivationPath)).to.eq("6d2f30272f30272f3027"); + expect(result.getProperty(KeyProperties.index)).to.eq("0"); + }); + + it("KeyProperties.derivationPath - `m/1'/0'/0'` - returns key", () => { + const result = apollo.createPrivateKey({ + [KeyProperties.type]: KeyTypes.EC, + [KeyProperties.curve]: Curve.SECP256K1, + [KeyProperties.seed]: seedHex, + [KeyProperties.derivationPath]: `m/1'/0'/0'` + }); + + expect(result).to.be.an.instanceOf(Secp256k1PrivateKey); + expect(result.raw).to.eql(Uint8Array.from([76, 199, 11, 14, 245, 150, 196, 75, 166, 18, 237, 102, 248, 151, 5, 102, 136, 236, 82, 7, 203, 175, 156, 107, 13, 197, 175, 90, 127, 155, 202, 55])); + expect(result.type).to.eq(KeyTypes.EC); + expect(result.curve).to.eq(Curve.SECP256K1); + expect(result.getProperty(KeyProperties.curve)).to.eq(Curve.SECP256K1); + expect(result.getProperty(KeyProperties.chainCode)).to.eq("fee48c5a862316d1ea59b77258850f64de2a316796db043a4ebca1616c1c0d24"); + expect(result.getProperty(KeyProperties.derivationPath)).to.eq("6d2f31272f30272f3027"); + expect(result.getProperty(KeyProperties.index)).to.eq("1"); + }); + + it("KeyProperties.derivationPath - `m/2'/0'/0'` - returns key", () => { + const result = apollo.createPrivateKey({ + [KeyProperties.type]: KeyTypes.EC, + [KeyProperties.curve]: Curve.SECP256K1, + [KeyProperties.seed]: seedHex, + [KeyProperties.derivationPath]: `m/2'/0'/0'` + }); + + expect(result).to.be.an.instanceOf(Secp256k1PrivateKey); + expect(result.raw).to.eql(Uint8Array.from([190, 150, 87, 185, 32, 22, 148, 107, 164, 112, 97, 31, 218, 74, 115, 41, 249, 24, 178, 59, 13, 116, 112, 115, 233, 84, 190, 204, 176, 11, 151, 64])); + expect(result.type).to.eq(KeyTypes.EC); + expect(result.curve).to.eq(Curve.SECP256K1); + expect(result.getProperty(KeyProperties.curve)).to.eq(Curve.SECP256K1); + expect(result.getProperty(KeyProperties.chainCode)).to.eq("6bfbb6d7bee48110dd0dd1437caa9e88dba86e4bc28585e8e8ab052c96414a48"); + expect(result.getProperty(KeyProperties.derivationPath)).to.eq("6d2f32272f30272f3027"); + expect(result.getProperty(KeyProperties.index)).to.eq("2"); + }); + + // ? what behaviour do we expect in these cases + // ? should index even be considered > remove from KeyProperties parameter + // derivationPath and index + // index and no derivationPath + + // TODO keyData tests + }); + + + // ed25519 private key returned + // with keyData + // without keyData + }); +}); diff --git a/tests/apollo/Apollo.test.ts b/tests/apollo/Apollo.test.ts index add0c87d6..ca68c70a6 100644 --- a/tests/apollo/Apollo.test.ts +++ b/tests/apollo/Apollo.test.ts @@ -1,5 +1,4 @@ import { expect, assert } from "chai"; -import * as Fixtures from "../fixtures"; import Apollo from "../../src/apollo/Apollo"; import { Secp256k1KeyPair } from "../../src/apollo/utils/Secp256k1KeyPair"; @@ -22,6 +21,7 @@ import { StorableKey, MnemonicWordList } from "../../src/domain/models"; +import * as Fixtures from "../fixtures"; describe("Apollo", () => { let apollo: Apollo; @@ -300,37 +300,25 @@ describe("Apollo", () => { }); it("Should derive secp256k1 privateKey the same way as if we create a new key in Apollo.", async () => { - // const seed = apollo.createRandomSeed().seed; const seedHex = "a4dd58542e9959eccb56832a953c0e54b3321036b6165ec2f3c1ef533cd1d6da5fae8010c587535404534c192397483c765505f67e62b26026392f8a0cf8ba51"; - const privateKey = apollo.createPrivateKey({ - type: KeyTypes.EC, - curve: Curve.SECP256K1, - // seed: Buffer.from(seed.value).toString("hex"), - seed: seedHex - }); - - const derivationPathStr = `m/0'/0'/1'`; - const path = DerivationPath.fromPath(derivationPathStr); - - const derivedFromPrivate = - privateKey.isDerivable() && privateKey.derive(path); - - expect(derivedFromPrivate).to.not.equal(false); - - const privateKey2 = apollo.createPrivateKey({ + const createKeyArgs = { type: KeyTypes.EC, curve: Curve.SECP256K1, - // seed: Buffer.from(seed.value).toString("hex"), seed: seedHex, - derivationPath: derivationPathStr, - }); + }; + const privateKey = apollo.createPrivateKey({ ...createKeyArgs }); + const path = DerivationPath.from(`m/0'/0'/1'`); + const derived = privateKey.isDerivable() && privateKey.derive(path); - const raw1 = Buffer.from( - (derivedFromPrivate as PrivateKey).getEncoded() - ).toString("base64url"); + expect(derived).to.not.equal(false); - const raw2 = Buffer.from(privateKey2.getEncoded()).toString("base64url"); + const withDerivationPath = apollo.createPrivateKey({ + ...createKeyArgs, + derivationPath: path, + }); + const raw1 = Buffer.from((derived as PrivateKey).getEncoded()).toString("base64url"); + const raw2 = Buffer.from(withDerivationPath.getEncoded()).toString("base64url"); const raw3 = Buffer.from(privateKey.getEncoded()).toString("base64url"); expect(raw1).to.equal(raw2); diff --git a/tests/apollo/keys/Secp256k1.test.ts b/tests/apollo/keys/Secp256k1.test.ts index f387c0011..2fb48e483 100644 --- a/tests/apollo/keys/Secp256k1.test.ts +++ b/tests/apollo/keys/Secp256k1.test.ts @@ -7,9 +7,18 @@ import { Secp256k1KeyPair } from "../../../src/apollo/utils/Secp256k1KeyPair"; import { ECPublicKeyInitialization } from "../../../src/domain/models/errors/Apollo"; import { DerivationPath } from "../../../src/apollo/utils/derivation/DerivationPath"; +import ApolloPKG from "@atala/apollo"; +const ApolloSDK = ApolloPKG.io.iohk.atala.prism.apollo; +const HDKey = ApolloSDK.derivation.HDKey; +const BigIntegerWrapper = ApolloSDK.derivation.BigIntegerWrapper; + describe("Keys", () => { describe("Secp256k1", () => { describe("PrivateKey", () => { + const seedHex = "947877896c61a5c64f266adbebbc69a2a01f1a2cfbf72c08a11c693d0429ccded34bdc0c28b5be910a5095b97e7bc6e3e209527ce8e75f9964d25cd6f6ad63e0"; + const chainCodeHex = "7e9952eb18d135283fd633180e31b202a5ec87e3e37cc66c6836f18bdf9684b2"; + + test("invalid key size - throws", () => { const raw = new Uint8Array([36]); expect(() => new Secp256k1PrivateKey(raw)).throws(ECPublicKeyInitialization); @@ -24,6 +33,90 @@ describe("Keys", () => { expect(privateKey.isDerivable()).to.be.true; }); + // isDerivable true - so test derive function + describe("derive", () => { + const baseHex = "e8133470f8b807e7b405a8d1214d1a6c82c989a80fc51d9858eb574c49ff9fe5"; + const baseRaw = Buffer.from(baseHex, "hex"); + + test("keySpecification.chainCode missing - throws", () => { + const key = new Secp256k1PrivateKey(baseRaw); + const derivationPath = DerivationPath.from(0); + + expect(() => key.derive(derivationPath)).to.throw; + }); + + test("DerivationPath - m/0'/0'/0'", () => { + const key = new Secp256k1PrivateKey(raw); + const derivationPath = DerivationPath.from(`m/0'/0'/0'`); + key.keySpecification.set(KeyProperties.chainCode, chainCodeHex); + + const result = key.derive(derivationPath); + + expect(result).to.be.an.instanceOf(Secp256k1PrivateKey); + expect(result.raw).to.eql(Uint8Array.from([12, 175, 213, 208, 150, 154, 3, 194, 3, 156, 49, 33, 35, 255, 156, 238, 125, 190, 36, 208, 31, 209, 82, 108, 171, 255, 50, 80, 236, 226, 166, 255])); + expect(result.type).to.eq(KeyTypes.EC); + expect(result.curve).to.eq(Curve.SECP256K1); + + // chainCode changes parent -> child + expect(result.getProperty(KeyProperties.chainCode)).to.not.eq(chainCodeHex); + expect(result.getProperty(KeyProperties.chainCode)).to.eq("55c577fab08382958dcdfcfd6c34e4c45d9ec467c20abb81ce627991ac9e7863"); + expect(result.getProperty(KeyProperties.curve)).to.eq(Curve.SECP256K1); + expect(result.getProperty(KeyProperties.index)).to.eq("0"); + + // expect(result.getProperty(KeyProperties.derivationPath)).to.eq(derivationPath.toString()); + expect(result.getProperty(KeyProperties.derivationPath)).to.eq("6d2f30272f30272f3027"); + }); + + test("DerivationPath - m/1'/0'/0'", () => { + const key = new Secp256k1PrivateKey(raw); + const derivationPath = DerivationPath.from("m/1'/0'/0'"); + key.keySpecification.set(KeyProperties.chainCode, chainCodeHex); + + const result = key.derive(derivationPath); + + + expect(result.raw).to.eql(Uint8Array.from([220, 223, 118, 183, 102, 141, 198, 60, 221, 162, 132, 68, 233, 188, 169, 39, 128, 174, 202, 114, 4, 203, 31, 40, 35, 85, 166, 164, 178, 17, 158, 150])); + expect(result.getProperty(KeyProperties.chainCode)).to.eq("f22fdc4dd573ce17243983faa6492fc33fab35ecfa3f8ad09aa958044a2752f7"); + expect(result.getProperty(KeyProperties.index)).to.eq("1"); + // expect(result.getProperty(KeyProperties.derivationPath)).to.eq(`m/1'/0'/0'`); + expect(result.getProperty(KeyProperties.derivationPath)).to.eq("6d2f31272f30272f3027"); + }); + + test("DerivationPath - m/2'/0'/0'", () => { + const key = new Secp256k1PrivateKey(raw); + const derivationPath = DerivationPath.from("m/2'/0'/0'"); + key.keySpecification.set(KeyProperties.chainCode, chainCodeHex); + + const result = key.derive(derivationPath); + + expect(result.raw).to.eql(Uint8Array.from([58, 84, 10, 170, 72, 91, 146, 143, 203, 60, 169, 120, 33, 226, 221, 43, 96, 150, 44, 108, 105, 33, 243, 19, 115, 162, 33, 142, 129, 22, 122, 221])); + expect(result.getProperty(KeyProperties.chainCode)).to.eq("e56cd109bae854dcf3fc0b766067f9e825901bf1bcfc67dc4f5eaee74cf9c8ea"); + expect(result.getProperty(KeyProperties.index)).to.eq("2"); + // expect(result.getProperty(KeyProperties.derivationPath)).to.eq(`m/1'/0'/0'`); + expect(result.getProperty(KeyProperties.derivationPath)).to.eq("6d2f32272f30272f3027"); + }); + + test("Secp.derive returns same as HDKey.derive", () => { + const path = "m/42'/0'/0'"; + const hdkey = new HDKey( + Int8Array.from(baseRaw), + null, + Int8Array.from(Buffer.from(chainCodeHex, "hex")), + 0, + BigIntegerWrapper.initFromInt(0) + ); + const hdChild = hdkey.derive(path); + + const secp = new Secp256k1PrivateKey(baseRaw); + secp.keySpecification.set(KeyProperties.chainCode, chainCodeHex); + const secpChild = secp.derive(DerivationPath.from(path)); + + const hdResult = Buffer.from(hdChild.privateKey!).toString("hex"); + const spResult = Buffer.from(secpChild.raw).toString("hex"); + expect(hdResult).to.eql(spResult); + }); + }); + test("isExportable - not implemented", () => { expect(privateKey.isExportable()).to.be.false; }); @@ -80,10 +173,9 @@ describe("Keys", () => { test("instantiated through `derive` - index set", () => { const path = DerivationPath.fromPath(`m/0'/0'/0'`); - const tmp = new Secp256k1PrivateKey(privateKey.raw); - const seed = "223fa7bd4bde29fb0eada057770d3f70c32421bca9846fcaec58d6a36bc21a77ef49ac1c3b45163b2450e5679b55ef8a11006607ae78830bce425c02778b5210"; - tmp.keySpecification.set(KeyProperties.seed, seed); - const derived = tmp.derive(path); + const key = new Secp256k1PrivateKey(privateKey.raw); + key.keySpecification.set(KeyProperties.chainCode, chainCodeHex); + const derived = key.derive(path); expect(derived.index).to.equal(0); }); diff --git a/tests/fixtures/inmemory/factory.ts b/tests/fixtures/inmemory/factory.ts new file mode 100644 index 000000000..63c505eb5 --- /dev/null +++ b/tests/fixtures/inmemory/factory.ts @@ -0,0 +1,15 @@ +import { randomUUID } from "crypto"; +import { Apollo, Pluto, Store } from "../../../src"; +import InMemoryStore from "./index"; + +export const mockPluto = (args?: { apollo: Apollo; }) => { + const apollo = args?.apollo ?? new Apollo(); + + const store = new Store({ + name: 'test' + randomUUID(), + storage: InMemoryStore, + password: Buffer.from("demoapp").toString("hex") + }); + + return new Pluto(store, apollo); +}; diff --git a/tests/fixtures/keys.ts b/tests/fixtures/keys.ts index c34ee4208..05bd2db98 100644 --- a/tests/fixtures/keys.ts +++ b/tests/fixtures/keys.ts @@ -34,3 +34,42 @@ export const x25519 = new X25519KeyPair( x25519PrivateKey, x25519PrivateKey.publicKey() ); + +export const Derivations = [ + { + seed: '947877896c61a5c64f266adbebbc69a2a01f1a2cfbf72c08a11c693d0429ccded34bdc0c28b5be910a5095b97e7bc6e3e209527ce8e75f9964d25cd6f6ad63e0', + raw: 'e8133470f8b807e7b405a8d1214d1a6c82c989a80fc51d9858eb574c49ff9fe5', + path: "m/1'/0'/0'", + derived: '4cc70b0ef596c44ba612ed66f897056688ec5207cbaf9c6b0dc5af5a7f9bca37' + }, + { + seed: '947877896c61a5c64f266adbebbc69a2a01f1a2cfbf72c08a11c693d0429ccded34bdc0c28b5be910a5095b97e7bc6e3e209527ce8e75f9964d25cd6f6ad63e0', + raw: 'e8133470f8b807e7b405a8d1214d1a6c82c989a80fc51d9858eb574c49ff9fe5', + path: "m/1'/2'/3'", + derived: '37ef69dc5ebdc549fe7d4775381be9025e52b5089c5a15436293f660937f4801' + }, + { + seed: '947877896c61a5c64f266adbebbc69a2a01f1a2cfbf72c08a11c693d0429ccded34bdc0c28b5be910a5095b97e7bc6e3e209527ce8e75f9964d25cd6f6ad63e0', + raw: 'e8133470f8b807e7b405a8d1214d1a6c82c989a80fc51d9858eb574c49ff9fe5', + path: "m/2'/2'/4'", + derived: '9c34aa70ee4ab4666aac2a6511691e69d55be608f799c3f8dd83911758c6395e' + }, + { + seed: '947877896c61a5c64f266adbebbc69a2a01f1a2cfbf72c08a11c693d0429ccded34bdc0c28b5be910a5095b97e7bc6e3e209527ce8e75f9964d25cd6f6ad63e0', + raw: 'e8133470f8b807e7b405a8d1214d1a6c82c989a80fc51d9858eb574c49ff9fe5', + path: "m/44'/6'/4'", + derived: 'b5ac87a07fb28997cec262841f4d6e79089ce61b58e39681623c61edf4d690a0' + }, + { + seed: 'fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542', + raw: '4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e', + path: "m/3'/2'/1'", + derived: 'c4a82f0e3a06847d1d40962457eec69fbe426eedb6c2f69971df7412a3b630ec' + }, + { + seed: 'fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542', + raw: '4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e', + path: "m/224'/8'/16'", + derived: '8962f31369fb10f0585b4a22e7e24f75769b2eea9758a9aec33674eab024fa59' + }, +];