Add support for BLAKE2b-256 (#13204)

## Summary

You can upload these to PyPI and `warehouse` will validate them.
This commit is contained in:
Charlie Marsh 2025-04-29 18:39:41 -04:00 committed by GitHub
parent 62bca8c34c
commit 6bce5d712f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 120 additions and 20 deletions

11
Cargo.lock generated
View File

@ -382,6 +382,15 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
@ -970,6 +979,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]]
@ -5170,6 +5180,7 @@ dependencies = [
"astral-tokio-tar",
"async-compression",
"async_zip",
"blake2",
"fs-err 3.1.0",
"futures",
"md-5",

View File

@ -84,6 +84,7 @@ axoupdater = { version = "0.9.0", default-features = false }
backon = { version = "1.3.0" }
base64 = { version = "0.22.1" }
bitflags = { version = "2.6.0" }
blake2 = { version = "0.10.6" }
boxcar = { version = "0.2.5" }
bytecheck = { version = "0.8.0" }
cargo-util = { version = "0.2.14" }

View File

@ -305,6 +305,7 @@ mod tests {
),
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -361,6 +362,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -420,6 +422,7 @@ mod tests {
),
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -476,6 +479,7 @@ mod tests {
),
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -532,6 +536,7 @@ mod tests {
),
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -558,7 +563,7 @@ mod tests {
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r###"
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
base: BaseUrl(
Url {
@ -586,6 +591,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -595,7 +601,7 @@ mod tests {
},
],
}
"###);
"#);
}
#[test]
@ -612,7 +618,7 @@ mod tests {
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r###"
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
base: BaseUrl(
Url {
@ -640,6 +646,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -649,7 +656,7 @@ mod tests {
},
],
}
"###);
"#);
}
#[test]
@ -770,6 +777,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -796,7 +804,7 @@ mod tests {
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r###"
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
base: BaseUrl(
Url {
@ -824,6 +832,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -833,7 +842,7 @@ mod tests {
},
],
}
"###);
"#);
}
#[test]
@ -879,6 +888,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -935,6 +945,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -962,7 +973,7 @@ mod tests {
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap_err();
insta::assert_snapshot!(result, @"Unsupported hash algorithm (expected one of: `md5`, `sha256`, `sha384`, or `sha512`) on: `blake2=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61`");
insta::assert_snapshot!(result, @"Unsupported hash algorithm (expected one of: `md5`, `sha256`, `sha384`, `sha512`, or `blake2b`) on: `blake2=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61`");
}
#[test]
@ -980,7 +991,7 @@ mod tests {
let base = Url::parse("https://storage.googleapis.com/jax-releases/jax_cuda_releases.html")
.unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r###"
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
base: BaseUrl(
Url {
@ -1008,6 +1019,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -1023,6 +1035,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -1032,7 +1045,7 @@ mod tests {
},
],
}
"###);
"#);
}
/// Test for AWS Code Artifact
@ -1090,6 +1103,7 @@ mod tests {
),
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -1107,6 +1121,7 @@ mod tests {
),
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -1124,6 +1139,7 @@ mod tests {
),
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: Some(
Ok(
@ -1190,6 +1206,7 @@ mod tests {
),
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: Some(
Ok(
@ -1232,7 +1249,7 @@ mod tests {
let base = Url::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask/")
.unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r###"
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
base: BaseUrl(
Url {
@ -1264,6 +1281,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -1283,6 +1301,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -1302,6 +1321,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -1321,6 +1341,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -1340,6 +1361,7 @@ mod tests {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
requires_python: None,
size: None,
@ -1349,6 +1371,6 @@ mod tests {
},
],
}
"###);
"#);
}
}

View File

@ -23,6 +23,7 @@ uv-pypi-types = { workspace = true }
astral-tokio-tar = { workspace = true }
async-compression = { workspace = true, features = ["bzip2", "gzip", "zstd", "xz"] }
async_zip = { workspace = true }
blake2 = { workspace = true }
fs-err = { workspace = true, features = ["tokio"] }
futures = { workspace = true }
md-5 = { workspace = true }

View File

@ -1,7 +1,7 @@
use blake2::digest::consts::U32;
use sha2::Digest;
use std::pin::Pin;
use std::task::{Context, Poll};
use sha2::Digest;
use tokio::io::{AsyncReadExt, ReadBuf};
use uv_pypi_types::{HashAlgorithm, HashDigest};
@ -12,6 +12,7 @@ pub enum Hasher {
Sha256(sha2::Sha256),
Sha384(sha2::Sha384),
Sha512(sha2::Sha512),
Blake2b(blake2::Blake2b<U32>),
}
impl Hasher {
@ -21,6 +22,7 @@ impl Hasher {
Hasher::Sha256(hasher) => hasher.update(data),
Hasher::Sha384(hasher) => hasher.update(data),
Hasher::Sha512(hasher) => hasher.update(data),
Hasher::Blake2b(hasher) => hasher.update(data),
}
}
}
@ -32,6 +34,7 @@ impl From<HashAlgorithm> for Hasher {
HashAlgorithm::Sha256 => Hasher::Sha256(sha2::Sha256::new()),
HashAlgorithm::Sha384 => Hasher::Sha384(sha2::Sha384::new()),
HashAlgorithm::Sha512 => Hasher::Sha512(sha2::Sha512::new()),
HashAlgorithm::Blake2b => Hasher::Blake2b(blake2::Blake2b::new()),
}
}
}
@ -55,6 +58,10 @@ impl From<Hasher> for HashDigest {
algorithm: HashAlgorithm::Sha512,
digest: format!("{:x}", hasher.finalize()).into(),
},
Hasher::Blake2b(hasher) => HashDigest {
algorithm: HashAlgorithm::Blake2b,
digest: format!("{:x}", hasher.finalize()).into(),
},
}
}
}

