iotaledger / crypto.rs Goto Github PK
View Code? Open in Web Editor NEWThe canonical source of cryptographic ground-truth for IOTA projects that use Rust.
Home Page: https://iota.org
The canonical source of cryptographic ground-truth for IOTA projects that use Rust.
Home Page: https://iota.org
Secret/public key wrapper guidelines are needed: naming convention, interoperability traits, security concerns.
Wrappers for asymmetric and secret keys are common (ed25519, x25519, slip10). Often they use different names and argument/result types to do the same task and implement (or not) different traits, this is inconvenient. Also, secret key bytes can be leaked via local variables which is discouraged.
This issue should introduce a guideline (maybe, a wiki page) and check the current implementations for conformance. A draft can be found here: wrappers-guidelines.
Yes.
Originally posted by nothingismagick December 11, 2020
Weβre using Discussions as a place to connect with other members of our community. We hope that you:
To get started, comment below with an introduction of yourself and tell us about what you do with this community.
As far as I observed in the code, stronghold currently support Ed25519 signing and verification in relation to BIP39 mnemonics.
Due to the use of Ed25519 across the board we also need facilities to securely generate Ed25519 key pairs, storing them at rest, sign messages and verify them.
Ed25519 has emerged in our discussion to be the selected standard we are gonna use pretty much for every signing purpose.
Write a list of what you want this feature to do.
No.
To implement DKG and distributed private key management for BLS threshold cryptography
No.
merged into the
aes
crate
Details | |
---|---|
Status | unmaintained |
Package | aesni |
Version | 0.10.0 |
URL | RustCrypto/block-ciphers#200 |
Date | 2021-04-29 |
The aesni
crate has been merged into the aes
crate. The new repository
location is at:
<https://github.com/RustCrypto/block-ciphers/tree/master/aes>
AES-NI is now autodetected at runtime on i686
/x86-64
platforms.
If AES-NI is not present, the aes
crate will fallback to a constant-time
portable software implementation.
To prevent this fallback (and have absence of AES-NI result in an illegal
instruction crash instead), continue to pass the same RUSTFLAGS which were
previously required for the aesni
crate to compile:
RUSTFLAGS=-Ctarget-feature=+aes,+ssse3
See advisory page for additional details.
A workflow that compiles the project on an architecture that actually doesn't have the standard library.
Would prevent adding standard symbols by mistake.
Similar to https://github.com/iotaledger/bee/blob/coordicide/.github/workflows/no_std.yml
Currently, the only supported version is BLAKE2b-256. This should be extended to also support the 160-bit version of BLAKE2b:
Blake2b256
.b2sum --length 160
The crate's main Error enum implements Display but the one on bip39.rs > wordlist doesn't.
It's a standard and simplifies error handling on consumers.
No.
BIP39Generate
in this project supports English and Japanese. The BIP39 list of language available has 8 more. Would be cool having support to the other languages, specially Portuguese.
See iotaledger/stronghold.rs#498
Yes.
Some dependencies are potentially out of date (like pbkdf2
). Currently we are running a cargo audit
via cron in our workflows. We should have a workflow that detects out of date dependencies and flags them for review / or alternatively use the renovate
bot.
This crate will soon enter stable maintenance, and as such it is not expected that developers will be continuously working on it.
process.exit(1)
if any truthy values persist after analysis.DEPS: up to date
/ DEPS: out of date
In anticipation of the forthcoming beta release, we will need to make sure that we have done a number of things:
lib.rs
#![warn(missing_docs)]
crypto.rs should provide a Cipher
trait to expose a consistent API and make it easier to use multiple algorithms with generic types.
This ensures all implementations have a consistent API and allows the following code:
use crate::ciphers::{aes::Aes256Gcm, chacha::XChaCha20Poly1305, traits::Cipher}
fn generic_encrypt<C: Cipher>(...) -> Result<<C as Cipher>::Tag> {
C::encrypt(...)
}
let tag = generic_encrypt::<Aes256Gcm>(...)?;
// OR
let tag = generic_encrypt::<XChaCha20Poly1305>(...)?;
Cipher
trait, available at crate::ciphers::traits::Cipher
Cipher
implementation for all relevant types in crate::ciphers
An example of what this trait might look like is below.
Implementations need to specify:
consts
)encrypt/decrypt
functions with explicitly sized arguments (where appropriate)The trait would also provide easier-to-use try_encrypt/try_decrypt
functions that accept rust slices as arguments.
Notes:
try_generic_array
is simply a length check/pointer cast - no data is being copieddecrypt
and try_decrypt
return the length of the plaintext. This is to accommodate implementations that may apply padding.fn pad(plaintext: &[u8]) -> usize;
)
0
)use aes::cipher::generic_array::{ArrayLength, GenericArray};
use core::convert::TryInto;
pub mod consts {
pub use aes::cipher::generic_array::typenum::*;
}
pub type Key<T> = GenericArray<u8, T>;
pub type Nonce<T> = GenericArray<u8, T>;
pub type Tag<T> = GenericArray<u8, T>;
pub trait Cipher {
type KeyLength: ArrayLength<u8>;
type NonceLength: ArrayLength<u8>;
type TagLength: ArrayLength<u8>;
type Error;
const NAME: &'static str;
fn encrypt(
key: &Key<Self::KeyLength>,
iv: &Nonce<Self::NonceLength>,
aad: &[u8],
ptx: &[u8],
ctx: &mut [u8],
tag: &mut Tag<Self::TagLength>,
) -> Result<(), Self::Error>;
fn decrypt(
key: &Key<Self::KeyLength>,
iv: &Nonce<Self::NonceLength>,
aad: &[u8],
tag: &Tag<Self::TagLength>,
ctx: &[u8],
ptx: &mut [u8],
) -> Result<usize, Self::Error>;
fn try_encrypt(
key: &[u8],
iv: &[u8],
aad: &[u8],
ptx: &[u8],
ctx: &mut [u8],
) -> crate::Result<Tag<Self::TagLength>> {
let key: &Key<Self::KeyLength> = try_generic_array(key)?;
let iv: &Nonce<Self::NonceLength> = try_generic_array(iv)?;
let mut tag: Tag<Self::TagLength> = Default::default();
Self::encrypt(key, iv, aad, ptx, ctx, &mut tag)
.map_err(|_| crate::Error::CipherError { alg: Self::NAME })?;
Ok(tag)
}
fn try_decrypt(
key: &[u8],
iv: &[u8],
aad: &[u8],
tag: &[u8],
ctx: &[u8],
ptx: &mut [u8],
) -> crate::Result<usize> {
let key: &Key<Self::KeyLength> = try_generic_array(key)?;
let iv: &Nonce<Self::NonceLength> = try_generic_array(iv)?;
let tag: &Tag<Self::TagLength> = try_generic_array(tag)?;
Self::decrypt(key, iv, aad, tag, ctx, ptx)
.map_err(|_| crate::Error::CipherError { alg: Self::NAME })
}
}
fn try_generic_array<T>(slice: &[u8]) -> crate::Result<&GenericArray<u8, T>>
where
T: ArrayLength<u8>,
{
if slice.len() == T::USIZE {
Ok(slice.into())
} else {
Err(crate::Error::BufferSize {
needs: T::USIZE,
has: slice.len(),
})
}
}
Yes
The current Cipher API is pretty complicated and inconsistent. The heavy use of the generic_array
library makes type conversion tricky from standard types such as Vectors
and Slices
. Also, when the cypher fails due to a type error, the error messages are not very descriptive; apparently it depends on the type. If you specify a slice without the appropriate length, in some cases you get an error describing that the length is wrong and in other cases you just get a generic panic.
Example (from stronghold):
let mut tag = [0u8; Self::TAG_LEN];
...
XChaCha20Poly1305::encrypt(
...
&mut tag
.try_into()?,
...
)?;
Despite the tag being a mutable slice of size 16 and the GenericArray
implementing a TryInto
trait for the conversion (&mut [u8; 16]
), the tag doesn't get updated by the encryption call. There is no compile time error for this and instead it becomes a runtime error. An appropriate implementation looks more like this:
let mut tag = vec![0u8; Self::TAG_LEN];
...
XChaCha20Poly1305::encrypt(
...
tag.as_mut_slice()
.try_into()?,
...
)?;
This becomes confusing because the nonce can be written like so without causing the same kind of errors:
let mut nonce = [0u8; Self::NONCE_LEN];
...
XChaCha20Poly1305::encrypt(
...
&nonce.try_into()?,
...
)?;
Also, the ordering of the parameters for the encryption and decryption functions are inconsistent. The Encryption function looks like this:
XChaCha20Poly1305::encrypt(
key,
nonce,
associated_data,
plaintext,
ciphertext,
tag,
)
Where as the decryption function looks like this:
XChaCha20Poly1305::decrypt(
key,
nonce,
associated_data,
tag,
ciphertext,
plaintext
)
The API would be much smoother if the position parameters stayed the same across the two different functions. The functions try_encrypt
and try_decrypt
also contain the same parameter order and the try_encrypt
function does not allow you to specify a tag.
The point of this feature is to make the overall cipher API much more user friendly. As it is right now, its unnecessarily troublesome and unpredictable. Documentation will help with some of this needless complexity, but the type conversion problems will still persist. Presumably the try
functions are meant to lessen this complexity, but the lack of a tag on try_encrypt
makes using it inefficient.
Write a list of what you want this feature to do.
tag
parameter on the try_encrypt
method (or make it clear in the docs that the try_encrypt
function returns a new tag).Yes
Continuing the discussion from #3 - crypto.rs should provide an alloc
feature that allows opting in to more convenient usage patterns (eg. returning Vec<T>
from a function) and to provide support for primitives requiring allocation (eg. RSA)
This improves usability of crypto.rs and is required to support certain algorithms (eg. RSA)
alloc
cargo feature to enable condition compilation with #[cfg(feature = "alloc")]
*_vec
functions where they make sense (eg. AES_256_GCM::encrypt_vec
)Maybe.
Currently, there is only hash
computing the BLAKE2b hash of variable output sizes. This might not be sufficient in some scenarios and there should be BLAKE2b-256 implementation of Digest
.
crate has been renamed to
cipher
Details | |
---|---|
Status | unmaintained |
Package | block-cipher |
Version | 0.8.0 |
URL | RustCrypto/traits#337 |
Date | 2020-10-15 |
This crate has been renamed from block-cipher
to cipher
.
The new repository location is at:
<https://github.com/RustCrypto/traits/tree/master/cipher>
See advisory page for additional details.
This is a tracking issue for the bindings needed for this library.
This is going to get merged at bee and then we can use it too.
@tensor-programming wrote and tested a pure rust implementation in the stronghold.rs repo, they can be used as a clean room implementation to compare the chosen implementation against.
The vast majority of users of crypto.rs as a rust library will expect to be able to pull its crates from crates.io and also to be able to publish their crate to crates.io - this however was proven NOT to work for stronghold when pulling in crypto.rs from git, i.e. stronghold could not publish to crates.io if it consumed a crate from any git source.
We should provide guidance for both types of users.
Line 16 in 752321d
str
and Line 195 in 752321d
Should the verify function return an error if there is a space added or should the mnemonic_to_seed function trim spaces? Also speaks anything against calling the verify function inside of mnemonic_to_seed
?
use crypto::{
keys::bip39::{mnemonic_to_seed, wordlist},
utils::rand,
};
fn main() {
let mut entropy = [0u8; 32];
rand::fill(&mut entropy).unwrap();
let mnemonic = wordlist::encode(&entropy, &wordlist::ENGLISH).unwrap();
let mut seed1 = [0u8; 64];
mnemonic_to_seed(&mnemonic, "", &mut seed1);
println!("seed {:?}", seed1);
// add space after mnemonic
let mnemonic_with_space = format!("{} ", mnemonic);
crypto::keys::bip39::wordlist::verify(
&mnemonic_with_space,
&crypto::keys::bip39::wordlist::ENGLISH,
)
.unwrap();
let mut seed2 = [0u8; 64];
mnemonic_to_seed(&mnemonic_with_space, "", &mut seed2);
println!("seed2 {:?}", seed2);
}
returns
seed1 [117, 243, 116, 246, 236, 156, 24, 14, 156, 200, 253, 204, 198, 202, 93, 73, 218, 90, 144, 218, 171, 118, 103, 134, 223, 233, 111, 3, 249, 194, 39, 111, 20, 138, 223, 95, 177, 16, 97, 85, 146, 32, 74, 73, 109, 52, 212, 83, 145, 199, 171, 231, 165, 26, 98, 187, 147, 181, 143, 212, 188, 113, 252, 9]
seed2 [143, 70, 253, 26, 35, 87, 146, 146, 203, 134, 198, 38, 253, 47, 231, 16, 251, 71, 22, 111, 152, 155, 144, 195, 196, 121, 232, 104, 20, 12, 235, 5, 55, 22, 182, 253, 135, 183, 123, 196, 29, 253, 63, 205, 194, 11, 219, 117, 4, 188, 126, 238, 27, 185, 159, 162, 132, 81, 48, 6, 249, 173, 170, 11]
Welcome. This Issue will be updated as we come up with the appropriate procedure.
Naming convention in ed25519 and x25519 modules is messy, some convenient methods/traits are not implemented, some functionality is missing.
In ed25519 there is no reason to specify compressed public key. Compressed representation is only used for serialization, uncompressed public key is not used. Why not just public key? Proposal: remove or deprecate names with compressed
word and introduce corresponding names without it.
Unify xxx_LENGTH
(ed25519::PUBLIC_KEY_LENGTH
) and xxx_LEN
(x25519::PUBLIC_KEY_LEN
). LEN
just looks ugly when other words are not shortened. Proposal: remove or deprecate xxx_LEN
and introduce corresponding xxx_LENGTH
.
There's no naming convention for conversions to/from bytes for PublicKey types (eg. fn from_compressed_bytes(bs: &[u8]) -> Result<ed25519::PublicKey>
). Proposal to implement the following methods and traits:
pub fn to_bytes(&self) -> [u8; PUBLIC_KEY_LENGTH]
pub fn as_slice(&self) -> &[u8]
pub fn from_bytes(bytes: [u8; PUBLIC_KEY_LENGTH]) -> Self
or pub fn try_from_bytes(bytes: [u8; PUBLIC_KEY_LENGTH]) -> Result<Self>
impl Into<[u8; PUBLIC_KEY_LENGTH]> for &PublicKey
impl AsRef<[u8]> for PublicKey
impl From<[u8; PUBLIC_KEY_LENGTH]> for PublicKey
or impl TryFrom<[u8; PUBLIC_KEY_LENGTH]> for PublicKey
What is the endianness of to/from bytes conversions? Is it a "network" byte order or "native"? RFC 8032 states "All values are coded as octet strings, and integers are coded using little-endian convention", so LE must be the "network" byte order when converting x25519::SecretKey, PublicKeys, ed25519::Signature and x25519::SharedSecret as bytes as they are essentially big integers internally. ed25519::SecretKey is represented as seed bytes and not as a scalar that is derived from the seed, so the byte order here is natural. The old implementation is mixed up: ed25519::SecretKey used to_le_bytes()
/from_le_bytes()
which is not correct, and x25519::SecretKey bytes is scalar bytes in LE byte order used to_bytes()
/from_bytes()
with le
infix missing. Also le
functions suggest there are be
variants which is confusing. Proposal: agree on "network" byte order which is LE (following RFC 8032), remove or deprecate to_le_bytes
/from_le_bytes
, use to_bytes()
/from_bytes()
.
Wrappers for PublicKey types do not currently implement common traits: Copy
, Clone
, PartialEq
, Eq
, Hash
, Debug
. Proposal: implement these traits. Rationale is trivial for each trait.
Generate SecretKey with prng. This allows SecretKeys to be deterministically generated from seed. Proposal: implement generate_with<R>(rng: &mut R)
constructor.
Allow the use of the same secret key for ed25519 and x25519. Rationale: this is helpful as it simplifies key management: instead of two key pairs for signature and DH implementations can use only one (ed25519::SecretKey,ed25519::PublicKey)
and convert it to x25519 keys when needed. Proposal: impl From<ed25519::SecretKey/PublicKey> for x25519::SecretKey/PublicKey
.
Why utils::rand::gen
s type is unrestricted? Proposal: Restrict to Sized + Copy
. Rationale: Sized is obviously required to get size of T at compile time. Copy is needed by types that are copyable with memmove, ie. they are simple and don't contain pointers.
Deprecate or just remove/rename/breaking change? If deprecate, then since which version? Proposal: deprecate old names since "1.0.0" version.
What should be the convention for conversions for Copy types: to_bytes(&self)
and impl From<&PublicKey> for [u8; 32]
vs to_bytes(self)
and impl From<PublicKey> for [u8; 32]
? Proposal: follow this clippy convention and use the latter syntax.
Write a list of what you want this feature to do.
Use this section to ask any questions that are related to the feature.
Yes.
Line 490 in 730b645
In iota-sdk we want to get it without a useless roundtrip from .bs()
No.
During a recent merge, we noticed that suddenly coverage went down in a part of the library that we hadn't touched. After some examination, we discovered that these two tests are flaky:
This test is flaky around 20% of the time:
test_wordlist_codec_error_detection
This one is flaky about 5% of the time:
test_wordlist_codec_different_data_different_encodings
To prove this, you can run:
$ while true; do; cargo test --features bip39,bip39-en,bip39-jp --test bip39; done
These two tests have something in common:
OsRng.fill_bytes(&mut data);
let ws = choose_wordlist();
let ms = wordlist::encode(&data, ws).unwrap();
Is there a particular case where the conversion would fail? Particularly, the current implementation implies there might be a case where decompressing the ed25519 public key as a CompressedEdwardsY
might fail. However, a comment also states that in the happy path of the conversion "pk
is a valid ed25519::PublicKey
". This is however already an invariant of the conversion (as we are converting from a ed25519::PublicKey
), making one think the conversion is infallible in practice:
match curve25519_dalek::edwards::CompressedEdwardsY(y_bytes).decompress() {
Some(decompressed_edwards) => {
// `pk` is a valid `ed25519::PublicKey` hence contains valid `EdwardsPoint`.
// x25519 uses Montgomery form, and `x25519::PublicKey` is just a `MontgomeryPoint`.
// `MontgomeryPoint` can be constructed from `EdwardsPoint` with `to_montgomery()` method.
// Can't construct `x25519::PublicKey` directly from `MontgomeryPoint`,
// do it via intermediate bytes.
Ok(PublicKey::from_bytes(decompressed_edwards.to_montgomery().to_bytes()))
}
None => Err(crate::error::Error::ConvertError {
from: "ed25519 public key",
to: "x25519 public key",
}),
}
If the conversion is in fact infallible, the trait should be From
instead of TryFrom
. Currently https://github.com/iotaledger/streams relies on this conversion being infallible. If it in fact isn't, I'd appreciate some clarification so that we can adjust our error handling.
Yes (upon confirmation)
The implementation of bip39 is questionable. From the first glance the following looks suspicious to me:
mnemonic_to_seed
modifies the input mnemonic (which is a secret) and doesn't clean up the local result (which is also secret). I'd advise against modifying secrets, it feels weird. Maybe there should be some sort of validation, or Mnemonic
type should encapsulate only valid mnemonics (with unnecessary spaces stripped and in NFKD form).salt
intermediate value is not cleared in mnemonic_to_seed
.Wordlist
accepts some "bad" and "incorrect" words and separators. Maybe, there should be a constructor for normalizing and checking words.data
in encode
should be called secret_entropy
or something to indicate its purpose: it should be handled with care and zeroized after use.encode
should return Mnemonic
, not just String
.CS
is not zeroized in encode
.decode
takes secret mnemonic as input ms
of type &str
and is modified (normalized to NFKD form). NFKD form should be validated/converted to in a Mnemonic
constructor, decode
should take a (valid) Mnemonic
as input and can't modify it (leak it into stack/heap memory). ms
local variable should be zeroized.separator
in decode
should already be normalized/valid in Wordlist
; no need to normalize it all the time.separator
is &str
? Why not char
? Is it correct to accept different spaces (tabs, space, invisible space, etc.) in one mnemonic?decode
there's no need for sub_whole_byte_case
function and multiple calls to it. Just compute the last argument once and run the function once.Originally posted by @semenov-vladyslav in #197 (comment)
merged into the
aes
crate
Details | |
---|---|
Status | unmaintained |
Package | aes-soft |
Version | 0.6.4 |
URL | RustCrypto/block-ciphers#200 |
Date | 2021-04-29 |
The aes-soft
crate has been merged into the aes
crate. The new repository
location is at:
<https://github.com/RustCrypto/block-ciphers/tree/master/aes>
AES-NI is now autodetected at runtime on i686
/x86-64
platforms.
If AES-NI is not present, the aes
crate will fallback to a constant-time
portable software implementation.
To force the use of a constant-time portable implementation on these platforms,
even if AES-NI is available, use the new force-soft
feature of the aes
crate to disable autodetection.
See advisory page for additional details.
crate has been renamed to
cipher
Details | |
---|---|
Status | unmaintained |
Package | stream-cipher |
Version | 0.7.1 |
URL | RustCrypto/traits#337 |
Date | 2020-10-15 |
This crate has been renamed from stream-cipher
to cipher
.
The new repository location is at:
<https://github.com/RustCrypto/traits/tree/master/cipher>
See advisory page for additional details.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.