-
Notifications
You must be signed in to change notification settings - Fork 26
Signature traits #1080
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
Open
wysiwys
wants to merge
127
commits into
main
Choose a base branch
from
wysiwys/signature-trait
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Signature traits #1080
Changes from all commits
Commits
Show all changes
127 commits
Select commit
Hold shift + click to select a range
d1b362e
begin implementation of signature trait
wysiwys 7071732
start implementing signature trait in ed25519
wysiwys c217ba7
begin implementing `secrets::Signature` trait
wysiwys 108cbc8
blanket impl secrets trait from owned trait
wysiwys 05fe7b3
config for signature traits
wysiwys 9fcf035
keep original visibility
wysiwys 67209db
config as generic type
wysiwys d12a600
begin implementing for rsa
wysiwys 6c642cd
implement auxiliary data for sign and verify
wysiwys b71cc61
take public key as input
wysiwys 63f0580
allow destructured parameter names
wysiwys 35356d2
implement for all sha2 variants and structure modules
wysiwys 8fdd726
adapt test
wysiwys 4bad367
separate traits for auxiliary info
wysiwys 5e8a1fb
split out traits and use aliases
wysiwys cdc800c
formatting
wysiwys 05381db
split slice macro into two macros
wysiwys fd94741
use correct error type
wysiwys 31f50e0
feature flags
wysiwys 15b04e2
use existing sha2 type
wysiwys af370bf
rename traits
wysiwys 09fce1e
include context
wysiwys 1d19b7c
add missing NoAux trait
wysiwys 5b2c4f3
directly reference error type from `owned`
wysiwys 6b6888a
add comment about randomness
wysiwys 70a3f2c
formatting
wysiwys c933907
implement error conversions
wysiwys 326b7be
consistent types
wysiwys 5173d9f
extract traits
wysiwys a0b98eb
exclude signature trait implementation
wysiwys f7943ec
keep struct fields private
wysiwys 6dcbc71
fix build warning
wysiwys 550454a
finish rsa test
wysiwys e8f46bd
simplify error handling to return `LibraryError`
wysiwys 2e8555b
call underlying functions directly
wysiwys 5066665
inline `sign()` and `verify()`
wysiwys bcfcd80
add doc comments to errors
wysiwys 3b2f996
implement `core::fmt::Display` for errors
wysiwys 8b412cb
feature-gated `core::error::Error` implementation
wysiwys 23b49bd
use constants for lengths
wysiwys 42f2cde
use `()` instead of `&()` as placeholder
wysiwys d831972
pub use `SignError` only
wysiwys fb97b15
add doc comments
wysiwys 66b211d
consistent argument names
wysiwys 2acb941
reorganize rsa trait implementation
wysiwys 528ab46
formatting
wysiwys 088b69d
improve top-level doc comment and module naming
wysiwys a19711a
doc comments for `libcrux-ml-dsa`
wysiwys bb18d95
reorganize module
wysiwys a39aa22
improve doc comments
wysiwys 0b4f4b3
reorganize trait implementation module
wysiwys e81f5e4
add doc comments and improve generic type name
wysiwys 951818b
use `signing_key` and `verification_key` terms
wysiwys ca63e9e
improve generic type name
wysiwys 0fe954c
add valid parameter structs to doc comment
wysiwys 3d1ee8f
rename parameter
wysiwys 9065e77
more precise error variant
wysiwys 32dc6d1
remove TODOs about phrasing
wysiwys 77fbdba
remove unneeded TODO for error conversion
wysiwys 615920f
document `SignAux` and `VerifyAux` types
wysiwys 4240c95
remove TODO
wysiwys 81da552
remove unused lifetime
wysiwys e0ac944
move auxiliary types to associated types and comment out NoAux traits
wysiwys aa3621f
move associated types above doc comments
wysiwys e0195d4
use associated type for signing key
wysiwys 2f7909e
use struct for RSA signing key in trait implementation
wysiwys 7fa3fe7
blanket impl `secrets::Sign` trait for U8 array refs only
wysiwys 00e7030
implement secrets trait for RSA
wysiwys 1769370
doc comments
wysiwys e265945
remove unused NoAux traits
wysiwys 1210b9f
remove unneeded TODO
wysiwys 16f441c
add generic test
wysiwys 5af82aa
add verification_key as argument and rename test
wysiwys 1d9e173
pub use `arrayref::Verify` in owned and secrets traits modules
wysiwys a433c5d
add test for each trait
wysiwys 02132d3
formatting
wysiwys b1e7fc9
reexport error types
wysiwys 262e451
implement `owned` trait for clarity
wysiwys 89ed799
update comment
wysiwys 5cfd1fa
declassify implementation
wysiwys 261f32e
remove outdated comment
wysiwys 177c8c2
make rsa types generic over `u8`/`U8` and reorganize trait implementa…
wysiwys 5caff1f
move `Declassify` implementation to `impl_hacl.rs`
wysiwys 90c9fda
use type aliases and move generic key types to separate module
wysiwys 56dd87b
update tests
wysiwys 4ee67dc
ensure consistent struct layout
wysiwys bdd34d2
ensure struct layout is same as underlying array
wysiwys bc6c70b
directly link to modules
wysiwys 8307322
consistent doc comments and links
wysiwys a0b34d2
move libcrux_secrets trait implementations to `generic_keys` module
wysiwys 2798f56
formatting
wysiwys 31aec24
remove Signature trait implementation
wysiwys c1d7f67
use secrets in all traits, and remove dedicated secrets trait
wysiwys 61cb13c
update trait implementations
wysiwys 60958f4
remove `context` from traits API
wysiwys 27cc66a
update comments
wysiwys ff2a996
use array signing key type
wysiwys 1fca205
allow longer signature buffers and return number of bytes written
wysiwys 095164c
fix typo
wysiwys 49b628e
restore original rsa implementation
wysiwys 28ff2ab
combine traits
wysiwys 384401f
add context
wysiwys 6d7ebf6
begin implementing keygen
wysiwys a870aca
replace `std::marker::PhantomData` with `core::marker::PhantomData`
wysiwys 7a69374
improve doc comments and arguments
wysiwys 5cbd348
exclude signers for eurydice
wysiwys 2f5c4ed
implement `Sign` trait on standalone struct
wysiwys 2694838
remove generic `Signer` struct
wysiwys 2a529f3
update feature name
wysiwys 1ae6d54
use literal for context in `impl_context` macro
wysiwys 6982e57
formatting
wysiwys 68a9eb4
add key-centric API
wysiwys 57fd42f
add `libcrux-signature` crate
wysiwys f5e9a15
remove VerifyAux from traits
wysiwys 2dac988
update doctests
wysiwys 197f2d8
move `SignAux` type to `SignTypes`
wysiwys 8edd064
update generic test
wysiwys 3089f62
update secrets types
wysiwys b6d36fb
Add a whole lot of documentation and make secret independence checkin…
keks 1c65231
clean up errors, constrain context
keks e903a0c
hax: don't extract ml-dsa trait implementations
keks e736c85
Update libcrux-ml-dsa/Cargo.toml
wysiwys e4bbf46
don't use DeclassifyRefMut in keygen
wysiwys 4df177d
fix lints
wysiwys 21f0a64
formatting
wysiwys 7370be0
use imports to simplify error matching
wysiwys 8b06bfc
move error conversions to separate `From` implementations
wysiwys File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
[package] | ||
authors.workspace = true | ||
edition.workspace = true | ||
homepage.workspace = true | ||
license.workspace = true | ||
name = "libcrux-signature" | ||
readme.workspace = true | ||
repository.workspace = true | ||
version.workspace = true | ||
|
||
[dependencies] | ||
libcrux-ecdsa = { version = "0.0.3", path = "../../../ecdsa", optional = true } | ||
libcrux-ed25519 = { version = "0.0.3", path = "../../../ed25519", optional = true } | ||
libcrux-ml-dsa = { version = "0.0.3", path = "../../../libcrux-ml-dsa", optional = true } | ||
libcrux-traits = { version = "0.0.3", path = "../../../traits", optional = true } | ||
libcrux-secrets = { version = "0.0.3", path = "../../../secrets", optional = true } | ||
|
||
[features] | ||
default = ["ed25519", "ecdsa", "mldsa"] | ||
|
||
check-secret-independence = [ | ||
"libcrux-secrets/check-secret-independence", | ||
"libcrux-traits/check-secret-independence", | ||
"libcrux-ml-dsa/expose-secret-independence", | ||
"libcrux-ed25519/check-secret-independence", | ||
"libcrux-ecdsa/check-secret-independence", | ||
] | ||
|
||
ecdsa = ["dep:libcrux-ecdsa", "any"] | ||
ed25519 = ["dep:libcrux-ed25519", "any"] | ||
mldsa = ["dep:libcrux-ml-dsa", "any"] | ||
|
||
any = ["dep:libcrux-traits", "dep:libcrux-secrets"] | ||
|
||
[dev-dependencies] | ||
rand = "0.9" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
#[cfg(any(feature = "ecdsa", feature = "ed25519", feature = "mldsa"))] | ||
pub use libcrux_traits::signature::{ | ||
key_centric_owned::{KeyPair, SigningKey, VerificationKey}, | ||
key_centric_refs::{SigningKeyRef, VerificationKeyRef}, | ||
owned::Sign, | ||
}; | ||
|
||
#[cfg(feature = "ecdsa")] | ||
pub mod ecdsa { | ||
pub mod p256 { | ||
//! ```rust | ||
//! use libcrux_signature::ecdsa::p256::{P256Signer as P256, Sha2_512, Nonce}; | ||
//! use libcrux_signature::{Sign, KeyPair, SigningKey, VerificationKey}; | ||
//! | ||
//! // generate a new nonce | ||
//! use rand::TryRngCore; | ||
//! let mut rng = rand::rngs::OsRng; | ||
//! let nonce = Nonce::random(&mut rng.unwrap_mut()).unwrap(); | ||
//! | ||
//! // generate a new signature keypair from random bytes | ||
//! let KeyPair { signing_key, verification_key } = | ||
//! P256::<Sha2_512>::generate_key_pair(&mut rng.unwrap_mut()).unwrap(); | ||
//! | ||
//! // sign | ||
//! let signature = signing_key.sign(b"payload", &nonce).unwrap(); | ||
//! | ||
//! // verify | ||
//! verification_key.verify(b"payload", &signature).unwrap(); | ||
//! ``` | ||
pub use libcrux_ecdsa::signers::p256::{ | ||
Nonce, Sha2_256, Sha2_384, Sha2_512, Signer as P256Signer, | ||
}; | ||
} | ||
} | ||
|
||
#[cfg(feature = "ed25519")] | ||
pub mod ed25519 { | ||
//! ```rust | ||
//! use libcrux_signature::ed25519::Ed25519; | ||
//! use libcrux_signature::{Sign, KeyPair, SigningKey, VerificationKey}; | ||
//! | ||
//! use rand::TryRngCore; | ||
//! let mut rng = rand::rngs::OsRng; | ||
//! // generate a new signature keypair from random bytes | ||
//! let KeyPair { signing_key, verification_key } | ||
//! = Ed25519::generate_key_pair(&mut rng.unwrap_mut()).unwrap(); | ||
//! | ||
//! // sign | ||
//! let signature = signing_key.sign(b"payload", ()).unwrap(); | ||
//! | ||
//! // verify | ||
//! verification_key.verify(b"payload", &signature).unwrap(); | ||
//! ``` | ||
pub use libcrux_ed25519::signers::Signer as Ed25519; | ||
} | ||
|
||
#[cfg(feature = "mldsa")] | ||
pub mod mldsa { | ||
//! ```rust | ||
//! use libcrux_signature::mldsa::{Context, MlDsa44, impl_context}; | ||
//! use libcrux_signature::{Sign, KeyPair, SigningKey, VerificationKey}; | ||
//! | ||
//! // set application context | ||
//! impl_context!(AppContext, b"context"); | ||
|
||
//! use rand::TryRngCore; | ||
//! let mut rng = rand::rngs::OsRng; | ||
//! | ||
//! // generate a new signature keypair from random bytes | ||
//! let KeyPair { signing_key, verification_key } | ||
//! = MlDsa44::<AppContext>::generate_key_pair(&mut rng.unwrap_mut()).unwrap(); | ||
//! | ||
//! // sign | ||
//! let signature = signing_key.sign(b"payload", [2; 32]).unwrap(); | ||
//! | ||
//! // verify | ||
//! verification_key.verify(b"payload", &signature).unwrap(); | ||
//! ``` | ||
//! | ||
//! ```rust | ||
//! use libcrux_signature::mldsa::{Context, MlDsa44, impl_context}; | ||
//! use libcrux_signature::SigningKeyRef; | ||
//! | ||
//! // set application context | ||
//! impl_context!(AppContext, b"context"); | ||
//! | ||
//! // signing key from bytes | ||
//! let signing_key = SigningKeyRef::<MlDsa44<AppContext>>::from_bytes(&[1; 2560]).unwrap(); | ||
//! | ||
//! // sign with randomness | ||
//! signing_key.sign(b"payload", [2; 32]).unwrap(); | ||
//! | ||
//! ``` | ||
pub use libcrux_ml_dsa::signers::{ | ||
impl_context, Context, MlDsa44Signer as MlDsa44, MlDsa65Signer as MlDsa65, | ||
MlDsa87Signer as MlDsa87, | ||
}; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,34 @@ | ||
[package] | ||
name = "libcrux-ecdsa" | ||
description = "Formally verified ECDSA signature library" | ||
name = "libcrux-ecdsa" | ||
readme = "Readme.md" | ||
version = "0.0.3" | ||
|
||
authors.workspace = true | ||
license.workspace = true | ||
homepage.workspace = true | ||
edition.workspace = true | ||
homepage.workspace = true | ||
license.workspace = true | ||
repository.workspace = true | ||
|
||
[dependencies] | ||
libcrux-p256 = { version = "=0.0.3", path = "../p256", features = [ | ||
"expose-hacl", | ||
] } | ||
libcrux-sha2 = { version = "=0.0.3", path = "../sha2" } | ||
libcrux-secrets = { version = "=0.0.3", path = "../secrets" } | ||
libcrux-traits = { version = "=0.0.3", path = "../traits" } | ||
rand = { version = "0.9", optional = true, default-features = false } | ||
|
||
[features] | ||
default = ["rand", "std"] | ||
rand = ["dep:rand"] | ||
std = ["rand?/std"] | ||
check-secret-independence = [ | ||
"libcrux-secrets/check-secret-independence", | ||
"libcrux-traits/check-secret-independence", | ||
] | ||
|
||
[dev-dependencies] | ||
rand_core = { version = "0.9" , features = ["os_rng"] } | ||
rand_core = { version = "0.9", features = ["os_rng"] } | ||
serde = { version = "1.0.217", features = ["derive"] } | ||
serde_json = "1.0.138" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
pub mod signers { | ||
//! [`libcrux_traits::signature`] APIs. | ||
|
||
use libcrux_secrets::{DeclassifyRef, U8}; | ||
use libcrux_traits::signature::arrayref; | ||
|
||
const SIGNING_KEY_LEN: usize = 32; | ||
const VERIFICATION_KEY_LEN: usize = 64; | ||
const SIG_LEN: usize = 64; | ||
const RAND_KEYGEN_LEN: usize = 32; | ||
|
||
macro_rules! impl_signature_traits { | ||
( | ||
$alias:ident, | ||
$name:ty, | ||
$sign_fn:ident, | ||
$verify_fn:ident | ||
) => { | ||
#[doc = concat!( | ||
"ECDSA P256 signer specialized for ", | ||
stringify!($name), | ||
" hash algorithm.\n\n", | ||
"This type alias provides a convenient way to access ECDSA signing and verification\n", | ||
"operations using the ", | ||
stringify!($name), | ||
" hash function. It implements all signature traits from\n", | ||
"`libcrux_traits::signature` including arrayref, owned, slice, and key-centric APIs.\n\n", | ||
"# Hash Algorithm\n", | ||
"Uses ", | ||
stringify!($name), | ||
" for message hashing before signing.\n\n", | ||
"# Key Sizes\n", | ||
"- Signing keys: 32 bytes\n", | ||
"- Verification keys: 64 bytes\n", | ||
"- Signatures: 64 bytes" | ||
)] | ||
pub type $alias = Signer<$name>; | ||
|
||
/// The [`arrayref`](libcrux_traits::signature::arrayref) version of the Sign trait. | ||
impl arrayref::Sign<SIGNING_KEY_LEN, VERIFICATION_KEY_LEN, SIG_LEN, RAND_KEYGEN_LEN> for Signer<$name> { | ||
/// Sign a payload using a provided signing key and `nonce`. | ||
#[inline(always)] | ||
fn sign( | ||
payload: &[u8], | ||
signing_key: &[U8; SIGNING_KEY_LEN], | ||
signature: &mut [u8; SIG_LEN], | ||
nonce: &Nonce, | ||
) -> Result<(), arrayref::SignError> { | ||
let result = libcrux_p256::$sign_fn( | ||
signature, | ||
payload.len().try_into().map_err(|_| arrayref::SignError::InvalidArgument)?, | ||
payload, | ||
signing_key.declassify_ref(), | ||
&nonce.0, | ||
); | ||
if !result { | ||
return Err(arrayref::SignError::LibraryError); | ||
keks marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
Ok(()) | ||
} | ||
|
||
/// Verify a signature using a provided verification key. | ||
#[inline(always)] | ||
fn verify( | ||
payload: &[u8], | ||
verification_key: &[u8; VERIFICATION_KEY_LEN], | ||
signature: &[u8; SIG_LEN], | ||
) -> Result<(), arrayref::VerifyError> { | ||
let result = libcrux_p256::$verify_fn( | ||
payload.len().try_into().map_err(|_| arrayref::VerifyError::InvalidPayloadLength)?, | ||
payload, | ||
verification_key, | ||
<&[u8; 32]>::try_from(&signature[0..32]).unwrap(), | ||
<&[u8; 32]>::try_from(&signature[32..]).unwrap(), | ||
); | ||
if !result { | ||
return Err(arrayref::VerifyError::LibraryError); | ||
keks marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
Ok(()) | ||
} | ||
|
||
fn keygen_derand( | ||
signing_key: &mut [U8; SIGNING_KEY_LEN], | ||
verification_key: &mut [u8; VERIFICATION_KEY_LEN], | ||
randomness: &[U8; RAND_KEYGEN_LEN], | ||
) -> Result<(), arrayref::KeyGenError> { | ||
use libcrux_traits::ecdh::arrayref::*; | ||
|
||
libcrux_p256::P256::secret_to_public(verification_key, &randomness).map_err(|err| match err { | ||
libcrux_traits::ecdh::arrayref::SecretToPublicError::InvalidSecret => arrayref::KeyGenError::InvalidRandomness, | ||
libcrux_traits::ecdh::arrayref::SecretToPublicError::Unknown => arrayref::KeyGenError::LibraryError, | ||
|
||
})?; | ||
*signing_key = *randomness; | ||
Ok(()) | ||
|
||
} | ||
} | ||
|
||
libcrux_traits::impl_signature_slice_trait!( | ||
Signer<$name> => SIGNING_KEY_LEN, VERIFICATION_KEY_LEN, SIG_LEN, RAND_KEYGEN_LEN, &Nonce, nonce); | ||
|
||
// key centric APIs | ||
libcrux_traits::signature::key_centric_owned::impl_sign_types!( | ||
Signer<$name>, | ||
SIGNING_KEY_LEN, | ||
VERIFICATION_KEY_LEN, | ||
SIG_LEN, | ||
RAND_KEYGEN_LEN, | ||
&'a Nonce | ||
); | ||
}; | ||
} | ||
|
||
/// [`libcrux_traits::signature`] APIs for p256. | ||
pub mod p256 { | ||
use super::*; | ||
|
||
pub use crate::p256::Nonce; | ||
|
||
/// Generic ECDSA P256 signer parameterized by hash algorithm. | ||
/// | ||
/// This struct provides the foundation for ECDSA signing and verification operations | ||
/// using the P256 elliptic curve. It is parameterized by a hash algorithm type `Hash` | ||
/// to support different hash functions while maintaining type safety. | ||
/// | ||
/// # Type Parameter | ||
/// - `Hash`: The hash algorithm type (e.g., `Sha2_256`, `Sha2_384`, `Sha2_512`) | ||
/// | ||
/// # Usage | ||
/// This struct is typically not used directly. Instead, use the specialized type aliases: | ||
/// - [`Sha2_256Signer`] for SHA-256 hashing | ||
/// - [`Sha2_384Signer`] for SHA-384 hashing | ||
/// - [`Sha2_512Signer`] for SHA-512 hashing | ||
/// | ||
/// # Security | ||
/// - Requires a cryptographically secure nonce for each signing operation | ||
/// - The same nonce must never be reused with the same key and different messages | ||
/// - Verification is deterministic and does not require additional randomness | ||
pub struct Signer<Hash> { | ||
_marker: core::marker::PhantomData<Hash>, | ||
} | ||
pub use libcrux_sha2::{Sha256 as Sha2_256, Sha384 as Sha2_384, Sha512 as Sha2_512}; | ||
|
||
impl_signature_traits!( | ||
Sha2_256Signer, | ||
Sha2_256, | ||
ecdsa_sign_p256_sha2, | ||
ecdsa_verif_p256_sha2 | ||
); | ||
|
||
impl_signature_traits!( | ||
Sha2_384Signer, | ||
Sha2_384, | ||
ecdsa_sign_p256_sha384, | ||
ecdsa_verif_p256_sha384 | ||
); | ||
|
||
impl_signature_traits!( | ||
Sha2_512Signer, | ||
Sha2_512, | ||
ecdsa_sign_p256_sha512, | ||
ecdsa_verif_p256_sha512 | ||
); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lib comment up here would be nice with a little info and an example on how to use this.