From 174bf18b57b2157950f90e7ad534ca32aa74392a Mon Sep 17 00:00:00 2001 From: Jernej Kos Date: Mon, 26 Sep 2022 19:45:18 +0200 Subject: [PATCH] runtime-sdk: Add in-memory signers for Ed25519 and Sr25519 --- Cargo.lock | 1 + contract-sdk/specs/token/oas20/Cargo.lock | 1 + runtime-sdk/Cargo.toml | 1 + runtime-sdk/src/crypto/signature/ed25519.rs | 61 +++++++++++++++++++ runtime-sdk/src/crypto/signature/sr25519.rs | 65 +++++++++++++++++++++ runtime-sdk/src/testing/keys.rs | 39 ++++++++----- tests/contracts/hello/Cargo.lock | 1 + 7 files changed, 156 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ddb6ef590..ccec78af45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2177,6 +2177,7 @@ dependencies = [ "byteorder", "curve25519-dalek 3.2.0", "digest 0.10.5", + "ed25519-dalek", "hex", "hmac", "impl-trait-for-tuples", diff --git a/contract-sdk/specs/token/oas20/Cargo.lock b/contract-sdk/specs/token/oas20/Cargo.lock index a24de5ad40..12a0ebc815 100644 --- a/contract-sdk/specs/token/oas20/Cargo.lock +++ b/contract-sdk/specs/token/oas20/Cargo.lock @@ -1800,6 +1800,7 @@ dependencies = [ "byteorder", "curve25519-dalek 3.2.0", "digest 0.10.5", + "ed25519-dalek", "hex", "hmac", "impl-trait-for-tuples", diff --git a/runtime-sdk/Cargo.toml b/runtime-sdk/Cargo.toml index 0768b613e7..5baec92dc5 100644 --- a/runtime-sdk/Cargo.toml +++ b/runtime-sdk/Cargo.toml @@ -14,6 +14,7 @@ oasis-runtime-sdk-macros = { path = "../runtime-sdk-macros", optional = true } # Third party. byteorder = "1.4.3" curve25519-dalek = "3.2.0" +ed25519-dalek = "1.0.1" digest = "0.10.3" hmac = "0.11.0" sha2 = "0.9.8" diff --git a/runtime-sdk/src/crypto/signature/ed25519.rs b/runtime-sdk/src/crypto/signature/ed25519.rs index f10eec7ccb..6ad04ce9de 100644 --- a/runtime-sdk/src/crypto/signature/ed25519.rs +++ b/runtime-sdk/src/crypto/signature/ed25519.rs @@ -1,5 +1,6 @@ //! Ed25519 signatures. use curve25519_dalek::edwards::CompressedEdwardsY; +use sha2::{Digest, Sha512Trunc256}; use oasis_core_runtime::common::crypto::signature::{ PublicKey as CorePublicKey, Signature as CoreSignature, @@ -94,3 +95,63 @@ impl From for CorePublicKey { pk.0 } } + +/// A memory-backed signer for Ed25519. +pub struct MemorySigner { + sk: ed25519_dalek::ExpandedSecretKey, +} + +impl MemorySigner { + /// Creates a new signer from a RFC 8032 seed. + pub fn new_from_seed(seed: &[u8]) -> Result { + let sk = ed25519_dalek::SecretKey::from_bytes(&seed).map_err(|_| Error::InvalidArgument)?; + let esk = ed25519_dalek::ExpandedSecretKey::from(&sk); + Ok(Self { sk: esk }) + } + + /// Generates a new signer deterministically from a test key name string. + pub fn new_test(name: &str) -> Self { + let mut digest = Sha512Trunc256::new(); + digest.update(name.as_bytes()); + let seed = digest.finalize(); + + Self::new_from_seed(&seed).unwrap() + } + + /// Public key corresponding to the signer. + pub fn public(&self) -> PublicKey { + let pk = ed25519_dalek::PublicKey::from(&self.sk); + PublicKey::from_bytes(pk.as_bytes()).unwrap() + } + + /// Generates a signature with the private key over the context and message. + pub fn context_sign(&self, context: &[u8], message: &[u8]) -> Result { + let mut digest = Sha512Trunc256::new(); + for byte in &[context, message] { + digest.update(byte); + } + let message = digest.finalize(); + + let pk = ed25519_dalek::PublicKey::from(&self.sk); + let signature = self.sk.sign(&message, &pk); + + Ok(signature.to_bytes().to_vec().into()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_memory_signer() { + let signer = MemorySigner::new_test("memory signer test"); + let ctx = b"oasis-core/test: context"; + let message = b"this is a message"; + let signature = signer.context_sign(ctx, message).unwrap(); + let pk = signer.public(); + + pk.verify(ctx, message, &signature) + .expect("signature should verify"); + } +} diff --git a/runtime-sdk/src/crypto/signature/sr25519.rs b/runtime-sdk/src/crypto/signature/sr25519.rs index 591b6423a5..ae08d1a472 100644 --- a/runtime-sdk/src/crypto/signature/sr25519.rs +++ b/runtime-sdk/src/crypto/signature/sr25519.rs @@ -69,3 +69,68 @@ impl From<&'static str> for PublicKey { PublicKey::from_bytes(&base64::decode(s).unwrap()).unwrap() } } + +/// A memory-backed signer for Sr25519. +pub struct MemorySigner { + kp: schnorrkel::Keypair, +} + +impl MemorySigner { + /// Creates a new signer from a seed. + pub fn new_from_seed(seed: &[u8]) -> Result { + let sk = + schnorrkel::MiniSecretKey::from_bytes(&seed).map_err(|_| Error::InvalidArgument)?; + let kp = sk.expand_to_keypair(schnorrkel::keys::ExpansionMode::Ed25519); + Ok(Self { kp }) + } + + /// Generates a new signer deterministically from a test key name string. + pub fn new_test(name: &str) -> Self { + let mut digest = Sha512Trunc256::new(); + digest.update(name.as_bytes()); + let seed = digest.finalize(); + + Self::new_from_seed(&seed).unwrap() + } + + /// Public key corresponding to the signer. + pub fn public(&self) -> PublicKey { + PublicKey::from_bytes(&self.kp.public.to_bytes()).unwrap() + } + + /// Generates a signature with the private key over the context and message. + pub fn context_sign(&self, context: &[u8], message: &[u8]) -> Result { + // Convert the context to a Sr25519 SigningContext. + let context = schnorrkel::context::SigningContext::new(context); + + // Generate a SigningTranscript from the context, and a pre-hash + // of the message. + // + // Note: This requires using Sha512Trunc256 instead of our hash, + // due to the need for FixedOutput. + let mut digest = Sha512Trunc256::new(); + digest.update(message); + let transcript = context.hash256(digest); + + let signature = self.kp.sign(transcript); + + Ok(signature.to_bytes().to_vec().into()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_memory_signer() { + let signer = MemorySigner::new_test("memory signer test"); + let ctx = b"oasis-core/test: context"; + let message = b"this is a message"; + let signature = signer.context_sign(ctx, message).unwrap(); + let pk = signer.public(); + + pk.verify(ctx, message, &signature) + .expect("signature should verify"); + } +} diff --git a/runtime-sdk/src/testing/keys.rs b/runtime-sdk/src/testing/keys.rs index 3d0723b306..33c17faf94 100644 --- a/runtime-sdk/src/testing/keys.rs +++ b/runtime-sdk/src/testing/keys.rs @@ -1,10 +1,8 @@ //! Module that contains known test keys. -// TODO: Should be derived from seeds once implemented in the Rust version. - /// Define an ed25519 test key. macro_rules! test_key_ed25519 { - ($doc:expr, $name:ident, $pk:expr) => { + ($doc:expr, $name:ident, $seed:expr) => { #[doc = " Test key "] #[doc=$doc] #[doc = "."] @@ -25,7 +23,14 @@ macro_rules! test_key_ed25519 { #[doc=$doc] #[doc = "."] pub fn pk_ed25519() -> ed25519::PublicKey { - $pk.into() + signer().public() + } + + #[doc = " Test Ed25519 signer "] + #[doc=$doc] + #[doc = "."] + pub fn signer() -> ed25519::MemorySigner { + ed25519::MemorySigner::new_test($seed) } #[doc = " Test address derivation information "] @@ -90,7 +95,7 @@ macro_rules! test_key_secp256k1 { /// Define an sr25519 test key. macro_rules! test_key_sr25519 { - ($doc:expr, $name:ident, $pk:expr) => { + ($doc:expr, $name:ident, $seed:expr) => { #[doc = " Test key "] #[doc=$doc] #[doc = "."] @@ -111,7 +116,14 @@ macro_rules! test_key_sr25519 { #[doc=$doc] #[doc = "."] pub fn pk_sr25519() -> sr25519::PublicKey { - $pk.into() + signer().public() + } + + #[doc = " Test Sr25519 signer "] + #[doc=$doc] + #[doc = "."] + pub fn signer() -> sr25519::MemorySigner { + sr25519::MemorySigner::new_test($seed) } #[doc = " Test address derivation information "] @@ -131,10 +143,11 @@ macro_rules! test_key_sr25519 { }; } -test_key_ed25519!("A", alice, "NcPzNW3YU2T+ugNUtUWtoQnRvbOL9dYSaBfbjHLP1pE="); -test_key_ed25519!("B", bob, "YgkEiVSR4SMQdfXw+ppuFYlqH0seutnCKk8KG8PyAx0="); -test_key_ed25519!("C", charlie, "8l1AQE+ETOPLckiNJ7NOD+AfZdaPw6wguir/vSF11YI="); -test_key_secp256k1!("D", dave, "AwF6GNjbybMzhi3XRj5R1oTiMMkO1nAwB7NZAlH1X4BE"); -test_key_secp256k1!("E", erin, "A9i0oSK+5sLSONbMYGmaFUA+Fb8zzqYEMUMspacIgO09"); -test_key_sr25519!("F", frank, "ljm9ZwdAldhlyWM2B4C+3gQZis+ceaxnt6QA4rOcP0k="); -test_key_sr25519!("G", grace, "0MHrNhjVTOFWmsOgpWcC3L8jIX3ZatKr0/yxMPtwckc="); +test_key_ed25519!("Alice", alice, "oasis-runtime-sdk/test-keys: alice"); +test_key_ed25519!("Bob", bob, "oasis-runtime-sdk/test-keys: bob"); +test_key_ed25519!("Charlie", charlie, "oasis-runtime-sdk/test-keys: charlie"); +test_key_ed25519!("Cory", cory, "ekiden test entity key seed"); +test_key_secp256k1!("Dave", dave, "AwF6GNjbybMzhi3XRj5R1oTiMMkO1nAwB7NZAlH1X4BE"); +test_key_secp256k1!("Erin", erin, "A9i0oSK+5sLSONbMYGmaFUA+Fb8zzqYEMUMspacIgO09"); +test_key_sr25519!("Frank", frank, "oasis-runtime-sdk/test-keys: frank"); +test_key_sr25519!("Grace", grace, "oasis-runtime-sdk/test-keys: grace"); diff --git a/tests/contracts/hello/Cargo.lock b/tests/contracts/hello/Cargo.lock index 53acc95f4d..e568c4b19a 100644 --- a/tests/contracts/hello/Cargo.lock +++ b/tests/contracts/hello/Cargo.lock @@ -1810,6 +1810,7 @@ dependencies = [ "byteorder", "curve25519-dalek 3.2.0", "digest 0.10.5", + "ed25519-dalek", "hex", "hmac", "impl-trait-for-tuples",