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

Schnorrkel uprev #368

Merged
merged 12 commits into from
Aug 19, 2020
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
5 changes: 3 additions & 2 deletions Cargo.lock

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

8 changes: 4 additions & 4 deletions consensus/enclave/trusted/Cargo.lock

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

5 changes: 2 additions & 3 deletions crypto/sig/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@ edition = "2018"
mc-crypto-hashes = { path = "../hashes" }
mc-crypto-keys = { path = "../keys", default-features = false }

digest = { version = "0.8.1", default-features = false }
merlin = { version = "2.0", default-features = false }
rand_core = { version = "0.5", default-features = false }
rand_hc = "0.2"
# FIXME: Introduces *_rng methods that take an rng to bypass rand_hack that fails no_std builds
schnorrkel = { git = "https://github.com/sugargoat/schnorrkel", rev = "60eedb2d3e005539052e1a2aef864bc78323c66c", default-features = false}
schnorrkel = { version = "0.9", default-features = false }

[dev-dependencies]
mc-util-from-random = { path = "../../util/from-random" }
Expand Down
34 changes: 23 additions & 11 deletions crypto/sig/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,13 @@ and the nonce should be *pseudorandomly generated* from the message and the priv
If a PRF is used to compute the nonce, then the nonce is hard to distinguish from random even if
the messages that are signed are adversarially chosen.