View File

@ -195,6 +195,8 @@ pub struct Hashes {
pub sha384: Option<SmallString>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sha512: Option<SmallString>,
#[serde(skip_serializing_if = "Option::is_none")]
pub blake2b: Option<SmallString>,
}
impl Hashes {
@ -221,24 +223,35 @@ impl Hashes {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
}),
"sha256" => Ok(Hashes {
md5: None,
sha256: Some(SmallString::from(value)),
sha384: None,
sha512: None,
blake2b: None,
}),
"sha384" => Ok(Hashes {
md5: None,
sha256: None,
sha384: Some(SmallString::from(value)),
sha512: None,
blake2b: None,
}),
"sha512" => Ok(Hashes {
md5: None,
sha256: None,
sha384: None,
sha512: Some(SmallString::from(value)),
blake2b: None,
}),
"blake2b" => Ok(Hashes {
md5: None,
sha256: None,
sha384: None,
sha512: None,
blake2b: Some(SmallString::from(value)),
}),
_ => Err(HashError::UnsupportedHashAlgorithm(fragment.to_string())),
}
@ -270,24 +283,35 @@ impl FromStr for Hashes {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
}),
"sha256" => Ok(Hashes {
md5: None,
sha256: Some(SmallString::from(value)),
sha384: None,
sha512: None,
blake2b: None,
}),
"sha384" => Ok(Hashes {
md5: None,
sha256: None,
sha384: Some(SmallString::from(value)),
sha512: None,
blake2b: None,
}),
"sha512" => Ok(Hashes {
md5: None,
sha256: None,
sha384: None,
sha512: Some(SmallString::from(value)),
blake2b: None,
}),
"blake2b" => Ok(Hashes {
md5: None,
sha256: None,
sha384: None,
sha512: None,
blake2b: Some(SmallString::from(value)),
}),
_ => Err(HashError::UnsupportedHashAlgorithm(s.to_string())),
}
@ -315,6 +339,7 @@ pub enum HashAlgorithm {
Sha256,
Sha384,
Sha512,
Blake2b,
}
impl FromStr for HashAlgorithm {
@ -326,6 +351,7 @@ impl FromStr for HashAlgorithm {
"sha256" => Ok(Self::Sha256),
"sha384" => Ok(Self::Sha384),
"sha512" => Ok(Self::Sha512),
"blake2b" => Ok(Self::Blake2b),
_ => Err(HashError::UnsupportedHashAlgorithm(s.to_string())),
}
}
@ -338,6 +364,7 @@ impl std::fmt::Display for HashAlgorithm {
Self::Sha256 => write!(f, "sha256"),
Self::Sha384 => write!(f, "sha384"),
Self::Sha512 => write!(f, "sha512"),
Self::Blake2b => write!(f, "blake2b"),
}
}
}
@ -503,6 +530,7 @@ impl From<HashDigests> for Hashes {
HashAlgorithm::Sha256 => hashes.sha256 = Some(digest.digest),
HashAlgorithm::Sha384 => hashes.sha384 = Some(digest.digest),
HashAlgorithm::Sha512 => hashes.sha512 = Some(digest.digest),
HashAlgorithm::Blake2b => hashes.blake2b = Some(digest.digest),
}
}
hashes
@ -551,7 +579,7 @@ pub enum HashError {
InvalidFragment(String),
#[error(
"Unsupported hash algorithm (expected one of: `md5`, `sha256`, `sha384`, or `sha512`) on: `{0}`"
"Unsupported hash algorithm (expected one of: `md5`, `sha256`, `sha384`, `sha512`, or `blake2b`) on: `{0}`"
)]
UnsupportedHashAlgorithm(String),
}
@ -562,6 +590,21 @@ mod tests {
#[test]
fn parse_hashes() -> Result<(), HashError> {
let hashes: Hashes =
"blake2b:af4793213ee66ef8fae3b93b3e29206f6b251e65c97bd91d8e1c5596ef15af0a".parse()?;
assert_eq!(
hashes,
Hashes {
md5: None,
sha256: None,
sha384: None,
sha512: None,
blake2b: Some(
"af4793213ee66ef8fae3b93b3e29206f6b251e65c97bd91d8e1c5596ef15af0a".into()
),
}
);
let hashes: Hashes =
"sha512:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".parse()?;
assert_eq!(
@ -573,6 +616,7 @@ mod tests {
sha512: Some(
"40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".into()
),
blake2b: None,
}
);
@ -586,7 +630,8 @@ mod tests {
sha384: Some(
"40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".into()
),
sha512: None
sha512: None,
blake2b: None,
}
);
@ -600,7 +645,8 @@ mod tests {
"40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".into()
),
sha384: None,
sha512: None
sha512: None,
blake2b: None,
}
);
@ -614,7 +660,8 @@ mod tests {
),
sha256: None,
sha384: None,
sha512: None
sha512: None,
blake2b: None,
}
);

