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

feat: Add Schnorr signing API #518

Merged
merged 6 commits into from
Sep 17, 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
34 changes: 34 additions & 0 deletions examples/management_canister/src/caller/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,40 @@ mod ecdsa {
}
}

mod schnorr {
use super::*;
use ic_cdk::api::management_canister::schnorr::*;

#[update]
async fn execute_schnorr_methods() {
let key_id = SchnorrKeyId {
algorithm: SchnorrAlgorithm::Bip340secp256k1,
name: "dfx_test_key".to_string(),
};
let derivation_path = vec![];
let arg = SchnorrPublicKeyArgument {
canister_id: None,
derivation_path: derivation_path.clone(),
key_id: key_id.clone(),
};
let SchnorrPublicKeyResponse {
public_key,
chain_code,
} = schnorr_public_key(arg).await.unwrap().0;
assert_eq!(public_key.len(), 33);
assert_eq!(chain_code.len(), 32);

let message = "hello world".into();
let arg = SignWithSchnorrArgument {
message,
derivation_path,
key_id,
};
let SignWithSchnorrResponse { signature } = sign_with_schnorr(arg).await.unwrap().0;
assert_eq!(signature.len(), 64);
}
}

mod bitcoin {
use super::*;
use ic_cdk::api::{call::RejectionCode, management_canister::bitcoin::*};
Expand Down
7 changes: 7 additions & 0 deletions examples/management_canister/tests/basic.bats
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ teardown() {
assert_success
}

@test "schnorr methods succeed" {
dfx start --clean --background
dfx deploy
run dfx canister call caller execute_schnorr_methods
assert_success
}

@test "bitcoin methods succeed" {
bitcoind -regtest -daemonwait

Expand Down
8 changes: 7 additions & 1 deletion src/ic-cdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [unreleased]

* Add `AllowedViewers` to `LogVisibility` enum.
### Changed

- Add `AllowedViewers` variant to `LogVisibility` enum. (#512)

### Added

- Support Threshold Schnorr signing management canister API. (#518)

## [0.16.0] - 2024-08-27

Expand Down
4 changes: 2 additions & 2 deletions src/ic-cdk/src/api/management_canister/ecdsa/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! The ECDSA API.
//! Threshold ECDSA signing API.

use crate::api::call::{call, call_with_payment128, CallResult};
use candid::Principal;
Expand All @@ -23,7 +23,7 @@ pub async fn ecdsa_public_key(
///
/// This call requires cycles payment.
/// This method handles the cycles cost under the hood.
/// Check [Gas and cycles cost](https://internetcomputer.org/docs/current/developer-docs/gas-cost) for more details.
/// Check [Threshold signatures](https://internetcomputer.org/docs/current/references/t-sigs-how-it-works) for more details.
pub async fn sign_with_ecdsa(arg: SignWithEcdsaArgument) -> CallResult<(SignWithEcdsaResponse,)> {
call_with_payment128(
Principal::management_canister(),
Expand Down
1 change: 1 addition & 0 deletions src/ic-cdk/src/api/management_canister/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ pub mod ecdsa;
pub mod http_request;
pub mod main;
pub mod provisional;
pub mod schnorr;
43 changes: 43 additions & 0 deletions src/ic-cdk/src/api/management_canister/schnorr/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//! Threshold Schnorr signing API.

use crate::api::call::{call, call_with_payment128, CallResult};
use candid::Principal;

mod types;
pub use types::*;

// Source: https://internetcomputer.org/docs/current/references/t-sigs-how-it-works/#fees-for-the-t-schnorr-production-key
const SIGN_WITH_SCHNORR_FEE: u128 = 26_153_846_153;
lwshang marked this conversation as resolved.
Show resolved Hide resolved

/// Return a SEC1 encoded Schnorr public key for the given canister using the given derivation path.
///
/// See [IC method `schnorr_public_key`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-schnorr_public_key).
pub async fn schnorr_public_key(
arg: SchnorrPublicKeyArgument,
) -> CallResult<(SchnorrPublicKeyResponse,)> {
call(
Principal::management_canister(),
"schnorr_public_key",
(arg,),
)
.await
}

/// Return a new Schnorr signature of the given message that can be separately verified against a derived Schnorr public key.
///
/// See [IC method `sign_with_schnorr`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-sign_with_schnorr).
///
/// This call requires cycles payment.
/// This method handles the cycles cost under the hood.
/// Check [Threshold signatures](https://internetcomputer.org/docs/current/references/t-sigs-how-it-works) for more details.
pub async fn sign_with_schnorr(
arg: SignWithSchnorrArgument,
) -> CallResult<(SignWithSchnorrResponse,)> {
call_with_payment128(
Principal::management_canister(),
"sign_with_schnorr",
(arg,),
SIGN_WITH_SCHNORR_FEE,
)
.await
}
86 changes: 86 additions & 0 deletions src/ic-cdk/src/api/management_canister/schnorr/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use candid::CandidType;
use serde::{Deserialize, Serialize};

use super::super::main::CanisterId;

/// Argument Type of [schnorr_public_key](super::schnorr_public_key).
#[derive(
CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default,
)]
pub struct SchnorrPublicKeyArgument {
/// Canister id, default to the canister id of the caller if None.
pub canister_id: Option<CanisterId>,
/// A vector of variable length byte strings.
pub derivation_path: Vec<Vec<u8>>,
/// See [SchnorrKeyId].
pub key_id: SchnorrKeyId,
}

/// Response Type of [schnorr_public_key](super::schnorr_public_key).
#[derive(
CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default,
)]
pub struct SchnorrPublicKeyResponse {
/// An Schnorr public key encoded in SEC1 compressed form.
pub public_key: Vec<u8>,
/// Can be used to deterministically derive child keys of the public_key.
pub chain_code: Vec<u8>,
}

/// Argument Type of [sign_with_schnorr](super::sign_with_schnorr).
#[derive(
CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default,
)]
pub struct SignWithSchnorrArgument {
/// Message to be signed.
pub message: Vec<u8>,
/// A vector of variable length byte strings.
pub derivation_path: Vec<Vec<u8>>,
/// See [SchnorrKeyId].
pub key_id: SchnorrKeyId,
}

/// Response Type of [sign_with_schnorr](super::sign_with_schnorr).
#[derive(
CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default,
)]
pub struct SignWithSchnorrResponse {
/// The encoding of the signature depends on the key ID's algorithm.
pub signature: Vec<u8>,
}

/// Schnorr KeyId.
#[derive(
CandidType, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default,
)]
pub struct SchnorrKeyId {
/// See [SchnorrAlgorithm].
pub algorithm: SchnorrAlgorithm,
/// Name.
pub name: String,
}

/// Schnorr Algorithm.
#[derive(
CandidType,
Serialize,
Deserialize,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Clone,
Copy,
Default,
)]
pub enum SchnorrAlgorithm {
/// BIP-340 secp256k1.
#[serde(rename = "bip340secp256k1")]
#[default]
Bip340secp256k1,
/// ed25519.
#[serde(rename = "ed25519")]
Ed25519,
}