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

feature: add API endpoint 'ecdsa_public_key' to ic-management #652

Closed
53 changes: 38 additions & 15 deletions packages/ic-management/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const { status, memory_size, ...rest } = await canisterStatus(YOUR_CANISTER_ID);

### :factory: ICManagementCanister

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L30)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L34)

#### Methods

Expand All @@ -72,14 +72,15 @@ const { status, memory_size, ...rest } = await canisterStatus(YOUR_CANISTER_ID);
- [canisterStatus](#gear-canisterstatus)
- [deleteCanister](#gear-deletecanister)
- [provisionalCreateCanisterWithCycles](#gear-provisionalcreatecanisterwithcycles)
- [ecdsaPublicKey](#gear-ecdsapublickey)

##### :gear: create

| Method | Type |
| -------- | ---------------------------------------------------------------- |
| `create` | `(options: ICManagementCanisterOptions) => ICManagementCanister` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L35)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L39)

##### :gear: createCanister

Expand All @@ -89,7 +90,7 @@ Create a new canister
| ---------------- | ------------------------------------------------------------------------------------- |
| `createCanister` | `({ settings, senderCanisterVersion, }?: CreateCanisterParams) => Promise<Principal>` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L75)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L79)

##### :gear: updateSettings

Expand All @@ -99,7 +100,7 @@ Update canister settings
| ---------------- | ------------------------------------------------------------------------------------------- |
| `updateSettings` | `({ canisterId, senderCanisterVersion, settings, }: UpdateSettingsParams) => Promise<void>` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L96)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L100)

##### :gear: installCode

Expand All @@ -109,7 +110,7 @@ Install code to a canister
| ------------- | ----------------------------------------------------------------------------------------------------- |
| `installCode` | `({ mode, canisterId, wasmModule, arg, senderCanisterVersion, }: InstallCodeParams) => Promise<void>` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L118)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L122)

##### :gear: uploadChunk

Expand All @@ -124,7 +125,7 @@ Parameters:
- `params.canisterId`: The canister in which the chunks will be stored.
- `params.chunk`: A chunk of Wasm module.

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L143)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L147)

##### :gear: clearChunkStore

Expand All @@ -138,7 +139,7 @@ Parameters:

- `params.canisterId`: The canister in which the chunks are stored.

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L163)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L167)

##### :gear: storedChunks

Expand All @@ -152,7 +153,7 @@ Parameters:

- `params.canisterId`: The canister in which the chunks are stored.

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L182)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L186)

##### :gear: installChunkedCode

Expand All @@ -172,7 +173,7 @@ Parameters:
- `params.storeCanisterId`: Specifies the canister in whose chunk storage the chunks are stored (this parameter defaults to target_canister if not specified).
- `params.wasmModuleHash`: The Wasm module hash as hex string. Used to check that the SHA-256 hash of wasm_module is equal to the wasm_module_hash parameter and can calls install_code with parameters.

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L207)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L211)

##### :gear: uninstallCode

Expand All @@ -182,7 +183,7 @@ Uninstall code from a canister
| --------------- | -------------------------------------------------------------------------------- |
| `uninstallCode` | `({ canisterId, senderCanisterVersion, }: UninstallCodeParams) => Promise<void>` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L240)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L244)

##### :gear: startCanister

Expand All @@ -192,7 +193,7 @@ Start a canister
| --------------- | ------------------------------------------ |
| `startCanister` | `(canisterId: Principal) => Promise<void>` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L255)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L259)

##### :gear: stopCanister

Expand All @@ -202,7 +203,7 @@ Stop a canister
| -------------- | ------------------------------------------ |
| `stopCanister` | `(canisterId: Principal) => Promise<void>` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L264)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L268)

##### :gear: canisterStatus

Expand All @@ -212,7 +213,7 @@ Get canister details (memory size, status, etc.)
| ---------------- | ------------------------------------------------------------ |
| `canisterStatus` | `(canisterId: Principal) => Promise<canister_status_result>` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L273)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L277)

##### :gear: deleteCanister

Expand All @@ -222,7 +223,7 @@ Deletes a canister
| ---------------- | ------------------------------------------ |
| `deleteCanister` | `(canisterId: Principal) => Promise<void>` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L284)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L288)

##### :gear: provisionalCreateCanisterWithCycles

Expand All @@ -232,7 +233,29 @@ Creates a canister. Only available on development instances.
| ------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `provisionalCreateCanisterWithCycles` | `({ settings, amount, canisterId, }?: ProvisionalCreateCanisterWithCyclesParams) => Promise<Principal>` |

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L296)
[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L300)

##### :gear: ecdsaPublicKey

Calculate a SEC1 encoded ECDSA public key for the given canister using the given derivation path.

The `derivation_path` is a vector of variable length byte strings. Each byte string may be of arbitrary length, including empty. The total number of byte strings in the `derivation_path` must be at most 255. The suggested standard to be used is [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki).

The `key_id` is a struct specifying both a `curve` and a `name`. The availability of a particular `key_id` depends on implementation.

The optional parameter `canister_id` can be set to specify the canister ID that will be used to derive the address.

| Method | Type |
| ---------------- | --------------------------------------------------------------------------------------------------- |
| `ecdsaPublicKey` | `({ keyId, canisterId, derivationPath, }: EcdsaPublicKeyParams) => Promise<EcdsaPublicKeyResponse>` |

Parameters:

- `params.keyId`: The key id for which the public key will be derived, consisting of a `name` and a `curve`.
- `params.canisterId`: The specific canister ID for which the public key will be derived; it can be optional.
- `params.derivationPath`: The derivation path that will be used to derive the public key. The suggested standard is [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki).

