Skip to content

Commit

Permalink
feat: implement threshold Schnorr
Browse files Browse the repository at this point in the history
  • Loading branch information
zensh committed Jul 27, 2024
1 parent 5018e27 commit 5e2c1bc
Show file tree
Hide file tree
Showing 16 changed files with 427 additions and 96 deletions.
49 changes: 49 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ serde_bytes = "0.11"
serde_json = { version = "1", features = ["preserve_order"] }
structured-logger = "1"
tokio = { version = "1", features = ["full"] }
k256 = { version = "0.13", features = ["ecdsa"] }
k256 = { version = "0.13", features = ["ecdsa", "schnorr"] }
ed25519-dalek = "2"
x25519-dalek = { version = "2", features = ["static_secrets"] }
hex = "0.4"
Expand Down
2 changes: 2 additions & 0 deletions src/ic_cose_canister/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ once_cell = { workspace = true }
ic-cdk-timers = { workspace = true }
ic-stable-structures = { workspace = true }
ic-http-certification = { workspace = true }
ed25519-dalek = { workspace = true }
x25519-dalek = { workspace = true }
lazy_static = "1.4"
getrandom = { version = "0.2", features = ["custom"] }
ic_cose_types = { path = "../ic_cose_types", version = "0.1" }
ic-crypto-extended-bip32 = { git = "https://github.com/dfinity/ic/", rev = "d19fa446ab35780b2c6d8b82ea32d808cca558d5" }
ic-crypto-ed25519 = { git = "https://github.com/dfinity/ic/", rev = "d19fa446ab35780b2c6d8b82ea32d808cca558d5" }
icrc-ledger-types = { git = "https://github.com/dfinity/ic/", rev = "d19fa446ab35780b2c6d8b82ea32d808cca558d5" }
9 changes: 6 additions & 3 deletions src/ic_cose_canister/ic_cose_canister.did
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ type Result_6 = variant { Ok : vec NamespaceInfo; Err : text };
type Result_7 = variant { Ok : CreateSettingOutput; Err : text };
type Result_8 = variant { Ok : SettingInfo; Err : text };
type Result_9 = variant { Ok : SettingArchivedPayload; Err : text };
type SchnorrAlgorithm = variant { ed25519; bip340secp256k1 };
type SettingArchivedPayload = record {
dek : opt blob;
version : nat32;
Expand Down Expand Up @@ -150,16 +151,18 @@ service : (opt ChainArgs) -> {
namespace_remove_managers : (text, vec principal) -> (Result);
namespace_remove_users : (text, vec principal) -> (Result);
namespace_update_info : (UpdateNamespaceInput) -> (Result);
schnorr_public_key : (text, opt PublicKeyInput) -> (Result_4) query;
schnorr_sign : (text, SignInput) -> (Result_5);
schnorr_public_key : (SchnorrAlgorithm, opt PublicKeyInput) -> (
Result_4,
) query;
schnorr_sign : (SchnorrAlgorithm, SignInput) -> (Result_5);
setting_add_readers : (SettingPath, vec principal) -> (Result);
setting_create : (SettingPath, CreateSettingInput) -> (Result_7);
setting_get : (SettingPath) -> (Result_8) query;
setting_get_archived_payload : (SettingPath) -> (Result_9) query;
setting_get_info : (SettingPath) -> (Result_8) query;
setting_remove_readers : (SettingPath, vec principal) -> (Result);
setting_update_info : (SettingPath, UpdateSettingInfoInput) -> (Result_7);
sign_identity : (text, text, opt text) -> (Result_5);
sign_identity : (text, text, opt SchnorrAlgorithm) -> (Result_5);
state_get_info : () -> (Result_10) query;
update_setting_payload : (SettingPath, UpdateSettingPayloadInput) -> (
Result_7,
Expand Down
58 changes: 45 additions & 13 deletions src/ic_cose_canister/src/api_cose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ use ic_cose_types::{
cose_aes256_key, ecdh::ecdh_x25519, encrypt0::cose_encrypt0, format_error, mac3_256,
CborSerializable,
},
types::{CosePath, ECDHInput, ECDHOutput, PublicKeyInput, PublicKeyOutput, SignInput},
types::{
CosePath, ECDHInput, ECDHOutput, PublicKeyInput, PublicKeyOutput, SchnorrAlgorithm,
SignInput,
},
validate_key, MILLISECONDS,
};
use serde_bytes::ByteBuf;
Expand All @@ -18,10 +21,7 @@ fn ecdsa_public_key(input: Option<PublicKeyInput>) -> Result<PublicKeyOutput, St
None => store::state::with(|s| {
s.ecdsa_public_key
.as_ref()
.map(|k| PublicKeyOutput {
public_key: ByteBuf::from(k.public_key.clone()),
chain_code: ByteBuf::from(k.chain_code.clone()),
})
.cloned()
.ok_or_else(|| "failed to retrieve ECDSA public key".to_string())
}),
}
Expand All @@ -30,32 +30,64 @@ fn ecdsa_public_key(input: Option<PublicKeyInput>) -> Result<PublicKeyOutput, St
#[ic_cdk::update(guard = "is_authenticated")]
async fn ecdsa_sign(input: SignInput) -> Result<ByteBuf, String> {
let caller = ic_cdk::caller();
store::ns::ecdsa_sign(&caller, input.ns, input.derivation_path, input.message).await
store::ns::ecdsa_sign_with(&caller, input.ns, input.derivation_path, input.message).await
}

#[ic_cdk::query]
fn schnorr_public_key(
_algorithm: String,
_input: Option<PublicKeyInput>,
algorithm: SchnorrAlgorithm,
input: Option<PublicKeyInput>,
) -> Result<PublicKeyOutput, String> {
Err("not implemented".to_string())
let caller = ic_cdk::caller();
match input {
Some(input) => {
store::ns::schnorr_public_key(&caller, algorithm, input.ns, input.derivation_path)
}
None => store::state::with(|s| match algorithm {
SchnorrAlgorithm::Bip340Secp256k1 => s
.schnorr_secp256k1_public_key
.as_ref()
.cloned()
.ok_or_else(|| "failed to retrieve schnorr secp256k1 public key".to_string()),
SchnorrAlgorithm::Ed25519 => s
.schnorr_ed25519_public_key
.as_ref()
.cloned()
.ok_or_else(|| "failed to retrieve schnorr ed25519 public key".to_string()),
}),
}
}

#[ic_cdk::update(guard = "is_authenticated")]
async fn schnorr_sign(_algorithm: String, _input: SignInput) -> Result<ByteBuf, String> {
Err("not implemented".to_string())
async fn schnorr_sign(algorithm: SchnorrAlgorithm, input: SignInput) -> Result<ByteBuf, String> {
let caller = ic_cdk::caller();
store::ns::schnorr_sign_with(
&caller,
algorithm,
input.ns,
input.derivation_path,
input.message,
)
.await
}

#[ic_cdk::update(guard = "is_authenticated")]
async fn sign_identity(
namespace: String,
audience: String,
_algorithm: Option<String>,
algorithm: Option<SchnorrAlgorithm>,
) -> Result<ByteBuf, String> {
validate_key(&namespace)?;
let caller = ic_cdk::caller();
let now_ms = ic_cdk::api::time() / MILLISECONDS;
store::ns::ecdsa_sign_identity(&caller, namespace, audience, now_ms).await
store::ns::sign_identity(
&caller,
algorithm.unwrap_or(SchnorrAlgorithm::Ed25519),
namespace,
audience,
now_ms,
)
.await
}

#[ic_cdk::update(guard = "is_authenticated")]
Expand Down
29 changes: 16 additions & 13 deletions src/ic_cose_canister/src/ecdsa.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
use ic_cdk::api::management_canister::ecdsa;
use ic_cose_types::{format_error, types::PublicKeyOutput};
use ic_crypto_extended_bip32::{DerivationIndex, DerivationPath, ExtendedBip32DerivationOutput};

pub type ECDSAPublicKey = ecdsa::EcdsaPublicKeyResponse;
use serde_bytes::ByteBuf;

/// Returns a valid extended BIP-32 derivation path from an Account (Principal + subaccount)
pub fn derive_public_key(
ecdsa_public_key: &ECDSAPublicKey,
ecdsa_public_key: &PublicKeyOutput,
derivation_path: Vec<Vec<u8>>,
) -> ECDSAPublicKey {
) -> Result<PublicKeyOutput, String> {
let ExtendedBip32DerivationOutput {
derived_public_key,
derived_chain_code,
} = DerivationPath::new(derivation_path.into_iter().map(DerivationIndex).collect())
.public_key_derivation(&ecdsa_public_key.public_key, &ecdsa_public_key.chain_code)
.expect("bug: failed to derive an ECDSA public key from valid inputs");
ECDSAPublicKey {
public_key: derived_public_key,
chain_code: derived_chain_code,
}
.map_err(format_error)?;
Ok(PublicKeyOutput {
public_key: ByteBuf::from(derived_public_key),
chain_code: ByteBuf::from(derived_chain_code),
})
}

pub async fn sign_with(
pub async fn sign_with_ecdsa(
key_name: String,
derivation_path: Vec<Vec<u8>>,
message_hash: Vec<u8>,
Expand All @@ -45,10 +45,10 @@ pub async fn sign_with(
Ok(response.signature)
}

pub async fn public_key_with(
pub async fn ecdsa_public_key(
key_name: String,
derivation_path: Vec<Vec<u8>>,
) -> Result<ECDSAPublicKey, String> {
) -> Result<PublicKeyOutput, String> {
let args = ecdsa::EcdsaPublicKeyArgument {
canister_id: None,
derivation_path,
Expand All @@ -62,5 +62,8 @@ pub async fn public_key_with(
.await
.map_err(|err| format!("ecdsa_public_key failed {:?}", err))?;

Ok(response)
Ok(PublicKeyOutput {
public_key: ByteBuf::from(response.public_key),
chain_code: ByteBuf::from(response.chain_code),
})
}
1 change: 1 addition & 0 deletions src/ic_cose_canister/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod api_init;
mod api_namespace;
mod api_setting;
mod ecdsa;
mod schnorr;
mod store;

use api_init::ChainArgs;
Expand Down
Loading

0 comments on commit 5e2c1bc

Please sign in to comment.