From f7852d0024023da1ffbba7dcf0f54abfe3efe090 Mon Sep 17 00:00:00 2001 From: Wilfred Godfrey Date: Tue, 9 Jul 2019 11:13:42 +1200 Subject: [PATCH] Add sign & verify methods w/ signing context param. https://github.com/paritytech/schnorrkel-js/issues/12 --- Cargo.toml | 2 +- src/lib.rs | 63 +++++++++++++++++++++++++++++++++++++++++++++++++- src/wrapper.rs | 46 ++++++++++++++++++++++++++++++++---- 3 files changed, 104 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1a37e3d..19c4ce6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ crate-type = ["cdylib"] [dependencies] wasm-bindgen = { version = "0.2" } wasm-bindgen-test = { version = "0.2" } -schnorrkel = { version = "0.1", features = ["nightly"]} +schnorrkel = { version = "0.6.1", features = ["nightly"]} [dev-dependencies] hex-literal = "0.1.4" diff --git a/src/lib.rs b/src/lib.rs index 8a612df..4ff5598 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,7 +54,28 @@ pub fn sign(public: &[u8], private: &[u8], message: &[u8]) -> Vec { __sign(public, private, message).to_vec() } -/// Verify a message and its corresponding against a public key; +/// Sign a message with a given signing context +/// +/// The combination of both public and private key must be provided. +/// This is effectively equivalent to a keypair. +/// +/// * public: UIntArray with 32 element +/// * private: UIntArray with 64 element +/// * message: Arbitrary length UIntArray +/// * signing_ctx: Arbitrary length UIntArray +/// +/// * returned vector is the signature consisting of 64 bytes. +#[wasm_bindgen] +pub fn sign_with_ctx( + public: &[u8], + private: &[u8], + message: &[u8], + signing_ctx: &[u8], + ) -> Vec { + __sign_with_ctx(public, private, message, signing_ctx).to_vec() +} + +/// Verify a message and its corresponding signature against a public key; /// /// * signature: UIntArray with 64 element /// * message: Arbitrary length UIntArray @@ -64,6 +85,22 @@ pub fn verify(signature: &[u8], message: &[u8], pubkey: &[u8]) -> bool { __verify(signature, message, pubkey) } +/// Verify a message with a given signing context; +/// +/// * signature: UIntArray with 64 element +/// * message: Arbitrary length UIntArray +/// * pubkey: UIntArray with 32 element +/// * signing_ctx: UIntArray with 32 element +#[wasm_bindgen] +pub fn verify_with_ctx( + signature: &[u8], + message: &[u8], + pubkey: &[u8], + signing_ctx: &[u8] + ) -> bool { + __verify_with_ctx(signature, message, pubkey, signing_ctx) +} + /// Generate a secret key (aka. private key) from a seed phrase. /// /// * seed: UIntArray with 32 element @@ -129,6 +166,18 @@ pub mod tests { assert!(signature.len() == SIGNATURE_LENGTH); } + #[wasm_bindgen_test] + fn can_sign_message_with_ctx() { + let seed = generate_random_seed(); + let keypair = keypair_from_seed(seed.as_slice()); + let private = &keypair[0..SECRET_KEY_LENGTH]; + let public = &keypair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH]; + let message = b"this is a message"; + let ctx = b"my context"; + let signature = sign_with_ctx(public, private, message, ctx); + assert!(signature.len() == SIGNATURE_LENGTH); + } + #[wasm_bindgen_test] fn can_verify_message() { let seed = generate_random_seed(); @@ -140,6 +189,18 @@ pub mod tests { assert!(verify(&signature[..], message, public)); } + #[wasm_bindgen_test] + fn can_verify_message_with_ctx() { + let seed = generate_random_seed(); + let keypair = keypair_from_seed(seed.as_slice()); + let private = &keypair[0..SECRET_KEY_LENGTH]; + let public = &keypair[SECRET_KEY_LENGTH..KEYPAIR_LENGTH]; + let message = b"this is a message"; + let signing_ctx = b"a signing context"; + let signature = sign_with_ctx(public, private, message, signing_ctx); + assert!(verify_with_ctx(&signature[..], message, public, signing_ctx)); + } + #[wasm_bindgen_test] fn hard_derives_pair() { let cc = hex!("14416c6963650000000000000000000000000000000000000000000000000000"); // Alice diff --git a/src/wrapper.rs b/src/wrapper.rs index eeeae84..7c194ac 100644 --- a/src/wrapper.rs +++ b/src/wrapper.rs @@ -5,7 +5,7 @@ use schnorrkel::derive::{Derivation, ChainCode, CHAIN_CODE_LENGTH}; use schnorrkel::sign::{Signature,SIGNATURE_LENGTH}; // We must make sure that this is the same as declared in the substrate source code. -const SIGNING_CTX: &'static [u8] = b"substrate"; +const DEFAULT_SIGNING_CTX: &'static[u8] = b"substrate"; /// Private helper function. fn keypair_from_seed(seed: &[u8]) -> Keypair { @@ -67,7 +67,12 @@ pub fn __secret_from_seed(seed: &[u8]) -> [u8; SECRET_KEY_LENGTH] { s } -pub fn __verify(signature: &[u8], message: &[u8], pubkey: &[u8]) -> bool { +pub fn __verify_with_ctx( + signature: &[u8], + message: &[u8], + pubkey: &[u8], + signing_ctx: &[u8] + ) -> bool { let sig = match Signature::from_bytes(signature) { Ok(some_sig) => some_sig, Err(_) => return false @@ -76,10 +81,26 @@ pub fn __verify(signature: &[u8], message: &[u8], pubkey: &[u8]) -> bool { Ok(some_pk) => some_pk, Err(_) => return false }; - pk.verify_simple(SIGNING_CTX, message, &sig) + + pk.verify_simple(signing_ctx, message, &sig).is_ok() +} + +pub fn __verify(signature: &[u8], message: &[u8], pubkey: &[u8]) -> bool { + __verify_with_ctx( + signature, + message, + pubkey, + DEFAULT_SIGNING_CTX + ) } -pub fn __sign(public: &[u8], private: &[u8], message: &[u8]) -> [u8; SIGNATURE_LENGTH] { +pub fn __sign_with_ctx( + public: &[u8], + private: &[u8], + message: &[u8], + signing_ctx: &[u8] + ) -> [u8; SIGNATURE_LENGTH] { + // despite being a method of KeyPair, only the secret is used for signing. let secret = match SecretKey::from_bytes(private) { Ok(some_secret) => some_secret, @@ -91,6 +112,21 @@ pub fn __sign(public: &[u8], private: &[u8], message: &[u8]) -> [u8; SIGNATURE_L Err(_) => panic!("Provided public key is invalid.") }; - let context = signing_context(SIGNING_CTX); + let context = signing_context(signing_ctx); + secret.sign(context.bytes(message), &public).to_bytes() } + +pub fn __sign( + public: &[u8], + private: &[u8], + message: &[u8], + ) -> [u8; SIGNATURE_LENGTH] { + __sign_with_ctx( + public, + private, + message, + DEFAULT_SIGNING_CTX + ) +} +