[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ic-management/src/ic-management.canister.ts#L333)

<!-- TSDOC_END -->

Expand Down
56 changes: 55 additions & 1 deletion packages/ic-management/src/ic-management.canister.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ import {
} from "./ic-management.mock";
import {
CanisterSettings,
EcdsaPublicKeyParams,
InstallCodeParams,
InstallMode,
KeyId,
LogVisibility,
UnsupportedLogVisibility,
toInstallMode,
Expand All @@ -25,7 +27,10 @@ import {
type StoredChunksParams,
type UploadChunkParams,
} from "./types/ic-management.params";
import { CanisterStatusResponse } from "./types/ic-management.responses";
import {
CanisterStatusResponse,
EcdsaPublicKeyResponse,
} from "./types/ic-management.responses";

describe("ICManagementCanister", () => {
const mockAgent: HttpAgent = mock<HttpAgent>();
Expand Down Expand Up @@ -665,4 +670,53 @@ describe("ICManagementCanister", () => {
expect(call).rejects.toThrowError(Error);
});
});

describe("ecdsaPublicKey", () => {
const keyId: KeyId = {
name: "testKey",
curve: { secp256k1: null },
};
const canisterId = mockCanisterId;
const derivationPath = [
new Uint8Array([1, 2, 3]),
new Uint8Array([4, 5, 6]),
];
const params: EcdsaPublicKeyParams = {
keyId,
canisterId,
derivationPath,
};

it("should return the ECDSA public key successfully", async () => {
const response: EcdsaPublicKeyResponse = {
public_key: new Uint8Array([10, 11, 12]),
chain_code: new Uint8Array([20, 21, 22]),
};
const service = mock<IcManagementService>();
service.ecdsa_public_key.mockResolvedValue(response);

const icManagement = await createICManagement(service);

const res = await icManagement.ecdsaPublicKey({ ...params });

expect(res).toEqual(response);
expect(service.ecdsa_public_key).toHaveBeenCalledWith({
key_id: keyId,
canister_id: toNullable(canisterId),
derivation_path: derivationPath,
});
});

it("throws Error", async () => {
const error = new Error("Test");
const service = mock<IcManagementService>();
service.ecdsa_public_key.mockRejectedValue(error);

const icManagement = await createICManagement(service);

const call = () => icManagement.ecdsaPublicKey({ ...params });

expect(call).rejects.toThrowError(Error);
});
});
});
34 changes: 33 additions & 1 deletion packages/ic-management/src/ic-management.canister.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
toInstallMode,
type ClearChunkStoreParams,
type CreateCanisterParams,
type EcdsaPublicKeyParams,
type InstallChunkedCodeParams,
type InstallCodeParams,
type ProvisionalCreateCanisterWithCyclesParams,
Expand All @@ -25,7 +26,10 @@ import {
type UpdateSettingsParams,
type UploadChunkParams,
} from "./types/ic-management.params";
import type { CanisterStatusResponse } from "./types/ic-management.responses";
import type {
CanisterStatusResponse,
EcdsaPublicKeyResponse,
} from "./types/ic-management.responses";

export class ICManagementCanister {
private constructor(private readonly service: IcManagementService) {
Expand Down Expand Up @@ -308,4 +312,32 @@ export class ICManagementCanister {

return canister_id;
};

/**
* Calculate a SEC1 encoded ECDSA public key for the given canister using the given derivation path.
*
* The `derivation_path` is a vector of variable length byte strings. Each byte string may be of arbitrary length, including empty. The total number of byte strings in the `derivation_path` must be at most 255. The suggested standard to be used is [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki).
*
* The `key_id` is a struct specifying both a `curve` and a `name`. The availability of a particular `key_id` depends on implementation.
*
* The optional parameter `canister_id` can be set to specify the canister ID that will be used to derive the address.
*
* @link https://internetcomputer.org/docs/current/references/ic-interface-spec#ic-ecdsa_public_key
*
* @param {EcdsaPublicKeyParams} params
* @param {KeyId} params.keyId The key id for which the public key will be derived, consisting of a `name` and a `curve`.
* @param {Principal} [params.canisterId] The specific canister ID for which the public key will be derived; it can be optional.
* @param {string} params.derivationPath The derivation path that will be used to derive the public key. The suggested standard is [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki).
* @returns {Promise<EcdsaPublicKeyResponse>} The return result is an extended public key consisting of an ECDSA `public_key`, encoded in SEC1 compressed form, and a `chain_code`, which can be used to deterministically derive child keys of the `public_key`.
*/
ecdsaPublicKey = ({
keyId,
canisterId,
derivationPath,
}: EcdsaPublicKeyParams): Promise<EcdsaPublicKeyResponse> =>
this.service.ecdsa_public_key({
key_id: keyId,
canister_id: toNullable(canisterId),
derivation_path: derivationPath,
});
}
15 changes: 15 additions & 0 deletions packages/ic-management/src/types/ic-management.params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,18 @@ export interface ProvisionalTopUpCanisterParams {
canisterId: Principal;
amount: bigint;
}

export interface EcdsaCurve {
secp256k1: null;
}

export interface KeyId {
name: string;
curve: EcdsaCurve;
}

export interface EcdsaPublicKeyParams {
keyId: KeyId;
canisterId?: Principal;
derivationPath: Array<Uint8Array | number[]>;
}
5 changes: 5 additions & 0 deletions packages/ic-management/src/types/ic-management.responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ export type CanisterStatusResponse = ServiceResponse<
IcManagementService,
"canister_status"
>;

export type EcdsaPublicKeyResponse = {
public_key: Uint8Array | number[];
chain_code: Uint8Array | number[];
};