For this generation, we can think of the private key
as a source of entropy for a *secret seed* to the PRF.
We note that when secret entropy like this is available,
then PRFs exist under weak assumptions, and do not require the "random oracle model" for hash functions.
For this generation, we can think of the private key as a source of entropy for a *secret seed* to the PRF.
We note that when secret entropy like this is available, then PRFs exist under weak assumptions, and do not require the "random oracle model" for hash functions.
(See for instance [Chapter 3.8 in Pass "A Course in Cryptography"](https://www.cs.cornell.edu/courses/cs4830/2010fa/lecnotes.pdf)
It is known that PRFs of this form exist if cryptographic Pseudorandom Generators (PRGs) exist. [Hastad, Impagliazzo, Levin and Luby famously showed](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.185.988)
that PRG's exist if and only if one-way functions exist. This is a necessary assumption for semantically-secure symmetric key cryptography.)

In the `sign` function in this crate, we take as an assumption that secret-prefix-Blake2b is a PRF.
In the `sign` function in this crate, we take as an assumption that a Merlin Transcript's [`challenge_bytes`](https://docs.rs/merlin/1.0.3/merlin/struct.Transcript.html#method.challenge_bytes) method is a PRF.

The [ed25519 manuscript]((http://ed25519.cr.yp.to/ed25519-20110926.pdf)) from 2011-09-26 has remarks in the section "pseudorandom generation of r", where
`r` is the nonce, which support this idea:
Expand All @@ -77,13 +75,27 @@ The [ed25519 manuscript]((http://ed25519.cr.yp.to/ed25519-20110926.pdf)) from 20
> a cipher such as AES, combined with standard PRF-stretching mechanisms to support a long input;
> but we prefer to reuse `H` to save area in hardware implementations.

We point out that Blake was one of the SHA-3 finalists, and was also explicitly designed to model a PRF.
We prefer Blake2b here because it reduces the number of different hash functions in our system overall.
We point out that Merlin Transcripts implement a subset of the STROBE protocol, which states in [STROBE Protocol Framework](https://strobe.sourceforge.io/),

Assuming the Blake2b has the secret-prefix-PRF property, we can say that the signatures created this way
are hard to distinguish from signatures created where the nonce is truly uniformly random, even if the messages
that are signed are adversarially chosen. So, if Schnorrkel is secure when the nonces are truly random and
the RNG is the OS-RNG, or, when the nonce is created using the mini-secret-key expansion, then this should also be secure.
> Strobe is based on SHA-3, or rather Keccak-f and cSHAKE (draft NIST SP 800-185).

We prefer using Merlin transcripts to produce the nonce here, instead of a cryptographic hash function. Schnorrkel already relies on Merlin -- it has several benefits, like automatic domain separation and framing. By using this here instead of a hash function like SHA3, we can reduce the total number of cryptographic assumptions underpinning this signature scheme.

When using Merlin for this, instead of assuming that a hash function has the secret-prefix-PRF property, we assume that the following pseudo-rust function does, for any particular merlin transcript, where the private_key argument is identified with the secret prefix:

```
fn produce_nonce(transcript, private_key, message) -> [u8; 32]
transcript.append_message("private", private_key);
transcript.append_message("message", message);
let mut nonce = [0u8; 32];
transcript.challenge_bytes("nonce", &mut nonce);
nonce
}
```

This assumption can be justified if we believe that the STROBE "PRF" operation functions as a PRF once STROBE has been keyed. We refer the reader to https://strobe.sourceforge.io/papers/strobe-20170130.pdf for a discussion of the usage and security properties of STROBE. For more discussion, see also the documentation around Merlin: https://merlin.cool/transcript/ops.html.

With this assumption in hand, we can say that signatures created this way are hard to distinguish from signatures created where the nonce is truly uniformly random, even if the messages that are signed are adversarially chosen. So, if Schnorrkel is secure when the nonces are truly random and the RNG is the OS-RNG, or, when the nonce is created using the mini-secret-key expansion, then this should also be secure.

Rng required by Schnorrkel
--------------------------
Expand Down
39 changes: 21 additions & 18 deletions crypto/sig/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@
//! and implements many handy traits for performing high-level cryptography operations,
//! and this crate provides a way to create signatures that is compatible with these key pairs.

use digest::Input;
use mc_crypto_hashes::Blake2b256;
use mc_crypto_keys::{RistrettoPrivate, RistrettoPublic};
use merlin::Transcript;
use rand_core::SeedableRng;
use rand_hc::Hc128Rng as FixedRng;
use schnorrkel::{signing_context, SecretKey};
use schnorrkel::{context::attach_rng, signing_context, SecretKey};
pub use schnorrkel::{Signature, SignatureError, SIGNATURE_LENGTH};

/// Create a deterministic Schnorrkel signature
Expand All @@ -25,16 +24,17 @@ pub use schnorrkel::{Signature, SignatureError, SIGNATURE_LENGTH};
/// Returns:
/// * A 64-byte Schnorrkel Signature object which can be converted to and from bytes using its API.
pub fn sign(context_tag: &[u8], private_key: &RistrettoPrivate, message: &[u8]) -> Signature {
// Nonce is hash( private_key || message )
// FIXME: We should probably hash the context_tag in as well.
// Or just use something like merlin instead of Blake2b256.
// In that case we make the assumption that Keccak, which underlies STROBE,
// is a strong Pseudorandom permutation, and that consequently Merlin with
// a partially secret input is a PRF.
let mut hasher = Blake2b256::new();
hasher.input(private_key.to_bytes());
hasher.input(message);
let nonce = hasher.result();
// Create a deterministic nonce using a merlin transcript. See this crate's README
// for a security statement.
let nonce = {
let mut transcript = Transcript::new(b"SigningNonce");
transcript.append_message(b"context", &context_tag);
transcript.append_message(b"private", &private_key.to_bytes());
transcript.append_message(b"message", &message);
let mut nonce = [0u8; 32];
transcript.challenge_bytes(b"nonce", &mut nonce);
nonce
};

// Construct a Schnorrkel SecretKey object from private_key and our nonce value
let mut secret_bytes = [0u8; 64];
Expand All @@ -43,11 +43,14 @@ pub fn sign(context_tag: &[u8], private_key: &RistrettoPrivate, message: &[u8])
let secret_key = SecretKey::from_bytes(&secret_bytes).unwrap();
let keypair = secret_key.to_keypair();

// Context provides domain separation for signature
let ctx = signing_context(context_tag);
// NOTE: The fog_authority_fingerprint_sig is deterministic due to using the above hash as the rng seed
let mut csprng: FixedRng = SeedableRng::from_seed(nonce.into());
keypair.sign_rng(ctx.bytes(message), &mut csprng)
// SigningContext provides domain separation for signature
let mut t = Transcript::new(b"SigningContext");
t.append_message(b"", context_tag);
t.append_message(b"sign-bytes", message);
// NOTE: The fog_authority_fingerprint_sig is deterministic due to using the above nonce as the rng seed
let csprng: FixedRng = SeedableRng::from_seed(nonce);
let transcript = attach_rng(t, csprng);
keypair.sign(transcript)
}

/// Verify a Schnorrkel signature
Expand Down
17 changes: 9 additions & 8 deletions util/url-encoding/src/payment_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ mod tests {

use core::{convert::TryFrom, str::FromStr};

use hex::FromHex;
use mc_account_keys::{AccountKey, RootEntropy, RootIdentity};
use mc_crypto_keys::RistrettoPrivate;
use mc_util_test_helper::{run_with_several_seeds, RngCore};
Expand All @@ -78,7 +79,8 @@ mod tests {
root_entropy: RootEntropy::from(&[0u8; 32]),
fog_report_url: "fog://example.com".to_owned(),
fog_report_id: Default::default(),
fog_authority_fingerprint: Default::default(),
fog_authority_fingerprint: Vec::from_hex("23e9dfabdaf74c69428ec0dfac15784eedc7466e")
.unwrap(),
};

let acct = AccountKey::from(&identity);
Expand All @@ -93,7 +95,7 @@ mod tests {
})
.unwrap();

assert_eq!(mob_url.as_ref(), "mob://example.com/9i_xwzoihbGu5hLthygfLGi7K1sPFDmhPkq3KPmO-2p4kBwRg06ELfa-mMEnlTUT4RYJXUEizCfYB7RRHLgeEWfP?s=QqLfvkgCM29apl9PBGhIag-XlF-qy_CF2_qb7znsWhFViPW0f5v-ggZnCm0vkK5aaWAfP4uxWb5lWUa8zBpNjT9A");
assert_eq!(mob_url.as_ref(), "mob://example.com/9i_xwzoihbGu5hLthygfLGi7K1sPFDmhPkq3KPmO-2p4kBwRg06ELfa-mMEnlTUT4RYJXUEizCfYB7RRHLgeEWfP?s=gg1381eECt9C0_0DMLgXuNgKWmNokd5LL9y8ylFkuzCKnF2p2znWQTGIjfq1ePaKxxRpPlmRQ3lPAY93JrOchCyk");

let payload2 = PaymentRequest::try_from(&mob_url).unwrap();

Expand Down Expand Up @@ -132,7 +134,7 @@ mod tests {
&RistrettoPrivate::try_from(&[1u8; 32]).unwrap(),
"fog://fog.mobilecoin.com".to_string(),
0.to_string(),
b"deadbeef".to_vec(),
Vec::from_hex("23e9dfabdaf74c69428ec0dfac15784eedc7466e").unwrap(),
);

let addr = acct.default_subaddress();
Expand All @@ -148,8 +150,7 @@ mod tests {
})
.unwrap();

assert_eq!(mob_url.as_ref(), "mob://fog.mobilecoin.com/oGbA6juTWhUdfL6qNMocAGN96wNiZpZegP0TUjKXHEM-GYmM50bLJVeL6NgftIumjt8nwYw7MjEnQT7hCw9bVUgh?a=777&m=2+baby+goats&s=-ry4OlNUCMW1o8tZ188x4I8ppwTPik7t5jRxALmGDhB6hbitNs5Wx5W9go-BPkyieM_NbFVAlP848faDVXEFjAm1#0");

assert_eq!(mob_url.as_ref(), "mob://fog.mobilecoin.com/oGbA6juTWhUdfL6qNMocAGN96wNiZpZegP0TUjKXHEM-GYmM50bLJVeL6NgftIumjt8nwYw7MjEnQT7hCw9bVUgh?a=777&m=2+baby+goats&s=ruXXc2snZySyUAlvbLuzbaTQB-ZathDvUo73-50U7i_nnbOIZ31UivYz9gaokRaiYB2JZfOQVY2tFl9ntPtwhFvg#0");
let payload2 = PaymentRequest::try_from(&mob_url).unwrap();

assert_eq!(payload, payload2);
Expand All @@ -163,7 +164,7 @@ mod tests {
&RistrettoPrivate::try_from(&[1u8; 32]).unwrap(),
"fog://fog.mobilecoin.com".to_string(),
0.to_string(),
b"deadbeef".to_vec(),
Vec::from_hex("23e9dfabdaf74c69428ec0dfac15784eedc7466e").unwrap(),
);

let addr = acct.default_subaddress();
Expand All @@ -179,7 +180,7 @@ mod tests {
})
.unwrap();

assert_eq!(mob_url.as_ref(), "mob://fog.mobilecoin.com/oGbA6juTWhUdfL6qNMocAGN96wNiZpZegP0TUjKXHEM-GYmM50bLJVeL6NgftIumjt8nwYw7MjEnQT7hCw9bVUgh?a=777&m=%D9%84%D8%B3%D9%84%D8%A7%D9%85+%D8%B9%D9%84%D9%8A%D9%83%D9%85&s=-ry4OlNUCMW1o8tZ188x4I8ppwTPik7t5jRxALmGDhB6hbitNs5Wx5W9go-BPkyieM_NbFVAlP848faDVXEFjAm1#0");
assert_eq!(mob_url.as_ref(), "mob://fog.mobilecoin.com/oGbA6juTWhUdfL6qNMocAGN96wNiZpZegP0TUjKXHEM-GYmM50bLJVeL6NgftIumjt8nwYw7MjEnQT7hCw9bVUgh?a=777&m=%D9%84%D8%B3%D9%84%D8%A7%D9%85+%D8%B9%D9%84%D9%8A%D9%83%D9%85&s=ruXXc2snZySyUAlvbLuzbaTQB-ZathDvUo73-50U7i_nnbOIZ31UivYz9gaokRaiYB2JZfOQVY2tFl9ntPtwhFvg#0");

let payload2 = PaymentRequest::try_from(&mob_url).unwrap();

Expand Down Expand Up @@ -207,7 +208,7 @@ mod tests {
&RistrettoPrivate::try_from(&[1u8; 32]).unwrap(),
"fog://fog.mobilecoin.com".to_string(),
0.to_string(),
b"deadbeef".to_vec(),
Vec::from_hex("23e9dfabdaf74c69428ec0dfac15784eedc7466e").unwrap(),
);

let addr = acct.default_subaddress();
Expand Down
8 changes: 4 additions & 4 deletions util/url-encoding/tests/comparison.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ fn test_url_encoding() {
};
let encoded = MobUrl::try_from(&payload).unwrap();
let encoded_str: &str = encoded.as_ref();
assert_eq!("mob://fog.mobilecoin.signal.org/rmiEqq-34E3Fbm3hwxaYJtPZzu9THCBkQaqJDeZwuXG8mf2yOhmGoZmnKTu3--ZCj--5MdTwwCib2p7Dn3KTCl6E?a=666&m=2+baby+goats&s=KovIno-JXUsQuTSmUj4MDowMENWBpAbrHcT61x72MWNc24hBmdiRlPtpuxSdju_eaMXKeSrLLHjP7VltAuI_hP1f", encoded_str);
assert_eq!("mob://fog.mobilecoin.signal.org/rmiEqq-34E3Fbm3hwxaYJtPZzu9THCBkQaqJDeZwuXG8mf2yOhmGoZmnKTu3--ZCj--5MdTwwCib2p7Dn3KTCl6E?a=666&m=2+baby+goats&s=kjgEUqPUmd_pDqy6bJZwb3HcbzIfJva1pEV7SqPkmzBMz_k7lvGNnvN6QZh6O6_qFdDQTKYSdrr3biJWP4vkgYHq", encoded_str);
assert_eq!(232, encoded_str.len());

let b58_payload = RequestPayload::new_v4(
Expand All @@ -79,7 +79,7 @@ fn test_url_encoding() {
)
.unwrap();
let b58_encoded = "mob:///".to_string() + &b58_payload.encode();
assert_eq!("mob:///CzpFtx52f77AfogondLHGH4ZnhraB4igZKptek36H2mUPmj3qtLCV4UWB8QaDUqro3xBoKb4rXDSBm2nxV6GNz6pNfG5nwrdG17pPACnuh1NNFxyyUyEL6ckUfUhEYvPXLAy3JZhWCyi6g1S5MQd4NvaPXcptK14T5X2NP1yQei4paCBty8JxM4sc8mJa34NXYSySTnqAR53qC2WzmVKWtfuAAQXZU2jPR1kxZ2tJCdhBtERcfzsjKUAwZZMAYfgP9", b58_encoded);
assert_eq!("mob:///oSVfMupaCebhsoAJAY3uDqk6zBSV19PpavGTHxt29tubB9YiG8aFXSj9h3f9DS9VBxnSGjy77cSWZPF6o2J6HojmAc7SpvSvzCLxwnHmFwXUXkc8PhtDyTZ749znMXGAfd2zvFVaidC6GoiWDCN1DnQBStF8JVBsXP6UChTnjfuEVmY3J1f742uWpAU6LChyff78uvLZNAEsRNKFj6pkVW5ZRLVJK7GhhRru83CxaLnuCNqriNDdQfZUuWpK4Xrtib", b58_encoded);
assert_eq!(265, b58_encoded.len());
}

Expand All @@ -92,7 +92,7 @@ fn test_url_encoding() {
};
let encoded = MobUrl::try_from(&payload).unwrap();
let encoded_str: &str = encoded.as_ref();
assert_eq!("mob://fog.diogenes.mobilecoin.com/krmSAg7MnM0fn-yTIjV6tHtRA7Zj2JRZ4pJ-_PcweTkAu7afknATa5hFwtc_Zvi8R6d36cnpMA0-inMbZHiqMRqp?a=666&m=2+baby+goats&s=SC9cs96Ry9z4Js_VXkC35IMnTjpQCtEujN8D-R15qTsJloN2pZ75BbSzGtQJ99kBt8mM2YBhlTW9wuCfzHU3gJmx", encoded_str);
assert_eq!("mob://fog.diogenes.mobilecoin.com/krmSAg7MnM0fn-yTIjV6tHtRA7Zj2JRZ4pJ-_PcweTkAu7afknATa5hFwtc_Zvi8R6d36cnpMA0-inMbZHiqMRqp?a=666&m=2+baby+goats&s=HJ9SIosiJMmcDi9OBap9L5SY6Bzasr9CIAGmSIq2rgdR4MMpwAgKVdUPc1YrEwDIQzqsa03e6Z5fPjJRrxWJjwM5", encoded_str);
assert_eq!(234, encoded_str.len());

let b58_payload = RequestPayload::new_v4(
Expand All @@ -106,7 +106,7 @@ fn test_url_encoding() {
)
.unwrap();
let b58_encoded = "mob:///".to_string() + &b58_payload.encode();
assert_eq!("mob:///8dUCXPapoK52Zvhdfb3YHpKJRDPKvXAJmeKjkAxXv7o4QDftDV2JPybwQXzzuU5pqqS3QJkGFnFVWzxDNdd86vEDm3HDdHSgjjX2b2dxW9PDP9Ly3ziqLsLvy1d9xpdVUGAo6gniDHbjNypcVXwyU7hQUmbuHK8YsfJkKz2DPj8GxT5dgMhNzgbmzenpoexERAc1NehdHpwi6e6Tro63i6ny7akE2911sxb8Ar12Lgk44Zsfvf43oRtQVmGGpWR5idGb1", b58_encoded);
assert_eq!("mob:///JQPG3B9e4BXi7FB5Y2BRBBvykBMRYG6JCBop8bzsrqh29Wq7Z9wqxaACrdpqrRuUPd2Wrj1RwuZnA29njmEZE3BMsuCuBqFHyTLzhKZCJL7R4obo5Kzvht5F6YaVKcVzx8sbGdsVwS1Ty7jCopiVsyQBeTLZ8tCHWn83YJVHeoFUqkRp9Pvk65fxzVjSRDT8VcMTDYQsccR8KVV9w1qwTTYqsifwFWga8kCHvHRc4qwcqvjNj3aFQxQUTbrHf6fAR9tZu", b58_encoded);
assert_eq!(268, b58_encoded.len());
}
}