diff --git a/packages/ic-management/README.md b/packages/ic-management/README.md index 7d06a9b8..330c2df7 100644 --- a/packages/ic-management/README.md +++ b/packages/ic-management/README.md @@ -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 @@ -72,6 +72,7 @@ const { status, memory_size, ...rest } = await canisterStatus(YOUR_CANISTER_ID); - [canisterStatus](#gear-canisterstatus) - [deleteCanister](#gear-deletecanister) - [provisionalCreateCanisterWithCycles](#gear-provisionalcreatecanisterwithcycles) +- [ecdsaPublicKey](#gear-ecdsapublickey) ##### :gear: create @@ -79,7 +80,7 @@ const { status, memory_size, ...rest } = await canisterStatus(YOUR_CANISTER_ID); | -------- | ---------------------------------------------------------------- | | `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 @@ -89,7 +90,7 @@ Create a new canister | ---------------- | ------------------------------------------------------------------------------------- | | `createCanister` | `({ settings, senderCanisterVersion, }?: CreateCanisterParams) => Promise` | -[: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 @@ -99,7 +100,7 @@ Update canister settings | ---------------- | ------------------------------------------------------------------------------------------- | | `updateSettings` | `({ canisterId, senderCanisterVersion, settings, }: UpdateSettingsParams) => Promise` | -[: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 @@ -109,7 +110,7 @@ Install code to a canister | ------------- | ----------------------------------------------------------------------------------------------------- | | `installCode` | `({ mode, canisterId, wasmModule, arg, senderCanisterVersion, }: InstallCodeParams) => Promise` | -[: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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -182,7 +183,7 @@ Uninstall code from a canister | --------------- | -------------------------------------------------------------------------------- | | `uninstallCode` | `({ canisterId, senderCanisterVersion, }: UninstallCodeParams) => Promise` | -[: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 @@ -192,7 +193,7 @@ Start a canister | --------------- | ------------------------------------------ | | `startCanister` | `(canisterId: Principal) => Promise` | -[: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 @@ -202,7 +203,7 @@ Stop a canister | -------------- | ------------------------------------------ | | `stopCanister` | `(canisterId: Principal) => Promise` | -[: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 @@ -212,7 +213,7 @@ Get canister details (memory size, status, etc.) | ---------------- | ------------------------------------------------------------ | | `canisterStatus` | `(canisterId: Principal) => Promise` | -[: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 @@ -222,7 +223,7 @@ Deletes a canister | ---------------- | ------------------------------------------ | | `deleteCanister` | `(canisterId: Principal) => Promise` | -[: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 @@ -232,7 +233,29 @@ Creates a canister. Only available on development instances. | ------------------------------------- | ------------------------------------------------------------------------------------------------------- | | `provisionalCreateCanisterWithCycles` | `({ settings, amount, canisterId, }?: ProvisionalCreateCanisterWithCyclesParams) => Promise` | -[: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` | + +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) diff --git a/packages/ic-management/src/ic-management.canister.spec.ts b/packages/ic-management/src/ic-management.canister.spec.ts index 70cf4e14..554d94cf 100644 --- a/packages/ic-management/src/ic-management.canister.spec.ts +++ b/packages/ic-management/src/ic-management.canister.spec.ts @@ -15,8 +15,10 @@ import { } from "./ic-management.mock"; import { CanisterSettings, + EcdsaPublicKeyParams, InstallCodeParams, InstallMode, + KeyId, LogVisibility, UnsupportedLogVisibility, toInstallMode, @@ -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(); @@ -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(); + 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(); + service.ecdsa_public_key.mockRejectedValue(error); + + const icManagement = await createICManagement(service); + + const call = () => icManagement.ecdsaPublicKey({ ...params }); + + expect(call).rejects.toThrowError(Error); + }); + }); }); diff --git a/packages/ic-management/src/ic-management.canister.ts b/packages/ic-management/src/ic-management.canister.ts index b10c19e1..d31961b5 100644 --- a/packages/ic-management/src/ic-management.canister.ts +++ b/packages/ic-management/src/ic-management.canister.ts @@ -17,6 +17,7 @@ import { toInstallMode, type ClearChunkStoreParams, type CreateCanisterParams, + type EcdsaPublicKeyParams, type InstallChunkedCodeParams, type InstallCodeParams, type ProvisionalCreateCanisterWithCyclesParams, @@ -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) { @@ -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} 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 => + this.service.ecdsa_public_key({ + key_id: keyId, + canister_id: toNullable(canisterId), + derivation_path: derivationPath, + }); } diff --git a/packages/ic-management/src/types/ic-management.params.ts b/packages/ic-management/src/types/ic-management.params.ts index f05a5399..6032c93d 100644 --- a/packages/ic-management/src/types/ic-management.params.ts +++ b/packages/ic-management/src/types/ic-management.params.ts @@ -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; +} diff --git a/packages/ic-management/src/types/ic-management.responses.ts b/packages/ic-management/src/types/ic-management.responses.ts index 37204781..9065bdf5 100644 --- a/packages/ic-management/src/types/ic-management.responses.ts +++ b/packages/ic-management/src/types/ic-management.responses.ts @@ -5,3 +5,8 @@ export type CanisterStatusResponse = ServiceResponse< IcManagementService, "canister_status" >; + +export type EcdsaPublicKeyResponse = { + public_key: Uint8Array | number[]; + chain_code: Uint8Array | number[]; +};