Skip to content

Commit

Permalink
fix: Agent.createNewPrismDID to use derivationPath (#158)
Browse files Browse the repository at this point in the history
  • Loading branch information
curtis-h committed Mar 26, 2024
1 parent 85d34d9 commit b4a4a20
Show file tree
Hide file tree
Showing 17 changed files with 558 additions and 102 deletions.
1 change: 1 addition & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"request": "launch",
"program": "${workspaceRoot}/node_modules/jest/bin/jest.js",
"args": [
"${fileBasenameNoExtension}",
"--colors",
"--workerThreads",
"--maxWorkers",
Expand Down
51 changes: 22 additions & 29 deletions src/apollo/Apollo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}

Expand Down
57 changes: 30 additions & 27 deletions src/apollo/utils/Secp256k1PrivateKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() {
Expand All @@ -105,6 +107,7 @@ export class Secp256k1PrivateKey
);
}

// ?? move to `from` property
static secp256k1FromBigInteger(bigInteger: BN): Secp256k1PrivateKey {
return new Secp256k1PrivateKey(Uint8Array.from(bigInteger.toArray()));
}
Expand Down
10 changes: 3 additions & 7 deletions src/apollo/utils/Secp256k1PublicKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,21 @@ 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
*/
export class Secp256k1PublicKey extends PublicKey implements StorableKey, ExportableKey, VerifiableKey {
public readonly recoveryId = StorableKey.recoveryId("secp256k1", "pub");


public keySpecification: Map<KeyProperties | string, string> = new Map();
public size: number;
public raw: Uint8Array;
Expand Down
57 changes: 57 additions & 0 deletions src/apollo/utils/derivation/DerivationPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -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
*
Expand Down
10 changes: 10 additions & 0 deletions src/domain/models/KeyProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
3 changes: 3 additions & 0 deletions src/domain/models/errors/Apollo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
5 changes: 5 additions & 0 deletions src/pluto/repositories/KeyRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ export class KeyRepository extends MapperRepository<Models.Key, Domain.PrivateKe
...model,
raw: Buffer.from(model.rawHex, "hex")
});

if (model.index != undefined) {
domain.keySpecification.set(Domain.KeyProperties.index, model.index.toString());
}

return this.withId(domain, model.uuid);
}

Expand Down
21 changes: 14 additions & 7 deletions src/prism-agent/Agent.DIDHigherFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
Curve,
DID,
KeyProperties,
KeyTypes,
PublicKey,
Seed,
Service,
Expand All @@ -12,13 +13,13 @@ import { Apollo } from "../domain/buildingBlocks/Apollo";
import { Castor } from "../domain/buildingBlocks/Castor";
import { Pluto } from "../domain/buildingBlocks/Pluto";
import { AgentError } from "../domain/models/Errors";
import { KeyTypes } from "../domain/models";
import {
AgentDIDHigherFunctions as AgentDIDHigherFunctionsClass,
ConnectionsManager,
MediatorHandler,
} from "./types";
import { PrismKeyPathIndexTask } from "./Agent.PrismKeyPathIndexTask";
import { DerivationPath } from "../apollo/utils/derivation/DerivationPath";

/**
* An extension for the Edge agent that groups some DID related operations mainly used to expose the create did functionality
Expand Down Expand Up @@ -141,12 +142,13 @@ export class AgentDIDHigherFunctions implements AgentDIDHigherFunctionsClass {
services: Service[],
keyPathIndex?: number
): Promise<DID> {
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();
Expand All @@ -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<number> {
const getIndexTask = new PrismKeyPathIndexTask(this.pluto);
const index = await getIndexTask.run();
const next = index + 1;
Expand Down
2 changes: 1 addition & 1 deletion src/prism-agent/Agent.PrismKeyPathIndexTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ export class PrismKeyPathIndexTask {

return keyPathIndex;
}
}
}
4 changes: 2 additions & 2 deletions src/prism-agent/Agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -169,7 +169,7 @@ export default class Agent
pluto,
pollux,
seed
)
);
const manager = new ConnectionsManager(castor, mercury, pluto, agentCredentials, handler);

const agent = new Agent(
Expand Down

1 comment on commit b4a4a20

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines Statements Branches Functions
Coverage: 69%
70.04% (1822/2601) 56.6% (716/1265) 72.65% (542/746)

JUnit

Tests Skipped Failures Errors Time
408 2 💤 0 ❌ 0 🔥 59.985s ⏱️

Please sign in to comment.