View File

@ -4490,24 +4490,35 @@ impl From<Hash> for Hashes {
sha256: None,
sha384: None,
sha512: None,
blake2b: None,
},
HashAlgorithm::Sha256 => Hashes {
md5: None,
sha256: Some(value.0.digest),
sha384: None,
sha512: None,
blake2b: None,
},
HashAlgorithm::Sha384 => Hashes {
md5: None,
sha256: None,
sha384: Some(value.0.digest),
sha512: None,
blake2b: None,
},
HashAlgorithm::Sha512 => Hashes {
md5: None,
sha256: None,
sha384: None,
sha512: Some(value.0.digest),
blake2b: None,
},
HashAlgorithm::Blake2b => Hashes {
md5: None,
sha256: None,
sha384: None,
sha512: None,
blake2b: Some(value.0.digest),
},
}
}

View File

@ -3440,14 +3440,14 @@ fn require_hashes_unknown_algorithm() -> Result<()> {
uv_snapshot!(context.pip_sync()
.arg("requirements.txt")
.arg("--require-hashes"), @r###"
.arg("--require-hashes"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Unsupported hash algorithm (expected one of: `md5`, `sha256`, `sha384`, or `sha512`) on: `foo`
"###
error: Unsupported hash algorithm (expected one of: `md5`, `sha256`, `sha384`, `sha512`, or `blake2b`) on: `foo`
"
);
Ok(())